diff options
Diffstat (limited to 'vcl/source/gdi/outdev3.cxx')
-rw-r--r-- | vcl/source/gdi/outdev3.cxx | 8022 |
1 files changed, 8022 insertions, 0 deletions
diff --git a/vcl/source/gdi/outdev3.cxx b/vcl/source/gdi/outdev3.cxx new file mode 100644 index 000000000000..bf1cc2728bf1 --- /dev/null +++ b/vcl/source/gdi/outdev3.cxx @@ -0,0 +1,8022 @@ +/************************************************************************* + * + * 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 <cstring> +#include <i18npool/mslangid.hxx> + +#ifndef _SV_SVSYS_HXX +#include <svsys.h> +#endif +#include <vcl/salgdi.hxx> +#include <vcl/sallayout.hxx> +#include <rtl/tencinfo.h> +#include <tools/debug.hxx> +#include <vcl/svdata.hxx> +#include <vcl/metric.hxx> +#include <vcl/impfont.hxx> +#include <vcl/metaact.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/outdata.hxx> +#include <vcl/outfont.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <tools/poly.hxx> +#include <vcl/outdev.h> +#include <vcl/virdev.hxx> +#include <vcl/print.hxx> +#include <vcl/event.hxx> +#include <vcl/window.h> +#include <vcl/window.hxx> +#include <vcl/svapp.hxx> +#include <vcl/bmpacc.hxx> +#include <unotools/fontcvt.hxx> +#include <vcl/outdev.hxx> +#include <vcl/edit.hxx> +#include <unotools/fontcfg.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/textlayout.hxx> +#ifndef _OSL_FILE_H +#include <osl/file.h> +#endif +#ifdef ENABLE_GRAPHITE +#include <vcl/graphite_features.hxx> +#endif +#ifdef USE_BUILTIN_RASTERIZER +#include <vcl/glyphcache.hxx> +#endif + +#include <vcl/unohelp.hxx> +#include <pdfwriter_impl.hxx> +#include <vcl/controllayout.hxx> +#include <rtl/logfile.hxx> + +#ifndef _COM_SUN_STAR_BEANS_PROPERTYVALUES_HDL_ +#include <com/sun/star/beans/PropertyValues.hdl> +#endif +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/linguistic2/XLinguServiceManager.hpp> + +#if defined UNX +#define GLYPH_FONT_HEIGHT 128 +#elif defined OS2 +#define GLYPH_FONT_HEIGHT 176 +#else +#define GLYPH_FONT_HEIGHT 256 +#endif + +#include <sal/alloca.h> + +#include <cmath> +#include <cstring> + +#include <memory> +#include <algorithm> + + +// ======================================================================= + +DBG_NAMEEX( OutputDevice ) +DBG_NAMEEX( Font ) + +// ======================================================================= + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::rtl; +using namespace ::vcl; +using namespace ::utl; + +// ======================================================================= + +#define TEXT_DRAW_ELLIPSIS (TEXT_DRAW_ENDELLIPSIS | TEXT_DRAW_PATHELLIPSIS | TEXT_DRAW_NEWSELLIPSIS) + +// ======================================================================= + +#define UNDERLINE_LAST UNDERLINE_BOLDWAVE +#define STRIKEOUT_LAST STRIKEOUT_X + +// ======================================================================= + +static void ImplRotatePos( long nOriginX, long nOriginY, long& rX, long& rY, + int nOrientation ) +{ + if ( (nOrientation >= 0) && !(nOrientation % 900) ) + { + if ( (nOrientation >= 3600) ) + nOrientation %= 3600; + + if ( nOrientation ) + { + rX -= nOriginX; + rY -= nOriginY; + + if ( nOrientation == 900 ) + { + long nTemp = rX; + rX = rY; + rY = -nTemp; + } + else if ( nOrientation == 1800 ) + { + rX = -rX; + rY = -rY; + } + else /* ( nOrientation == 2700 ) */ + { + long nTemp = rX; + rX = -rY; + rY = nTemp; + } + + rX += nOriginX; + rY += nOriginY; + } + } + else + { + double nRealOrientation = nOrientation*F_PI1800; + double nCos = cos( nRealOrientation ); + double nSin = sin( nRealOrientation ); + + // Translation... + long nX = rX-nOriginX; + long nY = rY-nOriginY; + + // Rotation... + rX = +((long)(nCos*nX + nSin*nY)) + nOriginX; + rY = -((long)(nSin*nX - nCos*nY)) + nOriginY; + } +} + +// ======================================================================= + +void OutputDevice::ImplUpdateFontData( bool bNewFontLists ) +{ + // the currently selected logical font is no longer needed + if ( mpFontEntry ) + { + mpFontCache->Release( mpFontEntry ); + mpFontEntry = NULL; + } + + mbInitFont = true; + mbNewFont = true; + + if ( bNewFontLists ) + { + if ( mpGetDevFontList ) + { + delete mpGetDevFontList; + mpGetDevFontList = NULL; + } + if ( mpGetDevSizeList ) + { + delete mpGetDevSizeList; + mpGetDevSizeList = NULL; + } + + // release all physically selected fonts on this device + if( ImplGetGraphics() ) + mpGraphics->ReleaseFonts(); + } + + if ( GetOutDevType() == OUTDEV_PRINTER || mpPDFWriter ) + { + ImplSVData* pSVData = ImplGetSVData(); + + if( mpFontCache && mpFontCache != pSVData->maGDIData.mpScreenFontCache ) + mpFontCache->Invalidate(); + + if ( bNewFontLists ) + { + // we need a graphics + if ( ImplGetGraphics() ) + { + if( mpFontList && mpFontList != pSVData->maGDIData.mpScreenFontList ) + mpFontList->Clear(); + + if( mpPDFWriter ) + { + if( mpFontList && mpFontList != pSVData->maGDIData.mpScreenFontList ) + delete mpFontList; + if( mpFontCache && mpFontCache != pSVData->maGDIData.mpScreenFontCache ) + delete mpFontCache; + mpFontList = mpPDFWriter->filterDevFontList( pSVData->maGDIData.mpScreenFontList ); + mpFontCache = new ImplFontCache( FALSE ); + } + else + { + if( mpOutDevData ) + mpOutDevData->maDevFontSubst.Clear(); + mpGraphics->GetDevFontList( mpFontList ); + mpGraphics->GetDevFontSubstList( this ); + } + } + } + } + + // also update child windows if needed + if ( GetOutDevType() == OUTDEV_WINDOW ) + { + Window* pChild = ((Window*)this)->mpWindowImpl->mpFirstChild; + while ( pChild ) + { + pChild->ImplUpdateFontData( true ); + pChild = pChild->mpWindowImpl->mpNext; + } + } +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplUpdateAllFontData( bool bNewFontLists ) +{ + ImplSVData* pSVData = ImplGetSVData(); + + // update all windows + Window* pFrame = pSVData->maWinData.mpFirstFrame; + while ( pFrame ) + { + pFrame->ImplUpdateFontData( bNewFontLists ); + + Window* pSysWin = pFrame->mpWindowImpl->mpFrameData->mpFirstOverlap; + while ( pSysWin ) + { + pSysWin->ImplUpdateFontData( bNewFontLists ); + pSysWin = pSysWin->mpWindowImpl->mpNextOverlap; + } + + pFrame = pFrame->mpWindowImpl->mpFrameData->mpNextFrame; + } + + // update all virtual devices + VirtualDevice* pVirDev = pSVData->maGDIData.mpFirstVirDev; + while ( pVirDev ) + { + pVirDev->ImplUpdateFontData( bNewFontLists ); + pVirDev = pVirDev->mpNext; + } + + // update all printers + Printer* pPrinter = pSVData->maGDIData.mpFirstPrinter; + while ( pPrinter ) + { + pPrinter->ImplUpdateFontData( bNewFontLists ); + pPrinter = pPrinter->mpNext; + } + + // clear global font lists to have them updated + pSVData->maGDIData.mpScreenFontCache->Invalidate(); + if ( bNewFontLists ) + { + pSVData->maGDIData.mpScreenFontList->Clear(); + pFrame = pSVData->maWinData.mpFirstFrame; + if ( pFrame ) + { + if ( pFrame->ImplGetGraphics() ) + // MT: Stupid typecast here and somewhere ((OutputDevice*)&aVDev)->, because bug in .NET2002 compiler. + ((OutputDevice*)pFrame)->mpGraphics->GetDevFontList( pFrame->mpWindowImpl->mpFrameData->mpFontList ); + } + } +} + +// ======================================================================= + + +// ======================================================================= + +// TODO: remove this method when the CWS-gfbfcfg dust has settled +void ImplFreeOutDevFontData() +{} + +// ======================================================================= + +void OutputDevice::BeginFontSubstitution() +{ + ImplSVData* pSVData = ImplGetSVData(); + pSVData->maGDIData.mbFontSubChanged = FALSE; +} + +// ----------------------------------------------------------------------- + +void OutputDevice::EndFontSubstitution() +{ + ImplSVData* pSVData = ImplGetSVData(); + if ( pSVData->maGDIData.mbFontSubChanged ) + { + ImplUpdateAllFontData( false ); + + Application* pApp = GetpApp(); + DataChangedEvent aDCEvt( DATACHANGED_FONTSUBSTITUTION ); + pApp->DataChanged( aDCEvt ); + pApp->NotifyAllWindows( aDCEvt ); + pSVData->maGDIData.mbFontSubChanged = FALSE; + } +} + +// ----------------------------------------------------------------------- + +void OutputDevice::AddFontSubstitute( const XubString& rFontName, + const XubString& rReplaceFontName, + USHORT nFlags ) +{ + ImplDirectFontSubstitution*& rpSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst; + if( !rpSubst ) + rpSubst = new ImplDirectFontSubstitution(); + rpSubst->AddFontSubstitute( rFontName, rReplaceFontName, nFlags ); + ImplGetSVData()->maGDIData.mbFontSubChanged = TRUE; +} + +// ----------------------------------------------------------------------- + +void ImplDirectFontSubstitution::AddFontSubstitute( const String& rFontName, + const String& rSubstFontName, USHORT nFlags ) +{ + maFontSubstList.push_back( ImplFontSubstEntry( rFontName, rSubstFontName, nFlags ) ); +} + +// ----------------------------------------------------------------------- + +ImplFontSubstEntry::ImplFontSubstEntry( const String& rFontName, + const String& rSubstFontName, USHORT nSubstFlags ) +: maName( rFontName ) +, maReplaceName( rSubstFontName ) +, mnFlags( nSubstFlags ) +{ + maSearchName = rFontName; + maSearchReplaceName = rSubstFontName; + GetEnglishSearchFontName( maSearchName ); + GetEnglishSearchFontName( maSearchReplaceName ); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplAddDevFontSubstitute( const XubString& rFontName, + const XubString& rReplaceFontName, + USHORT nFlags ) +{ + ImplInitOutDevData(); + mpOutDevData->maDevFontSubst.AddFontSubstitute( rFontName, rReplaceFontName, nFlags ); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::RemoveFontSubstitute( USHORT n ) +{ + ImplDirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst; + if( pSubst ) + pSubst->RemoveFontSubstitute( n ); +} + +// ----------------------------------------------------------------------- + +void ImplDirectFontSubstitution::RemoveFontSubstitute( int nIndex ) +{ + FontSubstList::iterator it = maFontSubstList.begin(); + for( int nCount = 0; (it != maFontSubstList.end()) && (nCount++ != nIndex); ++it ) ; + if( it != maFontSubstList.end() ) + maFontSubstList.erase( it ); +} + +// ----------------------------------------------------------------------- + +USHORT OutputDevice::GetFontSubstituteCount() +{ + const ImplDirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst; + if( !pSubst ) + return 0; + int nCount = pSubst->GetFontSubstituteCount(); + return (USHORT)nCount; +} + +// ----------------------------------------------------------------------- + +void OutputDevice::GetFontSubstitute( USHORT n, + XubString& rFontName, + XubString& rReplaceFontName, + USHORT& rFlags ) +{ + const ImplDirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst; + if( pSubst ) + pSubst->GetFontSubstitute( n, rFontName, rReplaceFontName, rFlags ); +} + +// ----------------------------------------------------------------------- + +bool ImplDirectFontSubstitution::GetFontSubstitute( int nIndex, + String& rFontName, String& rSubstFontName, USHORT& rFlags ) const +{ + FontSubstList::const_iterator it = maFontSubstList.begin(); + for( int nCount = 0; (it != maFontSubstList.end()) && (nCount++ != nIndex); ++it ) ; + if( it == maFontSubstList.end() ) + return false; + + const ImplFontSubstEntry* pEntry = &(*it); + rFontName = pEntry->maName; + rSubstFontName = pEntry->maReplaceName; + rFlags = pEntry->mnFlags; + return true; +} + +// ----------------------------------------------------------------------- + +bool ImplDirectFontSubstitution::FindFontSubstitute( String& rSubstName, + const String& rSearchName, USHORT nFlags ) const +{ + // TODO: get rid of O(N) searches + FontSubstList::const_iterator it = maFontSubstList.begin(); + for(; it != maFontSubstList.end(); ++it ) + { + const ImplFontSubstEntry& rEntry = *it; + if( ((rEntry.mnFlags & nFlags) || !nFlags) + && (rEntry.maSearchName == rSearchName) ) + { + rSubstName = rEntry.maSearchReplaceName; + return true; + } + } + + return false; +} + +// ----------------------------------------------------------------------- + +static void ImplFontSubstitute( String& rFontName, + USHORT nFlags, ImplDirectFontSubstitution* pDevSpecific ) +{ +#ifdef DBG_UTIL + String aTempName = rFontName; + GetEnglishSearchFontName( aTempName ); + DBG_ASSERT( aTempName == rFontName, "ImplFontSubstitute() called without a searchname" ); +#endif + + String aSubstFontName; + + // apply user-configurable font replacement (eg, from the list in Tools->Options) + const ImplDirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst; + if( pSubst && pSubst->FindFontSubstitute( aSubstFontName, rFontName, FONT_SUBSTITUTE_ALWAYS ) ) + { + rFontName = aSubstFontName; + return; + } + + // apply device specific font replacement (e.g. to use printer builtin fonts) + if( !pDevSpecific ) + return; + + if( pDevSpecific->FindFontSubstitute( aSubstFontName, rFontName, nFlags ) ) + { + rFontName = aSubstFontName; + return; + } +} + +// ----------------------------------------------------------------------- + +Font OutputDevice::GetDefaultFont( USHORT nType, LanguageType eLang, + ULONG nFlags, const OutputDevice* pOutDev ) +{ + DBG_TRACE( "OutputDevice::GetDefaultFont()" ); + + com::sun::star::lang::Locale aLocale; + if( eLang == LANGUAGE_NONE || eLang == LANGUAGE_SYSTEM || eLang == LANGUAGE_DONTKNOW ) + { + aLocale = Application::GetSettings().GetUILocale(); + } + else + { + MsLangId::convertLanguageToLocale( eLang, aLocale ); + } + + utl::DefaultFontConfiguration& rDefaults = *utl::DefaultFontConfiguration::get(); + String aSearch = rDefaults.getUserInterfaceFont( aLocale ); // ensure a fallback + String aDefault = rDefaults.getDefaultFont( aLocale, nType ); + if( aDefault.Len() ) + aSearch = aDefault; + + int nDefaultHeight = 12; + + Font aFont; + aFont.SetPitch( PITCH_VARIABLE ); + + switch ( nType ) + { + case DEFAULTFONT_SANS_UNICODE: + case DEFAULTFONT_UI_SANS: + aFont.SetFamily( FAMILY_SWISS ); + break; + + case DEFAULTFONT_SANS: + case DEFAULTFONT_LATIN_HEADING: + case DEFAULTFONT_LATIN_SPREADSHEET: + case DEFAULTFONT_LATIN_DISPLAY: + aFont.SetFamily( FAMILY_SWISS ); + break; + + case DEFAULTFONT_SERIF: + case DEFAULTFONT_LATIN_TEXT: + case DEFAULTFONT_LATIN_PRESENTATION: + aFont.SetFamily( FAMILY_ROMAN ); + break; + + case DEFAULTFONT_FIXED: + case DEFAULTFONT_LATIN_FIXED: + case DEFAULTFONT_UI_FIXED: + aFont.SetPitch( PITCH_FIXED ); + aFont.SetFamily( FAMILY_MODERN ); + break; + + case DEFAULTFONT_SYMBOL: + aFont.SetCharSet( RTL_TEXTENCODING_SYMBOL ); + break; + + case DEFAULTFONT_CJK_TEXT: + case DEFAULTFONT_CJK_PRESENTATION: + case DEFAULTFONT_CJK_SPREADSHEET: + case DEFAULTFONT_CJK_HEADING: + case DEFAULTFONT_CJK_DISPLAY: + aFont.SetFamily( FAMILY_SYSTEM ); // don't care, but don't use font subst config later... + break; + + case DEFAULTFONT_CTL_TEXT: + case DEFAULTFONT_CTL_PRESENTATION: + case DEFAULTFONT_CTL_SPREADSHEET: + case DEFAULTFONT_CTL_HEADING: + case DEFAULTFONT_CTL_DISPLAY: + aFont.SetFamily( FAMILY_SYSTEM ); // don't care, but don't use font subst config later... + break; + } + + if ( aSearch.Len() ) + { + aFont.SetHeight( nDefaultHeight ); + aFont.SetWeight( WEIGHT_NORMAL ); + aFont.SetLanguage( eLang ); + + if ( aFont.GetCharSet() == RTL_TEXTENCODING_DONTKNOW ) + aFont.SetCharSet( gsl_getSystemTextEncoding() ); + + // Should we only return available fonts on the given device + if ( pOutDev ) + { + pOutDev->ImplInitFontList(); + + // Search Font in the FontList + String aName; + String aSearchName; + xub_StrLen nIndex = 0; + do + { + aSearchName = GetNextFontToken( aSearch, nIndex ); + GetEnglishSearchFontName( aSearchName ); + ImplDevFontListData* pFontFamily = pOutDev->mpFontList->ImplFindBySearchName( aSearchName ); + if( pFontFamily ) + { + AddTokenFontName( aName, pFontFamily->GetFamilyName() ); + if( nFlags & DEFAULTFONT_FLAGS_ONLYONE ) + break; + } + } + while ( nIndex != STRING_NOTFOUND ); + aFont.SetName( aName ); + } + + // No Name, than set all names + if ( !aFont.GetName().Len() ) + { + xub_StrLen nIndex = 0; + if ( nFlags & DEFAULTFONT_FLAGS_ONLYONE ) + { + //aFont.SetName( aSearch.GetToken( 0, ';', nIndex ) ); + if( !pOutDev ) + pOutDev = (const OutputDevice *)ImplGetSVData()->mpDefaultWin; + if( !pOutDev ) + aFont.SetName( aSearch.GetToken( 0, ';', nIndex ) ); + else + { + pOutDev->ImplInitFontList(); + + aFont.SetName( aSearch ); + + // convert to pixel height + Size aSize = pOutDev->ImplLogicToDevicePixel( aFont.GetSize() ); + if ( !aSize.Height() ) + { + // use default pixel height only when logical height is zero + if ( aFont.GetHeight() ) + aSize.Height() = 1; + else + aSize.Height() = (12*pOutDev->mnDPIY)/72; + } + + // use default width only when logical width is zero + if( (0 == aSize.Width()) && (0 != aFont.GetSize().Width()) ) + aSize.Width() = 1; + + // get the name of the first available font + float fExactHeight = static_cast<float>(aSize.Height()); + ImplFontEntry* pEntry = pOutDev->mpFontCache->GetFontEntry( pOutDev->mpFontList, aFont, aSize, fExactHeight, pOutDev->mpOutDevData ? &pOutDev->mpOutDevData->maDevFontSubst : NULL ); + if( pEntry->maFontSelData.mpFontData ) + aFont.SetName( pEntry->maFontSelData.mpFontData->maName ); + else + aFont.SetName( pEntry->maFontSelData.maTargetName ); + } + } + else + aFont.SetName( aSearch ); + } + } + +#if OSL_DEBUG_LEVEL > 2 + const char* s = "DEFAULTFONT_SANS_UNKNOWN"; + switch ( nType ) + { + case DEFAULTFONT_SANS_UNICODE: s = "DEFAULTFONT_SANS_UNICODE"; break; + case DEFAULTFONT_UI_SANS: s = "DEFAULTFONT_UI_SANS"; break; + + case DEFAULTFONT_SANS: s = "DEFAULTFONT_SANS"; break; + case DEFAULTFONT_LATIN_HEADING: s = "DEFAULTFONT_LATIN_HEADING"; break; + case DEFAULTFONT_LATIN_SPREADSHEET: s = "DEFAULTFONT_LATIN_SPREADSHEET"; break; + case DEFAULTFONT_LATIN_DISPLAY: s = "DEFAULTFONT_LATIN_DISPLAY"; break; + + case DEFAULTFONT_SERIF: s = "DEFAULTFONT_SERIF"; break; + case DEFAULTFONT_LATIN_TEXT: s = "DEFAULTFONT_LATIN_TEXT"; break; + case DEFAULTFONT_LATIN_PRESENTATION: s = "DEFAULTFONT_LATIN_PRESENTATION"; break; + + case DEFAULTFONT_FIXED: s = "DEFAULTFONT_FIXED"; break; + case DEFAULTFONT_LATIN_FIXED: s = "DEFAULTFONT_LATIN_FIXED"; break; + case DEFAULTFONT_UI_FIXED: s = "DEFAULTFONT_UI_FIXED"; break; + + case DEFAULTFONT_SYMBOL: s = "DEFAULTFONT_SYMBOL"; break; + + case DEFAULTFONT_CJK_TEXT: s = "DEFAULTFONT_CJK_TEXT"; break; + case DEFAULTFONT_CJK_PRESENTATION: s = "DEFAULTFONT_CJK_PRESENTATION"; break; + case DEFAULTFONT_CJK_SPREADSHEET: s = "DEFAULTFONT_CJK_SPREADSHEET"; break; + case DEFAULTFONT_CJK_HEADING: s = "DEFAULTFONT_CJK_HEADING"; break; + case DEFAULTFONT_CJK_DISPLAY: s = "DEFAULTFONT_CJK_DISPLAY"; break; + + case DEFAULTFONT_CTL_TEXT: s = "DEFAULTFONT_CTL_TEXT"; break; + case DEFAULTFONT_CTL_PRESENTATION: s = "DEFAULTFONT_CTL_PRESENTATION"; break; + case DEFAULTFONT_CTL_SPREADSHEET: s = "DEFAULTFONT_CTL_SPREADSHEET"; break; + case DEFAULTFONT_CTL_HEADING: s = "DEFAULTFONT_CTL_HEADING"; break; + case DEFAULTFONT_CTL_DISPLAY: s = "DEFAULTFONT_CTL_DISPLAY"; break; + } + fprintf( stderr, " OutputDevice::GetDefaultFont() Type=\"%s\" lang=%d flags=%ld FontName=\"%s\"\n", + s, eLang, nFlags, + OUStringToOString( aFont.GetName(), RTL_TEXTENCODING_UTF8 ).getStr() + ); +#endif + + return aFont; +} + +// ======================================================================= + +static unsigned ImplIsCJKFont( const String& rFontName ) +{ + // Test, if Fontname includes CJK characters --> In this case we + // mention that it is a CJK font + const sal_Unicode* pStr = rFontName.GetBuffer(); + while ( *pStr ) + { + // japanese + if ( ((*pStr >= 0x3040) && (*pStr <= 0x30FF)) || + ((*pStr >= 0x3190) && (*pStr <= 0x319F)) ) + return IMPL_FONT_ATTR_CJK|IMPL_FONT_ATTR_CJK_JP; + + // korean + if ( ((*pStr >= 0xAC00) && (*pStr <= 0xD7AF)) || + ((*pStr >= 0x3130) && (*pStr <= 0x318F)) || + ((*pStr >= 0x1100) && (*pStr <= 0x11FF)) ) + return IMPL_FONT_ATTR_CJK|IMPL_FONT_ATTR_CJK_KR; + + // chinese + if ( ((*pStr >= 0x3400) && (*pStr <= 0x9FFF)) ) + return IMPL_FONT_ATTR_CJK|IMPL_FONT_ATTR_CJK_TC|IMPL_FONT_ATTR_CJK_SC; + + // cjk + if ( ((*pStr >= 0x3000) && (*pStr <= 0xD7AF)) || + ((*pStr >= 0xFF00) && (*pStr <= 0xFFEE)) ) + return IMPL_FONT_ATTR_CJK; + + pStr++; + } + + return 0; +} + +// ----------------------------------------------------------------------- + +static void ImplCalcType( ULONG& rType, FontWeight& rWeight, FontWidth& rWidth, + FontFamily eFamily, const FontNameAttr* pFontAttr ) +{ + if ( eFamily != FAMILY_DONTKNOW ) + { + if ( eFamily == FAMILY_SWISS ) + rType |= IMPL_FONT_ATTR_SANSSERIF; + else if ( eFamily == FAMILY_ROMAN ) + rType |= IMPL_FONT_ATTR_SERIF; + else if ( eFamily == FAMILY_SCRIPT ) + rType |= IMPL_FONT_ATTR_SCRIPT; + else if ( eFamily == FAMILY_MODERN ) + rType |= IMPL_FONT_ATTR_FIXED; + else if ( eFamily == FAMILY_DECORATIVE ) + rType |= IMPL_FONT_ATTR_DECORATIVE; + } + + if ( pFontAttr ) + { + rType |= pFontAttr->Type; + + if ( ((rWeight == WEIGHT_DONTKNOW) || (rWeight == WEIGHT_NORMAL)) && + (pFontAttr->Weight != WEIGHT_DONTKNOW) ) + rWeight = pFontAttr->Weight; + if ( ((rWidth == WIDTH_DONTKNOW) || (rWidth == WIDTH_NORMAL)) && + (pFontAttr->Width != WIDTH_DONTKNOW) ) + rWidth = pFontAttr->Width; + } +} + +// ======================================================================= + +ImplFontData::ImplFontData( const ImplDevFontAttributes& rDFA, int nMagic ) +: ImplDevFontAttributes( rDFA ), + mnWidth(0), + mnHeight(0), + mnMagic( nMagic ), + mpNext( NULL ) +{ + // StarSymbol is a unicode font, but it still deserves the symbol flag + if( !mbSymbolFlag ) + if( 0 == GetFamilyName().CompareIgnoreCaseToAscii( "starsymbol", 10) + || 0 == GetFamilyName().CompareIgnoreCaseToAscii( "opensymbol", 10) ) + mbSymbolFlag = true; +} + +// ----------------------------------------------------------------------- + +StringCompare ImplFontData::CompareIgnoreSize( const ImplFontData& rOther ) const +{ + // compare their width, weight, italic and style name + if( meWidthType < rOther.meWidthType ) + return COMPARE_LESS; + else if( meWidthType > rOther.meWidthType ) + return COMPARE_GREATER; + + if( meWeight < rOther.meWeight ) + return COMPARE_LESS; + else if( meWeight > rOther.meWeight ) + return COMPARE_GREATER; + + if( meItalic < rOther.meItalic ) + return COMPARE_LESS; + else if( meItalic > rOther.meItalic ) + return COMPARE_GREATER; + + StringCompare eCompare = maName.CompareTo( rOther.maName ); + return eCompare; +} + +// ----------------------------------------------------------------------- + +StringCompare ImplFontData::CompareWithSize( const ImplFontData& rOther ) const +{ + StringCompare eCompare = CompareIgnoreSize( rOther ); + if( eCompare != COMPARE_EQUAL ) + return eCompare; + + if( mnHeight < rOther.mnHeight ) + return COMPARE_LESS; + else if( mnHeight > rOther.mnHeight ) + return COMPARE_GREATER; + + if( mnWidth < rOther.mnWidth ) + return COMPARE_LESS; + else if( mnWidth > rOther.mnWidth ) + return COMPARE_GREATER; + + return COMPARE_EQUAL; +} + +// ----------------------------------------------------------------------- + +struct FontMatchStatus +{ +public: + int mnFaceMatch; + int mnHeightMatch; + int mnWidthMatch; + const xub_Unicode* mpTargetStyleName; +}; + +bool ImplFontData::IsBetterMatch( const ImplFontSelectData& rFSD, FontMatchStatus& rStatus ) const +{ + int nMatch = 0; + + const String& rFontName = rFSD.maTargetName; + if( (rFontName == maName) || rFontName.EqualsIgnoreCaseAscii( maName ) ) + nMatch += 240000; + + if( rStatus.mpTargetStyleName + && maStyleName.EqualsIgnoreCaseAscii( rStatus.mpTargetStyleName ) ) + nMatch += 120000; + + if( (rFSD.mePitch != PITCH_DONTKNOW) && (rFSD.mePitch == mePitch) ) + nMatch += 20000; + + // prefer NORMAL font width + // TODO: change when the upper layers can tell their width preference + if( meWidthType == WIDTH_NORMAL ) + nMatch += 400; + else if( (meWidthType == WIDTH_SEMI_EXPANDED) || (meWidthType == WIDTH_SEMI_CONDENSED) ) + nMatch += 300; + + if( rFSD.meWeight != WEIGHT_DONTKNOW ) + { + // if not bold prefer light fonts to bold fonts + int nReqWeight = (int)rFSD.meWeight; + if ( rFSD.meWeight > WEIGHT_MEDIUM ) + nReqWeight += 100; + + int nGivenWeight = (int)meWeight; + if( meWeight > WEIGHT_MEDIUM ) + nGivenWeight += 100; + + int nWeightDiff = nReqWeight - nGivenWeight; + + if ( nWeightDiff == 0 ) + nMatch += 1000; + else if ( nWeightDiff == +1 || nWeightDiff == -1 ) + nMatch += 700; + else if ( nWeightDiff < +50 && nWeightDiff > -50) + nMatch += 200; + } + else // requested weight == WEIGHT_DONTKNOW + { + // prefer NORMAL font weight + // TODO: change when the upper layers can tell their weight preference + if( meWeight == WEIGHT_NORMAL ) + nMatch += 450; + else if( meWeight == WEIGHT_MEDIUM ) + nMatch += 350; + else if( (meWeight == WEIGHT_SEMILIGHT) || (meWeight == WEIGHT_SEMIBOLD) ) + nMatch += 200; + else if( meWeight == WEIGHT_LIGHT ) + nMatch += 150; + } + + if ( rFSD.meItalic == ITALIC_NONE ) + { + if( meItalic == ITALIC_NONE ) + nMatch += 900; + } + else + { + if( rFSD.meItalic == meItalic ) + nMatch += 900; + else if( meItalic != ITALIC_NONE ) + nMatch += 600; + } + + if( mbDevice ) + nMatch += 1; + + int nHeightMatch = 0; + int nWidthMatch = 0; + + if( IsScalable() ) + { + if( rFSD.mnOrientation != 0 ) + nMatch += 80; + else if( rFSD.mnWidth != 0 ) + nMatch += 25; + else + nMatch += 5; + } + else + { + if( rFSD.mnHeight == mnHeight ) + { + nMatch += 20; + if( rFSD.mnWidth == mnWidth ) + nMatch += 10; + } + else + { + // for non-scalable fonts the size difference is very important + // prefer the smaller font face because of clipping/overlapping issues + int nHeightDiff = (rFSD.mnHeight - mnHeight) * 1000; + nHeightMatch = (nHeightDiff >= 0) ? -nHeightDiff : 100+nHeightDiff; + if( rFSD.mnHeight ) + nHeightMatch /= rFSD.mnHeight; + + if( (rFSD.mnWidth != 0) && (mnWidth != 0) && (rFSD.mnWidth != mnWidth) ) + { + int nWidthDiff = (rFSD.mnWidth - mnWidth) * 100; + nWidthMatch = (nWidthDiff >= 0) ? -nWidthDiff : +nWidthDiff; + } + } + } + + if( rStatus.mnFaceMatch > nMatch ) + return false; + else if( rStatus.mnFaceMatch < nMatch ) + { + rStatus.mnFaceMatch = nMatch; + rStatus.mnHeightMatch = nHeightMatch; + rStatus.mnWidthMatch = nWidthMatch; + return true; + } + + // when two fonts are still competing prefer the + // one with the best matching height + if( rStatus.mnHeightMatch > nHeightMatch ) + return false; + else if( rStatus.mnHeightMatch < nHeightMatch ) + { + rStatus.mnHeightMatch = nHeightMatch; + rStatus.mnWidthMatch = nWidthMatch; + return true; + } + + if( rStatus.mnWidthMatch > nWidthMatch ) + return false; + + rStatus.mnWidthMatch = nWidthMatch; + return true; +} + +// ======================================================================= + +ImplFontEntry::ImplFontEntry( const ImplFontSelectData& rFontSelData ) +: maFontSelData( rFontSelData ), + maMetric( rFontSelData ), + mpConversion( NULL ), + mnRefCount( 1 ), + mnSetFontFlags( 0 ), + mnOwnOrientation( 0 ), + mnOrientation( 0 ), + mbInit( false ), + mpUnicodeFallbackList( NULL ) +{ + maFontSelData.mpFontEntry = this; +} + +// ----------------------------------------------------------------------- + +ImplFontEntry::~ImplFontEntry() +{ + delete mpUnicodeFallbackList; +} + +// ----------------------------------------------------------------------- + +size_t ImplFontEntry::GFBCacheKey_Hash::operator()( const GFBCacheKey& rData ) const +{ + std::hash<sal_UCS4> a; + std::hash<int > b; + return a(rData.first) ^ b(rData.second); +} + +inline void ImplFontEntry::AddFallbackForUnicode( sal_UCS4 cChar, FontWeight eWeight, const String& rFontName ) +{ + if( !mpUnicodeFallbackList ) + mpUnicodeFallbackList = new UnicodeFallbackList; + (*mpUnicodeFallbackList)[ GFBCacheKey(cChar,eWeight) ] = rFontName; +} + +// ----------------------------------------------------------------------- + +inline bool ImplFontEntry::GetFallbackForUnicode( sal_UCS4 cChar, FontWeight eWeight, String* pFontName ) const +{ + if( !mpUnicodeFallbackList ) + return false; + + UnicodeFallbackList::const_iterator it = mpUnicodeFallbackList->find( GFBCacheKey(cChar,eWeight) ); + if( it == mpUnicodeFallbackList->end() ) + return false; + + *pFontName = (*it).second; + return true; +} + +// ----------------------------------------------------------------------- + +inline void ImplFontEntry::IgnoreFallbackForUnicode( sal_UCS4 cChar, FontWeight eWeight, const String& rFontName ) +{ +// DBG_ASSERT( mpUnicodeFallbackList, "ImplFontEntry::IgnoreFallbackForUnicode no list" ); + UnicodeFallbackList::iterator it = mpUnicodeFallbackList->find( GFBCacheKey(cChar,eWeight) ); +// DBG_ASSERT( it != mpUnicodeFallbackList->end(), "ImplFontEntry::IgnoreFallbackForUnicode no match" ); + if( it == mpUnicodeFallbackList->end() ) + return; + if( (*it).second == rFontName ) + mpUnicodeFallbackList->erase( it ); +} + +// ======================================================================= + +ImplDevFontListData::ImplDevFontListData( const String& rSearchName ) +: mpFirst( NULL ), + maSearchName( rSearchName ), + mnTypeFaces( 0 ), + mnMatchType( 0 ), + meMatchWeight( WEIGHT_DONTKNOW ), + meMatchWidth( WIDTH_DONTKNOW ), + meFamily( FAMILY_DONTKNOW ), + mePitch( PITCH_DONTKNOW ), + mnMinQuality( -1 ) +{} + +// ----------------------------------------------------------------------- + +ImplDevFontListData::~ImplDevFontListData() +{ + // release all physical font faces + while( mpFirst ) + { + ImplFontData* pFace = mpFirst; + mpFirst = pFace->GetNextFace(); + delete pFace; + } +} + +// ----------------------------------------------------------------------- + +bool ImplDevFontListData::AddFontFace( ImplFontData* pNewData ) +{ + pNewData->mpNext = NULL; + + if( !mpFirst ) + { + maName = pNewData->maName; + maMapNames = pNewData->maMapNames; + meFamily = pNewData->meFamily; + mePitch = pNewData->mePitch; + mnMinQuality = pNewData->mnQuality; + } + else + { + if( meFamily == FAMILY_DONTKNOW ) + meFamily = pNewData->meFamily; + if( mePitch == PITCH_DONTKNOW ) + mePitch = pNewData->mePitch; + if( mnMinQuality > pNewData->mnQuality ) + mnMinQuality = pNewData->mnQuality; + } + + // set attributes for attribute based font matching + if( pNewData->IsScalable() ) + mnTypeFaces |= IMPL_DEVFONT_SCALABLE; + + if( pNewData->IsSymbolFont() ) + mnTypeFaces |= IMPL_DEVFONT_SYMBOL; + else + mnTypeFaces |= IMPL_DEVFONT_NONESYMBOL; + + if( pNewData->meWeight != WEIGHT_DONTKNOW ) + { + if( pNewData->meWeight >= WEIGHT_SEMIBOLD ) + mnTypeFaces |= IMPL_DEVFONT_BOLD; + else if( pNewData->meWeight <= WEIGHT_SEMILIGHT ) + mnTypeFaces |= IMPL_DEVFONT_LIGHT; + else + mnTypeFaces |= IMPL_DEVFONT_NORMAL; + } + + if( pNewData->meItalic == ITALIC_NONE ) + mnTypeFaces |= IMPL_DEVFONT_NONEITALIC; + else if( (pNewData->meItalic == ITALIC_NORMAL) + || (pNewData->meItalic == ITALIC_OBLIQUE) ) + mnTypeFaces |= IMPL_DEVFONT_ITALIC; + + if( (meMatchWeight == WEIGHT_DONTKNOW) + || (meMatchWidth == WIDTH_DONTKNOW) + || (mnMatchType == 0) ) + { + // TODO: is it cheaper to calc matching attributes now or on demand? + // calc matching attributes if other entries are already initialized + + // MT: Perform05: Do lazy, quite expensive, not needed in start-up! + // const FontSubstConfiguration& rFontSubst = *FontSubstConfiguration::get(); + // InitMatchData( rFontSubst, maSearchName ); + // mbMatchData=true; // Somewhere else??? + } + + // reassign name (sharing saves memory) + if( pNewData->maName == maName ) + pNewData->maName = maName; + + // insert new physical font face into linked list + // TODO: get rid of linear search? + ImplFontData* pData; + ImplFontData** ppHere = &mpFirst; + for(; (pData=*ppHere) != NULL; ppHere=&pData->mpNext ) + { + StringCompare eComp = pNewData->CompareWithSize( *pData ); + if( eComp == COMPARE_GREATER ) + continue; + if( eComp == COMPARE_LESS ) + break; + + // ignore duplicate if its quality is worse + if( pNewData->mnQuality < pData->mnQuality ) + return false; + + // keep the device font if its quality is good enough + if( (pNewData->mnQuality == pData->mnQuality) + && (pData->mbDevice || !pNewData->mbDevice) ) + return false; + + // replace existing font face with a better one + pNewData->mpNext = pData->mpNext; + *ppHere = pNewData; + delete pData; + return true; + } + + // insert into or append to list of physical font faces + pNewData->mpNext = pData; + *ppHere = pNewData; + return true; +} + +// ----------------------------------------------------------------------- + +// get font attributes using the normalized font family name +void ImplDevFontListData::InitMatchData( const utl::FontSubstConfiguration& rFontSubst, + const String& rSearchName ) +{ + String aShortName; + // get font attributes from the decorated font name + rFontSubst.getMapName( rSearchName, aShortName, maMatchFamilyName, + meMatchWeight, meMatchWidth, mnMatchType ); + const FontNameAttr* pFontAttr = rFontSubst.getSubstInfo( rSearchName ); + // eventually use the stripped name + if( !pFontAttr ) + if( aShortName != rSearchName ) + pFontAttr = rFontSubst.getSubstInfo( aShortName ); + ImplCalcType( mnMatchType, meMatchWeight, meMatchWidth, meFamily, pFontAttr ); + mnMatchType |= ImplIsCJKFont( maName ); +} + +// ----------------------------------------------------------------------- + +ImplFontData* ImplDevFontListData::FindBestFontFace( const ImplFontSelectData& rFSD ) const +{ + if( !mpFirst ) + return NULL; + if( !mpFirst->GetNextFace() ) + return mpFirst; + + // FontName+StyleName should map to FamilyName+StyleName + const String& rSearchName = rFSD.maTargetName; + const xub_Unicode* pTargetStyleName = NULL; + if( (rSearchName.Len() > maSearchName.Len()) + && rSearchName.Equals( maSearchName, 0, maSearchName.Len() ) ) + pTargetStyleName = rSearchName.GetBuffer() + maSearchName.Len() + 1; + + // linear search, TODO: improve? + ImplFontData* pFontFace = mpFirst; + ImplFontData* pBestFontFace = pFontFace; + FontMatchStatus aFontMatchStatus = {0,0,0, pTargetStyleName}; + for(; pFontFace; pFontFace = pFontFace->GetNextFace() ) + if( pFontFace->IsBetterMatch( rFSD, aFontMatchStatus ) ) + pBestFontFace = pFontFace; + + return pBestFontFace; +} + +// ----------------------------------------------------------------------- + +// update device font list with unique font faces, with uniqueness +// meaning different font attributes, but not different fonts sizes +void ImplDevFontListData::UpdateDevFontList( ImplGetDevFontList& rDevFontList ) const +{ + ImplFontData* pPrevFace = NULL; + for( ImplFontData* pFace = mpFirst; pFace; pFace = pFace->GetNextFace() ) + { + if( !pPrevFace || pFace->CompareIgnoreSize( *pPrevFace ) ) + rDevFontList.Add( pFace ); + pPrevFace = pFace; + } +} + +// ----------------------------------------------------------------------- + +void ImplDevFontListData::GetFontHeights( std::set<int>& rHeights ) const +{ + // add all available font heights + for( const ImplFontData* pFace = mpFirst; pFace; pFace = pFace->GetNextFace() ) + rHeights.insert( pFace->GetHeight() ); +} + +// ----------------------------------------------------------------------- + +void ImplDevFontListData::UpdateCloneFontList( ImplDevFontList& rDevFontList, + bool bScalable, bool bEmbeddable ) const +{ + for( ImplFontData* pFace = mpFirst; pFace; pFace = pFace->GetNextFace() ) + { + if( bScalable && !pFace->IsScalable() ) + continue; + if( bEmbeddable && !pFace->IsEmbeddable() && !pFace->IsSubsettable() ) + continue; + + ImplFontData* pClonedFace = pFace->Clone(); + rDevFontList.Add( pClonedFace ); + } +} + +// ======================================================================= + +ImplDevFontList::ImplDevFontList() +: mbMatchData( false ) +, mbMapNames( false ) +, mpPreMatchHook( NULL ) +, mpFallbackHook( NULL ) +, mpFallbackList( NULL ) +, mnFallbackCount( -1 ) +{} + +// ----------------------------------------------------------------------- + +ImplDevFontList::~ImplDevFontList() +{ + Clear(); +} + +// ----------------------------------------------------------------------- + +void ImplDevFontList::SetPreMatchHook( ImplPreMatchFontSubstitution* pHook ) +{ + mpPreMatchHook = pHook; +} + +// ----------------------------------------------------------------------- + +void ImplDevFontList::SetFallbackHook( ImplGlyphFallbackFontSubstitution* pHook ) +{ + mpFallbackHook = pHook; +} + +// ----------------------------------------------------------------------- + +void ImplDevFontList::Clear() +{ + // remove fallback lists + delete[] mpFallbackList; + mpFallbackList = NULL; + mnFallbackCount = -1; + + // clear all entries in the device font list + DevFontList::iterator it = maDevFontList.begin(); + for(; it != maDevFontList.end(); ++it ) + { + ImplDevFontListData* pEntry = (*it).second; + delete pEntry; + } + + maDevFontList.clear(); + + // match data must be recalculated too + mbMatchData = false; +} + + +// ----------------------------------------------------------------------- + +void ImplDevFontList::InitGenericGlyphFallback( void ) const +{ + // normalized family names of fonts suited for glyph fallback + // if a font is available related fonts can be ignored + // TODO: implement dynamic lists + static const char* aGlyphFallbackList[] = { + // empty strings separate the names of unrelated fonts + "eudc", "", + "arialunicodems", "cyberbit", "code2000", "", + "andalesansui", "", + "starsymbol", "opensymbol", "", + "msmincho", "fzmingti", "fzheiti", "ipamincho", "sazanamimincho", "kochimincho", "", + "sunbatang", "sundotum", "baekmukdotum", "gulim", "batang", "dotum", "", + "hgmincholightj", "msunglightsc", "msunglighttc", "hymyeongjolightk", "", + "tahoma", "dejavusans", "timesnewroman", "lucidatypewriter", "lucidasans", "nimbussansl", "", + "shree", "mangal", "", + "raavi", "shruti", "tunga", "", + "latha", "gautami", "kartika", "vrinda", "", + "shayyalmt", "naskmt", "", + "david", "nachlieli", "lucidagrande", "", + "norasi", "angsanaupc", "", + "khmerossystem", "", + "muktinarrow", "", + "phetsarathot", "", + "padauk", "pinlonmyanmar", "", + "iskoolapota", "lklug", "", + 0 + }; + + bool bHasEudc = false; + int nMaxLevel = 0; + int nBestQuality = 0; + ImplDevFontListData** pFallbackList = NULL; + for( const char** ppNames = &aGlyphFallbackList[0];; ++ppNames ) + { + // advance to next sub-list when end-of-sublist marker + if( !**ppNames ) // #i46456# check for empty string, i.e., deref string itself not only ptr to it + { + if( nBestQuality > 0 ) + if( ++nMaxLevel >= MAX_FALLBACK ) + break; + if( !ppNames[1] ) + break; + nBestQuality = 0; + continue; + } + + // test if the glyph fallback candidate font is available and scalable + String aTokenName( *ppNames, RTL_TEXTENCODING_UTF8 ); + ImplDevFontListData* pFallbackFont = FindFontFamily( aTokenName ); + if( !pFallbackFont ) + continue; + if( !pFallbackFont->IsScalable() ) + continue; + + // keep the best font of the glyph fallback sub-list + if( nBestQuality < pFallbackFont->GetMinQuality() ) + { + nBestQuality = pFallbackFont->GetMinQuality(); + // store available glyph fallback fonts + if( !pFallbackList ) + pFallbackList = new ImplDevFontListData*[ MAX_FALLBACK ]; + pFallbackList[ nMaxLevel ] = pFallbackFont; + if( !bHasEudc && !nMaxLevel ) + bHasEudc = !strncmp( *ppNames, "eudc", 5 ); + } + } + + // sort the list of fonts for glyph fallback by quality (highest first) + // #i33947# keep the EUDC font at the front of the list + // an insertion sort is good enough for this short list + const int nSortStart = bHasEudc ? 1 : 0; + for( int i = nSortStart+1, j; i < nMaxLevel; ++i ) + { + ImplDevFontListData* pTestFont = pFallbackList[ i ]; + int nTestQuality = pTestFont->GetMinQuality(); + for( j = i; --j >= nSortStart; ) + if( nTestQuality > pFallbackList[j]->GetMinQuality() ) + pFallbackList[ j+1 ] = pFallbackList[ j ]; + else + break; + pFallbackList[ j+1 ] = pTestFont; + } + +#if defined(HDU_DEBUG) + for( int i = 0; i < nMaxLevel; ++i ) + { + ImplDevFontListData* pFont = pFallbackList[ i ]; + ByteString aFontName( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ); + fprintf( stderr, "GlyphFallbackFont[%d] (quality=%05d): \"%s\"\n", + i, pFont->GetMinQuality(), aFontName.GetBuffer() ); + } +#endif + + mnFallbackCount = nMaxLevel; + mpFallbackList = pFallbackList; +} + +// ----------------------------------------------------------------------- + +ImplDevFontListData* ImplDevFontList::GetGlyphFallbackFont( ImplFontSelectData& rFontSelData, + rtl::OUString& rMissingCodes, int nFallbackLevel ) const +{ + ImplDevFontListData* pFallbackData = NULL; + + // find a matching font candidate for platform specific glyph fallback + if( mpFallbackHook ) + { + // check cache for the first matching entry + // to avoid calling the expensive fallback hook (#i83491#) + sal_UCS4 cChar = 0; + bool bCached = true; + sal_Int32 nStrIndex = 0; + while( nStrIndex < rMissingCodes.getLength() ) + { + cChar = rMissingCodes.iterateCodePoints( &nStrIndex ); + bCached = rFontSelData.mpFontEntry->GetFallbackForUnicode( cChar, rFontSelData.GetWeight(), &rFontSelData.maSearchName ); + // ignore entries which don't have a fallback + if( !bCached || (rFontSelData.maSearchName.Len() != 0) ) + break; + } + + if( bCached ) + { + // there is a matching fallback in the cache + // so update rMissingCodes with codepoints not yet resolved by this fallback + int nRemainingLength = 0; + sal_UCS4* pRemainingCodes = (sal_UCS4*)alloca( rMissingCodes.getLength() * sizeof(sal_UCS4) ); + String aFontName; + while( nStrIndex < rMissingCodes.getLength() ) + { + cChar = rMissingCodes.iterateCodePoints( &nStrIndex ); + bCached = rFontSelData.mpFontEntry->GetFallbackForUnicode( cChar, rFontSelData.GetWeight(), &aFontName ); + if( !bCached || (rFontSelData.maSearchName != aFontName) ) + pRemainingCodes[ nRemainingLength++ ] = cChar; + } + rMissingCodes = rtl::OUString( pRemainingCodes, nRemainingLength ); + } + else + { + rtl::OUString aOldMissingCodes = rMissingCodes; + // call the hook to query the best matching glyph fallback font + if( mpFallbackHook->FindFontSubstitute( rFontSelData, rMissingCodes ) ) + // apply outdev3.cxx specific fontname normalization + GetEnglishSearchFontName( rFontSelData.maSearchName ); + else + rFontSelData.maSearchName = String(); + + // cache the result even if there was no match + for(;;) + { + if( !rFontSelData.mpFontEntry->GetFallbackForUnicode( cChar, rFontSelData.GetWeight(), &rFontSelData.maSearchName ) ) + rFontSelData.mpFontEntry->AddFallbackForUnicode( cChar, rFontSelData.GetWeight(), rFontSelData.maSearchName ); + if( nStrIndex >= aOldMissingCodes.getLength() ) + break; + cChar = aOldMissingCodes.iterateCodePoints( &nStrIndex ); + } + if( rFontSelData.maSearchName.Len() != 0 ) + { + // remove cache entries that were still not resolved + for( nStrIndex = 0; nStrIndex < rMissingCodes.getLength(); ) + { + cChar = rMissingCodes.iterateCodePoints( &nStrIndex ); + rFontSelData.mpFontEntry->IgnoreFallbackForUnicode( cChar, rFontSelData.GetWeight(), rFontSelData.maSearchName ); + } + } + } + + // find the matching device font + if( rFontSelData.maSearchName.Len() != 0 ) + pFallbackData = FindFontFamily( rFontSelData.maSearchName ); + } + + // else find a matching font candidate for generic glyph fallback + if( !pFallbackData ) + { + // initialize font candidates for generic glyph fallback if needed + if( mnFallbackCount < 0 ) + InitGenericGlyphFallback(); + // TODO: adjust nFallbackLevel by number of levels resolved by the fallback hook + if( nFallbackLevel < mnFallbackCount ) + pFallbackData = mpFallbackList[ nFallbackLevel ]; + } + + return pFallbackData; +} + +// ----------------------------------------------------------------------- + +void ImplDevFontList::Add( ImplFontData* pNewData ) +{ + int nAliasQuality = pNewData->mnQuality - 100; + String aMapNames = pNewData->maMapNames; + pNewData->maMapNames = String(); + + bool bKeepNewData = false; + for( xub_StrLen nMapNameIndex = 0; nMapNameIndex != STRING_NOTFOUND; ) + { + String aSearchName = pNewData->maName; + GetEnglishSearchFontName( aSearchName ); + + DevFontList::const_iterator it = maDevFontList.find( aSearchName ); + ImplDevFontListData* pFoundData = NULL; + if( it != maDevFontList.end() ) + pFoundData = (*it).second; + + if( !pFoundData ) + { + pFoundData = new ImplDevFontListData( aSearchName ); + maDevFontList[ aSearchName ] = pFoundData; + } + + bKeepNewData = pFoundData->AddFontFace( pNewData ); + + // add font alias if available + // a font alias should never win against an original font with similar quality + if( aMapNames.Len() >= nMapNameIndex ) + break; + if( bKeepNewData ) // try to recycle obsoleted object + pNewData = pNewData->CreateAlias(); + bKeepNewData = false; + pNewData->mnQuality = nAliasQuality; + pNewData->maName = GetNextFontToken( aMapNames, nMapNameIndex ); + } + + if( !bKeepNewData ) + delete pNewData; +} + +// ----------------------------------------------------------------------- + +// find the font from the normalized font family name +ImplDevFontListData* ImplDevFontList::ImplFindBySearchName( const String& rSearchName ) const +{ +#ifdef DEBUG + String aTempName = rSearchName; + GetEnglishSearchFontName( aTempName ); + DBG_ASSERT( aTempName == rSearchName, "ImplDevFontList::ImplFindBySearchName() called with non-normalized name" ); +#endif + + DevFontList::const_iterator it = maDevFontList.find( rSearchName ); + if( it == maDevFontList.end() ) + return NULL; + + ImplDevFontListData* pFoundData = (*it).second; + return pFoundData; +} + +// ----------------------------------------------------------------------- + +ImplDevFontListData* ImplDevFontList::ImplFindByAliasName( const String& rSearchName, const String& rShortName ) const +{ + // short circuit for impossible font name alias + if( !rSearchName.Len() ) + return NULL; + + // short circuit if no alias names are available + if( !mbMapNames ) + return NULL; + + // use the font's alias names to find the font + // TODO: get rid of linear search + DevFontList::const_iterator it = maDevFontList.begin(); + while( it != maDevFontList.end() ) + { + ImplDevFontListData* pData = (*it).second; + if( !pData->maMapNames.Len() ) + continue; + + // if one alias name matches we found a matching font + String aTempName; + xub_StrLen nIndex = 0; + do + { + aTempName = GetNextFontToken( pData->maMapNames, nIndex ); + // Test, if the Font name match with one of the mapping names + if ( (aTempName == rSearchName) || (aTempName == rShortName) ) + return pData; + } + while ( nIndex != STRING_NOTFOUND ); + } + + return NULL; +} + +// ----------------------------------------------------------------------- + +ImplDevFontListData* ImplDevFontList::FindFontFamily( const String& rFontName ) const +{ + // normalize the font fomily name and + String aName = rFontName; + GetEnglishSearchFontName( aName ); + ImplDevFontListData* pFound = ImplFindBySearchName( aName ); + return pFound; +} + +// ----------------------------------------------------------------------- + +ImplDevFontListData* ImplDevFontList::ImplFindByTokenNames( const String& rTokenStr ) const +{ + ImplDevFontListData* pFoundData = NULL; + + // use normalized font name tokens to find the font + for( xub_StrLen nTokenPos = 0; nTokenPos != STRING_NOTFOUND; ) + { + String aSearchName = GetNextFontToken( rTokenStr, nTokenPos ); + if( !aSearchName.Len() ) + continue; + GetEnglishSearchFontName( aSearchName ); + pFoundData = ImplFindBySearchName( aSearchName ); + if( pFoundData ) + break; + } + + return pFoundData; +} + +// ----------------------------------------------------------------------- + +ImplDevFontListData* ImplDevFontList::ImplFindBySubstFontAttr( const utl::FontNameAttr& rFontAttr ) const +{ + ImplDevFontListData* pFoundData = NULL; + + // use the font substitutions suggested by the FontNameAttr to find the font + ::std::vector< String >::const_iterator it = rFontAttr.Substitutions.begin(); + for(; it != rFontAttr.Substitutions.end(); ++it ) + { + String aSearchName( *it ); + GetEnglishSearchFontName( aSearchName ); + + pFoundData = ImplFindBySearchName( aSearchName ); + if( pFoundData ) + break; + } + + return pFoundData; +} + +// ----------------------------------------------------------------------- + +void ImplDevFontList::InitMatchData() const +{ + // short circuit if already done + if( mbMatchData ) + return; + mbMatchData = true; + + // calculate MatchData for all entries + const FontSubstConfiguration& rFontSubst = *FontSubstConfiguration::get(); + + DevFontList::const_iterator it = maDevFontList.begin(); + for(; it != maDevFontList.end(); ++it ) + { + const String& rSearchName = (*it).first; + ImplDevFontListData* pEntry = (*it).second; + + pEntry->InitMatchData( rFontSubst, rSearchName ); + } +} + +//---------------------------------------------------------------------------- +ImplDevFontListData* ImplDevFontList::ImplFindByLocale(com::sun::star::lang::Locale lc) const +{ + // get the default font for a specified locale + const DefaultFontConfiguration& rDefaults = *DefaultFontConfiguration::get(); + String aDefault = rDefaults.getUserInterfaceFont( lc ); + ImplDevFontListData* pFontData = ImplFindByTokenNames( aDefault ); + if( pFontData ) + return pFontData; + return 0; +} + +// ----------------------------------------------------------------------- + +ImplDevFontListData* ImplDevFontList::ImplFindByAttributes( ULONG nSearchType, + FontWeight eSearchWeight, FontWidth eSearchWidth, FontFamily /*eSearchFamily*/, + FontItalic eSearchItalic, const String& rSearchFamilyName ) const +{ + if( (eSearchItalic != ITALIC_NONE) && (eSearchItalic != ITALIC_DONTKNOW) ) + nSearchType |= IMPL_FONT_ATTR_ITALIC; + + // don't bother to match attributes if the attributes aren't worth matching + if( !nSearchType + && ((eSearchWeight == WEIGHT_DONTKNOW) || (eSearchWeight == WEIGHT_NORMAL)) + && ((eSearchWidth == WIDTH_DONTKNOW) || (eSearchWidth == WIDTH_NORMAL)) ) + return NULL; + + InitMatchData(); + ImplDevFontListData* pFoundData = NULL; + + long nTestMatch; + long nBestMatch = 40000; + ULONG nBestType = 0; + + DevFontList::const_iterator it = maDevFontList.begin(); + for(; it != maDevFontList.end(); ++it ) + { + ImplDevFontListData* pData = (*it).second; + + // Get all information about the matching font + ULONG nMatchType = pData->mnMatchType; + FontWeight eMatchWeight= pData->meMatchWeight; + FontWidth eMatchWidth = pData->meMatchWidth; + + // Calculate Match Value + // 1000000000 + // 100000000 + // 10000000 CJK, CTL, None-Latin, Symbol + // 1000000 FamilyName, Script, Fixed, -Special, -Decorative, + // Titling, Capitals, Outline, Shadow + // 100000 Match FamilyName, Serif, SansSerif, Italic, + // Width, Weight + // 10000 Scalable, Standard, Default, + // full, Normal, Knownfont, + // Otherstyle, +Special, +Decorative, + // 1000 Typewriter, Rounded, Gothic, Schollbook + // 100 + nTestMatch = 0; + + // test CJK script attributes + if ( nSearchType & IMPL_FONT_ATTR_CJK ) + { + // Matching language + if( 0 == ((nSearchType ^ nMatchType) & IMPL_FONT_ATTR_CJK_ALLLANG) ) + nTestMatch += 10000000*3; + if( nMatchType & IMPL_FONT_ATTR_CJK ) + nTestMatch += 10000000*2; + if( nMatchType & IMPL_FONT_ATTR_FULL ) + nTestMatch += 10000000; + } + else if ( nMatchType & IMPL_FONT_ATTR_CJK ) + nTestMatch -= 10000000; + + // test CTL script attributes + if( nSearchType & IMPL_FONT_ATTR_CTL ) + { + if( nMatchType & IMPL_FONT_ATTR_CTL ) + nTestMatch += 10000000*2; + if( nMatchType & IMPL_FONT_ATTR_FULL ) + nTestMatch += 10000000; + } + else if ( nMatchType & IMPL_FONT_ATTR_CTL ) + nTestMatch -= 10000000; + + // test LATIN script attributes + if( nSearchType & IMPL_FONT_ATTR_NONELATIN ) + { + if( nMatchType & IMPL_FONT_ATTR_NONELATIN ) + nTestMatch += 10000000*2; + if( nMatchType & IMPL_FONT_ATTR_FULL ) + nTestMatch += 10000000; + } + + // test SYMBOL attributes + if ( nSearchType & IMPL_FONT_ATTR_SYMBOL ) + { + const String& rSearchName = it->first; + // prefer some special known symbol fonts + if ( rSearchName.EqualsAscii( "starsymbol" ) ) + nTestMatch += 10000000*6+(10000*3); + else if ( rSearchName.EqualsAscii( "opensymbol" ) ) + nTestMatch += 10000000*6; + else if ( rSearchName.EqualsAscii( "starbats" ) + || rSearchName.EqualsAscii( "wingdings" ) + || rSearchName.EqualsAscii( "monotypesorts" ) + || rSearchName.EqualsAscii( "dingbats" ) + || rSearchName.EqualsAscii( "zapfdingbats" ) ) + nTestMatch += 10000000*5; + else if ( pData->mnTypeFaces & IMPL_DEVFONT_SYMBOL ) + nTestMatch += 10000000*4; + else + { + if( nMatchType & IMPL_FONT_ATTR_SYMBOL ) + nTestMatch += 10000000*2; + if( nMatchType & IMPL_FONT_ATTR_FULL ) + nTestMatch += 10000000; + } + } + else if ( (pData->mnTypeFaces & (IMPL_DEVFONT_SYMBOL | IMPL_DEVFONT_NONESYMBOL)) == IMPL_DEVFONT_SYMBOL ) + nTestMatch -= 10000000; + else if ( nMatchType & IMPL_FONT_ATTR_SYMBOL ) + nTestMatch -= 10000; + + // match stripped family name + if( rSearchFamilyName.Len() && (rSearchFamilyName == pData->maMatchFamilyName) ) + nTestMatch += 1000000*3; + + // match ALLSCRIPT? attribute + if( nSearchType & IMPL_FONT_ATTR_ALLSCRIPT ) + { + if( nMatchType & IMPL_FONT_ATTR_ALLSCRIPT ) + nTestMatch += 1000000*2; + if( nSearchType & IMPL_FONT_ATTR_ALLSUBSCRIPT ) + { + if( 0 == ((nSearchType ^ nMatchType) & IMPL_FONT_ATTR_ALLSUBSCRIPT) ) + nTestMatch += 1000000*2; + if( 0 != ((nSearchType ^ nMatchType) & IMPL_FONT_ATTR_BRUSHSCRIPT) ) + nTestMatch -= 1000000; + } + } + else if( nMatchType & IMPL_FONT_ATTR_ALLSCRIPT ) + nTestMatch -= 1000000; + + // test MONOSPACE+TYPEWRITER attributes + if( nSearchType & IMPL_FONT_ATTR_FIXED ) + { + if( nMatchType & IMPL_FONT_ATTR_FIXED ) + nTestMatch += 1000000*2; + // a typewriter attribute is even better + if( 0 == ((nSearchType ^ nMatchType) & IMPL_FONT_ATTR_TYPEWRITER) ) + nTestMatch += 10000*2; + } + else if( nMatchType & IMPL_FONT_ATTR_FIXED ) + nTestMatch -= 1000000; + + // test SPECIAL attribute + if( nSearchType & IMPL_FONT_ATTR_SPECIAL ) + { + if( nMatchType & IMPL_FONT_ATTR_SPECIAL ) + nTestMatch += 10000; + else if( !(nSearchType & IMPL_FONT_ATTR_ALLSERIFSTYLE) ) + { + if( nMatchType & IMPL_FONT_ATTR_SERIF ) + nTestMatch += 1000*2; + else if( nMatchType & IMPL_FONT_ATTR_SANSSERIF ) + nTestMatch += 1000; + } + } + else if( (nMatchType & IMPL_FONT_ATTR_SPECIAL) && !(nSearchType & IMPL_FONT_ATTR_SYMBOL) ) + nTestMatch -= 1000000; + + // test DECORATIVE attribute + if( nSearchType & IMPL_FONT_ATTR_DECORATIVE ) + { + if( nMatchType & IMPL_FONT_ATTR_DECORATIVE ) + nTestMatch += 10000; + else if( !(nSearchType & IMPL_FONT_ATTR_ALLSERIFSTYLE) ) + { + if( nMatchType & IMPL_FONT_ATTR_SERIF ) + nTestMatch += 1000*2; + else if ( nMatchType & IMPL_FONT_ATTR_SANSSERIF ) + nTestMatch += 1000; + } + } + else if( nMatchType & IMPL_FONT_ATTR_DECORATIVE ) + nTestMatch -= 1000000; + + // test TITLE+CAPITALS attributes + if( nSearchType & (IMPL_FONT_ATTR_TITLING | IMPL_FONT_ATTR_CAPITALS) ) + { + if( nMatchType & (IMPL_FONT_ATTR_TITLING | IMPL_FONT_ATTR_CAPITALS) ) + nTestMatch += 1000000*2; + if( 0 == ((nSearchType^nMatchType) & (IMPL_FONT_ATTR_TITLING | IMPL_FONT_ATTR_CAPITALS))) + nTestMatch += 1000000; + else if( (nMatchType & (IMPL_FONT_ATTR_TITLING | IMPL_FONT_ATTR_CAPITALS)) + && (nMatchType & (IMPL_FONT_ATTR_STANDARD | IMPL_FONT_ATTR_DEFAULT)) ) + nTestMatch += 1000000; + } + else if( nMatchType & (IMPL_FONT_ATTR_TITLING | IMPL_FONT_ATTR_CAPITALS) ) + nTestMatch -= 1000000; + + // test OUTLINE+SHADOW attributes + if( nSearchType & (IMPL_FONT_ATTR_OUTLINE | IMPL_FONT_ATTR_SHADOW) ) + { + if( nMatchType & (IMPL_FONT_ATTR_OUTLINE | IMPL_FONT_ATTR_SHADOW) ) + nTestMatch += 1000000*2; + if( 0 == ((nSearchType ^ nMatchType) & (IMPL_FONT_ATTR_OUTLINE | IMPL_FONT_ATTR_SHADOW)) ) + nTestMatch += 1000000; + else if( (nMatchType & (IMPL_FONT_ATTR_OUTLINE | IMPL_FONT_ATTR_SHADOW)) + && (nMatchType & (IMPL_FONT_ATTR_STANDARD | IMPL_FONT_ATTR_DEFAULT)) ) + nTestMatch += 1000000; + } + else if ( nMatchType & (IMPL_FONT_ATTR_OUTLINE | IMPL_FONT_ATTR_SHADOW) ) + nTestMatch -= 1000000; + + // test font name substrings + // TODO: calculate name matching score using e.g. Levenstein distance + if( (rSearchFamilyName.Len() >= 4) && (pData->maMatchFamilyName.Len() >= 4) + && ((rSearchFamilyName.Search( pData->maMatchFamilyName ) != STRING_NOTFOUND) + || (pData->maMatchFamilyName.Search( rSearchFamilyName ) != STRING_NOTFOUND)) ) + nTestMatch += 5000; + + // test SERIF attribute + if( nSearchType & IMPL_FONT_ATTR_SERIF ) + { + if( nMatchType & IMPL_FONT_ATTR_SERIF ) + nTestMatch += 1000000*2; + else if( nMatchType & IMPL_FONT_ATTR_SANSSERIF ) + nTestMatch -= 1000000; + } + + // test SANSERIF attribute + if( nSearchType & IMPL_FONT_ATTR_SANSSERIF ) + { + if( nMatchType & IMPL_FONT_ATTR_SANSSERIF ) + nTestMatch += 1000000; + else if ( nMatchType & IMPL_FONT_ATTR_SERIF ) + nTestMatch -= 1000000; + } + + // test ITALIC attribute + if( nSearchType & IMPL_FONT_ATTR_ITALIC ) + { + if( pData->mnTypeFaces & IMPL_DEVFONT_ITALIC ) + nTestMatch += 1000000*3; + if( nMatchType & IMPL_FONT_ATTR_ITALIC ) + nTestMatch += 1000000; + } + else if( !(nSearchType & IMPL_FONT_ATTR_ALLSCRIPT) + && ((nMatchType & IMPL_FONT_ATTR_ITALIC) + || !(pData->mnTypeFaces & IMPL_DEVFONT_NONEITALIC)) ) + nTestMatch -= 1000000*2; + + // test WIDTH attribute + if( (eSearchWidth != WIDTH_DONTKNOW) && (eSearchWidth != WIDTH_NORMAL) ) + { + if( eSearchWidth < WIDTH_NORMAL ) + { + if( eSearchWidth == eMatchWidth ) + nTestMatch += 1000000*3; + else if( (eMatchWidth < WIDTH_NORMAL) && (eMatchWidth != WIDTH_DONTKNOW) ) + nTestMatch += 1000000; + } + else + { + if( eSearchWidth == eMatchWidth ) + nTestMatch += 1000000*3; + else if( eMatchWidth > WIDTH_NORMAL ) + nTestMatch += 1000000; + } + } + else if( (eMatchWidth != WIDTH_DONTKNOW) && (eMatchWidth != WIDTH_NORMAL) ) + nTestMatch -= 1000000; + + // test WEIGHT attribute + if( (eSearchWeight != WEIGHT_DONTKNOW) && (eSearchWeight != WEIGHT_NORMAL) && (eSearchWeight != WEIGHT_MEDIUM) ) + { + if( eSearchWeight < WEIGHT_NORMAL ) + { + if( pData->mnTypeFaces & IMPL_DEVFONT_LIGHT ) + nTestMatch += 1000000; + if( (eMatchWeight < WEIGHT_NORMAL) && (eMatchWeight != WEIGHT_DONTKNOW) ) + nTestMatch += 1000000; + } + else + { + if( pData->mnTypeFaces & IMPL_DEVFONT_BOLD ) + nTestMatch += 1000000; + if( eMatchWeight > WEIGHT_BOLD ) + nTestMatch += 1000000; + } + } + else if( ((eMatchWeight != WEIGHT_DONTKNOW) && (eMatchWeight != WEIGHT_NORMAL) && (eMatchWeight != WEIGHT_MEDIUM)) + || !(pData->mnTypeFaces & IMPL_DEVFONT_NORMAL) ) + nTestMatch -= 1000000; + + // prefer scalable fonts + if( pData->mnTypeFaces & IMPL_DEVFONT_SCALABLE ) + nTestMatch += 10000*4; + else + nTestMatch -= 10000*4; + + // test STANDARD+DEFAULT+FULL+NORMAL attributes + if( nMatchType & IMPL_FONT_ATTR_STANDARD ) + nTestMatch += 10000*2; + if( nMatchType & IMPL_FONT_ATTR_DEFAULT ) + nTestMatch += 10000; + if( nMatchType & IMPL_FONT_ATTR_FULL ) + nTestMatch += 10000; + if( nMatchType & IMPL_FONT_ATTR_NORMAL ) + nTestMatch += 10000; + + // test OTHERSTYLE attribute + if( nMatchType & IMPL_FONT_ATTR_OTHERSTYLE ) + { + if( !(nMatchType & IMPL_FONT_ATTR_OTHERSTYLE) ) + nTestMatch -= 10000; + } + else if( nMatchType & IMPL_FONT_ATTR_OTHERSTYLE ) + nTestMatch -= 10000; + + // test ROUNDED attribute + if( 0 == ((nSearchType ^ nMatchType) & IMPL_FONT_ATTR_ROUNDED) ) + nTestMatch += 1000; + + // test TYPEWRITER attribute + if( 0 == ((nSearchType ^ nMatchType) & IMPL_FONT_ATTR_TYPEWRITER) ) + nTestMatch += 1000; + + // test GOTHIC attribute + if( nSearchType & IMPL_FONT_ATTR_GOTHIC ) + { + if( nMatchType & IMPL_FONT_ATTR_GOTHIC ) + nTestMatch += 1000*3; + if( nMatchType & IMPL_FONT_ATTR_SANSSERIF ) + nTestMatch += 1000*2; + } + + // test SCHOOLBOOK attribute + if( nSearchType & IMPL_FONT_ATTR_SCHOOLBOOK ) + { + if( nMatchType & IMPL_FONT_ATTR_SCHOOLBOOK ) + nTestMatch += 1000*3; + if( nMatchType & IMPL_FONT_ATTR_SERIF ) + nTestMatch += 1000*2; + } + + // compare with best matching font yet + if ( nTestMatch > nBestMatch ) + { + pFoundData = pData; + nBestMatch = nTestMatch; + nBestType = nMatchType; + } + else if( nTestMatch == nBestMatch ) + { + // some fonts are more suitable defaults + if( nMatchType & IMPL_FONT_ATTR_DEFAULT ) + { + pFoundData = pData; + nBestType = nMatchType; + } + else if( (nMatchType & IMPL_FONT_ATTR_STANDARD) && + !(nBestType & IMPL_FONT_ATTR_DEFAULT) ) + { + pFoundData = pData; + nBestType = nMatchType; + } + } + } + + return pFoundData; +} + +// ----------------------------------------------------------------------- + +ImplDevFontListData* ImplDevFontList::FindDefaultFont() const +{ + // try to find one of the default fonts of the + // UNICODE, SANSSERIF, SERIF or FIXED default font lists + const DefaultFontConfiguration& rDefaults = *DefaultFontConfiguration::get(); + com::sun::star::lang::Locale aLocale( OUString( RTL_CONSTASCII_USTRINGPARAM("en") ), OUString(), OUString() ); + String aFontname = rDefaults.getDefaultFont( aLocale, DEFAULTFONT_SANS_UNICODE ); + ImplDevFontListData* pFoundData = ImplFindByTokenNames( aFontname ); + if( pFoundData ) + return pFoundData; + + aFontname = rDefaults.getDefaultFont( aLocale, DEFAULTFONT_SANS ); + pFoundData = ImplFindByTokenNames( aFontname ); + if( pFoundData ) + return pFoundData; + + aFontname = rDefaults.getDefaultFont( aLocale, DEFAULTFONT_SERIF ); + pFoundData = ImplFindByTokenNames( aFontname ); + if( pFoundData ) + return pFoundData; + + aFontname = rDefaults.getDefaultFont( aLocale, DEFAULTFONT_FIXED ); + pFoundData = ImplFindByTokenNames( aFontname ); + if( pFoundData ) + return pFoundData; + + // now try to find a reasonable non-symbol font + + InitMatchData(); + + DevFontList::const_iterator it = maDevFontList.begin(); + for(; it != maDevFontList.end(); ++it ) + { + ImplDevFontListData* pData = (*it).second; + if( pData->mnMatchType & IMPL_FONT_ATTR_SYMBOL ) + continue; + pFoundData = pData; + if( pData->mnMatchType & (IMPL_FONT_ATTR_DEFAULT|IMPL_FONT_ATTR_STANDARD) ) + break; + } + if( pFoundData ) + return pFoundData; + + // finding any font is better than finding no font at all + it = maDevFontList.begin(); + if( it != maDevFontList.end() ) + pFoundData = (*it).second; + + return pFoundData; +} + +// ----------------------------------------------------------------------- + +ImplDevFontList* ImplDevFontList::Clone( bool bScalable, bool bEmbeddable ) const +{ + ImplDevFontList* pClonedList = new ImplDevFontList; +// pClonedList->mbMatchData = mbMatchData; + pClonedList->mbMapNames = mbMapNames; + pClonedList->mpPreMatchHook = mpPreMatchHook; + pClonedList->mpFallbackHook = mpFallbackHook; + + // TODO: clone the config-font attributes too? + pClonedList->mbMatchData = false; + + DevFontList::const_iterator it = maDevFontList.begin(); + for(; it != maDevFontList.end(); ++it ) + { + const ImplDevFontListData* pFontFace = (*it).second; + pFontFace->UpdateCloneFontList( *pClonedList, bScalable, bEmbeddable ); + } + + return pClonedList; +} + +// ----------------------------------------------------------------------- + +ImplGetDevFontList* ImplDevFontList::GetDevFontList() const +{ + ImplGetDevFontList* pGetDevFontList = new ImplGetDevFontList; + + DevFontList::const_iterator it = maDevFontList.begin(); + for(; it != maDevFontList.end(); ++it ) + { + const ImplDevFontListData* pFontFamily = (*it).second; + pFontFamily->UpdateDevFontList( *pGetDevFontList ); + } + + return pGetDevFontList; +} + +// ----------------------------------------------------------------------- + +ImplGetDevSizeList* ImplDevFontList::GetDevSizeList( const String& rFontName ) const +{ + ImplGetDevSizeList* pGetDevSizeList = new ImplGetDevSizeList( rFontName ); + + ImplDevFontListData* pFontFamily = FindFontFamily( rFontName ); + if( pFontFamily != NULL ) + { + std::set<int> rHeights; + pFontFamily->GetFontHeights( rHeights ); + + std::set<int>::const_iterator it = rHeights.begin(); + for(; it != rHeights.begin(); ++it ) + pGetDevSizeList->Add( *it ); + } + + return pGetDevSizeList; +} + +// ======================================================================= + +ImplFontSelectData::ImplFontSelectData( const Font& rFont, + const String& rSearchName, const Size& rSize, float fExactHeight) +: maSearchName( rSearchName ), + mnWidth( rSize.Width() ), + mnHeight( rSize.Height() ), + mfExactHeight( fExactHeight), + mnOrientation( rFont.GetOrientation() ), + meLanguage( rFont.GetLanguage() ), + mbVertical( rFont.IsVertical() ), + mbNonAntialiased( false ), + mpFontData( NULL ), + mpFontEntry( NULL ) +{ + maTargetName = maName; + + rFont.GetFontAttributes( *this ); + + // normalize orientation between 0 and 3600 + if( 3600 <= (unsigned)mnOrientation ) + { + if( mnOrientation >= 0 ) + mnOrientation %= 3600; + else + mnOrientation = 3600 - (-mnOrientation % 3600); + } + + // normalize width and height + if( mnHeight < 0 ) + mnHeight = -mnHeight; + if( mnWidth < 0 ) + mnWidth = -mnWidth; +} + +// ----------------------------------------------------------------------- + +ImplFontSelectData::ImplFontSelectData( const ImplFontData& rFontData, + const Size& rSize, float fExactHeight, int nOrientation, bool bVertical ) +: ImplFontAttributes( rFontData ), + mnWidth( rSize.Width() ), + mnHeight( rSize.Height() ), + mfExactHeight( fExactHeight ), + mnOrientation( nOrientation ), + meLanguage( 0 ), + mbVertical( bVertical ), + mbNonAntialiased( false ), + mpFontData( &rFontData ), + mpFontEntry( NULL ) +{ + maTargetName = maSearchName = maName; + // NOTE: no normalization for width/height/orientation +} + +// ======================================================================= + +size_t ImplFontCache::IFSD_Hash::operator()( const ImplFontSelectData& rFSD ) const +{ + // TODO: does it pay off to improve this hash function? + static FontNameHash aFontNameHash; + size_t nHash = aFontNameHash( rFSD.maSearchName ); +#ifdef ENABLE_GRAPHITE + // check for features and generate a unique hash if necessary + if (rFSD.maTargetName.Search(grutils::GrFeatureParser::FEAT_PREFIX) + != STRING_NOTFOUND) + { + nHash = aFontNameHash( rFSD.maTargetName ); + } +#endif + nHash += 11 * rFSD.mnHeight; + nHash += 19 * rFSD.meWeight; + nHash += 29 * rFSD.meItalic; + nHash += 37 * rFSD.mnOrientation; + nHash += 41 * rFSD.meLanguage; + if( rFSD.mbVertical ) + nHash += 53; + return nHash; +} + +// ----------------------------------------------------------------------- + +bool ImplFontCache::IFSD_Equal::operator()(const ImplFontSelectData& rA, const ImplFontSelectData& rB) const +{ + // check normalized font family name + if( rA.maSearchName != rB.maSearchName ) + return false; + + // check font transformation + if( (rA.mnHeight != rB.mnHeight) + || (rA.mnWidth != rB.mnWidth) + || (rA.mnOrientation != rB.mnOrientation) ) + return false; + + // check mapping relevant attributes + if( (rA.mbVertical != rB.mbVertical) + || (rA.meLanguage != rB.meLanguage) ) + return false; + + // check font face attributes + if( (rA.meWeight != rB.meWeight) + || (rA.meItalic != rB.meItalic) +// || (rA.meFamily != rB.meFamily) // TODO: remove this mostly obsolete member + || (rA.mePitch != rB.mePitch) ) + return false; + + // check style name + if( rA.maStyleName != rB.maStyleName) + return false; + + // Symbol fonts may recode from one type to another So they are only + // safely equivalent for equal targets + if ( + (rA.mpFontData && rA.mpFontData->IsSymbolFont()) || + (rB.mpFontData && rB.mpFontData->IsSymbolFont()) + ) + { + if (rA.maTargetName != rB.maTargetName) + return false; + } + +#ifdef ENABLE_GRAPHITE + // 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; +} + +// ----------------------------------------------------------------------- + +ImplFontCache::ImplFontCache( bool bPrinter ) +: mpFirstEntry( NULL ), + mnRef0Count( 0 ), + mbPrinter( bPrinter ) +{} + +// ----------------------------------------------------------------------- + +ImplFontCache::~ImplFontCache() +{ + FontInstanceList::iterator it = maFontInstanceList.begin(); + for(; it != maFontInstanceList.end(); ++it ) + { + ImplFontEntry* pEntry = (*it).second; + delete pEntry; + } +} + +// ----------------------------------------------------------------------- + +ImplFontEntry* ImplFontCache::GetFontEntry( ImplDevFontList* pFontList, + const Font& rFont, const Size& rSize, float fExactHeight, ImplDirectFontSubstitution* pDevSpecific ) +{ + String aSearchName = rFont.GetName(); + + // TODO: also add device specific name caching + if( !pDevSpecific ) + { + // check if the requested font name is already known + // if it is already known get its normalized search name + FontNameList::const_iterator it_name = maFontNameList.find( aSearchName ); + if( it_name != maFontNameList.end() ) + if( !(*it_name).second.EqualsAscii( "hg", 0, 2) +#ifdef ENABLE_GRAPHITE + && (aSearchName.Search(grutils::GrFeatureParser::FEAT_PREFIX) + == STRING_NOTFOUND) +#endif + ) + aSearchName = (*it_name).second; + } + + // initialize internal font request object + ImplFontSelectData aFontSelData( rFont, aSearchName, rSize, fExactHeight ); + return GetFontEntry( pFontList, aFontSelData, pDevSpecific ); +} + +// ----------------------------------------------------------------------- + +ImplFontEntry* ImplFontCache::GetFontEntry( ImplDevFontList* pFontList, + ImplFontSelectData& aFontSelData, ImplDirectFontSubstitution* pDevSpecific ) +{ + // check if a directly matching logical font instance is already cached, + // the most recently used font usually has a hit rate of >50% + ImplFontEntry *pEntry = NULL; + ImplDevFontListData* pFontFamily = NULL; + IFSD_Equal aIFSD_Equal; + if( mpFirstEntry && aIFSD_Equal( aFontSelData, mpFirstEntry->maFontSelData ) ) + pEntry = mpFirstEntry; + else + { + FontInstanceList::iterator it = maFontInstanceList.find( aFontSelData ); + if( it != maFontInstanceList.end() ) + pEntry = (*it).second; + } + + if( !pEntry ) // no direct cache hit + { + // find the best matching logical font family and update font selector accordingly + pFontFamily = pFontList->ImplFindByFont( aFontSelData, mbPrinter, pDevSpecific ); + DBG_ASSERT( (pFontFamily != NULL), "ImplFontCache::Get() No logical font found!" ); + if( pFontFamily ) + aFontSelData.maSearchName = pFontFamily->GetSearchName(); + + // check if an indirectly matching logical font instance is already cached + FontInstanceList::iterator it = maFontInstanceList.find( aFontSelData ); + if( it != maFontInstanceList.end() ) + { + // we have an indirect cache hit + pEntry = (*it).second; + // cache the requested and the selected font names + // => next time there is a good chance for a direct cache hit + // don't allow the cache to grow too big + // TODO: implement some fancy LRU caching? + if( maFontNameList.size() >= 4000 ) + maFontNameList.clear(); + // TODO: also add device specific name caching + if( !pDevSpecific ) + if( aFontSelData.maName != aFontSelData.maSearchName ) + maFontNameList[ aFontSelData.maName ] = aFontSelData.maSearchName; + } + } + + if( pEntry ) // cache hit => use existing font instance + { + // increase the font instance's reference count + if( !pEntry->mnRefCount++ ) + --mnRef0Count; + } + else // no cache hit => create a new font instance + { + // find the best matching physical font face + ImplFontData* pFontData = pFontFamily->FindBestFontFace( aFontSelData ); + aFontSelData.mpFontData = pFontData; + + // create a new logical font instance from this physical font face + pEntry = pFontData->CreateFontInstance( aFontSelData ); + + // if we found a different symbol font we need a symbol conversion table + if( pFontData->IsSymbolFont() ) + if( aFontSelData.maTargetName != aFontSelData.maSearchName ) + pEntry->mpConversion = ConvertChar::GetRecodeData( aFontSelData.maTargetName, aFontSelData.maSearchName ); + + // add the new entry to the cache + maFontInstanceList[ aFontSelData ] = pEntry; + } + + mpFirstEntry = pEntry; + return pEntry; +} + +// ----------------------------------------------------------------------- + +ImplDevFontListData* ImplDevFontList::ImplFindByFont( ImplFontSelectData& rFSD, + bool bPrinter, ImplDirectFontSubstitution* pDevSpecific ) const +{ + // give up if no fonts are available + if( !Count() ) + return NULL; + + // test if a font in the token list is available + // substitute the font if this was requested + USHORT nSubstFlags = FONT_SUBSTITUTE_ALWAYS; + if ( bPrinter ) + nSubstFlags |= FONT_SUBSTITUTE_SCREENONLY; + + bool bMultiToken = false; + xub_StrLen nTokenPos = 0; + String& aSearchName = rFSD.maSearchName; // TODO: get rid of reference + for(;;) + { + rFSD.maTargetName = GetNextFontToken( rFSD.maName, nTokenPos ); + aSearchName = rFSD.maTargetName; + +#ifdef ENABLE_GRAPHITE + // Until features are properly supported, they are appended to the + // font name, so we need to strip them off so the font is found. + xub_StrLen nFeat = aSearchName.Search(grutils::GrFeatureParser::FEAT_PREFIX); + String aOrigName = rFSD.maTargetName; + String aBaseFontName(aSearchName, 0, (nFeat != STRING_NOTFOUND)?nFeat:aSearchName.Len()); + if (nFeat != STRING_NOTFOUND && STRING_NOTFOUND != + aSearchName.Search(grutils::GrFeatureParser::FEAT_ID_VALUE_SEPARATOR, nFeat)) + { + aSearchName = aBaseFontName; + rFSD.maTargetName = aBaseFontName; + } + +#endif + + GetEnglishSearchFontName( aSearchName ); + ImplFontSubstitute( aSearchName, nSubstFlags, pDevSpecific ); + // #114999# special emboldening for Ricoh fonts + // TODO: smarter check for special cases by using PreMatch infrastructure? + if( (rFSD.meWeight > WEIGHT_MEDIUM) + && aSearchName.EqualsAscii( "hg", 0, 2) ) + { + String aBoldName; + if( aSearchName.EqualsAscii( "hggothicb", 0, 9) ) + aBoldName = String(RTL_CONSTASCII_USTRINGPARAM("hggothice")); + else if( aSearchName.EqualsAscii( "hgpgothicb", 0, 10) ) + aBoldName = String(RTL_CONSTASCII_USTRINGPARAM("hgpgothice")); + else if( aSearchName.EqualsAscii( "hgminchol", 0, 9) ) + aBoldName = String(RTL_CONSTASCII_USTRINGPARAM("hgminchob")); + else if( aSearchName.EqualsAscii( "hgpminchol", 0, 10) ) + aBoldName = String(RTL_CONSTASCII_USTRINGPARAM("hgpminchob")); + else if( aSearchName.EqualsAscii( "hgminchob" ) ) + aBoldName = String(RTL_CONSTASCII_USTRINGPARAM("hgminchoe")); + else if( aSearchName.EqualsAscii( "hgpminchob" ) ) + aBoldName = String(RTL_CONSTASCII_USTRINGPARAM("hgpminchoe")); + + if( aBoldName.Len() && ImplFindBySearchName( aBoldName ) ) + { + // the other font is available => use it + aSearchName = aBoldName; + // prevent synthetic emboldening of bold version + rFSD.meWeight = WEIGHT_DONTKNOW; + } + } + +#ifdef ENABLE_GRAPHITE + // restore the features to make the font selection data unique + rFSD.maTargetName = aOrigName; +#endif + // check if the current font name token or its substitute is valid + ImplDevFontListData* pFoundData = ImplFindBySearchName( aSearchName ); + if( pFoundData ) + return pFoundData; + + // some systems provide special customization + // e.g. they suggest "serif" as UI-font, but this name cannot be used directly + // because the system wants to map it to another font first, e.g. "Helvetica" +#ifdef ENABLE_GRAPHITE + // use the target name to search in the prematch hook + rFSD.maTargetName = aBaseFontName; +#endif + if( mpPreMatchHook ) + if( mpPreMatchHook->FindFontSubstitute( rFSD ) ) + GetEnglishSearchFontName( aSearchName ); +#ifdef ENABLE_GRAPHITE + // the prematch hook uses the target name to search, but we now need + // to restore the features to make the font selection data unique + rFSD.maTargetName = aOrigName; +#endif + pFoundData = ImplFindBySearchName( aSearchName ); + if( pFoundData ) + return pFoundData; + + // break after last font name token was checked unsuccessfully + if( nTokenPos == STRING_NOTFOUND) + break; + bMultiToken = true; + } + + // if the first font was not available find the next available font in + // the semicolon separated list of font names. A font is also considered + // available when there is a matching entry in the Tools->Options->Fonts + // dialog witho neither ALWAYS nor SCREENONLY flags set and the substitution + // font is available + for( nTokenPos = 0; nTokenPos != STRING_NOTFOUND; ) + { + if( bMultiToken ) + { + rFSD.maTargetName = GetNextFontToken( rFSD.maName, nTokenPos ); + aSearchName = rFSD.maTargetName; + GetEnglishSearchFontName( aSearchName ); + } + else + nTokenPos = STRING_NOTFOUND; + if( mpPreMatchHook ) + if( mpPreMatchHook->FindFontSubstitute( rFSD ) ) + GetEnglishSearchFontName( aSearchName ); + ImplFontSubstitute( aSearchName, nSubstFlags, pDevSpecific ); + ImplDevFontListData* pFoundData = ImplFindBySearchName( aSearchName ); + if( pFoundData ) + return pFoundData; + } + + // if no font with a directly matching name is available use the + // first font name token and get its attributes to find a replacement + if ( bMultiToken ) + { + nTokenPos = 0; + rFSD.maTargetName = GetNextFontToken( rFSD.maName, nTokenPos ); + aSearchName = rFSD.maTargetName; + GetEnglishSearchFontName( aSearchName ); + } + + String aSearchShortName; + String aSearchFamilyName; + FontWeight eSearchWeight = rFSD.meWeight; + FontWidth eSearchWidth = rFSD.meWidthType; + ULONG nSearchType = 0; + FontSubstConfiguration::getMapName( aSearchName, aSearchShortName, aSearchFamilyName, + eSearchWeight, eSearchWidth, nSearchType ); + + // note: the search name was already translated to english (if possible) + + // use the font's shortened name if needed + if ( aSearchShortName != aSearchName ) + { + ImplDevFontListData* pFoundData = ImplFindBySearchName( aSearchShortName ); + if( pFoundData ) + { +#ifdef UNX + /* #96738# don't use mincho as an replacement for "MS Mincho" on X11: Mincho is + a korean bitmap font that is not suitable here. Use the font replacement table, + that automatically leads to the desired "HG Mincho Light J". Same story for + MS Gothic, there are thai and korean "Gothic" fonts, so we even prefer Andale */ + static String aMS_Mincho( RTL_CONSTASCII_USTRINGPARAM("msmincho") ); + static String aMS_Gothic( RTL_CONSTASCII_USTRINGPARAM("msgothic") ); + if ((aSearchName != aMS_Mincho) && (aSearchName != aMS_Gothic)) + // TODO: add heuristic to only throw out the fake ms* fonts +#endif + { + return pFoundData; + } + } + } + + // use font fallback + const FontNameAttr* pFontAttr = NULL; + if( aSearchName.Len() ) + { + // get fallback info using FontSubstConfiguration and + // the target name, it's shortened name and family name in that order + const FontSubstConfiguration& rFontSubst = *FontSubstConfiguration::get(); + pFontAttr = rFontSubst.getSubstInfo( aSearchName ); + if ( !pFontAttr && (aSearchShortName != aSearchName) ) + pFontAttr = rFontSubst.getSubstInfo( aSearchShortName ); + if ( !pFontAttr && (aSearchFamilyName != aSearchShortName) ) + pFontAttr = rFontSubst.getSubstInfo( aSearchFamilyName ); + + // try the font substitutions suggested by the fallback info + if( pFontAttr ) + { + ImplDevFontListData* pFoundData = ImplFindBySubstFontAttr( *pFontAttr ); + if( pFoundData ) + return pFoundData; + } + } + + // if a target symbol font is not available use a default symbol font + if( rFSD.IsSymbolFont() ) + { + com::sun::star::lang::Locale aDefaultLocale( OUString( RTL_CONSTASCII_USTRINGPARAM("en") ), OUString(), OUString() ); + aSearchName = DefaultFontConfiguration::get()->getDefaultFont( aDefaultLocale, DEFAULTFONT_SYMBOL ); + ImplDevFontListData* pFoundData = ImplFindByTokenNames( aSearchName ); + if( pFoundData ) + return pFoundData; + } + + // now try the other font name tokens + while( nTokenPos != STRING_NOTFOUND ) + { + rFSD.maTargetName = GetNextFontToken( rFSD.maName, nTokenPos ); + if( !rFSD.maTargetName.Len() ) + continue; + + aSearchName = rFSD.maTargetName; + GetEnglishSearchFontName( aSearchName ); + + String aTempShortName; + String aTempFamilyName; + ULONG nTempType = 0; + FontWeight eTempWeight = rFSD.meWeight; + FontWidth eTempWidth = WIDTH_DONTKNOW; + FontSubstConfiguration::getMapName( aSearchName, aTempShortName, aTempFamilyName, + eTempWeight, eTempWidth, nTempType ); + + // use a shortend token name if available + if( aTempShortName != aSearchName ) + { + ImplDevFontListData* pFoundData = ImplFindBySearchName( aTempShortName ); + if( pFoundData ) + return pFoundData; + } + + // use a font name from font fallback list to determine font attributes + + // get fallback info using FontSubstConfiguration and + // the target name, it's shortened name and family name in that order + const FontSubstConfiguration& rFontSubst = *FontSubstConfiguration::get(); + const FontNameAttr* pTempFontAttr = rFontSubst.getSubstInfo( aSearchName ); + if ( !pTempFontAttr && (aTempShortName != aSearchName) ) + pTempFontAttr = rFontSubst.getSubstInfo( aTempShortName ); + if ( !pTempFontAttr && (aTempFamilyName != aTempShortName) ) + pTempFontAttr = rFontSubst.getSubstInfo( aTempFamilyName ); + + // try the font substitutions suggested by the fallback info + if( pTempFontAttr ) + { + ImplDevFontListData* pFoundData = ImplFindBySubstFontAttr( *pTempFontAttr ); + if( pFoundData ) + return pFoundData; + if( !pFontAttr ) + pFontAttr = pTempFontAttr; + } + } + + // if still needed use the alias names of the installed fonts + if( mbMapNames ) + { + ImplDevFontListData* pFoundData = ImplFindByAliasName( rFSD.maTargetName, aSearchShortName ); + if( pFoundData ) + return pFoundData; + } + + // if still needed use the font request's attributes to find a good match + switch( rFSD.meLanguage ) + { + case LANGUAGE_CHINESE: + case LANGUAGE_CHINESE_SIMPLIFIED: + case LANGUAGE_CHINESE_SINGAPORE: + nSearchType |= IMPL_FONT_ATTR_CJK | IMPL_FONT_ATTR_CJK_SC; + break; + case LANGUAGE_CHINESE_TRADITIONAL: + case LANGUAGE_CHINESE_HONGKONG: + case LANGUAGE_CHINESE_MACAU: + nSearchType |= IMPL_FONT_ATTR_CJK | IMPL_FONT_ATTR_CJK_TC; + break; + case LANGUAGE_KOREAN: + case LANGUAGE_KOREAN_JOHAB: + nSearchType |= IMPL_FONT_ATTR_CJK | IMPL_FONT_ATTR_CJK_KR; + break; + case LANGUAGE_JAPANESE: + nSearchType |= IMPL_FONT_ATTR_CJK | IMPL_FONT_ATTR_CJK_JP; + break; + default: + nSearchType |= ImplIsCJKFont( rFSD.maName ); + if( rFSD.IsSymbolFont() ) + nSearchType |= IMPL_FONT_ATTR_SYMBOL; + break; + } + + ImplCalcType( nSearchType, eSearchWeight, eSearchWidth, rFSD.meFamily, pFontAttr ); + ImplDevFontListData* pFoundData = ImplFindByAttributes( nSearchType, + eSearchWeight, eSearchWidth, rFSD.meFamily, rFSD.meItalic, aSearchFamilyName ); + + if( pFoundData ) + { + // overwrite font selection attributes using info from the typeface flags + if( (eSearchWeight >= WEIGHT_BOLD) + && (eSearchWeight > rFSD.meWeight) + && (pFoundData->mnTypeFaces & IMPL_DEVFONT_BOLD) ) + rFSD.meWeight = eSearchWeight; + else if( (eSearchWeight < WEIGHT_NORMAL) + && (eSearchWeight < rFSD.meWeight) + && (eSearchWeight != WEIGHT_DONTKNOW) + && (pFoundData->mnTypeFaces & IMPL_DEVFONT_LIGHT) ) + rFSD.meWeight = eSearchWeight; + + if( (nSearchType & IMPL_FONT_ATTR_ITALIC) + && ((rFSD.meItalic == ITALIC_DONTKNOW) || (rFSD.meItalic == ITALIC_NONE)) + && (pFoundData->mnTypeFaces & IMPL_DEVFONT_ITALIC) ) + rFSD.meItalic = ITALIC_NORMAL; + } + else + { + // if still needed fall back to default fonts + pFoundData = FindDefaultFont(); + } + + return pFoundData; +} + +// ----------------------------------------------------------------------- + +ImplFontEntry* ImplFontCache::GetGlyphFallbackFont( ImplDevFontList* pFontList, + ImplFontSelectData& rFontSelData, int nFallbackLevel, rtl::OUString& rMissingCodes ) +{ + // get a candidate font for glyph fallback + // unless the previously selected font got a device specific substitution + // e.g. PsPrint Arial->Helvetica for udiaeresis when Helvetica doesn't support it + if( nFallbackLevel >= 1) + { + ImplDevFontListData* pFallbackData = pFontList->GetGlyphFallbackFont( + rFontSelData, rMissingCodes, nFallbackLevel-1 ); + // escape when there are no font candidates + if( !pFallbackData ) + return NULL; + // override the font name + rFontSelData.maName = pFallbackData->GetFamilyName(); + // clear the cached normalized name + rFontSelData.maSearchName = String(); + } + + // get device font without doing device specific substitutions + ImplFontEntry* pFallbackFont = GetFontEntry( pFontList, rFontSelData, NULL ); + return pFallbackFont; +} + +// ----------------------------------------------------------------------- + +void ImplFontCache::Release( ImplFontEntry* pEntry ) +{ + static const int FONTCACHE_MAX = 50; + + DBG_ASSERT( (pEntry->mnRefCount > 0), "ImplFontCache::Release() - font refcount underflow" ); + if( --pEntry->mnRefCount > 0 ) + return; + + if( ++mnRef0Count < FONTCACHE_MAX ) + return; + + // remove unused entries from font instance cache + FontInstanceList::iterator it_next = maFontInstanceList.begin(); + while( it_next != maFontInstanceList.end() ) + { + FontInstanceList::iterator it = it_next++; + ImplFontEntry* pFontEntry = (*it).second; + if( pFontEntry->mnRefCount > 0 ) + continue; + + maFontInstanceList.erase( it ); + delete pFontEntry; + --mnRef0Count; + DBG_ASSERT( (mnRef0Count>=0), "ImplFontCache::Release() - refcount0 underflow" ); + + if( mpFirstEntry == pFontEntry ) + mpFirstEntry = NULL; + } + + DBG_ASSERT( (mnRef0Count==0), "ImplFontCache::Release() - refcount0 mismatch" ); +} + +// ----------------------------------------------------------------------- + +void ImplFontCache::Invalidate() +{ + // delete unreferenced entries + FontInstanceList::iterator it = maFontInstanceList.begin(); + for(; it != maFontInstanceList.end(); ++it ) + { + ImplFontEntry* pFontEntry = (*it).second; + if( pFontEntry->mnRefCount > 0 ) + continue; + + delete pFontEntry; + --mnRef0Count; + } + + // #112304# make sure the font cache is really clean + mpFirstEntry = NULL; + maFontInstanceList.clear(); + + DBG_ASSERT( (mnRef0Count==0), "ImplFontCache::Invalidate() - mnRef0Count non-zero" ); + +#ifdef USE_BUILTIN_RASTERIZER + // TODO: eventually move into SalGraphics layer + GlyphCache::GetInstance().InvalidateAllGlyphs(); +#endif +} + +// ======================================================================= + +ImplMultiTextLineInfo::ImplMultiTextLineInfo() +{ + mpLines = new PImplTextLineInfo[MULTITEXTLINEINFO_RESIZE]; + mnLines = 0; + mnSize = MULTITEXTLINEINFO_RESIZE; +} + + +ImplMultiTextLineInfo::~ImplMultiTextLineInfo() +{ + for ( xub_StrLen i = 0; i < mnLines; i++ ) + delete mpLines[i]; + delete [] mpLines; +} + +void ImplMultiTextLineInfo::AddLine( ImplTextLineInfo* pLine ) +{ + if ( mnSize == mnLines ) + { + mnSize += MULTITEXTLINEINFO_RESIZE; + PImplTextLineInfo* pNewLines = new PImplTextLineInfo[mnSize]; + memcpy( pNewLines, mpLines, mnLines*sizeof(PImplTextLineInfo) ); + mpLines = pNewLines; + } + + mpLines[mnLines] = pLine; + mnLines++; +} + +void ImplMultiTextLineInfo::Clear() +{ + for ( xub_StrLen i = 0; i < mnLines; i++ ) + delete mpLines[i]; + mnLines = 0; +} + +// ======================================================================= + +FontEmphasisMark OutputDevice::ImplGetEmphasisMarkStyle( const Font& rFont ) +{ + FontEmphasisMark nEmphasisMark = rFont.GetEmphasisMark(); + + // If no Position is set, then calculate the default position, which + // depends on the language + if ( !(nEmphasisMark & (EMPHASISMARK_POS_ABOVE | EMPHASISMARK_POS_BELOW)) ) + { + LanguageType eLang = rFont.GetLanguage(); + // In Chinese Simplified the EmphasisMarks are below/left + if ( (eLang == LANGUAGE_CHINESE_SIMPLIFIED) || + (eLang == LANGUAGE_CHINESE_SINGAPORE) ) + nEmphasisMark |= EMPHASISMARK_POS_BELOW; + else + { + eLang = rFont.GetCJKContextLanguage(); + // In Chinese Simplified the EmphasisMarks are below/left + if ( (eLang == LANGUAGE_CHINESE_SIMPLIFIED) || + (eLang == LANGUAGE_CHINESE_SINGAPORE) ) + nEmphasisMark |= EMPHASISMARK_POS_BELOW; + else + nEmphasisMark |= EMPHASISMARK_POS_ABOVE; + } + } + + return nEmphasisMark; +} + +// ----------------------------------------------------------------------- + +BOOL OutputDevice::ImplIsUnderlineAbove( const Font& rFont ) +{ + if ( !rFont.IsVertical() ) + return FALSE; + + if( (LANGUAGE_JAPANESE == rFont.GetLanguage()) + || (LANGUAGE_JAPANESE == rFont.GetCJKContextLanguage()) ) + // the underline is right for Japanese only + return TRUE; + + return FALSE; +} + +// ======================================================================= + +void OutputDevice::ImplInitFontList() const +{ + if( ! mpFontList->Count() ) + { + if( mpGraphics || ImplGetGraphics() ) + { + RTL_LOGFILE_CONTEXT( aLog, "OutputDevice::ImplInitFontList()" ); + mpGraphics->GetDevFontList( mpFontList ); + } + } +} + +// ======================================================================= + +void OutputDevice::ImplInitFont() const +{ + DBG_TESTSOLARMUTEX(); + + if ( mbInitFont ) + { + if ( meOutDevType != OUTDEV_PRINTER ) + { + // decide if antialiasing is appropriate + bool bNonAntialiased = (GetAntialiasing() & ANTIALIASING_DISABLE_TEXT) != 0; + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + bNonAntialiased |= ((rStyleSettings.GetDisplayOptions() & DISPLAY_OPTION_AA_DISABLE) != 0); + bNonAntialiased |= (int(rStyleSettings.GetAntialiasingMinPixelHeight()) > mpFontEntry->maFontSelData.mnHeight); + mpFontEntry->maFontSelData.mbNonAntialiased = bNonAntialiased; + } + + if( !mpPDFWriter || !mpPDFWriter->isBuiltinFont( mpFontEntry->maFontSelData.mpFontData ) ) + { + // select font in the device layers + mpFontEntry->mnSetFontFlags = mpGraphics->SetFont( &(mpFontEntry->maFontSelData), 0 ); + } + mbInitFont = false; + } +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplInitTextColor() +{ + DBG_TESTSOLARMUTEX(); + + if ( mbInitTextColor ) + { + mpGraphics->SetTextColor( ImplColorToSal( GetTextColor() ) ); + mbInitTextColor = FALSE; + } +} + +// ----------------------------------------------------------------------- + +bool OutputDevice::ImplNewFont() const +{ + DBG_TESTSOLARMUTEX(); + + // get correct font list on the PDF writer if necessary + if( mpPDFWriter ) + { + const ImplSVData* pSVData = ImplGetSVData(); + if( mpFontList == pSVData->maGDIData.mpScreenFontList + || mpFontCache == pSVData->maGDIData.mpScreenFontCache ) + const_cast<OutputDevice&>(*this).ImplUpdateFontData( true ); + } + + if ( !mbNewFont ) + return true; + + // we need a graphics + if ( !mpGraphics && !ImplGetGraphics() ) + return false; + SalGraphics* pGraphics = mpGraphics; + ImplInitFontList(); + + // convert to pixel height + // TODO: replace integer based aSize completely with subpixel accurate type + float fExactHeight = ImplFloatLogicHeightToDevicePixel( static_cast<float>(maFont.GetHeight()) ); + Size aSize = ImplLogicToDevicePixel( maFont.GetSize() ); + if ( !aSize.Height() ) + { + // use default pixel height only when logical height is zero + if ( maFont.GetSize().Height() ) + aSize.Height() = 1; + else + aSize.Height() = (12*mnDPIY)/72; + fExactHeight = static_cast<float>(aSize.Height()); + } + + // select the default width only when logical width is zero + if( (0 == aSize.Width()) && (0 != maFont.GetSize().Width()) ) + aSize.Width() = 1; + + // get font entry + ImplDirectFontSubstitution* pDevSpecificSubst = NULL; + if( mpOutDevData ) + pDevSpecificSubst = &mpOutDevData->maDevFontSubst; + ImplFontEntry* pOldEntry = mpFontEntry; + mpFontEntry = mpFontCache->GetFontEntry( mpFontList, maFont, aSize, fExactHeight, pDevSpecificSubst ); + if( pOldEntry ) + mpFontCache->Release( pOldEntry ); + + ImplFontEntry* pFontEntry = mpFontEntry; + // mark when lower layers need to get involved + mbNewFont = FALSE; + if( pFontEntry != pOldEntry ) + mbInitFont = TRUE; + + // select font when it has not been initialized yet + if ( !pFontEntry->mbInit ) + { + ImplInitFont(); + + // get metric data from device layers + if ( pGraphics ) + { + pFontEntry->mbInit = true; + + pFontEntry->maMetric.mnOrientation = sal::static_int_cast<short>(pFontEntry->maFontSelData.mnOrientation); + if( mpPDFWriter && mpPDFWriter->isBuiltinFont( pFontEntry->maFontSelData.mpFontData ) ) + mpPDFWriter->getFontMetric( &pFontEntry->maFontSelData, &(pFontEntry->maMetric) ); + else + pGraphics->GetFontMetric( &(pFontEntry->maMetric) ); + + pFontEntry->maMetric.ImplInitTextLineSize( this ); + pFontEntry->maMetric.ImplInitAboveTextLineSize(); + + pFontEntry->mnLineHeight = pFontEntry->maMetric.mnAscent + pFontEntry->maMetric.mnDescent; + + if( pFontEntry->maFontSelData.mnOrientation + && !pFontEntry->maMetric.mnOrientation + && (meOutDevType != OUTDEV_PRINTER) ) + { + pFontEntry->mnOwnOrientation = sal::static_int_cast<short>(pFontEntry->maFontSelData.mnOrientation); + pFontEntry->mnOrientation = pFontEntry->mnOwnOrientation; + } + else + pFontEntry->mnOrientation = pFontEntry->maMetric.mnOrientation; + } + } + + // enable kerning array if requested + if ( maFont.GetKerning() & KERNING_FONTSPECIFIC ) + { + // TODO: test if physical font supports kerning and disable if not + if( pFontEntry->maMetric.mbKernableFont ) + mbKerning = true; + } + else + mbKerning = false; + if ( maFont.GetKerning() & KERNING_ASIAN ) + mbKerning = true; + + // calculate EmphasisArea + mnEmphasisAscent = 0; + mnEmphasisDescent = 0; + if ( maFont.GetEmphasisMark() & EMPHASISMARK_STYLE ) + { + FontEmphasisMark nEmphasisMark = ImplGetEmphasisMarkStyle( maFont ); + long nEmphasisHeight = (pFontEntry->mnLineHeight*250)/1000; + if ( nEmphasisHeight < 1 ) + nEmphasisHeight = 1; + if ( nEmphasisMark & EMPHASISMARK_POS_BELOW ) + mnEmphasisDescent = nEmphasisHeight; + else + mnEmphasisAscent = nEmphasisHeight; + } + + // calculate text offset depending on TextAlignment + TextAlign eAlign = maFont.GetAlign(); + if ( eAlign == ALIGN_BASELINE ) + { + mnTextOffX = 0; + mnTextOffY = 0; + } + else if ( eAlign == ALIGN_TOP ) + { + mnTextOffX = 0; + mnTextOffY = +pFontEntry->maMetric.mnAscent + mnEmphasisAscent; + if ( pFontEntry->mnOrientation ) + ImplRotatePos( 0, 0, mnTextOffX, mnTextOffY, pFontEntry->mnOrientation ); + } + else // eAlign == ALIGN_BOTTOM + { + mnTextOffX = 0; + mnTextOffY = -pFontEntry->maMetric.mnDescent + mnEmphasisDescent; + if ( pFontEntry->mnOrientation ) + ImplRotatePos( 0, 0, mnTextOffX, mnTextOffY, pFontEntry->mnOrientation ); + } + + mbTextLines = ((maFont.GetUnderline() != UNDERLINE_NONE) && (maFont.GetUnderline() != UNDERLINE_DONTKNOW)) || + ((maFont.GetOverline() != UNDERLINE_NONE) && (maFont.GetOverline() != UNDERLINE_DONTKNOW)) || + ((maFont.GetStrikeout() != STRIKEOUT_NONE) && (maFont.GetStrikeout() != STRIKEOUT_DONTKNOW)); + mbTextSpecial = maFont.IsShadow() || maFont.IsOutline() || + (maFont.GetRelief() != RELIEF_NONE); + + // #95414# fix for OLE objects which use scale factors very creatively + if( mbMap && !aSize.Width() ) + { + int nOrigWidth = pFontEntry->maMetric.mnWidth; + float fStretch = (float)maMapRes.mnMapScNumX * maMapRes.mnMapScDenomY; + fStretch /= (float)maMapRes.mnMapScNumY * maMapRes.mnMapScDenomX; + int nNewWidth = (int)(nOrigWidth * fStretch + 0.5); + if( (nNewWidth != nOrigWidth) && (nNewWidth != 0) ) + { + Size aOrigSize = maFont.GetSize(); + const_cast<Font&>(maFont).SetSize( Size( nNewWidth, aSize.Height() ) ); + mbMap = FALSE; + mbNewFont = TRUE; + ImplNewFont(); // recurse once using stretched width + mbMap = TRUE; + const_cast<Font&>(maFont).SetSize( aOrigSize ); + } + } + + return true; +} + +// ----------------------------------------------------------------------- + +long OutputDevice::ImplGetTextWidth( const SalLayout& rSalLayout ) const +{ + long nWidth = rSalLayout.GetTextWidth(); + nWidth /= rSalLayout.GetUnitsPerPixel(); + return nWidth; +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawTextRect( long nBaseX, long nBaseY, + long nX, long nY, long nWidth, long nHeight ) +{ + short nOrientation = mpFontEntry->mnOrientation; + if ( nOrientation ) + { + // Rotate rect without rounding problems for 90 degree rotations + if ( !(nOrientation % 900) ) + { + nX -= nBaseX; + nY -= nBaseY; + + if ( nOrientation == 900 ) + { + long nTemp = nX; + nX = nY; + nY = -nTemp; + nTemp = nWidth; + nWidth = nHeight; + nHeight = nTemp; + nY -= nHeight; + } + else if ( nOrientation == 1800 ) + { + nX = -nX; + nY = -nY; + nX -= nWidth; + nY -= nHeight; + } + else /* ( nOrientation == 2700 ) */ + { + long nTemp = nX; + nX = -nY; + nY = nTemp; + nTemp = nWidth; + nWidth = nHeight; + nHeight = nTemp; + nX -= nWidth; + } + + nX += nBaseX; + nY += nBaseY; + } + else + { + // inflate because polygons are drawn smaller + Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) ); + Polygon aPoly( aRect ); + aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontEntry->mnOrientation ); + ImplDrawPolygon( aPoly ); + return; + } + } + + mpGraphics->DrawRect( nX, nY, nWidth, nHeight, this ); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawTextBackground( const SalLayout& rSalLayout ) +{ + const long nWidth = rSalLayout.GetTextWidth() / rSalLayout.GetUnitsPerPixel(); + const Point aBase = rSalLayout.DrawBase(); + const long nX = aBase.X(); + const long nY = aBase.Y(); + + if ( mbLineColor || mbInitLineColor ) + { + mpGraphics->SetLineColor(); + mbInitLineColor = TRUE; + } + mpGraphics->SetFillColor( ImplColorToSal( GetTextFillColor() ) ); + mbInitFillColor = TRUE; + + ImplDrawTextRect( nX, nY, nX, nY-mpFontEntry->maMetric.mnAscent-mnEmphasisAscent, + nWidth, + mpFontEntry->mnLineHeight+mnEmphasisAscent+mnEmphasisDescent ); +} + +// ----------------------------------------------------------------------- + +Rectangle OutputDevice::ImplGetTextBoundRect( const SalLayout& rSalLayout ) +{ + Point aPoint = rSalLayout.GetDrawPosition(); + long nX = aPoint.X(); + long nY = aPoint.Y(); + + long nWidth = rSalLayout.GetTextWidth(); + long nHeight = mpFontEntry->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent; + + nY -= mpFontEntry->maMetric.mnAscent + mnEmphasisAscent; + + if ( mpFontEntry->mnOrientation ) + { + long nBaseX = nX, nBaseY = nY; + if ( !(mpFontEntry->mnOrientation % 900) ) + { + long nX2 = nX+nWidth; + long nY2 = nY+nHeight; + ImplRotatePos( nBaseX, nBaseY, nX, nY, mpFontEntry->mnOrientation ); + ImplRotatePos( nBaseX, nBaseY, nX2, nY2, mpFontEntry->mnOrientation ); + nWidth = nX2-nX; + nHeight = nY2-nY; + } + else + { + // inflate by +1+1 because polygons are drawn smaller + Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) ); + Polygon aPoly( aRect ); + aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontEntry->mnOrientation ); + return aPoly.GetBoundRect(); + } + } + + return Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplInitTextLineSize() +{ + mpFontEntry->maMetric.ImplInitTextLineSize( this ); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplInitAboveTextLineSize() +{ + mpFontEntry->maMetric.ImplInitAboveTextLineSize(); +} + +// ----------------------------------------------------------------------- + +ImplFontMetricData::ImplFontMetricData( const ImplFontSelectData& rFontSelData ) +: ImplFontAttributes( rFontSelData ) +{ + // initialize the members provided by the font request + mnWidth = rFontSelData.mnWidth; + mnOrientation = sal::static_int_cast<short>(rFontSelData.mnOrientation); + + // intialize the used font name + if( rFontSelData.mpFontData ) + { + maName = rFontSelData.mpFontData->maName; + maStyleName= rFontSelData.mpFontData->maStyleName; + mbDevice = rFontSelData.mpFontData->mbDevice; + mbKernableFont = true; + } + else + { + xub_StrLen nTokenPos = 0; + maName = GetNextFontToken( rFontSelData.maName, nTokenPos ); + maStyleName= rFontSelData.maStyleName; + mbDevice = false; + mbKernableFont = false; + } + + // reset metrics that are usually measured for the font instance + mnAscent = 0; + mnDescent = 0; + mnIntLeading = 0; + mnExtLeading = 0; + mnSlant = 0; + mnMinKashida = 0; + + // reset metrics that are usually derived from the measurements + mnUnderlineSize = 0; + mnUnderlineOffset = 0; + mnBUnderlineSize = 0; + mnBUnderlineOffset = 0; + mnDUnderlineSize = 0; + mnDUnderlineOffset1 = 0; + mnDUnderlineOffset2 = 0; + mnWUnderlineSize = 0; + mnWUnderlineOffset = 0; + mnAboveUnderlineSize = 0; + mnAboveUnderlineOffset = 0; + mnAboveBUnderlineSize = 0; + mnAboveBUnderlineOffset = 0; + mnAboveDUnderlineSize = 0; + mnAboveDUnderlineOffset1 = 0; + mnAboveDUnderlineOffset2 = 0; + mnAboveWUnderlineSize = 0; + mnAboveWUnderlineOffset = 0; + mnStrikeoutSize = 0; + mnStrikeoutOffset = 0; + mnBStrikeoutSize = 0; + mnBStrikeoutOffset = 0; + mnDStrikeoutSize = 0; + mnDStrikeoutOffset1 = 0; + mnDStrikeoutOffset2 = 0; +} + +// ----------------------------------------------------------------------- + +void ImplFontMetricData::ImplInitTextLineSize( const OutputDevice* pDev ) +{ + long nDescent = mnDescent; + if ( nDescent <= 0 ) + { + nDescent = mnAscent / 10; + if ( !nDescent ) + nDescent = 1; + } + + // #i55341# for some fonts it is not a good idea to calculate + // their text line metrics from the real font descent + // => work around this problem just for these fonts + if( 3*nDescent > mnAscent ) + nDescent = mnAscent / 3; + + long nLineHeight = ((nDescent*25)+50) / 100; + if ( !nLineHeight ) + nLineHeight = 1; + long nLineHeight2 = nLineHeight / 2; + if ( !nLineHeight2 ) + nLineHeight2 = 1; + + long nBLineHeight = ((nDescent*50)+50) / 100; + if ( nBLineHeight == nLineHeight ) + nBLineHeight++; + long nBLineHeight2 = nBLineHeight/2; + if ( !nBLineHeight2 ) + nBLineHeight2 = 1; + + long n2LineHeight = ((nDescent*16)+50) / 100; + if ( !n2LineHeight ) + n2LineHeight = 1; + long n2LineDY = n2LineHeight; + /* #117909# + * add some pixels to minimum double line distance on higher resolution devices + */ + long nMin2LineDY = 1 + pDev->ImplGetDPIY()/150; + if ( n2LineDY < nMin2LineDY ) + n2LineDY = nMin2LineDY; + long n2LineDY2 = n2LineDY/2; + if ( !n2LineDY2 ) + n2LineDY2 = 1; + + long nUnderlineOffset = mnDescent/2 + 1; + long nStrikeoutOffset = -((mnAscent - mnIntLeading) / 3); + + mnUnderlineSize = nLineHeight; + mnUnderlineOffset = nUnderlineOffset - nLineHeight2; + + mnBUnderlineSize = nBLineHeight; + mnBUnderlineOffset = nUnderlineOffset - nBLineHeight2; + + mnDUnderlineSize = n2LineHeight; + mnDUnderlineOffset1 = nUnderlineOffset - n2LineDY2 - n2LineHeight; + mnDUnderlineOffset2 = mnDUnderlineOffset1 + n2LineDY + n2LineHeight; + + long nWCalcSize = mnDescent; + if ( nWCalcSize < 6 ) + { + if ( (nWCalcSize == 1) || (nWCalcSize == 2) ) + mnWUnderlineSize = nWCalcSize; + else + mnWUnderlineSize = 3; + } + else + mnWUnderlineSize = ((nWCalcSize*50)+50) / 100; + + // #109280# the following line assures that wavelnes are never placed below the descent, however + // for most fonts the waveline then is drawn into the text, so we better keep the old solution + // pFontEntry->maMetric.mnWUnderlineOffset = pFontEntry->maMetric.mnDescent + 1 - pFontEntry->maMetric.mnWUnderlineSize; + mnWUnderlineOffset = nUnderlineOffset; + + mnStrikeoutSize = nLineHeight; + mnStrikeoutOffset = nStrikeoutOffset - nLineHeight2; + + mnBStrikeoutSize = nBLineHeight; + mnBStrikeoutOffset = nStrikeoutOffset - nBLineHeight2; + + mnDStrikeoutSize = n2LineHeight; + mnDStrikeoutOffset1 = nStrikeoutOffset - n2LineDY2 - n2LineHeight; + mnDStrikeoutOffset2 = mnDStrikeoutOffset1 + n2LineDY + n2LineHeight; +} + +// ----------------------------------------------------------------------- + +void ImplFontMetricData::ImplInitAboveTextLineSize() +{ + long nIntLeading = mnIntLeading; + // TODO: assess usage of nLeading below (changed in extleading CWS) + // if no leading is available, we assume 15% of the ascent + if ( nIntLeading <= 0 ) + { + nIntLeading = mnAscent*15/100; + if ( !nIntLeading ) + nIntLeading = 1; + } + + long nLineHeight = ((nIntLeading*25)+50) / 100; + if ( !nLineHeight ) + nLineHeight = 1; + + long nBLineHeight = ((nIntLeading*50)+50) / 100; + if ( nBLineHeight == nLineHeight ) + nBLineHeight++; + + long n2LineHeight = ((nIntLeading*16)+50) / 100; + if ( !n2LineHeight ) + n2LineHeight = 1; + + long nCeiling = -mnAscent; + + mnAboveUnderlineSize = nLineHeight; + mnAboveUnderlineOffset = nCeiling + (nIntLeading - nLineHeight + 1) / 2; + + mnAboveBUnderlineSize = nBLineHeight; + mnAboveBUnderlineOffset = nCeiling + (nIntLeading - nBLineHeight + 1) / 2; + + mnAboveDUnderlineSize = n2LineHeight; + mnAboveDUnderlineOffset1 = nCeiling + (nIntLeading - 3*n2LineHeight + 1) / 2; + mnAboveDUnderlineOffset2 = nCeiling + (nIntLeading + n2LineHeight + 1) / 2; + + long nWCalcSize = nIntLeading; + if ( nWCalcSize < 6 ) + { + if ( (nWCalcSize == 1) || (nWCalcSize == 2) ) + mnAboveWUnderlineSize = nWCalcSize; + else + mnAboveWUnderlineSize = 3; + } + else + mnAboveWUnderlineSize = ((nWCalcSize*50)+50) / 100; + + mnAboveWUnderlineOffset = nCeiling + (nIntLeading + 1) / 2; +} + +// ----------------------------------------------------------------------- + +static void ImplDrawWavePixel( long nOriginX, long nOriginY, + long nCurX, long nCurY, + short nOrientation, + SalGraphics* pGraphics, + OutputDevice* pOutDev, + BOOL bDrawPixAsRect, + + long nPixWidth, long nPixHeight ) +{ + if ( nOrientation ) + ImplRotatePos( nOriginX, nOriginY, nCurX, nCurY, nOrientation ); + + if ( bDrawPixAsRect ) + { + + pGraphics->DrawRect( nCurX, nCurY, nPixWidth, nPixHeight, pOutDev ); + } + else + { + pGraphics->DrawPixel( nCurX, nCurY, pOutDev ); + } +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawWaveLine( long nBaseX, long nBaseY, + long nStartX, long nStartY, + long nWidth, long nHeight, + long nLineWidth, short nOrientation, + const Color& rColor ) +{ + if ( !nHeight ) + return; + + // Bei Hoehe von 1 Pixel reicht es, eine Linie auszugeben + if ( (nLineWidth == 1) && (nHeight == 1) ) + { + mpGraphics->SetLineColor( ImplColorToSal( rColor ) ); + mbInitLineColor = TRUE; + + long nEndX = nStartX+nWidth; + long nEndY = nStartY; + if ( nOrientation ) + { + ImplRotatePos( nBaseX, nBaseY, nStartX, nStartY, nOrientation ); + ImplRotatePos( nBaseX, nBaseY, nEndX, nEndY, nOrientation ); + } + mpGraphics->DrawLine( nStartX, nStartY, nEndX, nEndY, this ); + + } + else + { + long nCurX = nStartX; + long nCurY = nStartY; + long nDiffX = 2; + long nDiffY = nHeight-1; + long nCount = nWidth; + long nOffY = -1; + long nFreq; + long i; + long nPixWidth; + long nPixHeight; + BOOL bDrawPixAsRect; + // Auf Druckern die Pixel per DrawRect() ausgeben + if ( (GetOutDevType() == OUTDEV_PRINTER) || (nLineWidth > 1) ) + { + if ( mbLineColor || mbInitLineColor ) + { + mpGraphics->SetLineColor(); + mbInitLineColor = TRUE; + } + mpGraphics->SetFillColor( ImplColorToSal( rColor ) ); + mbInitFillColor = TRUE; + bDrawPixAsRect = TRUE; + nPixWidth = nLineWidth; + nPixHeight = ((nLineWidth*mnDPIX)+(mnDPIY/2))/mnDPIY; + } + else + { + mpGraphics->SetLineColor( ImplColorToSal( rColor ) ); + mbInitLineColor = TRUE; + nPixWidth = 1; + nPixHeight = 1; + bDrawPixAsRect = FALSE; + } + + if ( !nDiffY ) + { + while ( nWidth ) + { + ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation, + mpGraphics, this, + bDrawPixAsRect, nPixWidth, nPixHeight ); + nCurX++; + nWidth--; + } + } + else + { + nCurY += nDiffY; + nFreq = nCount / (nDiffX+nDiffY); + while ( nFreq-- ) + { + for( i = nDiffY; i; --i ) + { + ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation, + mpGraphics, this, + bDrawPixAsRect, nPixWidth, nPixHeight ); + nCurX++; + nCurY += nOffY; + } + for( i = nDiffX; i; --i ) + { + ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation, + mpGraphics, this, + bDrawPixAsRect, nPixWidth, nPixHeight ); + nCurX++; + } + nOffY = -nOffY; + } + nFreq = nCount % (nDiffX+nDiffY); + if ( nFreq ) + { + for( i = nDiffY; i && nFreq; --i, --nFreq ) + { + ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation, + mpGraphics, this, + bDrawPixAsRect, nPixWidth, nPixHeight ); + nCurX++; + nCurY += nOffY; + + } + for( i = nDiffX; i && nFreq; --i, --nFreq ) + { + ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation, + mpGraphics, this, + bDrawPixAsRect, nPixWidth, nPixHeight ); + nCurX++; + } + } + } + + } +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawWaveTextLine( long nBaseX, long nBaseY, + long nX, long nY, long nWidth, + FontUnderline eTextLine, + Color aColor, + BOOL bIsAbove ) +{ + ImplFontEntry* pFontEntry = mpFontEntry; + long nLineHeight; + long nLinePos; + + if ( bIsAbove ) + { + nLineHeight = pFontEntry->maMetric.mnAboveWUnderlineSize; + nLinePos = pFontEntry->maMetric.mnAboveWUnderlineOffset; + } + else + { + nLineHeight = pFontEntry->maMetric.mnWUnderlineSize; + nLinePos = pFontEntry->maMetric.mnWUnderlineOffset; + } + if ( (eTextLine == UNDERLINE_SMALLWAVE) && (nLineHeight > 3) ) + nLineHeight = 3; + long nLineWidth = (mnDPIX/300); + if ( !nLineWidth ) + nLineWidth = 1; + if ( eTextLine == UNDERLINE_BOLDWAVE ) + nLineWidth *= 2; + nLinePos += nY - (nLineHeight / 2); + long nLineWidthHeight = ((nLineWidth*mnDPIX)+(mnDPIY/2))/mnDPIY; + if ( eTextLine == UNDERLINE_DOUBLEWAVE ) + { + long nOrgLineHeight = nLineHeight; + nLineHeight /= 3; + if ( nLineHeight < 2 ) + { + if ( nOrgLineHeight > 1 ) + nLineHeight = 2; + else + nLineHeight = 1; + } + long nLineDY = nOrgLineHeight-(nLineHeight*2); + if ( nLineDY < nLineWidthHeight ) + nLineDY = nLineWidthHeight; + long nLineDY2 = nLineDY/2; + if ( !nLineDY2 ) + nLineDY2 = 1; + + nLinePos -= nLineWidthHeight-nLineDY2; + ImplDrawWaveLine( nBaseX, nBaseY, nX, nLinePos, nWidth, nLineHeight, + nLineWidth, mpFontEntry->mnOrientation, aColor ); + nLinePos += nLineWidthHeight+nLineDY; + ImplDrawWaveLine( nBaseX, nBaseY, nX, nLinePos, nWidth, nLineHeight, + nLineWidth, mpFontEntry->mnOrientation, aColor ); + } + else + { + nLinePos -= nLineWidthHeight/2; + ImplDrawWaveLine( nBaseX, nBaseY, nX, nLinePos, nWidth, nLineHeight, + nLineWidth, mpFontEntry->mnOrientation, aColor ); + } +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawStraightTextLine( long nBaseX, long nBaseY, + long nX, long nY, long nWidth, + FontUnderline eTextLine, + Color aColor, + BOOL bIsAbove ) +{ + ImplFontEntry* pFontEntry = mpFontEntry; + long nLineHeight = 0; + long nLinePos = 0; + long nLinePos2 = 0; + + if ( eTextLine > UNDERLINE_LAST ) + eTextLine = UNDERLINE_SINGLE; + + switch ( eTextLine ) + { + case UNDERLINE_SINGLE: + case UNDERLINE_DOTTED: + case UNDERLINE_DASH: + case UNDERLINE_LONGDASH: + case UNDERLINE_DASHDOT: + case UNDERLINE_DASHDOTDOT: + if ( bIsAbove ) + { + nLineHeight = pFontEntry->maMetric.mnAboveUnderlineSize; + nLinePos = nY + pFontEntry->maMetric.mnAboveUnderlineOffset; + } + else + { + nLineHeight = pFontEntry->maMetric.mnUnderlineSize; + nLinePos = nY + pFontEntry->maMetric.mnUnderlineOffset; + } + break; + case UNDERLINE_BOLD: + case UNDERLINE_BOLDDOTTED: + case UNDERLINE_BOLDDASH: + case UNDERLINE_BOLDLONGDASH: + case UNDERLINE_BOLDDASHDOT: + case UNDERLINE_BOLDDASHDOTDOT: + if ( bIsAbove ) + { + nLineHeight = pFontEntry->maMetric.mnAboveBUnderlineSize; + nLinePos = nY + pFontEntry->maMetric.mnAboveBUnderlineOffset; + } + else + { + nLineHeight = pFontEntry->maMetric.mnBUnderlineSize; + nLinePos = nY + pFontEntry->maMetric.mnBUnderlineOffset; + } + break; + case UNDERLINE_DOUBLE: + if ( bIsAbove ) + { + nLineHeight = pFontEntry->maMetric.mnAboveDUnderlineSize; + nLinePos = nY + pFontEntry->maMetric.mnAboveDUnderlineOffset1; + nLinePos2 = nY + pFontEntry->maMetric.mnAboveDUnderlineOffset2; + } + else + { + nLineHeight = pFontEntry->maMetric.mnDUnderlineSize; + nLinePos = nY + pFontEntry->maMetric.mnDUnderlineOffset1; + nLinePos2 = nY + pFontEntry->maMetric.mnDUnderlineOffset2; + } + break; + default: + break; + } + + if ( nLineHeight ) + { + if ( mbLineColor || mbInitLineColor ) + { + mpGraphics->SetLineColor(); + mbInitLineColor = TRUE; + } + mpGraphics->SetFillColor( ImplColorToSal( aColor ) ); + mbInitFillColor = TRUE; + + long nLeft = nX; + + switch ( eTextLine ) + { + case UNDERLINE_SINGLE: + case UNDERLINE_BOLD: + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight ); + break; + case UNDERLINE_DOUBLE: + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight ); + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos2, nWidth, nLineHeight ); + break; + case UNDERLINE_DOTTED: + case UNDERLINE_BOLDDOTTED: + { + long nDotWidth = nLineHeight*mnDPIY; + nDotWidth += mnDPIY/2; + nDotWidth /= mnDPIY; + long nTempWidth = nDotWidth; + long nEnd = nLeft+nWidth; + while ( nLeft < nEnd ) + { + if ( nLeft+nTempWidth > nEnd ) + nTempWidth = nEnd-nLeft; + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempWidth, nLineHeight ); + nLeft += nDotWidth*2; + } + } + break; + case UNDERLINE_DASH: + case UNDERLINE_LONGDASH: + case UNDERLINE_BOLDDASH: + case UNDERLINE_BOLDLONGDASH: + { + long nDotWidth = nLineHeight*mnDPIY; + nDotWidth += mnDPIY/2; + nDotWidth /= mnDPIY; + long nMinDashWidth; + long nMinSpaceWidth; + long nSpaceWidth; + long nDashWidth; + if ( (eTextLine == UNDERLINE_LONGDASH) || + (eTextLine == UNDERLINE_BOLDLONGDASH) ) + { + nMinDashWidth = nDotWidth*6; + nMinSpaceWidth = nDotWidth*2; + nDashWidth = 200; + nSpaceWidth = 100; + } + else + { + nMinDashWidth = nDotWidth*4; + nMinSpaceWidth = (nDotWidth*150)/100; + nDashWidth = 100; + nSpaceWidth = 50; + } + nDashWidth = ((nDashWidth*mnDPIX)+1270)/2540; + nSpaceWidth = ((nSpaceWidth*mnDPIX)+1270)/2540; + // DashWidth wird gegebenenfalls verbreitert, wenn + // die dicke der Linie im Verhaeltnis zur Laenge + // zu dick wird + if ( nDashWidth < nMinDashWidth ) + nDashWidth = nMinDashWidth; + if ( nSpaceWidth < nMinSpaceWidth ) + nSpaceWidth = nMinSpaceWidth; + long nTempWidth = nDashWidth; + long nEnd = nLeft+nWidth; + while ( nLeft < nEnd ) + { + if ( nLeft+nTempWidth > nEnd ) + nTempWidth = nEnd-nLeft; + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempWidth, nLineHeight ); + nLeft += nDashWidth+nSpaceWidth; + } + } + break; + case UNDERLINE_DASHDOT: + case UNDERLINE_BOLDDASHDOT: + { + long nDotWidth = nLineHeight*mnDPIY; + nDotWidth += mnDPIY/2; + nDotWidth /= mnDPIY; + long nDashWidth = ((100*mnDPIX)+1270)/2540; + long nMinDashWidth = nDotWidth*4; + // DashWidth wird gegebenenfalls verbreitert, wenn + // die dicke der Linie im Verhaeltnis zur Laenge + // zu dick wird + if ( nDashWidth < nMinDashWidth ) + nDashWidth = nMinDashWidth; + long nTempDotWidth = nDotWidth; + long nTempDashWidth = nDashWidth; + long nEnd = nLeft+nWidth; + while ( nLeft < nEnd ) + { + if ( nLeft+nTempDotWidth > nEnd ) + nTempDotWidth = nEnd-nLeft; + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight ); + nLeft += nDotWidth*2; + if ( nLeft > nEnd ) + break; + if ( nLeft+nTempDashWidth > nEnd ) + nTempDashWidth = nEnd-nLeft; + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDashWidth, nLineHeight ); + nLeft += nDashWidth+nDotWidth; + } + } + break; + case UNDERLINE_DASHDOTDOT: + case UNDERLINE_BOLDDASHDOTDOT: + { + long nDotWidth = nLineHeight*mnDPIY; + nDotWidth += mnDPIY/2; + nDotWidth /= mnDPIY; + long nDashWidth = ((100*mnDPIX)+1270)/2540; + long nMinDashWidth = nDotWidth*4; + // DashWidth wird gegebenenfalls verbreitert, wenn + // die dicke der Linie im Verhaeltnis zur Laenge + // zu dick wird + if ( nDashWidth < nMinDashWidth ) + nDashWidth = nMinDashWidth; + long nTempDotWidth = nDotWidth; + long nTempDashWidth = nDashWidth; + long nEnd = nLeft+nWidth; + while ( nLeft < nEnd ) + { + if ( nLeft+nTempDotWidth > nEnd ) + nTempDotWidth = nEnd-nLeft; + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight ); + nLeft += nDotWidth*2; + if ( nLeft > nEnd ) + break; + if ( nLeft+nTempDotWidth > nEnd ) + nTempDotWidth = nEnd-nLeft; + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight ); + nLeft += nDotWidth*2; + if ( nLeft > nEnd ) + break; + if ( nLeft+nTempDashWidth > nEnd ) + nTempDashWidth = nEnd-nLeft; + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDashWidth, nLineHeight ); + nLeft += nDashWidth+nDotWidth; + } + } + break; + default: + break; + } + } +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawStrikeoutLine( long nBaseX, long nBaseY, + long nX, long nY, long nWidth, + FontStrikeout eStrikeout, + Color aColor ) +{ + ImplFontEntry* pFontEntry = mpFontEntry; + long nLineHeight = 0; + long nLinePos = 0; + long nLinePos2 = 0; + + if ( eStrikeout > STRIKEOUT_LAST ) + eStrikeout = STRIKEOUT_SINGLE; + + switch ( eStrikeout ) + { + case STRIKEOUT_SINGLE: + nLineHeight = pFontEntry->maMetric.mnStrikeoutSize; + nLinePos = nY + pFontEntry->maMetric.mnStrikeoutOffset; + break; + case STRIKEOUT_BOLD: + nLineHeight = pFontEntry->maMetric.mnBStrikeoutSize; + nLinePos = nY + pFontEntry->maMetric.mnBStrikeoutOffset; + break; + case STRIKEOUT_DOUBLE: + nLineHeight = pFontEntry->maMetric.mnDStrikeoutSize; + nLinePos = nY + pFontEntry->maMetric.mnDStrikeoutOffset1; + nLinePos2 = nY + pFontEntry->maMetric.mnDStrikeoutOffset2; + break; + default: + break; + } + + if ( nLineHeight ) + { + if ( mbLineColor || mbInitLineColor ) + { + mpGraphics->SetLineColor(); + mbInitLineColor = TRUE; + } + mpGraphics->SetFillColor( ImplColorToSal( aColor ) ); + mbInitFillColor = TRUE; + + long nLeft = nX; + + switch ( eStrikeout ) + { + case STRIKEOUT_SINGLE: + case STRIKEOUT_BOLD: + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight ); + break; + case STRIKEOUT_DOUBLE: + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight ); + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos2, nWidth, nLineHeight ); + break; + default: + break; + } + } +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawStrikeoutChar( long nBaseX, long nBaseY, + long nX, long nY, long nWidth, + FontStrikeout eStrikeout, + Color aColor ) +{ + // PDF-export does its own strikeout drawing... why again? + if( mpPDFWriter && mpPDFWriter->isBuiltinFont(mpFontEntry->maFontSelData.mpFontData) ) + return; + + // prepare string for strikeout measurement + static char cStrikeoutChar; + if ( eStrikeout == STRIKEOUT_SLASH ) + cStrikeoutChar = '/'; + else // ( eStrikeout == STRIKEOUT_X ) + cStrikeoutChar = 'X'; + static const int nTestStrLen = 4; + static const int nMaxStrikeStrLen = 2048; + xub_Unicode aChars[ nMaxStrikeStrLen +1]; // +1 for valgrind... + for( int i = 0; i < nTestStrLen; ++i) + aChars[i] = cStrikeoutChar; + const String aStrikeoutTest( aChars, nTestStrLen ); + + // calculate approximation of strikeout atom size + long nStrikeoutWidth = nWidth; + SalLayout* pLayout = ImplLayout( aStrikeoutTest, 0, nTestStrLen ); + if( pLayout ) + { + nStrikeoutWidth = (pLayout->GetTextWidth() +nTestStrLen/2) / (nTestStrLen * pLayout->GetUnitsPerPixel()); + pLayout->Release(); + } + if( nStrikeoutWidth <= 0 ) // sanity check + return; + + // calculate acceptable strikeout length + // allow the strikeout to be one pixel larger than the text it strikes out + long nMaxWidth = nStrikeoutWidth / 2; + if ( nMaxWidth < 2 ) + nMaxWidth = 2; + nMaxWidth += nWidth + 1; + + int nStrikeStrLen = (nMaxWidth + nStrikeoutWidth - 1) / nStrikeoutWidth; + // if the text width is smaller than the strikeout text, then do not + // strike out at all. This case requires user interaction, e.g. adding + // a space to the text + if( nStrikeStrLen <= 0 ) + return; + if( nStrikeStrLen > nMaxStrikeStrLen ) + nStrikeStrLen = nMaxStrikeStrLen; + + // build the strikeout string + for( int i = nTestStrLen; i < nStrikeStrLen; ++i) + aChars[i] = cStrikeoutChar; + const String aStrikeoutText( aChars, xub_StrLen(nStrikeStrLen) ); + + if( mpFontEntry->mnOrientation ) + ImplRotatePos( nBaseX, nBaseY, nX, nY, mpFontEntry->mnOrientation ); + + // strikeout text has to be left aligned + ULONG nOrigTLM = mnTextLayoutMode; + mnTextLayoutMode = TEXT_LAYOUT_BIDI_STRONG | TEXT_LAYOUT_COMPLEX_DISABLED; + pLayout = ImplLayout( aStrikeoutText, 0, STRING_LEN ); + mnTextLayoutMode = nOrigTLM; + + if( !pLayout ) + return; + + // draw the strikeout text + const Color aOldColor = GetTextColor(); + SetTextColor( aColor ); + ImplInitTextColor(); + + pLayout->DrawBase() = Point( nX+mnTextOffX, nY+mnTextOffY ); + pLayout->DrawText( *mpGraphics ); + pLayout->Release(); + + SetTextColor( aOldColor ); + ImplInitTextColor(); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawTextLine( long nBaseX, + long nX, long nY, long nWidth, + FontStrikeout eStrikeout, + FontUnderline eUnderline, + FontUnderline eOverline, + BOOL bUnderlineAbove ) +{ + if ( !nWidth ) + return; + + Color aStrikeoutColor = GetTextColor(); + Color aUnderlineColor = GetTextLineColor(); + Color aOverlineColor = GetOverlineColor(); + BOOL bStrikeoutDone = FALSE; + BOOL bUnderlineDone = FALSE; + BOOL bOverlineDone = FALSE; + + // TODO: fix rotated text + if ( IsRTLEnabled() ) + // --- RTL --- mirror at basex + nX = nBaseX - nWidth - (nX - nBaseX - 1); + + if ( !IsTextLineColor() ) + aUnderlineColor = GetTextColor(); + + if ( !IsOverlineColor() ) + aOverlineColor = GetTextColor(); + + if ( (eUnderline == UNDERLINE_SMALLWAVE) || + (eUnderline == UNDERLINE_WAVE) || + (eUnderline == UNDERLINE_DOUBLEWAVE) || + (eUnderline == UNDERLINE_BOLDWAVE) ) + { + ImplDrawWaveTextLine( nBaseX, nY, nX, nY, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); + bUnderlineDone = TRUE; + } + if ( (eOverline == UNDERLINE_SMALLWAVE) || + (eOverline == UNDERLINE_WAVE) || + (eOverline == UNDERLINE_DOUBLEWAVE) || + (eOverline == UNDERLINE_BOLDWAVE) ) + { + ImplDrawWaveTextLine( nBaseX, nY, nX, nY, nWidth, eOverline, aOverlineColor, TRUE ); + bOverlineDone = TRUE; + } + + if ( (eStrikeout == STRIKEOUT_SLASH) || + (eStrikeout == STRIKEOUT_X) ) + { + ImplDrawStrikeoutChar( nBaseX, nY, nX, nY, nWidth, eStrikeout, aStrikeoutColor ); + bStrikeoutDone = TRUE; + } + + if ( !bUnderlineDone ) + ImplDrawStraightTextLine( nBaseX, nY, nX, nY, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); + + if ( !bOverlineDone ) + ImplDrawStraightTextLine( nBaseX, nY, nX, nY, nWidth, eOverline, aOverlineColor, TRUE ); + + if ( !bStrikeoutDone ) + ImplDrawStrikeoutLine( nBaseX, nY, nX, nY, nWidth, eStrikeout, aStrikeoutColor ); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawTextLines( SalLayout& rSalLayout, + FontStrikeout eStrikeout, FontUnderline eUnderline, FontUnderline eOverline, BOOL bWordLine, BOOL bUnderlineAbove ) +{ + if( bWordLine ) + { + Point aPos, aStartPt; + sal_Int32 nWidth = 0, nAdvance=0; + for( int nStart = 0;;) + { + sal_GlyphId nGlyphIndex; + if( !rSalLayout.GetNextGlyphs( 1, &nGlyphIndex, aPos, nStart, &nAdvance ) ) + break; + + if( !rSalLayout.IsSpacingGlyph( nGlyphIndex ) ) + { + if( !nWidth ) + { + aStartPt = aPos;//rSalLayout.DrawBase() - (aPos - rSalLayout.DrawOffset()); + } + + nWidth += nAdvance; + } + else if( nWidth > 0 ) + { + ImplDrawTextLine( rSalLayout.DrawBase().X(), aStartPt.X(), aStartPt.Y(), nWidth, + eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + nWidth = 0; + } + } + + if( nWidth > 0 ) + { + ImplDrawTextLine( rSalLayout.DrawBase().X(), aStartPt.X(), aStartPt.Y(), nWidth, + eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + } + } + else + { + Point aStartPt = rSalLayout.GetDrawPosition(); + int nWidth = rSalLayout.GetTextWidth() / rSalLayout.GetUnitsPerPixel(); + ImplDrawTextLine( rSalLayout.DrawBase().X(), aStartPt.X(), aStartPt.Y(), nWidth, + eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + } +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawMnemonicLine( long nX, long nY, long nWidth ) +{ + long nBaseX = nX; + if( /*ImplHasMirroredGraphics() &&*/ IsRTLEnabled() ) + { + // --- RTL --- + // add some strange offset + nX += 2; + // revert the hack that will be done later in ImplDrawTextLine + nX = nBaseX - nWidth - (nX - nBaseX - 1); + } + + ImplDrawTextLine( nBaseX, nX, nY, nWidth, STRIKEOUT_NONE, UNDERLINE_SINGLE, UNDERLINE_NONE, FALSE ); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplGetEmphasisMark( PolyPolygon& rPolyPoly, BOOL& rPolyLine, + Rectangle& rRect1, Rectangle& rRect2, + long& rYOff, long& rWidth, + FontEmphasisMark eEmphasis, + long nHeight, short /*nOrient*/ ) +{ + static const BYTE aAccentPolyFlags[24] = + { + 0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 0, 2, 0, 2, 2 + }; + + static const long aAccentPos[48] = + { + 78, 0, + 348, 79, + 599, 235, + 843, 469, + 938, 574, + 990, 669, + 990, 773, + 990, 843, + 964, 895, + 921, 947, + 886, 982, + 860, 999, + 825, 999, + 764, 999, + 721, 964, + 686, 895, + 625, 791, + 556, 660, + 469, 504, + 400, 400, + 261, 252, + 61, 61, + 0, 27, + 9, 0 + }; + + rWidth = 0; + rYOff = 0; + rPolyLine = FALSE; + + if ( !nHeight ) + return; + + FontEmphasisMark nEmphasisStyle = eEmphasis & EMPHASISMARK_STYLE; + long nDotSize = 0; + switch ( nEmphasisStyle ) + { + case EMPHASISMARK_DOT: + // Dot has 55% of the height + nDotSize = (nHeight*550)/1000; + if ( !nDotSize ) + nDotSize = 1; + if ( nDotSize <= 2 ) + rRect1 = Rectangle( Point(), Size( nDotSize, nDotSize ) ); + else + { + long nRad = nDotSize/2; + Polygon aPoly( Point( nRad, nRad ), nRad, nRad ); + rPolyPoly.Insert( aPoly ); + } + rYOff = ((nHeight*250)/1000)/2; // Center to the anthoer EmphasisMarks + rWidth = nDotSize; + break; + + case EMPHASISMARK_CIRCLE: + // Dot has 80% of the height + nDotSize = (nHeight*800)/1000; + if ( !nDotSize ) + nDotSize = 1; + if ( nDotSize <= 2 ) + rRect1 = Rectangle( Point(), Size( nDotSize, nDotSize ) ); + else + { + long nRad = nDotSize/2; + Polygon aPoly( Point( nRad, nRad ), nRad, nRad ); + rPolyPoly.Insert( aPoly ); + // BorderWidth is 15% + long nBorder = (nDotSize*150)/1000; + if ( nBorder <= 1 ) + rPolyLine = TRUE; + else + { + Polygon aPoly2( Point( nRad, nRad ), + nRad-nBorder, nRad-nBorder ); + rPolyPoly.Insert( aPoly2 ); + } + } + rWidth = nDotSize; + break; + + case EMPHASISMARK_DISC: + // Dot has 80% of the height + nDotSize = (nHeight*800)/1000; + if ( !nDotSize ) + nDotSize = 1; + if ( nDotSize <= 2 ) + rRect1 = Rectangle( Point(), Size( nDotSize, nDotSize ) ); + else + { + long nRad = nDotSize/2; + Polygon aPoly( Point( nRad, nRad ), nRad, nRad ); + rPolyPoly.Insert( aPoly ); + } + rWidth = nDotSize; + break; + + case EMPHASISMARK_ACCENT: + // Dot has 80% of the height + nDotSize = (nHeight*800)/1000; + if ( !nDotSize ) + nDotSize = 1; + if ( nDotSize <= 2 ) + { + if ( nDotSize == 1 ) + { + rRect1 = Rectangle( Point(), Size( nDotSize, nDotSize ) ); + rWidth = nDotSize; + } + else + { + rRect1 = Rectangle( Point(), Size( 1, 1 ) ); + rRect2 = Rectangle( Point( 1, 1 ), Size( 1, 1 ) ); + } + } + else + { + Polygon aPoly( sizeof( aAccentPos ) / sizeof( long ) / 2, + (const Point*)aAccentPos, + aAccentPolyFlags ); + double dScale = ((double)nDotSize)/1000.0; + aPoly.Scale( dScale, dScale ); + Polygon aTemp; + aPoly.AdaptiveSubdivide( aTemp ); + Rectangle aBoundRect = aTemp.GetBoundRect(); + rWidth = aBoundRect.GetWidth(); + nDotSize = aBoundRect.GetHeight(); + rPolyPoly.Insert( aTemp ); + } + break; + } + + // calculate position + long nOffY = 1+(mnDPIY/300); // one visible pixel space + long nSpaceY = nHeight-nDotSize; + if ( nSpaceY >= nOffY*2 ) + rYOff += nOffY; + if ( !(eEmphasis & EMPHASISMARK_POS_BELOW) ) + rYOff += nDotSize; +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawEmphasisMark( long nBaseX, long nX, long nY, + const PolyPolygon& rPolyPoly, BOOL bPolyLine, + const Rectangle& rRect1, const Rectangle& rRect2 ) +{ + // TODO: pass nWidth as width of this mark + long nWidth = 0; + + if( IsRTLEnabled() ) + // --- RTL --- mirror at basex + nX = nBaseX - nWidth - (nX - nBaseX - 1); + + nX -= mnOutOffX; + nY -= mnOutOffY; + + if ( rPolyPoly.Count() ) + { + if ( bPolyLine ) + { + Polygon aPoly = rPolyPoly.GetObject( 0 ); + aPoly.Move( nX, nY ); + DrawPolyLine( aPoly ); + } + else + { + PolyPolygon aPolyPoly = rPolyPoly; + aPolyPoly.Move( nX, nY ); + DrawPolyPolygon( aPolyPoly ); + } + } + + if ( !rRect1.IsEmpty() ) + { + Rectangle aRect( Point( nX+rRect1.Left(), + nY+rRect1.Top() ), rRect1.GetSize() ); + DrawRect( aRect ); + } + + if ( !rRect2.IsEmpty() ) + { + Rectangle aRect( Point( nX+rRect2.Left(), + nY+rRect2.Top() ), rRect2.GetSize() ); + + DrawRect( aRect ); + } +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawEmphasisMarks( SalLayout& rSalLayout ) +{ + Color aOldColor = GetTextColor(); + Color aOldLineColor = GetLineColor(); + Color aOldFillColor = GetFillColor(); + BOOL bOldMap = mbMap; + GDIMetaFile* pOldMetaFile = mpMetaFile; + mpMetaFile = NULL; + EnableMapMode( FALSE ); + + FontEmphasisMark nEmphasisMark = ImplGetEmphasisMarkStyle( maFont ); + PolyPolygon aPolyPoly; + Rectangle aRect1; + Rectangle aRect2; + long nEmphasisYOff; + long nEmphasisWidth; + long nEmphasisHeight; + BOOL bPolyLine; + + if ( nEmphasisMark & EMPHASISMARK_POS_BELOW ) + nEmphasisHeight = mnEmphasisDescent; + else + nEmphasisHeight = mnEmphasisAscent; + + ImplGetEmphasisMark( aPolyPoly, bPolyLine, + aRect1, aRect2, + nEmphasisYOff, nEmphasisWidth, + nEmphasisMark, + nEmphasisHeight, mpFontEntry->mnOrientation ); + + if ( bPolyLine ) + { + SetLineColor( GetTextColor() ); + SetFillColor(); + } + else + { + SetLineColor(); + SetFillColor( GetTextColor() ); + } + + Point aOffset = Point(0,0); + + if ( nEmphasisMark & EMPHASISMARK_POS_BELOW ) + aOffset.Y() += mpFontEntry->maMetric.mnDescent + nEmphasisYOff; + else + aOffset.Y() -= mpFontEntry->maMetric.mnAscent + nEmphasisYOff; + + long nEmphasisWidth2 = nEmphasisWidth / 2; + long nEmphasisHeight2 = nEmphasisHeight / 2; + aOffset += Point( nEmphasisWidth2, nEmphasisHeight2 ); + + Point aOutPoint; + Rectangle aRectangle; + for( int nStart = 0;;) + { + sal_GlyphId nGlyphIndex; + if( !rSalLayout.GetNextGlyphs( 1, &nGlyphIndex, aOutPoint, nStart ) ) + break; + + if( !mpGraphics->GetGlyphBoundRect( nGlyphIndex, aRectangle ) ) + continue; + + if( !rSalLayout.IsSpacingGlyph( nGlyphIndex ) ) + { + Point aAdjPoint = aOffset; + aAdjPoint.X() += aRectangle.Left() + (aRectangle.GetWidth() - nEmphasisWidth) / 2; + if ( mpFontEntry->mnOrientation ) + ImplRotatePos( 0, 0, aAdjPoint.X(), aAdjPoint.Y(), mpFontEntry->mnOrientation ); + aOutPoint += aAdjPoint; + aOutPoint -= Point( nEmphasisWidth2, nEmphasisHeight2 ); + ImplDrawEmphasisMark( rSalLayout.DrawBase().X(), + aOutPoint.X(), aOutPoint.Y(), + aPolyPoly, bPolyLine, aRect1, aRect2 ); + } + } + + SetLineColor( aOldLineColor ); + SetFillColor( aOldFillColor ); + EnableMapMode( bOldMap ); + mpMetaFile = pOldMetaFile; +} + +// ----------------------------------------------------------------------- + +bool OutputDevice::ImplDrawRotateText( SalLayout& rSalLayout ) +{ + int nX = rSalLayout.DrawBase().X(); + int nY = rSalLayout.DrawBase().Y(); + + Rectangle aBoundRect; + rSalLayout.DrawBase() = Point( 0, 0 ); + rSalLayout.DrawOffset() = Point( 0, 0 ); + if( !rSalLayout.GetBoundRect( *mpGraphics, aBoundRect ) ) + { + // guess vertical text extents if GetBoundRect failed + int nRight = rSalLayout.GetTextWidth(); + int nTop = mpFontEntry->maMetric.mnAscent + mnEmphasisAscent; + long nHeight = mpFontEntry->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent; + aBoundRect = Rectangle( 0, -nTop, nRight, nHeight - nTop ); + } + + // cache virtual device for rotation + if ( !mpOutDevData ) + ImplInitOutDevData(); + if ( !mpOutDevData->mpRotateDev ) + mpOutDevData->mpRotateDev = new VirtualDevice( *this, 1 ); + VirtualDevice* pVDev = mpOutDevData->mpRotateDev; + + // size it accordingly + if( !pVDev->SetOutputSizePixel( aBoundRect.GetSize() ) ) + return false; + + Font aFont( GetFont() ); + aFont.SetOrientation( 0 ); + aFont.SetSize( Size( mpFontEntry->maFontSelData.mnWidth, mpFontEntry->maFontSelData.mnHeight ) ); + pVDev->SetFont( aFont ); + pVDev->SetTextColor( Color( COL_BLACK ) ); + pVDev->SetTextFillColor(); + pVDev->ImplNewFont(); + pVDev->ImplInitFont(); + pVDev->ImplInitTextColor(); + + // draw text into upper left corner + rSalLayout.DrawBase() -= aBoundRect.TopLeft(); + rSalLayout.DrawText( *((OutputDevice*)pVDev)->mpGraphics ); + + Bitmap aBmp = pVDev->GetBitmap( Point(), aBoundRect.GetSize() ); + if ( !aBmp || !aBmp.Rotate( mpFontEntry->mnOwnOrientation, COL_WHITE ) ) + return false; + + // calculate rotation offset + Polygon aPoly( aBoundRect ); + aPoly.Rotate( Point(), mpFontEntry->mnOwnOrientation ); + Point aPoint = aPoly.GetBoundRect().TopLeft(); + aPoint += Point( nX, nY ); + + // mask output with text colored bitmap + GDIMetaFile* pOldMetaFile = mpMetaFile; + long nOldOffX = mnOutOffX; + long nOldOffY = mnOutOffY; + BOOL bOldMap = mbMap; + + mnOutOffX = 0L; + mnOutOffY = 0L; + mpMetaFile = NULL; + EnableMapMode( FALSE ); + + DrawMask( aPoint, aBmp, GetTextColor() ); + + EnableMapMode( bOldMap ); + mnOutOffX = nOldOffX; + mnOutOffY = nOldOffY; + mpMetaFile = pOldMetaFile; + + return true; +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawTextDirect( SalLayout& rSalLayout, BOOL bTextLines ) +{ + if( mpFontEntry->mnOwnOrientation ) + if( ImplDrawRotateText( rSalLayout ) ) + return; + + long nOldX = rSalLayout.DrawBase().X(); + if( ! (mpPDFWriter && mpPDFWriter->isBuiltinFont(mpFontEntry->maFontSelData.mpFontData) ) ) + { + if( ImplHasMirroredGraphics() ) + { + long w = meOutDevType == OUTDEV_VIRDEV ? mnOutWidth : mpGraphics->GetGraphicsWidth(); + long x = rSalLayout.DrawBase().X(); + rSalLayout.DrawBase().X() = w - 1 - x; + if( !IsRTLEnabled() ) + { + OutputDevice *pOutDevRef = (OutputDevice *)this; + // mirror this window back + long devX = w-pOutDevRef->mnOutWidth-pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX + rSalLayout.DrawBase().X() = devX + ( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().X() - devX) ) ; + } + } + else if( IsRTLEnabled() ) + { + //long w = meOutDevType == OUTDEV_VIRDEV ? mnOutWidth : mpGraphics->GetGraphicsWidth(); + //long x = rSalLayout.DrawBase().X(); + OutputDevice *pOutDevRef = (OutputDevice *)this; + // mirror this window back + long devX = pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX + rSalLayout.DrawBase().X() = pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().X() - devX) + devX; + } + + rSalLayout.DrawText( *mpGraphics ); + } + + rSalLayout.DrawBase().X() = nOldX; + + if( bTextLines ) + ImplDrawTextLines( rSalLayout, + maFont.GetStrikeout(), maFont.GetUnderline(), maFont.GetOverline(), + maFont.IsWordLineMode(), ImplIsUnderlineAbove( maFont ) ); + + // emphasis marks + if( maFont.GetEmphasisMark() & EMPHASISMARK_STYLE ) + ImplDrawEmphasisMarks( rSalLayout ); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawSpecialText( SalLayout& rSalLayout ) +{ + Color aOldColor = GetTextColor(); + Color aOldTextLineColor = GetTextLineColor(); + Color aOldOverlineColor = GetOverlineColor(); + FontRelief eRelief = maFont.GetRelief(); + + Point aOrigPos = rSalLayout.DrawBase(); + if ( eRelief != RELIEF_NONE ) + { + Color aReliefColor( COL_LIGHTGRAY ); + Color aTextColor( aOldColor ); + + Color aTextLineColor( aOldTextLineColor ); + Color aOverlineColor( aOldOverlineColor ); + + // we don't have a automatic color, so black is always drawn on white + if ( aTextColor.GetColor() == COL_BLACK ) + aTextColor = Color( COL_WHITE ); + if ( aTextLineColor.GetColor() == COL_BLACK ) + aTextLineColor = Color( COL_WHITE ); + if ( aOverlineColor.GetColor() == COL_BLACK ) + aOverlineColor = Color( COL_WHITE ); + + // relief-color is black for white text, in all other cases + // we set this to LightGray + if ( aTextColor.GetColor() == COL_WHITE ) + aReliefColor = Color( COL_BLACK ); + SetTextLineColor( aReliefColor ); + SetOverlineColor( aReliefColor ); + SetTextColor( aReliefColor ); + ImplInitTextColor(); + + // calculate offset - for high resolution printers the offset + // should be greater so that the effect is visible + long nOff = 1; + nOff += mnDPIX/300; + + if ( eRelief == RELIEF_ENGRAVED ) + nOff = -nOff; + rSalLayout.DrawOffset() += Point( nOff, nOff); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawOffset() -= Point( nOff, nOff); + + SetTextLineColor( aTextLineColor ); + SetOverlineColor( aOverlineColor ); + SetTextColor( aTextColor ); + ImplInitTextColor(); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + + SetTextLineColor( aOldTextLineColor ); + SetOverlineColor( aOldOverlineColor ); + + if ( aTextColor != aOldColor ) + { + SetTextColor( aOldColor ); + ImplInitTextColor(); + } + } + else + { + if ( maFont.IsShadow() ) + { + long nOff = 1 + ((mpFontEntry->mnLineHeight-24)/24); + if ( maFont.IsOutline() ) + nOff++; + SetTextLineColor(); + SetOverlineColor(); + if ( (GetTextColor().GetColor() == COL_BLACK) + || (GetTextColor().GetLuminance() < 8) ) + SetTextColor( Color( COL_LIGHTGRAY ) ); + else + SetTextColor( Color( COL_BLACK ) ); + ImplInitTextColor(); + rSalLayout.DrawBase() += Point( nOff, nOff ); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() -= Point( nOff, nOff ); + SetTextColor( aOldColor ); + SetTextLineColor( aOldTextLineColor ); + SetOverlineColor( aOldOverlineColor ); + ImplInitTextColor(); + + if ( !maFont.IsOutline() ) + ImplDrawTextDirect( rSalLayout, mbTextLines ); + } + + if ( maFont.IsOutline() ) + { + rSalLayout.DrawBase() = aOrigPos + Point(-1,-1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + Point(+1,+1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + Point(-1,+0); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + Point(-1,+1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + Point(+0,+1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + Point(+0,-1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + Point(+1,-1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + Point(+1,+0); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos; + + SetTextColor( Color( COL_WHITE ) ); + SetTextLineColor( Color( COL_WHITE ) ); + SetOverlineColor( Color( COL_WHITE ) ); + ImplInitTextColor(); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + SetTextColor( aOldColor ); + SetTextLineColor( aOldTextLineColor ); + SetOverlineColor( aOldOverlineColor ); + ImplInitTextColor(); + } + } +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawText( SalLayout& rSalLayout ) +{ + if( mbInitClipRegion ) + ImplInitClipRegion(); + if( mbOutputClipped ) + return; + if( mbInitTextColor ) + ImplInitTextColor(); + + rSalLayout.DrawBase() += Point( mnTextOffX, mnTextOffY ); + + if( IsTextFillColor() ) + ImplDrawTextBackground( rSalLayout ); + + if( mbTextSpecial ) + ImplDrawSpecialText( rSalLayout ); + else + ImplDrawTextDirect( rSalLayout, mbTextLines ); +} + +// ----------------------------------------------------------------------- + +long OutputDevice::ImplGetTextLines( ImplMultiTextLineInfo& rLineInfo, + long nWidth, const XubString& rStr, + USHORT nStyle, const ::vcl::ITextLayout& _rLayout ) +{ + DBG_ASSERTWARNING( nWidth >= 0, "ImplGetTextLines: nWidth <= 0!" ); + + if ( nWidth <= 0 ) + nWidth = 1; + + long nMaxLineWidth = 0; + rLineInfo.Clear(); + if ( rStr.Len() && (nWidth > 0) ) + { + ::rtl::OUString aText( rStr ); + uno::Reference < i18n::XBreakIterator > xBI; + // get service provider + uno::Reference< lang::XMultiServiceFactory > xSMgr( unohelper::GetMultiServiceFactory() ); + + uno::Reference< linguistic2::XHyphenator > xHyph; + if( xSMgr.is() ) + { + uno::Reference< linguistic2::XLinguServiceManager> xLinguMgr(xSMgr->createInstance(::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM("com.sun.star.linguistic2.LinguServiceManager"))),uno::UNO_QUERY); + if ( xLinguMgr.is() ) + { + xHyph = xLinguMgr->getHyphenator(); + } + } + + i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, uno::Sequence <beans::PropertyValue>(), 1 ); + i18n::LineBreakUserOptions aUserOptions; + + xub_StrLen nPos = 0; + xub_StrLen nLen = rStr.Len(); + while ( nPos < nLen ) + { + xub_StrLen nBreakPos = nPos; + + while ( ( nBreakPos < nLen ) && ( rStr.GetChar( nBreakPos ) != _CR ) && ( rStr.GetChar( nBreakPos ) != _LF ) ) + nBreakPos++; + + long nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos ); + if ( ( nLineWidth > nWidth ) && ( nStyle & TEXT_DRAW_WORDBREAK ) ) + { + if ( !xBI.is() ) + xBI = vcl::unohelper::CreateBreakIterator(); + + if ( xBI.is() ) + { + const com::sun::star::lang::Locale& rDefLocale(Application::GetSettings().GetUILocale()); + xub_StrLen nSoftBreak = _rLayout.GetTextBreak( rStr, nWidth, nPos, nBreakPos - nPos ); + DBG_ASSERT( nSoftBreak < nBreakPos, "Break?!" ); + //aHyphOptions.hyphenIndex = nSoftBreak; + i18n::LineBreakResults aLBR = xBI->getLineBreak( aText, nSoftBreak, rDefLocale, nPos, aHyphOptions, aUserOptions ); + nBreakPos = (xub_StrLen)aLBR.breakIndex; + if ( nBreakPos <= nPos ) + nBreakPos = nSoftBreak; + if ( (nStyle & TEXT_DRAW_WORDBREAK_HYPHENATION) == TEXT_DRAW_WORDBREAK_HYPHENATION ) + { + // Egal ob Trenner oder nicht: Das Wort nach dem Trenner durch + // die Silbentrennung jagen... + // nMaxBreakPos ist das letzte Zeichen was in die Zeile passt, + // nBreakPos ist der Wort-Anfang + // Ein Problem gibt es, wenn das Dok so schmal ist, dass ein Wort + // auf mehr als Zwei Zeilen gebrochen wird... + if ( xHyph.is() ) + { + sal_Unicode cAlternateReplChar = 0; + sal_Unicode cAlternateExtraChar = 0; + i18n::Boundary aBoundary = xBI->getWordBoundary( aText, nBreakPos, rDefLocale, ::com::sun::star::i18n::WordType::DICTIONARY_WORD, sal_True ); + // sal_uInt16 nWordStart = nBreakPos; + // sal_uInt16 nBreakPos_OLD = nBreakPos; + sal_uInt16 nWordStart = nPos; + sal_uInt16 nWordEnd = (USHORT) aBoundary.endPos; + DBG_ASSERT( nWordEnd > nWordStart, "ImpBreakLine: Start >= End?" ); + + USHORT nWordLen = nWordEnd - nWordStart; + if ( ( nWordEnd >= nSoftBreak ) && ( nWordLen > 3 ) ) + { + // #104415# May happen, because getLineBreak may differ from getWordBoudary with DICTIONARY_WORD + // DBG_ASSERT( nWordEnd >= nMaxBreakPos, "Hyph: Break?" ); + String aWord( aText, nWordStart, nWordLen ); + sal_uInt16 nMinTrail = static_cast<sal_uInt16>(nWordEnd-nSoftBreak+1); //+1: Vor dem angeknacksten Buchstaben + uno::Reference< linguistic2::XHyphenatedWord > xHyphWord; + if (xHyph.is()) + xHyphWord = xHyph->hyphenate( aWord, rDefLocale, aWord.Len() - nMinTrail, uno::Sequence< beans::PropertyValue >() ); + if (xHyphWord.is()) + { + sal_Bool bAlternate = xHyphWord->isAlternativeSpelling(); + sal_uInt16 _nWordLen = 1 + xHyphWord->getHyphenPos(); + + if ( ( _nWordLen >= 2 ) && ( (nWordStart+_nWordLen) >= ( 2 ) ) ) + { + if ( !bAlternate ) + { + nBreakPos = nWordStart + _nWordLen; + } + else + { + String aAlt( xHyphWord->getHyphenatedWord() ); + + // Wir gehen von zwei Faellen aus, die nun + // vorliegen koennen: + // 1) packen wird zu pak-ken + // 2) Schiffahrt wird zu Schiff-fahrt + // In Fall 1 muss ein Zeichen ersetzt werden, + // in Fall 2 wird ein Zeichen hinzugefuegt. + // Die Identifikation wird erschwert durch Worte wie + // "Schiffahrtsbrennesseln", da der Hyphenator alle + // Position des Wortes auftrennt und "Schifffahrtsbrennnesseln" + // ermittelt. Wir koennen also eigentlich nicht unmittelbar vom + // Index des AlternativWord auf aWord schliessen. + + // Das ganze geraffel wird durch eine Funktion am + // Hyphenator vereinfacht werden, sobald AMA sie einbaut... + sal_uInt16 nAltStart = _nWordLen - 1; + sal_uInt16 nTxtStart = nAltStart - (aAlt.Len() - aWord.Len()); + sal_uInt16 nTxtEnd = nTxtStart; + sal_uInt16 nAltEnd = nAltStart; + + // Die Bereiche zwischen den nStart und nEnd ist + // die Differenz zwischen Alternativ- und OriginalString. + while( nTxtEnd < aWord.Len() && nAltEnd < aAlt.Len() && + aWord.GetChar(nTxtEnd) != aAlt.GetChar(nAltEnd) ) + { + ++nTxtEnd; + ++nAltEnd; + } + + // Wenn ein Zeichen hinzugekommen ist, dann bemerken wir es jetzt: + if( nAltEnd > nTxtEnd && nAltStart == nAltEnd && + aWord.GetChar( nTxtEnd ) == aAlt.GetChar(nAltEnd) ) + { + ++nAltEnd; + ++nTxtStart; + ++nTxtEnd; + } + + DBG_ASSERT( ( nAltEnd - nAltStart ) == 1, "Alternate: Falsche Annahme!" ); + + if ( nTxtEnd > nTxtStart ) + cAlternateReplChar = aAlt.GetChar( nAltStart ); + else + cAlternateExtraChar = aAlt.GetChar( nAltStart ); + + nBreakPos = nWordStart + nTxtStart; + if ( cAlternateReplChar ) + nBreakPos++; + } + } // if (xHyphWord.is()) + } // if ( ( nWordEnd >= nSoftBreak ) && ( nWordLen > 3 ) ) + } // if ( xHyph.is() ) + } // if ( (nStyle & TEXT_DRAW_WORDBREAK_HYPHENATION) == TEXT_DRAW_WORDBREAK_HYPHENATION ) + } + nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos ); + } + else + { + // fallback to something really simple + USHORT nSpacePos = STRING_LEN; + long nW = 0; + do + { + nSpacePos = rStr.SearchBackward( sal_Unicode(' '), nSpacePos ); + if( nSpacePos != STRING_NOTFOUND ) + { + if( nSpacePos > nPos ) + nSpacePos--; + nW = _rLayout.GetTextWidth( rStr, nPos, nSpacePos-nPos ); + } + } while( nW > nWidth ); + + if( nSpacePos != STRING_NOTFOUND ) + { + nBreakPos = nSpacePos; + nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos ); + if( nBreakPos < rStr.Len()-1 ) + nBreakPos++; + } + } + } + + if ( nLineWidth > nMaxLineWidth ) + nMaxLineWidth = nLineWidth; + + rLineInfo.AddLine( new ImplTextLineInfo( nLineWidth, nPos, nBreakPos-nPos ) ); + + if ( nBreakPos == nPos ) + nBreakPos++; + nPos = nBreakPos; + + if ( ( rStr.GetChar( nPos ) == _CR ) || ( rStr.GetChar( nPos ) == _LF ) ) + { + nPos++; + // CR/LF? + if ( ( nPos < nLen ) && ( rStr.GetChar( nPos ) == _LF ) && ( rStr.GetChar( nPos-1 ) == _CR ) ) + nPos++; + } + } + } +#ifdef DBG_UTIL + for ( USHORT nL = 0; nL < rLineInfo.Count(); nL++ ) + { + ImplTextLineInfo* pLine = rLineInfo.GetLine( nL ); + String aLine( rStr, pLine->GetIndex(), pLine->GetLen() ); + DBG_ASSERT( aLine.Search( _CR ) == STRING_NOTFOUND, "ImplGetTextLines - Found CR!" ); + DBG_ASSERT( aLine.Search( _LF ) == STRING_NOTFOUND, "ImplGetTextLines - Found LF!" ); + } +#endif + + return nMaxLineWidth; +} + +// ======================================================================= + +void OutputDevice::SetAntialiasing( USHORT nMode ) +{ + if ( mnAntialiasing != nMode ) + { + mnAntialiasing = nMode; + mbInitFont = TRUE; + + if(mpGraphics) + { + mpGraphics->setAntiAliasB2DDraw(mnAntialiasing & ANTIALIASING_ENABLE_B2DDRAW); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetAntialiasing( nMode ); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::SetFont( const Font& rNewFont ) +{ + DBG_TRACE( "OutputDevice::SetFont()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + DBG_CHKOBJ( &rNewFont, Font, NULL ); + + Font aFont( rNewFont ); + aFont.SetLanguage(rNewFont.GetLanguage()); + if ( mnDrawMode & (DRAWMODE_BLACKTEXT | DRAWMODE_WHITETEXT | DRAWMODE_GRAYTEXT | DRAWMODE_GHOSTEDTEXT | DRAWMODE_SETTINGSTEXT | + DRAWMODE_BLACKFILL | DRAWMODE_WHITEFILL | DRAWMODE_GRAYFILL | DRAWMODE_NOFILL | + DRAWMODE_GHOSTEDFILL | DRAWMODE_SETTINGSFILL ) ) + { + Color aTextColor( aFont.GetColor() ); + + if ( mnDrawMode & DRAWMODE_BLACKTEXT ) + aTextColor = Color( COL_BLACK ); + else if ( mnDrawMode & DRAWMODE_WHITETEXT ) + aTextColor = Color( COL_WHITE ); + else if ( mnDrawMode & DRAWMODE_GRAYTEXT ) + { + const UINT8 cLum = aTextColor.GetLuminance(); + aTextColor = Color( cLum, cLum, cLum ); + } + else if ( mnDrawMode & DRAWMODE_SETTINGSTEXT ) + aTextColor = GetSettings().GetStyleSettings().GetFontColor(); + + if ( mnDrawMode & DRAWMODE_GHOSTEDTEXT ) + { + aTextColor = Color( (aTextColor.GetRed() >> 1 ) | 0x80, + (aTextColor.GetGreen() >> 1 ) | 0x80, + (aTextColor.GetBlue() >> 1 ) | 0x80 ); + } + + aFont.SetColor( aTextColor ); + + BOOL bTransFill = aFont.IsTransparent(); + if ( !bTransFill ) + { + Color aTextFillColor( aFont.GetFillColor() ); + + if ( mnDrawMode & DRAWMODE_BLACKFILL ) + aTextFillColor = Color( COL_BLACK ); + else if ( mnDrawMode & DRAWMODE_WHITEFILL ) + aTextFillColor = Color( COL_WHITE ); + else if ( mnDrawMode & DRAWMODE_GRAYFILL ) + { + const UINT8 cLum = aTextFillColor.GetLuminance(); + aTextFillColor = Color( cLum, cLum, cLum ); + } + else if( mnDrawMode & DRAWMODE_SETTINGSFILL ) + aTextFillColor = GetSettings().GetStyleSettings().GetWindowColor(); + else if ( mnDrawMode & DRAWMODE_NOFILL ) + { + aTextFillColor = Color( COL_TRANSPARENT ); + bTransFill = TRUE; + } + + if ( !bTransFill && (mnDrawMode & DRAWMODE_GHOSTEDFILL) ) + { + aTextFillColor = Color( (aTextFillColor.GetRed() >> 1) | 0x80, + (aTextFillColor.GetGreen() >> 1) | 0x80, + (aTextFillColor.GetBlue() >> 1) | 0x80 ); + } + + aFont.SetFillColor( aTextFillColor ); + } + } + + if ( mpMetaFile ) + { + mpMetaFile->AddAction( new MetaFontAction( aFont ) ); + // the color and alignment actions don't belong here + // TODO: get rid of them without breaking anything... + mpMetaFile->AddAction( new MetaTextAlignAction( aFont.GetAlign() ) ); + mpMetaFile->AddAction( new MetaTextFillColorAction( aFont.GetFillColor(), !aFont.IsTransparent() ) ); + } + +#if (OSL_DEBUG_LEVEL > 2) || defined (HDU_DEBUG) + fprintf( stderr, " OutputDevice::SetFont( name=\"%s\", h=%ld)\n", + OUStringToOString( aFont.GetName(), RTL_TEXTENCODING_UTF8 ).getStr(), + aFont.GetSize().Height() ); +#endif + + if ( !maFont.IsSameInstance( aFont ) ) + { + // Optimization MT/HDU: COL_TRANSPARENT means SetFont should ignore the font color, + // because SetTextColor() is used for this. + // #i28759# maTextColor might have been changed behind our back, commit then, too. + if( aFont.GetColor() != COL_TRANSPARENT + && (aFont.GetColor() != maFont.GetColor() || aFont.GetColor() != maTextColor ) ) + { + maTextColor = aFont.GetColor(); + mbInitTextColor = TRUE; + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextColorAction( aFont.GetColor() ) ); + } + maFont = aFont; + mbNewFont = TRUE; + + if( mpAlphaVDev ) + { + // #i30463# + // Since SetFont might change the text color, apply that only + // selectively to alpha vdev (which normally paints opaque text + // with COL_BLACK) + if( aFont.GetColor() != COL_TRANSPARENT ) + { + mpAlphaVDev->SetTextColor( COL_BLACK ); + aFont.SetColor( COL_TRANSPARENT ); + } + + mpAlphaVDev->SetFont( aFont ); + } + } +} + +// ----------------------------------------------------------------------- + +void OutputDevice::SetLayoutMode( ULONG nTextLayoutMode ) +{ + DBG_TRACE( "OutputDevice::SetTextLayoutMode()" ); + + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaLayoutModeAction( nTextLayoutMode ) ); + + mnTextLayoutMode = nTextLayoutMode; + + if( mpAlphaVDev ) + mpAlphaVDev->SetLayoutMode( nTextLayoutMode ); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::SetDigitLanguage( LanguageType eTextLanguage ) +{ + DBG_TRACE( "OutputDevice::SetTextLanguage()" ); + + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextLanguageAction( eTextLanguage ) ); + + meTextLanguage = eTextLanguage; + + if( mpAlphaVDev ) + mpAlphaVDev->SetDigitLanguage( eTextLanguage ); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::SetTextColor( const Color& rColor ) +{ + DBG_TRACE( "OutputDevice::SetTextColor()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + Color aColor( rColor ); + + if ( mnDrawMode & ( DRAWMODE_BLACKTEXT | DRAWMODE_WHITETEXT | + DRAWMODE_GRAYTEXT | DRAWMODE_GHOSTEDTEXT | + DRAWMODE_SETTINGSTEXT ) ) + { + if ( mnDrawMode & DRAWMODE_BLACKTEXT ) + aColor = Color( COL_BLACK ); + else if ( mnDrawMode & DRAWMODE_WHITETEXT ) + aColor = Color( COL_WHITE ); + else if ( mnDrawMode & DRAWMODE_GRAYTEXT ) + { + const UINT8 cLum = aColor.GetLuminance(); + aColor = Color( cLum, cLum, cLum ); + } + else if ( mnDrawMode & DRAWMODE_SETTINGSTEXT ) + aColor = GetSettings().GetStyleSettings().GetFontColor(); + + if ( mnDrawMode & DRAWMODE_GHOSTEDTEXT ) + { + aColor = Color( (aColor.GetRed() >> 1) | 0x80, + (aColor.GetGreen() >> 1) | 0x80, + (aColor.GetBlue() >> 1) | 0x80 ); + } + } + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextColorAction( aColor ) ); + + if ( maTextColor != aColor ) + { + maTextColor = aColor; + mbInitTextColor = TRUE; + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextColor( COL_BLACK ); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::SetTextFillColor() +{ + DBG_TRACE( "OutputDevice::SetTextFillColor()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextFillColorAction( Color(), FALSE ) ); + + if ( maFont.GetColor() != Color( COL_TRANSPARENT ) ) + maFont.SetFillColor( Color( COL_TRANSPARENT ) ); + if ( !maFont.IsTransparent() ) + maFont.SetTransparent( TRUE ); + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextFillColor(); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::SetTextFillColor( const Color& rColor ) +{ + DBG_TRACE( "OutputDevice::SetTextFillColor()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + Color aColor( rColor ); + BOOL bTransFill = ImplIsColorTransparent( aColor ) ? TRUE : FALSE; + + if ( !bTransFill ) + { + if ( mnDrawMode & ( DRAWMODE_BLACKFILL | DRAWMODE_WHITEFILL | + DRAWMODE_GRAYFILL | DRAWMODE_NOFILL | + DRAWMODE_GHOSTEDFILL | DRAWMODE_SETTINGSFILL ) ) + { + if ( mnDrawMode & DRAWMODE_BLACKFILL ) + aColor = Color( COL_BLACK ); + else if ( mnDrawMode & DRAWMODE_WHITEFILL ) + aColor = Color( COL_WHITE ); + else if ( mnDrawMode & DRAWMODE_GRAYFILL ) + { + const UINT8 cLum = aColor.GetLuminance(); + aColor = Color( cLum, cLum, cLum ); + } + else if( mnDrawMode & DRAWMODE_SETTINGSFILL ) + aColor = GetSettings().GetStyleSettings().GetWindowColor(); + else if ( mnDrawMode & DRAWMODE_NOFILL ) + { + aColor = Color( COL_TRANSPARENT ); + bTransFill = TRUE; + } + + if ( !bTransFill && (mnDrawMode & DRAWMODE_GHOSTEDFILL) ) + { + aColor = Color( (aColor.GetRed() >> 1) | 0x80, + (aColor.GetGreen() >> 1) | 0x80, + (aColor.GetBlue() >> 1) | 0x80 ); + } + } + } + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextFillColorAction( aColor, TRUE ) ); + + if ( maFont.GetFillColor() != aColor ) + maFont.SetFillColor( aColor ); + if ( maFont.IsTransparent() != bTransFill ) + maFont.SetTransparent( bTransFill ); + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextFillColor( COL_BLACK ); +} + +// ----------------------------------------------------------------------- + +Color OutputDevice::GetTextFillColor() const +{ + if ( maFont.IsTransparent() ) + return Color( COL_TRANSPARENT ); + else + return maFont.GetFillColor(); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::SetTextLineColor() +{ + DBG_TRACE( "OutputDevice::SetTextLineColor()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextLineColorAction( Color(), FALSE ) ); + + maTextLineColor = Color( COL_TRANSPARENT ); + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextLineColor(); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::SetTextLineColor( const Color& rColor ) +{ + DBG_TRACE( "OutputDevice::SetTextLineColor()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + Color aColor( rColor ); + + if ( mnDrawMode & ( DRAWMODE_BLACKTEXT | DRAWMODE_WHITETEXT | + DRAWMODE_GRAYTEXT | DRAWMODE_GHOSTEDTEXT | + DRAWMODE_SETTINGSTEXT ) ) + { + if ( mnDrawMode & DRAWMODE_BLACKTEXT ) + aColor = Color( COL_BLACK ); + else if ( mnDrawMode & DRAWMODE_WHITETEXT ) + aColor = Color( COL_WHITE ); + else if ( mnDrawMode & DRAWMODE_GRAYTEXT ) + { + const UINT8 cLum = aColor.GetLuminance(); + aColor = Color( cLum, cLum, cLum ); + } + else if ( mnDrawMode & DRAWMODE_SETTINGSTEXT ) + aColor = GetSettings().GetStyleSettings().GetFontColor(); + + if( (mnDrawMode & DRAWMODE_GHOSTEDTEXT) + && (aColor.GetColor() != COL_TRANSPARENT) ) + { + aColor = Color( (aColor.GetRed() >> 1) | 0x80, + (aColor.GetGreen() >> 1) | 0x80, + (aColor.GetBlue() >> 1) | 0x80 ); + } + } + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextLineColorAction( aColor, TRUE ) ); + + maTextLineColor = aColor; + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextLineColor( COL_BLACK ); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::SetOverlineColor() +{ + DBG_TRACE( "OutputDevice::SetOverlineColor()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaOverlineColorAction( Color(), FALSE ) ); + + maOverlineColor = Color( COL_TRANSPARENT ); + + if( mpAlphaVDev ) + mpAlphaVDev->SetOverlineColor(); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::SetOverlineColor( const Color& rColor ) +{ + DBG_TRACE( "OutputDevice::SetOverlineColor()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + Color aColor( rColor ); + + if ( mnDrawMode & ( DRAWMODE_BLACKTEXT | DRAWMODE_WHITETEXT | + DRAWMODE_GRAYTEXT | DRAWMODE_GHOSTEDTEXT | + DRAWMODE_SETTINGSTEXT ) ) + { + if ( mnDrawMode & DRAWMODE_BLACKTEXT ) + aColor = Color( COL_BLACK ); + else if ( mnDrawMode & DRAWMODE_WHITETEXT ) + aColor = Color( COL_WHITE ); + else if ( mnDrawMode & DRAWMODE_GRAYTEXT ) + { + const UINT8 cLum = aColor.GetLuminance(); + aColor = Color( cLum, cLum, cLum ); + } + else if ( mnDrawMode & DRAWMODE_SETTINGSTEXT ) + aColor = GetSettings().GetStyleSettings().GetFontColor(); + + if( (mnDrawMode & DRAWMODE_GHOSTEDTEXT) + && (aColor.GetColor() != COL_TRANSPARENT) ) + { + aColor = Color( (aColor.GetRed() >> 1) | 0x80, + (aColor.GetGreen() >> 1) | 0x80, + (aColor.GetBlue() >> 1) | 0x80 ); + } + } + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaOverlineColorAction( aColor, TRUE ) ); + + maOverlineColor = aColor; + + if( mpAlphaVDev ) + mpAlphaVDev->SetOverlineColor( COL_BLACK ); +} + +// ----------------------------------------------------------------------- + + +void OutputDevice::SetTextAlign( TextAlign eAlign ) +{ + DBG_TRACE( "OutputDevice::SetTextAlign()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextAlignAction( eAlign ) ); + + if ( maFont.GetAlign() != eAlign ) + { + maFont.SetAlign( eAlign ); + mbNewFont = TRUE; + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextAlign( eAlign ); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::DrawTextLine( const Point& rPos, long nWidth, + FontStrikeout eStrikeout, + FontUnderline eUnderline, + FontUnderline eOverline, + BOOL bUnderlineAbove ) +{ + DBG_TRACE( "OutputDevice::DrawTextLine()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextLineAction( rPos, nWidth, eStrikeout, eUnderline, eOverline ) ); + + if ( ((eUnderline == UNDERLINE_NONE) || (eUnderline == UNDERLINE_DONTKNOW)) && + ((eOverline == UNDERLINE_NONE) || (eOverline == UNDERLINE_DONTKNOW)) && + ((eStrikeout == STRIKEOUT_NONE) || (eStrikeout == STRIKEOUT_DONTKNOW)) ) + return; + + if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + // we need a graphics + if( !mpGraphics && !ImplGetGraphics() ) + return; + if( mbInitClipRegion ) + ImplInitClipRegion(); + if( mbOutputClipped ) + return; + + // initialize font if needed to get text offsets + // TODO: only needed for mnTextOff!=(0,0) + if( mbNewFont ) + if( !ImplNewFont() ) + return; + if( mbInitFont ) + ImplInitFont(); + + Point aPos = ImplLogicToDevicePixel( rPos ); + nWidth = ImplLogicWidthToDevicePixel( nWidth ); + aPos += Point( mnTextOffX, mnTextOffY ); + ImplDrawTextLine( aPos.X(), aPos.X(), aPos.Y(), nWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + + if( mpAlphaVDev ) + mpAlphaVDev->DrawTextLine( rPos, nWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove ); +} + +// ------------------------------------------------------------------------ + +BOOL OutputDevice::IsTextUnderlineAbove( const Font& rFont ) +{ + return ImplIsUnderlineAbove( rFont ); +} + +// ------------------------------------------------------------------------ + +void OutputDevice::DrawWaveLine( const Point& rStartPos, const Point& rEndPos, + USHORT nStyle ) +{ + DBG_TRACE( "OutputDevice::DrawWaveLine()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + // we need a graphics + if( !mpGraphics ) + if( !ImplGetGraphics() ) + return; + + if ( mbInitClipRegion ) + ImplInitClipRegion(); + if ( mbOutputClipped ) + return; + + if( mbNewFont ) + if( !ImplNewFont() ) + return; + + Point aStartPt = ImplLogicToDevicePixel( rStartPos ); + Point aEndPt = ImplLogicToDevicePixel( rEndPos ); + long nStartX = aStartPt.X(); + long nStartY = aStartPt.Y(); + long nEndX = aEndPt.X(); + long nEndY = aEndPt.Y(); + short nOrientation = 0; + + // when rotated + if ( (nStartY != nEndY) || (nStartX > nEndX) ) + { + long nDX = nEndX - nStartX; + double nO = atan2( -nEndY + nStartY, ((nDX == 0L) ? 0.000000001 : nDX) ); + nO /= F_PI1800; + nOrientation = (short)nO; + ImplRotatePos( nStartX, nStartY, nEndX, nEndY, -nOrientation ); + } + + long nWaveHeight; + if ( nStyle == WAVE_NORMAL ) + { + nWaveHeight = 3; + nStartY++; + nEndY++; + } + else if( nStyle == WAVE_SMALL ) + { + nWaveHeight = 2; + nStartY++; + nEndY++; + } + else // WAVE_FLAT + nWaveHeight = 1; + + // #109280# make sure the waveline does not exceed the descent to avoid paint problems + ImplFontEntry* pFontEntry = mpFontEntry; + if( nWaveHeight > pFontEntry->maMetric.mnWUnderlineSize ) + nWaveHeight = pFontEntry->maMetric.mnWUnderlineSize; + + ImplDrawWaveLine( nStartX, nStartY, nStartX, nStartY, + nEndX-nStartX, nWaveHeight, 1, + nOrientation, GetLineColor() ); + if( mpAlphaVDev ) + mpAlphaVDev->DrawWaveLine( rStartPos, rEndPos, nStyle ); +} + +// ----------------------------------------------------------------------- + +void OutputDevice::DrawText( const Point& rStartPt, const String& rStr, + xub_StrLen nIndex, xub_StrLen nLen, + MetricVector* pVector, String* pDisplayText + ) +{ + if( mpOutDevData && mpOutDevData->mpRecordLayout ) + { + pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects; + pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText; + } + + DBG_TRACE( "OutputDevice::DrawText()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + +#if OSL_DEBUG_LEVEL > 2 + fprintf( stderr, " OutputDevice::DrawText(\"%s\")\n", + OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ).getStr() ); +#endif + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextAction( rStartPt, rStr, nIndex, nLen ) ); + if( pVector ) + { + Region aClip( GetClipRegion() ); + if( meOutDevType == OUTDEV_WINDOW ) + aClip.Intersect( Rectangle( Point(), GetOutputSize() ) ); + if( mpOutDevData && mpOutDevData->mpRecordLayout ) + { + mpOutDevData->mpRecordLayout->m_aLineIndices.push_back( mpOutDevData->mpRecordLayout->m_aDisplayText.Len() ); + aClip.Intersect( mpOutDevData->maRecordRect ); + } + if( ! aClip.IsNull() ) + { + MetricVector aTmp; + GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, nIndex, aTmp ); + + bool bInserted = false; + for( MetricVector::const_iterator it = aTmp.begin(); it != aTmp.end(); ++it, nIndex++ ) + { + bool bAppend = false; + + if( aClip.IsOver( *it ) ) + bAppend = true; + else if( rStr.GetChar( nIndex ) == ' ' && bInserted ) + { + MetricVector::const_iterator next = it; + ++next; + if( next != aTmp.end() && aClip.IsOver( *next ) ) + bAppend = true; + } + + if( bAppend ) + { + pVector->push_back( *it ); + if( pDisplayText ) + pDisplayText->Append( rStr.GetChar( nIndex ) ); + bInserted = true; + } + } + } + else + { + GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, nIndex, *pVector ); + if( pDisplayText ) + pDisplayText->Append( rStr.Copy( nIndex, nLen ) ); + } + } + + if ( !IsDeviceOutputNecessary() || pVector ) + return; + + SalLayout* pSalLayout = ImplLayout( rStr, nIndex, nLen, rStartPt, 0, NULL, true ); + if( pSalLayout ) + { + ImplDrawText( *pSalLayout ); + pSalLayout->Release(); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawText( rStartPt, rStr, nIndex, nLen, pVector, pDisplayText ); +} + +// ----------------------------------------------------------------------- + +long OutputDevice::GetTextWidth( const String& rStr, + xub_StrLen nIndex, xub_StrLen nLen ) const +{ + DBG_TRACE( "OutputDevice::GetTextWidth()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + long nWidth = GetTextArray( rStr, NULL, nIndex, nLen ); + return nWidth; +} + +// ----------------------------------------------------------------------- + +long OutputDevice::GetTextHeight() const +{ + DBG_TRACE( "OutputDevice::GetTextHeight()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if( mbNewFont ) + if( !ImplNewFont() ) + return 0; + if( mbInitFont ) + if( !ImplNewFont() ) + return 0; + + long nHeight = mpFontEntry->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent; + + if ( mbMap ) + nHeight = ImplDevicePixelToLogicHeight( nHeight ); + + return nHeight; +} + +// ----------------------------------------------------------------------- + +void OutputDevice::DrawTextArray( const Point& rStartPt, const String& rStr, + const sal_Int32* pDXAry, + xub_StrLen nIndex, xub_StrLen nLen ) +{ + DBG_TRACE( "OutputDevice::DrawTextArray()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, nIndex, nLen ) ); + + if ( !IsDeviceOutputNecessary() ) + return; + if( !mpGraphics && !ImplGetGraphics() ) + return; + if( mbInitClipRegion ) + ImplInitClipRegion(); + if( mbOutputClipped ) + return; + + SalLayout* pSalLayout = ImplLayout( rStr, nIndex, nLen, rStartPt, 0, pDXAry, true ); + if( pSalLayout ) + { + ImplDrawText( *pSalLayout ); + pSalLayout->Release(); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawTextArray( rStartPt, rStr, pDXAry, nIndex, nLen ); +} + +// ----------------------------------------------------------------------- + +long OutputDevice::GetTextArray( const String& rStr, sal_Int32* pDXAry, + xub_StrLen nIndex, xub_StrLen nLen ) const +{ + DBG_TRACE( "OutputDevice::GetTextArray()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if( nIndex >= rStr.Len() ) + return 0; + if( (ULONG)nIndex+nLen >= rStr.Len() ) + nLen = rStr.Len() - nIndex; + + // do layout + SalLayout* pSalLayout = ImplLayout( rStr, nIndex, nLen ); + if( !pSalLayout ) + return 0; + + long nWidth = pSalLayout->FillDXArray( pDXAry ); + int nWidthFactor = pSalLayout->GetUnitsPerPixel(); + pSalLayout->Release(); + + // convert virtual char widths to virtual absolute positions + if( pDXAry ) + for( int i = 1; i < nLen; ++i ) + pDXAry[ i ] += pDXAry[ i-1 ]; + + // convert from font units to logical units + if( mbMap ) + { + if( pDXAry ) + for( int i = 0; i < nLen; ++i ) + pDXAry[i] = ImplDevicePixelToLogicWidth( pDXAry[i] ); + nWidth = ImplDevicePixelToLogicWidth( nWidth ); + } + + if( nWidthFactor > 1 ) + { + if( pDXAry ) + for( int i = 0; i < nLen; ++i ) + pDXAry[i] /= nWidthFactor; + nWidth /= nWidthFactor; + } + + return nWidth; +} + +// ----------------------------------------------------------------------- + +bool OutputDevice::GetCaretPositions( const XubString& rStr, sal_Int32* pCaretXArray, + xub_StrLen nIndex, xub_StrLen nLen, + sal_Int32* pDXAry, long nLayoutWidth, + BOOL bCellBreaking ) const +{ + DBG_TRACE( "OutputDevice::GetCaretPositions()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if( nIndex >= rStr.Len() ) + return false; + if( (ULONG)nIndex+nLen >= rStr.Len() ) + nLen = rStr.Len() - nIndex; + + // layout complex text + SalLayout* pSalLayout = ImplLayout( rStr, nIndex, nLen, + Point(0,0), nLayoutWidth, pDXAry ); + if( !pSalLayout ) + return false; + + int nWidthFactor = pSalLayout->GetUnitsPerPixel(); + pSalLayout->GetCaretPositions( 2*nLen, pCaretXArray ); + long nWidth = pSalLayout->GetTextWidth(); + pSalLayout->Release(); + + // fixup unknown caret positions + int i; + for( i = 0; i < 2 * nLen; ++i ) + if( pCaretXArray[ i ] >= 0 ) + break; + long nXPos = pCaretXArray[ i ]; + for( i = 0; i < 2 * nLen; ++i ) + { + if( pCaretXArray[ i ] >= 0 ) + nXPos = pCaretXArray[ i ]; + else + pCaretXArray[ i ] = nXPos; + } + + // handle window mirroring + if( IsRTLEnabled() ) + { + for( i = 0; i < 2 * nLen; ++i ) + pCaretXArray[i] = nWidth - pCaretXArray[i] - 1; + } + + // convert from font units to logical units + if( mbMap ) + { + for( i = 0; i < 2*nLen; ++i ) + pCaretXArray[i] = ImplDevicePixelToLogicWidth( pCaretXArray[i] ); + } + + if( nWidthFactor != 1 ) + { + for( i = 0; i < 2*nLen; ++i ) + pCaretXArray[i] /= nWidthFactor; + } + + // if requested move caret position to cell limits + if( bCellBreaking ) + { + ; // TODO + } + + return true; +} + +// ----------------------------------------------------------------------- + +void OutputDevice::DrawStretchText( const Point& rStartPt, ULONG nWidth, + const String& rStr, + xub_StrLen nIndex, xub_StrLen nLen ) +{ + DBG_TRACE( "OutputDevice::DrawStretchText()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaStretchTextAction( rStartPt, nWidth, rStr, nIndex, nLen ) ); + + if ( !IsDeviceOutputNecessary() ) + return; + + SalLayout* pSalLayout = ImplLayout( rStr, nIndex, nLen, rStartPt, nWidth, NULL, true ); + if( pSalLayout ) + { + ImplDrawText( *pSalLayout ); + pSalLayout->Release(); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawStretchText( rStartPt, nWidth, rStr, nIndex, nLen ); +} + +// ----------------------------------------------------------------------- + +ImplLayoutArgs OutputDevice::ImplPrepareLayoutArgs( String& rStr, + xub_StrLen nMinIndex, xub_StrLen nLen, + long nPixelWidth, const sal_Int32* pDXArray ) const +{ + // get string length for calculating extents + xub_StrLen nEndIndex = rStr.Len(); + if( (ULONG)nMinIndex + nLen < nEndIndex ) + nEndIndex = nMinIndex + nLen; + + // don't bother if there is nothing to do + if( nEndIndex < nMinIndex ) + nEndIndex = nMinIndex; + + int nLayoutFlags = 0; + if( mnTextLayoutMode & TEXT_LAYOUT_BIDI_RTL ) + nLayoutFlags |= SAL_LAYOUT_BIDI_RTL; + if( mnTextLayoutMode & TEXT_LAYOUT_BIDI_STRONG ) + nLayoutFlags |= SAL_LAYOUT_BIDI_STRONG; + else if( 0 == (mnTextLayoutMode & TEXT_LAYOUT_BIDI_RTL) ) + { + // disable Bidi if no RTL hint and no RTL codes used + const xub_Unicode* pStr = rStr.GetBuffer() + nMinIndex; + const xub_Unicode* pEnd = rStr.GetBuffer() + nEndIndex; + for( ; pStr < pEnd; ++pStr ) + if( ((*pStr >= 0x0580) && (*pStr < 0x0800)) // middle eastern scripts + || ((*pStr >= 0xFB18) && (*pStr < 0xFE00)) // hebrew + arabic A presentation forms + || ((*pStr >= 0xFE70) && (*pStr < 0xFEFF)) ) // arabic presentation forms B + break; + if( pStr >= pEnd ) + nLayoutFlags |= SAL_LAYOUT_BIDI_STRONG; + } + + if( mbKerning ) + nLayoutFlags |= SAL_LAYOUT_KERNING_PAIRS; + if( maFont.GetKerning() & KERNING_ASIAN ) + nLayoutFlags |= SAL_LAYOUT_KERNING_ASIAN; + if( maFont.IsVertical() ) + nLayoutFlags |= SAL_LAYOUT_VERTICAL; + + if( mnTextLayoutMode & TEXT_LAYOUT_ENABLE_LIGATURES ) + nLayoutFlags |= SAL_LAYOUT_ENABLE_LIGATURES; + else if( mnTextLayoutMode & TEXT_LAYOUT_COMPLEX_DISABLED ) + nLayoutFlags |= SAL_LAYOUT_COMPLEX_DISABLED; + else + { + // disable CTL for non-CTL text + const sal_Unicode* pStr = rStr.GetBuffer() + nMinIndex; + const sal_Unicode* pEnd = rStr.GetBuffer() + nEndIndex; + for( ; pStr < pEnd; ++pStr ) + if( ((*pStr >= 0x0300) && (*pStr < 0x0370)) // diacritical marks + || ((*pStr >= 0x0590) && (*pStr < 0x10A0)) // many CTL scripts + || ((*pStr >= 0x1100) && (*pStr < 0x1200)) // hangul jamo + || ((*pStr >= 0x1700) && (*pStr < 0x1900)) // many CTL scripts + || ((*pStr >= 0xFB1D) && (*pStr < 0xFE00)) // middle east presentation + || ((*pStr >= 0xFE70) && (*pStr < 0xFEFF)) ) // arabic presentation B + break; + if( pStr >= pEnd ) + nLayoutFlags |= SAL_LAYOUT_COMPLEX_DISABLED; + } + + if( meTextLanguage ) //TODO: (mnTextLayoutMode & TEXT_LAYOUT_SUBSTITUTE_DIGITS) + { + // disable character localization when no digits used + const sal_Unicode* pBase = rStr.GetBuffer(); + const sal_Unicode* pStr = pBase + nMinIndex; + const sal_Unicode* pEnd = pBase + nEndIndex; + for( ; pStr < pEnd; ++pStr ) + { + // TODO: are there non-digit localizations? + if( (*pStr >= '0') && (*pStr <= '9') ) + { + // translate characters to local preference + sal_UCS4 cChar = GetLocalizedChar( *pStr, meTextLanguage ); + if( cChar != *pStr ) + // TODO: are the localized digit surrogates? + rStr.SetChar( static_cast<USHORT>(pStr - pBase), + static_cast<sal_Unicode>(cChar) ); + } + } + } + + // right align for RTL text, DRAWPOS_REVERSED, RTL window style + bool bRightAlign = ((mnTextLayoutMode & TEXT_LAYOUT_BIDI_RTL) != 0); + if( mnTextLayoutMode & TEXT_LAYOUT_TEXTORIGIN_LEFT ) + bRightAlign = false; + else if ( mnTextLayoutMode & TEXT_LAYOUT_TEXTORIGIN_RIGHT ) + bRightAlign = true; + // SSA: hack for western office, ie text get right aligned + // for debugging purposes of mirrored UI + //static const char* pEnv = getenv( "SAL_RTL_MIRRORTEXT" ); + bool bRTLWindow = IsRTLEnabled(); + bRightAlign ^= bRTLWindow; + if( bRightAlign ) + nLayoutFlags |= SAL_LAYOUT_RIGHT_ALIGN; + + // set layout options + ImplLayoutArgs aLayoutArgs( rStr.GetBuffer(), rStr.Len(), nMinIndex, nEndIndex, nLayoutFlags ); + + int nOrientation = mpFontEntry ? mpFontEntry->mnOrientation : 0; + aLayoutArgs.SetOrientation( nOrientation ); + + aLayoutArgs.SetLayoutWidth( nPixelWidth ); + aLayoutArgs.SetDXArray( pDXArray ); + + return aLayoutArgs; +} + +// ----------------------------------------------------------------------- + +SalLayout* OutputDevice::ImplLayout( const String& rOrigStr, + xub_StrLen nMinIndex, + xub_StrLen nLen, + const Point& rLogicalPos, + long nLogicalWidth, + const sal_Int32* pDXArray, + bool bFilter ) const +{ + // we need a graphics + if( !mpGraphics ) + if( !ImplGetGraphics() ) + return NULL; + + // initialize font if needed + if( mbNewFont ) + if( !ImplNewFont() ) + return NULL; + if( mbInitFont ) + ImplInitFont(); + + // check string index and length + if( (unsigned)nMinIndex + nLen > rOrigStr.Len() ) + { + const int nNewLen = (int)rOrigStr.Len() - nMinIndex; + if( nNewLen <= 0 ) + return NULL; + nLen = static_cast<xub_StrLen>(nNewLen); + } + + String aStr = rOrigStr; + + // filter out special markers + if( bFilter ) + { + xub_StrLen nCutStart, nCutStop, nOrgLen = nLen; + bool bFiltered = mpGraphics->filterText( rOrigStr, aStr, nMinIndex, nLen, nCutStart, nCutStop ); + if( !nLen ) + return NULL; + + if( bFiltered && nCutStop != nCutStart && pDXArray ) + { + if( !nLen ) + pDXArray = NULL; + else + { + sal_Int32* pAry = (sal_Int32*)alloca(sizeof(sal_Int32)*nLen); + if( nCutStart > nMinIndex ) + memcpy( pAry, pDXArray, sizeof(sal_Int32)*(nCutStart-nMinIndex) ); + // note: nCutStart will never be smaller than nMinIndex + memcpy( pAry+nCutStart-nMinIndex, + pDXArray + nOrgLen - (nCutStop-nMinIndex), + sizeof(sal_Int32)*(nLen - (nCutStart-nMinIndex)) ); + pDXArray = pAry; + } + } + } + + // convert from logical units to physical units + // recode string if needed + if( mpFontEntry->mpConversion ) + mpFontEntry->mpConversion->RecodeString( aStr, 0, aStr.Len() ); + + long nPixelWidth = nLogicalWidth; + if( nLogicalWidth && mbMap ) + nPixelWidth = ImplLogicWidthToDevicePixel( nLogicalWidth ); + if( pDXArray && mbMap ) + { + // convert from logical units to font units using a temporary array + sal_Int32* pTempDXAry = (sal_Int32*)alloca( nLen * sizeof(sal_Int32) ); + // using base position for better rounding a.k.a. "dancing characters" + int nPixelXOfs = ImplLogicWidthToDevicePixel( rLogicalPos.X() ); + for( int i = 0; i < nLen; ++i ) + pTempDXAry[i] = ImplLogicWidthToDevicePixel( rLogicalPos.X() + pDXArray[i] ) - nPixelXOfs; + + pDXArray = pTempDXAry; + } + + ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nMinIndex, nLen, nPixelWidth, pDXArray ); + + // get matching layout object for base font + SalLayout* pSalLayout = NULL; + if( mpPDFWriter ) + pSalLayout = mpPDFWriter->GetTextLayout( aLayoutArgs, &mpFontEntry->maFontSelData ); + + if( !pSalLayout ) + pSalLayout = mpGraphics->GetTextLayout( aLayoutArgs, 0 ); + + // layout text + if( pSalLayout && !pSalLayout->LayoutText( aLayoutArgs ) ) + { + pSalLayout->Release(); + pSalLayout = NULL; + } + + if( !pSalLayout ) + return NULL; + + // do glyph fallback if needed + // #105768# avoid fallback for very small font sizes + if( aLayoutArgs.NeedFallback() ) + if( mpFontEntry && (mpFontEntry->maFontSelData.mnHeight >= 3) ) + pSalLayout = ImplGlyphFallbackLayout( pSalLayout, aLayoutArgs ); + + // position, justify, etc. the layout + pSalLayout->AdjustLayout( aLayoutArgs ); + pSalLayout->DrawBase() = ImplLogicToDevicePixel( rLogicalPos ); + // adjust to right alignment if necessary + if( aLayoutArgs.mnFlags & SAL_LAYOUT_RIGHT_ALIGN ) + { + long nRTLOffset; + if( pDXArray ) + nRTLOffset = pDXArray[ nLen - 1 ]; + else if( nPixelWidth ) + nRTLOffset = nPixelWidth; + else + nRTLOffset = pSalLayout->GetTextWidth() / pSalLayout->GetUnitsPerPixel(); + pSalLayout->DrawOffset().X() = 1 - nRTLOffset; + } + + return pSalLayout; +} + +// ----------------------------------------------------------------------- + +SalLayout* OutputDevice::ImplGlyphFallbackLayout( SalLayout* pSalLayout, ImplLayoutArgs& rLayoutArgs ) const +{ + // prepare multi level glyph fallback + MultiSalLayout* pMultiSalLayout = NULL; + ImplLayoutRuns aLayoutRuns = rLayoutArgs.maRuns; + rLayoutArgs.PrepareFallback(); + rLayoutArgs.mnFlags |= SAL_LAYOUT_FOR_FALLBACK; + +#if defined(HDU_DEBUG) + { + int nCharPos = -1; + bool bRTL = false; + fprintf(stderr,"OD:ImplLayout Glyph Fallback for"); + for( int i=0; i<8 && rLayoutArgs.GetNextPos( &nCharPos, &bRTL); ++i ) + fprintf(stderr," U+%04X", rLayoutArgs.mpStr[ nCharPos ] ); + fprintf(stderr,"\n"); + rLayoutArgs.ResetPos(); + } +#endif + // get list of unicodes that need glyph fallback + int nCharPos = -1; + bool bRTL = false; + rtl::OUStringBuffer aMissingCodeBuf; + while( rLayoutArgs.GetNextPos( &nCharPos, &bRTL) ) + aMissingCodeBuf.append( rLayoutArgs.mpStr[ nCharPos ] ); + rLayoutArgs.ResetPos(); + rtl::OUString aMissingCodes = aMissingCodeBuf.makeStringAndClear(); + + ImplFontSelectData aFontSelData = mpFontEntry->maFontSelData; + // when device specific font substitution may have been performed for + // the originally selected font then make sure that a fallback to that + // font is performed first + int nDevSpecificFallback = 0; + if( mpOutDevData && !mpOutDevData->maDevFontSubst.Empty() ) + nDevSpecificFallback = 1; + + // try if fallback fonts support the missing unicodes + for( int nFallbackLevel = 1; nFallbackLevel < MAX_FALLBACK; ++nFallbackLevel ) + { + // find a font family suited for glyph fallback +#ifndef FONTFALLBACK_HOOKS_DISABLED + // GetGlyphFallbackFont() needs a valid aFontSelData.mpFontEntry + // if the system-specific glyph fallback is active + aFontSelData.mpFontEntry = mpFontEntry; // reset the fontentry to base-level +#endif + ImplFontEntry* pFallbackFont = mpFontCache->GetGlyphFallbackFont( mpFontList, + aFontSelData, nFallbackLevel-nDevSpecificFallback, aMissingCodes ); + if( !pFallbackFont ) + break; + + aFontSelData.mpFontEntry = pFallbackFont; + aFontSelData.mpFontData = pFallbackFont->maFontSelData.mpFontData; + if( mpFontEntry && nFallbackLevel < MAX_FALLBACK-1) + { + // ignore fallback font if it is the same as the original font + if( mpFontEntry->maFontSelData.mpFontData == aFontSelData.mpFontData ) + { + mpFontCache->Release( pFallbackFont ); + continue; + } + } + +#if defined(HDU_DEBUG) + { + ByteString aOrigFontName( maFont.GetName(), RTL_TEXTENCODING_UTF8); + ByteString aFallbackName( aFontSelData.mpFontData->GetFamilyName(), + RTL_TEXTENCODING_UTF8); + fprintf(stderr,"\tGlyphFallback[lvl=%d] \"%s\" -> \"%s\" (q=%d)\n", + nFallbackLevel, aOrigFontName.GetBuffer(), aFallbackName.GetBuffer(), + aFontSelData.mpFontData->GetQuality()); + } +#endif + + pFallbackFont->mnSetFontFlags = mpGraphics->SetFont( &aFontSelData, nFallbackLevel ); + + // create and add glyph fallback layout to multilayout + rLayoutArgs.ResetPos(); + SalLayout* pFallback = mpGraphics->GetTextLayout( rLayoutArgs, nFallbackLevel ); + if( pFallback ) + { + if( pFallback->LayoutText( rLayoutArgs ) ) + { + if( !pMultiSalLayout ) + pMultiSalLayout = new MultiSalLayout( *pSalLayout ); + pMultiSalLayout->AddFallback( *pFallback, + rLayoutArgs.maRuns, aFontSelData.mpFontData ); + if (nFallbackLevel == MAX_FALLBACK-1) + pMultiSalLayout->SetInComplete(); + } + else + { + // there is no need for a font that couldn't resolve anything + pFallback->Release(); + } + } + + mpFontCache->Release( pFallbackFont ); + + // break when this fallback was sufficient + if( !rLayoutArgs.PrepareFallback() ) + break; + } + + if( pMultiSalLayout && pMultiSalLayout->LayoutText( rLayoutArgs ) ) + pSalLayout = pMultiSalLayout; + + // restore orig font settings + pSalLayout->InitFont(); + rLayoutArgs.maRuns = aLayoutRuns; + + return pSalLayout; +} + +// ----------------------------------------------------------------------- + +BOOL OutputDevice::GetTextIsRTL( + const String& rString, + xub_StrLen nIndex, xub_StrLen nLen ) const +{ + String aStr( rString ); + ImplLayoutArgs aArgs = ImplPrepareLayoutArgs( aStr, nIndex, nLen, 0, NULL ); + bool bRTL = false; + int nCharPos = -1; + aArgs.GetNextPos( &nCharPos, &bRTL ); + return (nCharPos != nIndex) ? TRUE : FALSE; +} + +// ----------------------------------------------------------------------- + +xub_StrLen OutputDevice::GetTextBreak( const String& rStr, long nTextWidth, + xub_StrLen nIndex, xub_StrLen nLen, + long nCharExtra, BOOL /*TODO: bCellBreaking*/ ) const +{ + DBG_TRACE( "OutputDevice::GetTextBreak()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + SalLayout* pSalLayout = ImplLayout( rStr, nIndex, nLen ); + xub_StrLen nRetVal = STRING_LEN; + if( pSalLayout ) + { + // convert logical widths into layout units + // NOTE: be very careful to avoid rounding errors for nCharExtra case + // problem with rounding errors especially for small nCharExtras + // TODO: remove when layout units have subpixel granularity + long nWidthFactor = pSalLayout->GetUnitsPerPixel(); + long nSubPixelFactor = (nWidthFactor < 64 ) ? 64 : 1; + nTextWidth *= nWidthFactor * nSubPixelFactor; + long nTextPixelWidth = ImplLogicWidthToDevicePixel( nTextWidth ); + long nExtraPixelWidth = 0; + if( nCharExtra != 0 ) + { + nCharExtra *= nWidthFactor * nSubPixelFactor; + nExtraPixelWidth = ImplLogicWidthToDevicePixel( nCharExtra ); + } + nRetVal = sal::static_int_cast<xub_StrLen>(pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor )); + + pSalLayout->Release(); + } + + return nRetVal; +} + +// ----------------------------------------------------------------------- + +xub_StrLen OutputDevice::GetTextBreak( const String& rStr, long nTextWidth, + sal_Unicode nHyphenatorChar, xub_StrLen& rHyphenatorPos, + xub_StrLen nIndex, xub_StrLen nLen, + long nCharExtra ) const +{ + DBG_TRACE( "OutputDevice::GetTextBreak()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + rHyphenatorPos = STRING_LEN; + + SalLayout* pSalLayout = ImplLayout( rStr, nIndex, nLen ); + if( !pSalLayout ) + return STRING_LEN; + + // convert logical widths into layout units + // NOTE: be very careful to avoid rounding errors for nCharExtra case + // problem with rounding errors especially for small nCharExtras + // TODO: remove when layout units have subpixel granularity + long nWidthFactor = pSalLayout->GetUnitsPerPixel(); + long nSubPixelFactor = (nWidthFactor < 64 ) ? 64 : 1; + + nTextWidth *= nWidthFactor * nSubPixelFactor; + long nTextPixelWidth = ImplLogicWidthToDevicePixel( nTextWidth ); + long nExtraPixelWidth = 0; + if( nCharExtra != 0 ) + { + nCharExtra *= nWidthFactor * nSubPixelFactor; + nExtraPixelWidth = ImplLogicWidthToDevicePixel( nCharExtra ); + } + + // calculate un-hyphenated break position + xub_StrLen nRetVal = sal::static_int_cast<xub_StrLen>(pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor )); + + // calculate hyphenated break position + String aHyphenatorStr( &nHyphenatorChar, 1 ); + xub_StrLen nTempLen = 1; + SalLayout* pHyphenatorLayout = ImplLayout( aHyphenatorStr, 0, nTempLen ); + if( pHyphenatorLayout ) + { + // calculate subpixel width of hyphenation character + long nHyphenatorPixelWidth = pHyphenatorLayout->GetTextWidth() * nSubPixelFactor; + pHyphenatorLayout->Release(); + + // calculate hyphenated break position + nTextPixelWidth -= nHyphenatorPixelWidth; + if( nExtraPixelWidth > 0 ) + nTextPixelWidth -= nExtraPixelWidth; + + rHyphenatorPos = sal::static_int_cast<xub_StrLen>(pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor )); + + if( rHyphenatorPos > nRetVal ) + rHyphenatorPos = nRetVal; + } + + pSalLayout->Release(); + return nRetVal; +} + +// ----------------------------------------------------------------------- + +void OutputDevice::ImplDrawText( OutputDevice& rTargetDevice, const Rectangle& rRect, + const String& rOrigStr, USHORT nStyle, + MetricVector* pVector, String* pDisplayText, + ::vcl::ITextLayout& _rLayout ) +{ + Color aOldTextColor; + Color aOldTextFillColor; + BOOL bRestoreFillColor = false; + if ( (nStyle & TEXT_DRAW_DISABLE) && ! pVector ) + { + BOOL bHighContrastBlack = FALSE; + BOOL bHighContrastWhite = FALSE; + const StyleSettings& rStyleSettings( rTargetDevice.GetSettings().GetStyleSettings() ); + if( rStyleSettings.GetHighContrastMode() ) + { + Color aCol; + if( rTargetDevice.IsBackground() ) + aCol = rTargetDevice.GetBackground().GetColor(); + else + // best guess is the face color here + // but it may be totally wrong. the background color + // was typically already reset + aCol = rStyleSettings.GetFaceColor(); + + bHighContrastBlack = aCol.IsDark(); + bHighContrastWhite = aCol.IsBright(); + } + + aOldTextColor = rTargetDevice.GetTextColor(); + if ( rTargetDevice.IsTextFillColor() ) + { + bRestoreFillColor = TRUE; + aOldTextFillColor = rTargetDevice.GetTextFillColor(); + } + if( bHighContrastBlack ) + rTargetDevice.SetTextColor( COL_GREEN ); + else if( bHighContrastWhite ) + rTargetDevice.SetTextColor( COL_LIGHTGREEN ); + else + { + // draw disabled text always without shadow + // as it fits better with native look + /* + SetTextColor( GetSettings().GetStyleSettings().GetLightColor() ); + Rectangle aRect = rRect; + aRect.Move( 1, 1 ); + DrawText( aRect, rOrigStr, nStyle & ~TEXT_DRAW_DISABLE ); + */ + rTargetDevice.SetTextColor( rTargetDevice.GetSettings().GetStyleSettings().GetDisableColor() ); + } + } + + long nWidth = rRect.GetWidth(); + long nHeight = rRect.GetHeight(); + + if ( ((nWidth <= 0) || (nHeight <= 0)) && (nStyle & TEXT_DRAW_CLIP) ) + return; + + Point aPos = rRect.TopLeft(); + + long nTextHeight = rTargetDevice.GetTextHeight(); + TextAlign eAlign = rTargetDevice.GetTextAlign(); + xub_StrLen nMnemonicPos = STRING_NOTFOUND; + + String aStr = rOrigStr; + if ( nStyle & TEXT_DRAW_MNEMONIC ) + aStr = GetNonMnemonicString( aStr, nMnemonicPos ); + + const bool bDrawMnemonics = !(rTargetDevice.GetSettings().GetStyleSettings().GetOptions() & STYLE_OPTION_NOMNEMONICS) && !pVector; + + // Mehrzeiligen Text behandeln wir anders + if ( nStyle & TEXT_DRAW_MULTILINE ) + { + + XubString aLastLine; + ImplMultiTextLineInfo aMultiLineInfo; + ImplTextLineInfo* pLineInfo; + long nMaxTextWidth; + xub_StrLen i; + xub_StrLen nLines; + xub_StrLen nFormatLines; + + if ( nTextHeight ) + { + nMaxTextWidth = ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, _rLayout ); + nLines = (xub_StrLen)(nHeight/nTextHeight); + nFormatLines = aMultiLineInfo.Count(); + if ( !nLines ) + nLines = 1; + if ( nFormatLines > nLines ) + { + if ( nStyle & TEXT_DRAW_ENDELLIPSIS ) + { + // Letzte Zeile zusammenbauen und kuerzen + nFormatLines = nLines-1; + + pLineInfo = aMultiLineInfo.GetLine( nFormatLines ); + aLastLine = aStr.Copy( pLineInfo->GetIndex() ); + aLastLine.ConvertLineEnd( LINEEND_LF ); + // Alle LineFeed's durch Spaces ersetzen + xub_StrLen nLastLineLen = aLastLine.Len(); + for ( i = 0; i < nLastLineLen; i++ ) + { + if ( aLastLine.GetChar( i ) == _LF ) + aLastLine.SetChar( i, ' ' ); + } + aLastLine = ImplGetEllipsisString( rTargetDevice, aLastLine, nWidth, nStyle, _rLayout ); + nStyle &= ~(TEXT_DRAW_VCENTER | TEXT_DRAW_BOTTOM); + nStyle |= TEXT_DRAW_TOP; + } + } + else + { + if ( nMaxTextWidth <= nWidth ) + nStyle &= ~TEXT_DRAW_CLIP; + } + + // Muss in der Hoehe geclippt werden? + if ( nFormatLines*nTextHeight > nHeight ) + nStyle |= TEXT_DRAW_CLIP; + + // Clipping setzen + if ( nStyle & TEXT_DRAW_CLIP ) + { + rTargetDevice.Push( PUSH_CLIPREGION ); + rTargetDevice.IntersectClipRegion( rRect ); + } + + // Vertikales Alignment + if ( nStyle & TEXT_DRAW_BOTTOM ) + aPos.Y() += nHeight-(nFormatLines*nTextHeight); + else if ( nStyle & TEXT_DRAW_VCENTER ) + aPos.Y() += (nHeight-(nFormatLines*nTextHeight))/2; + + // Font Alignment + if ( eAlign == ALIGN_BOTTOM ) + aPos.Y() += nTextHeight; + else if ( eAlign == ALIGN_BASELINE ) + aPos.Y() += rTargetDevice.GetFontMetric().GetAscent(); + + // Alle Zeilen ausgeben, bis auf die letzte + for ( i = 0; i < nFormatLines; i++ ) + { + pLineInfo = aMultiLineInfo.GetLine( i ); + if ( nStyle & TEXT_DRAW_RIGHT ) + aPos.X() += nWidth-pLineInfo->GetWidth(); + else if ( nStyle & TEXT_DRAW_CENTER ) + aPos.X() += (nWidth-pLineInfo->GetWidth())/2; + xub_StrLen nIndex = pLineInfo->GetIndex(); + xub_StrLen nLineLen = pLineInfo->GetLen(); + _rLayout.DrawText( aPos, aStr, nIndex, nLineLen, pVector, pDisplayText ); + if ( bDrawMnemonics ) + { + if ( (nMnemonicPos >= nIndex) && (nMnemonicPos < nIndex+nLineLen) ) + { + long nMnemonicX; + long nMnemonicY; + long nMnemonicWidth; + + sal_Int32* pCaretXArray = (sal_Int32*) alloca( 2 * sizeof(sal_Int32) * nLineLen ); + /*BOOL bRet =*/ _rLayout.GetCaretPositions( aStr, pCaretXArray, + nIndex, nLineLen ); + long lc_x1 = pCaretXArray[2*(nMnemonicPos - nIndex)]; + long lc_x2 = pCaretXArray[2*(nMnemonicPos - nIndex)+1]; + nMnemonicWidth = rTargetDevice.ImplLogicWidthToDevicePixel( ::abs((int)(lc_x1 - lc_x2)) ); + + Point aTempPos = rTargetDevice.LogicToPixel( aPos ); + nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( Min( lc_x1, lc_x2 ) ); + nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() ); + rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); + } + } + aPos.Y() += nTextHeight; + aPos.X() = rRect.Left(); + } + + + // Gibt es noch eine letzte Zeile, dann diese linksbuendig ausgeben, + // da die Zeile gekuerzt wurde + if ( aLastLine.Len() ) + _rLayout.DrawText( aPos, aLastLine, 0, STRING_LEN, pVector, pDisplayText ); + + // Clipping zuruecksetzen + if ( nStyle & TEXT_DRAW_CLIP ) + rTargetDevice.Pop(); + } + } + else + { + long nTextWidth = _rLayout.GetTextWidth( aStr, 0, STRING_LEN ); + + // Evt. Text kuerzen + if ( nTextWidth > nWidth ) + { + if ( nStyle & TEXT_DRAW_ELLIPSIS ) + { + aStr = ImplGetEllipsisString( rTargetDevice, aStr, nWidth, nStyle, _rLayout ); + nStyle &= ~(TEXT_DRAW_CENTER | TEXT_DRAW_RIGHT); + nStyle |= TEXT_DRAW_LEFT; + nTextWidth = _rLayout.GetTextWidth( aStr, 0, aStr.Len() ); + } + } + else + { + if ( nTextHeight <= nHeight ) + nStyle &= ~TEXT_DRAW_CLIP; + } + + // horizontal text alignment + if ( nStyle & TEXT_DRAW_RIGHT ) + aPos.X() += nWidth-nTextWidth; + else if ( nStyle & TEXT_DRAW_CENTER ) + aPos.X() += (nWidth-nTextWidth)/2; + + // vertical font alignment + if ( eAlign == ALIGN_BOTTOM ) + aPos.Y() += nTextHeight; + else if ( eAlign == ALIGN_BASELINE ) + aPos.Y() += rTargetDevice.GetFontMetric().GetAscent(); + + if ( nStyle & TEXT_DRAW_BOTTOM ) + aPos.Y() += nHeight-nTextHeight; + else if ( nStyle & TEXT_DRAW_VCENTER ) + aPos.Y() += (nHeight-nTextHeight)/2; + + long nMnemonicX = 0; + long nMnemonicY = 0; + long nMnemonicWidth = 0; + if ( nMnemonicPos != STRING_NOTFOUND ) + { + sal_Int32* pCaretXArray = (sal_Int32*) alloca( 2 * sizeof(sal_Int32) * aStr.Len() ); + /*BOOL bRet =*/ _rLayout.GetCaretPositions( aStr, pCaretXArray, 0, aStr.Len() ); + long lc_x1 = pCaretXArray[2*(nMnemonicPos)]; + long lc_x2 = pCaretXArray[2*(nMnemonicPos)+1]; + nMnemonicWidth = rTargetDevice.ImplLogicWidthToDevicePixel( ::abs((int)(lc_x1 - lc_x2)) ); + + Point aTempPos = rTargetDevice.LogicToPixel( aPos ); + nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( Min(lc_x1, lc_x2) ); + nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() ); + } + + if ( nStyle & TEXT_DRAW_CLIP ) + { + rTargetDevice.Push( PUSH_CLIPREGION ); + rTargetDevice.IntersectClipRegion( rRect ); + _rLayout.DrawText( aPos, aStr, 0, STRING_LEN, pVector, pDisplayText ); + if ( bDrawMnemonics ) + { + if ( nMnemonicPos != STRING_NOTFOUND ) + rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); + } + rTargetDevice.Pop(); + } + else + { + _rLayout.DrawText( aPos, aStr, 0, STRING_LEN, pVector, pDisplayText ); + if ( bDrawMnemonics ) + { + if ( nMnemonicPos != STRING_NOTFOUND ) + rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); + } + } + } + + if ( nStyle & TEXT_DRAW_DISABLE && !pVector ) + { + rTargetDevice.SetTextColor( aOldTextColor ); + if ( bRestoreFillColor ) + rTargetDevice.SetTextFillColor( aOldTextFillColor ); + } +} + +// ----------------------------------------------------------------------- + +void OutputDevice::AddTextRectActions( const Rectangle& rRect, + const String& rOrigStr, + USHORT nStyle, + GDIMetaFile& rMtf ) +{ + DBG_TRACE( "OutputDevice::AddTextRectActions( const Rectangle& )" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if ( !rOrigStr.Len() || rRect.IsEmpty() ) + return; + + // we need a graphics + if( !mpGraphics && !ImplGetGraphics() ) + return; + if( mbInitClipRegion ) + ImplInitClipRegion(); + + // temporarily swap in passed mtf for action generation, and + // disable output generation. + const BOOL bOutputEnabled( IsOutputEnabled() ); + GDIMetaFile* pMtf = mpMetaFile; + + mpMetaFile = &rMtf; + EnableOutput( FALSE ); + + // #i47157# Factored out to ImplDrawTextRect(), to be shared + // between us and DrawText() + DefaultTextLayout aLayout( *this ); + ImplDrawText( *this, rRect, rOrigStr, nStyle, NULL, NULL, aLayout ); + + // and restore again + EnableOutput( bOutputEnabled ); + mpMetaFile = pMtf; +} + +// ----------------------------------------------------------------------- + +void OutputDevice::DrawText( const Rectangle& rRect, const String& rOrigStr, USHORT nStyle, + MetricVector* pVector, String* pDisplayText, + ::vcl::ITextLayout* _pTextLayout ) +{ + if( mpOutDevData && mpOutDevData->mpRecordLayout ) + { + pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects; + pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText; + } + + DBG_TRACE( "OutputDevice::DrawText( const Rectangle& )" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + bool bDecomposeTextRectAction = ( _pTextLayout != NULL ) && _pTextLayout->DecomposeTextRectAction(); + if ( mpMetaFile && !bDecomposeTextRectAction ) + mpMetaFile->AddAction( new MetaTextRectAction( rRect, rOrigStr, nStyle ) ); + + if ( ( !IsDeviceOutputNecessary() && !pVector && !bDecomposeTextRectAction ) || !rOrigStr.Len() || rRect.IsEmpty() ) + return; + + // we need a graphics + if( !mpGraphics && !ImplGetGraphics() ) + return; + if( mbInitClipRegion ) + ImplInitClipRegion(); + if( mbOutputClipped && !bDecomposeTextRectAction ) + return; + + // temporarily disable mtf action generation (ImplDrawText _does_ + // create META_TEXT_ACTIONs otherwise) + GDIMetaFile* pMtf = mpMetaFile; + if ( !bDecomposeTextRectAction ) + mpMetaFile = NULL; + + // #i47157# Factored out to ImplDrawText(), to be used also + // from AddTextRectActions() + DefaultTextLayout aDefaultLayout( *this ); + ImplDrawText( *this, rRect, rOrigStr, nStyle, pVector, pDisplayText, _pTextLayout ? *_pTextLayout : aDefaultLayout ); + + // and enable again + mpMetaFile = pMtf; + + if( mpAlphaVDev ) + mpAlphaVDev->DrawText( rRect, rOrigStr, nStyle, pVector, pDisplayText ); +} + +// ----------------------------------------------------------------------- + +Rectangle OutputDevice::GetTextRect( const Rectangle& rRect, + const XubString& rStr, USHORT nStyle, + TextRectInfo* pInfo, + const ::vcl::ITextLayout* _pTextLayout ) const +{ + DBG_TRACE( "OutputDevice::GetTextRect()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + Rectangle aRect = rRect; + xub_StrLen nLines; + long nWidth = rRect.GetWidth(); + long nMaxWidth; + long nTextHeight = GetTextHeight(); + + String aStr = rStr; + if ( nStyle & TEXT_DRAW_MNEMONIC ) + aStr = GetNonMnemonicString( aStr ); + + if ( nStyle & TEXT_DRAW_MULTILINE ) + { + ImplMultiTextLineInfo aMultiLineInfo; + ImplTextLineInfo* pLineInfo; + xub_StrLen nFormatLines; + xub_StrLen i; + + nMaxWidth = 0; + DefaultTextLayout aDefaultLayout( *const_cast< OutputDevice* >( this ) ); + ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, _pTextLayout ? *_pTextLayout : aDefaultLayout ); + nFormatLines = aMultiLineInfo.Count(); + if ( !nTextHeight ) + nTextHeight = 1; + nLines = (USHORT)(aRect.GetHeight()/nTextHeight); + if ( pInfo ) + pInfo->mnLineCount = nFormatLines; + if ( !nLines ) + nLines = 1; + if ( nFormatLines <= nLines ) + nLines = nFormatLines; + else + { + if ( !(nStyle & TEXT_DRAW_ENDELLIPSIS) ) + nLines = nFormatLines; + else + { + if ( pInfo ) + pInfo->mbEllipsis = TRUE; + nMaxWidth = nWidth; + } + } + if ( pInfo ) + { + BOOL bMaxWidth = nMaxWidth == 0; + pInfo->mnMaxWidth = 0; + for ( i = 0; i < nLines; i++ ) + { + pLineInfo = aMultiLineInfo.GetLine( i ); + if ( bMaxWidth && (pLineInfo->GetWidth() > nMaxWidth) ) + nMaxWidth = pLineInfo->GetWidth(); + if ( pLineInfo->GetWidth() > pInfo->mnMaxWidth ) + pInfo->mnMaxWidth = pLineInfo->GetWidth(); + } + } + else if ( !nMaxWidth ) + { + for ( i = 0; i < nLines; i++ ) + { + pLineInfo = aMultiLineInfo.GetLine( i ); + if ( pLineInfo->GetWidth() > nMaxWidth ) + nMaxWidth = pLineInfo->GetWidth(); + } + } + } + else + { + nLines = 1; + nMaxWidth = _pTextLayout ? _pTextLayout->GetTextWidth( aStr, 0, aStr.Len() ) : GetTextWidth( aStr ); + + if ( pInfo ) + { + pInfo->mnLineCount = 1; + pInfo->mnMaxWidth = nMaxWidth; + } + + if ( (nMaxWidth > nWidth) && (nStyle & TEXT_DRAW_ELLIPSIS) ) + { + if ( pInfo ) + pInfo->mbEllipsis = TRUE; + nMaxWidth = nWidth; + } + } + + if ( nStyle & TEXT_DRAW_RIGHT ) + aRect.Left() = aRect.Right()-nMaxWidth+1; + else if ( nStyle & TEXT_DRAW_CENTER ) + { + aRect.Left() += (nWidth-nMaxWidth)/2; + aRect.Right() = aRect.Left()+nMaxWidth-1; + } + else + aRect.Right() = aRect.Left()+nMaxWidth-1; + + if ( nStyle & TEXT_DRAW_BOTTOM ) + aRect.Top() = aRect.Bottom()-(nTextHeight*nLines)+1; + else if ( nStyle & TEXT_DRAW_VCENTER ) + { + aRect.Top() += (aRect.GetHeight()-(nTextHeight*nLines))/2; + aRect.Bottom() = aRect.Top()+(nTextHeight*nLines)-1; + } + else + aRect.Bottom() = aRect.Top()+(nTextHeight*nLines)-1; + + aRect.Right()++; // #99188# get rid of rounding problems when using this rect later + return aRect; +} + +// ----------------------------------------------------------------------- + +static BOOL ImplIsCharIn( xub_Unicode c, const sal_Char* pStr ) +{ + while ( *pStr ) + { + if ( *pStr == c ) + return TRUE; + pStr++; + } + + return FALSE; +} + +// ----------------------------------------------------------------------- + +String OutputDevice::GetEllipsisString( const String& rOrigStr, long nMaxWidth, + USHORT nStyle ) const +{ + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + DefaultTextLayout aTextLayout( *const_cast< OutputDevice* >( this ) ); + return ImplGetEllipsisString( *this, rOrigStr, nMaxWidth, nStyle, aTextLayout ); +} + +// ----------------------------------------------------------------------- + +String OutputDevice::ImplGetEllipsisString( const OutputDevice& rTargetDevice, const XubString& rOrigStr, long nMaxWidth, + USHORT nStyle, const ::vcl::ITextLayout& _rLayout ) +{ + DBG_TRACE( "OutputDevice::ImplGetEllipsisString()" ); + + String aStr = rOrigStr; + xub_StrLen nIndex = _rLayout.GetTextBreak( aStr, nMaxWidth, 0, aStr.Len() ); + + + if ( nIndex != STRING_LEN ) + { + if ( nStyle & TEXT_DRAW_ENDELLIPSIS ) + { + aStr.Erase( nIndex ); + if ( nIndex > 1 ) + { + aStr.AppendAscii( "..." ); + while ( aStr.Len() && (_rLayout.GetTextWidth( aStr, 0, aStr.Len() ) > nMaxWidth) ) + { + if ( (nIndex > 1) || (nIndex == aStr.Len()) ) + nIndex--; + aStr.Erase( nIndex, 1 ); + } + } + + if ( !aStr.Len() && (nStyle & TEXT_DRAW_CLIP) ) + aStr += rOrigStr.GetChar( 0 ); + } + else if ( nStyle & TEXT_DRAW_PATHELLIPSIS ) + { + rtl::OUString aPath( rOrigStr ); + rtl::OUString aAbbreviatedPath; + osl_abbreviateSystemPath( aPath.pData, &aAbbreviatedPath.pData, nIndex, NULL ); + aStr = aAbbreviatedPath; + } + else if ( nStyle & TEXT_DRAW_NEWSELLIPSIS ) + { + static sal_Char const pSepChars[] = "."; + // Letztes Teilstueck ermitteln + xub_StrLen nLastContent = aStr.Len(); + while ( nLastContent ) + { + nLastContent--; + if ( ImplIsCharIn( aStr.GetChar( nLastContent ), pSepChars ) ) + break; + } + while ( nLastContent && + ImplIsCharIn( aStr.GetChar( nLastContent-1 ), pSepChars ) ) + nLastContent--; + + XubString aLastStr( aStr, nLastContent, aStr.Len() ); + XubString aTempLastStr1( RTL_CONSTASCII_USTRINGPARAM( "..." ) ); + aTempLastStr1 += aLastStr; + if ( _rLayout.GetTextWidth( aTempLastStr1, 0, aTempLastStr1.Len() ) > nMaxWidth ) + aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | TEXT_DRAW_ENDELLIPSIS, _rLayout ); + else + { + USHORT nFirstContent = 0; + while ( nFirstContent < nLastContent ) + { + nFirstContent++; + if ( ImplIsCharIn( aStr.GetChar( nFirstContent ), pSepChars ) ) + break; + } + while ( (nFirstContent < nLastContent) && + ImplIsCharIn( aStr.GetChar( nFirstContent ), pSepChars ) ) + nFirstContent++; + + if ( nFirstContent >= nLastContent ) + aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | TEXT_DRAW_ENDELLIPSIS, _rLayout ); + else + { + if ( nFirstContent > 4 ) + nFirstContent = 4; + XubString aFirstStr( aStr, 0, nFirstContent ); + aFirstStr.AppendAscii( "..." ); + XubString aTempStr = aFirstStr; + aTempStr += aLastStr; + if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.Len() ) > nMaxWidth ) + aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | TEXT_DRAW_ENDELLIPSIS, _rLayout ); + else + { + do + { + aStr = aTempStr; + if( nLastContent > aStr.Len() ) + nLastContent = aStr.Len(); + while ( nFirstContent < nLastContent ) + { + nLastContent--; + if ( ImplIsCharIn( aStr.GetChar( nLastContent ), pSepChars ) ) + break; + + } + while ( (nFirstContent < nLastContent) && + ImplIsCharIn( aStr.GetChar( nLastContent-1 ), pSepChars ) ) + nLastContent--; + + if ( nFirstContent < nLastContent ) + { + XubString aTempLastStr( aStr, nLastContent, aStr.Len() ); + aTempStr = aFirstStr; + aTempStr += aTempLastStr; + if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.Len() ) > nMaxWidth ) + break; + } + } + while ( nFirstContent < nLastContent ); + } + } + } + } + } + + return aStr; +} + +// ----------------------------------------------------------------------- + +void OutputDevice::DrawCtrlText( const Point& rPos, const XubString& rStr, + xub_StrLen nIndex, xub_StrLen nLen, + USHORT nStyle, MetricVector* pVector, String* pDisplayText ) +{ + DBG_TRACE( "OutputDevice::DrawCtrlText()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if ( !IsDeviceOutputNecessary() || (nIndex >= rStr.Len()) ) + return; + + // better get graphics here because ImplDrawMnemonicLine() will not + // we need a graphics + if( !mpGraphics && !ImplGetGraphics() ) + return; + if( mbInitClipRegion ) + ImplInitClipRegion(); + if ( mbOutputClipped ) + return; + + if( nIndex >= rStr.Len() ) + return; + if( (ULONG)nIndex+nLen >= rStr.Len() ) + nLen = rStr.Len() - nIndex; + + XubString aStr = rStr; + xub_StrLen nMnemonicPos = STRING_NOTFOUND; + + long nMnemonicX = 0; + long nMnemonicY = 0; + long nMnemonicWidth = 0; + if ( (nStyle & TEXT_DRAW_MNEMONIC) && nLen > 1 ) + { + aStr = GetNonMnemonicString( aStr, nMnemonicPos ); + if ( nMnemonicPos != STRING_NOTFOUND ) + { + if( nMnemonicPos < nIndex ) + --nIndex; + else if( nLen < STRING_LEN ) + { + if( nMnemonicPos < (nIndex+nLen) ) + --nLen; + DBG_ASSERT( nMnemonicPos < (nIndex+nLen), "Mnemonic underline marker after last character" ); + } + BOOL bInvalidPos = FALSE; + + if( nMnemonicPos >= nLen ) + { + // #106952# + // may occur in BiDi-Strings: the '~' is sometimes found behind the last char + // due to some strange BiDi text editors + // ->place the underline behind the string to indicate a failure + bInvalidPos = TRUE; + nMnemonicPos = nLen-1; + } + + sal_Int32* pCaretXArray = (sal_Int32*)alloca( 2 * sizeof(sal_Int32) * nLen ); + /*BOOL bRet =*/ GetCaretPositions( aStr, pCaretXArray, nIndex, nLen ); + long lc_x1 = pCaretXArray[ 2*(nMnemonicPos - nIndex) ]; + long lc_x2 = pCaretXArray[ 2*(nMnemonicPos - nIndex)+1 ]; + nMnemonicWidth = ::abs((int)(lc_x1 - lc_x2)); + + Point aTempPos( Min(lc_x1,lc_x2), GetFontMetric().GetAscent() ); + if( bInvalidPos ) // #106952#, place behind the (last) character + aTempPos = Point( Max(lc_x1,lc_x2), GetFontMetric().GetAscent() ); + + aTempPos += rPos; + aTempPos = LogicToPixel( aTempPos ); + nMnemonicX = mnOutOffX + aTempPos.X(); + nMnemonicY = mnOutOffY + aTempPos.Y(); + } + } + + if ( nStyle & TEXT_DRAW_DISABLE && ! pVector ) + { + Color aOldTextColor; + Color aOldTextFillColor; + BOOL bRestoreFillColor; + BOOL bHighContrastBlack = FALSE; + BOOL bHighContrastWhite = FALSE; + const StyleSettings& rStyleSettings( GetSettings().GetStyleSettings() ); + if( rStyleSettings.GetHighContrastMode() ) + { + if( IsBackground() ) + { + Wallpaper aWall = GetBackground(); + Color aCol = aWall.GetColor(); + bHighContrastBlack = aCol.IsDark(); + bHighContrastWhite = aCol.IsBright(); + } + } + + aOldTextColor = GetTextColor(); + if ( IsTextFillColor() ) + { + bRestoreFillColor = TRUE; + aOldTextFillColor = GetTextFillColor(); + } + else + bRestoreFillColor = FALSE; + + if( bHighContrastBlack ) + SetTextColor( COL_GREEN ); + else if( bHighContrastWhite ) + SetTextColor( COL_LIGHTGREEN ); + else + SetTextColor( GetSettings().GetStyleSettings().GetDisableColor() ); + + DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText ); + if ( !(GetSettings().GetStyleSettings().GetOptions() & STYLE_OPTION_NOMNEMONICS) && !pVector ) + { + if ( nMnemonicPos != STRING_NOTFOUND ) + ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); + } + SetTextColor( aOldTextColor ); + if ( bRestoreFillColor ) + SetTextFillColor( aOldTextFillColor ); + } + else + { + DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText ); + if ( !(GetSettings().GetStyleSettings().GetOptions() & STYLE_OPTION_NOMNEMONICS) && !pVector ) + { + if ( nMnemonicPos != STRING_NOTFOUND ) + ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawCtrlText( rPos, rStr, nIndex, nLen, nStyle, pVector, pDisplayText ); +} + +// ----------------------------------------------------------------------- + +long OutputDevice::GetCtrlTextWidth( const String& rStr, + xub_StrLen nIndex, xub_StrLen nLen, + USHORT nStyle ) const +{ + DBG_TRACE( "OutputDevice::GetCtrlTextSize()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if ( nStyle & TEXT_DRAW_MNEMONIC ) + { + xub_StrLen nMnemonicPos; + XubString aStr = GetNonMnemonicString( rStr, nMnemonicPos ); + if ( nMnemonicPos != STRING_NOTFOUND ) + { + if ( nMnemonicPos < nIndex ) + nIndex--; + else if ( (nLen < STRING_LEN) && + (nMnemonicPos >= nIndex) && (nMnemonicPos < (ULONG)(nIndex+nLen)) ) + nLen--; + } + return GetTextWidth( aStr, nIndex, nLen ); + } + else + return GetTextWidth( rStr, nIndex, nLen ); +} + +// ----------------------------------------------------------------------- + +String OutputDevice::GetNonMnemonicString( const String& rStr, xub_StrLen& rMnemonicPos ) +{ + String aStr = rStr; + xub_StrLen nLen = aStr.Len(); + xub_StrLen i = 0; + + rMnemonicPos = STRING_NOTFOUND; + while ( i < nLen ) + { + if ( aStr.GetChar( i ) == '~' ) + { + if ( aStr.GetChar( i+1 ) != '~' ) + { + if ( rMnemonicPos == STRING_NOTFOUND ) + rMnemonicPos = i; + aStr.Erase( i, 1 ); + nLen--; + } + else + { + aStr.Erase( i, 1 ); + nLen--; + i++; + } + } + else + i++; + } + + return aStr; +} + +// ----------------------------------------------------------------------- + +int OutputDevice::GetDevFontCount() const +{ + DBG_TRACE( "OutputDevice::GetDevFontCount()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if( !mpGetDevFontList ) + mpGetDevFontList = mpFontList->GetDevFontList(); + return mpGetDevFontList->Count(); +} + +// ----------------------------------------------------------------------- + +FontInfo OutputDevice::GetDevFont( int nDevFontIndex ) const +{ + DBG_TRACE( "OutputDevice::GetDevFont()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + FontInfo aFontInfo; + + ImplInitFontList(); + + int nCount = GetDevFontCount(); + if( nDevFontIndex < nCount ) + { + const ImplFontData& rData = *mpGetDevFontList->Get( nDevFontIndex ); + aFontInfo.SetName( rData.maName ); + aFontInfo.SetStyleName( rData.maStyleName ); + aFontInfo.SetCharSet( rData.mbSymbolFlag ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); + aFontInfo.SetFamily( rData.meFamily ); + aFontInfo.SetPitch( rData.mePitch ); + aFontInfo.SetWeight( rData.meWeight ); + aFontInfo.SetItalic( rData.meItalic ); + aFontInfo.SetWidthType( rData.meWidthType ); + if( rData.IsScalable() ) + aFontInfo.mpImplMetric->mnMiscFlags |= ImplFontMetric::SCALABLE_FLAG; + if( rData.mbDevice ) + aFontInfo.mpImplMetric->mnMiscFlags |= ImplFontMetric::DEVICE_FLAG; + } + + return aFontInfo; +} + +// ----------------------------------------------------------------------- + +BOOL OutputDevice::AddTempDevFont( const String& rFileURL, const String& rFontName ) +{ + DBG_TRACE( "OutputDevice::AddTempDevFont()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + ImplInitFontList(); + + if( !mpGraphics && !ImplGetGraphics() ) + return FALSE; + + bool bRC = mpGraphics->AddTempDevFont( mpFontList, rFileURL, rFontName ); + if( !bRC ) + return FALSE; + + if( mpAlphaVDev ) + mpAlphaVDev->AddTempDevFont( rFileURL, rFontName ); + + mpFontCache->Invalidate(); + return TRUE; +} + +// ----------------------------------------------------------------------- + +int OutputDevice::GetDevFontSizeCount( const Font& rFont ) const +{ + DBG_TRACE( "OutputDevice::GetDevFontSizeCount()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + delete mpGetDevSizeList; + + ImplInitFontList(); + mpGetDevSizeList = mpFontList->GetDevSizeList( rFont.GetName() ); + return mpGetDevSizeList->Count(); +} + +// ----------------------------------------------------------------------- + +Size OutputDevice::GetDevFontSize( const Font& rFont, int nSizeIndex ) const +{ + DBG_TRACE( "OutputDevice::GetDevFontSize()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + // check range + int nCount = GetDevFontSizeCount( rFont ); + if ( nSizeIndex >= nCount ) + return Size(); + + // when mapping is enabled round to .5 points + Size aSize( 0, mpGetDevSizeList->Get( nSizeIndex ) ); + if ( mbMap ) + { + aSize.Height() *= 10; + MapMode aMap( MAP_10TH_INCH, Point(), Fraction( 1, 72 ), Fraction( 1, 72 ) ); + aSize = PixelToLogic( aSize, aMap ); + aSize.Height() += 5; + aSize.Height() /= 10; + long nRound = aSize.Height() % 5; + if ( nRound >= 3 ) + aSize.Height() += (5-nRound); + else + aSize.Height() -= nRound; + aSize.Height() *= 10; + aSize = LogicToPixel( aSize, aMap ); + aSize = PixelToLogic( aSize ); + aSize.Height() += 5; + aSize.Height() /= 10; + } + return aSize; +} + +// ----------------------------------------------------------------------- + +BOOL OutputDevice::IsFontAvailable( const String& rFontName ) const +{ + DBG_TRACE( "OutputDevice::IsFontAvailable()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + ImplDevFontListData* pFound = mpFontList->FindFontFamily( rFontName ); + return (pFound != NULL); +} + +// ----------------------------------------------------------------------- + +FontMetric OutputDevice::GetFontMetric() const +{ + DBG_TRACE( "OutputDevice::GetFontMetric()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + FontMetric aMetric; + if( mbNewFont && !ImplNewFont() ) + return aMetric; + + ImplFontEntry* pEntry = mpFontEntry; + ImplFontMetricData* pMetric = &(pEntry->maMetric); + + // prepare metric + aMetric.Font::operator=( maFont ); + + // set aMetric with info from font + aMetric.SetName( maFont.GetName() ); + aMetric.SetStyleName( pMetric->maStyleName ); + aMetric.SetSize( PixelToLogic( Size( pMetric->mnWidth, pMetric->mnAscent+pMetric->mnDescent-pMetric->mnIntLeading ) ) ); + aMetric.SetCharSet( pMetric->mbSymbolFlag ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); + aMetric.SetFamily( pMetric->meFamily ); + aMetric.SetPitch( pMetric->mePitch ); + aMetric.SetWeight( pMetric->meWeight ); + aMetric.SetItalic( pMetric->meItalic ); + aMetric.SetWidthType( pMetric->meWidthType ); + if ( pEntry->mnOwnOrientation ) + aMetric.SetOrientation( pEntry->mnOwnOrientation ); + else + aMetric.SetOrientation( pMetric->mnOrientation ); + if( !pEntry->maMetric.mbKernableFont ) + aMetric.SetKerning( maFont.GetKerning() & ~KERNING_FONTSPECIFIC ); + + // set remaining metric fields + aMetric.mpImplMetric->mnMiscFlags = 0; + if( pMetric->mbDevice ) + aMetric.mpImplMetric->mnMiscFlags |= ImplFontMetric::DEVICE_FLAG; + if( pMetric->mbScalableFont ) + aMetric.mpImplMetric->mnMiscFlags |= ImplFontMetric::SCALABLE_FLAG; + aMetric.mpImplMetric->mnAscent = ImplDevicePixelToLogicHeight( pMetric->mnAscent+mnEmphasisAscent ); + aMetric.mpImplMetric->mnDescent = ImplDevicePixelToLogicHeight( pMetric->mnDescent+mnEmphasisDescent ); + aMetric.mpImplMetric->mnIntLeading = ImplDevicePixelToLogicHeight( pMetric->mnIntLeading+mnEmphasisAscent ); + aMetric.mpImplMetric->mnExtLeading = ImplDevicePixelToLogicHeight( pMetric->mnExtLeading ); + aMetric.mpImplMetric->mnLineHeight = ImplDevicePixelToLogicHeight( pMetric->mnAscent+pMetric->mnDescent+mnEmphasisAscent+mnEmphasisDescent ); + aMetric.mpImplMetric->mnSlant = ImplDevicePixelToLogicHeight( pMetric->mnSlant ); + +#ifdef UNX + // backwards compatible line metrics after fixing #i60945# + if( (meOutDevType == OUTDEV_VIRDEV) + && static_cast<const VirtualDevice*>(this)->ForceZeroExtleadBug() ) + aMetric.mpImplMetric->mnExtLeading = 0; +#endif + + return aMetric; +} + +// ----------------------------------------------------------------------- + +FontMetric OutputDevice::GetFontMetric( const Font& rFont ) const +{ + // select font, query metrics, select original font again + Font aOldFont = GetFont(); + const_cast<OutputDevice*>(this)->SetFont( rFont ); + FontMetric aMetric( GetFontMetric() ); + const_cast<OutputDevice*>(this)->SetFont( aOldFont ); + return aMetric; +} + +// ----------------------------------------------------------------------- + +/** OutputDevice::GetSysFontData + * + * @param nFallbacklevel Fallback font level (0 = best matching font) + * + * Retrieve detailed font information in platform independent structure + * + * @return SystemFontData + **/ +SystemFontData OutputDevice::GetSysFontData(int nFallbacklevel) const +{ + SystemFontData aSysFontData; + aSysFontData.nSize = sizeof(aSysFontData); + + if (!mpGraphics) ImplGetGraphics(); + if (mpGraphics) aSysFontData = mpGraphics->GetSysFontData(nFallbacklevel); + + return aSysFontData; +} + + +// ----------------------------------------------------------------------- + +/** OutputDevice::GetSysTextLayoutData + * + * @param rStartPt Start point of the text + * @param rStr Text string that will be transformed into layout of glyphs + * @param nIndex Position in the string from where layout will be done + * @param nLen Length of the string + * @param pDXAry Custom layout adjustment data + * + * Export finalized glyph layout data as platform independent SystemTextLayoutData + * (see vcl/inc/vcl/sysdata.hxx) + * + * Only parameters rStartPt and rStr are mandatory, the rest is optional + * (default values will be used) + * + * @return SystemTextLayoutData + **/ +SystemTextLayoutData OutputDevice::GetSysTextLayoutData(const Point& rStartPt, const XubString& rStr, xub_StrLen nIndex, xub_StrLen nLen, + const sal_Int32* pDXAry) const +{ + DBG_TRACE( "OutputDevice::GetSysTextLayoutData()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + SystemTextLayoutData aSysLayoutData; + aSysLayoutData.nSize = sizeof(aSysLayoutData); + aSysLayoutData.rGlyphData.reserve( 256 ); + + if ( mpMetaFile ) { + if (pDXAry) + mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, nIndex, nLen ) ); + else + mpMetaFile->AddAction( new MetaTextAction( rStartPt, rStr, nIndex, nLen ) ); + } + + if ( !IsDeviceOutputNecessary() ) return aSysLayoutData; + + SalLayout* rLayout = ImplLayout( rStr, nIndex, nLen, rStartPt, 0, pDXAry, true ); + + // setup glyphs + Point aPos; + sal_GlyphId aGlyphId; + int nFallbacklevel = 0; + for( int nStart = 0; rLayout->GetNextGlyphs( 1, &aGlyphId, aPos, nStart ); ) + { + // NOTE: Windows backend is producing unicode chars (ucs4), so on windows, + // ETO_GLYPH_INDEX is unusable, unless extra glyph conversion is made. + + SystemGlyphData aGlyph; + aGlyph.index = static_cast<unsigned long> (aGlyphId & GF_IDXMASK); + aGlyph.x = aPos.X(); + aGlyph.y = aPos.Y(); + aSysLayoutData.rGlyphData.push_back(aGlyph); + + int nLevel = (aGlyphId & GF_FONTMASK) >> GF_FONTSHIFT; + if (nLevel > nFallbacklevel && nLevel < MAX_FALLBACK) + nFallbacklevel = nLevel; + } + + // Get font data + aSysLayoutData.aSysFontData = GetSysFontData(nFallbacklevel); + aSysLayoutData.orientation = rLayout->GetOrientation(); + + rLayout->Release(); + + return aSysLayoutData; +} + +// ----------------------------------------------------------------------- + + +long OutputDevice::GetMinKashida() const +{ + DBG_TRACE( "OutputDevice::GetMinKashida()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + if( mbNewFont && !ImplNewFont() ) + return 0; + + ImplFontEntry* pEntry = mpFontEntry; + ImplFontMetricData* pMetric = &(pEntry->maMetric); + return ImplDevicePixelToLogicWidth( pMetric->mnMinKashida ); +} +// ----------------------------------------------------------------------- + +long OutputDevice::GetMinKashida( const Font& rFont ) const +{ + // select font, query Kashida, select original font again + Font aOldFont = GetFont(); + const_cast<OutputDevice*>(this)->SetFont( rFont ); + long aKashida = GetMinKashida(); + const_cast<OutputDevice*>(this)->SetFont( aOldFont ); + return aKashida; +} + +// ----------------------------------------------------------------------- +xub_StrLen OutputDevice::ValidateKashidas ( const String& rTxt, + xub_StrLen nIdx, xub_StrLen nLen, + xub_StrLen nKashCount, + const xub_StrLen* pKashidaPos, + xub_StrLen* pKashidaPosDropped ) const +{ + // do layout + SalLayout* pSalLayout = ImplLayout( rTxt, nIdx, nLen ); + if( !pSalLayout ) + return 0; + xub_StrLen nDropped = 0; + for( int i = 0; i < nKashCount; ++i ) + { + if( !pSalLayout->IsKashidaPosValid( pKashidaPos[ i ] )) + { + pKashidaPosDropped[ nDropped ] = pKashidaPos [ i ]; + ++nDropped; + } + } + pSalLayout->Release(); + return nDropped; +} + + + +// ----------------------------------------------------------------------- + + +// TODO: best is to get rid of this method completely +ULONG OutputDevice::GetKerningPairCount() const +{ + DBG_TRACE( "OutputDevice::GetKerningPairCount()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if( mbNewFont && !ImplNewFont() ) + return 0; + if( mbInitFont ) + ImplInitFont(); + + if( mpPDFWriter && mpPDFWriter->isBuiltinFont( mpFontEntry->maFontSelData.mpFontData ) ) + return 0; + + // get the kerning pair count from the device layer + int nKernPairs = mpGraphics->GetKernPairs( 0, NULL ); + return nKernPairs; +} + +// ----------------------------------------------------------------------- + +inline bool CmpKernData( const KerningPair& a, const KerningPair& b ) +{ + return (a.nChar1 < b.nChar1) || ((a.nChar1 == a.nChar2) && (a.nChar2 < a.nChar2)); +} + +// TODO: best is to get rid of this method completely +void OutputDevice::GetKerningPairs( ULONG nRequestedPairs, KerningPair* pKernPairs ) const +{ + DBG_TRACE( "OutputDevice::GetKerningPairs()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + if( mbNewFont && !ImplNewFont() ) + return; + if( mbInitFont ) + ImplInitFont(); + + if( mpPDFWriter && mpPDFWriter->isBuiltinFont( mpFontEntry->maFontSelData.mpFontData ) ) + return; + + // get the kerning pairs directly from the device layer + int nKernPairs = mpGraphics->GetKernPairs( nRequestedPairs, (ImplKernPairData*)pKernPairs ); + + // sort kerning pairs + std::sort( pKernPairs, pKernPairs+nKernPairs, CmpKernData ); +} + +// ----------------------------------------------------------------------- + +BOOL OutputDevice::GetGlyphBoundRects( const Point& rOrigin, const String& rStr, + int nIndex, int nLen, int nBase, MetricVector& rVector ) +{ + DBG_TRACE( "OutputDevice::GetGlyphBoundRect_CTL()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + rVector.clear(); + + if( nLen == STRING_LEN ) + nLen = rStr.Len() - nIndex; + + Rectangle aRect; + for( int i = 0; i < nLen; i++ ) + { + if( !GetTextBoundRect( aRect, rStr, sal::static_int_cast<xub_StrLen>(nBase), sal::static_int_cast<xub_StrLen>(nIndex+i), 1 ) ) + break; + aRect.Move( rOrigin.X(), rOrigin.Y() ); + rVector.push_back( aRect ); + } + + return (nLen == (int)rVector.size()); +} + +// ----------------------------------------------------------------------- + +BOOL OutputDevice::GetTextBoundRect( Rectangle& rRect, + const String& rStr, xub_StrLen nBase, xub_StrLen nIndex, xub_StrLen nLen, + ULONG nLayoutWidth, const sal_Int32* pDXAry ) const +{ + DBG_TRACE( "OutputDevice::GetTextBoundRect()" ); + DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); + + BOOL bRet = FALSE; + rRect.SetEmpty(); + + SalLayout* pSalLayout = NULL; + const Point aPoint; + // calculate offset when nBase!=nIndex + long nXOffset = 0; + if( nBase != nIndex ) + { + xub_StrLen nStart = Min( nBase, nIndex ); + xub_StrLen nOfsLen = Max( nBase, nIndex ) - nStart; + pSalLayout = ImplLayout( rStr, nStart, nOfsLen, aPoint, nLayoutWidth, pDXAry ); + if( pSalLayout ) + { + nXOffset = pSalLayout->GetTextWidth(); + nXOffset /= pSalLayout->GetUnitsPerPixel(); + pSalLayout->Release(); + // TODO: fix offset calculation for Bidi case + if( nBase < nIndex) + nXOffset = -nXOffset; + } + } + + pSalLayout = ImplLayout( rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry ); + Rectangle aPixelRect; + if( pSalLayout ) + { + bRet = pSalLayout->GetBoundRect( *mpGraphics, aPixelRect ); + + if( bRet ) + { + int nWidthFactor = pSalLayout->GetUnitsPerPixel(); + + if( nWidthFactor > 1 ) + { + double fFactor = 1.0 / nWidthFactor; + aPixelRect.Left() + = static_cast< long >(aPixelRect.Left() * fFactor); + aPixelRect.Right() + = static_cast< long >(aPixelRect.Right() * fFactor); + aPixelRect.Top() + = static_cast< long >(aPixelRect.Top() * fFactor); + aPixelRect.Bottom() + = static_cast< long >(aPixelRect.Bottom() * fFactor); + } + + Point aRotatedOfs( mnTextOffX, mnTextOffY ); + aRotatedOfs -= pSalLayout->GetDrawPosition( Point( nXOffset, 0 ) ); + aPixelRect += aRotatedOfs; + rRect = PixelToLogic( aPixelRect ); + if( mbMap ) + rRect += Point( maMapRes.mnMapOfsX, maMapRes.mnMapOfsY ); + } + + pSalLayout->Release(); + } + + if( bRet || (OUTDEV_PRINTER == meOutDevType) || !mpFontEntry ) + return bRet; + + // fall back to bitmap method to get the bounding rectangle, + // so we need a monochrome virtual device with matching font + VirtualDevice aVDev( 1 ); + Font aFont( GetFont() ); + aFont.SetShadow( FALSE ); + aFont.SetOutline( FALSE ); + aFont.SetRelief( RELIEF_NONE ); + aFont.SetOrientation( 0 ); + aFont.SetSize( Size( mpFontEntry->maFontSelData.mnWidth, mpFontEntry->maFontSelData.mnHeight ) ); + aVDev.SetFont( aFont ); + aVDev.SetTextAlign( ALIGN_TOP ); + + // layout the text on the virtual device + pSalLayout = aVDev.ImplLayout( rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry ); + if( !pSalLayout ) + return false; + + // make the bitmap big enough + // TODO: use factors when it would get too big + long nWidth = pSalLayout->GetTextWidth(); + long nHeight = mpFontEntry->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent; + Point aOffset( nWidth/2, 8 ); + Size aOutSize( nWidth + 2*aOffset.X(), nHeight + 2*aOffset.Y() ); + if( !nWidth || !aVDev.SetOutputSizePixel( aOutSize ) ) + return false; + + // draw text in black + pSalLayout->DrawBase() = aOffset; + aVDev.SetTextColor( Color( COL_BLACK ) ); + aVDev.SetTextFillColor(); + aVDev.ImplInitTextColor(); + aVDev.ImplDrawText( *pSalLayout ); + pSalLayout->Release(); + + // find extents using the bitmap + Bitmap aBmp = aVDev.GetBitmap( Point(), aOutSize ); + BitmapReadAccess* pAcc = aBmp.AcquireReadAccess(); + if( !pAcc ) + return FALSE; + const BitmapColor aBlack( pAcc->GetBestMatchingColor( Color( COL_BLACK ) ) ); + const long nW = pAcc->Width(); + const long nH = pAcc->Height(); + long nLeft = 0; + long nRight = 0; + + // find top left point + long nTop = 0; + for(; nTop < nH; ++nTop ) + { + for( nLeft = 0; nLeft < nW; ++nLeft ) + if( pAcc->GetPixel( nTop, nLeft ) == aBlack ) + break; + if( nLeft < nW ) + break; + } + + // find bottom right point + long nBottom = nH; + while( --nBottom >= nTop ) + { + for( nRight = nW; --nRight >= 0; ) + if( pAcc->GetPixel( nBottom, nRight ) == aBlack ) + break; + if( nRight >= 0 ) + break; + } + if( nRight < nLeft ) + { + long nX = nRight; + nRight = nLeft; + nLeft = nX; + } + + for( long nY = nTop; nY <= nBottom; ++nY ) + { + // find leftmost point + long nX; + for( nX = 0; nX < nLeft; ++nX ) + if( pAcc->GetPixel( nY, nX ) == aBlack ) + break; + nLeft = nX; + + // find rightmost point + for( nX = nW; --nX > nRight; ) + if( pAcc->GetPixel( nY, nX ) == aBlack ) + break; + nRight = nX; + } + + aBmp.ReleaseAccess( pAcc ); + + if( nTop <= nBottom ) + { + Size aSize( nRight - nLeft + 1, nBottom - nTop + 1 ); + Point aTopLeft( nLeft, nTop ); + aTopLeft -= aOffset; + // adjust to text alignment + aTopLeft.Y()+= mnTextOffY - (mpFontEntry->maMetric.mnAscent + mnEmphasisAscent); + // convert to logical coordinates + aSize = PixelToLogic( aSize ); + aTopLeft.X() = ImplDevicePixelToLogicWidth( aTopLeft.X() ); + aTopLeft.Y() = ImplDevicePixelToLogicHeight( aTopLeft.Y() ); + rRect = Rectangle( aTopLeft, aSize ); + return TRUE; + } + + return FALSE; +} + +// ----------------------------------------------------------------------- + +BOOL OutputDevice::GetTextOutlines( ::basegfx::B2DPolyPolygonVector& rVector, + const String& rStr, xub_StrLen nBase, xub_StrLen nIndex, xub_StrLen nLen, + BOOL bOptimize, ULONG nTWidth, const sal_Int32* pDXArray ) const +{ + // the fonts need to be initialized + if( mbNewFont ) + ImplNewFont(); + if( mbInitFont ) + ImplInitFont(); + if( !mpFontEntry ) + return FALSE; + + BOOL bRet = FALSE; + rVector.clear(); + if( nLen == STRING_LEN ) + nLen = rStr.Len() - nIndex; + rVector.reserve( nLen ); + + // we want to get the Rectangle in logical units, so to + // avoid rounding errors we just size the font in logical units + BOOL bOldMap = mbMap; + if( bOldMap ) + { + const_cast<OutputDevice&>(*this).mbMap = FALSE; + const_cast<OutputDevice&>(*this).mbNewFont = TRUE; + } + + SalLayout* pSalLayout = NULL; + + // calculate offset when nBase!=nIndex + long nXOffset = 0; + if( nBase != nIndex ) + { + xub_StrLen nStart = Min( nBase, nIndex ); + xub_StrLen nOfsLen = Max( nBase, nIndex ) - nStart; + pSalLayout = ImplLayout( rStr, nStart, nOfsLen, Point(0,0), nTWidth, pDXArray ); + if( pSalLayout ) + { + nXOffset = pSalLayout->GetTextWidth(); + pSalLayout->Release(); + // TODO: fix offset calculation for Bidi case + if( nBase > nIndex) + nXOffset = -nXOffset; + } + } + + pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), nTWidth, pDXArray ); + if( pSalLayout ) + { + bRet = pSalLayout->GetOutline( *mpGraphics, rVector ); + if( bRet ) + { + // transform polygon to pixel units + ::basegfx::B2DHomMatrix aMatrix; + + int nWidthFactor = pSalLayout->GetUnitsPerPixel(); + if( nXOffset | mnTextOffX | mnTextOffY ) + { + Point aRotatedOfs( mnTextOffX*nWidthFactor, mnTextOffY*nWidthFactor ); + aRotatedOfs -= pSalLayout->GetDrawPosition( Point( nXOffset, 0 ) ); + aMatrix.translate( aRotatedOfs.X(), aRotatedOfs.Y() ); + } + + if( nWidthFactor > 1 ) + { + double fFactor = 1.0 / nWidthFactor; + aMatrix.scale( fFactor, fFactor ); + } + + if( !aMatrix.isIdentity() ) + { + ::basegfx::B2DPolyPolygonVector::iterator aIt = rVector.begin(); + for(; aIt != rVector.end(); ++aIt ) + (*aIt).transform( aMatrix ); + } + } + + pSalLayout->Release(); + } + + if( bOldMap ) + { + // restore original font size and map mode + const_cast<OutputDevice&>(*this).mbMap = bOldMap; + const_cast<OutputDevice&>(*this).mbNewFont = TRUE; + } + + if( bRet || (OUTDEV_PRINTER == meOutDevType) || !mpFontEntry ) + return bRet; + + // fall back to bitmap conversion ------------------------------------------ + + // Here, we can savely assume that the mapping between characters and glyphs + // is one-to-one. This is most probably valid for the old bitmap fonts. + + // fall back to bitmap method to get the bounding rectangle, + // so we need a monochrome virtual device with matching font + pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), nTWidth, pDXArray ); + if (pSalLayout == 0) + return false; + long nOrgWidth = pSalLayout->GetTextWidth(); + long nOrgHeight = mpFontEntry->mnLineHeight + mnEmphasisAscent + + mnEmphasisDescent; + pSalLayout->Release(); + + VirtualDevice aVDev(1); + + Font aFont(GetFont()); + aFont.SetShadow(false); + aFont.SetOutline(false); + aFont.SetRelief(RELIEF_NONE); + aFont.SetOrientation(0); + if( bOptimize ) + { + aFont.SetSize( Size( 0, GLYPH_FONT_HEIGHT ) ); + aVDev.SetMapMode( MAP_PIXEL ); + } + aVDev.SetFont( aFont ); + aVDev.SetTextAlign( ALIGN_TOP ); + aVDev.SetTextColor( Color(COL_BLACK) ); + aVDev.SetTextFillColor(); + + pSalLayout = aVDev.ImplLayout( rStr, nIndex, nLen, Point(0,0), nTWidth, pDXArray ); + if (pSalLayout == 0) + return false; + long nWidth = pSalLayout->GetTextWidth(); + long nHeight = ((OutputDevice*)&aVDev)->mpFontEntry->mnLineHeight + ((OutputDevice*)&aVDev)->mnEmphasisAscent + + ((OutputDevice*)&aVDev)->mnEmphasisDescent; + pSalLayout->Release(); + + if( !nWidth || !nHeight ) + return TRUE; + double fScaleX = static_cast< double >(nOrgWidth) / nWidth; + double fScaleY = static_cast< double >(nOrgHeight) / nHeight; + + // calculate offset when nBase!=nIndex + // TODO: fix offset calculation for Bidi case + nXOffset = 0; + if( nBase != nIndex ) + { + xub_StrLen nStart = ((nBase < nIndex) ? nBase : nIndex); + xub_StrLen nLength = ((nBase > nIndex) ? nBase : nIndex) - nStart; + pSalLayout = aVDev.ImplLayout( rStr, nStart, nLength, Point(0,0), nTWidth, pDXArray ); + if( pSalLayout ) + { + nXOffset = pSalLayout->GetTextWidth(); + pSalLayout->Release(); + if( nBase > nIndex) + nXOffset = -nXOffset; + } + } + + bRet = true; + bool bRTL = false; + String aStr( rStr ); // prepare for e.g. localized digits + ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nIndex, nLen, 0, NULL ); + for( int nCharPos = -1; aLayoutArgs.GetNextPos( &nCharPos, &bRTL);) + { + bool bSuccess = false; + + // draw character into virtual device + pSalLayout = aVDev.ImplLayout( rStr, static_cast< xub_StrLen >(nCharPos), 1, Point(0,0), nTWidth, pDXArray ); + if (pSalLayout == 0) + return false; + long nCharWidth = pSalLayout->GetTextWidth(); + + Point aOffset(nCharWidth / 2, 8); + Size aSize(nCharWidth + 2 * aOffset.X(), nHeight + 2 * aOffset.Y()); + bSuccess = (bool)aVDev.SetOutputSizePixel(aSize); + if( bSuccess ) + { + // draw glyph into virtual device + aVDev.Erase(); + pSalLayout->DrawBase() += aOffset; + pSalLayout->DrawBase() += Point( ((OutputDevice*)&aVDev)->mnTextOffX, ((OutputDevice*)&aVDev)->mnTextOffY ); + pSalLayout->DrawText( *((OutputDevice*)&aVDev)->mpGraphics ); + pSalLayout->Release(); + + // convert character image into outline + Bitmap aBmp( aVDev.GetBitmap(Point(0, 0), aSize)); + + PolyPolygon aPolyPoly; + bool bVectorized = aBmp.Vectorize(aPolyPoly, BMP_VECTORIZE_OUTER | BMP_VECTORIZE_REDUCE_EDGES); + if( !bVectorized ) + bSuccess = false; + else + { + // convert units to logical width + for (USHORT j = 0; j < aPolyPoly.Count(); ++j) + { + Polygon& rPoly = aPolyPoly[j]; + for (USHORT k = 0; k < rPoly.GetSize(); ++k) + { + Point& rPt = rPoly[k]; + rPt -= aOffset; + int nPixelX = rPt.X() - ((OutputDevice&)aVDev).mnTextOffX + nXOffset; + int nPixelY = rPt.Y() - ((OutputDevice&)aVDev).mnTextOffY; + rPt.X() = ImplDevicePixelToLogicWidth( nPixelX ); + rPt.Y() = ImplDevicePixelToLogicHeight( nPixelY ); + } + } + + + // ignore "empty" glyphs: + if( aPolyPoly.Count() > 0 ) + { + // convert to B2DPolyPolygon + // TODO: get rid of intermediate tool's PolyPolygon + ::basegfx::B2DPolyPolygon aB2DPolyPoly = aPolyPoly.getB2DPolyPolygon(); + ::basegfx::B2DHomMatrix aMatrix; + aMatrix.scale( fScaleX, fScaleY ); + int nAngle = GetFont().GetOrientation(); + if( nAngle ) + aMatrix.rotate( nAngle * F_PI1800 ); + aB2DPolyPoly.transform( aMatrix ); + rVector.push_back( aB2DPolyPoly ); + } + } + } + + nXOffset += nCharWidth; + bRet = bRet && bSuccess; + } + + return bRet; +} + +// ----------------------------------------------------------------------- + +BOOL OutputDevice::GetTextOutlines( PolyPolyVector& rResultVector, + const String& rStr, xub_StrLen nBase, xub_StrLen nIndex, + xub_StrLen nLen, BOOL bOptimize, ULONG nTWidth, const sal_Int32* pDXArray ) const +{ + rResultVector.clear(); + + // get the basegfx polypolygon vector + ::basegfx::B2DPolyPolygonVector aB2DPolyPolyVector; + if( !GetTextOutlines( aB2DPolyPolyVector, rStr, nBase, nIndex, nLen, + bOptimize, nTWidth, pDXArray ) ) + return FALSE; + + // convert to a tool polypolygon vector + rResultVector.reserve( aB2DPolyPolyVector.size() ); + ::basegfx::B2DPolyPolygonVector::const_iterator aIt = aB2DPolyPolyVector.begin(); + for(; aIt != aB2DPolyPolyVector.end(); ++aIt ) + rResultVector.push_back(PolyPolygon(*aIt)); // #i76339# + + return TRUE; +} + +// ----------------------------------------------------------------------- + +BOOL OutputDevice::GetTextOutline( PolyPolygon& rPolyPoly, + const String& rStr, xub_StrLen nBase, xub_StrLen nIndex, xub_StrLen nLen, + BOOL bOptimize, ULONG nTWidth, const sal_Int32* pDXArray ) const +{ + rPolyPoly.Clear(); + + // get the basegfx polypolygon vector + ::basegfx::B2DPolyPolygonVector aB2DPolyPolyVector; + if( !GetTextOutlines( aB2DPolyPolyVector, rStr, nBase, nIndex, nLen, + bOptimize, nTWidth, pDXArray ) ) + return FALSE; + + // convert and merge into a tool polypolygon + ::basegfx::B2DPolyPolygonVector::const_iterator aIt = aB2DPolyPolyVector.begin(); + for(; aIt != aB2DPolyPolyVector.end(); ++aIt ) + for( unsigned int i = 0; i < aIt->count(); ++i ) + rPolyPoly.Insert(Polygon((*aIt).getB2DPolygon( i ))); // #i76339# + + return TRUE; +} + +// ----------------------------------------------------------------------- + +BOOL OutputDevice::GetFontCharMap( FontCharMap& rFontCharMap ) const +{ + rFontCharMap.Reset(); + + // we need a graphics + if( !mpGraphics && !ImplGetGraphics() ) + return FALSE; + + if( mbNewFont ) + ImplNewFont(); + if( mbInitFont ) + ImplInitFont(); + if( !mpFontEntry ) + return FALSE; + + // a little font charmap cache helps considerably + static const int NMAXITEMS = 16; + static int nUsedItems = 0, nCurItem = 0; + + struct CharMapCacheItem { const ImplFontData* mpFontData; FontCharMap maCharMap; }; + static CharMapCacheItem aCache[ NMAXITEMS ]; + + const ImplFontData* pFontData = mpFontEntry->maFontSelData.mpFontData; + + int i; + for( i = nUsedItems; --i >= 0; ) + if( pFontData == aCache[i].mpFontData ) + break; + if( i >= 0 ) // found in cache + { + rFontCharMap.Reset( aCache[i].maCharMap.mpImpl ); + } + else // need to cache + { + ImplFontCharMap* pNewMap = mpGraphics->GetImplFontCharMap(); + rFontCharMap.Reset( pNewMap ); + + // manage cache round-robin and insert data + CharMapCacheItem& rItem = aCache[ nCurItem ]; + rItem.mpFontData = pFontData; + rItem.maCharMap.Reset( pNewMap ); + + if( ++nCurItem >= NMAXITEMS ) + nCurItem = 0; + + if( ++nUsedItems >= NMAXITEMS ) + nUsedItems = NMAXITEMS; + } + + if( rFontCharMap.IsDefaultMap() ) + return FALSE; + return TRUE; +} + +// ----------------------------------------------------------------------- + +xub_StrLen OutputDevice::HasGlyphs( const Font& rTempFont, const String& rStr, + xub_StrLen nIndex, xub_StrLen nLen ) const +{ + if( nIndex >= rStr.Len() ) + return nIndex; + xub_StrLen nEnd = nIndex + nLen; + if( (ULONG)nIndex+nLen > rStr.Len() ) + nEnd = rStr.Len(); + + DBG_ASSERT( nIndex < nEnd, "StartPos >= EndPos?" ); + DBG_ASSERT( nEnd <= rStr.Len(), "String too short" ); + + // to get the map temporarily set font + const Font aOrigFont = GetFont(); + const_cast<OutputDevice&>(*this).SetFont( rTempFont ); + FontCharMap aFontCharMap; + BOOL bRet = GetFontCharMap( aFontCharMap ); + const_cast<OutputDevice&>(*this).SetFont( aOrigFont ); + + // if fontmap is unknown assume it doesn't have the glyphs + if( bRet == FALSE ) + return nIndex; + + const sal_Unicode* pStr = rStr.GetBuffer(); + for( pStr += nIndex; nIndex < nEnd; ++pStr, ++nIndex ) + if( ! aFontCharMap.HasChar( *pStr ) ) + return nIndex; + + return STRING_LEN; +} + +// ----------------------------------------------------------------------- |