/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include "i18nlangtag/mslangid.hxx" #include "i18nlangtag/languagetag.hxx" #include "rtl/tencinfo.h" #include "rtl/logfile.hxx" #include "tools/debug.hxx" #include "tools/poly.hxx" #include "basegfx/polygon/b2dpolygon.hxx" #include "basegfx/polygon/b2dpolypolygon.hxx" #include "basegfx/matrix/b2dhommatrix.hxx" #include "vcl/metric.hxx" #include "vcl/metaact.hxx" #include "vcl/gdimtf.hxx" #include "vcl/virdev.hxx" #include "vcl/print.hxx" #include "vcl/event.hxx" #include "vcl/window.hxx" #include "vcl/svapp.hxx" #include "vcl/bmpacc.hxx" #include "vcl/outdev.hxx" #include "vcl/edit.hxx" // declare system types in sysdata.hxx #include #include "vcl/sysdata.hxx" #include "vcl/unohelp.hxx" #include "vcl/controllayout.hxx" #include "salgdi.hxx" #include "sallayout.hxx" #include "svdata.hxx" #include "impfont.hxx" #include "outdata.hxx" #include "outfont.hxx" #include "outdev.h" #include "textlayout.hxx" #include "svids.hrc" #include "window.h" #include "unotools/fontcvt.hxx" #include "unotools/fontcfg.hxx" #include "osl/file.h" #include #if ENABLE_GRAPHITE #include "graphite_features.hxx" #endif #include "pdfwriter_impl.hxx" #include "com/sun/star/beans/PropertyValues.hpp" #include "com/sun/star/i18n/XBreakIterator.hpp" #include "com/sun/star/i18n/WordType.hpp" #include "com/sun/star/linguistic2/LinguServiceManager.hpp" #include #if defined UNX #define GLYPH_FONT_HEIGHT 128 #else #define GLYPH_FONT_HEIGHT 256 #endif #include "sal/alloca.h" #include #include #include #include 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::ImplClearFontData( const 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 = 0; mpFontCache = 0; } } } } // also update child windows if needed if ( GetOutDevType() == OUTDEV_WINDOW ) { Window* pChild = ((Window*)this)->mpWindowImpl->mpFirstChild; while ( pChild ) { pChild->ImplClearFontData( true ); pChild = pChild->mpWindowImpl->mpNext; } } } void OutputDevice::ImplRefreshFontData( const bool bNewFontLists ) { // if ( GetOutDevType() == OUTDEV_PRINTER || mpPDFWriter ) { ImplSVData* pSVData = ImplGetSVData(); if ( bNewFontLists ) { // we need a graphics if ( ImplGetGraphics() ) { if( mpPDFWriter ) { mpFontList = pSVData->maGDIData.mpScreenFontList->Clone( true, true ); mpFontCache = new ImplFontCache( sal_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->ImplRefreshFontData( true ); pChild = pChild->mpWindowImpl->mpNext; } } } void OutputDevice::ImplUpdateFontData( bool bNewFontLists ) { ImplClearFontData( bNewFontLists ); ImplRefreshFontData( bNewFontLists ); } void OutputDevice::ImplUpdateAllFontData( bool bNewFontLists ) { ImplSVData* pSVData = ImplGetSVData(); ImplUpdateFontDataForAllFrames( &OutputDevice::ImplClearFontData, bNewFontLists ); // clear global font lists to have them updated pSVData->maGDIData.mpScreenFontCache->Invalidate(); if ( bNewFontLists ) { pSVData->maGDIData.mpScreenFontList->Clear(); Window * pFrame = pSVData->maWinData.mpFirstFrame; if ( pFrame ) { if ( pFrame->ImplGetGraphics() ) { // Stupid typecast here and somewhere ((OutputDevice*)&aVDev)->, because bug in .NET2002 compiler OutputDevice *pDevice = (OutputDevice*)pFrame; pDevice->mpGraphics->ClearDevFontCache(); pDevice->mpGraphics->GetDevFontList(pFrame->mpWindowImpl->mpFrameData->mpFontList); } } } ImplUpdateFontDataForAllFrames( &OutputDevice::ImplRefreshFontData, bNewFontLists ); } void OutputDevice::ImplUpdateFontDataForAllFrames( const FontUpdateHandler_t pHdl, const bool bNewFontLists ) { ImplSVData* const pSVData = ImplGetSVData(); // update all windows Window* pFrame = pSVData->maWinData.mpFirstFrame; while ( pFrame ) { ( pFrame->*pHdl )( bNewFontLists ); Window* pSysWin = pFrame->mpWindowImpl->mpFrameData->mpFirstOverlap; while ( pSysWin ) { ( pSysWin->*pHdl )( bNewFontLists ); pSysWin = pSysWin->mpWindowImpl->mpNextOverlap; } pFrame = pFrame->mpWindowImpl->mpFrameData->mpNextFrame; } // update all virtual devices VirtualDevice* pVirDev = pSVData->maGDIData.mpFirstVirDev; while ( pVirDev ) { ( pVirDev->*pHdl )( bNewFontLists ); pVirDev = pVirDev->mpNext; } // update all printers Printer* pPrinter = pSVData->maGDIData.mpFirstPrinter; while ( pPrinter ) { ( pPrinter->*pHdl )( bNewFontLists ); pPrinter = pPrinter->mpNext; } } void OutputDevice::BeginFontSubstitution() { ImplSVData* pSVData = ImplGetSVData(); pSVData->maGDIData.mbFontSubChanged = sal_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 = sal_False; } } void OutputDevice::AddFontSubstitute( const OUString& rFontName, const OUString& rReplaceFontName, sal_uInt16 nFlags ) { ImplDirectFontSubstitution*& rpSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst; if( !rpSubst ) rpSubst = new ImplDirectFontSubstitution(); rpSubst->AddFontSubstitute( rFontName, rReplaceFontName, nFlags ); ImplGetSVData()->maGDIData.mbFontSubChanged = sal_True; } void ImplDirectFontSubstitution::AddFontSubstitute( const OUString& rFontName, const OUString& rSubstFontName, sal_uInt16 nFlags ) { maFontSubstList.push_back( ImplFontSubstEntry( rFontName, rSubstFontName, nFlags ) ); } ImplFontSubstEntry::ImplFontSubstEntry( const OUString& rFontName, const OUString& rSubstFontName, sal_uInt16 nSubstFlags ) : maName( rFontName ) , maReplaceName( rSubstFontName ) , mnFlags( nSubstFlags ) { maSearchName = rFontName; maSearchReplaceName = rSubstFontName; GetEnglishSearchFontName( maSearchName ); GetEnglishSearchFontName( maSearchReplaceName ); } void OutputDevice::ImplAddDevFontSubstitute( const OUString& rFontName, const OUString& rReplaceFontName, sal_uInt16 nFlags ) { ImplInitOutDevData(); mpOutDevData->maDevFontSubst.AddFontSubstitute( rFontName, rReplaceFontName, nFlags ); } void OutputDevice::RemoveFontSubstitute( sal_uInt16 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 ); } sal_uInt16 OutputDevice::GetFontSubstituteCount() { const ImplDirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst; if( !pSubst ) return 0; int nCount = pSubst->GetFontSubstituteCount(); return (sal_uInt16)nCount; } bool ImplDirectFontSubstitution::FindFontSubstitute( OUString& rSubstName, const OUString& rSearchName, sal_uInt16 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( OUString& rFontName, sal_uInt16 nFlags, ImplDirectFontSubstitution* pDevSpecific ) { #ifdef DBG_UTIL OUString aTempName = rFontName; GetEnglishSearchFontName( aTempName ); DBG_ASSERT( aTempName == rFontName, "ImplFontSubstitute() called without a searchname" ); #endif OUString 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; } } //hidpi TODO: This routine has hard-coded font-sizes that break places such as DialControl Font OutputDevice::GetDefaultFont( sal_uInt16 nType, LanguageType eLang, sal_uLong nFlags, const OutputDevice* pOutDev ) { LanguageTag aLanguageTag( ( eLang == LANGUAGE_NONE || eLang == LANGUAGE_SYSTEM || eLang == LANGUAGE_DONTKNOW ) ? Application::GetSettings().GetUILanguageTag() : LanguageTag( eLang )); utl::DefaultFontConfiguration& rDefaults = utl::DefaultFontConfiguration::get(); OUString aSearch = rDefaults.getUserInterfaceFont( aLanguageTag ); // ensure a fallback OUString aDefault = rDefaults.getDefaultFont( aLanguageTag, nType ); if( !aDefault.isEmpty() ) aSearch = aDefault; 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.isEmpty() ) { aFont.SetHeight( 12 ); // corresponds to nDefaultHeight aFont.SetWeight( WEIGHT_NORMAL ); aFont.SetLanguage( eLang ); if ( aFont.GetCharSet() == RTL_TEXTENCODING_DONTKNOW ) aFont.SetCharSet( osl_getThreadTextEncoding() ); // Should we only return available fonts on the given device if ( pOutDev ) { pOutDev->ImplInitFontList(); // Search Font in the FontList OUString aName; OUString aSearchName; sal_Int32 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 != -1 ); aFont.SetName( aName ); } // No Name, than set all names if ( aFont.GetName().isEmpty() ) { if ( nFlags & DEFAULTFONT_FLAGS_ONLYONE ) { if( !pOutDev ) pOutDev = (const OutputDevice *)ImplGetSVData()->mpDefaultWin; if( !pOutDev ) { sal_Int32 nIndex = 0; 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(aSize.Height()); ImplFontEntry* pEntry = pOutDev->mpFontCache->GetFontEntry( pOutDev->mpFontList, aFont, aSize, fExactHeight, pOutDev->mpOutDevData ? &pOutDev->mpOutDevData->maDevFontSubst : NULL ); if (pEntry) { if( pEntry->maFontSelData.mpFontData ) aFont.SetName( pEntry->maFontSelData.mpFontData->GetFamilyName() ); 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 OUString& rFontName ) { // Test, if Fontname includes CJK characters --> In this case we // mention that it is a CJK font for(int i = 0; i < rFontName.getLength(); i++) { const sal_Unicode ch = rFontName[i]; // japanese if ( ((ch >= 0x3040) && (ch <= 0x30FF)) || ((ch >= 0x3190) && (ch <= 0x319F)) ) return IMPL_FONT_ATTR_CJK|IMPL_FONT_ATTR_CJK_JP; // korean if ( ((ch >= 0xAC00) && (ch <= 0xD7AF)) || ((ch >= 0x3130) && (ch <= 0x318F)) || ((ch >= 0x1100) && (ch <= 0x11FF)) ) return IMPL_FONT_ATTR_CJK|IMPL_FONT_ATTR_CJK_KR; // chinese if ( ((ch >= 0x3400) && (ch <= 0x9FFF)) ) return IMPL_FONT_ATTR_CJK|IMPL_FONT_ATTR_CJK_TC|IMPL_FONT_ATTR_CJK_SC; // cjk if ( ((ch >= 0x3000) && (ch <= 0xD7AF)) || ((ch >= 0xFF00) && (ch <= 0xFFEE)) ) return IMPL_FONT_ATTR_CJK; } return 0; } static void ImplCalcType( sal_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; } } PhysicalFontFace::PhysicalFontFace( 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( !IsSymbolFont() ) if ( IsStarSymbol( GetFamilyName() ) ) SetSymbolFlag( true ); } sal_Int32 PhysicalFontFace::CompareIgnoreSize( const PhysicalFontFace& rOther ) const { // compare their width, weight, italic and style name if( GetWidthType() < rOther.GetWidthType() ) return -1; else if( GetWidthType() > rOther.GetWidthType() ) return 1; if( GetWeight() < rOther.GetWeight() ) return -1; else if( GetWeight() > rOther.GetWeight() ) return 1; if( GetSlant() < rOther.GetSlant() ) return -1; else if( GetSlant() > rOther.GetSlant() ) return 1; return GetFamilyName().compareTo( rOther.GetFamilyName() ); } sal_Int32 PhysicalFontFace::CompareWithSize( const PhysicalFontFace& rOther ) const { sal_Int32 nCompare = CompareIgnoreSize( rOther ); if (nCompare != 0) return nCompare; if( mnHeight < rOther.mnHeight ) return -1; else if( mnHeight > rOther.mnHeight ) return 1; if( mnWidth < rOther.mnWidth ) return -1; else if( mnWidth > rOther.mnWidth ) return 1; return 0; } struct FontMatchStatus { public: int mnFaceMatch; int mnHeightMatch; int mnWidthMatch; const OUString* mpTargetStyleName; }; bool PhysicalFontFace::IsBetterMatch( const FontSelectPattern& rFSD, FontMatchStatus& rStatus ) const { int nMatch = 0; const OUString& rFontName = rFSD.maTargetName; if( rFontName.equalsIgnoreAsciiCase( GetFamilyName() ) ) nMatch += 240000; if( rStatus.mpTargetStyleName && GetStyleName().equalsIgnoreAsciiCase( *rStatus.mpTargetStyleName ) ) nMatch += 120000; if( (rFSD.GetPitch() != PITCH_DONTKNOW) && (rFSD.GetPitch() == GetPitch()) ) nMatch += 20000; // prefer NORMAL font width // TODO: change when the upper layers can tell their width preference if( GetWidthType() == WIDTH_NORMAL ) nMatch += 400; else if( (GetWidthType() == WIDTH_SEMI_EXPANDED) || (GetWidthType() == WIDTH_SEMI_CONDENSED) ) nMatch += 300; if( rFSD.GetWeight() != WEIGHT_DONTKNOW ) { // if not bold or requiring emboldening prefer light fonts to bold fonts FontWeight ePatternWeight = rFSD.mbEmbolden ? WEIGHT_NORMAL : rFSD.GetWeight(); int nReqWeight = (int)ePatternWeight; if ( ePatternWeight > WEIGHT_MEDIUM ) nReqWeight += 100; int nGivenWeight = (int)GetWeight(); if( GetWeight() > 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( GetWeight() == WEIGHT_NORMAL ) nMatch += 450; else if( GetWeight() == WEIGHT_MEDIUM ) nMatch += 350; else if( (GetWeight() == WEIGHT_SEMILIGHT) || (GetWeight() == WEIGHT_SEMIBOLD) ) nMatch += 200; else if( GetWeight() == WEIGHT_LIGHT ) nMatch += 150; } // if requiring custom matrix to fake italic, prefer upright font FontItalic ePatternItalic = rFSD.maItalicMatrix != ItalicMatrix() ? ITALIC_NONE : rFSD.GetSlant(); if ( ePatternItalic == ITALIC_NONE ) { if( GetSlant() == ITALIC_NONE ) nMatch += 900; } else { if( ePatternItalic == GetSlant() ) nMatch += 900; else if( GetSlant() != 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 FontSelectPattern& 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 { boost::hash a; boost::hash b; return a(rData.first) ^ b(rData.second); } inline void ImplFontEntry::AddFallbackForUnicode( sal_UCS4 cChar, FontWeight eWeight, const OUString& rFontName ) { if( !mpUnicodeFallbackList ) mpUnicodeFallbackList = new UnicodeFallbackList; (*mpUnicodeFallbackList)[ GFBCacheKey(cChar,eWeight) ] = rFontName; } inline bool ImplFontEntry::GetFallbackForUnicode( sal_UCS4 cChar, FontWeight eWeight, OUString* 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 OUString& 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 OUString& 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 ) { PhysicalFontFace* pFace = mpFirst; mpFirst = pFace->GetNextFace(); delete pFace; } } bool ImplDevFontListData::AddFontFace( PhysicalFontFace* pNewData ) { pNewData->mpNext = NULL; if( !mpFirst ) { maName = pNewData->GetFamilyName(); maMapNames = pNewData->maMapNames; meFamily = pNewData->GetFamilyType(); mePitch = pNewData->GetPitch(); mnMinQuality = pNewData->mnQuality; } else { if( meFamily == FAMILY_DONTKNOW ) meFamily = pNewData->GetFamilyType(); if( mePitch == PITCH_DONTKNOW ) mePitch = pNewData->GetPitch(); 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->GetWeight() != WEIGHT_DONTKNOW ) { if( pNewData->GetWeight() >= WEIGHT_SEMIBOLD ) mnTypeFaces |= IMPL_DEVFONT_BOLD; else if( pNewData->GetWeight() <= WEIGHT_SEMILIGHT ) mnTypeFaces |= IMPL_DEVFONT_LIGHT; else mnTypeFaces |= IMPL_DEVFONT_NORMAL; } if( pNewData->GetSlant() == ITALIC_NONE ) mnTypeFaces |= IMPL_DEVFONT_NONEITALIC; else if( (pNewData->GetSlant() == ITALIC_NORMAL) || (pNewData->GetSlant() == 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 // 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->GetFamilyName() == GetFamilyName() ) pNewData->SetFamilyName( GetFamilyName() ); // insert new physical font face into linked list // TODO: get rid of linear search? PhysicalFontFace* pData; PhysicalFontFace** ppHere = &mpFirst; for(; (pData=*ppHere) != NULL; ppHere=&pData->mpNext ) { sal_Int32 eComp = pNewData->CompareWithSize( *pData ); if( eComp > 0 ) continue; if( eComp < 0 ) 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 OUString& rSearchName ) { OUString aShortName; OUString aMatchFamilyName(maMatchFamilyName); // get font attributes from the decorated font name rFontSubst.getMapName( rSearchName, aShortName, aMatchFamilyName, meMatchWeight, meMatchWidth, mnMatchType ); maMatchFamilyName = aMatchFamilyName; 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 ); } PhysicalFontFace* ImplDevFontListData::FindBestFontFace( const FontSelectPattern& rFSD ) const { if( !mpFirst ) return NULL; if( !mpFirst->GetNextFace() ) return mpFirst; // FontName+StyleName should map to FamilyName+StyleName const OUString& rSearchName = rFSD.maTargetName; OUString aTargetStyleName; const OUString* pTargetStyleName = NULL; if( (rSearchName.getLength() > maSearchName.getLength()) && rSearchName.startsWith( maSearchName ) ) { aTargetStyleName = rSearchName.copy(maSearchName.getLength() + 1); pTargetStyleName = &aTargetStyleName; } // TODO: linear search improve! PhysicalFontFace* pFontFace = mpFirst; PhysicalFontFace* 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 { PhysicalFontFace* pPrevFace = NULL; for( PhysicalFontFace* pFace = mpFirst; pFace; pFace = pFace->GetNextFace() ) { if( !pPrevFace || pFace->CompareIgnoreSize( *pPrevFace ) ) rDevFontList.Add( pFace ); pPrevFace = pFace; } } void ImplDevFontListData::GetFontHeights( std::set& rHeights ) const { // add all available font heights for( const PhysicalFontFace* pFace = mpFirst; pFace; pFace = pFace->GetNextFace() ) rHeights.insert( pFace->GetHeight() ); } void ImplDevFontListData::UpdateCloneFontList( ImplDevFontList& rDevFontList, bool bScalable, bool bEmbeddable ) const { for( PhysicalFontFace* pFace = mpFirst; pFace; pFace = pFace->GetNextFace() ) { if( bScalable && !pFace->IsScalable() ) continue; if( bEmbeddable && !pFace->IsEmbeddable() && !pFace->IsSubsettable() ) continue; PhysicalFontFace* 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", "liberationsans", "", "shree", "mangal", "", "raavi", "shruti", "tunga", "", "latha", "gautami", "kartika", "vrinda", "", "shayyalmt", "naskmt", "scheherazade", "", "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 OUString aTokenName( *ppNames, strlen(*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 ); } } #ifdef SAL_FONTENUM_STABLE_ON_PLATFORM // #i113472# // 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; } #endif mnFallbackCount = nMaxLevel; mpFallbackList = pFallbackList; } ImplDevFontListData* ImplDevFontList::GetGlyphFallbackFont( FontSelectPattern& rFontSelData, 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.isEmpty() ) 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) ); OUString 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 = OUString( pRemainingCodes, nRemainingLength ); } else { 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 = ""; // See fdo#32665 for an example. FreeSerif that has glyphs in normal // font, but not in the italic or bold version bool bSubSetOfFontRequiresPropertyFaking = rFontSelData.mbEmbolden || rFontSelData.maItalicMatrix != ItalicMatrix(); // Cache the result even if there was no match, unless its from part of a font for which the properties need // to be faked. We need to rework this cache to take into account that fontconfig can return different fonts // for different input sizes, weights, etc. Basically the cache is way to naive if (!bSubSetOfFontRequiresPropertyFaking) { 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.isEmpty() ) { // 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.isEmpty() ) 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( PhysicalFontFace* pNewData ) { OUString aSearchName = pNewData->GetFamilyName(); 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; } bool bKeepNewData = pFoundData->AddFontFace( pNewData ); if( !bKeepNewData ) delete pNewData; } // find the font from the normalized font family name ImplDevFontListData* ImplDevFontList::ImplFindBySearchName( const OUString& rSearchName ) const { #ifdef DEBUG OUString 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 OUString& rSearchName, const OUString& rShortName) const { // short circuit for impossible font name alias if (rSearchName.isEmpty()) 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.isEmpty() ) continue; // if one alias name matches we found a matching font OUString aTempName; sal_Int32 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 != -1 ); } return NULL; } ImplDevFontListData* ImplDevFontList::FindFontFamily( const OUString& rFontName ) const { // normalize the font family name and OUString aName = rFontName; GetEnglishSearchFontName( aName ); ImplDevFontListData* pFound = ImplFindBySearchName( aName ); return pFound; } ImplDevFontListData* ImplDevFontList::ImplFindByTokenNames(const OUString& rTokenStr) const { ImplDevFontListData* pFoundData = NULL; // use normalized font name tokens to find the font for( sal_Int32 nTokenPos = 0; nTokenPos != -1; ) { OUString aSearchName = GetNextFontToken( rTokenStr, nTokenPos ); if( aSearchName.isEmpty() ) 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< OUString >::const_iterator it = rFontAttr.Substitutions.begin(); for(; it != rFontAttr.Substitutions.end(); ++it ) { OUString aSearchName( *it ); GetEnglishSearchFontName( aSearchName ); pFoundData = ImplFindBySearchName( aSearchName ); if( pFoundData ) return pFoundData; } // use known attributes from the configuration to find a matching substitute const sal_uLong nSearchType = rFontAttr.Type; if( nSearchType != 0 ) { const FontWeight eSearchWeight = rFontAttr.Weight; const FontWidth eSearchWidth = rFontAttr.Width; const FontItalic eSearchSlant = ITALIC_DONTKNOW; const OUString aSearchName; pFoundData = ImplFindByAttributes( nSearchType, eSearchWeight, eSearchWidth, eSearchSlant, aSearchName ); if( pFoundData ) return pFoundData; } return NULL; } 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 OUString& rSearchName = (*it).first; ImplDevFontListData* pEntry = (*it).second; pEntry->InitMatchData( rFontSubst, rSearchName ); } } ImplDevFontListData* ImplDevFontList::ImplFindByAttributes( sal_uLong nSearchType, FontWeight eSearchWeight, FontWidth eSearchWidth, FontItalic eSearchItalic, const OUString& 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; sal_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 sal_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 OUString& 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.isEmpty() && (rSearchFamilyName.equals(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.getLength() >= 4) && (pData->maMatchFamilyName.getLength() >= 4) && ((rSearchFamilyName.indexOf( pData->maMatchFamilyName ) != -1) || (pData->maMatchFamilyName.indexOf( rSearchFamilyName ) != -1)) ) 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( ((nSearchType ^ nMatchType) & IMPL_FONT_ATTR_OTHERSTYLE) != 0 ) { 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(); LanguageTag aLanguageTag( OUString( "en")); OUString aFontname = rDefaults.getDefaultFont( aLanguageTag, DEFAULTFONT_SANS_UNICODE ); ImplDevFontListData* pFoundData = ImplFindByTokenNames( aFontname ); if( pFoundData ) return pFoundData; aFontname = rDefaults.getDefaultFont( aLanguageTag, DEFAULTFONT_SANS ); pFoundData = ImplFindByTokenNames( aFontname ); if( pFoundData ) return pFoundData; aFontname = rDefaults.getDefaultFont( aLanguageTag, DEFAULTFONT_SERIF ); pFoundData = ImplFindByTokenNames( aFontname ); if( pFoundData ) return pFoundData; aFontname = rDefaults.getDefaultFont( aLanguageTag, 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->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 OUString& rFontName ) const { ImplGetDevSizeList* pGetDevSizeList = new ImplGetDevSizeList( rFontName ); ImplDevFontListData* pFontFamily = FindFontFamily( rFontName ); if( pFontFamily != NULL ) { std::set rHeights; pFontFamily->GetFontHeights( rHeights ); std::set::const_iterator it = rHeights.begin(); for(; it != rHeights.begin(); ++it ) pGetDevSizeList->Add( *it ); } return pGetDevSizeList; } FontSelectPatternAttributes::FontSelectPatternAttributes( const Font& rFont, const OUString& 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 ) , mbEmbolden( false ) { maTargetName = GetFamilyName(); 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; } FontSelectPattern::FontSelectPattern( const Font& rFont, const OUString& rSearchName, const Size& rSize, float fExactHeight) : FontSelectPatternAttributes(rFont, rSearchName, rSize, fExactHeight) , mpFontData( NULL ) , mpFontEntry( NULL ) { } // NOTE: this ctor is still used on Windows. Do not remove. FontSelectPatternAttributes::FontSelectPatternAttributes( const PhysicalFontFace& 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 ) , mbEmbolden( false ) { maTargetName = maSearchName = GetFamilyName(); // NOTE: no normalization for width/height/orientation } #ifdef WNT FontSelectPattern::FontSelectPattern( const PhysicalFontFace& rFontData, const Size& rSize, float fExactHeight, int nOrientation, bool bVertical ) : FontSelectPatternAttributes(rFontData, rSize, fExactHeight, nOrientation, bVertical) , mpFontData( &rFontData ) , mpFontEntry( NULL ) { } #endif void FontSelectPattern::copyAttributes(const FontSelectPatternAttributes &rAttributes) { static_cast(*this) = rAttributes; } size_t ImplFontCache::IFSD_Hash::operator()( const FontSelectPattern& rFSD ) const { return rFSD.hashCode(); } size_t FontSelectPatternAttributes::hashCode() const { // TODO: does it pay off to improve this hash function? static FontNameHash aFontNameHash; size_t nHash = aFontNameHash( maSearchName ); #if ENABLE_GRAPHITE // check for features and generate a unique hash if necessary if (maTargetName.indexOf(grutils::GrFeatureParser::FEAT_PREFIX) != -1) { nHash = aFontNameHash( maTargetName ); } #endif nHash += 11 * mnHeight; nHash += 19 * GetWeight(); nHash += 29 * GetSlant(); nHash += 37 * mnOrientation; nHash += 41 * meLanguage; if( mbVertical ) nHash += 53; return nHash; } bool FontSelectPatternAttributes::operator==(const FontSelectPatternAttributes& rOther) const { if (static_cast(*this) != static_cast(rOther)) return false; if (maTargetName != rOther.maTargetName) return false; if (maSearchName != rOther.maSearchName) return false; if (mnWidth != rOther.mnWidth) return false; if (mnHeight != rOther.mnHeight) return false; if (mfExactHeight != rOther.mfExactHeight) return false; if (mnOrientation != rOther.mnOrientation) return false; if (meLanguage != rOther.meLanguage) return false; if (mbVertical != rOther.mbVertical) return false; if (mbNonAntialiased != rOther.mbNonAntialiased) return false; if (mbEmbolden != rOther.mbEmbolden) return false; if (maItalicMatrix != rOther.maItalicMatrix) return false; return true; } bool ImplFontCache::IFSD_Equal::operator()(const FontSelectPattern& rA, const FontSelectPattern& 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.GetWeight() != rB.GetWeight()) || (rA.GetSlant() != rB.GetSlant()) // || (rA.meFamily != rB.meFamily) // TODO: remove this mostly obsolete member || (rA.GetPitch() != rB.GetPitch()) ) return false; // check style name if( rA.GetStyleName() != rB.GetStyleName() ) 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; } #if ENABLE_GRAPHITE // check for features if ((rA.maTargetName.indexOf(grutils::GrFeatureParser::FEAT_PREFIX) != -1 || rB.maTargetName.indexOf(grutils::GrFeatureParser::FEAT_PREFIX) != -1) && rA.maTargetName != rB.maTargetName) return false; #endif if (rA.mbEmbolden != rB.mbEmbolden) return false; if (rA.maItalicMatrix != rB.maItalicMatrix) return false; 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 ) { OUString aSearchName = rFont.GetName(); // initialize internal font request object FontSelectPattern aFontSelData( rFont, aSearchName, rSize, fExactHeight ); return GetFontEntry( pFontList, aFontSelData, pDevSpecific ); } ImplFontEntry* ImplFontCache::GetFontEntry( ImplDevFontList* pFontList, FontSelectPattern& aFontSelData, ImplDirectFontSubstitution* pDevSpecific ) { FontSelectPattern aFontSelDataOrig(aFontSelData); // 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; } } PhysicalFontFace* pFontData = NULL; if (!pEntry && pFontFamily)// no cache hit => find the best matching physical font face { bool bOrigWasSymbol = aFontSelData.mpFontData && aFontSelData.mpFontData->IsSymbolFont(); pFontData = pFontFamily->FindBestFontFace( aFontSelData ); aFontSelData.mpFontData = pFontData; bool bNewIsSymbol = aFontSelData.mpFontData && aFontSelData.mpFontData->IsSymbolFont(); if (bNewIsSymbol != bOrigWasSymbol) { // it is possible, though generally unlikely, that at this point we // will attempt to use a symbol font as a last-ditch fallback for a // non-symbol font request or vice versa, and by changing // aFontSelData.mpFontData to/from a symbol font we may now find // something in the cache that can be reused which previously // wasn't a candidate FontInstanceList::iterator it = maFontInstanceList.find( aFontSelData ); if( it != maFontInstanceList.end() ) pEntry = (*it).second; } } if( pEntry ) // cache hit => use existing font instance { // increase the font instance's reference count if( !pEntry->mnRefCount++ ) --mnRef0Count; } if (!pEntry && pFontData)// still no cache hit => create a new font instance { // create a new logical font instance from this physical font face pEntry = pFontData->CreateFontInstance( aFontSelData ); // if we're subtituting from or to a symbol font we may need a symbol // conversion table if( pFontData->IsSymbolFont() || aFontSelData.IsSymbolFont() ) { if( aFontSelData.maTargetName != aFontSelData.maSearchName ) pEntry->mpConversion = ConvertChar::GetRecodeData( aFontSelData.maTargetName, aFontSelData.maSearchName ); } #ifdef MACOSX //It might be better to dig out the font version of the target font //to see if it's a modern re-coded apple symbol font in case that //font shows up on a different platform if (!pEntry->mpConversion && aFontSelData.maTargetName.equalsIgnoreAsciiCase("symbol") && aFontSelData.maSearchName.equalsIgnoreAsciiCase("symbol")) { pEntry->mpConversion = ConvertChar::GetRecodeData( OUString("Symbol"), OUString("AppleSymbol") ); } #endif // Add the new entry to the cache with the original FontSelectPattern, // so that we can find it next time as a direct cache hit. maFontInstanceList[ aFontSelDataOrig ] = pEntry; } mpFirstEntry = pEntry; return pEntry; } namespace { OUString stripCharSetFromName(OUString aName) { // I worry that someone will have a font which *does* have // e.g. "Greek" legitimately at the end of its name :-( const char*suffixes[] = { " baltic", " ce", " cyr", " greek", " tur", " (arabic)", " (hebrew)", " (thai)", " (vietnamese)" }; // These can be crazily piled up, e.g. Times New Roman CYR Greek bool bFinished = false; while (!bFinished) { bFinished = true; for (size_t i = 0; i < SAL_N_ELEMENTS(suffixes); ++i) { size_t nLen = strlen(suffixes[i]); if (aName.endsWithIgnoreAsciiCaseAsciiL(suffixes[i], nLen)) { bFinished = false; aName = aName.copy(0, aName.getLength() - nLen); } } } return aName; } } ImplDevFontListData* ImplDevFontList::ImplFindByFont( FontSelectPattern& 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 sal_uInt16 nSubstFlags = FONT_SUBSTITUTE_ALWAYS; if ( bPrinter ) nSubstFlags |= FONT_SUBSTITUTE_SCREENONLY; bool bMultiToken = false; sal_Int32 nTokenPos = 0; OUString& aSearchName = rFSD.maSearchName; // TODO: get rid of reference for(;;) { rFSD.maTargetName = GetNextFontToken( rFSD.GetFamilyName(), nTokenPos ); aSearchName = rFSD.maTargetName; #if 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. sal_Int32 nFeat = aSearchName.indexOf(grutils::GrFeatureParser::FEAT_PREFIX); OUString aOrigName = rFSD.maTargetName; OUString aBaseFontName = aSearchName.copy( 0, (nFeat != -1) ? nFeat : aSearchName.getLength() ); if (nFeat != -1 && -1 != aSearchName.indexOf(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.GetWeight() > WEIGHT_MEDIUM) && aSearchName.startsWithIgnoreAsciiCase( "hg" ) ) { OUString aBoldName; if( aSearchName.startsWithIgnoreAsciiCase( "hggothicb" ) ) aBoldName = "hggothice"; else if( aSearchName.startsWithIgnoreAsciiCase( "hgpgothicb" ) ) aBoldName = "hgpgothice"; else if( aSearchName.startsWithIgnoreAsciiCase( "hgminchol" ) ) aBoldName = "hgminchob"; else if( aSearchName.startsWithIgnoreAsciiCase( "hgpminchol" ) ) aBoldName = "hgpminchob"; else if( aSearchName.equalsIgnoreAsciiCase( "hgminchob" ) ) aBoldName = "hgminchoe"; else if( aSearchName.equalsIgnoreAsciiCase( "hgpminchob" ) ) aBoldName = "hgpminchoe"; if( !aBoldName.isEmpty() && ImplFindBySearchName( aBoldName ) ) { // the other font is available => use it aSearchName = aBoldName; // prevent synthetic emboldening of bold version rFSD.SetWeight(WEIGHT_DONTKNOW); } } #if 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" #if ENABLE_GRAPHITE // use the target name to search in the prematch hook rFSD.maTargetName = aBaseFontName; #endif // Related: fdo#49271 RTF files often contain weird-ass // Win 3.1/Win95 style fontnames which attempt to put the // charset encoding into the filename // http://www.webcenter.ru/~kazarn/eng/fonts_ttf.htm OUString sStrippedName = stripCharSetFromName(rFSD.maTargetName); if (!sStrippedName.equals(rFSD.maTargetName)) { rFSD.maTargetName = sStrippedName; aSearchName = rFSD.maTargetName; GetEnglishSearchFontName(aSearchName); pFoundData = ImplFindBySearchName(aSearchName); if( pFoundData ) return pFoundData; } if( mpPreMatchHook ) { if( mpPreMatchHook->FindFontSubstitute( rFSD ) ) GetEnglishSearchFontName( aSearchName ); } #if 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 == -1) 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 != -1; ) { if( bMultiToken ) { rFSD.maTargetName = GetNextFontToken( rFSD.GetFamilyName(), nTokenPos ); aSearchName = rFSD.maTargetName; GetEnglishSearchFontName( aSearchName ); } else nTokenPos = -1; 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.GetFamilyName(), nTokenPos ); aSearchName = rFSD.maTargetName; GetEnglishSearchFontName( aSearchName ); } OUString aSearchShortName; OUString aSearchFamilyName; FontWeight eSearchWeight = rFSD.GetWeight(); FontWidth eSearchWidth = rFSD.GetWidthType(); sal_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 OUString aMS_Mincho( "msmincho" ); static OUString aMS_Gothic( "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.isEmpty() ) { // 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() ) { LanguageTag aDefaultLanguageTag( OUString( "en")); aSearchName = DefaultFontConfiguration::get().getDefaultFont( aDefaultLanguageTag, DEFAULTFONT_SYMBOL ); ImplDevFontListData* pFoundData = ImplFindByTokenNames( aSearchName ); if( pFoundData ) return pFoundData; } // now try the other font name tokens while( nTokenPos != -1 ) { rFSD.maTargetName = GetNextFontToken( rFSD.GetFamilyName(), nTokenPos ); if( rFSD.maTargetName.isEmpty() ) continue; aSearchName = rFSD.maTargetName; GetEnglishSearchFontName( aSearchName ); OUString aTempShortName; OUString aTempFamilyName; sal_uLong nTempType = 0; FontWeight eTempWeight = rFSD.GetWeight(); 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 if (MsLangId::isSimplifiedChinese(rFSD.meLanguage)) nSearchType |= IMPL_FONT_ATTR_CJK | IMPL_FONT_ATTR_CJK_SC; else if (MsLangId::isTraditionalChinese(rFSD.meLanguage)) nSearchType |= IMPL_FONT_ATTR_CJK | IMPL_FONT_ATTR_CJK_TC; else if (MsLangId::isKorean(rFSD.meLanguage)) nSearchType |= IMPL_FONT_ATTR_CJK | IMPL_FONT_ATTR_CJK_KR; else if (rFSD.meLanguage == LANGUAGE_JAPANESE) nSearchType |= IMPL_FONT_ATTR_CJK | IMPL_FONT_ATTR_CJK_JP; else { nSearchType |= ImplIsCJKFont( rFSD.GetFamilyName() ); if( rFSD.IsSymbolFont() ) nSearchType |= IMPL_FONT_ATTR_SYMBOL; } ImplCalcType( nSearchType, eSearchWeight, eSearchWidth, rFSD.GetFamilyType(), pFontAttr ); ImplDevFontListData* pFoundData = ImplFindByAttributes( nSearchType, eSearchWeight, eSearchWidth, rFSD.GetSlant(), aSearchFamilyName ); if( pFoundData ) { // overwrite font selection attributes using info from the typeface flags if( (eSearchWeight >= WEIGHT_BOLD) && (eSearchWeight > rFSD.GetWeight()) && (pFoundData->mnTypeFaces & IMPL_DEVFONT_BOLD) ) rFSD.SetWeight( eSearchWeight ); else if( (eSearchWeight < WEIGHT_NORMAL) && (eSearchWeight < rFSD.GetWeight()) && (eSearchWeight != WEIGHT_DONTKNOW) && (pFoundData->mnTypeFaces & IMPL_DEVFONT_LIGHT) ) rFSD.SetWeight( eSearchWeight ); if( (nSearchType & IMPL_FONT_ATTR_ITALIC) && ((rFSD.GetSlant() == ITALIC_DONTKNOW) || (rFSD.GetSlant() == ITALIC_NONE)) && (pFoundData->mnTypeFaces & IMPL_DEVFONT_ITALIC) ) rFSD.SetItalic( ITALIC_NORMAL ); } else { // if still needed fall back to default fonts pFoundData = FindDefaultFont(); } return pFoundData; } ImplFontEntry* ImplFontCache::GetGlyphFallbackFont( ImplDevFontList* pFontList, FontSelectPattern& rFontSelData, int nFallbackLevel, 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 = NULL; //fdo#33898 If someone has EUDC installed then they really want that to //be used as the first-choice glyph fallback seeing as it's filled with //private area codes with don't make any sense in any other font so //prioritise it here if it's available. Ideally we would remove from //rMissingCodes all the glyphs which it is able to resolve as an //optimization, but that's tricky to achieve cross-platform without //sufficient heavy-weight code that's likely to undo the value of the //optimization if (nFallbackLevel == 1) pFallbackData = pFontList->FindFontFamily(OUString("EUDC")); if (!pFallbackData) pFallbackData = pFontList->GetGlyphFallbackFont(rFontSelData, rMissingCodes, nFallbackLevel-1); // escape when there are no font candidates if( !pFallbackData ) return NULL; // override the font name rFontSelData.SetFamilyName( pFallbackData->GetFamilyName() ); // clear the cached normalized name rFontSelData.maSearchName = ""; } // 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" ); } ImplMultiTextLineInfo::ImplMultiTextLineInfo() { mpLines = new PImplTextLineInfo[MULTITEXTLINEINFO_RESIZE]; mnLines = 0; mnSize = MULTITEXTLINEINFO_RESIZE; } ImplMultiTextLineInfo::~ImplMultiTextLineInfo() { for( sal_Int32 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( sal_Int32 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 (MsLangId::isSimplifiedChinese(eLang)) nEmphasisMark |= EMPHASISMARK_POS_BELOW; else { eLang = rFont.GetCJKContextLanguage(); // In Chinese Simplified the EmphasisMarks are below/left if (MsLangId::isSimplifiedChinese(eLang)) nEmphasisMark |= EMPHASISMARK_POS_BELOW; else nEmphasisMark |= EMPHASISMARK_POS_ABOVE; } } return nEmphasisMark; } sal_Bool OutputDevice::ImplIsUnderlineAbove( const Font& rFont ) { if ( !rFont.IsVertical() ) return sal_False; if( (LANGUAGE_JAPANESE == rFont.GetLanguage()) || (LANGUAGE_JAPANESE == rFont.GetCJKContextLanguage()) ) // the underline is right for Japanese only return sal_True; return sal_False; } void OutputDevice::ImplInitFontList() const { if( ! mpFontList->Count() ) { if( mpGraphics || ImplGetGraphics() ) { SAL_INFO( "vcl.gdi", "OutputDevice::ImplInitFontList()" ); mpGraphics->GetDevFontList( mpFontList ); } } if( meOutDevType == OUTDEV_WINDOW && ! mpFontList->Count() ) { OUString aError( "Application error: no fonts and no vcl resource found on your system" ); ResMgr* pMgr = ImplGetResMgr(); if( pMgr ) { OUString aResStr(ResId(SV_ACCESSERROR_NO_FONTS, *pMgr).toString()); if( !aResStr.isEmpty() ) aError = aResStr; } Application::Abort( aError ); } } void OutputDevice::ImplInitFont() const { DBG_TESTSOLARMUTEX(); if (!mpFontEntry) return; 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; } // 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 = sal_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(*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(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(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; if (!pFontEntry) return false; // mark when lower layers need to get involved mbNewFont = sal_False; if( pFontEntry != pOldEntry ) mbInitFont = sal_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(pFontEntry->maFontSelData.mnOrientation); 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(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(maFont).SetSize( Size( nNewWidth, aSize.Height() ) ); mbMap = sal_False; mbNewFont = sal_True; ImplNewFont(); // recurse once using stretched width mbMap = sal_True; const_cast(maFont).SetSize( aOrigSize ); } } return true; } void OutputDevice::ImplDrawTextRect( long nBaseX, long nBaseY, long nDistX, long nDistY, long nWidth, long nHeight ) { long nX = nDistX; long nY = nDistY; short nOrientation = mpFontEntry->mnOrientation; if ( nOrientation ) { // Rotate rect without rounding problems for 90 degree rotations if ( !(nOrientation % 900) ) { 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; } } else { nX += nBaseX; nY += nBaseY; // 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; } } nX += nBaseX; nY += nBaseY; 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 = sal_True; } mpGraphics->SetFillColor( ImplColorToSal( GetTextFillColor() ) ); mbInitFillColor = sal_True; ImplDrawTextRect( nX, nY, 0, -(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(); } bool ImplFontAttributes::operator==(const ImplFontAttributes& rOther) const { if (maName != rOther.maName) return false; if (maStyleName != rOther.maStyleName) return false; if (meWeight != rOther.meWeight) return false; if (meItalic != rOther.meItalic) return false; if (meFamily != rOther.meFamily) return false; if (mePitch != rOther.mePitch) return false; if (meWidthType != rOther.meWidthType) return false; if (mbSymbolFlag != rOther.mbSymbolFlag) return false; return true; } ImplFontMetricData::ImplFontMetricData( const FontSelectPattern& rFontSelData ) : ImplFontAttributes( rFontSelData ) { // initialize the members provided by the font request mnWidth = rFontSelData.mnWidth; mnSlant = rFontSelData.GetSlant(); mnOrientation = sal::static_int_cast(rFontSelData.mnOrientation); // intialize the used font name if( rFontSelData.mpFontData ) { SetFamilyName( rFontSelData.mpFontData->GetFamilyName() ); SetStyleName( rFontSelData.mpFontData->GetStyleName() ); mbDevice = rFontSelData.mpFontData->mbDevice; mbKernableFont = true; } else { sal_Int32 nTokenPos = 0; SetFamilyName( GetNextFontToken( rFontSelData.GetFamilyName(), nTokenPos ) ); SetStyleName( rFontSelData.GetStyleName() ); mbDevice = false; mbKernableFont = false; } // reset metrics that are usually measured for the font instance mnAscent = 0; mnDescent = 0; mnIntLeading = 0; mnExtLeading = 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 nDistX, long nDistY, long nWidth, long nHeight, long nLineWidth, short nOrientation, const Color& rColor ) { if ( !nHeight ) return; long nStartX = nBaseX + nDistX; long nStartY = nBaseY + nDistY; // If the height is 1 pixel, it's enough ouput a line if ( (nLineWidth == 1) && (nHeight == 1) ) { mpGraphics->SetLineColor( ImplColorToSal( rColor ) ); mbInitLineColor = sal_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; // On printers that ouput pixel via DrawRect() if ( (GetOutDevType() == OUTDEV_PRINTER) || (nLineWidth > 1) ) { if ( mbLineColor || mbInitLineColor ) { mpGraphics->SetLineColor(); mbInitLineColor = sal_True; } mpGraphics->SetFillColor( ImplColorToSal( rColor ) ); mbInitFillColor = sal_True; bDrawPixAsRect = true; nPixWidth = nLineWidth; nPixHeight = ((nLineWidth*mnDPIX)+(mnDPIY/2))/mnDPIY; } else { mpGraphics->SetLineColor( ImplColorToSal( rColor ) ); mbInitLineColor = sal_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 nDistX, long nDistY, long nWidth, FontUnderline eTextLine, Color aColor, sal_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 += nDistY - (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, nDistX, nLinePos, nWidth, nLineHeight, nLineWidth, mpFontEntry->mnOrientation, aColor ); nLinePos += nLineWidthHeight+nLineDY; ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight, nLineWidth, mpFontEntry->mnOrientation, aColor ); } else { nLinePos -= nLineWidthHeight/2; ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight, nLineWidth, mpFontEntry->mnOrientation, aColor ); } } void OutputDevice::ImplDrawStraightTextLine( long nBaseX, long nBaseY, long nDistX, long nDistY, long nWidth, FontUnderline eTextLine, Color aColor, sal_Bool bIsAbove ) { ImplFontEntry* pFontEntry = mpFontEntry; long nLineHeight = 0; long nLinePos = 0; long nLinePos2 = 0; const long nY = nDistY; 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 = sal_True; } mpGraphics->SetFillColor( ImplColorToSal( aColor ) ); mbInitFillColor = sal_True; long nLeft = nDistX; 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 will be increased if the line is getting too thick // in proportion to the line's length 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 will be increased if the line is getting too thick // in proportion to the line's length 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 will be increased if the line is getting too thick // in proportion to the line's length 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 nDistX, long nDistY, long nWidth, FontStrikeout eStrikeout, Color aColor ) { ImplFontEntry* pFontEntry = mpFontEntry; long nLineHeight = 0; long nLinePos = 0; long nLinePos2 = 0; long nY = nDistY; 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 = sal_True; } mpGraphics->SetFillColor( ImplColorToSal( aColor ) ); mbInitFillColor = sal_True; const long& nLeft = nDistX; 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 nDistX, long nDistY, long nWidth, FontStrikeout eStrikeout, Color aColor ) { // See qadevOOo/testdocs/StrikeThrough.odt for examples if you need // to tweak this if (!nWidth) 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; sal_Unicode aChars[nMaxStrikeStrLen+1]; // +1 for valgrind... for( int i = 0; i < nTestStrLen; ++i) aChars[i] = cStrikeoutChar; const OUString aStrikeoutTest(aChars, nTestStrLen); // calculate approximation of strikeout atom size long nStrikeoutWidth = 0; SalLayout* pLayout = ImplLayout( aStrikeoutTest, 0, nTestStrLen ); if( pLayout ) { nStrikeoutWidth = pLayout->GetTextWidth() / (nTestStrLen * pLayout->GetUnitsPerPixel()); pLayout->Release(); } if( nStrikeoutWidth <= 0 ) // sanity check return; int nStrikeStrLen = (nWidth+(nStrikeoutWidth-1)) / nStrikeoutWidth; if( nStrikeStrLen > nMaxStrikeStrLen ) nStrikeStrLen = nMaxStrikeStrLen; // build the strikeout string for( int i = nTestStrLen; i < nStrikeStrLen; ++i) aChars[i] = cStrikeoutChar; const OUString aStrikeoutText(aChars, nStrikeStrLen); if( mpFontEntry->mnOrientation ) ImplRotatePos( 0, 0, nDistX, nDistY, mpFontEntry->mnOrientation ); nBaseX += nDistX; nBaseY += nDistY; // strikeout text has to be left aligned sal_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( nBaseX+mnTextOffX, nBaseY+mnTextOffY ); Rectangle aPixelRect; aPixelRect.Left() = nBaseX+mnTextOffX; aPixelRect.Right() = aPixelRect.Left()+nWidth; aPixelRect.Bottom() = nBaseY+mpFontEntry->maMetric.mnDescent; aPixelRect.Top() = nBaseY-mpFontEntry->maMetric.mnAscent; if (mpFontEntry->mnOrientation) { Polygon aPoly( aPixelRect ); aPoly.Rotate( Point(nBaseX+mnTextOffX, nBaseY+mnTextOffY), mpFontEntry->mnOrientation); aPixelRect = aPoly.GetBoundRect(); } Push( PUSH_CLIPREGION ); IntersectClipRegion( PixelToLogic(aPixelRect) ); if( mbInitClipRegion ) ImplInitClipRegion(); pLayout->DrawText( *mpGraphics ); pLayout->Release(); Pop(); SetTextColor( aOldColor ); ImplInitTextColor(); } void OutputDevice::ImplDrawTextLine( long nX, long nY, long nDistX, long nWidth, FontStrikeout eStrikeout, FontUnderline eUnderline, FontUnderline eOverline, sal_Bool bUnderlineAbove ) { if ( !nWidth ) return; Color aStrikeoutColor = GetTextColor(); Color aUnderlineColor = GetTextLineColor(); Color aOverlineColor = GetOverlineColor(); bool bStrikeoutDone = false; bool bUnderlineDone = false; bool bOverlineDone = false; if ( IsRTLEnabled() ) { // --- RTL --- mirror at basex long nXAdd = nWidth - nDistX; if( mpFontEntry->mnOrientation ) nXAdd = FRound( nXAdd * cos( mpFontEntry->mnOrientation * F_PI1800 ) ); nX += nXAdd - 1; } if ( !IsTextLineColor() ) aUnderlineColor = GetTextColor(); if ( !IsOverlineColor() ) aOverlineColor = GetTextColor(); if ( (eUnderline == UNDERLINE_SMALLWAVE) || (eUnderline == UNDERLINE_WAVE) || (eUnderline == UNDERLINE_DOUBLEWAVE) || (eUnderline == UNDERLINE_BOLDWAVE) ) { ImplDrawWaveTextLine( nX, nY, nDistX, 0, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); bUnderlineDone = true; } if ( (eOverline == UNDERLINE_SMALLWAVE) || (eOverline == UNDERLINE_WAVE) || (eOverline == UNDERLINE_DOUBLEWAVE) || (eOverline == UNDERLINE_BOLDWAVE) ) { ImplDrawWaveTextLine( nX, nY, nDistX, 0, nWidth, eOverline, aOverlineColor, sal_True ); bOverlineDone = true; } if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) ) { ImplDrawStrikeoutChar( nX, nY, nDistX, 0, nWidth, eStrikeout, aStrikeoutColor ); bStrikeoutDone = true; } if ( !bUnderlineDone ) ImplDrawStraightTextLine( nX, nY, nDistX, 0, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); if ( !bOverlineDone ) ImplDrawStraightTextLine( nX, nY, nDistX, 0, nWidth, eOverline, aOverlineColor, sal_True ); if ( !bStrikeoutDone ) ImplDrawStrikeoutLine( nX, nY, nDistX, 0, nWidth, eStrikeout, aStrikeoutColor ); } void OutputDevice::ImplDrawTextLines( SalLayout& rSalLayout, FontStrikeout eStrikeout, FontUnderline eUnderline, FontUnderline eOverline, sal_Bool bWordLine, sal_Bool bUnderlineAbove ) { if( bWordLine ) { // draw everything relative to the layout base point const Point aStartPt = rSalLayout.DrawBase(); // calculate distance of each word from the base point Point aPos; sal_Int32 nDist = 0, nWidth = 0, nAdvance=0; for( int nStart = 0;;) { // iterate through the layouted glyphs sal_GlyphId nGlyphIndex; if( !rSalLayout.GetNextGlyphs( 1, &nGlyphIndex, aPos, nStart, &nAdvance ) ) break; // calculate the boundaries of each word if( !rSalLayout.IsSpacingGlyph( nGlyphIndex ) ) { if( !nWidth ) { // get the distance to the base point (as projected to baseline) nDist = aPos.X() - aStartPt.X(); if( mpFontEntry->mnOrientation ) { const long nDY = aPos.Y() - aStartPt.Y(); const double fRad = mpFontEntry->mnOrientation * F_PI1800; nDist = FRound( nDist*cos(fRad) - nDY*sin(fRad) ); } } // update the length of the textline nWidth += nAdvance; } else if( nWidth > 0 ) { // draw the textline for each word ImplDrawTextLine( aStartPt.X(), aStartPt.Y(), nDist, nWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove ); nWidth = 0; } } // draw textline for the last word if( nWidth > 0 ) { ImplDrawTextLine( aStartPt.X(), aStartPt.Y(), nDist, nWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove ); } } else { Point aStartPt = rSalLayout.GetDrawPosition(); int nWidth = rSalLayout.GetTextWidth() / rSalLayout.GetUnitsPerPixel(); ImplDrawTextLine( aStartPt.X(), aStartPt.Y(), 0, 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( nX, nY, 0, nWidth, STRIKEOUT_NONE, UNDERLINE_SINGLE, UNDERLINE_NONE, sal_False ); } void OutputDevice::ImplGetEmphasisMark( PolyPolygon& rPolyPoly, sal_Bool& rPolyLine, Rectangle& rRect1, Rectangle& rRect2, long& rYOff, long& rWidth, FontEmphasisMark eEmphasis, long nHeight, short /*nOrient*/ ) { static const sal_uInt8 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 = sal_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 another 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 = sal_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, sal_Bool bPolyLine, const Rectangle& rRect1, const Rectangle& rRect2 ) { if( IsRTLEnabled() ) // --- RTL --- mirror at basex nX = nBaseX - (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 aOldLineColor = GetLineColor(); Color aOldFillColor = GetFillColor(); sal_Bool bOldMap = mbMap; GDIMetaFile* pOldMetaFile = mpMetaFile; mpMetaFile = NULL; EnableMapMode( sal_False ); FontEmphasisMark nEmphasisMark = ImplGetEmphasisMarkStyle( maFont ); PolyPolygon aPolyPoly; Rectangle aRect1; Rectangle aRect2; long nEmphasisYOff; long nEmphasisWidth; long nEmphasisHeight; sal_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; sal_Bool bOldMap = mbMap; mnOutOffX = 0L; mnOutOffY = 0L; mpMetaFile = NULL; EnableMapMode( sal_False ); DrawMask( aPoint, aBmp, GetTextColor() ); EnableMapMode( bOldMap ); mnOutOffX = nOldOffX; mnOutOffY = nOldOffY; mpMetaFile = pOldMetaFile; return true; } void OutputDevice::ImplDrawTextDirect( SalLayout& rSalLayout, sal_Bool bTextLines ) { if( mpFontEntry->mnOwnOrientation ) if( ImplDrawRotateText( rSalLayout ) ) return; long nOldX = rSalLayout.DrawBase().X(); 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() ) { 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 OUString& rStr, sal_uInt16 nStyle, const ::vcl::ITextLayout& _rLayout ) { DBG_ASSERTWARNING( nWidth >= 0, "ImplGetTextLines: nWidth <= 0!" ); if ( nWidth <= 0 ) nWidth = 1; long nMaxLineWidth = 0; rLineInfo.Clear(); if ( !rStr.isEmpty() && (nWidth > 0) ) { OUString aText( rStr ); uno::Reference < i18n::XBreakIterator > xBI; // get service provider uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() ); uno::Reference< linguistic2::XHyphenator > xHyph; if ( nStyle & TEXT_DRAW_WORDBREAK ) { uno::Reference< linguistic2::XLinguServiceManager2> xLinguMgr = linguistic2::LinguServiceManager::create(xContext); xHyph = xLinguMgr->getHyphenator(); } sal_Int32 nPos = 0; sal_Int32 nLen = rStr.getLength(); while ( nPos < nLen ) { sal_Int32 nBreakPos = nPos; while ( ( nBreakPos < nLen ) && ( rStr[ nBreakPos ] != '\r' ) && ( rStr[ nBreakPos ] != '\n' ) ) 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().GetUILanguageTag().getLocale()); xub_StrLen nSoftBreak = _rLayout.GetTextBreak( rStr, nWidth, nPos, nBreakPos - nPos ); DBG_ASSERT( nSoftBreak < nBreakPos, "Break?!" ); i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, uno::Sequence (), 1 ); i18n::LineBreakUserOptions aUserOptions; 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 ) { // Whether hyphen or not: Put the word after the hyphen through // word boundary. // // nMaxBreakPos the last char that fits into the line // nBreakPos is the word's start // // We run into a problem if the doc is so narrow, that a word // is broken into more than two lines ... if ( xHyph.is() ) { sal_Unicode cAlternateReplChar = 0; i18n::Boundary aBoundary = xBI->getWordBoundary( aText, nBreakPos, rDefLocale, ::com::sun::star::i18n::WordType::DICTIONARY_WORD, sal_True ); sal_Int32 nWordStart = nPos; sal_Int32 nWordEnd = (sal_Int32) aBoundary.endPos; DBG_ASSERT( nWordEnd > nWordStart, "ImpBreakLine: Start >= End?" ); sal_Int32 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?" ); OUString aWord = aText.copy( nWordStart, nWordLen ); sal_uInt16 nMinTrail = static_cast(nWordEnd-nSoftBreak+1); //+1: Before the "broken off" char uno::Reference< linguistic2::XHyphenatedWord > xHyphWord; if (xHyph.is()) xHyphWord = xHyph->hyphenate( aWord, rDefLocale, aWord.getLength() - 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 { OUString aAlt( xHyphWord->getHyphenatedWord() ); // We can have two cases: // 1) "packen" turns into "pak-ken" // 2) "Schiffahrt" turns into "Schiff-fahrt" // // In case 1 we need to replace a char // In case 2 we add a char // // Correct recognition is made harder by words such as // "Schiffahrtsbrennesseln", as the Hyphenator splits all // positions of the word and comes up with "Schifffahrtsbrennnesseln" // Thus, we cannot infer the aWord from the AlternativWord's // index. // TODO: The whole junk will be made easier by a function in // the Hyphenator, as soon as AMA adds it. sal_uInt16 nAltStart = _nWordLen - 1; sal_uInt16 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength()); sal_uInt16 nTxtEnd = nTxtStart; sal_uInt16 nAltEnd = nAltStart; // The area between nStart and nEnd is the difference // between AlternativString and OriginalString while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() && aWord[nTxtEnd] != aAlt[nAltEnd] ) { ++nTxtEnd; ++nAltEnd; } // If a char was added, we notice it now: if( nAltEnd > nTxtEnd && nAltStart == nAltEnd && aWord[ nTxtEnd ] == aAlt[nAltEnd] ) { ++nAltEnd; ++nTxtStart; ++nTxtEnd; } DBG_ASSERT( ( nAltEnd - nAltStart ) == 1, "Alternate: Wrong assumption!" ); if ( nTxtEnd > nTxtStart ) cAlternateReplChar = aAlt[ nAltStart ]; nBreakPos = nWordStart + nTxtStart; if ( cAlternateReplChar ) nBreakPos++; } } } } } } nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos ); } else { // fallback to something really simple sal_Int32 nSpacePos = rStr.getLength(); long nW = 0; do { nSpacePos = rStr.lastIndexOf( ' ', nSpacePos ); if( nSpacePos != -1 ) { if( nSpacePos > nPos ) nSpacePos--; nW = _rLayout.GetTextWidth( rStr, nPos, nSpacePos-nPos ); } } while( nW > nWidth ); if( nSpacePos != -1 ) { nBreakPos = nSpacePos; nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos ); if( nBreakPos < rStr.getLength()-1 ) nBreakPos++; } } } if ( nLineWidth > nMaxLineWidth ) nMaxLineWidth = nLineWidth; rLineInfo.AddLine( new ImplTextLineInfo( nLineWidth, nPos, nBreakPos-nPos ) ); if ( nBreakPos == nPos ) nBreakPos++; nPos = nBreakPos; if ( nPos < nLen && ( ( rStr[ nPos ] == '\r' ) || ( rStr[ nPos ] == '\n' ) ) ) { nPos++; // CR/LF? if ( ( nPos < nLen ) && ( rStr[ nPos ] == '\n' ) && ( rStr[ nPos-1 ] == '\r' ) ) nPos++; } } } #ifdef DBG_UTIL for ( sal_uInt16 nL = 0; nL < rLineInfo.Count(); nL++ ) { ImplTextLineInfo* pLine = rLineInfo.GetLine( nL ); OUString aLine = rStr.copy( pLine->GetIndex(), pLine->GetLen() ); DBG_ASSERT( aLine.indexOf( '\r' ) == -1, "ImplGetTextLines - Found CR!" ); DBG_ASSERT( aLine.indexOf( '\n' ) == -1, "ImplGetTextLines - Found LF!" ); } #endif return nMaxLineWidth; } void OutputDevice::SetAntialiasing( sal_uInt16 nMode ) { if ( mnAntialiasing != nMode ) { mnAntialiasing = nMode; mbInitFont = sal_True; if(mpGraphics) { mpGraphics->setAntiAliasB2DDraw(mnAntialiasing & ANTIALIASING_ENABLE_B2DDRAW); } } if( mpAlphaVDev ) mpAlphaVDev->SetAntialiasing( nMode ); } void OutputDevice::SetFont( const Font& rNewFont ) { 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 sal_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 ); sal_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 sal_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 = sal_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 ( !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 = sal_True; if( mpMetaFile ) mpMetaFile->AddAction( new MetaTextColorAction( aFont.GetColor() ) ); } maFont = aFont; mbNewFont = sal_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( sal_uLong nTextLayoutMode ) { if( mpMetaFile ) mpMetaFile->AddAction( new MetaLayoutModeAction( nTextLayoutMode ) ); mnTextLayoutMode = nTextLayoutMode; if( mpAlphaVDev ) mpAlphaVDev->SetLayoutMode( nTextLayoutMode ); } void OutputDevice::SetDigitLanguage( LanguageType eTextLanguage ) { if( mpMetaFile ) mpMetaFile->AddAction( new MetaTextLanguageAction( eTextLanguage ) ); meTextLanguage = eTextLanguage; if( mpAlphaVDev ) mpAlphaVDev->SetDigitLanguage( eTextLanguage ); } void OutputDevice::SetTextColor( const Color& rColor ) { 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 sal_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 = sal_True; } if( mpAlphaVDev ) mpAlphaVDev->SetTextColor( COL_BLACK ); } void OutputDevice::SetTextFillColor() { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); if ( mpMetaFile ) mpMetaFile->AddAction( new MetaTextFillColorAction( Color(), sal_False ) ); if ( maFont.GetColor() != Color( COL_TRANSPARENT ) ) maFont.SetFillColor( Color( COL_TRANSPARENT ) ); if ( !maFont.IsTransparent() ) maFont.SetTransparent( sal_True ); if( mpAlphaVDev ) mpAlphaVDev->SetTextFillColor(); } void OutputDevice::SetTextFillColor( const Color& rColor ) { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); Color aColor( rColor ); sal_Bool bTransFill = ImplIsColorTransparent( aColor ) ? sal_True : sal_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 sal_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 = sal_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, sal_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_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); if ( mpMetaFile ) mpMetaFile->AddAction( new MetaTextLineColorAction( Color(), sal_False ) ); maTextLineColor = Color( COL_TRANSPARENT ); if( mpAlphaVDev ) mpAlphaVDev->SetTextLineColor(); } void OutputDevice::SetTextLineColor( const Color& rColor ) { 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 sal_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, sal_True ) ); maTextLineColor = aColor; if( mpAlphaVDev ) mpAlphaVDev->SetTextLineColor( COL_BLACK ); } void OutputDevice::SetOverlineColor() { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); if ( mpMetaFile ) mpMetaFile->AddAction( new MetaOverlineColorAction( Color(), sal_False ) ); maOverlineColor = Color( COL_TRANSPARENT ); if( mpAlphaVDev ) mpAlphaVDev->SetOverlineColor(); } void OutputDevice::SetOverlineColor( const Color& rColor ) { 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 sal_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, sal_True ) ); maOverlineColor = aColor; if( mpAlphaVDev ) mpAlphaVDev->SetOverlineColor( COL_BLACK ); } void OutputDevice::SetTextAlign( TextAlign eAlign ) { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); if ( mpMetaFile ) mpMetaFile->AddAction( new MetaTextAlignAction( eAlign ) ); if ( maFont.GetAlign() != eAlign ) { maFont.SetAlign( eAlign ); mbNewFont = sal_True; } if( mpAlphaVDev ) mpAlphaVDev->SetTextAlign( eAlign ); } void OutputDevice::DrawTextLine( const Point& rPos, long nWidth, FontStrikeout eStrikeout, FontUnderline eUnderline, FontUnderline eOverline, sal_Bool bUnderlineAbove ) { 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(), 0, nWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove ); if( mpAlphaVDev ) mpAlphaVDev->DrawTextLine( rPos, nWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove ); } void OutputDevice::DrawWaveLine( const Point& rStartPos, const Point& rEndPos, sal_uInt16 nStyle ) { 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++; if (mnDPIScaleFactor > 1) { nWaveHeight *= mnDPIScaleFactor; // odd heights look better than even if (mnDPIScaleFactor % 2 == 0) { nStartY++; // Shift down an additional pixel to create more visual separation. nWaveHeight--; } } } 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, 0, 0, nEndX-nStartX, nWaveHeight, mnDPIScaleFactor, nOrientation, GetLineColor()); if( mpAlphaVDev ) mpAlphaVDev->DrawWaveLine( rStartPos, rEndPos, nStyle ); } void OutputDevice::DrawText( const Point& rStartPt, const OUString& rStr, xub_StrLen nIndex, xub_StrLen nLen, MetricVector* pVector, OUString* pDisplayText ) { if( mpOutDevData && mpOutDevData->mpRecordLayout ) { pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects; pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText; } DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); #if OSL_DEBUG_LEVEL > 2 fprintf( stderr, " OutputDevice::DrawText(\"%s\")\n", OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ).getStr() ); #endif if( nLen == STRING_LEN ) nLen = rStr.getLength() - nIndex; 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.getLength() ); 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[ 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 += OUString(rStr[ nIndex ]); bInserted = true; } } } else { GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, nIndex, *pVector ); if( pDisplayText ) *pDisplayText += 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 OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen ) const { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); long nWidth = GetTextArray( rStr, NULL, nIndex, nLen ); return nWidth; } long OutputDevice::GetTextHeight() const { 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; } float OutputDevice::approximate_char_width() const { return GetTextWidth("aemnnxEM") / 8.0; } void OutputDevice::DrawTextArray( const Point& rStartPt, const OUString& rStr, const sal_Int32* pDXAry, xub_StrLen nIndex, xub_StrLen nLen ) { 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 OUString& rStr, sal_Int32* pDXAry, sal_Int32 nIndex, sal_Int32 nLen ) const { // MEM: default nLen = STRING_LENGTH DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); if( nIndex >= rStr.getLength() ) return 0; if( nLen < 0 || nIndex+nLen >= rStr.getLength() ) nLen = rStr.getLength() - 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 OUString& rStr, sal_Int32* pCaretXArray, sal_Int32 nIndex, sal_Int32 nLen, sal_Int32* pDXAry, long nLayoutWidth, sal_Bool bCellBreaking ) const { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); if( nIndex >= rStr.getLength() ) return false; if( nIndex+nLen >= rStr.getLength() ) nLen = rStr.getLength() - 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 ) { ; // FIXME } return true; } void OutputDevice::DrawStretchText( const Point& rStartPt, sal_uLong nWidth, const OUString& rStr, xub_StrLen nIndex, xub_StrLen nLen ) { 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( OUString& rStr, const sal_Int32 nMinIndex, const sal_Int32 nLen, long nPixelWidth, const sal_Int32* pDXArray ) const { // get string length for calculating extents sal_Int32 nEndIndex = rStr.getLength(); if( 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 sal_Unicode* pStr = rStr.getStr() + nMinIndex; const sal_Unicode* pEnd = rStr.getStr() + 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.getStr() + nMinIndex; const sal_Unicode* pEnd = rStr.getStr() + 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 || ((*pStr >= 0xFE00) && (*pStr < 0xFE10)) // variation selectors in BMP || ((pStr + 1 < pEnd) && (pStr[0] == 0xDB40) && (0xDD00 <= pStr[1]) && (pStr[1] < 0xDEF0)) // variation selector supplement ) 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.getStr(); const sal_Unicode* pStr = pBase + nMinIndex; const sal_Unicode* pEnd = pBase + nEndIndex; OUStringBuffer sTmpStr(rStr); 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? sTmpStr[pStr - pBase] = cChar; } } rStr = sTmpStr.makeStringAndClear(); } // 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 bool bRTLWindow = IsRTLEnabled(); bRightAlign ^= bRTLWindow; if( bRightAlign ) nLayoutFlags |= SAL_LAYOUT_RIGHT_ALIGN; // set layout options ImplLayoutArgs aLayoutArgs( rStr.getStr(), rStr.getLength(), nMinIndex, nEndIndex, nLayoutFlags, maFont.GetLanguageTag() ); int nOrientation = mpFontEntry ? mpFontEntry->mnOrientation : 0; aLayoutArgs.SetOrientation( nOrientation ); aLayoutArgs.SetLayoutWidth( nPixelWidth ); aLayoutArgs.SetDXArray( pDXArray ); return aLayoutArgs; } SalLayout* OutputDevice::ImplLayout( const OUString& rOrigStr, sal_Int32 nMinIndex, sal_Int32 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( -1 == nLen || nMinIndex + nLen > rOrigStr.getLength() ) { const sal_Int32 nNewLen = rOrigStr.getLength() - nMinIndex; if( nNewLen <= 0 ) return NULL; nLen = nNewLen; } OUString aStr = rOrigStr; // filter out special markers if( bFilter ) { sal_Int32 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.getLength() ); } 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 ); #ifdef MACOSX // CoreText layouts are immutable and already contain the text color // so we need to provide the color already for the layout request // even if this layout will never be drawn if( mbInitTextColor ) const_cast(*this).ImplInitTextColor(); #endif // get matching layout object for base font SalLayout* 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::getFallbackFont(ImplFontEntry &rFallbackFont, FontSelectPattern &rFontSelData, int nFallbackLevel, ImplLayoutArgs& rLayoutArgs) const { rFallbackFont.mnSetFontFlags = mpGraphics->SetFont( &rFontSelData, nFallbackLevel ); rLayoutArgs.ResetPos(); SalLayout* pFallback = mpGraphics->GetTextLayout( rLayoutArgs, nFallbackLevel ); if (!pFallback) return NULL; if (!pFallback->LayoutText(rLayoutArgs)) { // there is no need for a font that couldn't resolve anything pFallback->Release(); return NULL; } pFallback->AdjustLayout( rLayoutArgs ); return pFallback; } 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; // get list of unicodes that need glyph fallback int nCharPos = -1; bool bRTL = false; OUStringBuffer aMissingCodeBuf; while( rLayoutArgs.GetNextPos( &nCharPos, &bRTL) ) aMissingCodeBuf.append( rLayoutArgs.mpStr[ nCharPos ] ); rLayoutArgs.ResetPos(); OUString aMissingCodes = aMissingCodeBuf.makeStringAndClear(); FontSelectPattern 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; } } // create and add glyph fallback layout to multilayout SalLayout* pFallback = getFallbackFont(*pFallbackFont, aFontSelData, nFallbackLevel, rLayoutArgs); if (pFallback) { if( !pMultiSalLayout ) pMultiSalLayout = new MultiSalLayout( *pSalLayout ); pMultiSalLayout->AddFallback( *pFallback, rLayoutArgs.maRuns, aFontSelData.mpFontData ); if (nFallbackLevel == MAX_FALLBACK-1) pMultiSalLayout->SetInComplete(); } 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; } sal_Bool OutputDevice::GetTextIsRTL( const OUString& rString, sal_Int32 nIndex, sal_Int32 nLen ) const { OUString aStr( rString ); ImplLayoutArgs aArgs = ImplPrepareLayoutArgs( aStr, nIndex, nLen, 0, NULL ); bool bRTL = false; int nCharPos = -1; aArgs.GetNextPos( &nCharPos, &bRTL ); return (nCharPos != nIndex) ? sal_True : sal_False; } xub_StrLen OutputDevice::GetTextBreak( const OUString& rStr, long nTextWidth, sal_Int32 nIndex, sal_Int32 nLen, long nCharExtra, sal_Bool /*TODO: bCellBreaking*/ ) const { 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(pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor )); pSalLayout->Release(); } return nRetVal; } xub_StrLen OutputDevice::GetTextBreak( const OUString& rStr, long nTextWidth, sal_Unicode nHyphenatorChar, sal_Int32& rHyphenatorPos, sal_Int32 nIndex, sal_Int32 nLen, long nCharExtra ) const { 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(pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor )); // calculate hyphenated break position OUString aHyphenatorStr(nHyphenatorChar); sal_Int32 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; // why does this return "int" and use STRING_LEN for errors??? xub_StrLen nTmp = sal::static_int_cast( pSalLayout->GetTextBreak(nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor)); nTmp = std::min(nTmp, nRetVal); // TODO: remove nTmp when GetTextBreak sal_Int32 rHyphenatorPos = (nTmp == STRING_LEN) ? -1 : nTmp; } pSalLayout->Release(); return nRetVal; } void OutputDevice::ImplDrawText( OutputDevice& rTargetDevice, const Rectangle& rRect, const OUString& rOrigStr, sal_uInt16 nStyle, MetricVector* pVector, OUString* pDisplayText, ::vcl::ITextLayout& _rLayout ) { Color aOldTextColor; Color aOldTextFillColor; bool bRestoreFillColor = false; if ( (nStyle & TEXT_DRAW_DISABLE) && ! pVector ) { sal_Bool bHighContrastBlack = sal_False; sal_Bool bHighContrastWhite = sal_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 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(); sal_Int32 nMnemonicPos = -1; OUString aStr = rOrigStr; if ( nStyle & TEXT_DRAW_MNEMONIC ) aStr = GetNonMnemonicString( aStr, nMnemonicPos ); const bool bDrawMnemonics = !(rTargetDevice.GetSettings().GetStyleSettings().GetOptions() & STYLE_OPTION_NOMNEMONICS) && !pVector; // We treat multiline text differently if ( nStyle & TEXT_DRAW_MULTILINE ) { OUString aLastLine; ImplMultiTextLineInfo aMultiLineInfo; ImplTextLineInfo* pLineInfo; xub_StrLen i; xub_StrLen nLines; xub_StrLen nFormatLines; if ( nTextHeight ) { long 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 ) { // Create last line and shorten it nFormatLines = nLines-1; pLineInfo = aMultiLineInfo.GetLine( nFormatLines ); aLastLine = convertLineEnd(aStr.copy(pLineInfo->GetIndex()), LINEEND_LF); // Replace all LineFeeds with Spaces OUStringBuffer aLastLineBuffer(aLastLine); sal_Int32 nLastLineLen = aLastLineBuffer.getLength(); for ( i = 0; i < nLastLineLen; i++ ) { if ( aLastLineBuffer[ i ] == '\n' ) aLastLineBuffer[ i ] = ' '; } aLastLine = aLastLineBuffer.makeStringAndClear(); 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; } // Do we need to clip the height? if ( nFormatLines*nTextHeight > nHeight ) nStyle |= TEXT_DRAW_CLIP; // Set clipping if ( nStyle & TEXT_DRAW_CLIP ) { rTargetDevice.Push( PUSH_CLIPREGION ); rTargetDevice.IntersectClipRegion( rRect ); } // Vertical 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(); // Output all lines except for the last one 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 ); /*sal_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( std::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(); } // If there still is a last line, we output it left-aligned as the line would be clipped if ( !aLastLine.isEmpty() ) _rLayout.DrawText( aPos, aLastLine, 0, STRING_LEN, pVector, pDisplayText ); // Reset clipping if ( nStyle & TEXT_DRAW_CLIP ) rTargetDevice.Pop(); } } else { long nTextWidth = _rLayout.GetTextWidth( aStr, 0, -1 ); // Clip text if needed 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.getLength() ); } } 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 != -1 ) { sal_Int32* pCaretXArray = (sal_Int32*) alloca( 2 * sizeof(sal_Int32) * aStr.getLength() ); /*sal_Bool bRet =*/ _rLayout.GetCaretPositions( aStr, pCaretXArray, 0, aStr.getLength() ); 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( std::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 != -1 ) rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); } rTargetDevice.Pop(); } else { _rLayout.DrawText( aPos, aStr, 0, STRING_LEN, pVector, pDisplayText ); if ( bDrawMnemonics ) { if ( nMnemonicPos != -1 ) 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 OUString& rOrigStr, sal_uInt16 nStyle, GDIMetaFile& rMtf ) { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); if ( rOrigStr.isEmpty() || 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 sal_Bool bOutputEnabled( IsOutputEnabled() ); GDIMetaFile* pMtf = mpMetaFile; mpMetaFile = &rMtf; EnableOutput( sal_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 OUString& rOrigStr, sal_uInt16 nStyle, MetricVector* pVector, OUString* pDisplayText, ::vcl::ITextLayout* _pTextLayout ) { if( mpOutDevData && mpOutDevData->mpRecordLayout ) { pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects; pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText; } 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.isEmpty() || 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 OUString& rStr, sal_uInt16 nStyle, TextRectInfo* pInfo, const ::vcl::ITextLayout* _pTextLayout ) const { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); Rectangle aRect = rRect; xub_StrLen nLines; long nWidth = rRect.GetWidth(); long nMaxWidth; long nTextHeight = GetTextHeight(); OUString 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 = (sal_uInt16)(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 = sal_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.getLength() ) : GetTextWidth( aStr ); if ( pInfo ) { pInfo->mnLineCount = 1; pInfo->mnMaxWidth = nMaxWidth; } if ( (nMaxWidth > nWidth) && (nStyle & TEXT_DRAW_ELLIPSIS) ) { if ( pInfo ) pInfo->mbEllipsis = sal_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; // #99188# get rid of rounding problems when using this rect later if (nStyle & TEXT_DRAW_RIGHT) aRect.Left()--; else aRect.Right()++; return aRect; } static bool ImplIsCharIn( sal_Unicode c, const sal_Char* pStr ) { while ( *pStr ) { if ( *pStr == c ) return true; pStr++; } return false; } OUString OutputDevice::GetEllipsisString( const OUString& rOrigStr, long nMaxWidth, sal_uInt16 nStyle ) const { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); DefaultTextLayout aTextLayout( *const_cast< OutputDevice* >( this ) ); return ImplGetEllipsisString( *this, rOrigStr, nMaxWidth, nStyle, aTextLayout ); } OUString OutputDevice::ImplGetEllipsisString( const OutputDevice& rTargetDevice, const OUString& rOrigStr, long nMaxWidth, sal_uInt16 nStyle, const ::vcl::ITextLayout& _rLayout ) { OUString aStr = rOrigStr; sal_Int32 nIndex = _rLayout.GetTextBreak( aStr, nMaxWidth, 0, aStr.getLength() ); if ( nIndex != STRING_LEN ) { if( (nStyle & TEXT_DRAW_CENTERELLIPSIS) == TEXT_DRAW_CENTERELLIPSIS ) { OUStringBuffer aTmpStr( aStr ); sal_Int32 nEraseChars = 4; while( nEraseChars < aStr.getLength() && _rLayout.GetTextWidth( aTmpStr.toString(), 0, aTmpStr.getLength() ) > nMaxWidth ) { aTmpStr = OUStringBuffer(aStr); sal_Int32 i = (aTmpStr.getLength() - nEraseChars)/2; aTmpStr.remove(i, nEraseChars++); aTmpStr.insert(i, "..."); } aStr = aTmpStr.makeStringAndClear(); } else if ( nStyle & TEXT_DRAW_ENDELLIPSIS ) { aStr = aStr.copy(0, nIndex); if ( nIndex > 1 ) { aStr += "..."; while ( !aStr.isEmpty() && (_rLayout.GetTextWidth( aStr, 0, aStr.getLength() ) > nMaxWidth) ) { if ( (nIndex > 1) || (nIndex == aStr.getLength()) ) nIndex--; aStr = aStr.replaceAt( nIndex, 1, ""); } } if ( aStr.isEmpty() && (nStyle & TEXT_DRAW_CLIP) ) aStr += OUString(rOrigStr[ 0 ]); } else if ( nStyle & TEXT_DRAW_PATHELLIPSIS ) { OUString aPath( rOrigStr ); OUString aAbbreviatedPath; osl_abbreviateSystemPath( aPath.pData, &aAbbreviatedPath.pData, nIndex, NULL ); aStr = aAbbreviatedPath; } else if ( nStyle & TEXT_DRAW_NEWSELLIPSIS ) { static sal_Char const pSepChars[] = "."; // Determine last section sal_Int32 nLastContent = aStr.getLength(); while ( nLastContent ) { nLastContent--; if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) ) break; } while ( nLastContent && ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) ) nLastContent--; OUString aLastStr = aStr.copy(nLastContent); OUString aTempLastStr1( "..." ); aTempLastStr1 += aLastStr; if ( _rLayout.GetTextWidth( aTempLastStr1, 0, aTempLastStr1.getLength() ) > nMaxWidth ) aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | TEXT_DRAW_ENDELLIPSIS, _rLayout ); else { sal_Int32 nFirstContent = 0; while ( nFirstContent < nLastContent ) { nFirstContent++; if ( ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) ) break; } while ( (nFirstContent < nLastContent) && ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) ) nFirstContent++; // MEM continue here if ( nFirstContent >= nLastContent ) aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | TEXT_DRAW_ENDELLIPSIS, _rLayout ); else { if ( nFirstContent > 4 ) nFirstContent = 4; OUString aFirstStr = aStr.copy( 0, nFirstContent ); aFirstStr += "..."; OUString aTempStr = aFirstStr + aLastStr; if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth ) aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | TEXT_DRAW_ENDELLIPSIS, _rLayout ); else { do { aStr = aTempStr; if( nLastContent > aStr.getLength() ) nLastContent = aStr.getLength(); while ( nFirstContent < nLastContent ) { nLastContent--; if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) ) break; } while ( (nFirstContent < nLastContent) && ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) ) nLastContent--; if ( nFirstContent < nLastContent ) { OUString aTempLastStr = aStr.copy( nLastContent ); aTempStr = aFirstStr + aTempLastStr; if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth ) break; } } while ( nFirstContent < nLastContent ); } } } } } return aStr; } void OutputDevice::DrawCtrlText( const Point& rPos, const OUString& rStr, xub_StrLen nIndex, xub_StrLen nLen, sal_uInt16 nStyle, MetricVector* pVector, OUString* pDisplayText ) { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); if ( !IsDeviceOutputNecessary() || (nIndex >= rStr.getLength()) ) 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.getLength() ) return; if( (sal_Int32)nIndex+nLen >= rStr.getLength() ) nLen = rStr.getLength() - nIndex; OUString aStr = rStr; sal_Int32 nMnemonicPos = -1; long nMnemonicX = 0; long nMnemonicY = 0; long nMnemonicWidth = 0; if ( (nStyle & TEXT_DRAW_MNEMONIC) && nLen > 1 ) { aStr = GetNonMnemonicString( aStr, nMnemonicPos ); if ( nMnemonicPos != -1 ) { 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 ); /*sal_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( std::min(lc_x1,lc_x2), GetFontMetric().GetAscent() ); if( bInvalidPos ) // #106952#, place behind the (last) character aTempPos = Point( std::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; sal_Bool bHighContrastBlack = sal_False; sal_Bool bHighContrastWhite = sal_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 != -1 ) 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 != -1 ) ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); } } if( mpAlphaVDev ) mpAlphaVDev->DrawCtrlText( rPos, rStr, nIndex, nLen, nStyle, pVector, pDisplayText ); } long OutputDevice::GetCtrlTextWidth( const OUString& rStr, xub_StrLen nIndex, xub_StrLen nLen, sal_uInt16 nStyle ) const { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); if ( nStyle & TEXT_DRAW_MNEMONIC ) { sal_Int32 nMnemonicPos; OUString aStr = GetNonMnemonicString( rStr, nMnemonicPos ); if ( nMnemonicPos != -1 ) { if ( nMnemonicPos < nIndex ) nIndex--; else if ( (nLen < STRING_LEN) && (nMnemonicPos >= nIndex) && ((sal_uLong)nMnemonicPos < (sal_uLong)(nIndex+nLen)) ) nLen--; } return GetTextWidth( aStr, nIndex, nLen ); } else return GetTextWidth( rStr, nIndex, nLen ); } OUString OutputDevice::GetNonMnemonicString( const OUString& rStr, sal_Int32& rMnemonicPos ) { OUString aStr = rStr; sal_Int32 nLen = aStr.getLength(); sal_Int32 i = 0; rMnemonicPos = -1; while ( i < nLen ) { if ( aStr[ i ] == '~' ) { if ( nLen <= i+1 ) break; if ( aStr[ i+1 ] != '~' ) { if ( rMnemonicPos == -1 ) rMnemonicPos = i; aStr = aStr.replaceAt( i, 1, "" ); nLen--; } else { aStr = aStr.replaceAt( i, 1, "" ); nLen--; i++; } } else i++; } return aStr; } int OutputDevice::GetDevFontCount() const { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); if( !mpGetDevFontList ) mpGetDevFontList = mpFontList->GetDevFontList(); return mpGetDevFontList->Count(); } FontInfo OutputDevice::GetDevFont( int nDevFontIndex ) const { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); FontInfo aFontInfo; ImplInitFontList(); int nCount = GetDevFontCount(); if( nDevFontIndex < nCount ) { const PhysicalFontFace& rData = *mpGetDevFontList->Get( nDevFontIndex ); aFontInfo.SetName( rData.GetFamilyName() ); aFontInfo.SetStyleName( rData.GetStyleName() ); aFontInfo.SetCharSet( rData.IsSymbolFont() ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); aFontInfo.SetFamily( rData.GetFamilyType() ); aFontInfo.SetPitch( rData.GetPitch() ); aFontInfo.SetWeight( rData.GetWeight() ); aFontInfo.SetItalic( rData.GetSlant() ); aFontInfo.SetWidthType( rData.GetWidthType() ); if( rData.IsScalable() ) aFontInfo.mpImplMetric->mnMiscFlags |= ImplFontMetric::SCALABLE_FLAG; if( rData.mbDevice ) aFontInfo.mpImplMetric->mnMiscFlags |= ImplFontMetric::DEVICE_FLAG; } return aFontInfo; } sal_Bool OutputDevice::AddTempDevFont( const OUString& rFileURL, const OUString& rFontName ) { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); ImplInitFontList(); if( !mpGraphics && !ImplGetGraphics() ) return sal_False; bool bRC = mpGraphics->AddTempDevFont( mpFontList, rFileURL, rFontName ); if( !bRC ) return sal_False; if( mpAlphaVDev ) mpAlphaVDev->AddTempDevFont( rFileURL, rFontName ); mpFontCache->Invalidate(); return sal_True; } int OutputDevice::GetDevFontSizeCount( const Font& rFont ) const { 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_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; } sal_Bool OutputDevice::IsFontAvailable( const OUString& rFontName ) const { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); ImplDevFontListData* pFound = mpFontList->FindFontFamily( rFontName ); return (pFound != NULL); } FontMetric OutputDevice::GetFontMetric() const { 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->GetStyleName() ); aMetric.SetSize( PixelToLogic( Size( pMetric->mnWidth, pMetric->mnAscent+pMetric->mnDescent-pMetric->mnIntLeading ) ) ); aMetric.SetCharSet( pMetric->IsSymbolFont() ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); aMetric.SetFamily( pMetric->GetFamilyType() ); aMetric.SetPitch( pMetric->GetPitch() ); aMetric.SetWeight( pMetric->GetWeight() ); aMetric.SetItalic( pMetric->GetSlant() ); aMetric.SetWidthType( pMetric->GetWidthType() ); 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(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(this)->SetFont( rFont ); FontMetric aMetric( GetFontMetric() ); const_cast(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 OUString& rStr, xub_StrLen nIndex, xub_StrLen nLen, const sal_Int32* pDXAry) const { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); SystemTextLayoutData aSysLayoutData; aSysLayoutData.nSize = sizeof(aSysLayoutData); aSysLayoutData.rGlyphData.reserve( 256 ); aSysLayoutData.orientation = 0; 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* pLayout = ImplLayout( rStr, nIndex, nLen, rStartPt, 0, pDXAry, true ); if ( !pLayout ) return aSysLayoutData; // setup glyphs Point aPos; sal_GlyphId aGlyphId; for( int nStart = 0; pLayout->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 (aGlyphId & GF_IDXMASK); aGlyph.x = aPos.X(); aGlyph.y = aPos.Y(); int nLevel = (aGlyphId & GF_FONTMASK) >> GF_FONTSHIFT; aGlyph.fallbacklevel = nLevel < MAX_FALLBACK ? nLevel : 0; aSysLayoutData.rGlyphData.push_back(aGlyph); } // Get font data aSysLayoutData.orientation = pLayout->GetOrientation(); pLayout->Release(); return aSysLayoutData; } long OutputDevice::GetMinKashida() const { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); if( mbNewFont && !ImplNewFont() ) return 0; ImplFontEntry* pEntry = mpFontEntry; ImplFontMetricData* pMetric = &(pEntry->maMetric); return ImplDevicePixelToLogicWidth( pMetric->mnMinKashida ); } xub_StrLen OutputDevice::ValidateKashidas ( const OUString& 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; } sal_Bool OutputDevice::GetGlyphBoundRects( const Point& rOrigin, const OUString& rStr, int nIndex, int nLen, int nBase, MetricVector& rVector ) { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); rVector.clear(); if( nLen == STRING_LEN ) nLen = rStr.getLength() - nIndex; Rectangle aRect; for( int i = 0; i < nLen; i++ ) { if( !GetTextBoundRect( aRect, rStr, sal::static_int_cast(nBase), sal::static_int_cast(nIndex+i), 1 ) ) break; aRect.Move( rOrigin.X(), rOrigin.Y() ); rVector.push_back( aRect ); } return (nLen == (int)rVector.size()); } sal_Bool OutputDevice::GetTextBoundRect( Rectangle& rRect, const OUString& rStr, xub_StrLen nBase, xub_StrLen nIndex, xub_StrLen nLen, sal_uLong nLayoutWidth, const sal_Int32* pDXAry ) const { DBG_CHKTHIS( OutputDevice, ImplDbgCheckOutputDevice ); sal_Bool bRet = sal_False; rRect.SetEmpty(); SalLayout* pSalLayout = NULL; const Point aPoint; // calculate offset when nBase!=nIndex long nXOffset = 0; if( nBase != nIndex ) { xub_StrLen nStart = std::min( nBase, nIndex ); xub_StrLen nOfsLen = std::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( sal_False ); aFont.SetOutline( sal_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 sal_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 sal_True; } return sal_False; } sal_Bool OutputDevice::GetTextOutlines( ::basegfx::B2DPolyPolygonVector& rVector, const OUString& rStr, xub_StrLen nBase, xub_StrLen nIndex, xub_StrLen nLen, sal_Bool bOptimize, sal_uLong nTWidth, const sal_Int32* pDXArray ) const { // the fonts need to be initialized if( mbNewFont ) ImplNewFont(); if( mbInitFont ) ImplInitFont(); if( !mpFontEntry ) return sal_False; sal_Bool bRet = sal_False; rVector.clear(); if( nLen == STRING_LEN ) nLen = rStr.getLength() - 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 sal_Bool bOldMap = mbMap; if( bOldMap ) { const_cast(*this).mbMap = sal_False; const_cast(*this).mbNewFont = sal_True; } SalLayout* pSalLayout = NULL; // calculate offset when nBase!=nIndex long nXOffset = 0; if( nBase != nIndex ) { xub_StrLen nStart = std::min( nBase, nIndex ); xub_StrLen nOfsLen = std::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(*this).mbMap = bOldMap; const_cast(*this).mbNewFont = sal_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 sal_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; OUString aStr( rStr ); // prepare for e.g. localized digits sal_Int32 nIndex2 = nIndex; // only needed until nIndex is sal_Int32 sal_Int32 nLen2 = nLen; // only needed until nLen is sal_Int32 ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nIndex2, nLen2, 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 (sal_uInt16 j = 0; j < aPolyPoly.Count(); ++j) { Polygon& rPoly = aPolyPoly[j]; for (sal_uInt16 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; } sal_Bool OutputDevice::GetTextOutlines( PolyPolyVector& rResultVector, const OUString& rStr, xub_StrLen nBase, xub_StrLen nIndex, xub_StrLen nLen, sal_Bool bOptimize, sal_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 sal_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 sal_True; } sal_Bool OutputDevice::GetTextOutline( PolyPolygon& rPolyPoly, const OUString& rStr, xub_StrLen nBase, xub_StrLen nIndex, xub_StrLen nLen, sal_Bool bOptimize, sal_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 sal_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 sal_True; } bool OutputDevice::GetFontCapabilities( FontCapabilities& rFontCapabilities ) const { // we need a graphics if( !mpGraphics && !ImplGetGraphics() ) return false; if( mbNewFont ) ImplNewFont(); if( mbInitFont ) ImplInitFont(); if( !mpFontEntry ) return false; return mpGraphics->GetImplFontCapabilities(rFontCapabilities); } sal_Bool OutputDevice::GetFontCharMap( FontCharMap& rFontCharMap ) const { rFontCharMap.Reset(); // we need a graphics if( !mpGraphics && !ImplGetGraphics() ) return sal_False; if( mbNewFont ) ImplNewFont(); if( mbInitFont ) ImplInitFont(); if( !mpFontEntry ) return sal_False; #ifdef ENABLE_IFC_CACHE // a little font charmap cache helps considerably static const int NMAXITEMS = 16; static int nUsedItems = 0, nCurItem = 0; struct CharMapCacheItem { const PhysicalFontFace* mpFontData; FontCharMap maCharMap; }; static CharMapCacheItem aCache[ NMAXITEMS ]; const PhysicalFontFace* 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 #endif // ENABLE_IFC_CACHE { const ImplFontCharMap* pNewMap = mpGraphics->GetImplFontCharMap(); rFontCharMap.Reset( pNewMap ); #ifdef ENABLE_IFC_CACHE // 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; #endif // ENABLE_IFC_CACHE } if( rFontCharMap.IsDefaultMap() ) return sal_False; return sal_True; } xub_StrLen OutputDevice::HasGlyphs( const Font& rTempFont, const OUString& rStr, xub_StrLen nIndex, xub_StrLen nLen ) const { if( nIndex >= rStr.getLength() ) return nIndex; sal_Int32 nEnd = nIndex + nLen; if( nIndex+nLen > rStr.getLength() ) nEnd = rStr.getLength(); DBG_ASSERT( nIndex < nEnd, "StartPos >= EndPos?" ); DBG_ASSERT( nEnd <= rStr.getLength(), "String too short" ); // to get the map temporarily set font const Font aOrigFont = GetFont(); const_cast(*this).SetFont( rTempFont ); FontCharMap aFontCharMap; sal_Bool bRet = GetFontCharMap( aFontCharMap ); const_cast(*this).SetFont( aOrigFont ); // if fontmap is unknown assume it doesn't have the glyphs if( bRet == sal_False ) return nIndex; for( sal_Int32 i = nIndex; nIndex < nEnd; ++i, ++nIndex ) if( ! aFontCharMap.HasChar( rStr[i] ) ) return nIndex; return STRING_LEN; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */