tdf#164627 docx export: consolidate getWordCompatibilityMode()
[LibreOffice.git] / vcl / source / outdev / textline.cxx
blob42ffa0613aa2c092a9a89ff2fc067ece074227cf
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/types.h>
21 #include <basegfx/matrix/b2dhommatrixtools.hxx>
22 #include <basegfx/polygon/WaveLine.hxx>
23 #include <tools/helpers.hxx>
24 #include <o3tl/hash_combine.hxx>
25 #include <o3tl/lru_map.hxx>
26 #include <comphelper/configuration.hxx>
27 #include <tools/lazydelete.hxx>
28 #include <vcl/metaact.hxx>
29 #include <vcl/settings.hxx>
30 #include <vcl/virdev.hxx>
31 #include <vcl/skia/SkiaHelper.hxx>
33 #include <drawmode.hxx>
34 #include <salgdi.hxx>
35 #include <impglyphitem.hxx>
37 #include <cassert>
39 #define UNDERLINE_LAST LINESTYLE_BOLDWAVE
40 #define STRIKEOUT_LAST STRIKEOUT_X
42 namespace {
43 struct WavyLineCache final
45 WavyLineCache () : m_aItems( 10 ) {}
47 bool find( Color aLineColor, size_t nLineWidth, size_t nWaveHeight, size_t nWordWidth, BitmapEx& rOutput )
49 Key aKey = { nWaveHeight, sal_uInt32(aLineColor) };
50 auto item = m_aItems.find( aKey );
51 if ( item == m_aItems.end() )
52 return false;
53 // needs update
54 if ( item->second.m_aLineWidth != nLineWidth || item->second.m_aWordWidth < nWordWidth )
56 return false;
58 rOutput = item->second.m_Bitmap;
59 return true;
62 void insert( const BitmapEx& aBitmap, const Color& aLineColor, const size_t nLineWidth, const size_t nWaveHeight, const size_t nWordWidth, BitmapEx& rOutput )
64 Key aKey = { nWaveHeight, sal_uInt32(aLineColor) };
65 m_aItems.insert( std::pair< Key, WavyLineCacheItem>( aKey, { nLineWidth, nWordWidth, aBitmap } ) );
66 rOutput = aBitmap;
69 private:
70 struct WavyLineCacheItem
72 size_t m_aLineWidth;
73 size_t m_aWordWidth;
74 BitmapEx m_Bitmap;
77 struct Key
79 size_t m_aFirst;
80 size_t m_aSecond;
81 bool operator ==( const Key& rOther ) const
83 return ( m_aFirst == rOther.m_aFirst && m_aSecond == rOther.m_aSecond );
87 struct Hash
89 size_t operator() ( const Key& rKey ) const
91 size_t aSeed = 0;
92 o3tl::hash_combine(aSeed, rKey.m_aFirst);
93 o3tl::hash_combine(aSeed, rKey.m_aSecond);
94 return aSeed;
98 o3tl::lru_map< Key, WavyLineCacheItem, Hash > m_aItems;
102 void OutputDevice::ImplInitTextLineSize()
104 mpFontInstance->mxFontMetric->ImplInitTextLineSize( this );
107 void OutputDevice::ImplInitAboveTextLineSize()
109 mpFontInstance->mxFontMetric->ImplInitAboveTextLineSize( this );
112 void OutputDevice::ImplDrawWavePixel( tools::Long nOriginX, tools::Long nOriginY,
113 tools::Long nCurX, tools::Long nCurY,
114 tools::Long nWidth,
115 Degree10 nOrientation,
116 SalGraphics* pGraphics,
117 const OutputDevice& rOutDev,
118 tools::Long nPixWidth, tools::Long nPixHeight )
120 if (nOrientation)
122 Point aPoint( nOriginX, nOriginY );
123 aPoint.RotateAround( nCurX, nCurY, nOrientation );
126 if (shouldDrawWavePixelAsRect(nWidth))
128 pGraphics->DrawRect( nCurX, nCurY, nPixWidth, nPixHeight, rOutDev );
130 else
132 pGraphics->DrawPixel( nCurX, nCurY, rOutDev );
136 bool OutputDevice::shouldDrawWavePixelAsRect(tools::Long nLineWidth) const
138 if (nLineWidth > 1)
139 return true;
141 return false;
144 void OutputDevice::SetWaveLineColors(Color const& rColor, tools::Long nLineWidth)
146 // On printers that output pixel via DrawRect()
147 if (nLineWidth > 1)
149 if (mbLineColor || mbInitLineColor)
151 mpGraphics->SetLineColor();
152 mbInitLineColor = true;
155 mpGraphics->SetFillColor( rColor );
156 mbInitFillColor = true;
158 else
160 mpGraphics->SetLineColor( rColor );
161 mbInitLineColor = true;
165 Size OutputDevice::GetWaveLineSize(tools::Long nLineWidth) const
167 if (nLineWidth > 1)
168 return Size(nLineWidth, ((nLineWidth*mnDPIX)+(mnDPIY/2))/mnDPIY);
170 return Size(1, 1);
173 void OutputDevice::ImplDrawWaveLine( tools::Long nBaseX, tools::Long nBaseY,
174 tools::Long nDistX, tools::Long nDistY,
175 tools::Long nWidth, tools::Long nHeight,
176 tools::Long nLineWidth, Degree10 nOrientation,
177 const Color& rColor )
179 if ( !nHeight )
180 return;
182 tools::Long nStartX = nBaseX + nDistX;
183 tools::Long nStartY = nBaseY + nDistY;
185 // If the height is 1 pixel, it's enough output a line
186 if ( (nLineWidth == 1) && (nHeight == 1) )
188 mpGraphics->SetLineColor( rColor );
189 mbInitLineColor = true;
191 tools::Long nEndX = nStartX+nWidth;
192 tools::Long nEndY = nStartY;
193 if ( nOrientation )
195 Point aOriginPt( nBaseX, nBaseY );
196 aOriginPt.RotateAround( nStartX, nStartY, nOrientation );
197 aOriginPt.RotateAround( nEndX, nEndY, nOrientation );
199 mpGraphics->DrawLine( nStartX, nStartY, nEndX, nEndY, *this );
201 else
203 tools::Long nCurX = nStartX;
204 tools::Long nCurY = nStartY;
205 tools::Long nDiffX = 2;
206 tools::Long nDiffY = nHeight-1;
207 tools::Long nCount = nWidth;
208 tools::Long nOffY = -1;
210 SetWaveLineColors(rColor, nLineWidth);
211 Size aSize(GetWaveLineSize(nLineWidth));
213 tools::Long nPixWidth = aSize.Width();
214 tools::Long nPixHeight = aSize.Height();
216 if ( !nDiffY )
218 while ( nWidth )
220 ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
221 mpGraphics, *this,
222 nPixWidth, nPixHeight );
223 nCurX++;
224 nWidth--;
227 else
229 nCurY += nDiffY;
230 tools::Long nFreq = nCount / (nDiffX+nDiffY);
231 while ( nFreq-- )
233 for( tools::Long i = nDiffY; i; --i )
235 ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
236 mpGraphics, *this,
237 nPixWidth, nPixHeight );
238 nCurX++;
239 nCurY += nOffY;
241 for( tools::Long i = nDiffX; i; --i )
243 ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
244 mpGraphics, *this,
245 nPixWidth, nPixHeight );
246 nCurX++;
248 nOffY = -nOffY;
250 nFreq = nCount % (nDiffX+nDiffY);
251 if ( nFreq )
253 for( tools::Long i = nDiffY; i && nFreq; --i, --nFreq )
255 ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
256 mpGraphics, *this,
257 nPixWidth, nPixHeight );
258 nCurX++;
259 nCurY += nOffY;
262 for( tools::Long i = nDiffX; i && nFreq; --i, --nFreq )
264 ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
265 mpGraphics, *this,
266 nPixWidth, nPixHeight );
267 nCurX++;
274 void OutputDevice::ImplDrawWaveTextLine( tools::Long nBaseX, tools::Long nBaseY,
275 tools::Long nDistX, tools::Long nDistY, tools::Long nWidth,
276 FontLineStyle eTextLine,
277 Color aColor,
278 bool bIsAbove )
280 static bool bFuzzing = comphelper::IsFuzzing();
281 if (bFuzzing && nWidth > 10000)
283 SAL_WARN("vcl.gdi", "drawLine, skipping suspicious WaveTextLine of length: "
284 << nWidth << " for fuzzing performance");
285 return;
288 LogicalFontInstance* pFontInstance = mpFontInstance.get();
289 tools::Long nLineHeight;
290 tools::Long nLinePos;
292 if ( bIsAbove )
294 nLineHeight = pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize();
295 nLinePos = pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset();
297 else
299 nLineHeight = pFontInstance->mxFontMetric->GetWavelineUnderlineSize();
300 nLinePos = pFontInstance->mxFontMetric->GetWavelineUnderlineOffset();
302 if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
303 nLineHeight = 3;
305 tools::Long nLineWidth = mnDPIX / 300;
306 if ( !nLineWidth )
307 nLineWidth = 1;
309 if ( eTextLine == LINESTYLE_BOLDWAVE )
310 nLineWidth *= 2;
312 nLinePos += nDistY - (nLineHeight / 2);
314 tools::Long nLineWidthHeight = ((nLineWidth * mnDPIX) + (mnDPIY / 2)) / mnDPIY;
315 if ( eTextLine == LINESTYLE_DOUBLEWAVE )
317 tools::Long nOrgLineHeight = nLineHeight;
318 nLineHeight /= 3;
319 if ( nLineHeight < 2 )
321 if ( nOrgLineHeight > 1 )
322 nLineHeight = 2;
323 else
324 nLineHeight = 1;
327 tools::Long nLineDY = nOrgLineHeight-(nLineHeight*2);
328 if ( nLineDY < nLineWidthHeight )
329 nLineDY = nLineWidthHeight;
331 tools::Long nLineDY2 = nLineDY/2;
332 if ( !nLineDY2 )
333 nLineDY2 = 1;
335 nLinePos -= nLineWidthHeight-nLineDY2;
336 ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight,
337 nLineWidth, mpFontInstance->mnOrientation, aColor );
338 nLinePos += nLineWidthHeight+nLineDY;
339 ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight,
340 nLineWidth, mpFontInstance->mnOrientation, aColor );
342 else
344 nLinePos -= nLineWidthHeight/2;
345 ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight,
346 nLineWidth, mpFontInstance->mnOrientation, aColor );
350 void OutputDevice::ImplDrawStraightTextLine( tools::Long nBaseX, tools::Long nBaseY,
351 tools::Long nDistX, tools::Long nDistY, tools::Long nWidth,
352 FontLineStyle eTextLine,
353 Color aColor,
354 bool bIsAbove )
356 static bool bFuzzing = comphelper::IsFuzzing();
357 if (bFuzzing && nWidth > 25000)
359 SAL_WARN("vcl.gdi", "drawLine, skipping suspicious TextLine of length: "
360 << nWidth << " for fuzzing performance");
361 return;
364 LogicalFontInstance* pFontInstance = mpFontInstance.get();
365 tools::Long nLineHeight = 0;
366 tools::Long nLinePos = 0;
367 tools::Long nLinePos2 = 0;
369 const tools::Long nY = nDistY;
371 if ( eTextLine > UNDERLINE_LAST )
372 eTextLine = LINESTYLE_SINGLE;
374 switch ( eTextLine )
376 case LINESTYLE_SINGLE:
377 case LINESTYLE_DOTTED:
378 case LINESTYLE_DASH:
379 case LINESTYLE_LONGDASH:
380 case LINESTYLE_DASHDOT:
381 case LINESTYLE_DASHDOTDOT:
382 if ( bIsAbove )
384 nLineHeight = pFontInstance->mxFontMetric->GetAboveUnderlineSize();
385 nLinePos = nY + pFontInstance->mxFontMetric->GetAboveUnderlineOffset();
387 else
389 nLineHeight = pFontInstance->mxFontMetric->GetUnderlineSize();
390 nLinePos = nY + pFontInstance->mxFontMetric->GetUnderlineOffset();
392 break;
393 case LINESTYLE_BOLD:
394 case LINESTYLE_BOLDDOTTED:
395 case LINESTYLE_BOLDDASH:
396 case LINESTYLE_BOLDLONGDASH:
397 case LINESTYLE_BOLDDASHDOT:
398 case LINESTYLE_BOLDDASHDOTDOT:
399 if ( bIsAbove )
401 nLineHeight = pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize();
402 nLinePos = nY + pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset();
404 else
406 nLineHeight = pFontInstance->mxFontMetric->GetBoldUnderlineSize();
407 nLinePos = nY + pFontInstance->mxFontMetric->GetBoldUnderlineOffset();
409 break;
410 case LINESTYLE_DOUBLE:
411 if ( bIsAbove )
413 nLineHeight = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize();
414 nLinePos = nY + pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1();
415 nLinePos2 = nY + pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2();
417 else
419 nLineHeight = pFontInstance->mxFontMetric->GetDoubleUnderlineSize();
420 nLinePos = nY + pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1();
421 nLinePos2 = nY + pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2();
423 break;
424 default:
425 break;
428 if ( !nLineHeight )
429 return;
431 if ( mbLineColor || mbInitLineColor )
433 mpGraphics->SetLineColor();
434 mbInitLineColor = true;
436 mpGraphics->SetFillColor( aColor );
437 mbInitFillColor = true;
439 tools::Long nLeft = nDistX;
441 switch ( eTextLine )
443 case LINESTYLE_SINGLE:
444 case LINESTYLE_BOLD:
445 ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
446 break;
447 case LINESTYLE_DOUBLE:
448 ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
449 ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos2, nWidth, nLineHeight );
450 break;
451 case LINESTYLE_DOTTED:
452 case LINESTYLE_BOLDDOTTED:
454 tools::Long nDotWidth = nLineHeight*mnDPIY;
455 nDotWidth += mnDPIY/2;
456 nDotWidth /= mnDPIY;
458 tools::Long nTempWidth = nDotWidth;
459 tools::Long nEnd = nLeft+nWidth;
460 while ( nLeft < nEnd )
462 if ( nLeft+nTempWidth > nEnd )
463 nTempWidth = nEnd-nLeft;
465 ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempWidth, nLineHeight );
466 nLeft += nDotWidth*2;
469 break;
470 case LINESTYLE_DASH:
471 case LINESTYLE_LONGDASH:
472 case LINESTYLE_BOLDDASH:
473 case LINESTYLE_BOLDLONGDASH:
475 tools::Long nDotWidth = nLineHeight*mnDPIY;
476 nDotWidth += mnDPIY/2;
477 nDotWidth /= mnDPIY;
479 tools::Long nMinDashWidth;
480 tools::Long nMinSpaceWidth;
481 tools::Long nSpaceWidth;
482 tools::Long nDashWidth;
483 if ( (eTextLine == LINESTYLE_LONGDASH) ||
484 (eTextLine == LINESTYLE_BOLDLONGDASH) )
486 nMinDashWidth = nDotWidth*6;
487 nMinSpaceWidth = nDotWidth*2;
488 nDashWidth = 200;
489 nSpaceWidth = 100;
491 else
493 nMinDashWidth = nDotWidth*4;
494 nMinSpaceWidth = (nDotWidth*150)/100;
495 nDashWidth = 100;
496 nSpaceWidth = 50;
498 nDashWidth = o3tl::convert(nDashWidth * mnDPIX, o3tl::Length::mm100, o3tl::Length::in);
499 nSpaceWidth = o3tl::convert(nSpaceWidth * mnDPIX, o3tl::Length::mm100, o3tl::Length::in);
500 // DashWidth will be increased if the line is getting too thick
501 // in proportion to the line's length
502 if ( nDashWidth < nMinDashWidth )
503 nDashWidth = nMinDashWidth;
504 if ( nSpaceWidth < nMinSpaceWidth )
505 nSpaceWidth = nMinSpaceWidth;
507 tools::Long nTempWidth = nDashWidth;
508 tools::Long nEnd = nLeft+nWidth;
509 while ( nLeft < nEnd )
511 if ( nLeft+nTempWidth > nEnd )
512 nTempWidth = nEnd-nLeft;
513 ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempWidth, nLineHeight );
514 nLeft += nDashWidth+nSpaceWidth;
517 break;
518 case LINESTYLE_DASHDOT:
519 case LINESTYLE_BOLDDASHDOT:
521 tools::Long nDotWidth = nLineHeight*mnDPIY;
522 nDotWidth += mnDPIY/2;
523 nDotWidth /= mnDPIY;
525 tools::Long nDashWidth = o3tl::convert(100 * mnDPIX, o3tl::Length::mm100, o3tl::Length::in);
526 tools::Long nMinDashWidth = nDotWidth*4;
527 // DashWidth will be increased if the line is getting too thick
528 // in proportion to the line's length
529 if ( nDashWidth < nMinDashWidth )
530 nDashWidth = nMinDashWidth;
532 tools::Long nTempDotWidth = nDotWidth;
533 tools::Long nTempDashWidth = nDashWidth;
534 tools::Long nEnd = nLeft+nWidth;
535 while ( nLeft < nEnd )
537 if ( nLeft+nTempDotWidth > nEnd )
538 nTempDotWidth = nEnd-nLeft;
540 ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight );
541 nLeft += nDotWidth*2;
542 if ( nLeft > nEnd )
543 break;
545 if ( nLeft+nTempDashWidth > nEnd )
546 nTempDashWidth = nEnd-nLeft;
548 ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDashWidth, nLineHeight );
549 nLeft += nDashWidth+nDotWidth;
552 break;
553 case LINESTYLE_DASHDOTDOT:
554 case LINESTYLE_BOLDDASHDOTDOT:
556 tools::Long nDotWidth = nLineHeight*mnDPIY;
557 nDotWidth += mnDPIY/2;
558 nDotWidth /= mnDPIY;
560 tools::Long nDashWidth = o3tl::convert(100 * mnDPIX, o3tl::Length::mm100, o3tl::Length::in);
561 tools::Long nMinDashWidth = nDotWidth*4;
562 // DashWidth will be increased if the line is getting too thick
563 // in proportion to the line's length
564 if ( nDashWidth < nMinDashWidth )
565 nDashWidth = nMinDashWidth;
567 tools::Long nTempDotWidth = nDotWidth;
568 tools::Long nTempDashWidth = nDashWidth;
569 tools::Long nEnd = nLeft+nWidth;
570 while ( nLeft < nEnd )
572 if ( nLeft+nTempDotWidth > nEnd )
573 nTempDotWidth = nEnd-nLeft;
575 ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight );
576 nLeft += nDotWidth*2;
577 if ( nLeft > nEnd )
578 break;
580 if ( nLeft+nTempDotWidth > nEnd )
581 nTempDotWidth = nEnd-nLeft;
583 ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight );
584 nLeft += nDotWidth*2;
585 if ( nLeft > nEnd )
586 break;
588 if ( nLeft+nTempDashWidth > nEnd )
589 nTempDashWidth = nEnd-nLeft;
591 ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDashWidth, nLineHeight );
592 nLeft += nDashWidth+nDotWidth;
595 break;
596 default:
597 break;
601 void OutputDevice::ImplDrawStrikeoutLine( tools::Long nBaseX, tools::Long nBaseY,
602 tools::Long nDistX, tools::Long nDistY, tools::Long nWidth,
603 FontStrikeout eStrikeout,
604 Color aColor )
606 LogicalFontInstance* pFontInstance = mpFontInstance.get();
607 tools::Long nLineHeight = 0;
608 tools::Long nLinePos = 0;
609 tools::Long nLinePos2 = 0;
611 tools::Long nY = nDistY;
613 if ( eStrikeout > STRIKEOUT_LAST )
614 eStrikeout = STRIKEOUT_SINGLE;
616 switch ( eStrikeout )
618 case STRIKEOUT_SINGLE:
619 nLineHeight = pFontInstance->mxFontMetric->GetStrikeoutSize();
620 nLinePos = nY + pFontInstance->mxFontMetric->GetStrikeoutOffset();
621 break;
622 case STRIKEOUT_BOLD:
623 nLineHeight = pFontInstance->mxFontMetric->GetBoldStrikeoutSize();
624 nLinePos = nY + pFontInstance->mxFontMetric->GetBoldStrikeoutOffset();
625 break;
626 case STRIKEOUT_DOUBLE:
627 nLineHeight = pFontInstance->mxFontMetric->GetDoubleStrikeoutSize();
628 nLinePos = nY + pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1();
629 nLinePos2 = nY + pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2();
630 break;
631 default:
632 break;
635 if ( !nLineHeight )
636 return;
638 if ( mbLineColor || mbInitLineColor )
640 mpGraphics->SetLineColor();
641 mbInitLineColor = true;
643 mpGraphics->SetFillColor( aColor );
644 mbInitFillColor = true;
646 const tools::Long& nLeft = nDistX;
648 switch ( eStrikeout )
650 case STRIKEOUT_SINGLE:
651 case STRIKEOUT_BOLD:
652 ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
653 break;
654 case STRIKEOUT_DOUBLE:
655 ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
656 ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos2, nWidth, nLineHeight );
657 break;
658 default:
659 break;
663 void OutputDevice::ImplDrawStrikeoutChar( tools::Long nBaseX, tools::Long nBaseY,
664 tools::Long nDistX, tools::Long nDistY, tools::Long nWidth,
665 FontStrikeout eStrikeout,
666 Color aColor )
668 // See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
669 // to tweak this
670 if (!nWidth)
671 return;
673 // prepare string for strikeout measurement
674 const char cStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? '/' : 'X';
675 static const int nTestStrLen = 4;
676 static const int nMaxStrikeStrLen = 2048;
677 sal_Unicode aChars[nMaxStrikeStrLen+1]; // +1 for valgrind...
679 for( int i = 0; i < nTestStrLen; ++i)
680 aChars[i] = cStrikeoutChar;
682 const OUString aStrikeoutTest(aChars, nTestStrLen);
684 // calculate approximation of strikeout atom size
685 tools::Long nStrikeoutWidth = 0;
686 std::unique_ptr<SalLayout> pLayout = ImplLayout( aStrikeoutTest, 0, nTestStrLen );
687 if( pLayout )
689 nStrikeoutWidth = pLayout->GetTextWidth() / nTestStrLen;
691 if( nStrikeoutWidth <= 0 ) // sanity check
692 return;
694 int nStrikeStrLen = (nWidth+(nStrikeoutWidth-1)) / nStrikeoutWidth;
695 if( nStrikeStrLen > nMaxStrikeStrLen )
696 nStrikeStrLen = nMaxStrikeStrLen;
697 else if (nStrikeStrLen < 0)
698 nStrikeStrLen = 0;
700 // build the strikeout string
701 for( int i = nTestStrLen; i < nStrikeStrLen; ++i)
702 aChars[i] = cStrikeoutChar;
704 const OUString aStrikeoutText(aChars, nStrikeStrLen);
706 if( mpFontInstance->mnOrientation )
708 Point aOriginPt(0, 0);
709 aOriginPt.RotateAround( nDistX, nDistY, mpFontInstance->mnOrientation );
712 nBaseX += nDistX;
713 nBaseY += nDistY;
715 // strikeout text has to be left aligned
716 vcl::text::ComplexTextLayoutFlags nOrigTLM = mnTextLayoutMode;
717 mnTextLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiStrong;
718 pLayout = ImplLayout( aStrikeoutText, 0, aStrikeoutText.getLength() );
719 mnTextLayoutMode = nOrigTLM;
721 if( !pLayout )
722 return;
724 // draw the strikeout text
725 const Color aOldColor = GetTextColor();
726 SetTextColor( aColor );
727 ImplInitTextColor();
729 pLayout->DrawBase() = basegfx::B2DPoint(nBaseX + mnTextOffX, nBaseY + mnTextOffY);
731 tools::Rectangle aPixelRect;
732 aPixelRect.SetLeft( nBaseX+mnTextOffX );
733 aPixelRect.SetRight( aPixelRect.Left()+nWidth );
734 aPixelRect.SetBottom( nBaseY+mpFontInstance->mxFontMetric->GetDescent() );
735 aPixelRect.SetTop( nBaseY-mpFontInstance->mxFontMetric->GetAscent() );
737 if (mpFontInstance->mnOrientation)
739 tools::Polygon aPoly( aPixelRect );
740 aPoly.Rotate( Point(nBaseX+mnTextOffX, nBaseY+mnTextOffY), mpFontInstance->mnOrientation);
741 aPixelRect = aPoly.GetBoundRect();
744 Push( vcl::PushFlags::CLIPREGION );
745 IntersectClipRegion( PixelToLogic(aPixelRect) );
746 if( mbInitClipRegion )
747 InitClipRegion();
749 pLayout->DrawText( *mpGraphics );
751 Pop();
753 SetTextColor( aOldColor );
754 ImplInitTextColor();
757 void OutputDevice::ImplDrawTextLine( tools::Long nX, tools::Long nY,
758 tools::Long nDistX, double nWidth,
759 FontStrikeout eStrikeout,
760 FontLineStyle eUnderline,
761 FontLineStyle eOverline,
762 bool bUnderlineAbove )
764 if ( !nWidth )
765 return;
767 Color aStrikeoutColor = GetTextColor();
768 Color aUnderlineColor = GetTextLineColor();
769 Color aOverlineColor = GetOverlineColor();
770 bool bStrikeoutDone = false;
771 bool bUnderlineDone = false;
772 bool bOverlineDone = false;
774 if ( IsRTLEnabled() )
776 tools::Long nXAdd = nWidth - nDistX;
777 if( mpFontInstance->mnOrientation )
778 nXAdd = basegfx::fround<tools::Long>( nXAdd * cos( toRadians(mpFontInstance->mnOrientation) ) );
780 nX += nXAdd - 1;
783 if ( !IsTextLineColor() )
784 aUnderlineColor = GetTextColor();
786 if ( !IsOverlineColor() )
787 aOverlineColor = GetTextColor();
789 if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
790 (eUnderline == LINESTYLE_WAVE) ||
791 (eUnderline == LINESTYLE_DOUBLEWAVE) ||
792 (eUnderline == LINESTYLE_BOLDWAVE) )
794 ImplDrawWaveTextLine( nX, nY, nDistX, 0, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
795 bUnderlineDone = true;
797 if ( (eOverline == LINESTYLE_SMALLWAVE) ||
798 (eOverline == LINESTYLE_WAVE) ||
799 (eOverline == LINESTYLE_DOUBLEWAVE) ||
800 (eOverline == LINESTYLE_BOLDWAVE) )
802 ImplDrawWaveTextLine( nX, nY, nDistX, 0, nWidth, eOverline, aOverlineColor, true );
803 bOverlineDone = true;
806 if ( (eStrikeout == STRIKEOUT_SLASH) ||
807 (eStrikeout == STRIKEOUT_X) )
809 ImplDrawStrikeoutChar( nX, nY, nDistX, 0, nWidth, eStrikeout, aStrikeoutColor );
810 bStrikeoutDone = true;
813 if ( !bUnderlineDone )
814 ImplDrawStraightTextLine( nX, nY, nDistX, 0, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
816 if ( !bOverlineDone )
817 ImplDrawStraightTextLine( nX, nY, nDistX, 0, nWidth, eOverline, aOverlineColor, true );
819 if ( !bStrikeoutDone )
820 ImplDrawStrikeoutLine( nX, nY, nDistX, 0, nWidth, eStrikeout, aStrikeoutColor );
823 void OutputDevice::ImplDrawTextLines( SalLayout& rSalLayout, FontStrikeout eStrikeout,
824 FontLineStyle eUnderline, FontLineStyle eOverline,
825 bool bWordLine, bool bUnderlineAbove )
827 if( bWordLine )
829 // draw everything relative to the layout base point
830 const basegfx::B2DPoint aStartPt = rSalLayout.DrawBase();
832 // calculate distance of each word from the base point
833 basegfx::B2DPoint aPos;
834 double nDist = 0;
835 double nWidth = 0;
836 const GlyphItem* pGlyph;
837 int nStart = 0;
838 while (rSalLayout.GetNextGlyph(&pGlyph, aPos, nStart))
840 // calculate the boundaries of each word
841 if (!pGlyph->IsSpacing())
843 if( !nWidth )
845 // get the distance to the base point (as projected to baseline)
846 nDist = aPos.getX() - aStartPt.getX();
847 if( mpFontInstance->mnOrientation )
849 const double nDY = aPos.getY() - aStartPt.getY();
850 const double fRad = toRadians(mpFontInstance->mnOrientation);
851 nDist = basegfx::fround<tools::Long>(nDist * cos(fRad) - nDY * sin(fRad));
855 // update the length of the textline
856 nWidth += pGlyph->newWidth();
858 else if( nWidth > 0 )
860 // draw the textline for each word
861 ImplDrawTextLine( aStartPt.getX(), aStartPt.getY(), nDist, nWidth,
862 eStrikeout, eUnderline, eOverline, bUnderlineAbove );
863 nWidth = 0;
867 // draw textline for the last word
868 if( nWidth > 0 )
870 ImplDrawTextLine( aStartPt.getX(), aStartPt.getY(), nDist, nWidth,
871 eStrikeout, eUnderline, eOverline, bUnderlineAbove );
874 else
876 basegfx::B2DPoint aStartPt = rSalLayout.GetDrawPosition();
877 ImplDrawTextLine( aStartPt.getX(), aStartPt.getY(), 0,
878 rSalLayout.GetTextWidth(),
879 eStrikeout, eUnderline, eOverline, bUnderlineAbove );
883 void OutputDevice::ImplDrawMnemonicLine( tools::Long nX, tools::Long nY, tools::Long nWidth )
885 tools::Long nBaseX = nX;
886 if( /*HasMirroredGraphics() &&*/ IsRTLEnabled() )
888 // revert the hack that will be done later in ImplDrawTextLine
889 nX = nBaseX - nWidth - (nX - nBaseX - 1);
892 ImplDrawTextLine( nX, nY, 0, nWidth, STRIKEOUT_NONE, LINESTYLE_SINGLE, LINESTYLE_NONE, false );
895 void OutputDevice::SetTextLineColor()
898 if ( mpMetaFile )
899 mpMetaFile->AddAction( new MetaTextLineColorAction( Color(), false ) );
901 maTextLineColor = COL_TRANSPARENT;
903 if( mpAlphaVDev )
904 mpAlphaVDev->SetTextLineColor();
907 void OutputDevice::SetTextLineColor( const Color& rColor )
909 Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
911 if ( mpMetaFile )
912 mpMetaFile->AddAction( new MetaTextLineColorAction( aColor, true ) );
914 maTextLineColor = aColor;
916 if( mpAlphaVDev )
917 mpAlphaVDev->SetTextLineColor( COL_ALPHA_OPAQUE );
920 void OutputDevice::SetOverlineColor()
923 if ( mpMetaFile )
924 mpMetaFile->AddAction( new MetaOverlineColorAction( Color(), false ) );
926 maOverlineColor = COL_TRANSPARENT;
928 if( mpAlphaVDev )
929 mpAlphaVDev->SetOverlineColor();
932 void OutputDevice::SetOverlineColor( const Color& rColor )
934 Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
936 if ( mpMetaFile )
937 mpMetaFile->AddAction( new MetaOverlineColorAction( aColor, true ) );
939 maOverlineColor = aColor;
941 if( mpAlphaVDev )
942 mpAlphaVDev->SetOverlineColor( COL_ALPHA_OPAQUE );
945 void OutputDevice::DrawTextLine( const Point& rPos, tools::Long nWidth,
946 FontStrikeout eStrikeout,
947 FontLineStyle eUnderline,
948 FontLineStyle eOverline,
949 bool bUnderlineAbove )
951 assert(!is_double_buffered_window());
953 if ( mpMetaFile )
954 mpMetaFile->AddAction( new MetaTextLineAction( rPos, nWidth, eStrikeout, eUnderline, eOverline ) );
956 if ( ((eUnderline == LINESTYLE_NONE) || (eUnderline == LINESTYLE_DONTKNOW)) &&
957 ((eOverline == LINESTYLE_NONE) || (eOverline == LINESTYLE_DONTKNOW)) &&
958 ((eStrikeout == STRIKEOUT_NONE) || (eStrikeout == STRIKEOUT_DONTKNOW)) )
960 return;
962 if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
963 return;
965 if( mbInitClipRegion )
966 InitClipRegion();
968 if( mbOutputClipped )
969 return;
971 // initialize font if needed to get text offsets
972 // TODO: only needed for mnTextOff!=(0,0)
973 if (!InitFont())
974 return;
976 Point aPos = ImplLogicToDevicePixel( rPos );
977 double fWidth = ImplLogicWidthToDeviceSubPixel(nWidth);
978 aPos += Point( mnTextOffX, mnTextOffY );
979 ImplDrawTextLine( aPos.X(), aPos.X(), 0, fWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove );
981 if( mpAlphaVDev )
982 mpAlphaVDev->DrawTextLine( rPos, nWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove );
985 void OutputDevice::DrawWaveLine(const Point& rStartPos, const Point& rEndPos, tools::Long nLineWidth, tools::Long nWaveHeight)
987 assert(!is_double_buffered_window());
989 if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
990 return;
992 // we need a graphics
993 if( !mpGraphics && !AcquireGraphics() )
994 return;
995 assert(mpGraphics);
997 if ( mbInitClipRegion )
998 InitClipRegion();
1000 if ( mbOutputClipped )
1001 return;
1003 if (!InitFont())
1004 return;
1006 Point aStartPt = ImplLogicToDevicePixel(rStartPos);
1007 Point aEndPt = ImplLogicToDevicePixel(rEndPos);
1009 tools::Long nStartX = aStartPt.X();
1010 tools::Long nStartY = aStartPt.Y();
1011 tools::Long nEndX = aEndPt.X();
1012 tools::Long nEndY = aEndPt.Y();
1013 double fOrientation = 0.0;
1015 // handle rotation
1016 if (nStartY != nEndY || nStartX > nEndX)
1018 fOrientation = basegfx::rad2deg(std::atan2(nStartY - nEndY, nEndX - nStartX));
1019 // un-rotate the end point
1020 aStartPt.RotateAround(nEndX, nEndY, Degree10(static_cast<sal_Int16>(-fOrientation * 10.0)));
1023 // Handle HiDPI
1024 float fScaleFactor = GetDPIScaleFactor();
1025 if (fScaleFactor > 1.0f)
1027 nWaveHeight *= fScaleFactor;
1029 nStartY += fScaleFactor - 1; // Shift down additional pixel(s) to create more visual separation.
1031 // odd heights look better than even
1032 if (nWaveHeight % 2 == 0)
1034 nWaveHeight--;
1038 // #109280# make sure the waveline does not exceed the descent to avoid paint problems
1039 LogicalFontInstance* pFontInstance = mpFontInstance.get();
1040 if (nWaveHeight > pFontInstance->mxFontMetric->GetWavelineUnderlineSize()
1041 // tdf#153223 polyline with lineheight >0 not drawn when skia is off
1042 #ifdef MACOSX
1043 || !SkiaHelper::isVCLSkiaEnabled()
1044 #endif
1047 nWaveHeight = pFontInstance->mxFontMetric->GetWavelineUnderlineSize();
1048 // tdf#124848 hairline
1049 nLineWidth = 0;
1052 if ( fOrientation == 0.0 )
1054 static tools::DeleteOnDeinit< WavyLineCache > snLineCache {};
1055 if ( !snLineCache.get() )
1056 return;
1057 WavyLineCache& rLineCache = *snLineCache.get();
1058 BitmapEx aWavylinebmp;
1059 if ( !rLineCache.find( GetLineColor(), nLineWidth, nWaveHeight, nEndX - nStartX, aWavylinebmp ) )
1061 size_t nWordLength = nEndX - nStartX;
1062 // start with something big to avoid updating it frequently
1063 nWordLength = nWordLength < 1024 ? 1024 : nWordLength;
1064 ScopedVclPtrInstance< VirtualDevice > pVirtDev( *this, DeviceFormat::WITH_ALPHA );
1065 pVirtDev->SetOutputSizePixel( Size( nWordLength, nWaveHeight * 2 ), false );
1066 pVirtDev->SetLineColor( GetLineColor() );
1067 pVirtDev->SetBackground( Wallpaper( COL_TRANSPARENT ) );
1068 pVirtDev->Erase();
1069 pVirtDev->SetAntialiasing( AntialiasingFlags::Enable );
1070 pVirtDev->ImplDrawWaveLineBezier( 0, 0, nWordLength, 0, nWaveHeight, fOrientation, nLineWidth );
1071 BitmapEx aBitmapEx(pVirtDev->GetBitmapEx(Point(0, 0), pVirtDev->GetOutputSize()));
1073 // Ideally we don't need this block, but in the split rgb surface + separate alpha surface
1074 // with Antialiasing enabled and the svp/cairo backend we get both surfaces antialiased
1075 // so their combination of aliases merge to overly wash-out the color. Hack it by taking just
1076 // the alpha surface and use it to blend the original solid line color
1077 Bitmap aSolidColor(aBitmapEx.GetBitmap());
1078 aSolidColor.Erase(GetLineColor());
1079 aBitmapEx = BitmapEx(aSolidColor, aBitmapEx.GetAlphaMask());
1081 rLineCache.insert( aBitmapEx, GetLineColor(), nLineWidth, nWaveHeight, nWordLength, aWavylinebmp );
1083 if ( aWavylinebmp.ImplGetBitmapSalBitmap() != nullptr )
1085 Size _size( nEndX - nStartX, aWavylinebmp.GetSizePixel().Height() );
1086 DrawBitmapEx(Point( rStartPos.X(), rStartPos.Y() ), PixelToLogic( _size ), Point(), _size, aWavylinebmp);
1088 return;
1091 ImplDrawWaveLineBezier( nStartX, nStartY, nEndX, nEndY, nWaveHeight, fOrientation, nLineWidth );
1094 void OutputDevice::ImplDrawWaveLineBezier(tools::Long nStartX, tools::Long nStartY, tools::Long nEndX, tools::Long nEndY, tools::Long nWaveHeight, double fOrientation, tools::Long nLineWidth)
1096 // we need a graphics
1097 if( !mpGraphics && !AcquireGraphics() )
1098 return;
1099 assert(mpGraphics);
1101 if ( mbInitClipRegion )
1102 InitClipRegion();
1104 if ( mbOutputClipped )
1105 return;
1107 if (!InitFont())
1108 return;
1110 const basegfx::B2DRectangle aWaveLineRectangle(nStartX, nStartY, nEndX, nEndY + nWaveHeight);
1111 const basegfx::B2DPolygon aWaveLinePolygon = basegfx::createWaveLinePolygon(aWaveLineRectangle);
1112 const basegfx::B2DHomMatrix aRotationMatrix = basegfx::utils::createRotateAroundPoint(nStartX, nStartY, basegfx::deg2rad(-fOrientation));
1113 const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
1115 mpGraphics->SetLineColor(GetLineColor());
1116 mpGraphics->DrawPolyLine(
1117 aRotationMatrix,
1118 aWaveLinePolygon,
1119 0.0,
1120 nLineWidth,
1121 nullptr, // MM01
1122 basegfx::B2DLineJoin::NONE,
1123 css::drawing::LineCap_BUTT,
1124 basegfx::deg2rad(15.0),
1125 bPixelSnapHairline,
1126 *this);
1128 if( mpAlphaVDev )
1129 mpAlphaVDev->ImplDrawWaveLineBezier(nStartX, nStartY, nEndX, nEndY, nWaveHeight, fOrientation, nLineWidth);
1132 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */