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 .
20 #include <sal/config.h>
22 #include <sal/log.hxx>
23 #include <basegfx/matrix/b2dhommatrix.hxx>
24 #include <basegfx/matrix/b2dhommatrixtools.hxx>
25 #include <tools/lineend.hxx>
26 #include <tools/debug.hxx>
27 #include <comphelper/configuration.hxx>
29 #include <vcl/ctrl.hxx>
30 #include <vcl/metaact.hxx>
31 #include <vcl/metric.hxx>
32 #include <vcl/mnemonic.hxx>
33 #include <vcl/textrectinfo.hxx>
34 #include <vcl/virdev.hxx>
35 #include <vcl/sysdata.hxx>
37 #include <ImplLayoutArgs.hxx>
38 #include <ImplOutDevData.hxx>
39 #include <drawmode.hxx>
42 #include <textlayout.hxx>
43 #include <textlineinfo.hxx>
44 #include <impglyphitem.hxx>
45 #include <TextLayoutCache.hxx>
46 #include <font/PhysicalFontFace.hxx>
51 #define TEXT_DRAW_ELLIPSIS (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis)
53 void OutputDevice::SetLayoutMode( vcl::text::ComplexTextLayoutFlags nTextLayoutMode
)
56 mpMetaFile
->AddAction( new MetaLayoutModeAction( nTextLayoutMode
) );
58 mnTextLayoutMode
= nTextLayoutMode
;
61 mpAlphaVDev
->SetLayoutMode( nTextLayoutMode
);
64 void OutputDevice::SetDigitLanguage( LanguageType eTextLanguage
)
67 mpMetaFile
->AddAction( new MetaTextLanguageAction( eTextLanguage
) );
69 meTextLanguage
= eTextLanguage
;
72 mpAlphaVDev
->SetDigitLanguage( eTextLanguage
);
75 void OutputDevice::ImplInitTextColor()
79 if ( mbInitTextColor
)
81 mpGraphics
->SetTextColor( GetTextColor() );
82 mbInitTextColor
= false;
86 OUString
OutputDevice::GetEllipsisString( const OUString
& rOrigStr
, tools::Long nMaxWidth
,
87 DrawTextFlags nStyle
) const
89 vcl::DefaultTextLayout
aTextLayout(*const_cast< OutputDevice
* >(this));
90 return aTextLayout
.GetEllipsisString(rOrigStr
, nMaxWidth
, nStyle
);
93 void OutputDevice::ImplDrawTextRect( tools::Long nBaseX
, tools::Long nBaseY
,
94 tools::Long nDistX
, tools::Long nDistY
, tools::Long nWidth
, tools::Long nHeight
)
96 tools::Long nX
= nDistX
;
97 tools::Long nY
= nDistY
;
99 Degree10 nOrientation
= mpFontInstance
->mnOrientation
;
102 // Rotate rect without rounding problems for 90 degree rotations
103 if ( !(nOrientation
% 900_deg10
) )
105 if ( nOrientation
== 900_deg10
)
107 tools::Long nTemp
= nX
;
115 else if ( nOrientation
== 1800_deg10
)
122 else /* ( nOrientation == 2700 ) */
124 tools::Long nTemp
= nX
;
137 // inflate because polygons are drawn smaller
138 tools::Rectangle
aRect( Point( nX
, nY
), Size( nWidth
+1, nHeight
+1 ) );
139 tools::Polygon
aPoly( aRect
);
140 aPoly
.Rotate( Point( nBaseX
, nBaseY
), mpFontInstance
->mnOrientation
);
141 ImplDrawPolygon( aPoly
);
148 mpGraphics
->DrawRect( nX
, nY
, nWidth
, nHeight
, *this ); // original code
152 void OutputDevice::ImplDrawTextBackground( const SalLayout
& rSalLayout
)
154 const double nWidth
= rSalLayout
.GetTextWidth();
155 const basegfx::B2DPoint aBase
= rSalLayout
.DrawBase();
156 const tools::Long nX
= aBase
.getX();
157 const tools::Long nY
= aBase
.getY();
159 if ( mbLineColor
|| mbInitLineColor
)
161 mpGraphics
->SetLineColor();
162 mbInitLineColor
= true;
164 mpGraphics
->SetFillColor( GetTextFillColor() );
165 mbInitFillColor
= true;
167 ImplDrawTextRect( nX
, nY
, 0, -(mpFontInstance
->mxFontMetric
->GetAscent() + mnEmphasisAscent
),
169 mpFontInstance
->mnLineHeight
+mnEmphasisAscent
+mnEmphasisDescent
);
172 tools::Rectangle
OutputDevice::ImplGetTextBoundRect( const SalLayout
& rSalLayout
) const
174 basegfx::B2DPoint aPoint
= rSalLayout
.GetDrawPosition();
175 tools::Long nX
= aPoint
.getX();
176 tools::Long nY
= aPoint
.getY();
178 double nWidth
= rSalLayout
.GetTextWidth();
179 tools::Long nHeight
= mpFontInstance
->mnLineHeight
+ mnEmphasisAscent
+ mnEmphasisDescent
;
181 nY
-= mpFontInstance
->mxFontMetric
->GetAscent() + mnEmphasisAscent
;
183 if ( mpFontInstance
->mnOrientation
)
185 tools::Long nBaseX
= nX
, nBaseY
= nY
;
186 if ( !(mpFontInstance
->mnOrientation
% 900_deg10
) )
188 tools::Long nX2
= nX
+nWidth
;
189 tools::Long nY2
= nY
+nHeight
;
191 Point
aBasePt( nBaseX
, nBaseY
);
192 aBasePt
.RotateAround( nX
, nY
, mpFontInstance
->mnOrientation
);
193 aBasePt
.RotateAround( nX2
, nY2
, mpFontInstance
->mnOrientation
);
199 // inflate by +1+1 because polygons are drawn smaller
200 tools::Rectangle
aRect( Point( nX
, nY
), Size( nWidth
+1, nHeight
+1 ) );
201 tools::Polygon
aPoly( aRect
);
202 aPoly
.Rotate( Point( nBaseX
, nBaseY
), mpFontInstance
->mnOrientation
);
203 return aPoly
.GetBoundRect();
207 return tools::Rectangle( Point( nX
, nY
), Size( nWidth
, nHeight
) );
210 bool OutputDevice::ImplDrawRotateText( SalLayout
& rSalLayout
)
212 tools::Long nX
= rSalLayout
.DrawBase().getX();
213 tools::Long nY
= rSalLayout
.DrawBase().getY();
215 tools::Rectangle aBoundRect
;
216 rSalLayout
.DrawBase() = basegfx::B2DPoint( 0, 0 );
217 rSalLayout
.DrawOffset() = basegfx::B2DPoint
{ 0, 0 };
218 if (basegfx::B2DRectangle r
; rSalLayout
.GetBoundRect(r
))
220 aBoundRect
= SalLayout::BoundRect2Rectangle(r
);
224 // guess vertical text extents if GetBoundRect failed
225 double nRight
= rSalLayout
.GetTextWidth();
226 tools::Long nTop
= mpFontInstance
->mxFontMetric
->GetAscent() + mnEmphasisAscent
;
227 tools::Long nHeight
= mpFontInstance
->mnLineHeight
+ mnEmphasisAscent
+ mnEmphasisDescent
;
228 aBoundRect
= tools::Rectangle( 0, -nTop
, nRight
, nHeight
- nTop
);
231 // cache virtual device for rotation
232 if (!mpOutDevData
->mpRotateDev
)
233 mpOutDevData
->mpRotateDev
= VclPtr
<VirtualDevice
>::Create(*this);
234 VirtualDevice
* pVDev
= mpOutDevData
->mpRotateDev
;
236 // size it accordingly
237 if( !pVDev
->SetOutputSizePixel( aBoundRect
.GetSize() ) )
240 const vcl::font::FontSelectPattern
& rPattern
= mpFontInstance
->GetFontSelectPattern();
241 vcl::Font
aFont( GetFont() );
242 aFont
.SetOrientation( 0_deg10
);
243 aFont
.SetFontSize( Size( rPattern
.mnWidth
, rPattern
.mnHeight
) );
244 pVDev
->SetFont( aFont
);
245 pVDev
->SetTextColor( COL_BLACK
);
246 pVDev
->SetTextFillColor();
247 if (!pVDev
->InitFont())
249 pVDev
->ImplInitTextColor();
251 // draw text into upper left corner
252 rSalLayout
.DrawBase().adjustX(-aBoundRect
.Left());
253 rSalLayout
.DrawBase().adjustY(-aBoundRect
.Top());
254 rSalLayout
.DrawText( *pVDev
->mpGraphics
);
256 Bitmap aBmp
= pVDev
->GetBitmap( Point(), aBoundRect
.GetSize() );
257 if ( aBmp
.IsEmpty() || !aBmp
.Rotate( mpFontInstance
->mnOwnOrientation
, COL_WHITE
) )
260 // calculate rotation offset
261 tools::Polygon
aPoly( aBoundRect
);
262 aPoly
.Rotate( Point(), mpFontInstance
->mnOwnOrientation
);
263 Point aPoint
= aPoly
.GetBoundRect().TopLeft();
264 aPoint
+= Point( nX
, nY
);
266 // mask output with text colored bitmap
267 GDIMetaFile
* pOldMetaFile
= mpMetaFile
;
268 tools::Long nOldOffX
= mnOutOffX
;
269 tools::Long nOldOffY
= mnOutOffY
;
270 bool bOldMap
= mbMap
;
274 mpMetaFile
= nullptr;
275 EnableMapMode( false );
277 DrawMask( aPoint
, aBmp
, GetTextColor() );
279 EnableMapMode( bOldMap
);
280 mnOutOffX
= nOldOffX
;
281 mnOutOffY
= nOldOffY
;
282 mpMetaFile
= pOldMetaFile
;
287 void OutputDevice::ImplDrawTextDirect( SalLayout
& rSalLayout
,
290 if( mpFontInstance
->mnOwnOrientation
)
291 if( ImplDrawRotateText( rSalLayout
) )
294 auto nOldX
= rSalLayout
.DrawBase().getX();
295 if( HasMirroredGraphics() )
297 tools::Long w
= IsVirtual() ? mnOutWidth
: mpGraphics
->GetGraphicsWidth();
298 auto x
= rSalLayout
.DrawBase().getX();
299 rSalLayout
.DrawBase().setX( w
- 1 - x
);
300 if( !IsRTLEnabled() )
302 OutputDevice
*pOutDevRef
= this;
303 // mirror this window back
304 tools::Long devX
= w
-pOutDevRef
->mnOutWidth
-pOutDevRef
->mnOutOffX
; // re-mirrored mnOutOffX
305 rSalLayout
.DrawBase().setX( devX
+ ( pOutDevRef
->mnOutWidth
- 1 - (rSalLayout
.DrawBase().getX() - devX
) ) ) ;
308 else if( IsRTLEnabled() )
310 OutputDevice
*pOutDevRef
= this;
312 // mirror this window back
313 tools::Long devX
= pOutDevRef
->mnOutOffX
; // re-mirrored mnOutOffX
314 rSalLayout
.DrawBase().setX( pOutDevRef
->mnOutWidth
- 1 - (rSalLayout
.DrawBase().getX() - devX
) + devX
);
317 rSalLayout
.DrawText( *mpGraphics
);
318 rSalLayout
.DrawBase().setX( nOldX
);
321 ImplDrawTextLines( rSalLayout
,
322 maFont
.GetStrikeout(), maFont
.GetUnderline(), maFont
.GetOverline(),
323 maFont
.IsWordLineMode(), maFont
.IsUnderlineAbove() );
326 if( maFont
.GetEmphasisMark() & FontEmphasisMark::Style
)
327 ImplDrawEmphasisMarks( rSalLayout
);
330 void OutputDevice::ImplDrawSpecialText( SalLayout
& rSalLayout
)
332 Color aOldColor
= GetTextColor();
333 Color aOldTextLineColor
= GetTextLineColor();
334 Color aOldOverlineColor
= GetOverlineColor();
335 FontRelief eRelief
= maFont
.GetRelief();
337 basegfx::B2DPoint aOrigPos
= rSalLayout
.DrawBase();
338 if ( eRelief
!= FontRelief::NONE
)
340 Color
aReliefColor( COL_LIGHTGRAY
);
341 Color
aTextColor( aOldColor
);
343 Color
aTextLineColor( aOldTextLineColor
);
344 Color
aOverlineColor( aOldOverlineColor
);
346 // we don't have an automatic color, so black is always drawn on white
347 if ( aTextColor
== COL_BLACK
)
348 aTextColor
= COL_WHITE
;
349 if ( aTextLineColor
== COL_BLACK
)
350 aTextLineColor
= COL_WHITE
;
351 if ( aOverlineColor
== COL_BLACK
)
352 aOverlineColor
= COL_WHITE
;
354 // relief-color is black for white text, in all other cases
355 // we set this to LightGray
356 // coverity[copy_paste_error: FALSE] - this is intentional
357 if ( aTextColor
== COL_WHITE
)
358 aReliefColor
= COL_BLACK
;
359 SetTextLineColor( aReliefColor
);
360 SetOverlineColor( aReliefColor
);
361 SetTextColor( aReliefColor
);
364 // calculate offset - for high resolution printers the offset
365 // should be greater so that the effect is visible
366 tools::Long nOff
= 1;
369 if ( eRelief
== FontRelief::Engraved
)
372 auto aPrevOffset
= rSalLayout
.DrawOffset();
373 rSalLayout
.DrawOffset()
374 += basegfx::B2DPoint
{ static_cast<double>(nOff
), static_cast<double>(nOff
) };
375 ImplDrawTextDirect(rSalLayout
, mbTextLines
);
376 rSalLayout
.DrawOffset() = aPrevOffset
;
378 SetTextLineColor( aTextLineColor
);
379 SetOverlineColor( aOverlineColor
);
380 SetTextColor( aTextColor
);
382 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
384 SetTextLineColor( aOldTextLineColor
);
385 SetOverlineColor( aOldOverlineColor
);
387 if ( aTextColor
!= aOldColor
)
389 SetTextColor( aOldColor
);
395 if ( maFont
.IsShadow() )
397 tools::Long nOff
= 1 + ((mpFontInstance
->mnLineHeight
-24)/24);
398 if ( maFont
.IsOutline() )
402 if ( (GetTextColor() == COL_BLACK
)
403 || (GetTextColor().GetLuminance() < 8) )
404 SetTextColor( COL_LIGHTGRAY
);
406 SetTextColor( COL_BLACK
);
408 rSalLayout
.DrawBase() += basegfx::B2DPoint( nOff
, nOff
);
409 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
410 rSalLayout
.DrawBase() -= basegfx::B2DPoint( nOff
, nOff
);
411 SetTextColor( aOldColor
);
412 SetTextLineColor( aOldTextLineColor
);
413 SetOverlineColor( aOldOverlineColor
);
416 if ( !maFont
.IsOutline() )
417 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
420 if ( maFont
.IsOutline() )
422 rSalLayout
.DrawBase() = aOrigPos
+ basegfx::B2DPoint(-1,-1);
423 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
424 rSalLayout
.DrawBase() = aOrigPos
+ basegfx::B2DPoint(+1,+1);
425 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
426 rSalLayout
.DrawBase() = aOrigPos
+ basegfx::B2DPoint(-1,+0);
427 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
428 rSalLayout
.DrawBase() = aOrigPos
+ basegfx::B2DPoint(-1,+1);
429 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
430 rSalLayout
.DrawBase() = aOrigPos
+ basegfx::B2DPoint(+0,+1);
431 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
432 rSalLayout
.DrawBase() = aOrigPos
+ basegfx::B2DPoint(+0,-1);
433 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
434 rSalLayout
.DrawBase() = aOrigPos
+ basegfx::B2DPoint(+1,-1);
435 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
436 rSalLayout
.DrawBase() = aOrigPos
+ basegfx::B2DPoint(+1,+0);
437 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
438 rSalLayout
.DrawBase() = aOrigPos
;
440 SetTextColor( COL_WHITE
);
441 SetTextLineColor( COL_WHITE
);
442 SetOverlineColor( COL_WHITE
);
444 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
445 SetTextColor( aOldColor
);
446 SetTextLineColor( aOldTextLineColor
);
447 SetOverlineColor( aOldOverlineColor
);
453 void OutputDevice::ImplDrawText( SalLayout
& rSalLayout
)
455 if( mbInitClipRegion
)
457 if( mbOutputClipped
)
459 if( mbInitTextColor
)
462 rSalLayout
.DrawBase() += basegfx::B2DPoint(mnTextOffX
, mnTextOffY
);
464 if( IsTextFillColor() )
465 ImplDrawTextBackground( rSalLayout
);
468 ImplDrawSpecialText( rSalLayout
);
470 ImplDrawTextDirect( rSalLayout
, mbTextLines
);
473 void OutputDevice::SetTextColor( const Color
& rColor
)
476 Color
aColor(vcl::drawmode::GetTextColor(rColor
, GetDrawMode(), GetSettings().GetStyleSettings()));
479 mpMetaFile
->AddAction( new MetaTextColorAction( aColor
) );
481 if ( maTextColor
!= aColor
)
483 maTextColor
= aColor
;
484 mbInitTextColor
= true;
488 mpAlphaVDev
->SetTextColor( COL_ALPHA_OPAQUE
);
491 void OutputDevice::SetTextFillColor()
495 mpMetaFile
->AddAction( new MetaTextFillColorAction( Color(), false ) );
497 if ( maFont
.GetColor() != COL_TRANSPARENT
) {
498 maFont
.SetFillColor( COL_TRANSPARENT
);
500 if ( !maFont
.IsTransparent() )
501 maFont
.SetTransparent( true );
504 mpAlphaVDev
->SetTextFillColor();
507 void OutputDevice::SetTextFillColor( const Color
& rColor
)
509 Color
aColor(vcl::drawmode::GetFillColor(rColor
, GetDrawMode(), GetSettings().GetStyleSettings()));
512 mpMetaFile
->AddAction( new MetaTextFillColorAction( aColor
, true ) );
514 if ( maFont
.GetFillColor() != aColor
)
515 maFont
.SetFillColor( aColor
);
516 if ( maFont
.IsTransparent() != rColor
.IsTransparent() )
517 maFont
.SetTransparent( rColor
.IsTransparent() );
520 mpAlphaVDev
->SetTextFillColor( COL_ALPHA_OPAQUE
);
523 Color
OutputDevice::GetTextFillColor() const
525 if ( maFont
.IsTransparent() )
526 return COL_TRANSPARENT
;
528 return maFont
.GetFillColor();
531 void OutputDevice::SetTextAlign( TextAlign eAlign
)
535 mpMetaFile
->AddAction( new MetaTextAlignAction( eAlign
) );
537 if ( maFont
.GetAlignment() != eAlign
)
539 maFont
.SetAlignment( eAlign
);
544 mpAlphaVDev
->SetTextAlign( eAlign
);
547 vcl::Region
OutputDevice::GetOutputBoundsClipRegion() const
549 return GetClipRegion();
552 const SalLayoutFlags eDefaultLayout
= SalLayoutFlags::NONE
;
554 void OutputDevice::DrawText( const Point
& rStartPt
, const OUString
& rStr
,
555 sal_Int32 nIndex
, sal_Int32 nLen
,
556 std::vector
< tools::Rectangle
>* pVector
, OUString
* pDisplayText
,
557 const SalLayoutGlyphs
* pLayoutCache
560 assert(!is_double_buffered_window());
562 if( (nLen
< 0) || (nIndex
+ nLen
> rStr
.getLength()))
564 nLen
= rStr
.getLength() - nIndex
;
567 if (mpOutDevData
->mpRecordLayout
)
569 pVector
= &mpOutDevData
->mpRecordLayout
->m_aUnicodeBoundRects
;
570 pDisplayText
= &mpOutDevData
->mpRecordLayout
->m_aDisplayText
;
573 #if OSL_DEBUG_LEVEL > 2
574 SAL_INFO("vcl.gdi", "OutputDevice::DrawText(\"" << rStr
<< "\")");
578 mpMetaFile
->AddAction( new MetaTextAction( rStartPt
, rStr
, nIndex
, nLen
) );
581 vcl::Region
aClip(GetOutputBoundsClipRegion());
583 if (mpOutDevData
->mpRecordLayout
)
585 mpOutDevData
->mpRecordLayout
->m_aLineIndices
.push_back( mpOutDevData
->mpRecordLayout
->m_aDisplayText
.getLength() );
586 aClip
.Intersect( mpOutDevData
->maRecordRect
);
588 if( ! aClip
.IsNull() )
590 std::vector
< tools::Rectangle
> aTmp
;
591 GetGlyphBoundRects( rStartPt
, rStr
, nIndex
, nLen
, aTmp
);
593 bool bInserted
= false;
594 for( std::vector
< tools::Rectangle
>::const_iterator it
= aTmp
.begin(); it
!= aTmp
.end(); ++it
, nIndex
++ )
596 bool bAppend
= false;
598 if( aClip
.Overlaps( *it
) )
600 else if( rStr
[ nIndex
] == ' ' && bInserted
)
602 std::vector
< tools::Rectangle
>::const_iterator next
= it
;
604 if( next
!= aTmp
.end() && aClip
.Overlaps( *next
) )
610 pVector
->push_back( *it
);
612 *pDisplayText
+= OUStringChar(rStr
[ nIndex
]);
619 GetGlyphBoundRects( rStartPt
, rStr
, nIndex
, nLen
, *pVector
);
621 *pDisplayText
+= rStr
.subView( nIndex
, nLen
);
625 if ( !IsDeviceOutputNecessary() || pVector
)
629 // do not use cache with modified string
630 if(mpFontInstance
->mpConversion
)
631 pLayoutCache
= nullptr;
633 std::unique_ptr
<SalLayout
> pSalLayout
= ImplLayout(rStr
, nIndex
, nLen
, rStartPt
, 0, {}, {}, eDefaultLayout
, nullptr, pLayoutCache
);
636 ImplDrawText( *pSalLayout
);
640 mpAlphaVDev
->DrawText( rStartPt
, rStr
, nIndex
, nLen
, pVector
, pDisplayText
);
643 tools::Long
OutputDevice::GetTextWidth( const OUString
& rStr
, sal_Int32 nIndex
, sal_Int32 nLen
,
644 vcl::text::TextLayoutCache
const*const pLayoutCache
,
645 SalLayoutGlyphs
const*const pSalLayoutCache
) const
647 double nWidth
= GetTextWidthDouble(rStr
, nIndex
, nLen
, pLayoutCache
, pSalLayoutCache
);
648 return basegfx::fround
<tools::Long
>(nWidth
);
651 double OutputDevice::GetTextWidthDouble(const OUString
& rStr
, sal_Int32 nIndex
, sal_Int32 nLen
,
652 vcl::text::TextLayoutCache
const* const pLayoutCache
,
653 SalLayoutGlyphs
const* const pSalLayoutCache
) const
655 return GetTextArray(rStr
, nullptr, nIndex
, nLen
, false, pLayoutCache
, pSalLayoutCache
).nWidth
;
658 tools::Long
OutputDevice::GetTextHeight() const
663 tools::Long nHeight
= mpFontInstance
->mnLineHeight
+ mnEmphasisAscent
+ mnEmphasisDescent
;
666 nHeight
= ImplDevicePixelToLogicHeight( nHeight
);
671 double OutputDevice::GetTextHeightDouble() const
676 tools::Long nHeight
= mpFontInstance
->mnLineHeight
+ mnEmphasisAscent
+ mnEmphasisDescent
;
678 return ImplDevicePixelToLogicHeightDouble(nHeight
);
681 float OutputDevice::approximate_char_width() const
683 //note pango uses "The quick brown fox jumps over the lazy dog." for english
684 //and has a bunch of per-language strings which corresponds somewhat with
685 //makeRepresentativeText in include/svtools/sampletext.hxx
686 return GetTextWidth(u
"aemnnxEM"_ustr
) / 8.0;
689 float OutputDevice::approximate_digit_width() const
691 return GetTextWidth(u
"0123456789"_ustr
) / 10.0;
694 void OutputDevice::DrawPartialTextArray(const Point
& rStartPt
, const OUString
& rStr
,
695 KernArraySpan pDXArray
,
696 std::span
<const sal_Bool
> pKashidaArray
, sal_Int32 nIndex
,
697 sal_Int32 nLen
, sal_Int32 nPartIndex
, sal_Int32 nPartLen
,
698 SalLayoutFlags flags
, const SalLayoutGlyphs
* pLayoutCache
)
700 assert(!is_double_buffered_window());
702 if (nLen
< 0 || nIndex
+ nLen
>= rStr
.getLength())
704 nLen
= rStr
.getLength() - nIndex
;
707 if (nPartLen
< 0 || nPartIndex
+ nPartLen
>= rStr
.getLength())
709 nPartLen
= rStr
.getLength() - nPartIndex
;
714 mpMetaFile
->AddAction(new MetaTextArrayAction(rStartPt
, rStr
, pDXArray
, pKashidaArray
,
715 nPartIndex
, nPartLen
, nIndex
, nLen
));
718 if (!IsDeviceOutputNecessary())
721 if (!mpGraphics
&& !AcquireGraphics())
725 if (mbInitClipRegion
)
731 // Adding the UnclusteredGlyphs flag during layout enables per-glyph styling.
732 std::unique_ptr
<SalLayout
> pSalLayout
733 = ImplLayout(rStr
, nIndex
, nLen
, rStartPt
, 0, pDXArray
, pKashidaArray
,
734 flags
| SalLayoutFlags::UnclusteredGlyphs
, nullptr, pLayoutCache
,
735 /*pivot cluster*/ nPartIndex
,
736 /*min cluster*/ nPartIndex
,
737 /*end cluster*/ nPartIndex
+ nPartLen
);
741 ImplDrawText(*pSalLayout
);
746 mpAlphaVDev
->DrawPartialTextArray(rStartPt
, rStr
, pDXArray
, pKashidaArray
, nIndex
, nLen
,
747 nPartIndex
, nPartLen
, flags
, pLayoutCache
);
751 void OutputDevice::DrawTextArray( const Point
& rStartPt
, const OUString
& rStr
,
752 KernArraySpan pDXAry
,
753 std::span
<const sal_Bool
> pKashidaAry
,
754 sal_Int32 nIndex
, sal_Int32 nLen
, SalLayoutFlags flags
,
755 const SalLayoutGlyphs
* pSalLayoutCache
)
757 assert(!is_double_buffered_window());
759 if( nLen
< 0 || nIndex
+ nLen
>= rStr
.getLength() )
761 nLen
= rStr
.getLength() - nIndex
;
764 mpMetaFile
->AddAction( new MetaTextArrayAction( rStartPt
, rStr
, pDXAry
, pKashidaAry
, nIndex
, nLen
) );
766 if ( !IsDeviceOutputNecessary() )
768 if( !mpGraphics
&& !AcquireGraphics() )
771 if( mbInitClipRegion
)
773 if( mbOutputClipped
)
776 std::unique_ptr
<SalLayout
> pSalLayout
= ImplLayout(rStr
, nIndex
, nLen
, rStartPt
, 0, pDXAry
, pKashidaAry
, flags
, nullptr, pSalLayoutCache
);
779 ImplDrawText( *pSalLayout
);
783 mpAlphaVDev
->DrawTextArray( rStartPt
, rStr
, pDXAry
, pKashidaAry
, nIndex
, nLen
, flags
);
786 vcl::TextArrayMetrics
787 OutputDevice::GetTextArray(const OUString
& rStr
, KernArray
* pKernArray
, sal_Int32 nIndex
,
788 sal_Int32 nLen
, bool bCaret
,
789 vcl::text::TextLayoutCache
const* const pLayoutCache
,
790 SalLayoutGlyphs
const* const pSalLayoutCache
) const
792 return GetPartialTextArray(rStr
, pKernArray
, nIndex
, nLen
, nIndex
, nLen
, bCaret
, pLayoutCache
,
796 vcl::TextArrayMetrics
797 OutputDevice::GetPartialTextArray(const OUString
& rStr
, KernArray
* pKernArray
, sal_Int32 nIndex
,
798 sal_Int32 nLen
, sal_Int32 nPartIndex
, sal_Int32 nPartLen
,
799 bool bCaret
, const vcl::text::TextLayoutCache
* pLayoutCache
,
800 const SalLayoutGlyphs
* pSalLayoutCache
) const
802 if (nIndex
>= rStr
.getLength())
804 return {}; // TODO: this looks like a buggy caller?
807 if( nLen
< 0 || nIndex
+ nLen
>= rStr
.getLength() )
809 nLen
= rStr
.getLength() - nIndex
;
812 if (nPartLen
< 0 || nPartIndex
+ nPartLen
>= rStr
.getLength())
814 nPartLen
= rStr
.getLength() - nPartIndex
;
817 KernArray
* pDXAry
= pKernArray
;
820 std::unique_ptr
<SalLayout
> pSalLayout
;
821 if (nIndex
== nPartIndex
&& nLen
== nPartLen
)
823 pSalLayout
= ImplLayout(rStr
, nIndex
, nLen
, Point
{ 0, 0 }, 0, {}, {}, eDefaultLayout
,
824 pLayoutCache
, pSalLayoutCache
);
828 pSalLayout
= ImplLayout(rStr
, nIndex
, nLen
, Point
{ 0, 0 }, 0, {}, {}, eDefaultLayout
,
829 pLayoutCache
, pSalLayoutCache
,
830 /*pivot cluster*/ nPartIndex
,
831 /*min cluster*/ nPartIndex
,
832 /*end cluster*/ nPartIndex
+ nPartLen
);
837 // The caller expects this to init the elements of pDXAry.
838 // Adapting all the callers to check that GetTextArray succeeded seems
840 // Init here to 0 only in the (rare) error case, so that any missing
841 // element init in the happy case will still be found by tools,
842 // and hope that is sufficient.
845 pDXAry
->resize(nPartLen
);
846 std::fill(pDXAry
->begin(), pDXAry
->end(), 0);
852 std::vector
<double> aDXPixelArray
;
853 std::vector
<double>* pDXPixelArray
= nullptr;
856 aDXPixelArray
.resize(nPartLen
);
857 pDXPixelArray
= &aDXPixelArray
;
862 // Fall back to the unbounded DX array when there is no expanded layout context. This is
863 // necessary for certain situations where characters are appended to the input string, such as
864 // automatic ellipsis.
865 if (nIndex
== nPartIndex
&& nLen
== nPartLen
)
867 nWidth
= pSalLayout
->FillDXArray(pDXPixelArray
, bCaret
? rStr
: OUString());
871 nWidth
= pSalLayout
->FillPartialDXArray(pDXPixelArray
, bCaret
? rStr
: OUString(),
872 nPartIndex
- nIndex
, nPartLen
);
875 // convert virtual char widths to virtual absolute positions
878 for (int i
= 1; i
< nPartLen
; ++i
)
880 (*pDXPixelArray
)[i
] += (*pDXPixelArray
)[i
- 1];
884 // convert from font units to logical units
887 assert(pKernArray
&& "pDXPixelArray depends on pKernArray existing");
890 for (int i
= 0; i
< nPartLen
; ++i
)
891 (*pDXPixelArray
)[i
] = ImplDevicePixelToLogicWidthDouble((*pDXPixelArray
)[i
]);
897 pDXAry
->resize(nPartLen
);
898 for (int i
= 0; i
< nPartLen
; ++i
)
899 (*pDXAry
)[i
] = (*pDXPixelArray
)[i
];
902 vcl::TextArrayMetrics stReturnValue
;
904 basegfx::B2DRectangle stRect
;
905 if (pSalLayout
->GetBoundRect(stRect
))
907 auto stRect2
= SalLayout::BoundRect2Rectangle(stRect
);
908 stReturnValue
.aBounds
= ImplDevicePixelToLogic(stRect2
);
911 stReturnValue
.nWidth
= ImplDevicePixelToLogicWidthDouble(nWidth
);
913 return stReturnValue
;
916 void OutputDevice::GetCaretPositions( const OUString
& rStr
, KernArray
& rCaretPos
,
917 sal_Int32 nIndex
, sal_Int32 nLen
,
918 const SalLayoutGlyphs
* pGlyphs
) const
921 if( nIndex
>= rStr
.getLength() )
923 if( nIndex
+nLen
>= rStr
.getLength() )
924 nLen
= rStr
.getLength() - nIndex
;
926 sal_Int32 nCaretPos
= nLen
* 2;
927 rCaretPos
.resize(nCaretPos
);
930 std::unique_ptr
<SalLayout
> pSalLayout
= ImplLayout(rStr
, nIndex
, nLen
, Point(0, 0), 0, {}, {},
931 eDefaultLayout
, nullptr, pGlyphs
);
934 std::fill(rCaretPos
.begin(), rCaretPos
.end(), -1);
938 std::vector
<double> aCaretPixelPos
;
939 pSalLayout
->GetCaretPositions(aCaretPixelPos
, rStr
);
941 // fixup unknown caret positions
943 for (i
= 0; i
< nCaretPos
; ++i
)
944 if (aCaretPixelPos
[i
] >= 0)
946 double nXPos
= (i
< nCaretPos
) ? aCaretPixelPos
[i
] : -1;
947 for (i
= 0; i
< nCaretPos
; ++i
)
949 if (aCaretPixelPos
[i
] >= 0)
950 nXPos
= aCaretPixelPos
[i
];
952 aCaretPixelPos
[i
] = nXPos
;
955 // handle window mirroring
958 double nWidth
= pSalLayout
->GetTextWidth();
959 for (i
= 0; i
< nCaretPos
; ++i
)
960 aCaretPixelPos
[i
] = nWidth
- aCaretPixelPos
[i
] - 1;
963 // convert from font units to logical units
966 for (i
= 0; i
< nCaretPos
; ++i
)
967 aCaretPixelPos
[i
] = ImplDevicePixelToLogicWidthDouble(aCaretPixelPos
[i
]);
970 for (i
= 0; i
< nCaretPos
; ++i
)
971 rCaretPos
[i
] = aCaretPixelPos
[i
];
974 void OutputDevice::DrawStretchText( const Point
& rStartPt
, sal_Int32 nWidth
,
975 const OUString
& rStr
,
976 sal_Int32 nIndex
, sal_Int32 nLen
)
978 assert(!is_double_buffered_window());
980 if( (nLen
< 0) || (nIndex
+ nLen
>= rStr
.getLength()))
982 nLen
= rStr
.getLength() - nIndex
;
986 mpMetaFile
->AddAction( new MetaStretchTextAction( rStartPt
, nWidth
, rStr
, nIndex
, nLen
) );
988 if ( !IsDeviceOutputNecessary() )
991 std::unique_ptr
<SalLayout
> pSalLayout
= ImplLayout(rStr
, nIndex
, nLen
, rStartPt
, nWidth
);
994 ImplDrawText( *pSalLayout
);
998 mpAlphaVDev
->DrawStretchText( rStartPt
, nWidth
, rStr
, nIndex
, nLen
);
1001 vcl::text::ImplLayoutArgs
OutputDevice::ImplPrepareLayoutArgs( OUString
& rStr
,
1002 const sal_Int32 nMinIndex
, const sal_Int32 nLen
,
1004 SalLayoutFlags nLayoutFlags
,
1005 vcl::text::TextLayoutCache
const*const pLayoutCache
) const
1007 assert(nMinIndex
>= 0);
1010 // get string length for calculating extents
1011 sal_Int32 nEndIndex
= rStr
.getLength();
1012 if( nMinIndex
+ nLen
< nEndIndex
)
1013 nEndIndex
= nMinIndex
+ nLen
;
1015 // don't bother if there is nothing to do
1016 if( nEndIndex
< nMinIndex
)
1017 nEndIndex
= nMinIndex
;
1019 nLayoutFlags
|= GetBiDiLayoutFlags( rStr
, nMinIndex
, nEndIndex
);
1021 if( !maFont
.IsKerning() )
1022 nLayoutFlags
|= SalLayoutFlags::DisableKerning
;
1023 if( maFont
.GetKerning() & FontKerning::Asian
)
1024 nLayoutFlags
|= SalLayoutFlags::KerningAsian
;
1025 if( maFont
.IsVertical() )
1026 nLayoutFlags
|= SalLayoutFlags::Vertical
;
1027 if( maFont
.IsFixKerning() ||
1028 ( mpFontInstance
&& mpFontInstance
->GetFontSelectPattern().GetPitch() == PITCH_FIXED
) )
1029 nLayoutFlags
|= SalLayoutFlags::DisableLigatures
;
1031 if( meTextLanguage
) //TODO: (mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::SubstituteDigits)
1033 // disable character localization when no digits used
1034 const sal_Unicode
* pBase
= rStr
.getStr();
1035 const sal_Unicode
* pStr
= pBase
+ nMinIndex
;
1036 const sal_Unicode
* pEnd
= pBase
+ nEndIndex
;
1037 std::optional
<OUStringBuffer
> xTmpStr
;
1038 for( ; pStr
< pEnd
; ++pStr
)
1040 // TODO: are there non-digit localizations?
1041 if( (*pStr
>= '0') && (*pStr
<= '9') )
1043 // translate characters to local preference
1044 sal_UCS4 cChar
= GetLocalizedChar( *pStr
, meTextLanguage
);
1045 if( cChar
!= *pStr
)
1048 xTmpStr
= OUStringBuffer(rStr
);
1049 // TODO: are the localized digit surrogates?
1050 (*xTmpStr
)[pStr
- pBase
] = cChar
;
1055 rStr
= (*xTmpStr
).makeStringAndClear();
1058 // right align for RTL text, DRAWPOS_REVERSED, RTL window style
1059 bool bRightAlign
= bool(mnTextLayoutMode
& vcl::text::ComplexTextLayoutFlags::BiDiRtl
);
1060 if( mnTextLayoutMode
& vcl::text::ComplexTextLayoutFlags::TextOriginLeft
)
1061 bRightAlign
= false;
1062 else if ( mnTextLayoutMode
& vcl::text::ComplexTextLayoutFlags::TextOriginRight
)
1064 // SSA: hack for western office, ie text get right aligned
1065 // for debugging purposes of mirrored UI
1066 bool bRTLWindow
= IsRTLEnabled();
1067 bRightAlign
^= bRTLWindow
;
1069 nLayoutFlags
|= SalLayoutFlags::RightAlign
;
1071 // set layout options
1072 vcl::text::ImplLayoutArgs
aLayoutArgs(rStr
, nMinIndex
, nEndIndex
, nLayoutFlags
, maFont
.GetLanguageTag(), pLayoutCache
);
1074 Degree10 nOrientation
= mpFontInstance
? mpFontInstance
->mnOrientation
: 0_deg10
;
1075 aLayoutArgs
.SetOrientation( nOrientation
);
1077 aLayoutArgs
.SetLayoutWidth( nPixelWidth
);
1082 SalLayoutFlags
OutputDevice::GetBiDiLayoutFlags( std::u16string_view rStr
,
1083 const sal_Int32 nMinIndex
,
1084 const sal_Int32 nEndIndex
) const
1086 SalLayoutFlags nLayoutFlags
= SalLayoutFlags::NONE
;
1087 if( mnTextLayoutMode
& vcl::text::ComplexTextLayoutFlags::BiDiRtl
)
1088 nLayoutFlags
|= SalLayoutFlags::BiDiRtl
;
1089 if( mnTextLayoutMode
& vcl::text::ComplexTextLayoutFlags::BiDiStrong
)
1090 nLayoutFlags
|= SalLayoutFlags::BiDiStrong
;
1091 else if( !(mnTextLayoutMode
& vcl::text::ComplexTextLayoutFlags::BiDiRtl
) )
1093 // Disable Bidi if no RTL hint and only known LTR codes used.
1094 bool bAllLtr
= true;
1095 for (sal_Int32 i
= nMinIndex
; i
< nEndIndex
; i
++)
1097 // [0x0000, 0x052F] are Latin, Greek and Cyrillic.
1098 // [0x0370, 0x03FF] has a few holes as if Unicode 10.0.0, but
1099 // hopefully no RTL character will be encoded there.
1100 if (rStr
[i
] > 0x052F)
1107 nLayoutFlags
|= SalLayoutFlags::BiDiStrong
;
1109 return nLayoutFlags
;
1112 static OutputDevice::FontMappingUseData
* fontMappingUseData
= nullptr;
1114 static inline bool IsTrackingFontMappingUse()
1116 return fontMappingUseData
!= nullptr;
1119 static void TrackFontMappingUse( const vcl::Font
& originalFont
, const SalLayout
* salLayout
)
1121 assert(fontMappingUseData
);
1122 OUString originalName
= originalFont
.GetStyleName().isEmpty()
1123 ? originalFont
.GetFamilyName()
1124 : originalFont
.GetFamilyName() + "/" + originalFont
.GetStyleName();
1125 std::vector
<OUString
> usedFontNames
;
1126 SalLayoutGlyphs glyphs
= salLayout
->GetGlyphs(); // includes all font fallbacks
1128 while( const SalLayoutGlyphsImpl
* impl
= glyphs
.Impl(level
++))
1130 const vcl::font::PhysicalFontFace
* face
= impl
->GetFont()->GetFontFace();
1131 OUString name
= face
->GetStyleName().isEmpty()
1132 ? face
->GetFamilyName()
1133 : face
->GetFamilyName() + "/" + face
->GetStyleName();
1134 usedFontNames
.push_back( name
);
1136 for( OutputDevice::FontMappingUseItem
& item
: *fontMappingUseData
)
1138 if( item
.mOriginalFont
== originalName
&& item
.mUsedFonts
== usedFontNames
)
1144 fontMappingUseData
->push_back( { originalName
, std::move(usedFontNames
), 1 } );
1147 void OutputDevice::StartTrackingFontMappingUse()
1149 delete fontMappingUseData
;
1150 fontMappingUseData
= new FontMappingUseData
;
1153 OutputDevice::FontMappingUseData
OutputDevice::FinishTrackingFontMappingUse()
1155 if(!fontMappingUseData
)
1157 FontMappingUseData ret
= std::move( *fontMappingUseData
);
1158 delete fontMappingUseData
;
1159 fontMappingUseData
= nullptr;
1163 std::unique_ptr
<SalLayout
> OutputDevice::ImplLayout(
1164 const OUString
& rOrigStr
, sal_Int32 nMinIndex
, sal_Int32 nLen
, const Point
& rLogicalPos
,
1165 tools::Long nLogicalWidth
, KernArraySpan pDXArray
, std::span
<const sal_Bool
> pKashidaArray
,
1166 SalLayoutFlags flags
, vcl::text::TextLayoutCache
const* pLayoutCache
,
1167 const SalLayoutGlyphs
* pGlyphs
, std::optional
<sal_Int32
> nDrawOriginCluster
,
1168 std::optional
<sal_Int32
> nDrawMinCharPos
, std::optional
<sal_Int32
> nDrawEndCharPos
) const
1170 if (pGlyphs
&& !pGlyphs
->IsValid())
1172 SAL_WARN("vcl", "Trying to setup invalid cached glyphs - falling back to relayout!");
1178 for( int level
= 0;; ++level
)
1180 SalLayoutGlyphsImpl
* glyphsImpl
= pGlyphs
->Impl(level
);
1181 if(glyphsImpl
== nullptr)
1183 // It is allowed to reuse only glyphs created with SalLayoutFlags::GlyphItemsOnly.
1184 // If the glyphs have already been used, the AdjustLayout() call below might have
1185 // altered them (MultiSalLayout::ImplAdjustMultiLayout() drops glyphs that need
1186 // fallback from the base layout, but then GenericSalLayout::LayoutText()
1187 // would not know to call SetNeedFallback()).
1188 assert(glyphsImpl
->GetFlags() & SalLayoutFlags::GlyphItemsOnly
);
1196 // check string index and length
1197 if( -1 == nLen
|| nMinIndex
+ nLen
> rOrigStr
.getLength() )
1199 const sal_Int32 nNewLen
= rOrigStr
.getLength() - nMinIndex
;
1205 OUString aStr
= rOrigStr
;
1207 // recode string if needed
1208 if( mpFontInstance
->mpConversion
) {
1209 mpFontInstance
->mpConversion
->RecodeString( aStr
, 0, aStr
.getLength() );
1210 pLayoutCache
= nullptr; // don't use cache with modified string!
1214 double nPixelWidth
= nLogicalWidth
;
1215 if( nLogicalWidth
&& mbMap
)
1217 // convert from logical units to physical units
1218 nPixelWidth
= ImplLogicWidthToDeviceSubPixel(nLogicalWidth
);
1221 vcl::text::ImplLayoutArgs aLayoutArgs
= ImplPrepareLayoutArgs( aStr
, nMinIndex
, nLen
,
1222 nPixelWidth
, flags
, pLayoutCache
);
1224 if (nDrawOriginCluster
.has_value())
1226 aLayoutArgs
.mnDrawOriginCluster
= *nDrawOriginCluster
;
1229 if (nDrawMinCharPos
.has_value())
1231 aLayoutArgs
.mnDrawMinCharPos
= *nDrawMinCharPos
;
1234 if (nDrawEndCharPos
.has_value())
1236 aLayoutArgs
.mnDrawEndCharPos
= *nDrawEndCharPos
;
1239 double nEndGlyphCoord(0);
1240 if (!pDXArray
.empty() || !pKashidaArray
.empty())
1242 // The provided advance and kashida arrays are indexed relative to the first visible cluster
1243 auto nJustMinCluster
= nDrawMinCharPos
.value_or(nMinIndex
);
1244 auto nJustLen
= nLen
;
1245 if (nDrawEndCharPos
.has_value())
1247 nJustLen
= *nDrawEndCharPos
- nJustMinCluster
;
1250 JustificationData stJustification
{ nJustMinCluster
, nJustLen
};
1252 if (!pDXArray
.empty() && mbMap
)
1254 // convert from logical units to font units without rounding,
1255 // keeping accuracy for lower levels
1256 for (int i
= 0; i
< nJustLen
; ++i
)
1258 stJustification
.SetTotalAdvance(
1259 nJustMinCluster
+ i
,
1260 ImplLogicWidthToDeviceSubPixel(pDXArray
[i
]));
1263 nEndGlyphCoord
= stJustification
.GetTotalAdvance(nJustMinCluster
+ nJustLen
- 1);
1265 else if (!pDXArray
.empty())
1267 for (int i
= 0; i
< nJustLen
; ++i
)
1269 stJustification
.SetTotalAdvance(nJustMinCluster
+ i
, pDXArray
[i
]);
1273 = std::round(stJustification
.GetTotalAdvance(nJustMinCluster
+ nJustLen
- 1));
1276 if (!pKashidaArray
.empty())
1278 for (sal_Int32 i
= 0; i
< static_cast<sal_Int32
>(pKashidaArray
.size()); ++i
)
1280 stJustification
.SetKashidaPosition(nJustMinCluster
+ i
,
1281 static_cast<bool>(pKashidaArray
[i
]));
1285 aLayoutArgs
.SetJustificationData(std::move(stJustification
));
1288 // get matching layout object for base font
1289 std::unique_ptr
<SalLayout
> pSalLayout
= mpGraphics
->GetTextLayout(0);
1292 pSalLayout
->SetSubpixelPositioning(mbMap
);
1295 if( pSalLayout
&& !pSalLayout
->LayoutText( aLayoutArgs
, pGlyphs
? pGlyphs
->Impl(0) : nullptr ) )
1303 // do glyph fallback if needed
1304 // #105768# avoid fallback for very small font sizes
1305 if (aLayoutArgs
.HasFallbackRun() && mpFontInstance
->GetFontSelectPattern().mnHeight
>= 3)
1306 pSalLayout
= ImplGlyphFallbackLayout(std::move(pSalLayout
), aLayoutArgs
, pGlyphs
);
1308 if (flags
& SalLayoutFlags::GlyphItemsOnly
)
1309 // Return glyph items only after fallback handling. Otherwise they may
1310 // contain invalid glyph IDs.
1313 // position, justify, etc. the layout
1314 pSalLayout
->AdjustLayout( aLayoutArgs
);
1316 // default to on for pdf export, which uses SubPixelToLogic to convert back to
1317 // the logical coord space, of if we are scaling/mapping
1318 if (mbMap
|| meOutDevType
== OUTDEV_PDF
)
1319 pSalLayout
->DrawBase() = ImplLogicToDeviceSubPixel(rLogicalPos
);
1322 Point aDevicePos
= ImplLogicToDevicePixel(rLogicalPos
);
1323 pSalLayout
->DrawBase() = basegfx::B2DPoint(aDevicePos
.X(), aDevicePos
.Y());
1326 // adjust to right alignment if necessary
1327 if( aLayoutArgs
.mnFlags
& SalLayoutFlags::RightAlign
)
1330 if (!pDXArray
.empty())
1331 nRTLOffset
= nEndGlyphCoord
;
1332 else if( nPixelWidth
)
1333 nRTLOffset
= nPixelWidth
;
1335 nRTLOffset
= pSalLayout
->GetTextWidth();
1336 pSalLayout
->DrawOffset().setX( 1 - nRTLOffset
);
1339 if(IsTrackingFontMappingUse())
1340 TrackFontMappingUse(GetFont(), pSalLayout
.get());
1345 std::shared_ptr
<const vcl::text::TextLayoutCache
> OutputDevice::CreateTextLayoutCache(
1346 OUString
const& rString
)
1348 return vcl::text::TextLayoutCache::Create(rString
);
1351 bool OutputDevice::GetTextIsRTL( const OUString
& rString
, sal_Int32 nIndex
, sal_Int32 nLen
) const
1353 OUString
aStr( rString
);
1354 vcl::text::ImplLayoutArgs aArgs
= ImplPrepareLayoutArgs(aStr
, nIndex
, nLen
, 0);
1357 if (!aArgs
.GetNextPos(&nCharPos
, &bRTL
))
1359 return (nCharPos
!= nIndex
);
1362 sal_Int32
OutputDevice::GetTextBreak( const OUString
& rStr
, tools::Long nTextWidth
,
1363 sal_Int32 nIndex
, sal_Int32 nLen
,
1364 tools::Long nCharExtra
,
1365 vcl::text::TextLayoutCache
const*const pLayoutCache
,
1366 const SalLayoutGlyphs
* pGlyphs
) const
1368 std::unique_ptr
<SalLayout
> pSalLayout
= ImplLayout( rStr
, nIndex
, nLen
,
1369 Point(0,0), 0, {}, {}, eDefaultLayout
, pLayoutCache
, pGlyphs
);
1370 sal_Int32 nRetVal
= -1;
1373 // convert logical widths into layout units
1374 // NOTE: be very careful to avoid rounding errors for nCharExtra case
1375 // problem with rounding errors especially for small nCharExtras
1376 // TODO: remove when layout units have subpixel granularity
1377 tools::Long nSubPixelFactor
= 1;
1379 nSubPixelFactor
= 64;
1380 double nTextPixelWidth
= ImplLogicWidthToDeviceSubPixel(nTextWidth
* nSubPixelFactor
);
1381 double nExtraPixelWidth
= 0;
1382 if( nCharExtra
!= 0 )
1383 nExtraPixelWidth
= ImplLogicWidthToDeviceSubPixel(nCharExtra
* nSubPixelFactor
);
1384 nRetVal
= pSalLayout
->GetTextBreak( nTextPixelWidth
, nExtraPixelWidth
, nSubPixelFactor
);
1390 sal_Int32
OutputDevice::GetTextBreak( const OUString
& rStr
, tools::Long nTextWidth
,
1391 sal_Unicode nHyphenChar
, sal_Int32
& rHyphenPos
,
1392 sal_Int32 nIndex
, sal_Int32 nLen
,
1393 tools::Long nCharExtra
,
1394 vcl::text::TextLayoutCache
const*const pLayoutCache
,
1395 const SalLayoutGlyphs
* pGlyphs
) const
1399 std::unique_ptr
<SalLayout
> pSalLayout
= ImplLayout( rStr
, nIndex
, nLen
,
1400 Point(0,0), 0, {}, {}, eDefaultLayout
, pLayoutCache
, pGlyphs
);
1401 sal_Int32 nRetVal
= -1;
1404 // convert logical widths into layout units
1405 // NOTE: be very careful to avoid rounding errors for nCharExtra case
1406 // problem with rounding errors especially for small nCharExtras
1407 // TODO: remove when layout units have subpixel granularity
1408 tools::Long nSubPixelFactor
= 1;
1410 nSubPixelFactor
= 64;
1412 double nTextPixelWidth
= ImplLogicWidthToDeviceSubPixel(nTextWidth
* nSubPixelFactor
);
1413 double nExtraPixelWidth
= 0;
1414 if( nCharExtra
!= 0 )
1415 nExtraPixelWidth
= ImplLogicWidthToDeviceSubPixel(nCharExtra
* nSubPixelFactor
);
1417 // calculate un-hyphenated break position
1418 nRetVal
= pSalLayout
->GetTextBreak( nTextPixelWidth
, nExtraPixelWidth
, nSubPixelFactor
);
1420 // calculate hyphenated break position
1421 OUString
aHyphenStr(nHyphenChar
);
1422 std::unique_ptr
<SalLayout
> pHyphenLayout
= ImplLayout( aHyphenStr
, 0, 1 );
1425 // calculate subpixel width of hyphenation character
1426 double nHyphenPixelWidth
= pHyphenLayout
->GetTextWidth() * nSubPixelFactor
;
1428 // calculate hyphenated break position
1429 nTextPixelWidth
-= nHyphenPixelWidth
;
1430 if( nExtraPixelWidth
> 0 )
1431 nTextPixelWidth
-= nExtraPixelWidth
;
1433 rHyphenPos
= pSalLayout
->GetTextBreak(nTextPixelWidth
, nExtraPixelWidth
, nSubPixelFactor
);
1435 if( rHyphenPos
> nRetVal
)
1436 rHyphenPos
= nRetVal
;
1443 void OutputDevice::ImplDrawText( OutputDevice
& rTargetDevice
, const tools::Rectangle
& rRect
,
1444 const OUString
& rOrigStr
, DrawTextFlags nStyle
,
1445 std::vector
< tools::Rectangle
>* pVector
, OUString
* pDisplayText
,
1446 vcl::TextLayoutCommon
& _rLayout
)
1449 Color aOldTextColor
;
1450 Color aOldTextFillColor
;
1451 bool bRestoreFillColor
= false;
1452 if ( (nStyle
& DrawTextFlags::Disable
) && ! pVector
)
1454 bool bHighContrastBlack
= false;
1455 bool bHighContrastWhite
= false;
1456 const StyleSettings
& rStyleSettings( rTargetDevice
.GetSettings().GetStyleSettings() );
1457 if( rStyleSettings
.GetHighContrastMode() )
1460 if( rTargetDevice
.IsBackground() )
1461 aCol
= rTargetDevice
.GetBackground().GetColor();
1463 // best guess is the face color here
1464 // but it may be totally wrong. the background color
1465 // was typically already reset
1466 aCol
= rStyleSettings
.GetFaceColor();
1468 bHighContrastBlack
= aCol
.IsDark();
1469 bHighContrastWhite
= aCol
.IsBright();
1472 aOldTextColor
= rTargetDevice
.GetTextColor();
1473 if ( rTargetDevice
.IsTextFillColor() )
1475 bRestoreFillColor
= true;
1476 aOldTextFillColor
= rTargetDevice
.GetTextFillColor();
1478 if( bHighContrastBlack
)
1479 rTargetDevice
.SetTextColor( COL_GREEN
);
1480 else if( bHighContrastWhite
)
1481 rTargetDevice
.SetTextColor( COL_LIGHTGREEN
);
1484 // draw disabled text always without shadow
1485 // as it fits better with native look
1486 rTargetDevice
.SetTextColor( rTargetDevice
.GetSettings().GetStyleSettings().GetDisableColor() );
1490 tools::Long nWidth
= rRect
.GetWidth();
1491 tools::Long nHeight
= rRect
.GetHeight();
1493 if (nWidth
<= 0 || nHeight
<= 0)
1495 if (nStyle
& DrawTextFlags::Clip
)
1497 static bool bFuzzing
= comphelper::IsFuzzing();
1498 SAL_WARN_IF(bFuzzing
, "vcl", "skipping negative rectangle of: " << nWidth
<< " x " << nHeight
);
1503 Point aPos
= rRect
.TopLeft();
1505 tools::Long nTextHeight
= rTargetDevice
.GetTextHeight();
1506 TextAlign eAlign
= rTargetDevice
.GetTextAlign();
1507 sal_Int32 nMnemonicPos
= -1;
1509 OUString aStr
= rOrigStr
;
1510 if ( nStyle
& DrawTextFlags::Mnemonic
)
1511 aStr
= removeMnemonicFromString( aStr
, nMnemonicPos
);
1513 const bool bDrawMnemonics
= !(rTargetDevice
.GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics
) && !pVector
;
1515 // We treat multiline text differently
1516 if ( nStyle
& DrawTextFlags::MultiLine
)
1519 ImplMultiTextLineInfo aMultiLineInfo
;
1521 sal_Int32 nFormatLines
;
1525 tools::Long nMaxTextWidth
= _rLayout
.GetTextLines(rRect
, nTextHeight
, aMultiLineInfo
, nWidth
, aStr
, nStyle
);
1526 sal_Int32 nLines
= static_cast<sal_Int32
>(nHeight
/nTextHeight
);
1528 nFormatLines
= aMultiLineInfo
.Count();
1531 if ( nFormatLines
> nLines
)
1533 if ( nStyle
& DrawTextFlags::EndEllipsis
)
1535 // Create last line and shorten it
1536 nFormatLines
= nLines
-1;
1538 ImplTextLineInfo
& rLineInfo
= aMultiLineInfo
.GetLine( nFormatLines
);
1539 aLastLine
= convertLineEnd(aStr
.copy(rLineInfo
.GetIndex()), LINEEND_LF
);
1540 // Replace all LineFeeds with Spaces
1541 OUStringBuffer
aLastLineBuffer(aLastLine
);
1542 sal_Int32 nLastLineLen
= aLastLineBuffer
.getLength();
1543 for ( i
= 0; i
< nLastLineLen
; i
++ )
1545 if ( aLastLineBuffer
[ i
] == '\n' )
1546 aLastLineBuffer
[ i
] = ' ';
1548 aLastLine
= aLastLineBuffer
.makeStringAndClear();
1549 aLastLine
= _rLayout
.GetEllipsisString(aLastLine
, nWidth
, nStyle
);
1550 nStyle
&= ~DrawTextFlags(DrawTextFlags::VCenter
| DrawTextFlags::Bottom
);
1551 nStyle
|= DrawTextFlags::Top
;
1556 if ( nMaxTextWidth
<= nWidth
)
1557 nStyle
&= ~DrawTextFlags::Clip
;
1560 // Do we need to clip the height?
1561 if ( nFormatLines
*nTextHeight
> nHeight
)
1562 nStyle
|= DrawTextFlags::Clip
;
1565 if ( nStyle
& DrawTextFlags::Clip
)
1567 rTargetDevice
.Push( vcl::PushFlags::CLIPREGION
);
1568 rTargetDevice
.IntersectClipRegion( rRect
);
1571 // Vertical alignment
1572 if ( nStyle
& DrawTextFlags::Bottom
)
1573 aPos
.AdjustY(nHeight
-(nFormatLines
*nTextHeight
) );
1574 else if ( nStyle
& DrawTextFlags::VCenter
)
1575 aPos
.AdjustY((nHeight
-(nFormatLines
*nTextHeight
))/2 );
1578 if ( eAlign
== ALIGN_BOTTOM
)
1579 aPos
.AdjustY(nTextHeight
);
1580 else if ( eAlign
== ALIGN_BASELINE
)
1581 aPos
.AdjustY(rTargetDevice
.GetFontMetric().GetAscent() );
1583 // Output all lines except for the last one
1584 for ( i
= 0; i
< nFormatLines
; i
++ )
1586 ImplTextLineInfo
& rLineInfo
= aMultiLineInfo
.GetLine( i
);
1587 if ( nStyle
& DrawTextFlags::Right
)
1588 aPos
.AdjustX(nWidth
-rLineInfo
.GetWidth() );
1589 else if ( nStyle
& DrawTextFlags::Center
)
1590 aPos
.AdjustX((nWidth
-rLineInfo
.GetWidth())/2 );
1591 sal_Int32 nIndex
= rLineInfo
.GetIndex();
1592 sal_Int32 nLineLen
= rLineInfo
.GetLen();
1593 _rLayout
.DrawText( aPos
, aStr
, nIndex
, nLineLen
, pVector
, pDisplayText
);
1594 if ( bDrawMnemonics
)
1596 if ( (nMnemonicPos
>= nIndex
) && (nMnemonicPos
< nIndex
+nLineLen
) )
1598 tools::Long nMnemonicX
;
1599 tools::Long nMnemonicY
;
1602 _rLayout
.GetTextArray(aStr
, &aDXArray
, nIndex
, nLineLen
, true);
1603 sal_Int32 nPos
= nMnemonicPos
- nIndex
;
1604 sal_Int32 lc_x1
= nPos
? aDXArray
[nPos
- 1] : 0;
1605 sal_Int32 lc_x2
= aDXArray
[nPos
];
1606 double nMnemonicWidth
= rTargetDevice
.ImplLogicWidthToDeviceSubPixel(std::abs(lc_x1
- lc_x2
));
1608 Point aTempPos
= rTargetDevice
.LogicToPixel( aPos
);
1609 nMnemonicX
= rTargetDevice
.GetOutOffXPixel() + aTempPos
.X() + rTargetDevice
.ImplLogicWidthToDevicePixel( std::min( lc_x1
, lc_x2
) );
1610 nMnemonicY
= rTargetDevice
.GetOutOffYPixel() + aTempPos
.Y() + rTargetDevice
.ImplLogicWidthToDevicePixel( rTargetDevice
.GetFontMetric().GetAscent() );
1611 rTargetDevice
.ImplDrawMnemonicLine( nMnemonicX
, nMnemonicY
, nMnemonicWidth
);
1614 aPos
.AdjustY(nTextHeight
);
1615 aPos
.setX( rRect
.Left() );
1618 // If there still is a last line, we output it left-aligned as the line would be clipped
1619 if ( !aLastLine
.isEmpty() )
1620 _rLayout
.DrawText( aPos
, aLastLine
, 0, aLastLine
.getLength(), pVector
, pDisplayText
);
1623 if ( nStyle
& DrawTextFlags::Clip
)
1624 rTargetDevice
.Pop();
1629 tools::Long nTextWidth
= _rLayout
.GetTextWidth( aStr
, 0, -1 );
1631 // Clip text if needed
1632 if ( nTextWidth
> nWidth
)
1634 if ( nStyle
& TEXT_DRAW_ELLIPSIS
)
1636 aStr
= _rLayout
.GetEllipsisString(aStr
, nWidth
, nStyle
);
1637 nStyle
&= ~DrawTextFlags(DrawTextFlags::Center
| DrawTextFlags::Right
);
1638 nStyle
|= DrawTextFlags::Left
;
1639 nTextWidth
= _rLayout
.GetTextWidth( aStr
, 0, aStr
.getLength() );
1644 if ( nTextHeight
<= nHeight
)
1645 nStyle
&= ~DrawTextFlags::Clip
;
1648 // horizontal text alignment
1649 if ( nStyle
& DrawTextFlags::Right
)
1650 aPos
.AdjustX(nWidth
-nTextWidth
);
1651 else if ( nStyle
& DrawTextFlags::Center
)
1652 aPos
.AdjustX((nWidth
-nTextWidth
)/2 );
1654 // vertical font alignment
1655 if ( eAlign
== ALIGN_BOTTOM
)
1656 aPos
.AdjustY(nTextHeight
);
1657 else if ( eAlign
== ALIGN_BASELINE
)
1658 aPos
.AdjustY(rTargetDevice
.GetFontMetric().GetAscent() );
1660 if ( nStyle
& DrawTextFlags::Bottom
)
1661 aPos
.AdjustY(nHeight
-nTextHeight
);
1662 else if ( nStyle
& DrawTextFlags::VCenter
)
1663 aPos
.AdjustY((nHeight
-nTextHeight
)/2 );
1665 tools::Long nMnemonicX
= 0;
1666 tools::Long nMnemonicY
= 0;
1667 double nMnemonicWidth
= 0;
1668 if (nMnemonicPos
!= -1 && nMnemonicPos
< aStr
.getLength())
1671 _rLayout
.GetTextArray(aStr
, &aDXArray
, 0, aStr
.getLength(), true);
1672 tools::Long lc_x1
= nMnemonicPos
? aDXArray
[nMnemonicPos
- 1] : 0;
1673 tools::Long lc_x2
= aDXArray
[nMnemonicPos
];
1674 nMnemonicWidth
= rTargetDevice
.ImplLogicWidthToDeviceSubPixel(std::abs(lc_x1
- lc_x2
));
1676 Point aTempPos
= rTargetDevice
.LogicToPixel( aPos
);
1677 nMnemonicX
= rTargetDevice
.GetOutOffXPixel() + aTempPos
.X() + rTargetDevice
.ImplLogicWidthToDevicePixel( std::min(lc_x1
, lc_x2
) );
1678 nMnemonicY
= rTargetDevice
.GetOutOffYPixel() + aTempPos
.Y() + rTargetDevice
.ImplLogicWidthToDevicePixel( rTargetDevice
.GetFontMetric().GetAscent() );
1681 if ( nStyle
& DrawTextFlags::Clip
)
1683 rTargetDevice
.Push( vcl::PushFlags::CLIPREGION
);
1684 rTargetDevice
.IntersectClipRegion( rRect
);
1685 _rLayout
.DrawText( aPos
, aStr
, 0, aStr
.getLength(), pVector
, pDisplayText
);
1686 if ( bDrawMnemonics
&& nMnemonicPos
!= -1 )
1687 rTargetDevice
.ImplDrawMnemonicLine( nMnemonicX
, nMnemonicY
, nMnemonicWidth
);
1688 rTargetDevice
.Pop();
1692 _rLayout
.DrawText( aPos
, aStr
, 0, aStr
.getLength(), pVector
, pDisplayText
);
1693 if ( bDrawMnemonics
&& nMnemonicPos
!= -1 )
1694 rTargetDevice
.ImplDrawMnemonicLine( nMnemonicX
, nMnemonicY
, nMnemonicWidth
);
1698 if ( nStyle
& DrawTextFlags::Disable
&& !pVector
)
1700 rTargetDevice
.SetTextColor( aOldTextColor
);
1701 if ( bRestoreFillColor
)
1702 rTargetDevice
.SetTextFillColor( aOldTextFillColor
);
1706 void OutputDevice::AddTextRectActions( const tools::Rectangle
& rRect
,
1707 const OUString
& rOrigStr
,
1708 DrawTextFlags nStyle
,
1712 if ( rOrigStr
.isEmpty() || rRect
.IsEmpty() )
1715 // we need a graphics
1716 if( !mpGraphics
&& !AcquireGraphics() )
1719 if( mbInitClipRegion
)
1722 // temporarily swap in passed mtf for action generation, and
1723 // disable output generation.
1724 const bool bOutputEnabled( IsOutputEnabled() );
1725 GDIMetaFile
* pMtf
= mpMetaFile
;
1728 EnableOutput( false );
1730 // #i47157# Factored out to ImplDrawTextRect(), to be shared
1731 // between us and DrawText()
1732 vcl::DefaultTextLayout
aLayout( *this );
1733 ImplDrawText( *this, rRect
, rOrigStr
, nStyle
, nullptr, nullptr, aLayout
);
1735 // and restore again
1736 EnableOutput( bOutputEnabled
);
1740 void OutputDevice::DrawText( const tools::Rectangle
& rRect
, const OUString
& rOrigStr
, DrawTextFlags nStyle
,
1741 std::vector
< tools::Rectangle
>* pVector
, OUString
* pDisplayText
,
1742 vcl::TextLayoutCommon
* _pTextLayout
)
1744 assert(!is_double_buffered_window());
1746 if (mpOutDevData
->mpRecordLayout
)
1748 pVector
= &mpOutDevData
->mpRecordLayout
->m_aUnicodeBoundRects
;
1749 pDisplayText
= &mpOutDevData
->mpRecordLayout
->m_aDisplayText
;
1752 bool bDecomposeTextRectAction
= ( _pTextLayout
!= nullptr ) && _pTextLayout
->DecomposeTextRectAction();
1753 if ( mpMetaFile
&& !bDecomposeTextRectAction
)
1754 mpMetaFile
->AddAction( new MetaTextRectAction( rRect
, rOrigStr
, nStyle
) );
1756 if ( ( !IsDeviceOutputNecessary() && !pVector
&& !bDecomposeTextRectAction
) || rOrigStr
.isEmpty() || rRect
.IsEmpty() )
1759 // we need a graphics
1760 if( !mpGraphics
&& !AcquireGraphics() )
1763 if( mbInitClipRegion
)
1765 if (mbOutputClipped
&& !bDecomposeTextRectAction
&& !pDisplayText
)
1768 // temporarily disable mtf action generation (ImplDrawText _does_
1769 // create MetaActionType::TEXTs otherwise)
1770 GDIMetaFile
* pMtf
= mpMetaFile
;
1771 if ( !bDecomposeTextRectAction
)
1772 mpMetaFile
= nullptr;
1774 // #i47157# Factored out to ImplDrawText(), to be used also
1775 // from AddTextRectActions()
1776 vcl::DefaultTextLayout
aDefaultLayout( *this );
1777 ImplDrawText( *this, rRect
, rOrigStr
, nStyle
, pVector
, pDisplayText
, _pTextLayout
? *_pTextLayout
: aDefaultLayout
);
1783 mpAlphaVDev
->DrawText( rRect
, rOrigStr
, nStyle
, pVector
, pDisplayText
);
1786 tools::Rectangle
OutputDevice::GetTextRect( const tools::Rectangle
& rRect
,
1787 const OUString
& rStr
, DrawTextFlags nStyle
,
1788 TextRectInfo
* pInfo
,
1789 const vcl::TextLayoutCommon
* _pTextLayout
) const
1792 tools::Rectangle aRect
= rRect
;
1794 tools::Long nWidth
= rRect
.GetWidth();
1795 tools::Long nMaxWidth
;
1796 tools::Long nTextHeight
= GetTextHeight();
1798 OUString aStr
= rStr
;
1799 if ( nStyle
& DrawTextFlags::Mnemonic
)
1800 aStr
= removeMnemonicFromString( aStr
);
1802 if ( nStyle
& DrawTextFlags::MultiLine
)
1804 ImplMultiTextLineInfo aMultiLineInfo
;
1805 sal_Int32 nFormatLines
;
1809 vcl::DefaultTextLayout
aDefaultLayout( *const_cast< OutputDevice
* >( this ) );
1812 const_cast<vcl::TextLayoutCommon
*>(_pTextLayout
)->GetTextLines(rRect
, nTextHeight
, aMultiLineInfo
, nWidth
, aStr
, nStyle
);
1814 aDefaultLayout
.GetTextLines(rRect
, nTextHeight
, aMultiLineInfo
, nWidth
, aStr
, nStyle
);
1816 nFormatLines
= aMultiLineInfo
.Count();
1819 nLines
= static_cast<sal_uInt16
>(aRect
.GetHeight()/nTextHeight
);
1821 pInfo
->mnLineCount
= nFormatLines
;
1824 if ( nFormatLines
<= nLines
)
1825 nLines
= nFormatLines
;
1828 if ( !(nStyle
& DrawTextFlags::EndEllipsis
) )
1829 nLines
= nFormatLines
;
1833 pInfo
->mbEllipsis
= true;
1839 bool bMaxWidth
= nMaxWidth
== 0;
1840 pInfo
->mnMaxWidth
= 0;
1841 for ( i
= 0; i
< nLines
; i
++ )
1843 ImplTextLineInfo
& rLineInfo
= aMultiLineInfo
.GetLine( i
);
1844 if ( bMaxWidth
&& (rLineInfo
.GetWidth() > nMaxWidth
) )
1845 nMaxWidth
= rLineInfo
.GetWidth();
1846 if ( rLineInfo
.GetWidth() > pInfo
->mnMaxWidth
)
1847 pInfo
->mnMaxWidth
= rLineInfo
.GetWidth();
1850 else if ( !nMaxWidth
)
1852 for ( i
= 0; i
< nLines
; i
++ )
1854 ImplTextLineInfo
& rLineInfo
= aMultiLineInfo
.GetLine( i
);
1855 if ( rLineInfo
.GetWidth() > nMaxWidth
)
1856 nMaxWidth
= rLineInfo
.GetWidth();
1863 nMaxWidth
= _pTextLayout
? _pTextLayout
->GetTextWidth( aStr
, 0, aStr
.getLength() ) : GetTextWidth( aStr
);
1867 pInfo
->mnLineCount
= 1;
1868 pInfo
->mnMaxWidth
= nMaxWidth
;
1871 if ( (nMaxWidth
> nWidth
) && (nStyle
& TEXT_DRAW_ELLIPSIS
) )
1874 pInfo
->mbEllipsis
= true;
1879 if ( nStyle
& DrawTextFlags::Right
)
1880 aRect
.SetLeft( aRect
.Right()-nMaxWidth
+1 );
1881 else if ( nStyle
& DrawTextFlags::Center
)
1883 aRect
.AdjustLeft((nWidth
-nMaxWidth
)/2 );
1884 aRect
.SetRight( aRect
.Left()+nMaxWidth
-1 );
1887 aRect
.SetRight( aRect
.Left()+nMaxWidth
-1 );
1889 if ( nStyle
& DrawTextFlags::Bottom
)
1890 aRect
.SetTop( aRect
.Bottom()-(nTextHeight
*nLines
)+1 );
1891 else if ( nStyle
& DrawTextFlags::VCenter
)
1893 aRect
.AdjustTop((aRect
.GetHeight()-(nTextHeight
*nLines
))/2 );
1894 aRect
.SetBottom( aRect
.Top()+(nTextHeight
*nLines
)-1 );
1897 aRect
.SetBottom( aRect
.Top()+(nTextHeight
*nLines
)-1 );
1899 // #99188# get rid of rounding problems when using this rect later
1900 if (nStyle
& DrawTextFlags::Right
)
1901 aRect
.AdjustLeft( -1 );
1903 aRect
.AdjustRight( 1 );
1905 if (maFont
.GetOrientation() != 0_deg10
)
1907 tools::Polygon
aRotatedPolygon(aRect
);
1908 aRotatedPolygon
.Rotate(Point(aRect
.GetWidth() / 2, aRect
.GetHeight() / 2), maFont
.GetOrientation());
1909 return aRotatedPolygon
.GetBoundRect();
1915 void OutputDevice::DrawCtrlText( const Point
& rPos
, const OUString
& rStr
,
1916 const sal_Int32 nIndex
, const sal_Int32 nLen
,
1917 DrawTextFlags nStyle
, std::vector
< tools::Rectangle
>* pVector
, OUString
* pDisplayText
,
1918 const SalLayoutGlyphs
* pGlyphs
)
1920 assert(!is_double_buffered_window());
1922 if ( !IsDeviceOutputNecessary() || (nIndex
>= rStr
.getLength()) )
1925 // better get graphics here because ImplDrawMnemonicLine() will not
1926 // we need a graphics
1927 if( !mpGraphics
&& !AcquireGraphics() )
1930 if( mbInitClipRegion
)
1932 if ( mbOutputClipped
)
1935 // nIndex and nLen must go to mpAlphaVDev->DrawCtrlText unchanged
1936 sal_Int32 nCorrectedIndex
= nIndex
;
1937 sal_Int32 nCorrectedLen
= nLen
;
1938 if ((nCorrectedLen
< 0) || (nCorrectedIndex
+ nCorrectedLen
>= rStr
.getLength()))
1940 nCorrectedLen
= rStr
.getLength() - nCorrectedIndex
;
1942 sal_Int32 nMnemonicPos
= -1;
1944 tools::Long nMnemonicX
= 0;
1945 tools::Long nMnemonicY
= 0;
1946 tools::Long nMnemonicWidth
= 0;
1947 const OUString aStr
= removeMnemonicFromString(rStr
, nMnemonicPos
); // Strip mnemonics always
1948 if (nMnemonicPos
!= -1)
1950 if (nMnemonicPos
< nCorrectedIndex
)
1956 if (nMnemonicPos
< (nCorrectedIndex
+ nCorrectedLen
))
1959 if (nStyle
& DrawTextFlags::Mnemonic
&& !pVector
1960 && !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics
))
1962 SAL_WARN_IF( nMnemonicPos
>= (nCorrectedIndex
+nCorrectedLen
), "vcl", "Mnemonic underline marker after last character" );
1963 bool bInvalidPos
= false;
1965 if (nMnemonicPos
>= nCorrectedLen
)
1967 // may occur in BiDi-Strings: the '~' is sometimes found behind the last char
1968 // due to some strange BiDi text editors
1969 // -> place the underline behind the string to indicate a failure
1971 nMnemonicPos
= nCorrectedLen
- 1;
1975 GetTextArray(aStr
, &aDXArray
, nCorrectedIndex
, nCorrectedLen
, true, nullptr, pGlyphs
);
1976 sal_Int32 nPos
= nMnemonicPos
- nCorrectedIndex
;
1977 sal_Int32 lc_x1
= nPos
? aDXArray
[nPos
- 1] : 0;
1978 sal_Int32 lc_x2
= aDXArray
[nPos
];
1979 nMnemonicWidth
= std::abs(lc_x1
- lc_x2
);
1981 Point
aTempPos( std::min(lc_x1
,lc_x2
), GetFontMetric().GetAscent() );
1982 if( bInvalidPos
) // #106952#, place behind the (last) character
1983 aTempPos
= Point( std::max(lc_x1
,lc_x2
), GetFontMetric().GetAscent() );
1986 aTempPos
= LogicToPixel( aTempPos
);
1987 nMnemonicX
= mnOutOffX
+ aTempPos
.X();
1988 nMnemonicY
= mnOutOffY
+ aTempPos
.Y();
1991 nMnemonicPos
= -1; // Reset - we don't show the mnemonic
1994 std::optional
<Color
> oOldTextColor
;
1995 std::optional
<Color
> oOldTextFillColor
;
1996 if ( nStyle
& DrawTextFlags::Disable
&& ! pVector
)
1998 bool bHighContrastBlack
= false;
1999 bool bHighContrastWhite
= false;
2000 const StyleSettings
& rStyleSettings( GetSettings().GetStyleSettings() );
2001 if( rStyleSettings
.GetHighContrastMode() )
2003 if( IsBackground() )
2005 Wallpaper aWall
= GetBackground();
2006 Color aCol
= aWall
.GetColor();
2007 bHighContrastBlack
= aCol
.IsDark();
2008 bHighContrastWhite
= aCol
.IsBright();
2012 oOldTextColor
= GetTextColor();
2013 if ( IsTextFillColor() )
2014 oOldTextFillColor
= GetTextFillColor();
2016 if( bHighContrastBlack
)
2017 SetTextColor( COL_GREEN
);
2018 else if( bHighContrastWhite
)
2019 SetTextColor( COL_LIGHTGREEN
);
2021 SetTextColor( GetSettings().GetStyleSettings().GetDisableColor() );
2024 DrawText(rPos
, aStr
, nCorrectedIndex
, nCorrectedLen
, pVector
, pDisplayText
, pGlyphs
);
2025 if (nMnemonicPos
!= -1)
2026 ImplDrawMnemonicLine(nMnemonicX
, nMnemonicY
, nMnemonicWidth
);
2029 SetTextColor( *oOldTextColor
);
2030 if (oOldTextFillColor
)
2031 SetTextFillColor(*oOldTextFillColor
);
2034 mpAlphaVDev
->DrawCtrlText( rPos
, rStr
, nIndex
, nLen
, nStyle
, pVector
, pDisplayText
);
2037 tools::Long
OutputDevice::GetCtrlTextWidth( const OUString
& rStr
, const SalLayoutGlyphs
* pGlyphs
) const
2039 sal_Int32 nLen
= rStr
.getLength();
2040 sal_Int32 nIndex
= 0;
2042 sal_Int32 nMnemonicPos
;
2043 OUString aStr
= removeMnemonicFromString( rStr
, nMnemonicPos
);
2044 if ( nMnemonicPos
!= -1 )
2046 if ( nMnemonicPos
< nIndex
)
2048 else if (static_cast<sal_uLong
>(nMnemonicPos
) < static_cast<sal_uLong
>(nIndex
+nLen
))
2051 return GetTextWidth( aStr
, nIndex
, nLen
, nullptr, pGlyphs
);
2054 bool OutputDevice::GetTextBoundRect( tools::Rectangle
& rRect
,
2055 const OUString
& rStr
, sal_Int32 nBase
,
2056 sal_Int32 nIndex
, sal_Int32 nLen
,
2057 sal_uLong nLayoutWidth
, KernArraySpan pDXAry
,
2058 std::span
<const sal_Bool
> pKashidaAry
,
2059 const SalLayoutGlyphs
* pGlyphs
) const
2061 basegfx::B2DRectangle aRect
;
2062 bool bRet
= GetTextBoundRect(aRect
, rStr
, nBase
, nIndex
, nLen
, nLayoutWidth
, pDXAry
,
2063 pKashidaAry
, pGlyphs
);
2064 rRect
= SalLayout::BoundRect2Rectangle(aRect
);
2068 bool OutputDevice::GetTextBoundRect(basegfx::B2DRectangle
& rRect
, const OUString
& rStr
,
2069 sal_Int32 nBase
, sal_Int32 nIndex
, sal_Int32 nLen
,
2070 sal_uLong nLayoutWidth
, KernArraySpan pDXAry
,
2071 std::span
<const sal_Bool
> pKashidaAry
,
2072 const SalLayoutGlyphs
* pGlyphs
) const
2077 std::unique_ptr
<SalLayout
> pSalLayout
;
2079 // calculate offset when nBase!=nIndex
2080 double nXOffset
= 0;
2081 if( nBase
!= nIndex
)
2083 sal_Int32 nStart
= std::min( nBase
, nIndex
);
2084 sal_Int32 nOfsLen
= std::max( nBase
, nIndex
) - nStart
;
2085 pSalLayout
= ImplLayout( rStr
, nStart
, nOfsLen
, aPoint
, nLayoutWidth
, pDXAry
, pKashidaAry
);
2088 nXOffset
= pSalLayout
->GetTextWidth();
2089 // TODO: fix offset calculation for Bidi case
2091 nXOffset
= -nXOffset
;
2095 pSalLayout
= ImplLayout(rStr
, nIndex
, nLen
, aPoint
, nLayoutWidth
, pDXAry
, pKashidaAry
, eDefaultLayout
,
2099 basegfx::B2DRectangle aPixelRect
;
2100 bRet
= pSalLayout
->GetBoundRect(aPixelRect
);
2104 basegfx::B2DPoint aPos
= pSalLayout
->GetDrawPosition(basegfx::B2DPoint(nXOffset
, 0));
2105 auto m
= basegfx::utils::createTranslateB2DHomMatrix(mnTextOffX
- aPos
.getX(),
2106 mnTextOffY
- aPos
.getY());
2107 aPixelRect
.transform(m
);
2108 rRect
= PixelToLogic( aPixelRect
);
2111 m
= basegfx::utils::createTranslateB2DHomMatrix(maMapRes
.mnMapOfsX
,
2112 maMapRes
.mnMapOfsY
);
2121 bool OutputDevice::GetTextOutlines( basegfx::B2DPolyPolygonVector
& rVector
,
2122 const OUString
& rStr
, sal_Int32 nBase
,
2123 sal_Int32 nIndex
, sal_Int32 nLen
,
2124 sal_uLong nLayoutWidth
,
2125 KernArraySpan pDXArray
,
2126 std::span
<const sal_Bool
> pKashidaArray
) const
2135 nLen
= rStr
.getLength() - nIndex
;
2137 rVector
.reserve( nLen
);
2139 // we want to get the Rectangle in logical units, so to
2140 // avoid rounding errors we just size the font in logical units
2141 bool bOldMap
= mbMap
;
2144 const_cast<OutputDevice
&>(*this).mbMap
= false;
2145 const_cast<OutputDevice
&>(*this).mbNewFont
= true;
2148 std::unique_ptr
<SalLayout
> pSalLayout
;
2150 // calculate offset when nBase!=nIndex
2151 double nXOffset
= 0;
2152 if( nBase
!= nIndex
)
2154 sal_Int32 nStart
= std::min( nBase
, nIndex
);
2155 sal_Int32 nOfsLen
= std::max( nBase
, nIndex
) - nStart
;
2156 pSalLayout
= ImplLayout( rStr
, nStart
, nOfsLen
, Point(0,0), nLayoutWidth
, pDXArray
, pKashidaArray
);
2159 nXOffset
= pSalLayout
->GetTextWidth();
2161 // TODO: fix offset calculation for Bidi case
2163 nXOffset
= -nXOffset
;
2167 pSalLayout
= ImplLayout( rStr
, nIndex
, nLen
, Point(0,0), nLayoutWidth
, pDXArray
, pKashidaArray
);
2170 bRet
= pSalLayout
->GetOutline(rVector
);
2173 // transform polygon to pixel units
2174 basegfx::B2DHomMatrix aMatrix
;
2176 if (nXOffset
|| mnTextOffX
|| mnTextOffY
)
2178 basegfx::B2DPoint
aRotatedOfs(mnTextOffX
, mnTextOffY
);
2179 aRotatedOfs
-= pSalLayout
->GetDrawPosition(basegfx::B2DPoint(nXOffset
, 0));
2180 aMatrix
.translate( aRotatedOfs
.getX(), aRotatedOfs
.getY() );
2183 if( !aMatrix
.isIdentity() )
2185 for (auto & elem
: rVector
)
2186 elem
.transform( aMatrix
);
2195 // restore original font size and map mode
2196 const_cast<OutputDevice
&>(*this).mbMap
= bOldMap
;
2197 const_cast<OutputDevice
&>(*this).mbNewFont
= true;
2203 bool OutputDevice::GetTextOutlines( PolyPolyVector
& rResultVector
,
2204 const OUString
& rStr
, sal_Int32 nBase
,
2205 sal_Int32 nIndex
, sal_Int32 nLen
,
2206 sal_uLong nLayoutWidth
, KernArraySpan pDXArray
,
2207 std::span
<const sal_Bool
> pKashidaArray
) const
2209 rResultVector
.clear();
2211 // get the basegfx polypolygon vector
2212 basegfx::B2DPolyPolygonVector aB2DPolyPolyVector
;
2213 if( !GetTextOutlines( aB2DPolyPolyVector
, rStr
, nBase
, nIndex
, nLen
,
2214 nLayoutWidth
, pDXArray
, pKashidaArray
) )
2217 // convert to a tool polypolygon vector
2218 rResultVector
.reserve( aB2DPolyPolyVector
.size() );
2219 for (auto const& elem
: aB2DPolyPolyVector
)
2220 rResultVector
.emplace_back(elem
); // #i76339#
2225 bool OutputDevice::GetTextOutline( tools::PolyPolygon
& rPolyPoly
, const OUString
& rStr
) const
2229 // get the basegfx polypolygon vector
2230 basegfx::B2DPolyPolygonVector aB2DPolyPolyVector
;
2231 if( !GetTextOutlines( aB2DPolyPolyVector
, rStr
, 0/*nBase*/, 0/*nIndex*/, /*nLen*/-1,
2232 /*nLayoutWidth*/0, /*pDXArray*/{} ) )
2235 // convert and merge into a tool polypolygon
2236 for (auto const& elem
: aB2DPolyPolyVector
)
2237 for(auto const& rB2DPolygon
: elem
)
2238 rPolyPoly
.Insert(tools::Polygon(rB2DPolygon
)); // #i76339#
2243 void OutputDevice::SetSystemTextColor(SystemTextColorFlags nFlags
, bool bEnabled
)
2245 if (nFlags
& SystemTextColorFlags::Mono
)
2247 SetTextColor(COL_BLACK
);
2253 const StyleSettings
& rStyleSettings
= GetSettings().GetStyleSettings();
2254 SetTextColor(rStyleSettings
.GetDisableColor());
2259 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */