Bump version to 24.04.3.4
[LibreOffice.git] / cppcanvas / source / mtfrenderer / mtftools.cxx
blob94a83da70c19360fad3a25876f3c1b131bc66552
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 .
21 #include <comphelper/diagnose_ex.hxx>
22 #include <com/sun/star/rendering/XCanvas.hpp>
23 #include <basegfx/utils/canvastools.hxx>
24 #include <basegfx/polygon/b2dpolygontools.hxx>
25 #include <basegfx/polygon/b2dpolygon.hxx>
26 #include <basegfx/range/b2drectangle.hxx>
27 #include <basegfx/vector/b2dvector.hxx>
28 #include <canvas/canvastools.hxx>
29 #include <rtl/math.hxx>
30 #include <vcl/canvastools.hxx>
31 #include <vcl/virdev.hxx>
32 #include <vcl/metric.hxx>
33 #include "mtftools.hxx"
34 #include <outdevstate.hxx>
35 #include <basegfx/matrix/b2dhommatrixtools.hxx>
38 using namespace ::com::sun::star;
40 namespace cppcanvas::tools
42 void initRenderState( rendering::RenderState& renderState,
43 const ::cppcanvas::internal::OutDevState& outdevState )
45 ::canvas::tools::initRenderState( renderState );
46 ::canvas::tools::setRenderStateTransform( renderState,
47 outdevState.transform );
48 renderState.Clip = outdevState.xClipPoly;
51 ::Size getBaselineOffset( const ::cppcanvas::internal::OutDevState& outdevState,
52 const VirtualDevice& rVDev )
54 const ::FontMetric& aMetric = rVDev.GetFontMetric();
56 // calc offset for text output, the XCanvas always renders
57 // baseline offset.
58 switch( outdevState.textReferencePoint )
60 case ALIGN_TOP:
61 return ::Size( 0,
62 aMetric.GetInternalLeading() + aMetric.GetAscent() );
64 case ALIGN_BASELINE:
65 return ::Size( 0, 0 );
67 case ALIGN_BOTTOM:
68 return ::Size( 0,
69 -aMetric.GetDescent() );
71 default:
72 throw css::uno::RuntimeException(
73 "tools::getBaselineOffset(): Unexpected TextAlign value" );
77 ::basegfx::B2DHomMatrix& calcLogic2PixelLinearTransform( ::basegfx::B2DHomMatrix& o_rMatrix,
78 const VirtualDevice& rVDev )
80 // select size value in the middle of the available range,
81 // to have headroom both when map mode scales up, and when
82 // it scales down.
83 const ::Size aSizeLogic( 0x00010000L,
84 0x00010000L );
86 const ::Size aSizePixel( rVDev.LogicToPixel( aSizeLogic ) );
88 o_rMatrix = basegfx::utils::createScaleB2DHomMatrix(
89 aSizePixel.Width() / static_cast<double>(aSizeLogic.Width()),
90 aSizePixel.Height() / static_cast<double>(aSizeLogic.Height()) );
92 return o_rMatrix;
95 ::basegfx::B2DHomMatrix& calcLogic2PixelAffineTransform( ::basegfx::B2DHomMatrix& o_rMatrix,
96 const VirtualDevice& rVDev )
98 // retrieves scale
99 calcLogic2PixelLinearTransform(o_rMatrix, rVDev);
101 // translate according to curr map mode/pref map mode offset
102 const ::Point aEmptyPoint;
103 const ::Point& rTranslatedPoint(
104 rVDev.LogicToPixel( aEmptyPoint ));
106 o_rMatrix.translate(rTranslatedPoint.X(),
107 rTranslatedPoint.Y());
109 return o_rMatrix;
112 bool modifyClip( rendering::RenderState& o_rRenderState,
113 const struct ::cppcanvas::internal::OutDevState& rOutdevState,
114 const CanvasSharedPtr& rCanvas,
115 const ::basegfx::B2DPoint& rOffset,
116 const ::basegfx::B2DVector* pScaling,
117 const double* pRotation )
119 const bool bOffsetting( !rOffset.equalZero() );
120 const bool bScaling( pScaling &&
121 !rtl::math::approxEqual(pScaling->getX(), 1.0) &&
122 !rtl::math::approxEqual(pScaling->getY(), 1.0) );
123 const bool bRotation( pRotation &&
124 *pRotation != 0.0 );
126 if( !bOffsetting && !bScaling && !bRotation )
127 return false; // nothing to do
129 if( rOutdevState.clip.count() )
131 // general polygon case
133 ::basegfx::B2DPolyPolygon aLocalClip( rOutdevState.clip );
134 ::basegfx::B2DHomMatrix aTransform;
136 if( bOffsetting )
137 aTransform.translate( -rOffset.getX(),
138 -rOffset.getY() );
139 if( bScaling )
140 aTransform.scale( 1.0/pScaling->getX(), 1.0/pScaling->getY() );
142 if( bRotation )
143 aTransform.rotate( - *pRotation );
145 aLocalClip.transform( aTransform );
147 o_rRenderState.Clip = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
148 rCanvas->getUNOCanvas()->getDevice(),
149 aLocalClip );
151 return true;
153 else if( !rOutdevState.clipRect.IsEmpty() )
155 // simple rect case
157 const ::tools::Rectangle aLocalClipRect( rOutdevState.clipRect );
159 if( bRotation )
161 // rotation involved - convert to polygon first,
162 // then transform that
163 ::basegfx::B2DPolygon aLocalClip(
164 ::basegfx::utils::createPolygonFromRect(
165 vcl::unotools::b2DRectangleFromRectangle(aLocalClipRect) ) );
166 ::basegfx::B2DHomMatrix aTransform;
168 if( bOffsetting )
169 aTransform.translate( -rOffset.getX(),
170 -rOffset.getY() );
171 if( bScaling )
172 aTransform.scale( 1.0/pScaling->getX(), 1.0/pScaling->getY() );
174 aTransform.rotate( - *pRotation );
176 aLocalClip.transform( aTransform );
178 o_rRenderState.Clip = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
179 rCanvas->getUNOCanvas()->getDevice(),
180 ::basegfx::B2DPolyPolygon( aLocalClip ) );
182 else if( bScaling )
184 // scale and offset - do it on the fly, have to
185 // convert to float anyway.
186 o_rRenderState.Clip = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
187 rCanvas->getUNOCanvas()->getDevice(),
188 ::basegfx::B2DPolyPolygon(
189 ::basegfx::utils::createPolygonFromRect(
190 ::basegfx::B2DRectangle(
191 (aLocalClipRect.Left() - rOffset.getX())/pScaling->getX(),
192 (aLocalClipRect.Top() - rOffset.getY())/pScaling->getY(),
193 (aLocalClipRect.Right() - rOffset.getX())/pScaling->getX(),
194 (aLocalClipRect.Bottom() - rOffset.getY())/pScaling->getY() ) ) ) );
196 else
198 // offset only - do it on the fly, have to convert
199 // to float anyway.
200 o_rRenderState.Clip = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
201 rCanvas->getUNOCanvas()->getDevice(),
202 ::basegfx::B2DPolyPolygon(
203 ::basegfx::utils::createPolygonFromRect(
204 ::basegfx::B2DRectangle( aLocalClipRect.Left() - rOffset.getX(),
205 aLocalClipRect.Top() - rOffset.getY(),
206 aLocalClipRect.Right() - rOffset.getX(),
207 aLocalClipRect.Bottom() - rOffset.getY() ) ) ) );
210 return true;
213 // empty clip, nothing to do
214 return false;
217 // create overline/underline/strikeout line info struct
218 TextLineInfo createTextLineInfo( const ::VirtualDevice& rVDev,
219 const ::cppcanvas::internal::OutDevState& rState )
221 const bool bOldMode( rVDev.IsMapModeEnabled() );
223 // #i68512# Force metric regeneration with mapmode enabled
224 // (prolly OutDev bug)
225 rVDev.GetFontMetric();
227 // will restore map mode below
228 const_cast< ::VirtualDevice& >(rVDev).EnableMapMode( false );
230 const ::FontMetric aMetric = rVDev.GetFontMetric();
232 TextLineInfo aTextInfo(
233 (aMetric.GetDescent() + 2) / 4.0,
234 ((aMetric.GetInternalLeading() + 1.5) / 3.0),
235 (aMetric.GetInternalLeading() / 2.0) - aMetric.GetAscent(),
236 aMetric.GetDescent() / 2.0,
237 (aMetric.GetInternalLeading() - aMetric.GetAscent()) / 3.0,
238 rState.textOverlineStyle,
239 rState.textUnderlineStyle,
240 rState.textStrikeoutStyle );
242 const_cast< ::VirtualDevice& >(rVDev).EnableMapMode( bOldMode );
244 return aTextInfo;
247 namespace
249 void appendWaveline( ::basegfx::B2DPolyPolygon& o_rPoly,
250 const ::basegfx::B2DPoint& rStartPos,
251 const double nStartOffset,
252 const double nWidth,
253 const double nHeight,
254 sal_Int8 nLineStyle)
256 const double x(rStartPos.getX());
257 const double y(rStartPos.getY() + nStartOffset + nHeight);
258 double nWaveWidth = nHeight * 10.6 * 0.25;
259 // Offset for the double line.
260 double nOffset = 0.0;
262 if (nLineStyle == LINESTYLE_DOUBLEWAVE)
263 nOffset = -nHeight * 0.5;
264 else
265 nWaveWidth *= 2.0;
267 basegfx::B2DPolygon aLine;
268 aLine.append(basegfx::B2DPoint(x, y + nOffset));
269 aLine.append(basegfx::B2DPoint(x + nWidth, y + nOffset));
271 o_rPoly.append(::basegfx::utils::createWaveline(aLine, nWaveWidth, nWaveWidth * 0.5));
273 if (nLineStyle == LINESTYLE_DOUBLEWAVE)
275 nOffset = nHeight * 1.2;
277 basegfx::B2DPolygon aLine2;
278 aLine2.append(basegfx::B2DPoint(x, y + nOffset));
279 aLine2.append(basegfx::B2DPoint(x + nWidth, y + nOffset));
280 o_rPoly.append(::basegfx::utils::createWaveline(aLine2, nWaveWidth, nWaveWidth * 0.5));
284 void appendRect( ::basegfx::B2DPolyPolygon& o_rPoly,
285 const ::basegfx::B2DPoint& rStartPos,
286 const double nX1,
287 const double nY1,
288 const double nX2,
289 const double nY2 )
291 const double x( rStartPos.getX() );
292 const double y( rStartPos.getY() );
294 o_rPoly.append(
295 ::basegfx::utils::createPolygonFromRect(
296 ::basegfx::B2DRectangle( x + nX1, y + nY1, x + nX2, y + nY2 ) ) );
299 void appendRect( ::basegfx::B2DPolyPolygon& o_rPoly,
300 const double nX1,
301 const double nY1,
302 const double nX2,
303 const double nY2 )
305 o_rPoly.append(
306 ::basegfx::utils::createPolygonFromRect(
307 ::basegfx::B2DRectangle( nX1, nY1, nX2, nY2 ) ) );
310 bool appendDashes( ::basegfx::B2DPolyPolygon& o_rPoly,
311 const double nX,
312 double nY,
313 const double nLineWidth,
314 double nLineHeight,
315 sal_Int8 nLineStyle,
316 bool bIsOverline)
318 static const int aDottedArray[] = { 1, 1, 0}; // DOTTED LINE
319 static const int aDotDashArray[] = { 1, 1, 4, 1, 0}; // DASHDOT
320 static const int aDashDotDotArray[] = { 1, 1, 1, 1, 4, 1, 0}; // DASHDOTDOT
321 static const int aDashedArray[] = { 5, 2, 0}; // DASHED LINE
322 static const int aLongDashArray[] = { 7, 2, 0}; // LONGDASH
323 const int *pArray = nullptr;
324 bool bIsBold = false;
326 switch(nLineStyle)
328 case LINESTYLE_BOLDDOTTED:
329 bIsBold = true;
330 [[fallthrough]];
331 case LINESTYLE_DOTTED:
332 pArray = aDottedArray;
333 break;
335 case LINESTYLE_BOLDDASH:
336 bIsBold = true;
337 [[fallthrough]];
338 case LINESTYLE_DASH:
339 pArray = aDashedArray;
340 break;
342 case LINESTYLE_BOLDLONGDASH:
343 bIsBold = true;
344 [[fallthrough]];
345 case LINESTYLE_LONGDASH:
346 pArray = aLongDashArray;
347 break;
349 case LINESTYLE_BOLDDASHDOT:
350 bIsBold = true;
351 [[fallthrough]];
352 case LINESTYLE_DASHDOT:
353 pArray = aDotDashArray;
354 break;
355 case LINESTYLE_BOLDDASHDOTDOT:
356 bIsBold = true;
357 [[fallthrough]];
358 case LINESTYLE_DASHDOTDOT:
359 pArray = aDashDotDotArray;
360 break;
363 if (!pArray)
364 return false;
366 if (bIsBold)
368 if (bIsOverline)
369 nY -= nLineHeight;
371 nLineHeight *= 2;
374 const double nEnd = nX + nLineWidth;
375 sal_Int32 nIndex = 0;
376 bool bAppend = true;
377 double nX1 = nX;
379 while(nX1 < nEnd)
381 if (pArray[nIndex] == 0)
382 nIndex = 0;
384 const double nX2 = std::min(nEnd, nX1 + pArray[nIndex] * nLineHeight);
386 if (bAppend)
387 appendRect(o_rPoly, nX1, nY, nX2, nY + nLineHeight);
389 nX1 = nX2;
391 ++nIndex;
393 bAppend = !bAppend;
395 return true;
398 // create line actions for text such as underline and
399 // strikeout
400 void createOverlinePolyPolygon(::basegfx::B2DPolyPolygon& rTextLinesPolyPoly,
401 const ::basegfx::B2DPoint& rStartPos,
402 const double& rLineWidth,
403 const TextLineInfo& rTextLineInfo)
405 switch( rTextLineInfo.mnOverlineStyle )
407 case LINESTYLE_NONE: // nothing to do
408 case LINESTYLE_DONTKNOW:
409 break;
411 case LINESTYLE_DOUBLEWAVE:
412 case LINESTYLE_SMALLWAVE:
413 case LINESTYLE_BOLDWAVE:
414 case LINESTYLE_WAVE:
415 appendWaveline(
416 rTextLinesPolyPoly,
417 rStartPos,
418 rTextLineInfo.mnOverlineOffset,
419 rLineWidth,
420 rTextLineInfo.mnOverlineHeight,
421 rTextLineInfo.mnOverlineStyle);
423 break;
424 case LINESTYLE_SINGLE:
425 appendRect(
426 rTextLinesPolyPoly,
427 rStartPos,
429 rTextLineInfo.mnOverlineOffset,
430 rLineWidth,
431 rTextLineInfo.mnOverlineOffset + rTextLineInfo.mnOverlineHeight );
432 break;
433 case LINESTYLE_BOLD:
434 appendRect(
435 rTextLinesPolyPoly,
436 rStartPos,
438 rTextLineInfo.mnOverlineOffset - rTextLineInfo.mnOverlineHeight,
439 rLineWidth,
440 rTextLineInfo.mnOverlineOffset + rTextLineInfo.mnOverlineHeight );
441 break;
443 case LINESTYLE_DOUBLE:
444 appendRect(
445 rTextLinesPolyPoly,
446 rStartPos,
448 rTextLineInfo.mnOverlineOffset - rTextLineInfo.mnOverlineHeight * 2.0 ,
449 rLineWidth,
450 rTextLineInfo.mnOverlineOffset - rTextLineInfo.mnOverlineHeight );
452 appendRect(
453 rTextLinesPolyPoly,
454 rStartPos,
456 rTextLineInfo.mnOverlineOffset + rTextLineInfo.mnOverlineHeight,
457 rLineWidth,
458 rTextLineInfo.mnOverlineOffset + rTextLineInfo.mnOverlineHeight * 2.0 );
459 break;
461 default:
462 if (!appendDashes(
463 rTextLinesPolyPoly,
464 rStartPos.getX(),
465 rStartPos.getY() + rTextLineInfo.mnOverlineOffset,
466 rLineWidth,
467 rTextLineInfo.mnOverlineHeight,
468 rTextLineInfo.mnOverlineStyle,
469 true))
471 ENSURE_OR_THROW( false,
472 "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected overline case" );
477 void createUnderlinePolyPolygon(::basegfx::B2DPolyPolygon& rTextLinesPolyPoly,
478 const ::basegfx::B2DPoint& rStartPos,
479 const double& rLineWidth,
480 const TextLineInfo& rTextLineInfo )
483 switch( rTextLineInfo.mnUnderlineStyle )
485 case LINESTYLE_NONE: // nothing to do
486 case LINESTYLE_DONTKNOW:
487 break;
489 case LINESTYLE_DOUBLEWAVE:
490 case LINESTYLE_SMALLWAVE:
491 case LINESTYLE_BOLDWAVE:
492 case LINESTYLE_WAVE:
493 appendWaveline(
494 rTextLinesPolyPoly,
495 rStartPos,
496 rTextLineInfo.mnUnderlineOffset,
497 rLineWidth,
498 rTextLineInfo.mnLineHeight,
499 rTextLineInfo.mnUnderlineStyle);
500 break;
501 case LINESTYLE_SINGLE:
502 appendRect(
503 rTextLinesPolyPoly,
504 rStartPos,
506 rTextLineInfo.mnUnderlineOffset,
507 rLineWidth,
508 rTextLineInfo.mnUnderlineOffset + rTextLineInfo.mnLineHeight );
509 break;
511 case LINESTYLE_BOLD:
512 appendRect(
513 rTextLinesPolyPoly,
514 rStartPos,
516 rTextLineInfo.mnUnderlineOffset,
517 rLineWidth,
518 rTextLineInfo.mnUnderlineOffset + 2*rTextLineInfo.mnLineHeight );
519 break;
521 case LINESTYLE_DOUBLE:
522 appendRect(
523 rTextLinesPolyPoly,
524 rStartPos,
526 rTextLineInfo.mnUnderlineOffset - rTextLineInfo.mnLineHeight,
527 rLineWidth,
528 rTextLineInfo.mnUnderlineOffset );
530 appendRect(
531 rTextLinesPolyPoly,
532 rStartPos,
534 rTextLineInfo.mnUnderlineOffset + 2*rTextLineInfo.mnLineHeight,
535 rLineWidth,
536 rTextLineInfo.mnUnderlineOffset + 3*rTextLineInfo.mnLineHeight );
537 break;
539 default:
540 if (!appendDashes(
541 rTextLinesPolyPoly,
542 rStartPos.getX(),
543 rStartPos.getY() + rTextLineInfo.mnUnderlineOffset,
544 rLineWidth,
545 rTextLineInfo.mnLineHeight,
546 rTextLineInfo.mnUnderlineStyle,
547 false))
549 ENSURE_OR_THROW( false,
550 "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected underline case" );
555 void createStrikeoutPolyPolygon(::basegfx::B2DPolyPolygon& rTextLinesPolyPoly,
556 const ::basegfx::B2DPoint& rStartPos,
557 const double& rLineWidth,
558 const TextLineInfo& rTextLineInfo)
560 switch( rTextLineInfo.mnStrikeoutStyle )
562 case STRIKEOUT_NONE: // nothing to do
563 case STRIKEOUT_DONTKNOW:
564 break;
566 case STRIKEOUT_SLASH: // TODO(Q1): we should handle this in the text layer
567 case STRIKEOUT_X:
568 break;
570 case STRIKEOUT_SINGLE:
571 appendRect(
572 rTextLinesPolyPoly,
573 rStartPos,
575 rTextLineInfo.mnStrikeoutOffset,
576 rLineWidth,
577 rTextLineInfo.mnStrikeoutOffset + rTextLineInfo.mnLineHeight );
578 break;
580 case STRIKEOUT_BOLD:
581 appendRect(
582 rTextLinesPolyPoly,
583 rStartPos,
585 rTextLineInfo.mnStrikeoutOffset,
586 rLineWidth,
587 rTextLineInfo.mnStrikeoutOffset + 2*rTextLineInfo.mnLineHeight );
588 break;
590 case STRIKEOUT_DOUBLE:
591 appendRect(
592 rTextLinesPolyPoly,
593 rStartPos,
595 rTextLineInfo.mnStrikeoutOffset - rTextLineInfo.mnLineHeight,
596 rLineWidth,
597 rTextLineInfo.mnStrikeoutOffset );
599 appendRect(
600 rTextLinesPolyPoly,
601 rStartPos,
603 rTextLineInfo.mnStrikeoutOffset + 2*rTextLineInfo.mnLineHeight,
604 rLineWidth,
605 rTextLineInfo.mnStrikeoutOffset + 3*rTextLineInfo.mnLineHeight );
606 break;
608 default:
609 ENSURE_OR_THROW( false,
610 "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected strikeout case" );
615 ::basegfx::B2DPolyPolygon createTextLinesPolyPolygon( const ::basegfx::B2DPoint& rStartPos,
616 const double& rLineWidth,
617 const TextLineInfo& rTextLineInfo )
619 // fill the polypolygon with all text lines
620 ::basegfx::B2DPolyPolygon aTextLinesPolyPoly;
622 createOverlinePolyPolygon(aTextLinesPolyPoly, rStartPos, rLineWidth, rTextLineInfo);
623 createUnderlinePolyPolygon(aTextLinesPolyPoly, rStartPos, rLineWidth, rTextLineInfo);
624 createStrikeoutPolyPolygon(aTextLinesPolyPoly, rStartPos, rLineWidth, rTextLineInfo);
625 return aTextLinesPolyPoly;
628 ::basegfx::B2DRange calcDevicePixelBounds( const ::basegfx::B2DRange& rBounds,
629 const rendering::ViewState& viewState,
630 const rendering::RenderState& renderState )
632 ::basegfx::B2DHomMatrix aTransform;
633 ::canvas::tools::mergeViewAndRenderTransform( aTransform,
634 viewState,
635 renderState );
637 ::basegfx::B2DRange aTransformedBounds;
638 return ::canvas::tools::calcTransformedRectBounds( aTransformedBounds,
639 rBounds,
640 aTransform );
643 // create line actions for text such as underline and
644 // strikeout
645 ::basegfx::B2DPolyPolygon createTextLinesPolyPolygon( const double& rStartOffset,
646 const double& rLineWidth,
647 const TextLineInfo& rTextLineInfo )
649 return createTextLinesPolyPolygon(
650 ::basegfx::B2DPoint( rStartOffset,
651 0.0 ),
652 rLineWidth,
653 rTextLineInfo );
656 void createTextLinesPolyPolygon( const double& rStartOffset,
657 const double& rLineWidth,
658 const TextLineInfo& rTextLineInfo,
659 ::basegfx::B2DPolyPolygon& rOverlinePolyPoly,
660 ::basegfx::B2DPolyPolygon& rUnderlinePolyPoly,
661 ::basegfx::B2DPolyPolygon& rStrikeoutPolyPoly )
663 ::basegfx::B2DPoint aStartPos(rStartOffset, 0.0);
665 createOverlinePolyPolygon(rOverlinePolyPoly, aStartPos, rLineWidth, rTextLineInfo);
666 createUnderlinePolyPolygon(rUnderlinePolyPoly, aStartPos, rLineWidth, rTextLineInfo);
667 createStrikeoutPolyPolygon(rStrikeoutPolyPoly, aStartPos, rLineWidth, rTextLineInfo);
671 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */