1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <basegfx/matrix/b2dhommatrix.hxx>
23 #include <com/sun/star/i18n/WordType.hpp>
24 #include <com/sun/star/i18n/XBreakIterator.hpp>
25 #include <com/sun/star/linguistic2/LinguServiceManager.hpp>
27 #include <comphelper/processfactory.hxx>
29 #include <sal/log.hxx>
30 #include <tools/lineend.hxx>
31 #include <tools/debug.hxx>
32 #include <vcl/gdimtf.hxx>
33 #include <vcl/metaact.hxx>
34 #include <vcl/metric.hxx>
35 #include <vcl/textrectinfo.hxx>
36 #include <vcl/virdev.hxx>
37 #include <vcl/sysdata.hxx>
38 #include <vcl/unohelp.hxx>
39 #include <vcl/controllayout.hxx>
41 # include <vcl/opengl/OpenGLHelper.hxx>
44 #include <outdata.hxx>
48 #include <textlayout.hxx>
49 #include <textlineinfo.hxx>
50 #include <impglyphitem.hxx>
51 #include <boost/optional.hpp>
53 #define TEXT_DRAW_ELLIPSIS (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis)
55 ImplMultiTextLineInfo::ImplMultiTextLineInfo()
59 ImplMultiTextLineInfo::~ImplMultiTextLineInfo()
63 void ImplMultiTextLineInfo::AddLine( ImplTextLineInfo
* pLine
)
65 mvLines
.push_back(std::unique_ptr
<ImplTextLineInfo
>(pLine
));
68 void ImplMultiTextLineInfo::Clear()
73 void OutputDevice::ImplInitTextColor()
77 if ( mbInitTextColor
)
79 mpGraphics
->SetTextColor( GetTextColor() );
80 mbInitTextColor
= false;
84 void OutputDevice::ImplDrawTextRect( long nBaseX
, long nBaseY
,
85 long nDistX
, long nDistY
, long nWidth
, long nHeight
)
90 short nOrientation
= mpFontInstance
->mnOrientation
;
93 // Rotate rect without rounding problems for 90 degree rotations
94 if ( !(nOrientation
% 900) )
96 if ( nOrientation
== 900 )
106 else if ( nOrientation
== 1800 )
113 else /* ( nOrientation == 2700 ) */
128 // inflate because polygons are drawn smaller
129 tools::Rectangle
aRect( Point( nX
, nY
), Size( nWidth
+1, nHeight
+1 ) );
130 tools::Polygon
aPoly( aRect
);
131 aPoly
.Rotate( Point( nBaseX
, nBaseY
), mpFontInstance
->mnOrientation
);
132 ImplDrawPolygon( aPoly
);
139 mpGraphics
->DrawRect( nX
, nY
, nWidth
, nHeight
, this ); // original code
143 void OutputDevice::ImplDrawTextBackground( const SalLayout
& rSalLayout
)
145 const long nWidth
= rSalLayout
.GetTextWidth() / rSalLayout
.GetUnitsPerPixel();
146 const Point aBase
= rSalLayout
.DrawBase();
147 const long nX
= aBase
.X();
148 const long nY
= aBase
.Y();
150 if ( mbLineColor
|| mbInitLineColor
)
152 mpGraphics
->SetLineColor();
153 mbInitLineColor
= true;
155 mpGraphics
->SetFillColor( GetTextFillColor() );
156 mbInitFillColor
= true;
158 ImplDrawTextRect( nX
, nY
, 0, -(mpFontInstance
->mxFontMetric
->GetAscent() + mnEmphasisAscent
),
160 mpFontInstance
->mnLineHeight
+mnEmphasisAscent
+mnEmphasisDescent
);
163 tools::Rectangle
OutputDevice::ImplGetTextBoundRect( const SalLayout
& rSalLayout
)
165 Point aPoint
= rSalLayout
.GetDrawPosition();
166 long nX
= aPoint
.X();
167 long nY
= aPoint
.Y();
169 long nWidth
= rSalLayout
.GetTextWidth();
170 long nHeight
= mpFontInstance
->mnLineHeight
+ mnEmphasisAscent
+ mnEmphasisDescent
;
172 nY
-= mpFontInstance
->mxFontMetric
->GetAscent() + mnEmphasisAscent
;
174 if ( mpFontInstance
->mnOrientation
)
176 long nBaseX
= nX
, nBaseY
= nY
;
177 if ( !(mpFontInstance
->mnOrientation
% 900) )
179 long nX2
= nX
+nWidth
;
180 long nY2
= nY
+nHeight
;
182 Point
aBasePt( nBaseX
, nBaseY
);
183 aBasePt
.RotateAround( nX
, nY
, mpFontInstance
->mnOrientation
);
184 aBasePt
.RotateAround( nX2
, nY2
, mpFontInstance
->mnOrientation
);
190 // inflate by +1+1 because polygons are drawn smaller
191 tools::Rectangle
aRect( Point( nX
, nY
), Size( nWidth
+1, nHeight
+1 ) );
192 tools::Polygon
aPoly( aRect
);
193 aPoly
.Rotate( Point( nBaseX
, nBaseY
), mpFontInstance
->mnOrientation
);
194 return aPoly
.GetBoundRect();
198 return tools::Rectangle( Point( nX
, nY
), Size( nWidth
, nHeight
) );
201 bool OutputDevice::ImplDrawRotateText( SalLayout
& rSalLayout
)
203 long nX
= rSalLayout
.DrawBase().X();
204 long nY
= rSalLayout
.DrawBase().Y();
206 tools::Rectangle aBoundRect
;
207 rSalLayout
.DrawBase() = Point( 0, 0 );
208 rSalLayout
.DrawOffset() = Point( 0, 0 );
209 if (!rSalLayout
.GetBoundRect(aBoundRect
))
211 // guess vertical text extents if GetBoundRect failed
212 long nRight
= rSalLayout
.GetTextWidth();
213 long nTop
= mpFontInstance
->mxFontMetric
->GetAscent() + mnEmphasisAscent
;
214 long nHeight
= mpFontInstance
->mnLineHeight
+ mnEmphasisAscent
+ mnEmphasisDescent
;
215 aBoundRect
= tools::Rectangle( 0, -nTop
, nRight
, nHeight
- nTop
);
218 // cache virtual device for rotation
219 if (!mpOutDevData
->mpRotateDev
)
220 mpOutDevData
->mpRotateDev
= VclPtr
<VirtualDevice
>::Create(*this, DeviceFormat::BITMASK
);
221 VirtualDevice
* pVDev
= mpOutDevData
->mpRotateDev
;
223 // size it accordingly
224 if( !pVDev
->SetOutputSizePixel( aBoundRect
.GetSize() ) )
227 const FontSelectPattern
& rPattern
= mpFontInstance
->GetFontSelectPattern();
228 vcl::Font
aFont( GetFont() );
229 aFont
.SetOrientation( 0 );
230 aFont
.SetFontSize( Size( rPattern
.mnWidth
, rPattern
.mnHeight
) );
231 pVDev
->SetFont( aFont
);
232 pVDev
->SetTextColor( COL_BLACK
);
233 pVDev
->SetTextFillColor();
234 if (!pVDev
->InitFont())
236 pVDev
->ImplInitTextColor();
238 // draw text into upper left corner
239 rSalLayout
.DrawBase() -= aBoundRect
.TopLeft();
240 rSalLayout
.DrawText( *pVDev
->mpGraphics
);
242 Bitmap aBmp
= pVDev
->GetBitmap( Point(), aBoundRect
.GetSize() );
243 if ( !aBmp
|| !aBmp
.Rotate( mpFontInstance
->mnOwnOrientation
, COL_WHITE
) )
246 // calculate rotation offset
247 tools::Polygon
aPoly( aBoundRect
);
248 aPoly
.Rotate( Point(), mpFontInstance
->mnOwnOrientation
);
249 Point aPoint
= aPoly
.GetBoundRect().TopLeft();
250 aPoint
+= Point( nX
, nY
);
252 // mask output with text colored bitmap
253 GDIMetaFile
* pOldMetaFile
= mpMetaFile
;
254 long nOldOffX
= mnOutOffX
;
255 long nOldOffY
= mnOutOffY
;
256 bool bOldMap
= mbMap
;
260 mpMetaFile
= nullptr;
261 EnableMapMode( false );
263 DrawMask( aPoint
, aBmp
, GetTextColor() );
265 EnableMapMode( bOldMap
);
266 mnOutOffX
= nOldOffX
;
267 mnOutOffY
= nOldOffY
;
268 mpMetaFile
= pOldMetaFile
;
273 void OutputDevice::ImplDrawTextDirect( SalLayout
& rSalLayout
,
276 if( mpFontInstance
->mnOwnOrientation
)
277 if( ImplDrawRotateText( rSalLayout
) )
280 long nOldX
= rSalLayout
.DrawBase().X();
281 if( HasMirroredGraphics() )
283 long w
= IsVirtual() ? mnOutWidth
: mpGraphics
->GetGraphicsWidth();
284 long x
= rSalLayout
.DrawBase().X();
285 rSalLayout
.DrawBase().setX( w
- 1 - x
);
286 if( !IsRTLEnabled() )
288 OutputDevice
*pOutDevRef
= this;
289 // mirror this window back
290 long devX
= w
-pOutDevRef
->mnOutWidth
-pOutDevRef
->mnOutOffX
; // re-mirrored mnOutOffX
291 rSalLayout
.DrawBase().setX( devX
+ ( pOutDevRef
->mnOutWidth
- 1 - (rSalLayout
.DrawBase().X() - devX
) ) ) ;
294 else if( IsRTLEnabled() )
296 OutputDevice
*pOutDevRef
= this;
298 // mirror this window back
299 long devX
= pOutDevRef
->mnOutOffX
; // re-mirrored mnOutOffX
300 rSalLayout
.DrawBase().setX( pOutDevRef
->mnOutWidth
- 1 - (rSalLayout
.DrawBase().X() - devX
) + devX
);
303 rSalLayout
.DrawText( *mpGraphics
);
304 rSalLayout
.DrawBase().setX( nOldX
);
307 ImplDrawTextLines( rSalLayout
,
308 maFont
.GetStrikeout(), maFont
.GetUnderline(), maFont
.GetOverline(),
309 maFont
.IsWordLineMode(), maFont
.IsUnderlineAbove() );
312 if( maFont
.GetEmphasisMark() & FontEmphasisMark::Style
)
313 ImplDrawEmphasisMarks( rSalLayout
);
316 void OutputDevice::ImplDrawSpecialText( SalLayout
& rSalLayout
)
318 Color aOldColor
= GetTextColor();
319 Color aOldTextLineColor
= GetTextLineColor();
320 Color aOldOverlineColor
= GetOverlineColor();
321 FontRelief eRelief
= maFont
.GetRelief();
323 Point aOrigPos
= rSalLayout
.DrawBase();
324 if ( eRelief
!= FontRelief::NONE
)
326 Color
aReliefColor( COL_LIGHTGRAY
);
327 Color
aTextColor( aOldColor
);
329 Color
aTextLineColor( aOldTextLineColor
);
330 Color
aOverlineColor( aOldOverlineColor
);
332 // we don't have an automatic color, so black is always drawn on white
333 if ( aTextColor
== COL_BLACK
)
334 aTextColor
= COL_WHITE
;
335 if ( aTextLineColor
== COL_BLACK
)
336 aTextLineColor
= COL_WHITE
;
337 if ( aOverlineColor
== COL_BLACK
)
338 aOverlineColor
= COL_WHITE
;
340 // relief-color is black for white text, in all other cases
341 // we set this to LightGray
342 if ( aTextColor
== COL_WHITE
)
343 aReliefColor
= COL_BLACK
;
344 SetTextLineColor( aReliefColor
);
345 SetOverlineColor( aReliefColor
);
346 SetTextColor( aReliefColor
);
349 // calculate offset - for high resolution printers the offset
350 // should be greater so that the effect is visible
354 if ( eRelief
== FontRelief::Engraved
)
356 rSalLayout
.DrawOffset() += Point( nOff
, nOff
);
357 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
358 rSalLayout
.DrawOffset() -= Point( nOff
, nOff
);
360 SetTextLineColor( aTextLineColor
);
361 SetOverlineColor( aOverlineColor
);
362 SetTextColor( aTextColor
);
364 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
366 SetTextLineColor( aOldTextLineColor
);
367 SetOverlineColor( aOldOverlineColor
);
369 if ( aTextColor
!= aOldColor
)
371 SetTextColor( aOldColor
);
377 if ( maFont
.IsShadow() )
379 long nOff
= 1 + ((mpFontInstance
->mnLineHeight
-24)/24);
380 if ( maFont
.IsOutline() )
384 if ( (GetTextColor() == COL_BLACK
)
385 || (GetTextColor().GetLuminance() < 8) )
386 SetTextColor( COL_LIGHTGRAY
);
388 SetTextColor( COL_BLACK
);
390 rSalLayout
.DrawBase() += Point( nOff
, nOff
);
391 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
392 rSalLayout
.DrawBase() -= Point( nOff
, nOff
);
393 SetTextColor( aOldColor
);
394 SetTextLineColor( aOldTextLineColor
);
395 SetOverlineColor( aOldOverlineColor
);
398 if ( !maFont
.IsOutline() )
399 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
402 if ( maFont
.IsOutline() )
404 rSalLayout
.DrawBase() = aOrigPos
+ Point(-1,-1);
405 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
406 rSalLayout
.DrawBase() = aOrigPos
+ Point(+1,+1);
407 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
408 rSalLayout
.DrawBase() = aOrigPos
+ Point(-1,+0);
409 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
410 rSalLayout
.DrawBase() = aOrigPos
+ Point(-1,+1);
411 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
412 rSalLayout
.DrawBase() = aOrigPos
+ Point(+0,+1);
413 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
414 rSalLayout
.DrawBase() = aOrigPos
+ Point(+0,-1);
415 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
416 rSalLayout
.DrawBase() = aOrigPos
+ Point(+1,-1);
417 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
418 rSalLayout
.DrawBase() = aOrigPos
+ Point(+1,+0);
419 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
420 rSalLayout
.DrawBase() = aOrigPos
;
422 SetTextColor( COL_WHITE
);
423 SetTextLineColor( COL_WHITE
);
424 SetOverlineColor( COL_WHITE
);
426 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
427 SetTextColor( aOldColor
);
428 SetTextLineColor( aOldTextLineColor
);
429 SetOverlineColor( aOldOverlineColor
);
435 void OutputDevice::ImplDrawText( SalLayout
& rSalLayout
)
438 if( mbInitClipRegion
)
440 if( mbOutputClipped
)
442 if( mbInitTextColor
)
445 rSalLayout
.DrawBase() += Point( mnTextOffX
, mnTextOffY
);
447 if( IsTextFillColor() )
448 ImplDrawTextBackground( rSalLayout
);
451 ImplDrawSpecialText( rSalLayout
);
453 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
456 long OutputDevice::ImplGetTextLines( ImplMultiTextLineInfo
& rLineInfo
,
457 long nWidth
, const OUString
& rStr
,
458 DrawTextFlags nStyle
, const vcl::ITextLayout
& _rLayout
)
460 SAL_WARN_IF( nWidth
<= 0, "vcl", "ImplGetTextLines: nWidth <= 0!" );
465 long nMaxLineWidth
= 0;
469 const bool bHyphenate
= (nStyle
& DrawTextFlags::WordBreakHyphenation
) == DrawTextFlags::WordBreakHyphenation
;
470 css::uno::Reference
< css::linguistic2::XHyphenator
> xHyph
;
473 // get service provider
474 css::uno::Reference
<css::uno::XComponentContext
> xContext(comphelper::getProcessComponentContext());
475 css::uno::Reference
<css::linguistic2::XLinguServiceManager2
> xLinguMgr
= css::linguistic2::LinguServiceManager::create(xContext
);
476 xHyph
= xLinguMgr
->getHyphenator();
479 css::uno::Reference
<css::i18n::XBreakIterator
> xBI
;
481 sal_Int32 nLen
= rStr
.getLength();
482 while ( nPos
< nLen
)
484 sal_Int32 nBreakPos
= nPos
;
486 while ( ( nBreakPos
< nLen
) && ( rStr
[ nBreakPos
] != '\r' ) && ( rStr
[ nBreakPos
] != '\n' ) )
489 long nLineWidth
= _rLayout
.GetTextWidth( rStr
, nPos
, nBreakPos
-nPos
);
490 if ( ( nLineWidth
> nWidth
) && ( nStyle
& DrawTextFlags::WordBreak
) )
493 xBI
= vcl::unohelper::CreateBreakIterator();
497 const css::lang::Locale
& rDefLocale(Application::GetSettings().GetUILanguageTag().getLocale());
498 sal_Int32 nSoftBreak
= _rLayout
.GetTextBreak( rStr
, nWidth
, nPos
, nBreakPos
- nPos
);
499 if (nSoftBreak
== -1)
503 SAL_WARN_IF( nSoftBreak
>= nBreakPos
, "vcl", "Break?!" );
504 css::i18n::LineBreakHyphenationOptions
aHyphOptions( xHyph
, css::uno::Sequence
<css::beans::PropertyValue
>(), 1 );
505 css::i18n::LineBreakUserOptions aUserOptions
;
506 css::i18n::LineBreakResults aLBR
= xBI
->getLineBreak( rStr
, nSoftBreak
, rDefLocale
, nPos
, aHyphOptions
, aUserOptions
);
507 nBreakPos
= aLBR
.breakIndex
;
508 if ( nBreakPos
<= nPos
)
509 nBreakPos
= nSoftBreak
;
512 // Whether hyphen or not: Put the word after the hyphen through
515 // nMaxBreakPos the last char that fits into the line
516 // nBreakPos is the word's start
518 // We run into a problem if the doc is so narrow, that a word
519 // is broken into more than two lines ...
522 sal_Unicode cAlternateReplChar
= 0;
523 css::i18n::Boundary aBoundary
= xBI
->getWordBoundary( rStr
, nBreakPos
, rDefLocale
, css::i18n::WordType::DICTIONARY_WORD
, true );
524 sal_Int32 nWordStart
= nPos
;
525 sal_Int32 nWordEnd
= aBoundary
.endPos
;
526 SAL_WARN_IF( nWordEnd
<= nWordStart
, "vcl", "ImpBreakLine: Start >= End?" );
528 sal_Int32 nWordLen
= nWordEnd
- nWordStart
;
529 if ( ( nWordEnd
>= nSoftBreak
) && ( nWordLen
> 3 ) )
531 // #104415# May happen, because getLineBreak may differ from getWordBoudary with DICTIONARY_WORD
532 // SAL_WARN_IF( nWordEnd < nMaxBreakPos, "vcl", "Hyph: Break?" );
533 OUString aWord
= rStr
.copy( nWordStart
, nWordLen
);
534 sal_Int32 nMinTrail
= nWordEnd
-nSoftBreak
+1; //+1: Before the "broken off" char
535 css::uno::Reference
< css::linguistic2::XHyphenatedWord
> xHyphWord
;
537 xHyphWord
= xHyph
->hyphenate( aWord
, rDefLocale
, aWord
.getLength() - nMinTrail
, css::uno::Sequence
< css::beans::PropertyValue
>() );
540 bool bAlternate
= xHyphWord
->isAlternativeSpelling();
541 sal_Int32 _nWordLen
= 1 + xHyphWord
->getHyphenPos();
543 if ( ( _nWordLen
>= 2 ) && ( (nWordStart
+_nWordLen
) >= 2 ) )
547 nBreakPos
= nWordStart
+ _nWordLen
;
551 OUString
aAlt( xHyphWord
->getHyphenatedWord() );
553 // We can have two cases:
554 // 1) "packen" turns into "pak-ken"
555 // 2) "Schiffahrt" turns into "Schiff-fahrt"
557 // In case 1 we need to replace a char
558 // In case 2 we add a char
560 // Correct recognition is made harder by words such as
561 // "Schiffahrtsbrennesseln", as the Hyphenator splits all
562 // positions of the word and comes up with "Schifffahrtsbrennnesseln"
563 // Thus, we cannot infer the aWord from the AlternativeWord's
565 // TODO: The whole junk will be made easier by a function in
566 // the Hyphenator, as soon as AMA adds it.
567 sal_Int32 nAltStart
= _nWordLen
- 1;
568 sal_Int32 nTxtStart
= nAltStart
- (aAlt
.getLength() - aWord
.getLength());
569 sal_Int32 nTxtEnd
= nTxtStart
;
570 sal_Int32 nAltEnd
= nAltStart
;
572 // The area between nStart and nEnd is the difference
573 // between AlternativeString and OriginalString
574 while( nTxtEnd
< aWord
.getLength() && nAltEnd
< aAlt
.getLength() &&
575 aWord
[nTxtEnd
] != aAlt
[nAltEnd
] )
581 // If a char was added, we notice it now:
582 if( nAltEnd
> nTxtEnd
&& nAltStart
== nAltEnd
&&
583 aWord
[ nTxtEnd
] == aAlt
[nAltEnd
] )
590 SAL_WARN_IF( ( nAltEnd
- nAltStart
) != 1, "vcl", "Alternate: Wrong assumption!" );
592 if ( nTxtEnd
> nTxtStart
)
593 cAlternateReplChar
= aAlt
[ nAltStart
];
595 nBreakPos
= nWordStart
+ nTxtStart
;
596 if ( cAlternateReplChar
)
604 nLineWidth
= _rLayout
.GetTextWidth( rStr
, nPos
, nBreakPos
-nPos
);
608 // fallback to something really simple
609 sal_Int32 nSpacePos
= rStr
.getLength();
613 nSpacePos
= rStr
.lastIndexOf( ' ', nSpacePos
);
614 if( nSpacePos
!= -1 )
616 if( nSpacePos
> nPos
)
618 nW
= _rLayout
.GetTextWidth( rStr
, nPos
, nSpacePos
-nPos
);
620 } while( nW
> nWidth
);
622 if( nSpacePos
!= -1 )
624 nBreakPos
= nSpacePos
;
625 nLineWidth
= _rLayout
.GetTextWidth( rStr
, nPos
, nBreakPos
-nPos
);
626 if( nBreakPos
< rStr
.getLength()-1 )
632 if ( nLineWidth
> nMaxLineWidth
)
633 nMaxLineWidth
= nLineWidth
;
635 rLineInfo
.AddLine( new ImplTextLineInfo( nLineWidth
, nPos
, nBreakPos
-nPos
) );
637 if ( nBreakPos
== nPos
)
641 if ( nPos
< nLen
&& ( ( rStr
[ nPos
] == '\r' ) || ( rStr
[ nPos
] == '\n' ) ) )
645 if ( ( nPos
< nLen
) && ( rStr
[ nPos
] == '\n' ) && ( rStr
[ nPos
-1 ] == '\r' ) )
651 for ( sal_Int32 nL
= 0; nL
< rLineInfo
.Count(); nL
++ )
653 ImplTextLineInfo
* pLine
= rLineInfo
.GetLine( nL
);
654 OUString aLine
= rStr
.copy( pLine
->GetIndex(), pLine
->GetLen() );
655 SAL_WARN_IF( aLine
.indexOf( '\r' ) != -1, "vcl", "ImplGetTextLines - Found CR!" );
656 SAL_WARN_IF( aLine
.indexOf( '\n' ) != -1, "vcl", "ImplGetTextLines - Found LF!" );
660 return nMaxLineWidth
;
663 void OutputDevice::SetTextColor( const Color
& rColor
)
666 Color
aColor( rColor
);
668 if ( mnDrawMode
& ( DrawModeFlags::BlackText
| DrawModeFlags::WhiteText
|
669 DrawModeFlags::GrayText
|
670 DrawModeFlags::SettingsText
) )
672 if ( mnDrawMode
& DrawModeFlags::BlackText
)
674 else if ( mnDrawMode
& DrawModeFlags::WhiteText
)
676 else if ( mnDrawMode
& DrawModeFlags::GrayText
)
678 const sal_uInt8 cLum
= aColor
.GetLuminance();
679 aColor
= Color( cLum
, cLum
, cLum
);
681 else if ( mnDrawMode
& DrawModeFlags::SettingsText
)
682 aColor
= GetSettings().GetStyleSettings().GetFontColor();
686 mpMetaFile
->AddAction( new MetaTextColorAction( aColor
) );
688 if ( maTextColor
!= aColor
)
690 maTextColor
= aColor
;
691 mbInitTextColor
= true;
695 mpAlphaVDev
->SetTextColor( COL_BLACK
);
698 void OutputDevice::SetTextFillColor()
702 mpMetaFile
->AddAction( new MetaTextFillColorAction( Color(), false ) );
704 if ( maFont
.GetColor() != COL_TRANSPARENT
) {
705 maFont
.SetFillColor( COL_TRANSPARENT
);
707 if ( !maFont
.IsTransparent() )
708 maFont
.SetTransparent( true );
711 mpAlphaVDev
->SetTextFillColor();
714 void OutputDevice::SetTextFillColor( const Color
& rColor
)
716 Color
aColor( rColor
);
717 bool bTransFill
= ImplIsColorTransparent( aColor
);
721 if ( mnDrawMode
& ( DrawModeFlags::BlackFill
| DrawModeFlags::WhiteFill
|
722 DrawModeFlags::GrayFill
| DrawModeFlags::NoFill
|
723 DrawModeFlags::SettingsFill
) )
725 if ( mnDrawMode
& DrawModeFlags::BlackFill
)
727 else if ( mnDrawMode
& DrawModeFlags::WhiteFill
)
729 else if ( mnDrawMode
& DrawModeFlags::GrayFill
)
731 const sal_uInt8 cLum
= aColor
.GetLuminance();
732 aColor
= Color( cLum
, cLum
, cLum
);
734 else if( mnDrawMode
& DrawModeFlags::SettingsFill
)
735 aColor
= GetSettings().GetStyleSettings().GetWindowColor();
736 else if ( mnDrawMode
& DrawModeFlags::NoFill
)
738 aColor
= COL_TRANSPARENT
;
745 mpMetaFile
->AddAction( new MetaTextFillColorAction( aColor
, true ) );
747 if ( maFont
.GetFillColor() != aColor
)
748 maFont
.SetFillColor( aColor
);
749 if ( maFont
.IsTransparent() != bTransFill
)
750 maFont
.SetTransparent( bTransFill
);
753 mpAlphaVDev
->SetTextFillColor( COL_BLACK
);
756 Color
OutputDevice::GetTextFillColor() const
758 if ( maFont
.IsTransparent() )
759 return COL_TRANSPARENT
;
761 return maFont
.GetFillColor();
764 void OutputDevice::SetTextAlign( TextAlign eAlign
)
768 mpMetaFile
->AddAction( new MetaTextAlignAction( eAlign
) );
770 if ( maFont
.GetAlignment() != eAlign
)
772 maFont
.SetAlignment( eAlign
);
777 mpAlphaVDev
->SetTextAlign( eAlign
);
780 void OutputDevice::DrawText( const Point
& rStartPt
, const OUString
& rStr
,
781 sal_Int32 nIndex
, sal_Int32 nLen
,
782 MetricVector
* pVector
, OUString
* pDisplayText
,
783 const SalLayoutGlyphs
* pLayoutCache
786 assert(!is_double_buffered_window());
788 if( (nLen
< 0) || (nIndex
+ nLen
>= rStr
.getLength()))
790 nLen
= rStr
.getLength() - nIndex
;
793 if (mpOutDevData
->mpRecordLayout
)
795 pVector
= &mpOutDevData
->mpRecordLayout
->m_aUnicodeBoundRects
;
796 pDisplayText
= &mpOutDevData
->mpRecordLayout
->m_aDisplayText
;
799 #if OSL_DEBUG_LEVEL > 2
800 SAL_INFO("vcl.gdi", "OutputDevice::DrawText(\"" << rStr
<< "\")");
804 mpMetaFile
->AddAction( new MetaTextAction( rStartPt
, rStr
, nIndex
, nLen
) );
807 vcl::Region
aClip( GetClipRegion() );
808 if( meOutDevType
== OUTDEV_WINDOW
)
809 aClip
.Intersect( tools::Rectangle( Point(), GetOutputSize() ) );
810 if (mpOutDevData
->mpRecordLayout
)
812 mpOutDevData
->mpRecordLayout
->m_aLineIndices
.push_back( mpOutDevData
->mpRecordLayout
->m_aDisplayText
.getLength() );
813 aClip
.Intersect( mpOutDevData
->maRecordRect
);
815 if( ! aClip
.IsNull() )
818 GetGlyphBoundRects( rStartPt
, rStr
, nIndex
, nLen
, aTmp
);
820 bool bInserted
= false;
821 for( MetricVector::const_iterator it
= aTmp
.begin(); it
!= aTmp
.end(); ++it
, nIndex
++ )
823 bool bAppend
= false;
825 if( aClip
.IsOver( *it
) )
827 else if( rStr
[ nIndex
] == ' ' && bInserted
)
829 MetricVector::const_iterator next
= it
;
831 if( next
!= aTmp
.end() && aClip
.IsOver( *next
) )
837 pVector
->push_back( *it
);
839 *pDisplayText
+= OUStringChar(rStr
[ nIndex
]);
846 GetGlyphBoundRects( rStartPt
, rStr
, nIndex
, nLen
, *pVector
);
848 *pDisplayText
+= rStr
.copy( nIndex
, nLen
);
852 if ( !IsDeviceOutputNecessary() || pVector
)
856 // do not use cache with modified string
857 if(mpFontInstance
->mpConversion
)
858 pLayoutCache
= nullptr;
862 // Cache text layout crashes on mac with OpenGL enabled
863 // Force it to not use the cache
864 if(OpenGLHelper::isVCLOpenGLEnabled())
865 pLayoutCache
= nullptr;
868 std::unique_ptr
<SalLayout
> pSalLayout
= ImplLayout(rStr
, nIndex
, nLen
, rStartPt
, 0, nullptr, SalLayoutFlags::NONE
, nullptr, pLayoutCache
);
871 ImplDrawText( *pSalLayout
);
875 mpAlphaVDev
->DrawText( rStartPt
, rStr
, nIndex
, nLen
, pVector
, pDisplayText
);
878 long OutputDevice::GetTextWidth( const OUString
& rStr
, sal_Int32 nIndex
, sal_Int32 nLen
,
879 vcl::TextLayoutCache
const*const pLayoutCache
,
880 SalLayoutGlyphs
const*const pSalLayoutCache
) const
883 long nWidth
= GetTextArray( rStr
, nullptr, nIndex
,
884 nLen
, pLayoutCache
, pSalLayoutCache
);
889 long OutputDevice::GetTextHeight() const
894 long nHeight
= mpFontInstance
->mnLineHeight
+ mnEmphasisAscent
+ mnEmphasisDescent
;
897 nHeight
= ImplDevicePixelToLogicHeight( nHeight
);
902 float OutputDevice::approximate_char_width() const
904 //note pango uses "The quick brown fox jumps over the lazy dog." for english
905 //and has a bunch of per-language strings which corresponds somewhat with
906 //makeRepresentativeText in include/svtools/sampletext.hxx
907 return GetTextWidth("aemnnxEM") / 8.0;
910 float OutputDevice::approximate_digit_width() const
912 return GetTextWidth("0123456789") / 10.0;
915 void OutputDevice::DrawTextArray( const Point
& rStartPt
, const OUString
& rStr
,
917 sal_Int32 nIndex
, sal_Int32 nLen
, SalLayoutFlags flags
,
918 const SalLayoutGlyphs
* pSalLayoutCache
)
920 assert(!is_double_buffered_window());
922 if( nLen
< 0 || nIndex
+ nLen
>= rStr
.getLength() )
924 nLen
= rStr
.getLength() - nIndex
;
927 mpMetaFile
->AddAction( new MetaTextArrayAction( rStartPt
, rStr
, pDXAry
, nIndex
, nLen
) );
929 if ( !IsDeviceOutputNecessary() )
931 if( !mpGraphics
&& !AcquireGraphics() )
933 if( mbInitClipRegion
)
935 if( mbOutputClipped
)
938 std::unique_ptr
<SalLayout
> pSalLayout
= ImplLayout(rStr
, nIndex
, nLen
, rStartPt
, 0, pDXAry
, flags
, nullptr, pSalLayoutCache
);
941 ImplDrawText( *pSalLayout
);
945 mpAlphaVDev
->DrawTextArray( rStartPt
, rStr
, pDXAry
, nIndex
, nLen
, flags
);
948 long OutputDevice::GetTextArray( const OUString
& rStr
, long* pDXAry
,
949 sal_Int32 nIndex
, sal_Int32 nLen
,
950 vcl::TextLayoutCache
const*const pLayoutCache
,
951 SalLayoutGlyphs
const*const pSalLayoutCache
) const
953 if( nIndex
>= rStr
.getLength() )
954 return 0; // TODO: this looks like a buggy caller?
956 if( nLen
< 0 || nIndex
+ nLen
>= rStr
.getLength() )
958 nLen
= rStr
.getLength() - nIndex
;
962 std::unique_ptr
<SalLayout
> pSalLayout
= ImplLayout(rStr
, nIndex
, nLen
,
963 Point(0,0), 0, nullptr, SalLayoutFlags::NONE
, pLayoutCache
, pSalLayoutCache
);
966 // The caller expects this to init the elements of pDXAry.
967 // Adapting all the callers to check that GetTextArray succeeded seems
969 // Init here to 0 only in the (rare) error case, so that any missing
970 // element init in the happy case will still be found by tools,
971 // and hope that is sufficient.
974 memset(pDXAry
, 0, nLen
* sizeof(*pDXAry
));
979 #if VCL_FLOAT_DEVICE_PIXEL
980 std::unique_ptr
<DeviceCoordinate
[]> pDXPixelArray
;
983 pDXPixelArray
.reset(new DeviceCoordinate
[nLen
]);
985 DeviceCoordinate nWidth
= pSalLayout
->FillDXArray( pDXPixelArray
.get() );
986 int nWidthFactor
= pSalLayout
->GetUnitsPerPixel();
988 // convert virtual char widths to virtual absolute positions
991 for( int i
= 1; i
< nLen
; ++i
)
993 pDXPixelArray
[ i
] += pDXPixelArray
[ i
-1 ];
1000 for( int i
= 0; i
< nLen
; ++i
)
1002 pDXPixelArray
[i
] = ImplDevicePixelToLogicWidth( pDXPixelArray
[i
] );
1005 nWidth
= ImplDevicePixelToLogicWidth( nWidth
);
1007 if( nWidthFactor
> 1 )
1011 for( int i
= 0; i
< nLen
; ++i
)
1013 pDXPixelArray
[i
] /= nWidthFactor
;
1016 nWidth
/= nWidthFactor
;
1020 for( int i
= 0; i
< nLen
; ++i
)
1022 pDXAry
[i
] = basegfx::fround(pDXPixelArray
[i
]);
1025 return basegfx::fround(nWidth
);
1027 #else /* ! VCL_FLOAT_DEVICE_PIXEL */
1029 long nWidth
= pSalLayout
->FillDXArray( pDXAry
);
1030 int nWidthFactor
= pSalLayout
->GetUnitsPerPixel();
1032 // convert virtual char widths to virtual absolute positions
1034 for( int i
= 1; i
< nLen
; ++i
)
1035 pDXAry
[ i
] += pDXAry
[ i
-1 ];
1037 // convert from font units to logical units
1041 for( int i
= 0; i
< nLen
; ++i
)
1042 pDXAry
[i
] = ImplDevicePixelToLogicWidth( pDXAry
[i
] );
1043 nWidth
= ImplDevicePixelToLogicWidth( nWidth
);
1046 if( nWidthFactor
> 1 )
1049 for( int i
= 0; i
< nLen
; ++i
)
1050 pDXAry
[i
] /= nWidthFactor
;
1051 nWidth
/= nWidthFactor
;
1054 #endif /* VCL_FLOAT_DEVICE_PIXEL */
1057 void OutputDevice::GetCaretPositions( const OUString
& rStr
, long* pCaretXArray
,
1058 sal_Int32 nIndex
, sal_Int32 nLen
,
1059 const SalLayoutGlyphs
* pGlyphs
) const
1062 if( nIndex
>= rStr
.getLength() )
1064 if( nIndex
+nLen
>= rStr
.getLength() )
1065 nLen
= rStr
.getLength() - nIndex
;
1067 // layout complex text
1068 std::unique_ptr
<SalLayout
> pSalLayout
= ImplLayout(rStr
, nIndex
, nLen
, Point(0, 0), 0, nullptr,
1069 SalLayoutFlags::NONE
, nullptr, pGlyphs
);
1073 int nWidthFactor
= pSalLayout
->GetUnitsPerPixel();
1074 pSalLayout
->GetCaretPositions( 2*nLen
, pCaretXArray
);
1075 long nWidth
= pSalLayout
->GetTextWidth();
1077 // fixup unknown caret positions
1079 for( i
= 0; i
< 2 * nLen
; ++i
)
1080 if( pCaretXArray
[ i
] >= 0 )
1082 long nXPos
= pCaretXArray
[ i
];
1083 for( i
= 0; i
< 2 * nLen
; ++i
)
1085 if( pCaretXArray
[ i
] >= 0 )
1086 nXPos
= pCaretXArray
[ i
];
1088 pCaretXArray
[ i
] = nXPos
;
1091 // handle window mirroring
1092 if( IsRTLEnabled() )
1094 for( i
= 0; i
< 2 * nLen
; ++i
)
1095 pCaretXArray
[i
] = nWidth
- pCaretXArray
[i
] - 1;
1098 // convert from font units to logical units
1101 for( i
= 0; i
< 2*nLen
; ++i
)
1102 pCaretXArray
[i
] = ImplDevicePixelToLogicWidth( pCaretXArray
[i
] );
1105 if( nWidthFactor
!= 1 )
1107 for( i
= 0; i
< 2*nLen
; ++i
)
1108 pCaretXArray
[i
] /= nWidthFactor
;
1112 void OutputDevice::DrawStretchText( const Point
& rStartPt
, sal_uLong nWidth
,
1113 const OUString
& rStr
,
1114 sal_Int32 nIndex
, sal_Int32 nLen
)
1116 assert(!is_double_buffered_window());
1118 if( (nLen
< 0) || (nIndex
+ nLen
>= rStr
.getLength()))
1120 nLen
= rStr
.getLength() - nIndex
;
1124 mpMetaFile
->AddAction( new MetaStretchTextAction( rStartPt
, nWidth
, rStr
, nIndex
, nLen
) );
1126 if ( !IsDeviceOutputNecessary() )
1129 std::unique_ptr
<SalLayout
> pSalLayout
= ImplLayout(rStr
, nIndex
, nLen
, rStartPt
, nWidth
);
1132 ImplDrawText( *pSalLayout
);
1136 mpAlphaVDev
->DrawStretchText( rStartPt
, nWidth
, rStr
, nIndex
, nLen
);
1139 ImplLayoutArgs
OutputDevice::ImplPrepareLayoutArgs( OUString
& rStr
,
1140 const sal_Int32 nMinIndex
, const sal_Int32 nLen
,
1141 DeviceCoordinate nPixelWidth
, const DeviceCoordinate
* pDXArray
,
1142 SalLayoutFlags nLayoutFlags
,
1143 vcl::TextLayoutCache
const*const pLayoutCache
) const
1145 assert(nMinIndex
>= 0);
1148 // get string length for calculating extents
1149 sal_Int32 nEndIndex
= rStr
.getLength();
1150 if( nMinIndex
+ nLen
< nEndIndex
)
1151 nEndIndex
= nMinIndex
+ nLen
;
1153 // don't bother if there is nothing to do
1154 if( nEndIndex
< nMinIndex
)
1155 nEndIndex
= nMinIndex
;
1157 if( mnTextLayoutMode
& ComplexTextLayoutFlags::BiDiRtl
)
1158 nLayoutFlags
|= SalLayoutFlags::BiDiRtl
;
1159 if( mnTextLayoutMode
& ComplexTextLayoutFlags::BiDiStrong
)
1160 nLayoutFlags
|= SalLayoutFlags::BiDiStrong
;
1161 else if( !(mnTextLayoutMode
& ComplexTextLayoutFlags::BiDiRtl
) )
1163 // Disable Bidi if no RTL hint and only known LTR codes used.
1164 bool bAllLtr
= true;
1165 for (sal_Int32 i
= nMinIndex
; i
< nEndIndex
; i
++)
1167 // [0x0000, 0x052F] are Latin, Greek and Cyrillic.
1168 // [0x0370, 0x03FF] has a few holes as if Unicode 10.0.0, but
1169 // hopefully no RTL character will be encoded there.
1170 if (rStr
[i
] > 0x052F)
1177 nLayoutFlags
|= SalLayoutFlags::BiDiStrong
;
1180 if( !maFont
.IsKerning() )
1181 nLayoutFlags
|= SalLayoutFlags::DisableKerning
;
1182 if( maFont
.GetKerning() & FontKerning::Asian
)
1183 nLayoutFlags
|= SalLayoutFlags::KerningAsian
;
1184 if( maFont
.IsVertical() )
1185 nLayoutFlags
|= SalLayoutFlags::Vertical
;
1187 if( meTextLanguage
) //TODO: (mnTextLayoutMode & ComplexTextLayoutFlags::SubstituteDigits)
1189 // disable character localization when no digits used
1190 const sal_Unicode
* pBase
= rStr
.getStr();
1191 const sal_Unicode
* pStr
= pBase
+ nMinIndex
;
1192 const sal_Unicode
* pEnd
= pBase
+ nEndIndex
;
1193 boost::optional
<OUStringBuffer
> xTmpStr
;
1194 for( ; pStr
< pEnd
; ++pStr
)
1196 // TODO: are there non-digit localizations?
1197 if( (*pStr
>= '0') && (*pStr
<= '9') )
1199 // translate characters to local preference
1200 sal_UCS4 cChar
= GetLocalizedChar( *pStr
, meTextLanguage
);
1201 if( cChar
!= *pStr
)
1204 xTmpStr
= OUStringBuffer(rStr
);
1205 // TODO: are the localized digit surrogates?
1206 (*xTmpStr
)[pStr
- pBase
] = cChar
;
1211 rStr
= (*xTmpStr
).makeStringAndClear();
1214 // right align for RTL text, DRAWPOS_REVERSED, RTL window style
1215 bool bRightAlign
= bool(mnTextLayoutMode
& ComplexTextLayoutFlags::BiDiRtl
);
1216 if( mnTextLayoutMode
& ComplexTextLayoutFlags::TextOriginLeft
)
1217 bRightAlign
= false;
1218 else if ( mnTextLayoutMode
& ComplexTextLayoutFlags::TextOriginRight
)
1220 // SSA: hack for western office, ie text get right aligned
1221 // for debugging purposes of mirrored UI
1222 bool bRTLWindow
= IsRTLEnabled();
1223 bRightAlign
^= bRTLWindow
;
1225 nLayoutFlags
|= SalLayoutFlags::RightAlign
;
1227 // set layout options
1228 ImplLayoutArgs
aLayoutArgs(rStr
, nMinIndex
, nEndIndex
, nLayoutFlags
, maFont
.GetLanguageTag(), pLayoutCache
);
1230 int nOrientation
= mpFontInstance
? mpFontInstance
->mnOrientation
: 0;
1231 aLayoutArgs
.SetOrientation( nOrientation
);
1233 aLayoutArgs
.SetLayoutWidth( nPixelWidth
);
1234 aLayoutArgs
.SetDXArray( pDXArray
);
1239 std::unique_ptr
<SalLayout
> OutputDevice::ImplLayout(const OUString
& rOrigStr
,
1240 sal_Int32 nMinIndex
, sal_Int32 nLen
,
1241 const Point
& rLogicalPos
, long nLogicalWidth
,
1242 const long* pDXArray
, SalLayoutFlags flags
,
1243 vcl::TextLayoutCache
const* pLayoutCache
,
1244 const SalLayoutGlyphs
* pGlyphs
) const
1246 if (pGlyphs
&& !pGlyphs
->IsValid())
1248 SAL_WARN("vcl", "Trying to setup invalid cached glyphs - falling back to relayout!");
1255 // check string index and length
1256 if( -1 == nLen
|| nMinIndex
+ nLen
> rOrigStr
.getLength() )
1258 const sal_Int32 nNewLen
= rOrigStr
.getLength() - nMinIndex
;
1264 OUString aStr
= rOrigStr
;
1266 // convert from logical units to physical units
1267 // recode string if needed
1268 if( mpFontInstance
->mpConversion
) {
1269 mpFontInstance
->mpConversion
->RecodeString( aStr
, 0, aStr
.getLength() );
1270 pLayoutCache
= nullptr; // don't use cache with modified string!
1274 DeviceCoordinate nPixelWidth
= static_cast<DeviceCoordinate
>(nLogicalWidth
);
1275 if( nLogicalWidth
&& mbMap
)
1277 nPixelWidth
= LogicWidthToDeviceCoordinate( nLogicalWidth
);
1280 std::unique_ptr
<DeviceCoordinate
[]> xDXPixelArray
;
1281 DeviceCoordinate
* pDXPixelArray(nullptr);
1286 // convert from logical units to font units using a temporary array
1287 xDXPixelArray
.reset(new DeviceCoordinate
[nLen
]);
1288 pDXPixelArray
= xDXPixelArray
.get();
1289 // using base position for better rounding a.k.a. "dancing characters"
1290 DeviceCoordinate nPixelXOfs
= LogicWidthToDeviceCoordinate( rLogicalPos
.X() );
1291 for( int i
= 0; i
< nLen
; ++i
)
1293 pDXPixelArray
[i
] = LogicWidthToDeviceCoordinate( rLogicalPos
.X() + pDXArray
[i
] ) - nPixelXOfs
;
1298 #if VCL_FLOAT_DEVICE_PIXEL
1299 xDXPixelArray
.reset(new DeviceCoordinate
[nLen
]);
1300 pDXPixelArray
= xDXPixelArray
.get();
1301 for( int i
= 0; i
< nLen
; ++i
)
1303 pDXPixelArray
[i
] = pDXArray
[i
];
1305 #else /* !VCL_FLOAT_DEVICE_PIXEL */
1306 pDXPixelArray
= const_cast<DeviceCoordinate
*>(pDXArray
);
1307 #endif /* !VCL_FLOAT_DEVICE_PIXEL */
1311 ImplLayoutArgs aLayoutArgs
= ImplPrepareLayoutArgs( aStr
, nMinIndex
, nLen
,
1312 nPixelWidth
, pDXPixelArray
, flags
, pLayoutCache
);
1314 // get matching layout object for base font
1315 std::unique_ptr
<SalLayout
> pSalLayout
= mpGraphics
->GetTextLayout(0);
1318 if( pSalLayout
&& !pSalLayout
->LayoutText( aLayoutArgs
, pGlyphs
) )
1326 // do glyph fallback if needed
1327 // #105768# avoid fallback for very small font sizes
1328 if (aLayoutArgs
.NeedFallback() && mpFontInstance
->GetFontSelectPattern().mnHeight
>= 3)
1329 pSalLayout
= ImplGlyphFallbackLayout(std::move(pSalLayout
), aLayoutArgs
);
1331 if (flags
& SalLayoutFlags::GlyphItemsOnly
)
1332 // Return glyph items only after fallback handling. Otherwise they may
1333 // contain invalid glyph IDs.
1336 // position, justify, etc. the layout
1337 pSalLayout
->AdjustLayout( aLayoutArgs
);
1338 pSalLayout
->DrawBase() = ImplLogicToDevicePixel( rLogicalPos
);
1339 // adjust to right alignment if necessary
1340 if( aLayoutArgs
.mnFlags
& SalLayoutFlags::RightAlign
)
1342 DeviceCoordinate nRTLOffset
;
1344 nRTLOffset
= pDXPixelArray
[ nLen
- 1 ];
1345 else if( nPixelWidth
)
1346 nRTLOffset
= nPixelWidth
;
1348 nRTLOffset
= pSalLayout
->GetTextWidth() / pSalLayout
->GetUnitsPerPixel();
1349 pSalLayout
->DrawOffset().setX( 1 - nRTLOffset
);
1355 std::shared_ptr
<vcl::TextLayoutCache
> OutputDevice::CreateTextLayoutCache(
1356 OUString
const& rString
)
1358 return GenericSalLayout::CreateTextLayoutCache(rString
);
1361 bool OutputDevice::GetTextIsRTL( const OUString
& rString
, sal_Int32 nIndex
, sal_Int32 nLen
) const
1363 OUString
aStr( rString
);
1364 ImplLayoutArgs aArgs
= ImplPrepareLayoutArgs( aStr
, nIndex
, nLen
, 0, nullptr );
1367 if (!aArgs
.GetNextPos(&nCharPos
, &bRTL
))
1369 return (nCharPos
!= nIndex
);
1372 sal_Int32
OutputDevice::GetTextBreak( const OUString
& rStr
, long nTextWidth
,
1373 sal_Int32 nIndex
, sal_Int32 nLen
,
1375 vcl::TextLayoutCache
const*const pLayoutCache
,
1376 const SalLayoutGlyphs
* pGlyphs
) const
1378 std::unique_ptr
<SalLayout
> pSalLayout
= ImplLayout( rStr
, nIndex
, nLen
,
1379 Point(0,0), 0, nullptr, SalLayoutFlags::NONE
, pLayoutCache
, pGlyphs
);
1380 sal_Int32 nRetVal
= -1;
1383 // convert logical widths into layout units
1384 // NOTE: be very careful to avoid rounding errors for nCharExtra case
1385 // problem with rounding errors especially for small nCharExtras
1386 // TODO: remove when layout units have subpixel granularity
1387 long nWidthFactor
= pSalLayout
->GetUnitsPerPixel();
1388 long nSubPixelFactor
= (nWidthFactor
< 64 ) ? 64 : 1;
1389 nTextWidth
*= nWidthFactor
* nSubPixelFactor
;
1390 DeviceCoordinate nTextPixelWidth
= LogicWidthToDeviceCoordinate( nTextWidth
);
1391 DeviceCoordinate nExtraPixelWidth
= 0;
1392 if( nCharExtra
!= 0 )
1394 nCharExtra
*= nWidthFactor
* nSubPixelFactor
;
1395 nExtraPixelWidth
= LogicWidthToDeviceCoordinate( nCharExtra
);
1397 nRetVal
= pSalLayout
->GetTextBreak( nTextPixelWidth
, nExtraPixelWidth
, nSubPixelFactor
);
1403 sal_Int32
OutputDevice::GetTextBreak( const OUString
& rStr
, long nTextWidth
,
1404 sal_Unicode nHyphenChar
, sal_Int32
& rHyphenPos
,
1405 sal_Int32 nIndex
, sal_Int32 nLen
,
1407 vcl::TextLayoutCache
const*const pLayoutCache
) const
1411 std::unique_ptr
<SalLayout
> pSalLayout
= ImplLayout( rStr
, nIndex
, nLen
,
1412 Point(0,0), 0, nullptr, SalLayoutFlags::NONE
, pLayoutCache
);
1413 sal_Int32 nRetVal
= -1;
1416 // convert logical widths into layout units
1417 // NOTE: be very careful to avoid rounding errors for nCharExtra case
1418 // problem with rounding errors especially for small nCharExtras
1419 // TODO: remove when layout units have subpixel granularity
1420 long nWidthFactor
= pSalLayout
->GetUnitsPerPixel();
1421 long nSubPixelFactor
= (nWidthFactor
< 64 ) ? 64 : 1;
1423 nTextWidth
*= nWidthFactor
* nSubPixelFactor
;
1424 DeviceCoordinate nTextPixelWidth
= LogicWidthToDeviceCoordinate( nTextWidth
);
1425 DeviceCoordinate nExtraPixelWidth
= 0;
1426 if( nCharExtra
!= 0 )
1428 nCharExtra
*= nWidthFactor
* nSubPixelFactor
;
1429 nExtraPixelWidth
= LogicWidthToDeviceCoordinate( nCharExtra
);
1432 // calculate un-hyphenated break position
1433 nRetVal
= pSalLayout
->GetTextBreak( nTextPixelWidth
, nExtraPixelWidth
, nSubPixelFactor
);
1435 // calculate hyphenated break position
1436 OUString
aHyphenStr(nHyphenChar
);
1437 std::unique_ptr
<SalLayout
> pHyphenLayout
= ImplLayout( aHyphenStr
, 0, 1 );
1440 // calculate subpixel width of hyphenation character
1441 long nHyphenPixelWidth
= pHyphenLayout
->GetTextWidth() * nSubPixelFactor
;
1443 // calculate hyphenated break position
1444 nTextPixelWidth
-= nHyphenPixelWidth
;
1445 if( nExtraPixelWidth
> 0 )
1446 nTextPixelWidth
-= nExtraPixelWidth
;
1448 rHyphenPos
= pSalLayout
->GetTextBreak(nTextPixelWidth
, nExtraPixelWidth
, nSubPixelFactor
);
1450 if( rHyphenPos
> nRetVal
)
1451 rHyphenPos
= nRetVal
;
1458 void OutputDevice::ImplDrawText( OutputDevice
& rTargetDevice
, const tools::Rectangle
& rRect
,
1459 const OUString
& rOrigStr
, DrawTextFlags nStyle
,
1460 MetricVector
* pVector
, OUString
* pDisplayText
,
1461 vcl::ITextLayout
& _rLayout
)
1464 Color aOldTextColor
;
1465 Color aOldTextFillColor
;
1466 bool bRestoreFillColor
= false;
1467 if ( (nStyle
& DrawTextFlags::Disable
) && ! pVector
)
1469 bool bHighContrastBlack
= false;
1470 bool bHighContrastWhite
= false;
1471 const StyleSettings
& rStyleSettings( rTargetDevice
.GetSettings().GetStyleSettings() );
1472 if( rStyleSettings
.GetHighContrastMode() )
1475 if( rTargetDevice
.IsBackground() )
1476 aCol
= rTargetDevice
.GetBackground().GetColor();
1478 // best guess is the face color here
1479 // but it may be totally wrong. the background color
1480 // was typically already reset
1481 aCol
= rStyleSettings
.GetFaceColor();
1483 bHighContrastBlack
= aCol
.IsDark();
1484 bHighContrastWhite
= aCol
.IsBright();
1487 aOldTextColor
= rTargetDevice
.GetTextColor();
1488 if ( rTargetDevice
.IsTextFillColor() )
1490 bRestoreFillColor
= true;
1491 aOldTextFillColor
= rTargetDevice
.GetTextFillColor();
1493 if( bHighContrastBlack
)
1494 rTargetDevice
.SetTextColor( COL_GREEN
);
1495 else if( bHighContrastWhite
)
1496 rTargetDevice
.SetTextColor( COL_LIGHTGREEN
);
1499 // draw disabled text always without shadow
1500 // as it fits better with native look
1501 rTargetDevice
.SetTextColor( rTargetDevice
.GetSettings().GetStyleSettings().GetDisableColor() );
1505 long nWidth
= rRect
.GetWidth();
1506 long nHeight
= rRect
.GetHeight();
1508 if ( ((nWidth
<= 0) || (nHeight
<= 0)) && (nStyle
& DrawTextFlags::Clip
) )
1511 Point aPos
= rRect
.TopLeft();
1513 long nTextHeight
= rTargetDevice
.GetTextHeight();
1514 TextAlign eAlign
= rTargetDevice
.GetTextAlign();
1515 sal_Int32 nMnemonicPos
= -1;
1517 OUString aStr
= rOrigStr
;
1518 if ( nStyle
& DrawTextFlags::Mnemonic
)
1519 aStr
= GetNonMnemonicString( aStr
, nMnemonicPos
);
1521 const bool bDrawMnemonics
= !(rTargetDevice
.GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics
) && !pVector
;
1523 // We treat multiline text differently
1524 if ( nStyle
& DrawTextFlags::MultiLine
)
1528 ImplMultiTextLineInfo aMultiLineInfo
;
1529 ImplTextLineInfo
* pLineInfo
;
1532 sal_Int32 nFormatLines
;
1536 long nMaxTextWidth
= ImplGetTextLines( aMultiLineInfo
, nWidth
, aStr
, nStyle
, _rLayout
);
1537 nLines
= static_cast<sal_Int32
>(nHeight
/nTextHeight
);
1538 nFormatLines
= aMultiLineInfo
.Count();
1541 if ( nFormatLines
> nLines
)
1543 if ( nStyle
& DrawTextFlags::EndEllipsis
)
1545 // Create last line and shorten it
1546 nFormatLines
= nLines
-1;
1548 pLineInfo
= aMultiLineInfo
.GetLine( nFormatLines
);
1549 aLastLine
= convertLineEnd(aStr
.copy(pLineInfo
->GetIndex()), LINEEND_LF
);
1550 // Replace all LineFeeds with Spaces
1551 OUStringBuffer
aLastLineBuffer(aLastLine
);
1552 sal_Int32 nLastLineLen
= aLastLineBuffer
.getLength();
1553 for ( i
= 0; i
< nLastLineLen
; i
++ )
1555 if ( aLastLineBuffer
[ i
] == '\n' )
1556 aLastLineBuffer
[ i
] = ' ';
1558 aLastLine
= aLastLineBuffer
.makeStringAndClear();
1559 aLastLine
= ImplGetEllipsisString( rTargetDevice
, aLastLine
, nWidth
, nStyle
, _rLayout
);
1560 nStyle
&= ~DrawTextFlags(DrawTextFlags::VCenter
| DrawTextFlags::Bottom
);
1561 nStyle
|= DrawTextFlags::Top
;
1566 if ( nMaxTextWidth
<= nWidth
)
1567 nStyle
&= ~DrawTextFlags::Clip
;
1570 // Do we need to clip the height?
1571 if ( nFormatLines
*nTextHeight
> nHeight
)
1572 nStyle
|= DrawTextFlags::Clip
;
1575 if ( nStyle
& DrawTextFlags::Clip
)
1577 rTargetDevice
.Push( PushFlags::CLIPREGION
);
1578 rTargetDevice
.IntersectClipRegion( rRect
);
1581 // Vertical alignment
1582 if ( nStyle
& DrawTextFlags::Bottom
)
1583 aPos
.AdjustY(nHeight
-(nFormatLines
*nTextHeight
) );
1584 else if ( nStyle
& DrawTextFlags::VCenter
)
1585 aPos
.AdjustY((nHeight
-(nFormatLines
*nTextHeight
))/2 );
1588 if ( eAlign
== ALIGN_BOTTOM
)
1589 aPos
.AdjustY(nTextHeight
);
1590 else if ( eAlign
== ALIGN_BASELINE
)
1591 aPos
.AdjustY(rTargetDevice
.GetFontMetric().GetAscent() );
1593 // Output all lines except for the last one
1594 for ( i
= 0; i
< nFormatLines
; i
++ )
1596 pLineInfo
= aMultiLineInfo
.GetLine( i
);
1597 if ( nStyle
& DrawTextFlags::Right
)
1598 aPos
.AdjustX(nWidth
-pLineInfo
->GetWidth() );
1599 else if ( nStyle
& DrawTextFlags::Center
)
1600 aPos
.AdjustX((nWidth
-pLineInfo
->GetWidth())/2 );
1601 sal_Int32 nIndex
= pLineInfo
->GetIndex();
1602 sal_Int32 nLineLen
= pLineInfo
->GetLen();
1603 _rLayout
.DrawText( aPos
, aStr
, nIndex
, nLineLen
, pVector
, pDisplayText
);
1604 if ( bDrawMnemonics
)
1606 if ( (nMnemonicPos
>= nIndex
) && (nMnemonicPos
< nIndex
+nLineLen
) )
1610 DeviceCoordinate nMnemonicWidth
;
1612 std::unique_ptr
<long[]> const pCaretXArray(new long[2 * nLineLen
]);
1613 /*sal_Bool bRet =*/ _rLayout
.GetCaretPositions( aStr
, pCaretXArray
.get(),
1615 long lc_x1
= pCaretXArray
[2*(nMnemonicPos
- nIndex
)];
1616 long lc_x2
= pCaretXArray
[2*(nMnemonicPos
- nIndex
)+1];
1617 nMnemonicWidth
= rTargetDevice
.LogicWidthToDeviceCoordinate( std::abs(lc_x1
- lc_x2
) );
1619 Point aTempPos
= rTargetDevice
.LogicToPixel( aPos
);
1620 nMnemonicX
= rTargetDevice
.GetOutOffXPixel() + aTempPos
.X() + rTargetDevice
.ImplLogicWidthToDevicePixel( std::min( lc_x1
, lc_x2
) );
1621 nMnemonicY
= rTargetDevice
.GetOutOffYPixel() + aTempPos
.Y() + rTargetDevice
.ImplLogicWidthToDevicePixel( rTargetDevice
.GetFontMetric().GetAscent() );
1622 rTargetDevice
.ImplDrawMnemonicLine( nMnemonicX
, nMnemonicY
, nMnemonicWidth
);
1625 aPos
.AdjustY(nTextHeight
);
1626 aPos
.setX( rRect
.Left() );
1629 // If there still is a last line, we output it left-aligned as the line would be clipped
1630 if ( !aLastLine
.isEmpty() )
1631 _rLayout
.DrawText( aPos
, aLastLine
, 0, aLastLine
.getLength(), pVector
, pDisplayText
);
1634 if ( nStyle
& DrawTextFlags::Clip
)
1635 rTargetDevice
.Pop();
1640 long nTextWidth
= _rLayout
.GetTextWidth( aStr
, 0, -1 );
1642 // Clip text if needed
1643 if ( nTextWidth
> nWidth
)
1645 if ( nStyle
& TEXT_DRAW_ELLIPSIS
)
1647 aStr
= ImplGetEllipsisString( rTargetDevice
, aStr
, nWidth
, nStyle
, _rLayout
);
1648 nStyle
&= ~DrawTextFlags(DrawTextFlags::Center
| DrawTextFlags::Right
);
1649 nStyle
|= DrawTextFlags::Left
;
1650 nTextWidth
= _rLayout
.GetTextWidth( aStr
, 0, aStr
.getLength() );
1655 if ( nTextHeight
<= nHeight
)
1656 nStyle
&= ~DrawTextFlags::Clip
;
1659 // horizontal text alignment
1660 if ( nStyle
& DrawTextFlags::Right
)
1661 aPos
.AdjustX(nWidth
-nTextWidth
);
1662 else if ( nStyle
& DrawTextFlags::Center
)
1663 aPos
.AdjustX((nWidth
-nTextWidth
)/2 );
1665 // vertical font alignment
1666 if ( eAlign
== ALIGN_BOTTOM
)
1667 aPos
.AdjustY(nTextHeight
);
1668 else if ( eAlign
== ALIGN_BASELINE
)
1669 aPos
.AdjustY(rTargetDevice
.GetFontMetric().GetAscent() );
1671 if ( nStyle
& DrawTextFlags::Bottom
)
1672 aPos
.AdjustY(nHeight
-nTextHeight
);
1673 else if ( nStyle
& DrawTextFlags::VCenter
)
1674 aPos
.AdjustY((nHeight
-nTextHeight
)/2 );
1676 long nMnemonicX
= 0;
1677 long nMnemonicY
= 0;
1678 DeviceCoordinate nMnemonicWidth
= 0;
1679 if ( nMnemonicPos
!= -1 )
1681 std::unique_ptr
<long[]> const pCaretXArray(new long[2 * aStr
.getLength()]);
1682 /*sal_Bool bRet =*/ _rLayout
.GetCaretPositions( aStr
, pCaretXArray
.get(), 0, aStr
.getLength() );
1683 long lc_x1
= pCaretXArray
[2*nMnemonicPos
];
1684 long lc_x2
= pCaretXArray
[2*nMnemonicPos
+1];
1685 nMnemonicWidth
= rTargetDevice
.LogicWidthToDeviceCoordinate( std::abs(lc_x1
- lc_x2
) );
1687 Point aTempPos
= rTargetDevice
.LogicToPixel( aPos
);
1688 nMnemonicX
= rTargetDevice
.GetOutOffXPixel() + aTempPos
.X() + rTargetDevice
.ImplLogicWidthToDevicePixel( std::min(lc_x1
, lc_x2
) );
1689 nMnemonicY
= rTargetDevice
.GetOutOffYPixel() + aTempPos
.Y() + rTargetDevice
.ImplLogicWidthToDevicePixel( rTargetDevice
.GetFontMetric().GetAscent() );
1692 if ( nStyle
& DrawTextFlags::Clip
)
1694 rTargetDevice
.Push( PushFlags::CLIPREGION
);
1695 rTargetDevice
.IntersectClipRegion( rRect
);
1696 _rLayout
.DrawText( aPos
, aStr
, 0, aStr
.getLength(), pVector
, pDisplayText
);
1697 if ( bDrawMnemonics
&& nMnemonicPos
!= -1 )
1698 rTargetDevice
.ImplDrawMnemonicLine( nMnemonicX
, nMnemonicY
, nMnemonicWidth
);
1699 rTargetDevice
.Pop();
1703 _rLayout
.DrawText( aPos
, aStr
, 0, aStr
.getLength(), pVector
, pDisplayText
);
1704 if ( bDrawMnemonics
&& nMnemonicPos
!= -1 )
1705 rTargetDevice
.ImplDrawMnemonicLine( nMnemonicX
, nMnemonicY
, nMnemonicWidth
);
1709 if ( nStyle
& DrawTextFlags::Disable
&& !pVector
)
1711 rTargetDevice
.SetTextColor( aOldTextColor
);
1712 if ( bRestoreFillColor
)
1713 rTargetDevice
.SetTextFillColor( aOldTextFillColor
);
1717 void OutputDevice::AddTextRectActions( const tools::Rectangle
& rRect
,
1718 const OUString
& rOrigStr
,
1719 DrawTextFlags nStyle
,
1723 if ( rOrigStr
.isEmpty() || rRect
.IsEmpty() )
1726 // we need a graphics
1727 if( !mpGraphics
&& !AcquireGraphics() )
1729 if( mbInitClipRegion
)
1732 // temporarily swap in passed mtf for action generation, and
1733 // disable output generation.
1734 const bool bOutputEnabled( IsOutputEnabled() );
1735 GDIMetaFile
* pMtf
= mpMetaFile
;
1738 EnableOutput( false );
1740 // #i47157# Factored out to ImplDrawTextRect(), to be shared
1741 // between us and DrawText()
1742 vcl::DefaultTextLayout
aLayout( *this );
1743 ImplDrawText( *this, rRect
, rOrigStr
, nStyle
, nullptr, nullptr, aLayout
);
1745 // and restore again
1746 EnableOutput( bOutputEnabled
);
1750 void OutputDevice::DrawText( const tools::Rectangle
& rRect
, const OUString
& rOrigStr
, DrawTextFlags nStyle
,
1751 MetricVector
* pVector
, OUString
* pDisplayText
,
1752 vcl::ITextLayout
* _pTextLayout
)
1754 assert(!is_double_buffered_window());
1756 if (mpOutDevData
->mpRecordLayout
)
1758 pVector
= &mpOutDevData
->mpRecordLayout
->m_aUnicodeBoundRects
;
1759 pDisplayText
= &mpOutDevData
->mpRecordLayout
->m_aDisplayText
;
1762 bool bDecomposeTextRectAction
= ( _pTextLayout
!= nullptr ) && _pTextLayout
->DecomposeTextRectAction();
1763 if ( mpMetaFile
&& !bDecomposeTextRectAction
)
1764 mpMetaFile
->AddAction( new MetaTextRectAction( rRect
, rOrigStr
, nStyle
) );
1766 if ( ( !IsDeviceOutputNecessary() && !pVector
&& !bDecomposeTextRectAction
) || rOrigStr
.isEmpty() || rRect
.IsEmpty() )
1769 // we need a graphics
1770 if( !mpGraphics
&& !AcquireGraphics() )
1772 if( mbInitClipRegion
)
1774 if( mbOutputClipped
&& !bDecomposeTextRectAction
)
1777 // temporarily disable mtf action generation (ImplDrawText _does_
1778 // create MetaActionType::TEXTs otherwise)
1779 GDIMetaFile
* pMtf
= mpMetaFile
;
1780 if ( !bDecomposeTextRectAction
)
1781 mpMetaFile
= nullptr;
1783 // #i47157# Factored out to ImplDrawText(), to be used also
1784 // from AddTextRectActions()
1785 vcl::DefaultTextLayout
aDefaultLayout( *this );
1786 ImplDrawText( *this, rRect
, rOrigStr
, nStyle
, pVector
, pDisplayText
, _pTextLayout
? *_pTextLayout
: aDefaultLayout
);
1792 mpAlphaVDev
->DrawText( rRect
, rOrigStr
, nStyle
, pVector
, pDisplayText
);
1795 tools::Rectangle
OutputDevice::GetTextRect( const tools::Rectangle
& rRect
,
1796 const OUString
& rStr
, DrawTextFlags nStyle
,
1797 TextRectInfo
* pInfo
,
1798 const vcl::ITextLayout
* _pTextLayout
) const
1801 tools::Rectangle aRect
= rRect
;
1803 long nWidth
= rRect
.GetWidth();
1805 long nTextHeight
= GetTextHeight();
1807 OUString aStr
= rStr
;
1808 if ( nStyle
& DrawTextFlags::Mnemonic
)
1809 aStr
= GetNonMnemonicString( aStr
);
1811 if ( nStyle
& DrawTextFlags::MultiLine
)
1813 ImplMultiTextLineInfo aMultiLineInfo
;
1814 ImplTextLineInfo
* pLineInfo
;
1815 sal_Int32 nFormatLines
;
1819 vcl::DefaultTextLayout
aDefaultLayout( *const_cast< OutputDevice
* >( this ) );
1820 ImplGetTextLines( aMultiLineInfo
, nWidth
, aStr
, nStyle
, _pTextLayout
? *_pTextLayout
: aDefaultLayout
);
1821 nFormatLines
= aMultiLineInfo
.Count();
1824 nLines
= static_cast<sal_uInt16
>(aRect
.GetHeight()/nTextHeight
);
1826 pInfo
->mnLineCount
= nFormatLines
;
1829 if ( nFormatLines
<= nLines
)
1830 nLines
= nFormatLines
;
1833 if ( !(nStyle
& DrawTextFlags::EndEllipsis
) )
1834 nLines
= nFormatLines
;
1838 pInfo
->mbEllipsis
= true;
1844 bool bMaxWidth
= nMaxWidth
== 0;
1845 pInfo
->mnMaxWidth
= 0;
1846 for ( i
= 0; i
< nLines
; i
++ )
1848 pLineInfo
= aMultiLineInfo
.GetLine( i
);
1849 if ( bMaxWidth
&& (pLineInfo
->GetWidth() > nMaxWidth
) )
1850 nMaxWidth
= pLineInfo
->GetWidth();
1851 if ( pLineInfo
->GetWidth() > pInfo
->mnMaxWidth
)
1852 pInfo
->mnMaxWidth
= pLineInfo
->GetWidth();
1855 else if ( !nMaxWidth
)
1857 for ( i
= 0; i
< nLines
; i
++ )
1859 pLineInfo
= aMultiLineInfo
.GetLine( i
);
1860 if ( pLineInfo
->GetWidth() > nMaxWidth
)
1861 nMaxWidth
= pLineInfo
->GetWidth();
1868 nMaxWidth
= _pTextLayout
? _pTextLayout
->GetTextWidth( aStr
, 0, aStr
.getLength() ) : GetTextWidth( aStr
);
1872 pInfo
->mnLineCount
= 1;
1873 pInfo
->mnMaxWidth
= nMaxWidth
;
1876 if ( (nMaxWidth
> nWidth
) && (nStyle
& TEXT_DRAW_ELLIPSIS
) )
1879 pInfo
->mbEllipsis
= true;
1884 if ( nStyle
& DrawTextFlags::Right
)
1885 aRect
.SetLeft( aRect
.Right()-nMaxWidth
+1 );
1886 else if ( nStyle
& DrawTextFlags::Center
)
1888 aRect
.AdjustLeft((nWidth
-nMaxWidth
)/2 );
1889 aRect
.SetRight( aRect
.Left()+nMaxWidth
-1 );
1892 aRect
.SetRight( aRect
.Left()+nMaxWidth
-1 );
1894 if ( nStyle
& DrawTextFlags::Bottom
)
1895 aRect
.SetTop( aRect
.Bottom()-(nTextHeight
*nLines
)+1 );
1896 else if ( nStyle
& DrawTextFlags::VCenter
)
1898 aRect
.AdjustTop((aRect
.GetHeight()-(nTextHeight
*nLines
))/2 );
1899 aRect
.SetBottom( aRect
.Top()+(nTextHeight
*nLines
)-1 );
1902 aRect
.SetBottom( aRect
.Top()+(nTextHeight
*nLines
)-1 );
1904 // #99188# get rid of rounding problems when using this rect later
1905 if (nStyle
& DrawTextFlags::Right
)
1906 aRect
.AdjustLeft( -1 );
1908 aRect
.AdjustRight( 1 );
1912 static bool ImplIsCharIn( sal_Unicode c
, const sal_Char
* pStr
)
1924 OUString
OutputDevice::GetEllipsisString( const OUString
& rOrigStr
, long nMaxWidth
,
1925 DrawTextFlags nStyle
) const
1927 vcl::DefaultTextLayout
aTextLayout( *const_cast< OutputDevice
* >( this ) );
1928 return ImplGetEllipsisString( *this, rOrigStr
, nMaxWidth
, nStyle
, aTextLayout
);
1931 OUString
OutputDevice::ImplGetEllipsisString( const OutputDevice
& rTargetDevice
, const OUString
& rOrigStr
, long nMaxWidth
,
1932 DrawTextFlags nStyle
, const vcl::ITextLayout
& _rLayout
)
1934 OUString aStr
= rOrigStr
;
1935 sal_Int32 nIndex
= _rLayout
.GetTextBreak( aStr
, nMaxWidth
, 0, aStr
.getLength() );
1939 if( (nStyle
& DrawTextFlags::CenterEllipsis
) == DrawTextFlags::CenterEllipsis
)
1941 OUStringBuffer
aTmpStr( aStr
);
1942 // speed it up by removing all but 1.33x as many as the break pos.
1943 sal_Int32 nEraseChars
= std::max
<sal_Int32
>(4, aStr
.getLength() - (nIndex
*4)/3);
1944 while( nEraseChars
< aStr
.getLength() && _rLayout
.GetTextWidth( aTmpStr
.toString(), 0, aTmpStr
.getLength() ) > nMaxWidth
)
1947 sal_Int32 i
= (aTmpStr
.getLength() - nEraseChars
)/2;
1948 aTmpStr
.remove(i
, nEraseChars
++);
1949 aTmpStr
.insert(i
, "...");
1951 aStr
= aTmpStr
.makeStringAndClear();
1953 else if ( nStyle
& DrawTextFlags::EndEllipsis
)
1955 aStr
= aStr
.copy(0, nIndex
);
1959 while ( !aStr
.isEmpty() && (_rLayout
.GetTextWidth( aStr
, 0, aStr
.getLength() ) > nMaxWidth
) )
1961 if ( (nIndex
> 1) || (nIndex
== aStr
.getLength()) )
1963 aStr
= aStr
.replaceAt( nIndex
, 1, "");
1967 if ( aStr
.isEmpty() && (nStyle
& DrawTextFlags::Clip
) )
1968 aStr
+= OUStringChar(rOrigStr
[ 0 ]);
1970 else if ( nStyle
& DrawTextFlags::PathEllipsis
)
1972 OUString
aPath( rOrigStr
);
1973 OUString aAbbreviatedPath
;
1974 osl_abbreviateSystemPath( aPath
.pData
, &aAbbreviatedPath
.pData
, nIndex
, nullptr );
1975 aStr
= aAbbreviatedPath
;
1977 else if ( nStyle
& DrawTextFlags::NewsEllipsis
)
1979 static sal_Char
const pSepChars
[] = ".";
1980 // Determine last section
1981 sal_Int32 nLastContent
= aStr
.getLength();
1982 while ( nLastContent
)
1985 if ( ImplIsCharIn( aStr
[ nLastContent
], pSepChars
) )
1988 while ( nLastContent
&&
1989 ImplIsCharIn( aStr
[ nLastContent
-1 ], pSepChars
) )
1992 OUString aLastStr
= aStr
.copy(nLastContent
);
1993 OUString aTempLastStr1
= "..." + aLastStr
;
1994 if ( _rLayout
.GetTextWidth( aTempLastStr1
, 0, aTempLastStr1
.getLength() ) > nMaxWidth
)
1995 aStr
= OutputDevice::ImplGetEllipsisString( rTargetDevice
, aStr
, nMaxWidth
, nStyle
| DrawTextFlags::EndEllipsis
, _rLayout
);
1998 sal_Int32 nFirstContent
= 0;
1999 while ( nFirstContent
< nLastContent
)
2002 if ( ImplIsCharIn( aStr
[ nFirstContent
], pSepChars
) )
2005 while ( (nFirstContent
< nLastContent
) &&
2006 ImplIsCharIn( aStr
[ nFirstContent
], pSepChars
) )
2008 // MEM continue here
2009 if ( nFirstContent
>= nLastContent
)
2010 aStr
= OutputDevice::ImplGetEllipsisString( rTargetDevice
, aStr
, nMaxWidth
, nStyle
| DrawTextFlags::EndEllipsis
, _rLayout
);
2013 if ( nFirstContent
> 4 )
2015 OUString aFirstStr
= aStr
.copy( 0, nFirstContent
) + "...";
2016 OUString aTempStr
= aFirstStr
+ aLastStr
;
2017 if ( _rLayout
.GetTextWidth( aTempStr
, 0, aTempStr
.getLength() ) > nMaxWidth
)
2018 aStr
= OutputDevice::ImplGetEllipsisString( rTargetDevice
, aStr
, nMaxWidth
, nStyle
| DrawTextFlags::EndEllipsis
, _rLayout
);
2024 if( nLastContent
> aStr
.getLength() )
2025 nLastContent
= aStr
.getLength();
2026 while ( nFirstContent
< nLastContent
)
2029 if ( ImplIsCharIn( aStr
[ nLastContent
], pSepChars
) )
2033 while ( (nFirstContent
< nLastContent
) &&
2034 ImplIsCharIn( aStr
[ nLastContent
-1 ], pSepChars
) )
2037 if ( nFirstContent
< nLastContent
)
2039 OUString aTempLastStr
= aStr
.copy( nLastContent
);
2040 aTempStr
= aFirstStr
+ aTempLastStr
;
2042 if ( _rLayout
.GetTextWidth( aTempStr
, 0, aTempStr
.getLength() ) > nMaxWidth
)
2046 while ( nFirstContent
< nLastContent
);
2056 void OutputDevice::DrawCtrlText( const Point
& rPos
, const OUString
& rStr
,
2057 sal_Int32 nIndex
, sal_Int32 nLen
,
2058 DrawTextFlags nStyle
, MetricVector
* pVector
, OUString
* pDisplayText
,
2059 const SalLayoutGlyphs
* pGlyphs
)
2061 assert(!is_double_buffered_window());
2063 if( (nLen
< 0) || (nIndex
+ nLen
>= rStr
.getLength()))
2065 nLen
= rStr
.getLength() - nIndex
;
2068 if ( !IsDeviceOutputNecessary() || (nIndex
>= rStr
.getLength()) )
2071 // better get graphics here because ImplDrawMnemonicLine() will not
2072 // we need a graphics
2073 if( !mpGraphics
&& !AcquireGraphics() )
2075 if( mbInitClipRegion
)
2077 if ( mbOutputClipped
)
2080 if( nIndex
>= rStr
.getLength() )
2083 if( (nLen
< 0) || (nIndex
+ nLen
>= rStr
.getLength()))
2085 nLen
= rStr
.getLength() - nIndex
;
2087 OUString aStr
= rStr
;
2088 sal_Int32 nMnemonicPos
= -1;
2090 long nMnemonicX
= 0;
2091 long nMnemonicY
= 0;
2092 long nMnemonicWidth
= 0;
2093 if ( (nStyle
& DrawTextFlags::Mnemonic
) && nLen
> 1 )
2095 aStr
= GetNonMnemonicString( aStr
, nMnemonicPos
);
2096 if ( nMnemonicPos
!= -1 )
2098 if( nMnemonicPos
< nIndex
)
2104 if( nMnemonicPos
< (nIndex
+nLen
) )
2106 SAL_WARN_IF( nMnemonicPos
>= (nIndex
+nLen
), "vcl", "Mnemonic underline marker after last character" );
2108 bool bInvalidPos
= false;
2110 if( nMnemonicPos
>= nLen
)
2112 // may occur in BiDi-Strings: the '~' is sometimes found behind the last char
2113 // due to some strange BiDi text editors
2114 // -> place the underline behind the string to indicate a failure
2116 nMnemonicPos
= nLen
-1;
2119 std::unique_ptr
<long[]> const pCaretXArray(new long[2 * nLen
]);
2120 /*sal_Bool bRet =*/ GetCaretPositions( aStr
, pCaretXArray
.get(), nIndex
, nLen
, pGlyphs
);
2121 long lc_x1
= pCaretXArray
[ 2*(nMnemonicPos
- nIndex
) ];
2122 long lc_x2
= pCaretXArray
[ 2*(nMnemonicPos
- nIndex
)+1 ];
2123 nMnemonicWidth
= ::abs(static_cast<int>(lc_x1
- lc_x2
));
2125 Point
aTempPos( std::min(lc_x1
,lc_x2
), GetFontMetric().GetAscent() );
2126 if( bInvalidPos
) // #106952#, place behind the (last) character
2127 aTempPos
= Point( std::max(lc_x1
,lc_x2
), GetFontMetric().GetAscent() );
2130 aTempPos
= LogicToPixel( aTempPos
);
2131 nMnemonicX
= mnOutOffX
+ aTempPos
.X();
2132 nMnemonicY
= mnOutOffY
+ aTempPos
.Y();
2136 bool autoacc
= ImplGetSVData()->maNWFData
.mbAutoAccel
;
2138 if ( nStyle
& DrawTextFlags::Disable
&& ! pVector
)
2140 Color aOldTextColor
;
2141 Color aOldTextFillColor
;
2142 bool bRestoreFillColor
;
2143 bool bHighContrastBlack
= false;
2144 bool bHighContrastWhite
= false;
2145 const StyleSettings
& rStyleSettings( GetSettings().GetStyleSettings() );
2146 if( rStyleSettings
.GetHighContrastMode() )
2148 if( IsBackground() )
2150 Wallpaper aWall
= GetBackground();
2151 Color aCol
= aWall
.GetColor();
2152 bHighContrastBlack
= aCol
.IsDark();
2153 bHighContrastWhite
= aCol
.IsBright();
2157 aOldTextColor
= GetTextColor();
2158 if ( IsTextFillColor() )
2160 bRestoreFillColor
= true;
2161 aOldTextFillColor
= GetTextFillColor();
2164 bRestoreFillColor
= false;
2166 if( bHighContrastBlack
)
2167 SetTextColor( COL_GREEN
);
2168 else if( bHighContrastWhite
)
2169 SetTextColor( COL_LIGHTGREEN
);
2171 SetTextColor( GetSettings().GetStyleSettings().GetDisableColor() );
2173 DrawText( rPos
, aStr
, nIndex
, nLen
, pVector
, pDisplayText
);
2174 if (!(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics
)
2175 && (!autoacc
|| !(nStyle
& DrawTextFlags::HideMnemonic
)) )
2177 if ( nMnemonicPos
!= -1 )
2178 ImplDrawMnemonicLine( nMnemonicX
, nMnemonicY
, nMnemonicWidth
);
2180 SetTextColor( aOldTextColor
);
2181 if ( bRestoreFillColor
)
2182 SetTextFillColor( aOldTextFillColor
);
2186 DrawText( rPos
, aStr
, nIndex
, nLen
, pVector
, pDisplayText
, pGlyphs
);
2187 if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics
) && !pVector
2188 && (!autoacc
|| !(nStyle
& DrawTextFlags::HideMnemonic
)) )
2190 if ( nMnemonicPos
!= -1 )
2191 ImplDrawMnemonicLine( nMnemonicX
, nMnemonicY
, nMnemonicWidth
);
2196 mpAlphaVDev
->DrawCtrlText( rPos
, rStr
, nIndex
, nLen
, nStyle
, pVector
, pDisplayText
);
2199 long OutputDevice::GetCtrlTextWidth( const OUString
& rStr
, const SalLayoutGlyphs
* pGlyphs
) const
2201 sal_Int32 nLen
= rStr
.getLength();
2202 sal_Int32 nIndex
= 0;
2204 sal_Int32 nMnemonicPos
;
2205 OUString aStr
= GetNonMnemonicString( rStr
, nMnemonicPos
);
2206 if ( nMnemonicPos
!= -1 )
2208 if ( nMnemonicPos
< nIndex
)
2210 else if (static_cast<sal_uLong
>(nMnemonicPos
) < static_cast<sal_uLong
>(nIndex
+nLen
))
2213 return GetTextWidth( aStr
, nIndex
, nLen
, nullptr, pGlyphs
);
2216 OUString
OutputDevice::GetNonMnemonicString( const OUString
& rStr
, sal_Int32
& rMnemonicPos
)
2218 OUString aStr
= rStr
;
2219 sal_Int32 nLen
= aStr
.getLength();
2225 if ( aStr
[ i
] == '~' )
2230 if ( aStr
[ i
+1 ] != '~' )
2232 if ( rMnemonicPos
== -1 )
2234 aStr
= aStr
.replaceAt( i
, 1, "" );
2239 aStr
= aStr
.replaceAt( i
, 1, "" );
2251 /** OutputDevice::GetSysTextLayoutData
2253 * @param rStartPt Start point of the text
2254 * @param rStr Text string that will be transformed into layout of glyphs
2255 * @param nIndex Position in the string from where layout will be done
2256 * @param nLen Length of the string
2257 * @param pDXAry Custom layout adjustment data
2259 * Export finalized glyph layout data as platform independent SystemTextLayoutData
2260 * (see vcl/inc/vcl/sysdata.hxx)
2262 * Only parameters rStartPt and rStr are mandatory, the rest is optional
2263 * (default values will be used)
2265 * @return SystemTextLayoutData
2267 SystemTextLayoutData
OutputDevice::GetSysTextLayoutData(const Point
& rStartPt
, const OUString
& rStr
, sal_Int32 nIndex
, sal_Int32 nLen
,
2268 const long* pDXAry
) const
2270 if( (nLen
< 0) || (nIndex
+ nLen
>= rStr
.getLength()))
2272 nLen
= rStr
.getLength() - nIndex
;
2275 SystemTextLayoutData aSysLayoutData
;
2276 aSysLayoutData
.rGlyphData
.reserve( 256 );
2277 aSysLayoutData
.orientation
= 0;
2282 mpMetaFile
->AddAction( new MetaTextArrayAction( rStartPt
, rStr
, pDXAry
, nIndex
, nLen
) );
2284 mpMetaFile
->AddAction( new MetaTextAction( rStartPt
, rStr
, nIndex
, nLen
) );
2287 if ( !IsDeviceOutputNecessary() ) return aSysLayoutData
;
2289 std::unique_ptr
<SalLayout
> pLayout
= ImplLayout(rStr
, nIndex
, nLen
, rStartPt
, 0, pDXAry
);
2291 if ( !pLayout
) return aSysLayoutData
;
2295 const GlyphItem
* pGlyph
;
2297 SystemGlyphData aSystemGlyph
;
2298 while (pLayout
->GetNextGlyph(&pGlyph
, aPos
, nStart
, nullptr, &aSystemGlyph
.fallbacklevel
))
2300 aSystemGlyph
.index
= pGlyph
->glyphId();
2301 aSystemGlyph
.x
= aPos
.X();
2302 aSystemGlyph
.y
= aPos
.Y();
2303 aSysLayoutData
.rGlyphData
.push_back(aSystemGlyph
);
2307 aSysLayoutData
.orientation
= pLayout
->GetOrientation();
2309 return aSysLayoutData
;
2312 bool OutputDevice::GetTextBoundRect( tools::Rectangle
& rRect
,
2313 const OUString
& rStr
, sal_Int32 nBase
,
2314 sal_Int32 nIndex
, sal_Int32 nLen
,
2315 sal_uLong nLayoutWidth
, const long* pDXAry
,
2316 const SalLayoutGlyphs
* pGlyphs
) const
2321 std::unique_ptr
<SalLayout
> pSalLayout
;
2323 // calculate offset when nBase!=nIndex
2325 if( nBase
!= nIndex
)
2327 sal_Int32 nStart
= std::min( nBase
, nIndex
);
2328 sal_Int32 nOfsLen
= std::max( nBase
, nIndex
) - nStart
;
2329 pSalLayout
= ImplLayout( rStr
, nStart
, nOfsLen
, aPoint
, nLayoutWidth
, pDXAry
);
2332 nXOffset
= pSalLayout
->GetTextWidth();
2333 nXOffset
/= pSalLayout
->GetUnitsPerPixel();
2334 // TODO: fix offset calculation for Bidi case
2336 nXOffset
= -nXOffset
;
2340 pSalLayout
= ImplLayout(rStr
, nIndex
, nLen
, aPoint
, nLayoutWidth
, pDXAry
, SalLayoutFlags::NONE
,
2342 tools::Rectangle aPixelRect
;
2345 bRet
= pSalLayout
->GetBoundRect(aPixelRect
);
2349 int nWidthFactor
= pSalLayout
->GetUnitsPerPixel();
2351 if( nWidthFactor
> 1 )
2353 double fFactor
= 1.0 / nWidthFactor
;
2355 static_cast< long >(aPixelRect
.Left() * fFactor
) );
2356 aPixelRect
.SetRight(
2357 static_cast< long >(aPixelRect
.Right() * fFactor
) );
2359 static_cast< long >(aPixelRect
.Top() * fFactor
) );
2360 aPixelRect
.SetBottom(
2361 static_cast< long >(aPixelRect
.Bottom() * fFactor
) );
2364 Point
aRotatedOfs( mnTextOffX
, mnTextOffY
);
2365 aRotatedOfs
-= pSalLayout
->GetDrawPosition( Point( nXOffset
, 0 ) );
2366 aPixelRect
+= aRotatedOfs
;
2367 rRect
= PixelToLogic( aPixelRect
);
2369 rRect
+= Point( maMapRes
.mnMapOfsX
, maMapRes
.mnMapOfsY
);
2376 bool OutputDevice::GetTextOutlines( basegfx::B2DPolyPolygonVector
& rVector
,
2377 const OUString
& rStr
, sal_Int32 nBase
,
2378 sal_Int32 nIndex
, sal_Int32 nLen
,
2379 sal_uLong nLayoutWidth
, const long* pDXArray
) const
2388 nLen
= rStr
.getLength() - nIndex
;
2390 rVector
.reserve( nLen
);
2392 // we want to get the Rectangle in logical units, so to
2393 // avoid rounding errors we just size the font in logical units
2394 bool bOldMap
= mbMap
;
2397 const_cast<OutputDevice
&>(*this).mbMap
= false;
2398 const_cast<OutputDevice
&>(*this).mbNewFont
= true;
2401 std::unique_ptr
<SalLayout
> pSalLayout
;
2403 // calculate offset when nBase!=nIndex
2405 if( nBase
!= nIndex
)
2407 sal_Int32 nStart
= std::min( nBase
, nIndex
);
2408 sal_Int32 nOfsLen
= std::max( nBase
, nIndex
) - nStart
;
2409 pSalLayout
= ImplLayout( rStr
, nStart
, nOfsLen
, Point(0,0), nLayoutWidth
, pDXArray
);
2412 nXOffset
= pSalLayout
->GetTextWidth();
2414 // TODO: fix offset calculation for Bidi case
2416 nXOffset
= -nXOffset
;
2420 pSalLayout
= ImplLayout( rStr
, nIndex
, nLen
, Point(0,0), nLayoutWidth
, pDXArray
);
2423 bRet
= pSalLayout
->GetOutline(rVector
);
2426 // transform polygon to pixel units
2427 basegfx::B2DHomMatrix aMatrix
;
2429 int nWidthFactor
= pSalLayout
->GetUnitsPerPixel();
2430 if( nXOffset
| mnTextOffX
| mnTextOffY
)
2432 Point
aRotatedOfs( mnTextOffX
*nWidthFactor
, mnTextOffY
*nWidthFactor
);
2433 aRotatedOfs
-= pSalLayout
->GetDrawPosition( Point( nXOffset
, 0 ) );
2434 aMatrix
.translate( aRotatedOfs
.X(), aRotatedOfs
.Y() );
2437 if( nWidthFactor
> 1 )
2439 double fFactor
= 1.0 / nWidthFactor
;
2440 aMatrix
.scale( fFactor
, fFactor
);
2443 if( !aMatrix
.isIdentity() )
2445 for (auto & elem
: rVector
)
2446 elem
.transform( aMatrix
);
2455 // restore original font size and map mode
2456 const_cast<OutputDevice
&>(*this).mbMap
= bOldMap
;
2457 const_cast<OutputDevice
&>(*this).mbNewFont
= true;
2463 bool OutputDevice::GetTextOutlines( PolyPolyVector
& rResultVector
,
2464 const OUString
& rStr
, sal_Int32 nBase
,
2465 sal_Int32 nIndex
, sal_Int32 nLen
,
2466 sal_uLong nTWidth
, const long* pDXArray
) const
2468 rResultVector
.clear();
2470 // get the basegfx polypolygon vector
2471 basegfx::B2DPolyPolygonVector aB2DPolyPolyVector
;
2472 if( !GetTextOutlines( aB2DPolyPolyVector
, rStr
, nBase
, nIndex
, nLen
,
2473 nTWidth
, pDXArray
) )
2476 // convert to a tool polypolygon vector
2477 rResultVector
.reserve( aB2DPolyPolyVector
.size() );
2478 for (auto const& elem
: aB2DPolyPolyVector
)
2479 rResultVector
.emplace_back(elem
); // #i76339#
2484 bool OutputDevice::GetTextOutline( tools::PolyPolygon
& rPolyPoly
, const OUString
& rStr
,
2486 sal_uLong nTWidth
, const long* pDXArray
) const
2490 // get the basegfx polypolygon vector
2491 basegfx::B2DPolyPolygonVector aB2DPolyPolyVector
;
2492 if( !GetTextOutlines( aB2DPolyPolyVector
, rStr
, 0/*nBase*/, 0/*nIndex*/, nLen
,
2493 nTWidth
, pDXArray
) )
2496 // convert and merge into a tool polypolygon
2497 for (auto const& elem
: aB2DPolyPolyVector
)
2498 for(auto const& rB2DPolygon
: elem
)
2499 rPolyPoly
.Insert(tools::Polygon(rB2DPolygon
)); // #i76339#
2504 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */