Files
loongoffice/vcl/source/outdev/text.cxx
Tor Lillqvist 4393bd096c WaE: macro is not used
Change-Id: I32a4874bb4be9741b509c5781cd90c17a3e97ab5
2014-04-20 09:57:06 +03:00

2850 lines
99 KiB
C++

/* -*- 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"
#include <vcl/settings.hxx>
// declare system types in sysdata.hxx
#include <svsys.h>
#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 "PhysicalFontCollection.hxx"
#include "PhysicalFontFace.hxx"
#include "PhysicalFontFamily.hxx"
#include "textlayout.hxx"
#include "svids.hrc"
#include "window.h"
#include "unotools/fontcvt.hxx"
#include "unotools/fontcfg.hxx"
#include "osl/file.h"
#include <config_graphite.h>
#if ENABLE_GRAPHITE
#include "graphite_features.hxx"
#endif
#include "../gdi/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 <comphelper/processfactory.hxx>
#include "sal/alloca.h"
#include <cmath>
#include <cstring>
#include <memory>
#include <algorithm>
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)
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;
}
void OutputDevice::ImplInitTextColor()
{
DBG_TESTSOLARMUTEX();
if ( mbInitTextColor )
{
mpGraphics->SetTextColor( ImplColorToSal( GetTextColor() ) );
mbInitTextColor = false;
}
}
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 = true;
}
mpGraphics->SetFillColor( ImplColorToSal( GetTextFillColor() ) );
mbInitFillColor = 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 ) );
}
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->InitFont();
pVDev->ImplInitTextColor();
// draw text into upper left corner
rSalLayout.DrawBase() -= aBoundRect.TopLeft();
rSalLayout.DrawText( *((OutputDevice*)pVDev)->mpGraphics );
Bitmap aBmp = pVDev->GetBitmap( Point(), aBoundRect.GetSize() );
if ( !aBmp || !aBmp.Rotate( mpFontEntry->mnOwnOrientation, COL_WHITE ) )
return false;
// calculate rotation offset
Polygon aPoly( aBoundRect );
aPoly.Rotate( Point(), mpFontEntry->mnOwnOrientation );
Point aPoint = aPoly.GetBoundRect().TopLeft();
aPoint += Point( nX, nY );
// mask output with text colored bitmap
GDIMetaFile* pOldMetaFile = mpMetaFile;
long nOldOffX = mnOutOffX;
long nOldOffY = mnOutOffY;
bool bOldMap = mbMap;
mnOutOffX = 0L;
mnOutOffY = 0L;
mpMetaFile = NULL;
EnableMapMode( false );
DrawMask( aPoint, aBmp, GetTextColor() );
EnableMapMode( bOldMap );
mnOutOffX = nOldOffX;
mnOutOffY = nOldOffY;
mpMetaFile = pOldMetaFile;
return true;
}
bool OutputDevice::ImplDrawTextDirect( SalLayout& rSalLayout, bool bTextLines, sal_uInt32 flags )
{
if( mpFontEntry->mnOwnOrientation )
if( ImplDrawRotateText( rSalLayout ) )
return true;
long nOldX = rSalLayout.DrawBase().X();
if( HasMirroredGraphics() )
{
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;
}
if(flags)
{
if( ! rSalLayout.DrawTextSpecial( *mpGraphics, flags ))
{
rSalLayout.DrawBase().X() = nOldX;
return false;
}
}
else
{
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 );
return true;
}
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() )
{
if(! ImplDrawTextDirect( rSalLayout, mbTextLines, DRAWTEXT_F_OUTLINE))
{
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) )
{
uno::Reference < i18n::XBreakIterator > xBI;
// get service provider
uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
bool bHyphenate = (nStyle & TEXT_DRAW_WORDBREAK_HYPHENATION)
== TEXT_DRAW_WORDBREAK_HYPHENATION;
uno::Reference< linguistic2::XHyphenator > xHyph;
if ( bHyphenate )
{
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());
sal_Int32 nSoftBreak = _rLayout.GetTextBreak( rStr, nWidth, nPos, nBreakPos - nPos );
DBG_ASSERT( nSoftBreak < nBreakPos, "Break?!" );
i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, uno::Sequence <beans::PropertyValue>(), 1 );
i18n::LineBreakUserOptions aUserOptions;
i18n::LineBreakResults aLBR = xBI->getLineBreak( rStr, nSoftBreak, rDefLocale, nPos, aHyphOptions, aUserOptions );
nBreakPos = aLBR.breakIndex;
if ( nBreakPos <= nPos )
nBreakPos = nSoftBreak;
if ( bHyphenate )
{
// 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( rStr, nBreakPos, rDefLocale, ::com::sun::star::i18n::WordType::DICTIONARY_WORD, sal_True );
sal_Int32 nWordStart = nPos;
sal_Int32 nWordEnd = 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 = rStr.copy( nWordStart, nWordLen );
sal_Int32 nMinTrail = 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())
{
bool bAlternate = xHyphWord->isAlternativeSpelling();
sal_Int32 _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_Int32 nAltStart = _nWordLen - 1;
sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength());
sal_Int32 nTxtEnd = nTxtStart;
sal_Int32 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::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 )
{
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 = true;
}
if( mpAlphaVDev )
mpAlphaVDev->SetTextColor( COL_BLACK );
}
void OutputDevice::SetTextFillColor()
{
if ( mpMetaFile )
mpMetaFile->AddAction( new MetaTextFillColorAction( Color(), false ) );
if ( maFont.GetColor() != Color( COL_TRANSPARENT ) )
maFont.SetFillColor( Color( COL_TRANSPARENT ) );
if ( !maFont.IsTransparent() )
maFont.SetTransparent( true );
if( mpAlphaVDev )
mpAlphaVDev->SetTextFillColor();
}
void OutputDevice::SetTextFillColor( const Color& rColor )
{
Color aColor( rColor );
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 = true;
}
if ( !bTransFill && (mnDrawMode & DRAWMODE_GHOSTEDFILL) )
{
aColor = Color( (aColor.GetRed() >> 1) | 0x80,
(aColor.GetGreen() >> 1) | 0x80,
(aColor.GetBlue() >> 1) | 0x80 );
}
}
}
if ( mpMetaFile )
mpMetaFile->AddAction( new MetaTextFillColorAction( aColor, true ) );
if ( maFont.GetFillColor() != aColor )
maFont.SetFillColor( aColor );
if ( maFont.IsTransparent() != bTransFill )
maFont.SetTransparent( bTransFill );
if( mpAlphaVDev )
mpAlphaVDev->SetTextFillColor( COL_BLACK );
}
Color OutputDevice::GetTextFillColor() const
{
if ( maFont.IsTransparent() )
return Color( COL_TRANSPARENT );
else
return maFont.GetFillColor();
}
void OutputDevice::SetTextAlign( TextAlign eAlign )
{
if ( mpMetaFile )
mpMetaFile->AddAction( new MetaTextAlignAction( eAlign ) );
if ( maFont.GetAlign() != eAlign )
{
maFont.SetAlign( eAlign );
mbNewFont = true;
}
if( mpAlphaVDev )
mpAlphaVDev->SetTextAlign( eAlign );
}
void OutputDevice::DrawText( const Point& rStartPt, const OUString& rStr,
sal_Int32 nIndex, sal_Int32 nLen,
MetricVector* pVector, OUString* pDisplayText
)
{
if(nLen == 0x0FFFF)
{
SAL_INFO("sal.rtl.xub",
"GetTextOutlines Suspicious arguments nLen:" << nLen);
}
if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
{
nLen = rStr.getLength() - nIndex;
}
if( mpOutDevData && mpOutDevData->mpRecordLayout )
{
pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects;
pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText;
}
#if OSL_DEBUG_LEVEL > 2
fprintf( stderr, " OutputDevice::DrawText(\"%s\")\n",
OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ).getStr() );
#endif
if ( mpMetaFile )
mpMetaFile->AddAction( new MetaTextAction( rStartPt, rStr, nIndex, nLen ) );
if( pVector )
{
Region aClip( GetClipRegion() );
if( meOutDevType == OUTDEV_WINDOW )
aClip.Intersect( Rectangle( Point(), GetOutputSize() ) );
if( mpOutDevData && mpOutDevData->mpRecordLayout )
{
mpOutDevData->mpRecordLayout->m_aLineIndices.push_back( mpOutDevData->mpRecordLayout->m_aDisplayText.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);
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
{
long nWidth = GetTextArray( rStr, NULL, nIndex, nLen );
return nWidth;
}
long OutputDevice::GetTextHeight() const
{
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,
sal_Int32 nIndex, sal_Int32 nLen )
{
if(nLen == 0x0FFFF)
{
SAL_INFO("sal.rtl.xub",
"DrawTextArray Suspicious arguments nLen:" << nLen);
}
if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
{
nLen = rStr.getLength() - nIndex;
}
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);
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
{
if(nLen == 0x0FFFF)
{
SAL_INFO("sal.rtl.xub",
"GetTextArray Suspicious arguments nLen:" << nLen);
}
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,
bool bCellBreaking ) const
{
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,
sal_Int32 nIndex, sal_Int32 nLen )
{
if(nIndex < 0 || nIndex == 0x0FFFF || nLen == 0x0FFFF)
{
SAL_INFO("sal.rtl.xub",
"DrawStretchText Suspicious arguments nIndex:" << nIndex << " nLen:" << nLen);
}
if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
{
nLen = rStr.getLength() - nIndex;
}
if ( mpMetaFile )
mpMetaFile->AddAction( new MetaStretchTextAction( rStartPt, nWidth, rStr, nIndex, nLen ) );
if ( !IsDeviceOutputNecessary() )
return;
SalLayout* pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, nWidth, NULL);
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
{
assert(nMinIndex >= 0);
assert(nLen >= 0);
// 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) const
{
// we need a graphics
if( !mpGraphics )
if( !ImplGetGraphics() )
return NULL;
// initialize font if needed
if( mbNewFont )
if( !ImplNewFont() )
return NULL;
if( mbInitFont )
InitFont();
// 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;
// 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 );
#if defined(MACOSX) || defined(IOS)
// 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<OutputDevice&>(*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() && 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;
}
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;
}
sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, long nTextWidth,
sal_Int32 nIndex, sal_Int32 nLen,
long nCharExtra ) const
{
SalLayout* pSalLayout = ImplLayout( rStr, nIndex, nLen );
sal_Int32 nRetVal = -1;
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 = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor );
pSalLayout->Release();
}
return nRetVal;
}
sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, long nTextWidth,
sal_Unicode nHyphenChar, sal_Int32& rHyphenPos,
sal_Int32 nIndex, sal_Int32 nLen,
long nCharExtra ) const
{
rHyphenPos = -1;
SalLayout* pSalLayout = ImplLayout( rStr, nIndex, nLen );
sal_Int32 nRetVal = -1;
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 );
}
// calculate un-hyphenated break position
nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor );
// calculate hyphenated break position
OUString aHyphenStr(nHyphenChar);
sal_Int32 nTempLen = 1;
SalLayout* pHyphenLayout = ImplLayout( aHyphenStr, 0, nTempLen );
if( pHyphenLayout )
{
// calculate subpixel width of hyphenation character
long nHyphenPixelWidth = pHyphenLayout->GetTextWidth() * nSubPixelFactor;
pHyphenLayout->Release();
// calculate hyphenated break position
nTextPixelWidth -= nHyphenPixelWidth;
if( nExtraPixelWidth > 0 )
nTextPixelWidth -= nExtraPixelWidth;
rHyphenPos = pSalLayout->GetTextBreak(nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor);
if( rHyphenPos > nRetVal )
rHyphenPos = nRetVal;
}
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 )
{
bool bHighContrastBlack = false;
bool bHighContrastWhite = false;
const StyleSettings& rStyleSettings( rTargetDevice.GetSettings().GetStyleSettings() );
if( rStyleSettings.GetHighContrastMode() )
{
Color aCol;
if( rTargetDevice.IsBackground() )
aCol = rTargetDevice.GetBackground().GetColor();
else
// best guess is the face color here
// but it may be totally wrong. the background color
// was typically already reset
aCol = rStyleSettings.GetFaceColor();
bHighContrastBlack = aCol.IsDark();
bHighContrastWhite = aCol.IsBright();
}
aOldTextColor = rTargetDevice.GetTextColor();
if ( rTargetDevice.IsTextFillColor() )
{
bRestoreFillColor = true;
aOldTextFillColor = rTargetDevice.GetTextFillColor();
}
if( bHighContrastBlack )
rTargetDevice.SetTextColor( COL_GREEN );
else if( bHighContrastWhite )
rTargetDevice.SetTextColor( COL_LIGHTGREEN );
else
{
// draw disabled text always without shadow
// as it fits better with native look
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;
sal_Int32 i;
sal_Int32 nLines;
sal_Int32 nFormatLines;
if ( nTextHeight )
{
long nMaxTextWidth = ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, _rLayout );
nLines = (sal_Int32)(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;
sal_Int32 nIndex = pLineInfo->GetIndex();
sal_Int32 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, aLastLine.getLength(), 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, aStr.getLength(), pVector, pDisplayText );
if ( bDrawMnemonics )
{
if ( nMnemonicPos != -1 )
rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
}
rTargetDevice.Pop();
}
else
{
_rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), 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 )
{
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 bool bOutputEnabled( IsOutputEnabled() );
GDIMetaFile* pMtf = mpMetaFile;
mpMetaFile = &rMtf;
EnableOutput( false );
// #i47157# Factored out to ImplDrawTextRect(), to be shared
// between us and DrawText()
DefaultTextLayout aLayout( *this );
ImplDrawText( *this, rRect, rOrigStr, nStyle, NULL, NULL, aLayout );
// and restore again
EnableOutput( bOutputEnabled );
mpMetaFile = pMtf;
}
void OutputDevice::DrawText( const Rectangle& rRect, const 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;
}
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
{
Rectangle aRect = rRect;
sal_Int32 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;
sal_Int32 nFormatLines;
sal_Int32 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 = 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 = 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
{
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 != -1 )
{
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,
sal_Int32 nIndex, sal_Int32 nLen,
sal_uInt16 nStyle, MetricVector* pVector, OUString* pDisplayText )
{
if(nLen == 0x0FFFF)
{
SAL_INFO("sal.rtl.xub",
"DrawCtrlText Suspicious arguments nLen:" << nLen);
}
if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
{
nLen = rStr.getLength() - nIndex;
}
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( (nLen < 0) || (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( 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;
bool bHighContrastBlack = false;
bool bHighContrastWhite = false;
const StyleSettings& rStyleSettings( GetSettings().GetStyleSettings() );
if( rStyleSettings.GetHighContrastMode() )
{
if( IsBackground() )
{
Wallpaper aWall = GetBackground();
Color aCol = aWall.GetColor();
bHighContrastBlack = aCol.IsDark();
bHighContrastWhite = aCol.IsBright();
}
}
aOldTextColor = GetTextColor();
if ( IsTextFillColor() )
{
bRestoreFillColor = true;
aOldTextFillColor = GetTextFillColor();
}
else
bRestoreFillColor = false;
if( bHighContrastBlack )
SetTextColor( COL_GREEN );
else if( bHighContrastWhite )
SetTextColor( COL_LIGHTGREEN );
else
SetTextColor( GetSettings().GetStyleSettings().GetDisableColor() );
DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText );
if ( !(GetSettings().GetStyleSettings().GetOptions() & STYLE_OPTION_NOMNEMONICS) && !pVector )
{
if ( nMnemonicPos != -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,
sal_Int32 nIndex, sal_Int32 nLen,
sal_uInt16 nStyle ) const
{
if(nLen == 0x0FFFF)
{
SAL_INFO("sal.rtl.xub",
"GetCtrlTextWidth Suspicious arguments nLen:" << nLen);
}
/* defensive code */
if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
{
nLen = rStr.getLength() - nIndex;
}
if ( nStyle & TEXT_DRAW_MNEMONIC )
{
sal_Int32 nMnemonicPos;
OUString aStr = GetNonMnemonicString( rStr, nMnemonicPos );
if ( nMnemonicPos != -1 )
{
if ( nMnemonicPos < nIndex )
nIndex--;
else if ( (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;
}
/** 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, sal_Int32 nIndex, sal_Int32 nLen,
const sal_Int32* pDXAry) const
{
if(nLen == 0x0FFFF)
{
SAL_INFO("sal.rtl.xub",
"GetSysTextLayoutData Suspicious arguments nLen:" << nLen);
}
if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
{
nLen = rStr.getLength() - nIndex;
}
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);
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<unsigned long> (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;
}
bool OutputDevice::GetTextBoundRect( Rectangle& rRect,
const OUString& rStr, sal_Int32 nBase,
sal_Int32 nIndex, sal_Int32 nLen,
sal_uLong nLayoutWidth, const sal_Int32* pDXAry ) const
{
if(nLen == 0x0FFFF)
{
SAL_INFO("sal.rtl.xub",
"GetTextBoundRect Suspicious arguments nLen:" << nLen);
}
bool bRet = false;
rRect.SetEmpty();
SalLayout* pSalLayout = NULL;
const Point aPoint;
// calculate offset when nBase!=nIndex
long nXOffset = 0;
if( nBase != nIndex )
{
sal_Int32 nStart = std::min( nBase, nIndex );
sal_Int32 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( false );
aFont.SetOutline( false );
aFont.SetRelief( RELIEF_NONE );
aFont.SetOrientation( 0 );
aFont.SetSize( Size( mpFontEntry->maFontSelData.mnWidth, mpFontEntry->maFontSelData.mnHeight ) );
aVDev.SetFont( aFont );
aVDev.SetTextAlign( ALIGN_TOP );
// layout the text on the virtual device
pSalLayout = aVDev.ImplLayout( rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry );
if( !pSalLayout )
return false;
// make the bitmap big enough
// TODO: use factors when it would get too big
long nWidth = pSalLayout->GetTextWidth();
long nHeight = mpFontEntry->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
Point aOffset( nWidth/2, 8 );
Size aOutSize( nWidth + 2*aOffset.X(), nHeight + 2*aOffset.Y() );
if( !nWidth || !aVDev.SetOutputSizePixel( aOutSize ) )
return false;
// draw text in black
pSalLayout->DrawBase() = aOffset;
aVDev.SetTextColor( Color( COL_BLACK ) );
aVDev.SetTextFillColor();
aVDev.ImplInitTextColor();
aVDev.ImplDrawText( *pSalLayout );
pSalLayout->Release();
// find extents using the bitmap
Bitmap aBmp = aVDev.GetBitmap( Point(), aOutSize );
BitmapReadAccess* pAcc = aBmp.AcquireReadAccess();
if( !pAcc )
return false;
const BitmapColor aBlack( pAcc->GetBestMatchingColor( Color( COL_BLACK ) ) );
const long nW = pAcc->Width();
const long nH = pAcc->Height();
long nLeft = 0;
long nRight = 0;
// find top left point
long nTop = 0;
for(; nTop < nH; ++nTop )
{
for( nLeft = 0; nLeft < nW; ++nLeft )
if( pAcc->GetPixel( nTop, nLeft ) == aBlack )
break;
if( nLeft < nW )
break;
}
// find bottom right point
long nBottom = nH;
while( --nBottom >= nTop )
{
for( nRight = nW; --nRight >= 0; )
if( pAcc->GetPixel( nBottom, nRight ) == aBlack )
break;
if( nRight >= 0 )
break;
}
if( nRight < nLeft )
{
long nX = nRight;
nRight = nLeft;
nLeft = nX;
}
for( long nY = nTop; nY <= nBottom; ++nY )
{
// find leftmost point
long nX;
for( nX = 0; nX < nLeft; ++nX )
if( pAcc->GetPixel( nY, nX ) == aBlack )
break;
nLeft = nX;
// find rightmost point
for( nX = nW; --nX > nRight; )
if( pAcc->GetPixel( nY, nX ) == aBlack )
break;
nRight = nX;
}
aBmp.ReleaseAccess( pAcc );
if( nTop <= nBottom )
{
Size aSize( nRight - nLeft + 1, nBottom - nTop + 1 );
Point aTopLeft( nLeft, nTop );
aTopLeft -= aOffset;
// adjust to text alignment
aTopLeft.Y()+= mnTextOffY - (mpFontEntry->maMetric.mnAscent + mnEmphasisAscent);
// convert to logical coordinates
aSize = PixelToLogic( aSize );
aTopLeft.X() = ImplDevicePixelToLogicWidth( aTopLeft.X() );
aTopLeft.Y() = ImplDevicePixelToLogicHeight( aTopLeft.Y() );
rRect = Rectangle( aTopLeft, aSize );
return true;
}
return false;
}
bool OutputDevice::GetTextOutlines( ::basegfx::B2DPolyPolygonVector& rVector,
const OUString& rStr, sal_Int32 nBase,
sal_Int32 nIndex, sal_Int32 nLen,
bool bOptimize, sal_uLong nLayoutWidth, const sal_Int32* pDXArray ) const
{
if(nLen == 0x0FFFF)
{
SAL_INFO("sal.rtl.xub",
"GetTextOutlines Suspicious arguments nLen:" << nLen);
}
// the fonts need to be initialized
if( mbNewFont )
ImplNewFont();
if( mbInitFont )
InitFont();
if( !mpFontEntry )
return false;
bool bRet = false;
rVector.clear();
if( nLen < 0 )
{
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
bool bOldMap = mbMap;
if( bOldMap )
{
const_cast<OutputDevice&>(*this).mbMap = false;
const_cast<OutputDevice&>(*this).mbNewFont = true;
}
SalLayout* pSalLayout = NULL;
// calculate offset when nBase!=nIndex
long nXOffset = 0;
if( nBase != nIndex )
{
sal_Int32 nStart = std::min( nBase, nIndex );
sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart;
pSalLayout = ImplLayout( rStr, nStart, nOfsLen, Point(0,0), nLayoutWidth, 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), nLayoutWidth, pDXArray );
if( pSalLayout )
{
bRet = pSalLayout->GetOutline( *mpGraphics, rVector );
if( bRet )
{
// transform polygon to pixel units
::basegfx::B2DHomMatrix aMatrix;
int nWidthFactor = pSalLayout->GetUnitsPerPixel();
if( nXOffset | mnTextOffX | mnTextOffY )
{
Point aRotatedOfs( mnTextOffX*nWidthFactor, mnTextOffY*nWidthFactor );
aRotatedOfs -= pSalLayout->GetDrawPosition( Point( nXOffset, 0 ) );
aMatrix.translate( aRotatedOfs.X(), aRotatedOfs.Y() );
}
if( nWidthFactor > 1 )
{
double fFactor = 1.0 / nWidthFactor;
aMatrix.scale( fFactor, fFactor );
}
if( !aMatrix.isIdentity() )
{
::basegfx::B2DPolyPolygonVector::iterator aIt = rVector.begin();
for(; aIt != rVector.end(); ++aIt )
(*aIt).transform( aMatrix );
}
}
pSalLayout->Release();
}
if( bOldMap )
{
// restore original font size and map mode
const_cast<OutputDevice&>(*this).mbMap = bOldMap;
const_cast<OutputDevice&>(*this).mbNewFont = true;
}
if( bRet || (OUTDEV_PRINTER == meOutDevType) || !mpFontEntry )
return bRet;
// fall back to bitmap conversion
// Here, we can savely assume that the mapping between characters and glyphs
// is one-to-one. This is most probably valid for the old bitmap fonts.
// fall back to bitmap method to get the bounding rectangle,
// so we need a monochrome virtual device with matching font
pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), nLayoutWidth, 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), nLayoutWidth, pDXArray );
if (pSalLayout == 0)
return false;
long nWidth = pSalLayout->GetTextWidth();
long nHeight = ((OutputDevice*)&aVDev)->mpFontEntry->mnLineHeight + ((OutputDevice*)&aVDev)->mnEmphasisAscent
+ ((OutputDevice*)&aVDev)->mnEmphasisDescent;
pSalLayout->Release();
if( !nWidth || !nHeight )
return true;
double fScaleX = static_cast< double >(nOrgWidth) / nWidth;
double fScaleY = static_cast< double >(nOrgHeight) / nHeight;
// calculate offset when nBase!=nIndex
// TODO: fix offset calculation for Bidi case
nXOffset = 0;
if( nBase != nIndex )
{
sal_Int32 nStart = ((nBase < nIndex) ? nBase : nIndex);
sal_Int32 nLength = ((nBase > nIndex) ? nBase : nIndex) - nStart;
pSalLayout = aVDev.ImplLayout( rStr, nStart, nLength, Point(0,0), nLayoutWidth, 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, nCharPos, 1, Point(0,0), nLayoutWidth, 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;
}
bool OutputDevice::GetTextOutlines( PolyPolyVector& rResultVector,
const OUString& rStr, sal_Int32 nBase,
sal_Int32 nIndex, sal_Int32 nLen, bool bOptimize,
sal_uLong nTWidth, const sal_Int32* pDXArray ) const
{
if(nLen == 0x0FFFF)
{
SAL_INFO("sal.rtl.xub",
"GetTextOutlines Suspicious arguments nLen:" << nLen);
}
rResultVector.clear();
// get the basegfx polypolygon vector
::basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
if( !GetTextOutlines( aB2DPolyPolyVector, rStr, nBase, nIndex, nLen,
bOptimize, nTWidth, pDXArray ) )
return false;
// convert to a tool polypolygon vector
rResultVector.reserve( aB2DPolyPolyVector.size() );
::basegfx::B2DPolyPolygonVector::const_iterator aIt = aB2DPolyPolyVector.begin();
for(; aIt != aB2DPolyPolyVector.end(); ++aIt )
rResultVector.push_back(PolyPolygon(*aIt)); // #i76339#
return true;
}
bool OutputDevice::GetTextOutline( PolyPolygon& rPolyPoly, const OUString& rStr,
sal_Int32 nBase, sal_Int32 nIndex, sal_Int32 nLen,
bool bOptimize, sal_uLong nTWidth, const sal_Int32* pDXArray ) const
{
if(nLen == 0x0FFFF)
{
SAL_INFO("sal.rtl.xub",
"GetTextOutline Suspicious arguments nLen:" << nLen);
}
rPolyPoly.Clear();
// get the basegfx polypolygon vector
::basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
if( !GetTextOutlines( aB2DPolyPolyVector, rStr, nBase, nIndex, nLen,
bOptimize, nTWidth, pDXArray ) )
return false;
// convert and merge into a tool polypolygon
::basegfx::B2DPolyPolygonVector::const_iterator aIt = aB2DPolyPolyVector.begin();
for(; aIt != aB2DPolyPolyVector.end(); ++aIt )
for( unsigned int i = 0; i < aIt->count(); ++i )
rPolyPoly.Insert(Polygon((*aIt).getB2DPolygon( i ))); // #i76339#
return true;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */