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