Revert "Revert "Revert "stronger typing for SwClient::GetRegisteredIn"" and fix SwIte...
[LibreOffice.git] / vcl / source / outdev / text.cxx
blobff83e0807a59287a252f13b811ac8e947705f915
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
40 #include <salgdi.hxx>
41 #include <svdata.hxx>
42 #include <textlayout.hxx>
43 #include <textlineinfo.hxx>
44 #include <impglyphitem.hxx>
45 #include <TextLayoutCache.hxx>
46 #include <font/PhysicalFontFace.hxx>
48 #include <memory>
49 #include <optional>
51 #define TEXT_DRAW_ELLIPSIS (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis)
53 void OutputDevice::SetLayoutMode( vcl::text::ComplexTextLayoutFlags nTextLayoutMode )
55 if( mpMetaFile )
56 mpMetaFile->AddAction( new MetaLayoutModeAction( nTextLayoutMode ) );
58 mnTextLayoutMode = nTextLayoutMode;
60 if( mpAlphaVDev )
61 mpAlphaVDev->SetLayoutMode( nTextLayoutMode );
64 void OutputDevice::SetDigitLanguage( LanguageType eTextLanguage )
66 if( mpMetaFile )
67 mpMetaFile->AddAction( new MetaTextLanguageAction( eTextLanguage ) );
69 meTextLanguage = eTextLanguage;
71 if( mpAlphaVDev )
72 mpAlphaVDev->SetDigitLanguage( eTextLanguage );
75 void OutputDevice::ImplInitTextColor()
77 DBG_TESTSOLARMUTEX();
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;
100 if ( nOrientation )
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;
108 nX = nY;
109 nY = -nTemp;
110 nTemp = nWidth;
111 nWidth = nHeight;
112 nHeight = nTemp;
113 nY -= nHeight;
115 else if ( nOrientation == 1800_deg10 )
117 nX = -nX;
118 nY = -nY;
119 nX -= nWidth;
120 nY -= nHeight;
122 else /* ( nOrientation == 2700 ) */
124 tools::Long nTemp = nX;
125 nX = -nY;
126 nY = nTemp;
127 nTemp = nWidth;
128 nWidth = nHeight;
129 nHeight = nTemp;
130 nX -= nWidth;
133 else
135 nX += nBaseX;
136 nY += nBaseY;
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 );
142 return;
146 nX += nBaseX;
147 nY += nBaseY;
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),
168 nWidth,
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 );
194 nWidth = nX2-nX;
195 nHeight = nY2-nY;
197 else
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);
222 else
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() ) )
238 return false;
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())
248 return false;
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 ) )
258 return false;
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;
272 mnOutOffX = 0;
273 mnOutOffY = 0;
274 mpMetaFile = nullptr;
275 EnableMapMode( false );
277 DrawMask( aPoint, aBmp, GetTextColor() );
279 EnableMapMode( bOldMap );
280 mnOutOffX = nOldOffX;
281 mnOutOffY = nOldOffY;
282 mpMetaFile = pOldMetaFile;
284 return true;
287 void OutputDevice::ImplDrawTextDirect( SalLayout& rSalLayout,
288 bool bTextLines)
290 if( mpFontInstance->mnOwnOrientation )
291 if( ImplDrawRotateText( rSalLayout ) )
292 return;
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 );
320 if( bTextLines )
321 ImplDrawTextLines( rSalLayout,
322 maFont.GetStrikeout(), maFont.GetUnderline(), maFont.GetOverline(),
323 maFont.IsWordLineMode(), maFont.IsUnderlineAbove() );
325 // emphasis marks
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 );
362 ImplInitTextColor();
364 // calculate offset - for high resolution printers the offset
365 // should be greater so that the effect is visible
366 tools::Long nOff = 1;
367 nOff += mnDPIX/300;
369 if ( eRelief == FontRelief::Engraved )
370 nOff = -nOff;
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 );
381 ImplInitTextColor();
382 ImplDrawTextDirect( rSalLayout, mbTextLines );
384 SetTextLineColor( aOldTextLineColor );
385 SetOverlineColor( aOldOverlineColor );
387 if ( aTextColor != aOldColor )
389 SetTextColor( aOldColor );
390 ImplInitTextColor();
393 else
395 if ( maFont.IsShadow() )
397 tools::Long nOff = 1 + ((mpFontInstance->mnLineHeight-24)/24);
398 if ( maFont.IsOutline() )
399 nOff++;
400 SetTextLineColor();
401 SetOverlineColor();
402 if ( (GetTextColor() == COL_BLACK)
403 || (GetTextColor().GetLuminance() < 8) )
404 SetTextColor( COL_LIGHTGRAY );
405 else
406 SetTextColor( COL_BLACK );
407 ImplInitTextColor();
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 );
414 ImplInitTextColor();
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 );
443 ImplInitTextColor();
444 ImplDrawTextDirect( rSalLayout, mbTextLines );
445 SetTextColor( aOldColor );
446 SetTextLineColor( aOldTextLineColor );
447 SetOverlineColor( aOldOverlineColor );
448 ImplInitTextColor();
453 void OutputDevice::ImplDrawText( SalLayout& rSalLayout )
455 if( mbInitClipRegion )
456 InitClipRegion();
457 if( mbOutputClipped )
458 return;
459 if( mbInitTextColor )
460 ImplInitTextColor();
462 rSalLayout.DrawBase() += basegfx::B2DPoint(mnTextOffX, mnTextOffY);
464 if( IsTextFillColor() )
465 ImplDrawTextBackground( rSalLayout );
467 if( mbTextSpecial )
468 ImplDrawSpecialText( rSalLayout );
469 else
470 ImplDrawTextDirect( rSalLayout, mbTextLines );
473 void OutputDevice::SetTextColor( const Color& rColor )
476 Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
478 if ( mpMetaFile )
479 mpMetaFile->AddAction( new MetaTextColorAction( aColor ) );
481 if ( maTextColor != aColor )
483 maTextColor = aColor;
484 mbInitTextColor = true;
487 if( mpAlphaVDev )
488 mpAlphaVDev->SetTextColor( COL_ALPHA_OPAQUE );
491 void OutputDevice::SetTextFillColor()
494 if ( mpMetaFile )
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 );
503 if( mpAlphaVDev )
504 mpAlphaVDev->SetTextFillColor();
507 void OutputDevice::SetTextFillColor( const Color& rColor )
509 Color aColor(vcl::drawmode::GetFillColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
511 if ( mpMetaFile )
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() );
519 if( mpAlphaVDev )
520 mpAlphaVDev->SetTextFillColor( COL_ALPHA_OPAQUE );
523 Color OutputDevice::GetTextFillColor() const
525 if ( maFont.IsTransparent() )
526 return COL_TRANSPARENT;
527 else
528 return maFont.GetFillColor();
531 void OutputDevice::SetTextAlign( TextAlign eAlign )
534 if ( mpMetaFile )
535 mpMetaFile->AddAction( new MetaTextAlignAction( eAlign ) );
537 if ( maFont.GetAlignment() != eAlign )
539 maFont.SetAlignment( eAlign );
540 mbNewFont = true;
543 if( mpAlphaVDev )
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 << "\")");
575 #endif
577 if ( mpMetaFile )
578 mpMetaFile->AddAction( new MetaTextAction( rStartPt, rStr, nIndex, nLen ) );
579 if( pVector )
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 ) )
599 bAppend = true;
600 else if( rStr[ nIndex ] == ' ' && bInserted )
602 std::vector< tools::Rectangle >::const_iterator next = it;
603 ++next;
604 if( next != aTmp.end() && aClip.Overlaps( *next ) )
605 bAppend = true;
608 if( bAppend )
610 pVector->push_back( *it );
611 if( pDisplayText )
612 *pDisplayText += OUStringChar(rStr[ nIndex ]);
613 bInserted = true;
617 else
619 GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, *pVector );
620 if( pDisplayText )
621 *pDisplayText += rStr.subView( nIndex, nLen );
625 if ( !IsDeviceOutputNecessary() || pVector )
626 return;
628 if(mpFontInstance)
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);
634 if(pSalLayout)
636 ImplDrawText( *pSalLayout );
639 if( mpAlphaVDev )
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
660 if (!InitFont())
661 return 0;
663 tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
665 if ( mbMap )
666 nHeight = ImplDevicePixelToLogicHeight( nHeight );
668 return nHeight;
671 double OutputDevice::GetTextHeightDouble() const
673 if (!InitFont())
674 return 0;
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;
712 if (mpMetaFile)
714 mpMetaFile->AddAction(new MetaTextArrayAction(rStartPt, rStr, pDXArray, pKashidaArray,
715 nPartIndex, nPartLen, nIndex, nLen));
718 if (!IsDeviceOutputNecessary())
719 return;
721 if (!mpGraphics && !AcquireGraphics())
722 return;
724 assert(mpGraphics);
725 if (mbInitClipRegion)
726 InitClipRegion();
728 if (mbOutputClipped)
729 return;
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);
739 if (pSalLayout)
741 ImplDrawText(*pSalLayout);
744 if (mpAlphaVDev)
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;
763 if ( mpMetaFile )
764 mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen ) );
766 if ( !IsDeviceOutputNecessary() )
767 return;
768 if( !mpGraphics && !AcquireGraphics() )
769 return;
770 assert(mpGraphics);
771 if( mbInitClipRegion )
772 InitClipRegion();
773 if( mbOutputClipped )
774 return;
776 std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry, pKashidaAry, flags, nullptr, pSalLayoutCache);
777 if( pSalLayout )
779 ImplDrawText( *pSalLayout );
782 if( mpAlphaVDev )
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,
793 pSalLayoutCache);
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;
819 // do layout
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);
826 else
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);
835 if( !pSalLayout )
837 // The caller expects this to init the elements of pDXAry.
838 // Adapting all the callers to check that GetTextArray succeeded seems
839 // too much work.
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.
843 if (pDXAry)
845 pDXAry->resize(nPartLen);
846 std::fill(pDXAry->begin(), pDXAry->end(), 0);
849 return {};
852 std::vector<double> aDXPixelArray;
853 std::vector<double>* pDXPixelArray = nullptr;
854 if(pDXAry)
856 aDXPixelArray.resize(nPartLen);
857 pDXPixelArray = &aDXPixelArray;
860 double nWidth = 0.0;
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());
869 else
871 nWidth = pSalLayout->FillPartialDXArray(pDXPixelArray, bCaret ? rStr : OUString(),
872 nPartIndex - nIndex, nPartLen);
875 // convert virtual char widths to virtual absolute positions
876 if( pDXPixelArray )
878 for (int i = 1; i < nPartLen; ++i)
880 (*pDXPixelArray)[i] += (*pDXPixelArray)[i - 1];
884 // convert from font units to logical units
885 if (pDXPixelArray)
887 assert(pKernArray && "pDXPixelArray depends on pKernArray existing");
888 if (mbMap)
890 for (int i = 0; i < nPartLen; ++i)
891 (*pDXPixelArray)[i] = ImplDevicePixelToLogicWidthDouble((*pDXPixelArray)[i]);
895 if (pDXAry)
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() )
922 return;
923 if( nIndex+nLen >= rStr.getLength() )
924 nLen = rStr.getLength() - nIndex;
926 sal_Int32 nCaretPos = nLen * 2;
927 rCaretPos.resize(nCaretPos);
929 // do layout
930 std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, Point(0, 0), 0, {}, {},
931 eDefaultLayout, nullptr, pGlyphs);
932 if( !pSalLayout )
934 std::fill(rCaretPos.begin(), rCaretPos.end(), -1);
935 return;
938 std::vector<double> aCaretPixelPos;
939 pSalLayout->GetCaretPositions(aCaretPixelPos, rStr);
941 // fixup unknown caret positions
942 int i;
943 for (i = 0; i < nCaretPos; ++i)
944 if (aCaretPixelPos[i] >= 0)
945 break;
946 double nXPos = (i < nCaretPos) ? aCaretPixelPos[i] : -1;
947 for (i = 0; i < nCaretPos; ++i)
949 if (aCaretPixelPos[i] >= 0)
950 nXPos = aCaretPixelPos[i];
951 else
952 aCaretPixelPos[i] = nXPos;
955 // handle window mirroring
956 if( IsRTLEnabled() )
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
964 if( mbMap )
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;
985 if ( mpMetaFile )
986 mpMetaFile->AddAction( new MetaStretchTextAction( rStartPt, nWidth, rStr, nIndex, nLen ) );
988 if ( !IsDeviceOutputNecessary() )
989 return;
991 std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, nWidth);
992 if( pSalLayout )
994 ImplDrawText( *pSalLayout );
997 if( mpAlphaVDev )
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,
1003 double nPixelWidth,
1004 SalLayoutFlags nLayoutFlags,
1005 vcl::text::TextLayoutCache const*const pLayoutCache) const
1007 assert(nMinIndex >= 0);
1008 assert(nLen >= 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 )
1047 if (!xTmpStr)
1048 xTmpStr = OUStringBuffer(rStr);
1049 // TODO: are the localized digit surrogates?
1050 (*xTmpStr)[pStr - pBase] = cChar;
1054 if (xTmpStr)
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 )
1063 bRightAlign = true;
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;
1068 if( bRightAlign )
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 );
1079 return aLayoutArgs;
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)
1102 bAllLtr = false;
1103 break;
1106 if (bAllLtr)
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
1127 int level = 0;
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 )
1140 ++item.mCount;
1141 return;
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)
1156 return {};
1157 FontMappingUseData ret = std::move( *fontMappingUseData );
1158 delete fontMappingUseData;
1159 fontMappingUseData = nullptr;
1160 return ret;
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!");
1173 pGlyphs = nullptr;
1175 #ifdef DBG_UTIL
1176 if (pGlyphs)
1178 for( int level = 0;; ++level )
1180 SalLayoutGlyphsImpl* glyphsImpl = pGlyphs->Impl(level);
1181 if(glyphsImpl == nullptr)
1182 break;
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);
1191 #endif
1193 if (!InitFont())
1194 return nullptr;
1196 // check string index and length
1197 if( -1 == nLen || nMinIndex + nLen > rOrigStr.getLength() )
1199 const sal_Int32 nNewLen = rOrigStr.getLength() - nMinIndex;
1200 if( nNewLen <= 0 )
1201 return nullptr;
1202 nLen = nNewLen;
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!
1211 pGlyphs = nullptr;
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]);
1272 nEndGlyphCoord
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);
1291 if (pSalLayout)
1292 pSalLayout->SetSubpixelPositioning(mbMap);
1294 // layout text
1295 if( pSalLayout && !pSalLayout->LayoutText( aLayoutArgs, pGlyphs ? pGlyphs->Impl(0) : nullptr ) )
1297 pSalLayout.reset();
1300 if( !pSalLayout )
1301 return 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.
1311 return pSalLayout;
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);
1320 else
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 )
1329 double nRTLOffset;
1330 if (!pDXArray.empty())
1331 nRTLOffset = nEndGlyphCoord;
1332 else if( nPixelWidth )
1333 nRTLOffset = nPixelWidth;
1334 else
1335 nRTLOffset = pSalLayout->GetTextWidth();
1336 pSalLayout->DrawOffset().setX( 1 - nRTLOffset );
1339 if(IsTrackingFontMappingUse())
1340 TrackFontMappingUse(GetFont(), pSalLayout.get());
1342 return pSalLayout;
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);
1355 bool bRTL = false;
1356 int nCharPos = -1;
1357 if (!aArgs.GetNextPos(&nCharPos, &bRTL))
1358 return false;
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;
1371 if( pSalLayout )
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;
1378 if (!mbMap)
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 );
1387 return nRetVal;
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
1397 rHyphenPos = -1;
1399 std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
1400 Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pGlyphs);
1401 sal_Int32 nRetVal = -1;
1402 if( pSalLayout )
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;
1409 if (!mbMap)
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 );
1423 if( pHyphenLayout )
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;
1440 return 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() )
1459 Color aCol;
1460 if( rTargetDevice.IsBackground() )
1461 aCol = rTargetDevice.GetBackground().GetColor();
1462 else
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 );
1482 else
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)
1496 return;
1497 static bool bFuzzing = comphelper::IsFuzzing();
1498 SAL_WARN_IF(bFuzzing, "vcl", "skipping negative rectangle of: " << nWidth << " x " << nHeight);
1499 if (bFuzzing)
1500 return;
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;
1520 sal_Int32 i;
1521 sal_Int32 nFormatLines;
1523 if ( nTextHeight )
1525 tools::Long nMaxTextWidth = _rLayout.GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle);
1526 sal_Int32 nLines = static_cast<sal_Int32>(nHeight/nTextHeight);
1527 OUString aLastLine;
1528 nFormatLines = aMultiLineInfo.Count();
1529 if (nLines <= 0)
1530 nLines = 1;
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;
1554 else
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;
1564 // Set clipping
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 );
1577 // Font alignment
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;
1601 KernArray aDXArray;
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 );
1622 // Reset clipping
1623 if ( nStyle & DrawTextFlags::Clip )
1624 rTargetDevice.Pop();
1627 else
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() );
1642 else
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())
1670 KernArray aDXArray;
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();
1690 else
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,
1709 GDIMetaFile& rMtf )
1712 if ( rOrigStr.isEmpty() || rRect.IsEmpty() )
1713 return;
1715 // we need a graphics
1716 if( !mpGraphics && !AcquireGraphics() )
1717 return;
1718 assert(mpGraphics);
1719 if( mbInitClipRegion )
1720 InitClipRegion();
1722 // temporarily swap in passed mtf for action generation, and
1723 // disable output generation.
1724 const bool bOutputEnabled( IsOutputEnabled() );
1725 GDIMetaFile* pMtf = mpMetaFile;
1727 mpMetaFile = &rMtf;
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 );
1737 mpMetaFile = pMtf;
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() )
1757 return;
1759 // we need a graphics
1760 if( !mpGraphics && !AcquireGraphics() )
1761 return;
1762 assert(mpGraphics);
1763 if( mbInitClipRegion )
1764 InitClipRegion();
1765 if (mbOutputClipped && !bDecomposeTextRectAction && !pDisplayText)
1766 return;
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 );
1779 // and enable again
1780 mpMetaFile = pMtf;
1782 if( mpAlphaVDev )
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;
1793 sal_Int32 nLines;
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;
1806 sal_Int32 i;
1808 nMaxWidth = 0;
1809 vcl::DefaultTextLayout aDefaultLayout( *const_cast< OutputDevice* >( this ) );
1811 if (_pTextLayout)
1812 const_cast<vcl::TextLayoutCommon*>(_pTextLayout)->GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle);
1813 else
1814 aDefaultLayout.GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle);
1816 nFormatLines = aMultiLineInfo.Count();
1817 if ( !nTextHeight )
1818 nTextHeight = 1;
1819 nLines = static_cast<sal_uInt16>(aRect.GetHeight()/nTextHeight);
1820 if ( pInfo )
1821 pInfo->mnLineCount = nFormatLines;
1822 if ( !nLines )
1823 nLines = 1;
1824 if ( nFormatLines <= nLines )
1825 nLines = nFormatLines;
1826 else
1828 if ( !(nStyle & DrawTextFlags::EndEllipsis) )
1829 nLines = nFormatLines;
1830 else
1832 if ( pInfo )
1833 pInfo->mbEllipsis = true;
1834 nMaxWidth = nWidth;
1837 if ( pInfo )
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();
1860 else
1862 nLines = 1;
1863 nMaxWidth = _pTextLayout ? _pTextLayout->GetTextWidth( aStr, 0, aStr.getLength() ) : GetTextWidth( aStr );
1865 if ( pInfo )
1867 pInfo->mnLineCount = 1;
1868 pInfo->mnMaxWidth = nMaxWidth;
1871 if ( (nMaxWidth > nWidth) && (nStyle & TEXT_DRAW_ELLIPSIS) )
1873 if ( pInfo )
1874 pInfo->mbEllipsis = true;
1875 nMaxWidth = nWidth;
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 );
1886 else
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 );
1896 else
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 );
1902 else
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();
1912 return aRect;
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()) )
1923 return;
1925 // better get graphics here because ImplDrawMnemonicLine() will not
1926 // we need a graphics
1927 if( !mpGraphics && !AcquireGraphics() )
1928 return;
1929 assert(mpGraphics);
1930 if( mbInitClipRegion )
1931 InitClipRegion();
1932 if ( mbOutputClipped )
1933 return;
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)
1952 --nCorrectedIndex;
1954 else
1956 if (nMnemonicPos < (nCorrectedIndex + nCorrectedLen))
1957 --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
1970 bInvalidPos = true;
1971 nMnemonicPos = nCorrectedLen - 1;
1974 KernArray aDXArray;
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() );
1985 aTempPos += rPos;
1986 aTempPos = LogicToPixel( aTempPos );
1987 nMnemonicX = mnOutOffX + aTempPos.X();
1988 nMnemonicY = mnOutOffY + aTempPos.Y();
1990 else
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 );
2020 else
2021 SetTextColor( GetSettings().GetStyleSettings().GetDisableColor() );
2024 DrawText(rPos, aStr, nCorrectedIndex, nCorrectedLen, pVector, pDisplayText, pGlyphs);
2025 if (nMnemonicPos != -1)
2026 ImplDrawMnemonicLine(nMnemonicX, nMnemonicY, nMnemonicWidth);
2028 if (oOldTextColor)
2029 SetTextColor( *oOldTextColor );
2030 if (oOldTextFillColor)
2031 SetTextFillColor(*oOldTextFillColor);
2033 if( mpAlphaVDev )
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 )
2047 nIndex--;
2048 else if (static_cast<sal_uLong>(nMnemonicPos) < static_cast<sal_uLong>(nIndex+nLen))
2049 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);
2065 return bRet;
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
2074 bool bRet = false;
2075 rRect.reset();
2077 std::unique_ptr<SalLayout> pSalLayout;
2078 const Point aPoint;
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 );
2086 if( pSalLayout )
2088 nXOffset = pSalLayout->GetTextWidth();
2089 // TODO: fix offset calculation for Bidi case
2090 if( nBase < nIndex)
2091 nXOffset = -nXOffset;
2095 pSalLayout = ImplLayout(rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry, pKashidaAry, eDefaultLayout,
2096 nullptr, pGlyphs);
2097 if( pSalLayout )
2099 basegfx::B2DRectangle aPixelRect;
2100 bRet = pSalLayout->GetBoundRect(aPixelRect);
2102 if( bRet )
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 );
2109 if (mbMap)
2111 m = basegfx::utils::createTranslateB2DHomMatrix(maMapRes.mnMapOfsX,
2112 maMapRes.mnMapOfsY);
2113 rRect.transform(m);
2118 return bRet;
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
2128 if (!InitFont())
2129 return false;
2131 bool bRet = false;
2132 rVector.clear();
2133 if( nLen < 0 )
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;
2142 if( bOldMap )
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);
2157 if( pSalLayout )
2159 nXOffset = pSalLayout->GetTextWidth();
2160 pSalLayout.reset();
2161 // TODO: fix offset calculation for Bidi case
2162 if( nBase > nIndex)
2163 nXOffset = -nXOffset;
2167 pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), nLayoutWidth, pDXArray, pKashidaArray );
2168 if( pSalLayout )
2170 bRet = pSalLayout->GetOutline(rVector);
2171 if( bRet )
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 );
2190 pSalLayout.reset();
2193 if( bOldMap )
2195 // restore original font size and map mode
2196 const_cast<OutputDevice&>(*this).mbMap = bOldMap;
2197 const_cast<OutputDevice&>(*this).mbNewFont = true;
2200 return bRet;
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 ) )
2215 return false;
2217 // convert to a tool polypolygon vector
2218 rResultVector.reserve( aB2DPolyPolyVector.size() );
2219 for (auto const& elem : aB2DPolyPolyVector)
2220 rResultVector.emplace_back(elem); // #i76339#
2222 return true;
2225 bool OutputDevice::GetTextOutline( tools::PolyPolygon& rPolyPoly, const OUString& rStr ) const
2227 rPolyPoly.Clear();
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*/{} ) )
2233 return false;
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#
2240 return true;
2243 void OutputDevice::SetSystemTextColor(SystemTextColorFlags nFlags, bool bEnabled)
2245 if (nFlags & SystemTextColorFlags::Mono)
2247 SetTextColor(COL_BLACK);
2249 else
2251 if (!bEnabled)
2253 const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
2254 SetTextColor(rStyleSettings.GetDisableColor());
2259 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */