tdf#130857 qt weld: Support mail merge "Server Auth" dialog
[LibreOffice.git] / cppcanvas / source / mtfrenderer / implrenderer.cxx
blob381387080bd30fd78b69727b1d9acb1f25378284
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 <comphelper/diagnose_ex.hxx>
21 #include <tools/debug.hxx>
22 #include <utility>
23 #include <vcl/svapp.hxx>
24 #include <comphelper/propertysequence.hxx>
25 #include <comphelper/propertyvalue.hxx>
26 #include <cppcanvas/canvas.hxx>
27 #include <com/sun/star/rendering/XGraphicDevice.hpp>
28 #include <com/sun/star/rendering/TexturingMode.hpp>
29 #include <com/sun/star/uno/Sequence.hxx>
30 #include <com/sun/star/rendering/PanoseProportion.hpp>
31 #include <com/sun/star/rendering/XCanvasFont.hpp>
32 #include <com/sun/star/rendering/XCanvas.hpp>
33 #include <com/sun/star/rendering/PathCapType.hpp>
34 #include <com/sun/star/rendering/PathJoinType.hpp>
35 #include <basegfx/utils/canvastools.hxx>
36 #include <basegfx/utils/gradienttools.hxx>
37 #include <basegfx/numeric/ftools.hxx>
38 #include <basegfx/polygon/b2dpolypolygontools.hxx>
39 #include <basegfx/polygon/b2dpolygontools.hxx>
40 #include <basegfx/polygon/b2dpolygon.hxx>
41 #include <basegfx/polygon/b2dpolypolygon.hxx>
42 #include <basegfx/matrix/b2dhommatrix.hxx>
43 #include <basegfx/vector/b2dsize.hxx>
44 #include <basegfx/range/b2drectangle.hxx>
45 #include <basegfx/point/b2dpoint.hxx>
46 #include <basegfx/tuple/b2dtuple.hxx>
47 #include <basegfx/polygon/b2dpolygonclipper.hxx>
48 #include <canvas/canvastools.hxx>
49 #include <rtl/ustrbuf.hxx>
50 #include <vcl/canvastools.hxx>
51 #include <vcl/gdimtf.hxx>
52 #include <vcl/metaact.hxx>
53 #include <vcl/virdev.hxx>
54 #include <vcl/metric.hxx>
55 #include <vcl/graphictools.hxx>
56 #include <vcl/BitmapPalette.hxx>
57 #include <tools/poly.hxx>
58 #include <i18nlangtag/languagetag.hxx>
59 #include <implrenderer.hxx>
60 #include <tools.hxx>
61 #include <outdevstate.hxx>
62 #include <action.hxx>
63 #include <sal/log.hxx>
64 #include "bitmapaction.hxx"
65 #include "lineaction.hxx"
66 #include "pointaction.hxx"
67 #include "polypolyaction.hxx"
68 #include "textaction.hxx"
69 #include "transparencygroupaction.hxx"
70 #include <vector>
71 #include <algorithm>
72 #include <memory>
73 #include <string_view>
74 #include "mtftools.hxx"
76 using namespace ::com::sun::star;
79 // free support functions
80 // ======================
81 namespace
83 template < class MetaActionType > void setStateColor( MetaActionType* pAct,
84 bool& rIsColorSet,
85 uno::Sequence< double >& rColorSequence,
86 const cppcanvas::CanvasSharedPtr& rCanvas )
88 rIsColorSet = pAct->IsSetting();
89 if (!rIsColorSet)
90 return;
92 ::Color aColor( pAct->GetColor() );
94 // force alpha part of color to
95 // opaque. transparent painting is done
96 // explicitly via MetaActionType::Transparent
97 aColor.SetAlpha(255);
98 //aColor.SetTransparency(128);
100 rColorSequence = vcl::unotools::colorToDoubleSequence(
101 aColor,
102 rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() );
105 void setupStrokeAttributes( rendering::StrokeAttributes& o_rStrokeAttributes,
106 const ::cppcanvas::internal::ActionFactoryParameters& rParms,
107 const LineInfo& rLineInfo )
109 const ::basegfx::B2DSize aWidth( rLineInfo.GetWidth(), 0 );
110 o_rStrokeAttributes.StrokeWidth =
111 (rParms.mrStates.getState().mapModeTransform * aWidth).getLength();
113 // setup reasonable defaults
114 o_rStrokeAttributes.MiterLimit = 15.0; // 1.0 was no good default; GDI+'s limit is 10.0, our's is 15.0
115 o_rStrokeAttributes.StartCapType = rendering::PathCapType::BUTT;
116 o_rStrokeAttributes.EndCapType = rendering::PathCapType::BUTT;
118 switch (rLineInfo.GetLineJoin())
120 case basegfx::B2DLineJoin::NONE:
121 o_rStrokeAttributes.JoinType = rendering::PathJoinType::NONE;
122 break;
123 case basegfx::B2DLineJoin::Bevel:
124 o_rStrokeAttributes.JoinType = rendering::PathJoinType::BEVEL;
125 break;
126 case basegfx::B2DLineJoin::Miter:
127 o_rStrokeAttributes.JoinType = rendering::PathJoinType::MITER;
128 break;
129 case basegfx::B2DLineJoin::Round:
130 o_rStrokeAttributes.JoinType = rendering::PathJoinType::ROUND;
131 break;
134 switch(rLineInfo.GetLineCap())
136 default: /* css::drawing::LineCap_BUTT */
138 o_rStrokeAttributes.StartCapType = rendering::PathCapType::BUTT;
139 o_rStrokeAttributes.EndCapType = rendering::PathCapType::BUTT;
140 break;
142 case css::drawing::LineCap_ROUND:
144 o_rStrokeAttributes.StartCapType = rendering::PathCapType::ROUND;
145 o_rStrokeAttributes.EndCapType = rendering::PathCapType::ROUND;
146 break;
148 case css::drawing::LineCap_SQUARE:
150 o_rStrokeAttributes.StartCapType = rendering::PathCapType::SQUARE;
151 o_rStrokeAttributes.EndCapType = rendering::PathCapType::SQUARE;
152 break;
156 if( LineStyle::Dash != rLineInfo.GetStyle() )
157 return;
159 const ::cppcanvas::internal::OutDevState& rState( rParms.mrStates.getState() );
161 // TODO(F1): Interpret OutDev::GetRefPoint() for the start of the dashing.
163 // interpret dash info only if explicitly enabled as
164 // style
165 const ::basegfx::B2DSize aDistance( rLineInfo.GetDistance(), 0 );
166 const double nDistance( (rState.mapModeTransform * aDistance).getLength() );
168 const ::basegfx::B2DSize aDashLen( rLineInfo.GetDashLen(), 0 );
169 const double nDashLen( (rState.mapModeTransform * aDashLen).getLength() );
171 const ::basegfx::B2DSize aDotLen( rLineInfo.GetDotLen(), 0 );
172 const double nDotLen( (rState.mapModeTransform * aDotLen).getLength() );
174 const sal_Int32 nNumArryEntries( 2*rLineInfo.GetDashCount() +
175 2*rLineInfo.GetDotCount() );
177 o_rStrokeAttributes.DashArray.realloc( nNumArryEntries );
178 double* pDashArray = o_rStrokeAttributes.DashArray.getArray();
181 // iteratively fill dash array, first with dashes, then
182 // with dots.
185 sal_Int32 nCurrEntry=0;
187 for( sal_Int32 i=0; i<rLineInfo.GetDashCount(); ++i )
189 pDashArray[nCurrEntry++] = nDashLen;
190 pDashArray[nCurrEntry++] = nDistance;
192 for( sal_Int32 i=0; i<rLineInfo.GetDotCount(); ++i )
194 pDashArray[nCurrEntry++] = nDotLen;
195 pDashArray[nCurrEntry++] = nDistance;
200 /** Create masked BitmapEx, where the white areas of rBitmap are
201 transparent, and the other appear in rMaskColor.
203 BitmapEx createMaskBmpEx( const Bitmap& rBitmap,
204 const ::Color& rMaskColor )
206 const ::Color aWhite( COL_WHITE );
207 BitmapPalette aBiLevelPalette{
208 aWhite, rMaskColor
211 AlphaMask aMask( rBitmap.CreateAlphaMask( aWhite ));
212 Bitmap aSolid( rBitmap.GetSizePixel(),
213 vcl::PixelFormat::N8_BPP,
214 &aBiLevelPalette );
215 aSolid.Erase( rMaskColor );
217 return BitmapEx( aSolid, aMask );
220 OUString convertToLocalizedNumerals(std::u16string_view rStr,
221 LanguageType eTextLanguage)
223 OUStringBuffer aBuf(rStr);
224 for (sal_Int32 i = 0; i < aBuf.getLength(); ++i)
226 sal_Unicode nChar = aBuf[i];
227 if (nChar >= '0' && nChar <= '9')
228 aBuf[i] = GetLocalizedChar(nChar, eTextLanguage);
230 return aBuf.makeStringAndClear();
234 namespace cppcanvas::internal
236 // state stack manipulators
238 void VectorOfOutDevStates::clearStateStack()
240 m_aStates.clear();
241 const OutDevState aDefaultState;
242 m_aStates.push_back(aDefaultState);
245 OutDevState& VectorOfOutDevStates::getState()
247 return m_aStates.back();
250 const OutDevState& VectorOfOutDevStates::getState() const
252 return m_aStates.back();
255 void VectorOfOutDevStates::pushState(vcl::PushFlags nFlags)
257 m_aStates.push_back( getState() );
258 getState().pushFlags = nFlags;
261 void VectorOfOutDevStates::popState()
263 if( getState().pushFlags != vcl::PushFlags::ALL )
265 // a state is pushed which is incomplete, i.e. does not
266 // restore everything to the previous stack level when
267 // popped.
268 // That means, we take the old state, and restore every
269 // OutDevState member whose flag is set, from the new to the
270 // old state. Then the new state gets overwritten by the
271 // calculated state
273 // preset to-be-calculated new state with old state
274 OutDevState aCalculatedNewState( getState() );
276 // selectively copy to-be-restored content over saved old
277 // state
278 m_aStates.pop_back();
280 const OutDevState& rNewState( getState() );
282 if( aCalculatedNewState.pushFlags & vcl::PushFlags::LINECOLOR )
284 aCalculatedNewState.lineColor = rNewState.lineColor;
285 aCalculatedNewState.isLineColorSet = rNewState.isLineColorSet;
288 if( aCalculatedNewState.pushFlags & vcl::PushFlags::FILLCOLOR )
290 aCalculatedNewState.fillColor = rNewState.fillColor;
291 aCalculatedNewState.isFillColorSet = rNewState.isFillColorSet;
294 if( aCalculatedNewState.pushFlags & vcl::PushFlags::FONT )
296 aCalculatedNewState.xFont = rNewState.xFont;
297 aCalculatedNewState.fontRotation = rNewState.fontRotation;
298 aCalculatedNewState.textReliefStyle = rNewState.textReliefStyle;
299 aCalculatedNewState.textOverlineStyle = rNewState.textOverlineStyle;
300 aCalculatedNewState.textUnderlineStyle = rNewState.textUnderlineStyle;
301 aCalculatedNewState.textStrikeoutStyle = rNewState.textStrikeoutStyle;
302 aCalculatedNewState.textEmphasisMark = rNewState.textEmphasisMark;
303 aCalculatedNewState.isTextEffectShadowSet = rNewState.isTextEffectShadowSet;
304 aCalculatedNewState.isTextWordUnderlineSet = rNewState.isTextWordUnderlineSet;
305 aCalculatedNewState.isTextOutlineModeSet = rNewState.isTextOutlineModeSet;
308 if( aCalculatedNewState.pushFlags & vcl::PushFlags::TEXTCOLOR )
310 aCalculatedNewState.textColor = rNewState.textColor;
313 if( aCalculatedNewState.pushFlags & vcl::PushFlags::MAPMODE )
315 aCalculatedNewState.mapModeTransform = rNewState.mapModeTransform;
318 if( aCalculatedNewState.pushFlags & vcl::PushFlags::CLIPREGION )
320 aCalculatedNewState.clip = rNewState.clip;
321 aCalculatedNewState.clipRect = rNewState.clipRect;
322 aCalculatedNewState.xClipPoly = rNewState.xClipPoly;
325 // TODO(F2): Raster ops NYI
326 // if( (aCalculatedNewState.pushFlags & vcl::PushFlags::RASTEROP) )
327 // {
328 // }
330 if( aCalculatedNewState.pushFlags & vcl::PushFlags::TEXTFILLCOLOR )
332 aCalculatedNewState.textFillColor = rNewState.textFillColor;
333 aCalculatedNewState.isTextFillColorSet = rNewState.isTextFillColorSet;
336 if( aCalculatedNewState.pushFlags & vcl::PushFlags::TEXTALIGN )
338 aCalculatedNewState.textReferencePoint = rNewState.textReferencePoint;
341 // TODO(F1): Refpoint handling NYI
342 // if( (aCalculatedNewState.pushFlags & vcl::PushFlags::REFPOINT) )
343 // {
344 // }
346 if( aCalculatedNewState.pushFlags & vcl::PushFlags::TEXTLINECOLOR )
348 aCalculatedNewState.textLineColor = rNewState.textLineColor;
349 aCalculatedNewState.isTextLineColorSet = rNewState.isTextLineColorSet;
352 if( aCalculatedNewState.pushFlags & vcl::PushFlags::OVERLINECOLOR )
354 aCalculatedNewState.textOverlineColor = rNewState.textOverlineColor;
355 aCalculatedNewState.isTextOverlineColorSet = rNewState.isTextOverlineColorSet;
358 if( aCalculatedNewState.pushFlags & vcl::PushFlags::TEXTLAYOUTMODE )
360 aCalculatedNewState.textAlignment = rNewState.textAlignment;
361 aCalculatedNewState.textDirection = rNewState.textDirection;
364 // TODO(F2): Text language handling NYI
365 // if( (aCalculatedNewState.pushFlags & vcl::PushFlags::TEXTLANGUAGE) )
366 // {
367 // }
369 // always copy push mode
370 aCalculatedNewState.pushFlags = rNewState.pushFlags;
372 // flush to stack
373 getState() = std::move(aCalculatedNewState);
375 else
377 m_aStates.pop_back();
381 bool ImplRenderer::createFillAndStroke( const ::basegfx::B2DPolyPolygon& rPolyPoly,
382 const ActionFactoryParameters& rParms )
384 const OutDevState& rState( rParms.mrStates.getState() );
385 if( (!rState.isLineColorSet &&
386 !rState.isFillColorSet) ||
387 (!rState.lineColor.hasElements() &&
388 !rState.fillColor.hasElements()) )
390 return false;
393 std::shared_ptr<Action> pPolyAction(
394 internal::PolyPolyActionFactory::createPolyPolyAction(
395 rPolyPoly, rParms.mrCanvas, rState ) );
397 if( pPolyAction )
399 maActions.emplace_back(
400 pPolyAction,
401 rParms.mrCurrActionIndex );
403 rParms.mrCurrActionIndex += pPolyAction->getActionCount()-1;
406 return true;
409 bool ImplRenderer::createFillAndStroke( const ::basegfx::B2DPolygon& rPoly,
410 const ActionFactoryParameters& rParms )
412 return createFillAndStroke( ::basegfx::B2DPolyPolygon( rPoly ),
413 rParms );
416 void ImplRenderer::skipContent( GDIMetaFile& rMtf,
417 const char* pCommentString,
418 sal_Int32& io_rCurrActionIndex )
420 ENSURE_OR_THROW( pCommentString,
421 "ImplRenderer::skipContent(): NULL string given" );
423 MetaAction* pCurrAct;
424 while( (pCurrAct=rMtf.NextAction()) != nullptr )
426 // increment action index, we've skipped an action.
427 ++io_rCurrActionIndex;
429 if( pCurrAct->GetType() == MetaActionType::COMMENT &&
430 static_cast<MetaCommentAction*>(pCurrAct)->GetComment().equalsIgnoreAsciiCase(
431 pCommentString) )
433 // requested comment found, done
434 return;
438 // EOF
441 bool ImplRenderer::isActionContained( GDIMetaFile& rMtf,
442 const char* pCommentString,
443 MetaActionType nType )
445 ENSURE_OR_THROW( pCommentString,
446 "ImplRenderer::isActionContained(): NULL string given" );
448 bool bRet( false );
450 // at least _one_ call to GDIMetaFile::NextAction() is
451 // executed
452 size_t nPos( 1 );
454 MetaAction* pCurrAct;
455 while( (pCurrAct=rMtf.NextAction()) != nullptr )
457 if( pCurrAct->GetType() == nType )
459 bRet = true; // action type found
460 break;
463 if( pCurrAct->GetType() == MetaActionType::COMMENT &&
464 static_cast<MetaCommentAction*>(pCurrAct)->GetComment().equalsIgnoreAsciiCase(
465 pCommentString) )
467 // delimiting end comment found, done
468 bRet = false; // not yet found
469 break;
472 ++nPos;
475 // rewind metafile to previous position (this method must
476 // not change the current metaaction)
477 while (nPos)
479 --nPos;
480 rMtf.WindPrev();
483 if( !pCurrAct )
485 // EOF, and not yet found
486 bRet = false;
489 return bRet;
492 void ImplRenderer::createGradientAction( const ::tools::PolyPolygon& rPoly,
493 const ::Gradient& rGradient,
494 const ActionFactoryParameters& rParms,
495 bool bIsPolygonRectangle,
496 bool bSubsettableActions )
498 DBG_TESTSOLARMUTEX();
500 ::basegfx::B2DPolyPolygon aDevicePoly( rPoly.getB2DPolyPolygon() );
501 aDevicePoly.transform( rParms.mrStates.getState().mapModeTransform );
503 // decide, whether this gradient can be rendered natively
504 // by the canvas, or must be emulated via VCL gradient
505 // action extraction.
506 const sal_uInt16 nSteps( rGradient.GetSteps() );
508 if( // step count is infinite, can use native canvas
509 // gradients here
510 nSteps == 0 ||
511 // step count is sufficiently high, such that no
512 // discernible difference should be visible.
513 nSteps > 64 )
515 uno::Reference< lang::XMultiServiceFactory> xFactory(
516 rParms.mrCanvas->getUNOCanvas()->getDevice()->getParametricPolyPolygonFactory() );
518 if( xFactory.is() )
520 rendering::Texture aTexture;
522 aTexture.RepeatModeX = rendering::TexturingMode::CLAMP;
523 aTexture.RepeatModeY = rendering::TexturingMode::CLAMP;
524 aTexture.Alpha = 1.0;
527 // setup start/end color values
530 // scale color coefficients with gradient intensities
531 const sal_uInt16 nStartIntensity( rGradient.GetStartIntensity() );
532 ::Color aVCLStartColor( rGradient.GetStartColor() );
533 aVCLStartColor.SetRed( static_cast<sal_uInt8>(aVCLStartColor.GetRed() * nStartIntensity / 100) );
534 aVCLStartColor.SetGreen( static_cast<sal_uInt8>(aVCLStartColor.GetGreen() * nStartIntensity / 100) );
535 aVCLStartColor.SetBlue( static_cast<sal_uInt8>(aVCLStartColor.GetBlue() * nStartIntensity / 100) );
537 const sal_uInt16 nEndIntensity( rGradient.GetEndIntensity() );
538 ::Color aVCLEndColor( rGradient.GetEndColor() );
539 aVCLEndColor.SetRed( static_cast<sal_uInt8>(aVCLEndColor.GetRed() * nEndIntensity / 100) );
540 aVCLEndColor.SetGreen( static_cast<sal_uInt8>(aVCLEndColor.GetGreen() * nEndIntensity / 100) );
541 aVCLEndColor.SetBlue( static_cast<sal_uInt8>(aVCLEndColor.GetBlue() * nEndIntensity / 100) );
543 uno::Reference<rendering::XColorSpace> xColorSpace(
544 rParms.mrCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace());
545 const uno::Sequence< double > aStartColor(
546 vcl::unotools::colorToDoubleSequence( aVCLStartColor,
547 xColorSpace ));
548 const uno::Sequence< double > aEndColor(
549 vcl::unotools::colorToDoubleSequence( aVCLEndColor,
550 xColorSpace ));
552 uno::Sequence< uno::Sequence < double > > aColors;
553 uno::Sequence< double > aStops;
555 if( rGradient.GetStyle() == css::awt::GradientStyle_AXIAL )
557 aStops = { 0.0, 0.5, 1.0 };
558 aColors = { aEndColor, aStartColor, aEndColor };
560 else
562 aStops = { 0.0, 1.0 };
563 aColors = { aStartColor, aEndColor };
566 const ::basegfx::B2DRectangle aBounds(
567 ::basegfx::utils::getRange(aDevicePoly) );
568 const ::basegfx::B2DVector aOffset(
569 rGradient.GetOfsX() / 100.0,
570 rGradient.GetOfsY() / 100.0);
571 double fRotation = toRadians( rGradient.GetAngle() );
572 const double fBorder( rGradient.GetBorder() / 100.0 );
574 basegfx::B2DHomMatrix aRot90;
575 aRot90.rotate(M_PI_2);
577 basegfx::ODFGradientInfo aGradInfo;
578 OUString aGradientService;
579 switch( rGradient.GetStyle() )
581 case css::awt::GradientStyle_LINEAR:
582 aGradInfo = basegfx::utils::createLinearODFGradientInfo(
583 aBounds,
584 nSteps,
585 fBorder,
586 fRotation);
587 // map ODF to svg gradient orientation - x
588 // instead of y direction
589 aGradInfo.setTextureTransform(aGradInfo.getTextureTransform() * aRot90);
590 aGradientService = "LinearGradient";
591 break;
593 case css::awt::GradientStyle_AXIAL:
595 // Adapt the border so that it is suitable
596 // for the axial gradient. An axial
597 // gradient consists of two linear
598 // gradients. Each of those covers half
599 // of the total size. In order to
600 // compensate for the condensed display of
601 // the linear gradients, we have to
602 // enlarge the area taken up by the actual
603 // gradient (1-fBorder). After that we
604 // have to turn the result back into a
605 // border value, hence the second (left
606 // most 1-...
607 const double fAxialBorder (1-2*(1-fBorder));
608 aGradInfo = basegfx::utils::createAxialODFGradientInfo(
609 aBounds,
610 nSteps,
611 fAxialBorder,
612 fRotation);
613 // map ODF to svg gradient orientation - x
614 // instead of y direction
615 aGradInfo.setTextureTransform(aGradInfo.getTextureTransform() * aRot90);
617 // map ODF axial gradient to 3-stop linear
618 // gradient - shift left by 0.5
619 basegfx::B2DHomMatrix aShift;
621 aShift.translate(-0.5,0);
622 aGradInfo.setTextureTransform(aGradInfo.getTextureTransform() * aShift);
623 aGradientService = "LinearGradient";
624 break;
627 case css::awt::GradientStyle_RADIAL:
628 aGradInfo = basegfx::utils::createRadialODFGradientInfo(
629 aBounds,
630 aOffset,
631 nSteps,
632 fBorder);
633 aGradientService = "EllipticalGradient";
634 break;
636 case css::awt::GradientStyle_ELLIPTICAL:
637 aGradInfo = basegfx::utils::createEllipticalODFGradientInfo(
638 aBounds,
639 aOffset,
640 nSteps,
641 fBorder,
642 fRotation);
643 aGradientService = "EllipticalGradient";
644 break;
646 case css::awt::GradientStyle_SQUARE:
647 aGradInfo = basegfx::utils::createSquareODFGradientInfo(
648 aBounds,
649 aOffset,
650 nSteps,
651 fBorder,
652 fRotation);
653 aGradientService = "RectangularGradient";
654 break;
656 case css::awt::GradientStyle_RECT:
657 aGradInfo = basegfx::utils::createRectangularODFGradientInfo(
658 aBounds,
659 aOffset,
660 nSteps,
661 fBorder,
662 fRotation);
663 aGradientService = "RectangularGradient";
664 break;
666 default:
667 ENSURE_OR_THROW( false,
668 "ImplRenderer::createGradientAction(): Unexpected gradient type" );
669 break;
672 ::basegfx::unotools::affineMatrixFromHomMatrix( aTexture.AffineTransform,
673 aGradInfo.getTextureTransform() );
675 uno::Sequence<uno::Any> args(comphelper::InitAnyPropertySequence(
677 {"Colors", uno::Any(aColors)},
678 {"Stops", uno::Any(aStops)},
679 {"AspectRatio", uno::Any(aGradInfo.getAspectRatio())},
680 }));
681 aTexture.Gradient.set(
682 xFactory->createInstanceWithArguments(aGradientService,
683 args),
684 uno::UNO_QUERY);
685 if( aTexture.Gradient.is() )
687 std::shared_ptr<Action> pPolyAction(
688 internal::PolyPolyActionFactory::createPolyPolyAction(
689 aDevicePoly,
690 rParms.mrCanvas,
691 rParms.mrStates.getState(),
692 aTexture ) );
694 if( pPolyAction )
696 maActions.emplace_back(
697 pPolyAction,
698 rParms.mrCurrActionIndex );
700 rParms.mrCurrActionIndex += pPolyAction->getActionCount()-1;
703 // done, using native gradients
704 return;
709 // cannot currently use native canvas gradients, as a
710 // finite step size is given (this funny feature is not
711 // supported by the XCanvas API)
712 rParms.mrStates.pushState(vcl::PushFlags::ALL);
714 if( !bIsPolygonRectangle )
716 // only clip, if given polygon is not a rectangle in
717 // the first place (the gradient is always limited to
718 // the given bound rect)
719 updateClipping(
720 aDevicePoly,
721 rParms,
722 true );
725 GDIMetaFile aTmpMtf;
726 Gradient aGradient(rGradient);
727 aGradient.AddGradientActions( rPoly.GetBoundRect(), aTmpMtf );
729 createActions( aTmpMtf, rParms, bSubsettableActions );
731 rParms.mrStates.popState();
734 uno::Reference< rendering::XCanvasFont > ImplRenderer::createFont( double& o_rFontRotation,
735 const vcl::Font& rFont,
736 const ActionFactoryParameters& rParms )
738 rendering::FontRequest aFontRequest;
740 if( rParms.mrParms.maFontName )
741 aFontRequest.FontDescription.FamilyName = *rParms.mrParms.maFontName;
742 else
743 aFontRequest.FontDescription.FamilyName = rFont.GetFamilyName();
745 aFontRequest.FontDescription.StyleName = rFont.GetStyleName();
747 aFontRequest.FontDescription.IsSymbolFont = (rFont.GetCharSet() == RTL_TEXTENCODING_SYMBOL) ? util::TriState_YES : util::TriState_NO;
748 aFontRequest.FontDescription.IsVertical = rFont.IsVertical() ? util::TriState_YES : util::TriState_NO;
750 // TODO(F2): improve vclenum->panose conversion
751 aFontRequest.FontDescription.FontDescription.Weight =
752 rParms.mrParms.maFontWeight ?
753 *rParms.mrParms.maFontWeight :
754 ::canvas::tools::numeric_cast<sal_Int8>( ::basegfx::fround( rFont.GetWeight() ) );
755 aFontRequest.FontDescription.FontDescription.Letterform =
756 rParms.mrParms.maFontLetterForm ?
757 *rParms.mrParms.maFontLetterForm :
758 (rFont.GetItalic() == ITALIC_NONE) ? 0 : 9;
759 aFontRequest.FontDescription.FontDescription.Proportion =
760 (rFont.GetPitch() == PITCH_FIXED)
761 ? rendering::PanoseProportion::MONO_SPACED
762 : rendering::PanoseProportion::ANYTHING;
764 LanguageType aLang = rFont.GetLanguage();
765 aFontRequest.Locale = LanguageTag::convertToLocale( aLang, false);
767 // setup state-local text transformation,
768 // if the font be rotated
769 const auto nFontAngle( rFont.GetOrientation() );
770 if( nFontAngle )
772 // set to unity transform rotated by font angle
773 const double nAngle( toRadians(nFontAngle) );
774 o_rFontRotation = -nAngle;
776 else
778 o_rFontRotation = 0.0;
781 geometry::Matrix2D aFontMatrix;
782 ::canvas::tools::setIdentityMatrix2D( aFontMatrix );
784 // TODO(F2): use correct scale direction, font
785 // height might be width or anything else
787 // TODO(Q3): This code smells of programming by
788 // coincidence (the next two if statements)
790 ::Size rFontSizeLog( rFont.GetFontSize() );
792 if (rFontSizeLog.Height() == 0)
794 // guess 16 pixel (as in VCL)
795 rFontSizeLog = ::Size(0, 16);
797 // convert to target MapUnit if not pixels
798 rFontSizeLog = OutputDevice::LogicToLogic(rFontSizeLog, MapMode(MapUnit::MapPixel), rParms.mrVDev.GetMapMode());
801 const sal_Int32 nFontWidthLog = rFontSizeLog.Width();
802 if( nFontWidthLog != 0 )
804 vcl::Font aTestFont = rFont;
805 aTestFont.SetAverageFontWidth( 0 );
806 sal_Int32 nNormalWidth = rParms.mrVDev.GetFontMetric( aTestFont ).GetAverageFontWidth();
807 if( nNormalWidth != nFontWidthLog )
808 if( nNormalWidth )
809 aFontMatrix.m00 = static_cast<double>(nFontWidthLog) / nNormalWidth;
812 // #i52608# apply map mode scale also to font matrix - an
813 // anisotrophic mapmode must be reflected in an
814 // anisotrophic font matrix scale.
815 const OutDevState& rState( rParms.mrStates.getState() );
816 if( !::basegfx::fTools::equal(
817 rState.mapModeTransform.get(0,0),
818 rState.mapModeTransform.get(1,1)) )
820 const double nScaleX( rState.mapModeTransform.get(0,0) );
821 const double nScaleY( rState.mapModeTransform.get(1,1) );
823 // note: no reason to check for division by zero, we
824 // always have the value closer (or equal) to zero as
825 // the nominator.
826 if( fabs(nScaleX) < fabs(nScaleY) )
827 aFontMatrix.m00 *= nScaleX / nScaleY;
828 else
829 aFontMatrix.m11 *= nScaleY / nScaleX;
831 aFontRequest.CellSize = (rState.mapModeTransform * vcl::unotools::b2DSizeFromSize(rFontSizeLog)).getHeight();
833 if (rFont.GetEmphasisMark() != FontEmphasisMark::NONE)
835 uno::Sequence< beans::PropertyValue > aProperties{ comphelper::makePropertyValue(
836 u"EmphasisMark"_ustr, sal_uInt32(rFont.GetEmphasisMark())) };
837 return rParms.mrCanvas->getUNOCanvas()->createFont(aFontRequest,
838 aProperties,
839 aFontMatrix);
842 return rParms.mrCanvas->getUNOCanvas()->createFont( aFontRequest,
843 uno::Sequence< beans::PropertyValue >(),
844 aFontMatrix );
847 // create text effects such as shadow/relief/embossed
848 void ImplRenderer::createTextAction( const ::Point& rStartPoint,
849 const OUString& rString,
850 int nIndex,
851 int nLength,
852 KernArraySpan pCharWidths,
853 std::span<const sal_Bool> pKashidaArray,
854 const ActionFactoryParameters& rParms,
855 bool bSubsettableActions )
857 ENSURE_OR_THROW( nIndex >= 0 && nLength <= rString.getLength() + nIndex,
858 "ImplRenderer::createTextWithEffectsAction(): Invalid text index" );
860 if( !nLength )
861 return; // zero-length text, no visible output
863 const OutDevState& rState( rParms.mrStates.getState() );
865 // TODO(F2): implement all text effects
866 // if( rState.textAlignment ); // TODO(F2): NYI
868 ::Color aTextFillColor( COL_AUTO );
869 ::Color aShadowColor( COL_AUTO );
870 ::Color aReliefColor( COL_AUTO );
871 ::Size aShadowOffset;
872 ::Size aReliefOffset;
874 uno::Reference<rendering::XColorSpace> xColorSpace(
875 rParms.mrCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() );
877 if( rState.isTextEffectShadowSet )
879 // calculate shadow offset (similar to outdev3.cxx)
880 // TODO(F3): better match with outdev3.cxx
881 sal_Int32 nShadowOffset = static_cast<sal_Int32>(1.5 + ((rParms.mrVDev.GetFont().GetFontHeight()-24.0)/24.0));
882 if( nShadowOffset < 1 )
883 nShadowOffset = 1;
885 aShadowOffset.setWidth( nShadowOffset );
886 aShadowOffset.setHeight( nShadowOffset );
888 // determine shadow color (from outdev3.cxx)
889 ::Color aTextColor = vcl::unotools::doubleSequenceToColor(
890 rState.textColor, xColorSpace );
891 bool bIsDark = (aTextColor == COL_BLACK)
892 || (aTextColor.GetLuminance() < 8);
894 aShadowColor = bIsDark ? COL_LIGHTGRAY : COL_BLACK;
895 aShadowColor.SetAlpha( aTextColor.GetAlpha() );
898 if( rState.textReliefStyle != FontRelief::NONE )
900 // calculate relief offset (similar to outdev3.cxx)
901 sal_Int32 nReliefOffset = rParms.mrVDev.PixelToLogic( Size( 1, 1 ) ).Height();
902 nReliefOffset += nReliefOffset/2;
903 if( nReliefOffset < 1 )
904 nReliefOffset = 1;
906 if( rState.textReliefStyle == FontRelief::Engraved )
907 nReliefOffset = -nReliefOffset;
909 aReliefOffset.setWidth( nReliefOffset );
910 aReliefOffset.setHeight( nReliefOffset );
912 // determine relief color (from outdev3.cxx)
913 ::Color aTextColor = vcl::unotools::doubleSequenceToColor(
914 rState.textColor, xColorSpace );
916 aReliefColor = COL_LIGHTGRAY;
918 // we don't have an automatic color, so black is always
919 // drawn on white (literally copied from
920 // vcl/source/gdi/outdev3.cxx)
921 if( aTextColor == COL_BLACK )
923 aTextColor = COL_WHITE;
924 rParms.mrStates.getState().textColor =
925 vcl::unotools::colorToDoubleSequence(
926 aTextColor, xColorSpace );
929 if( aTextColor == COL_WHITE )
930 aReliefColor = COL_BLACK;
931 aReliefColor.SetAlpha( aTextColor.GetAlpha() );
934 if (rState.isTextFillColorSet)
935 aTextFillColor = vcl::unotools::doubleSequenceToColor(rState.textFillColor, xColorSpace);
937 // create the actual text action
938 std::shared_ptr<Action> pTextAction(
939 TextActionFactory::createTextAction(
940 rStartPoint,
941 aReliefOffset,
942 aReliefColor,
943 aShadowOffset,
944 aShadowColor,
945 aTextFillColor,
946 rString,
947 nIndex,
948 nLength,
949 pCharWidths,
950 pKashidaArray,
951 rParms.mrVDev,
952 rParms.mrCanvas,
953 rState,
954 rParms.mrParms,
955 bSubsettableActions ) );
957 std::shared_ptr<Action> pStrikeoutTextAction;
959 if ( rState.textStrikeoutStyle == STRIKEOUT_X || rState.textStrikeoutStyle == STRIKEOUT_SLASH )
961 ::tools::Long nWidth = rParms.mrVDev.GetTextWidth( rString,nIndex,nLength );
963 sal_Unicode pChars[4];
964 if ( rState.textStrikeoutStyle == STRIKEOUT_X )
965 pChars[0] = 'X';
966 else
967 pChars[0] = '/';
968 pChars[3]=pChars[2]=pChars[1]=pChars[0];
970 ::tools::Long nStrikeoutWidth = (rParms.mrVDev.GetTextWidth(
971 OUString(pChars, std::size(pChars))) + 2) / 4;
973 if( nStrikeoutWidth <= 0 )
974 nStrikeoutWidth = 1;
976 ::tools::Long nMaxWidth = nStrikeoutWidth/2;
977 if ( nMaxWidth < 2 )
978 nMaxWidth = 2;
979 nMaxWidth += nWidth + 1;
981 ::tools::Long nFullStrikeoutWidth = 0;
982 OUStringBuffer aStrikeoutText;
983 while( (nFullStrikeoutWidth+=nStrikeoutWidth ) < nMaxWidth+1 )
984 aStrikeoutText.append(pChars[0]);
986 sal_Int32 nLen = aStrikeoutText.getLength();
988 if( nLen )
990 ::tools::Long nInterval = ( nWidth - nStrikeoutWidth * nLen ) / nLen;
991 nStrikeoutWidth += nInterval;
992 KernArray aStrikeoutCharWidths;
994 for ( int i = 0;i< nLen; i++ )
995 aStrikeoutCharWidths.push_back(nStrikeoutWidth * (i + 1));
997 pStrikeoutTextAction =
998 TextActionFactory::createTextAction(
999 rStartPoint,
1000 aReliefOffset,
1001 aReliefColor,
1002 aShadowOffset,
1003 aShadowColor,
1004 aTextFillColor,
1005 aStrikeoutText.makeStringAndClear(),
1006 0/*nStartPos*/,
1007 nLen,
1008 aStrikeoutCharWidths,
1009 pKashidaArray,
1010 rParms.mrVDev,
1011 rParms.mrCanvas,
1012 rState,
1013 rParms.mrParms,
1014 bSubsettableActions ) ;
1018 if( !pTextAction )
1019 return;
1021 maActions.emplace_back(
1022 pTextAction,
1023 rParms.mrCurrActionIndex );
1025 if ( pStrikeoutTextAction )
1027 maActions.emplace_back(
1028 pStrikeoutTextAction,
1029 rParms.mrCurrActionIndex );
1032 rParms.mrCurrActionIndex += pTextAction->getActionCount()-1;
1035 void ImplRenderer::updateClipping( const ::basegfx::B2DPolyPolygon& rClipPoly,
1036 const ActionFactoryParameters& rParms,
1037 bool bIntersect )
1039 ::cppcanvas::internal::OutDevState& rState( rParms.mrStates.getState() );
1041 const bool bEmptyClipRect( rState.clipRect.IsEmpty() );
1042 const bool bEmptyClipPoly( rState.clip.count() == 0 );
1044 ENSURE_OR_THROW( bEmptyClipPoly || bEmptyClipRect,
1045 "ImplRenderer::updateClipping(): Clip rect and polygon are both set!" );
1047 if( !bIntersect ||
1048 (bEmptyClipRect && bEmptyClipPoly) )
1050 rState.clip = rClipPoly;
1052 else
1054 if( !bEmptyClipRect )
1056 // TODO(P3): Use Liang-Barsky polygon clip here,
1057 // after all, one object is just a rectangle!
1059 // convert rect to polygon beforehand, must revert
1060 // to general polygon clipping here.
1061 ::tools::Rectangle aRect = rState.clipRect;
1062 // #121100# VCL rectangular clips always
1063 // include one more pixel to the right
1064 // and the bottom
1065 aRect.AdjustRight(1);
1066 aRect.AdjustBottom(1);
1067 rState.clip = ::basegfx::B2DPolyPolygon(
1068 ::basegfx::utils::createPolygonFromRect(
1069 vcl::unotools::b2DRectangleFromRectangle(aRect) ) );
1072 // AW: Simplified
1073 rState.clip = basegfx::utils::clipPolyPolygonOnPolyPolygon(
1074 rClipPoly, rState.clip, true, false);
1077 // by now, our clip resides in the OutDevState::clip
1078 // poly-polygon.
1079 rState.clipRect.SetEmpty();
1081 if( rState.clip.count() == 0 )
1083 if( rState.clipRect.IsEmpty() )
1085 rState.xClipPoly.clear();
1087 else
1089 ::tools::Rectangle aRect = rState.clipRect;
1090 // #121100# VCL rectangular clips
1091 // always include one more pixel to
1092 // the right and the bottom
1093 aRect.AdjustRight(1);
1094 aRect.AdjustBottom(1);
1095 rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
1096 rParms.mrCanvas->getUNOCanvas()->getDevice(),
1097 ::basegfx::B2DPolyPolygon(
1098 ::basegfx::utils::createPolygonFromRect(
1099 vcl::unotools::b2DRectangleFromRectangle(aRect) ) ) );
1102 else
1104 rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
1105 rParms.mrCanvas->getUNOCanvas()->getDevice(),
1106 rState.clip );
1110 void ImplRenderer::updateClipping( const ::tools::Rectangle& rClipRect,
1111 const ActionFactoryParameters& rParms,
1112 bool bIntersect )
1114 ::cppcanvas::internal::OutDevState& rState( rParms.mrStates.getState() );
1116 const bool bEmptyClipRect( rState.clipRect.IsEmpty() );
1117 const bool bEmptyClipPoly( rState.clip.count() == 0 );
1119 ENSURE_OR_THROW( bEmptyClipPoly || bEmptyClipRect,
1120 "ImplRenderer::updateClipping(): Clip rect and polygon are both set!" );
1122 if( !bIntersect ||
1123 (bEmptyClipRect && bEmptyClipPoly) )
1125 rState.clipRect = rClipRect;
1126 rState.clip.clear();
1128 else if( bEmptyClipPoly )
1130 rState.clipRect.Intersection( rClipRect );
1131 rState.clip.clear();
1133 else
1135 // TODO(P3): Handle a fourth case here, when all clip
1136 // polygons are rectangular, once B2DMultiRange's
1137 // sweep line implementation is done.
1139 // general case: convert to polygon and clip
1142 // convert rect to polygon beforehand, must revert
1143 // to general polygon clipping here.
1144 ::basegfx::B2DPolyPolygon aClipPoly(
1145 ::basegfx::utils::createPolygonFromRect(
1146 vcl::unotools::b2DRectangleFromRectangle(rClipRect) ) );
1148 rState.clipRect.SetEmpty();
1150 // AW: Simplified
1151 rState.clip = basegfx::utils::clipPolyPolygonOnPolyPolygon(
1152 aClipPoly, rState.clip, true, false);
1155 if( rState.clip.count() == 0 )
1157 if( rState.clipRect.IsEmpty() )
1159 rState.xClipPoly.clear();
1161 else
1163 // #121100# VCL rectangular clips
1164 // always include one more pixel to
1165 // the right and the bottom
1166 ::tools::Rectangle aRect = rState.clipRect;
1167 aRect.AdjustRight(1);
1168 aRect.AdjustBottom(1);
1169 rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
1170 rParms.mrCanvas->getUNOCanvas()->getDevice(),
1171 ::basegfx::B2DPolyPolygon(
1172 ::basegfx::utils::createPolygonFromRect(
1173 vcl::unotools::b2DRectangleFromRectangle(aRect) ) ) );
1176 else
1178 rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
1179 rParms.mrCanvas->getUNOCanvas()->getDevice(),
1180 rState.clip );
1184 void ImplRenderer::createActions( GDIMetaFile& rMtf,
1185 const ActionFactoryParameters& rFactoryParms,
1186 bool bSubsettableActions )
1188 /* TODO(P2): interpret mtf-comments
1189 ================================
1191 - gradient fillings (do that via comments)
1193 - think about mapping. _If_ we do everything in logical
1194 coordinates (which would solve the probs for stroke
1195 widths and text offsets), then we would have to
1196 recalc scaling for every drawing operation. This is
1197 because the outdev map mode might change at any time.
1198 Also keep in mind, that, although we've double precision
1199 float arithmetic now, different offsets might still
1200 generate different roundings (aka
1201 'OutputDevice::SetPixelOffset())
1205 // alias common parameters
1206 VectorOfOutDevStates& rStates(rFactoryParms.mrStates);
1207 const CanvasSharedPtr& rCanvas(rFactoryParms.mrCanvas);
1208 ::VirtualDevice& rVDev(rFactoryParms.mrVDev);
1209 const Parameters& rParms(rFactoryParms.mrParms);
1210 sal_Int32& io_rCurrActionIndex(rFactoryParms.mrCurrActionIndex);
1213 // Loop over every metaaction
1214 // ==========================
1215 MetaAction* pCurrAct;
1217 // TODO(P1): think about caching
1218 for( pCurrAct=rMtf.FirstAction();
1219 pCurrAct;
1220 pCurrAct = rMtf.NextAction() )
1222 // execute every action, to keep VDev state up-to-date
1223 // currently used only for
1224 // - the map mode
1225 // - the line/fill color when processing a MetaActionType::Transparent
1226 // - SetFont to process font metric specific actions
1227 pCurrAct->Execute( &rVDev );
1229 SAL_INFO("cppcanvas.emf", "MTF\trecord type: 0x" << static_cast<sal_uInt16>(pCurrAct->GetType()) << " (" << static_cast<sal_uInt16>(pCurrAct->GetType()) << ")");
1231 switch( pCurrAct->GetType() )
1235 // In the first part of this monster-switch, we
1236 // handle all state-changing meta actions. These
1237 // are all handled locally.
1240 case MetaActionType::PUSH:
1242 MetaPushAction* pPushAction = static_cast<MetaPushAction*>(pCurrAct);
1243 rStates.pushState(pPushAction->GetFlags());
1245 break;
1247 case MetaActionType::POP:
1248 rStates.popState();
1249 break;
1251 case MetaActionType::TEXTLANGUAGE:
1252 case MetaActionType::REFPOINT:
1253 // handled via pCurrAct->Execute( &rVDev )
1254 break;
1256 case MetaActionType::MAPMODE:
1257 // modify current mapModeTransformation
1258 // transformation, such that subsequent
1259 // coordinates map correctly
1260 tools::calcLogic2PixelAffineTransform( rStates.getState().mapModeTransform,
1261 rVDev );
1262 break;
1264 // monitor clip regions, to assemble clip polygon on our own
1265 case MetaActionType::CLIPREGION:
1267 MetaClipRegionAction* pClipAction = static_cast<MetaClipRegionAction*>(pCurrAct);
1269 if( !pClipAction->IsClipping() )
1271 // clear clipping
1272 rStates.getState().clip.clear();
1274 else
1276 if( !pClipAction->GetRegion().HasPolyPolygonOrB2DPolyPolygon() )
1278 SAL_INFO( "cppcanvas.emf", "ImplRenderer::createActions(): non-polygonal clip "
1279 "region encountered, falling back to bounding box!" );
1281 // #121806# explicitly kept integer
1282 ::tools::Rectangle aClipRect(
1283 rVDev.LogicToPixel(
1284 pClipAction->GetRegion().GetBoundRect() ) );
1286 // intersect current clip with given rect
1287 updateClipping(
1288 aClipRect,
1289 rFactoryParms,
1290 false );
1292 else
1294 // set new clip polygon (don't intersect
1295 // with old one, just set it)
1297 // #121806# explicitly kept integer
1298 basegfx::B2DPolyPolygon aPolyPolygon(pClipAction->GetRegion().GetAsB2DPolyPolygon());
1300 aPolyPolygon.transform(rVDev.GetViewTransformation());
1301 updateClipping(
1302 aPolyPolygon,
1303 rFactoryParms,
1304 false );
1308 break;
1311 case MetaActionType::ISECTRECTCLIPREGION:
1313 MetaISectRectClipRegionAction* pClipAction = static_cast<MetaISectRectClipRegionAction*>(pCurrAct);
1315 // #121806# explicitly kept integer
1316 ::tools::Rectangle aClipRect(
1317 rVDev.LogicToPixel( pClipAction->GetRect() ) );
1319 // intersect current clip with given rect
1320 updateClipping(
1321 aClipRect,
1322 rFactoryParms,
1323 true );
1325 break;
1328 case MetaActionType::ISECTREGIONCLIPREGION:
1330 MetaISectRegionClipRegionAction* pClipAction = static_cast<MetaISectRegionClipRegionAction*>(pCurrAct);
1332 if( !pClipAction->GetRegion().HasPolyPolygonOrB2DPolyPolygon() )
1334 SAL_INFO( "cppcanvas.emf", "ImplRenderer::createActions(): non-polygonal clip "
1335 "region encountered, falling back to bounding box!" );
1337 // #121806# explicitly kept integer
1338 ::tools::Rectangle aClipRect(
1339 rVDev.LogicToPixel( pClipAction->GetRegion().GetBoundRect() ) );
1341 // intersect current clip with given rect
1342 updateClipping(
1343 aClipRect,
1344 rFactoryParms,
1345 true );
1347 else
1349 // intersect current clip with given clip polygon
1351 // #121806# explicitly kept integer
1352 basegfx::B2DPolyPolygon aPolyPolygon(pClipAction->GetRegion().GetAsB2DPolyPolygon());
1354 aPolyPolygon.transform(rVDev.GetViewTransformation());
1355 updateClipping(
1356 aPolyPolygon,
1357 rFactoryParms,
1358 true );
1361 break;
1364 case MetaActionType::MOVECLIPREGION:
1365 // TODO(F2): NYI
1366 break;
1368 case MetaActionType::LINECOLOR:
1369 if( !rParms.maLineColor )
1371 setStateColor( static_cast<MetaLineColorAction*>(pCurrAct),
1372 rStates.getState().isLineColorSet,
1373 rStates.getState().lineColor,
1374 rCanvas );
1376 else
1378 // #120994# Do switch on/off LineColor, even when an overriding one is set
1379 bool bSetting(static_cast<MetaLineColorAction*>(pCurrAct)->IsSetting());
1381 rStates.getState().isLineColorSet = bSetting;
1383 break;
1385 case MetaActionType::FILLCOLOR:
1386 if( !rParms.maFillColor )
1388 setStateColor( static_cast<MetaFillColorAction*>(pCurrAct),
1389 rStates.getState().isFillColorSet,
1390 rStates.getState().fillColor,
1391 rCanvas );
1393 else
1395 // #120994# Do switch on/off FillColor, even when an overriding one is set
1396 bool bSetting(static_cast<MetaFillColorAction*>(pCurrAct)->IsSetting());
1398 rStates.getState().isFillColorSet = bSetting;
1400 break;
1402 case MetaActionType::TEXTCOLOR:
1404 if( !rParms.maTextColor )
1406 // Text color is set unconditionally, thus, no
1407 // use of setStateColor here
1408 ::Color aColor( static_cast<MetaTextColorAction*>(pCurrAct)->GetColor() );
1410 // force alpha part of color to
1411 // opaque. transparent painting is done
1412 // explicitly via MetaActionType::Transparent
1413 aColor.SetAlpha(255);
1415 rStates.getState().textColor =
1416 vcl::unotools::colorToDoubleSequence(
1417 aColor,
1418 rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() );
1421 break;
1423 case MetaActionType::TEXTFILLCOLOR:
1424 if( !rParms.maTextColor )
1426 setStateColor( static_cast<MetaTextFillColorAction*>(pCurrAct),
1427 rStates.getState().isTextFillColorSet,
1428 rStates.getState().textFillColor,
1429 rCanvas );
1431 else
1433 // #120994# Do switch on/off TextFillColor, even when an overriding one is set
1434 bool bSetting(static_cast<MetaTextFillColorAction*>(pCurrAct)->IsSetting());
1436 rStates.getState().isTextFillColorSet = bSetting;
1438 break;
1440 case MetaActionType::TEXTLINECOLOR:
1441 if( !rParms.maTextColor )
1443 setStateColor( static_cast<MetaTextLineColorAction*>(pCurrAct),
1444 rStates.getState().isTextLineColorSet,
1445 rStates.getState().textLineColor,
1446 rCanvas );
1448 else
1450 // #120994# Do switch on/off TextLineColor, even when an overriding one is set
1451 bool bSetting(static_cast<MetaTextLineColorAction*>(pCurrAct)->IsSetting());
1453 rStates.getState().isTextLineColorSet = bSetting;
1455 break;
1457 case MetaActionType::OVERLINECOLOR:
1458 if( !rParms.maTextColor )
1460 setStateColor( static_cast<MetaOverlineColorAction*>(pCurrAct),
1461 rStates.getState().isTextOverlineColorSet,
1462 rStates.getState().textOverlineColor,
1463 rCanvas );
1465 else
1467 bool bSetting(static_cast<MetaOverlineColorAction*>(pCurrAct)->IsSetting());
1469 rStates.getState().isTextOverlineColorSet = bSetting;
1471 break;
1473 case MetaActionType::TEXTALIGN:
1475 ::cppcanvas::internal::OutDevState& rState = rStates.getState();
1476 const TextAlign eTextAlign( static_cast<MetaTextAlignAction*>(pCurrAct)->GetTextAlign() );
1478 rState.textReferencePoint = eTextAlign;
1480 break;
1482 case MetaActionType::FONT:
1484 ::cppcanvas::internal::OutDevState& rState = rStates.getState();
1485 const vcl::Font& rFont( static_cast<MetaFontAction*>(pCurrAct)->GetFont() );
1487 rState.xFont = createFont( rState.fontRotation,
1488 rFont,
1489 rFactoryParms );
1491 // TODO(Q2): define and use appropriate enumeration types
1492 rState.textReliefStyle = rFont.GetRelief();
1493 rState.textOverlineStyle = static_cast<sal_Int8>(rFont.GetOverline());
1494 rState.textUnderlineStyle = rParms.maFontUnderline.has_value() ?
1495 (*rParms.maFontUnderline ? sal_Int8(LINESTYLE_SINGLE) : sal_Int8(LINESTYLE_NONE)) :
1496 static_cast<sal_Int8>(rFont.GetUnderline());
1497 rState.textStrikeoutStyle = static_cast<sal_Int8>(rFont.GetStrikeout());
1498 rState.textEmphasisMark = rFont.GetEmphasisMark();
1499 rState.isTextEffectShadowSet = rFont.IsShadow();
1500 rState.isTextWordUnderlineSet = rFont.IsWordLineMode();
1501 rState.isTextOutlineModeSet = rFont.IsOutline();
1503 break;
1505 case MetaActionType::RASTEROP:
1506 // TODO(F2): NYI
1507 break;
1509 case MetaActionType::LAYOUTMODE:
1511 // TODO(F2): A lot is missing here
1512 vcl::text::ComplexTextLayoutFlags nLayoutMode = static_cast<MetaLayoutModeAction*>(pCurrAct)->GetLayoutMode();
1513 ::cppcanvas::internal::OutDevState& rState = rStates.getState();
1515 vcl::text::ComplexTextLayoutFlags nBidiLayoutMode = nLayoutMode & (vcl::text::ComplexTextLayoutFlags::BiDiRtl|vcl::text::ComplexTextLayoutFlags::BiDiStrong);
1516 if( nBidiLayoutMode == vcl::text::ComplexTextLayoutFlags::Default)
1517 rState.textDirection = rendering::TextDirection::WEAK_LEFT_TO_RIGHT;
1518 else if( nBidiLayoutMode == vcl::text::ComplexTextLayoutFlags::BiDiStrong)
1519 rState.textDirection = rendering::TextDirection::STRONG_LEFT_TO_RIGHT;
1520 else if( nBidiLayoutMode == vcl::text::ComplexTextLayoutFlags::BiDiRtl)
1521 rState.textDirection = rendering::TextDirection::WEAK_RIGHT_TO_LEFT;
1522 else if( nBidiLayoutMode == (vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::BiDiStrong))
1523 rState.textDirection = rendering::TextDirection::STRONG_RIGHT_TO_LEFT;
1525 rState.textAlignment = 0; // TODO(F2): rendering::TextAlignment::LEFT_ALIGNED;
1526 if( (nLayoutMode & (vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginRight) )
1527 && !(nLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginLeft ) )
1529 rState.textAlignment = 1; // TODO(F2): rendering::TextAlignment::RIGHT_ALIGNED;
1532 break;
1535 // In the second part of this monster-switch, we
1536 // handle all recursing meta actions. These are the
1537 // ones generating a metafile by themselves, which is
1538 // then processed by recursively calling this method.
1541 case MetaActionType::GRADIENT:
1543 MetaGradientAction* pGradAct = static_cast<MetaGradientAction*>(pCurrAct);
1544 createGradientAction( ::tools::PolyPolygon( pGradAct->GetRect() ),
1545 pGradAct->GetGradient(),
1546 rFactoryParms,
1547 true,
1548 bSubsettableActions );
1550 break;
1552 case MetaActionType::HATCH:
1554 // TODO(F2): use native Canvas hatches here
1555 GDIMetaFile aTmpMtf;
1557 rVDev.AddHatchActions( static_cast<MetaHatchAction*>(pCurrAct)->GetPolyPolygon(),
1558 static_cast<MetaHatchAction*>(pCurrAct)->GetHatch(),
1559 aTmpMtf );
1560 createActions( aTmpMtf, rFactoryParms,
1561 bSubsettableActions );
1563 break;
1565 case MetaActionType::EPS:
1567 MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pCurrAct);
1568 const GDIMetaFile& rSubstitute = pAct->GetSubstitute();
1570 // #121806# explicitly kept integer
1571 const Size aMtfSize( rSubstitute.GetPrefSize() );
1572 const Size aMtfSizePixPre( rVDev.LogicToPixel( aMtfSize,
1573 rSubstitute.GetPrefMapMode() ) );
1575 // #i44110# correct null-sized output - there
1576 // are metafiles which have zero size in at
1577 // least one dimension
1579 // Remark the 1L cannot be replaced, that would cause max to compare long/int
1580 const Size aMtfSizePix( std::max( aMtfSizePixPre.Width(), ::tools::Long(1) ),
1581 std::max( aMtfSizePixPre.Height(), ::tools::Long(1) ) );
1583 // Setup local transform, such that the
1584 // metafile renders itself into the given
1585 // output rectangle
1586 rStates.pushState(vcl::PushFlags::ALL);
1588 rVDev.Push();
1589 rVDev.SetMapMode( rSubstitute.GetPrefMapMode() );
1591 const ::Point aPos( rVDev.LogicToPixel( pAct->GetPoint() ) );
1592 const ::Size aSize( rVDev.LogicToPixel( pAct->GetSize() ) );
1594 rStates.getState().transform.translate( aPos.X(),
1595 aPos.Y() );
1596 rStates.getState().transform.scale( static_cast<double>(aSize.Width()) / aMtfSizePix.Width(),
1597 static_cast<double>(aSize.Height()) / aMtfSizePix.Height() );
1599 createActions( const_cast<GDIMetaFile&>(pAct->GetSubstitute()),
1600 rFactoryParms,
1601 bSubsettableActions );
1603 rVDev.Pop();
1604 rStates.popState();
1606 break;
1608 // handle metafile comments, to retrieve
1609 // meta-information for gradients, fills and
1610 // strokes. May skip actions, and may recurse.
1611 case MetaActionType::COMMENT:
1613 MetaCommentAction* pAct = static_cast<MetaCommentAction*>(pCurrAct);
1615 // Handle gradients
1616 if (pAct->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN"))
1618 MetaGradientExAction* pGradAction = nullptr;
1619 bool bDone( false );
1620 while( !bDone )
1622 pCurrAct=rMtf.NextAction();
1623 if (!pCurrAct)
1624 break;
1625 switch( pCurrAct->GetType() )
1627 // extract gradient info
1628 case MetaActionType::GRADIENTEX:
1629 pGradAction = static_cast<MetaGradientExAction*>(pCurrAct);
1630 break;
1632 // skip broken-down rendering, output gradient when sequence is ended
1633 case MetaActionType::COMMENT:
1634 if( static_cast<MetaCommentAction*>(pCurrAct)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END") )
1636 bDone = true;
1638 if( pGradAction )
1640 createGradientAction( pGradAction->GetPolyPolygon(),
1641 pGradAction->GetGradient(),
1642 rFactoryParms,
1643 false,
1644 bSubsettableActions );
1647 break;
1648 default: break;
1652 // TODO(P2): Handle drawing layer strokes, via
1653 // XPATHSTROKE_SEQ_BEGIN comment
1655 // Handle drawing layer fills
1656 else if( pAct->GetComment() == "XPATHFILL_SEQ_BEGIN" )
1658 const sal_uInt8* pData = pAct->GetData();
1659 if ( pData )
1661 SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pData), pAct->GetDataSize(), StreamMode::READ );
1663 SvtGraphicFill aFill;
1664 ReadSvtGraphicFill( aMemStm, aFill );
1666 // TODO(P2): Also handle gradients and
1667 // hatches like this
1669 // only evaluate comment for pure
1670 // bitmap fills. If a transparency
1671 // gradient is involved (denoted by
1672 // the FloatTransparent action), take
1673 // the normal meta actions.
1674 if( aFill.getFillType() == SvtGraphicFill::fillTexture &&
1675 !isActionContained( rMtf,
1676 "XPATHFILL_SEQ_END",
1677 MetaActionType::FLOATTRANSPARENT ) )
1679 rendering::Texture aTexture;
1681 // TODO(F1): the SvtGraphicFill
1682 // can also transport metafiles
1683 // here, handle that case, too
1684 Graphic aGraphic;
1685 aFill.getGraphic( aGraphic );
1687 BitmapEx aBmpEx( aGraphic.GetBitmapEx() );
1688 const ::Size aBmpSize( aBmpEx.GetSizePixel() );
1690 ::SvtGraphicFill::Transform aTransform;
1691 aFill.getTransform( aTransform );
1693 ::basegfx::B2DHomMatrix aMatrix;
1695 // convert to basegfx matrix
1696 aMatrix.set(0,0, aTransform.matrix[ 0 ] );
1697 aMatrix.set(0,1, aTransform.matrix[ 1 ] );
1698 aMatrix.set(0,2, aTransform.matrix[ 2 ] );
1699 aMatrix.set(1,0, aTransform.matrix[ 3 ] );
1700 aMatrix.set(1,1, aTransform.matrix[ 4 ] );
1701 aMatrix.set(1,2, aTransform.matrix[ 5 ] );
1703 ::basegfx::B2DHomMatrix aScale;
1704 aScale.scale( aBmpSize.Width(),
1705 aBmpSize.Height() );
1707 // post-multiply with the bitmap
1708 // size (XCanvas' texture assumes
1709 // the given bitmap to be
1710 // normalized to [0,1]x[0,1]
1711 // rectangle)
1712 aMatrix = aMatrix * aScale;
1714 // pre-multiply with the
1715 // logic-to-pixel scale factor
1716 // (the metafile comment works in
1717 // logical coordinates).
1718 ::basegfx::B2DHomMatrix aLogic2PixelTransform;
1719 aMatrix *= tools::calcLogic2PixelLinearTransform( aLogic2PixelTransform,
1720 rVDev );
1722 ::basegfx::unotools::affineMatrixFromHomMatrix(
1723 aTexture.AffineTransform,
1724 aMatrix );
1726 aTexture.Alpha = 1.0 - aFill.getTransparency();
1727 aTexture.Bitmap = vcl::unotools::xBitmapFromBitmapEx( aBmpEx );
1728 if( aFill.isTiling() )
1730 aTexture.RepeatModeX = rendering::TexturingMode::REPEAT;
1731 aTexture.RepeatModeY = rendering::TexturingMode::REPEAT;
1733 else
1735 aTexture.RepeatModeX = rendering::TexturingMode::NONE;
1736 aTexture.RepeatModeY = rendering::TexturingMode::NONE;
1739 ::tools::PolyPolygon aPath;
1740 aFill.getPath( aPath );
1742 ::basegfx::B2DPolyPolygon aPoly( aPath.getB2DPolyPolygon() );
1743 aPoly.transform( rStates.getState().mapModeTransform );
1744 std::shared_ptr<Action> pPolyAction(
1745 internal::PolyPolyActionFactory::createPolyPolyAction(
1746 aPoly,
1747 rCanvas,
1748 rStates.getState(),
1749 aTexture ) );
1751 if( pPolyAction )
1753 maActions.emplace_back(
1754 pPolyAction,
1755 io_rCurrActionIndex );
1757 io_rCurrActionIndex += pPolyAction->getActionCount()-1;
1760 // skip broken-down render output
1761 skipContent( rMtf,
1762 "XPATHFILL_SEQ_END",
1763 io_rCurrActionIndex );
1767 // Handle drawing layer fills
1768 else if( pAct->GetComment() == "EMF_PLUS" ) {
1769 SAL_WARN ("cppcanvas.emf", "EMF+ code was refactored and removed");
1770 } else if( pAct->GetComment() == "EMF_PLUS_HEADER_INFO" ) {
1771 SAL_INFO ("cppcanvas.emf", "EMF+ passed to canvas mtf renderer - header info, size: " << pAct->GetDataSize ());
1773 SvMemoryStream rMF (const_cast<sal_uInt8 *>(pAct->GetData ()), pAct->GetDataSize (), StreamMode::READ);
1775 rMF.ReadInt32( nFrameLeft ).ReadInt32( nFrameTop ).ReadInt32( nFrameRight ).ReadInt32( nFrameBottom );
1776 SAL_INFO ("cppcanvas.emf", "EMF+ picture frame: " << nFrameLeft << "," << nFrameTop << " - " << nFrameRight << "," << nFrameBottom);
1777 rMF.ReadInt32( nPixX ).ReadInt32( nPixY ).ReadInt32( nMmX ).ReadInt32( nMmY );
1778 SAL_INFO ("cppcanvas.emf", "EMF+ ref device pixel size: " << nPixX << "x" << nPixY << " mm size: " << nMmX << "x" << nMmY);
1780 ReadXForm( rMF, aBaseTransform );
1781 //aWorldTransform.Set (aBaseTransform);
1784 break;
1787 // In the third part of this monster-switch, we
1788 // handle all 'acting' meta actions. These are all
1789 // processed by constructing function objects for
1790 // them, which will later ease caching.
1793 case MetaActionType::POINT:
1795 const OutDevState& rState( rStates.getState() );
1796 if( rState.lineColor.hasElements() )
1798 std::shared_ptr<Action> pPointAction(
1799 internal::PointActionFactory::createPointAction(
1800 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint(
1801 static_cast<MetaPointAction*>(pCurrAct)->GetPoint() ),
1802 rCanvas,
1803 rState ) );
1805 if( pPointAction )
1807 maActions.emplace_back(
1808 pPointAction,
1809 io_rCurrActionIndex );
1811 io_rCurrActionIndex += pPointAction->getActionCount()-1;
1815 break;
1817 case MetaActionType::PIXEL:
1819 const OutDevState& rState( rStates.getState() );
1820 if( rState.lineColor.hasElements() )
1822 std::shared_ptr<Action> pPointAction(
1823 internal::PointActionFactory::createPointAction(
1824 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint(
1825 static_cast<MetaPixelAction*>(pCurrAct)->GetPoint() ),
1826 rCanvas,
1827 rState,
1828 static_cast<MetaPixelAction*>(pCurrAct)->GetColor() ) );
1830 if( pPointAction )
1832 maActions.emplace_back(
1833 pPointAction,
1834 io_rCurrActionIndex );
1836 io_rCurrActionIndex += pPointAction->getActionCount()-1;
1840 break;
1842 case MetaActionType::LINE:
1844 const OutDevState& rState( rStates.getState() );
1845 if( rState.lineColor.hasElements() )
1847 MetaLineAction* pLineAct = static_cast<MetaLineAction*>(pCurrAct);
1849 const LineInfo& rLineInfo( pLineAct->GetLineInfo() );
1851 const ::basegfx::B2DPoint aStartPoint(
1852 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( pLineAct->GetStartPoint() ));
1853 const ::basegfx::B2DPoint aEndPoint(
1854 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( pLineAct->GetEndPoint() ));
1856 std::shared_ptr<Action> pLineAction;
1858 if( rLineInfo.IsDefault() )
1860 // plain hair line
1861 pLineAction =
1862 internal::LineActionFactory::createLineAction(
1863 aStartPoint,
1864 aEndPoint,
1865 rCanvas,
1866 rState );
1868 if( pLineAction )
1870 maActions.emplace_back(
1871 pLineAction,
1872 io_rCurrActionIndex );
1874 io_rCurrActionIndex += pLineAction->getActionCount()-1;
1877 else if( LineStyle::NONE != rLineInfo.GetStyle() )
1879 // 'thick' line
1880 rendering::StrokeAttributes aStrokeAttributes;
1882 setupStrokeAttributes( aStrokeAttributes,
1883 rFactoryParms,
1884 rLineInfo );
1886 // XCanvas can only stroke polygons,
1887 // not simple lines - thus, handle
1888 // this case via the polypolygon
1889 // action
1890 ::basegfx::B2DPolygon aPoly;
1891 aPoly.append( aStartPoint );
1892 aPoly.append( aEndPoint );
1893 pLineAction =
1894 internal::PolyPolyActionFactory::createPolyPolyAction(
1895 ::basegfx::B2DPolyPolygon( aPoly ),
1896 rCanvas, rState, aStrokeAttributes );
1898 if( pLineAction )
1900 maActions.emplace_back(
1901 pLineAction,
1902 io_rCurrActionIndex );
1904 io_rCurrActionIndex += pLineAction->getActionCount()-1;
1907 // else: line style is default
1908 // (i.e. invisible), don't generate action
1911 break;
1913 case MetaActionType::RECT:
1915 const ::tools::Rectangle& rRect(
1916 static_cast<MetaRectAction*>(pCurrAct)->GetRect() );
1918 if( rRect.IsEmpty() )
1919 break;
1921 const OutDevState& rState( rStates.getState() );
1922 const ::basegfx::B2DPoint aTopLeftPixel(
1923 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( rRect.TopLeft() ) );
1924 const ::basegfx::B2DPoint aBottomRightPixel(
1925 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( rRect.BottomRight() ) +
1926 // #121100# OutputDevice::DrawRect() fills
1927 // rectangles Apple-like, i.e. with one
1928 // additional pixel to the right and bottom.
1929 ::basegfx::B2DPoint(1,1) );
1931 createFillAndStroke( ::basegfx::utils::createPolygonFromRect(
1932 ::basegfx::B2DRange( aTopLeftPixel,
1933 aBottomRightPixel )),
1934 rFactoryParms );
1935 break;
1938 case MetaActionType::ROUNDRECT:
1940 const ::tools::Rectangle& rRect(
1941 static_cast<MetaRoundRectAction*>(pCurrAct)->GetRect());
1943 if( rRect.IsEmpty() )
1944 break;
1946 ::basegfx::B2DPolygon aPoly(
1947 ::basegfx::utils::createPolygonFromRect(
1948 ::basegfx::B2DRange(
1949 vcl::unotools::b2DPointFromPoint( rRect.TopLeft() ),
1950 vcl::unotools::b2DPointFromPoint( rRect.BottomRight() ) +
1951 ::basegfx::B2DPoint(1,1) ),
1952 static_cast<double>(static_cast<MetaRoundRectAction*>(pCurrAct)->GetHorzRound()) / rRect.GetWidth(),
1953 static_cast<double>(static_cast<MetaRoundRectAction*>(pCurrAct)->GetVertRound()) / rRect.GetHeight() ) );
1954 aPoly.transform( rStates.getState().mapModeTransform );
1956 createFillAndStroke( aPoly,
1957 rFactoryParms );
1959 break;
1961 case MetaActionType::ELLIPSE:
1963 const ::tools::Rectangle& rRect(
1964 static_cast<MetaEllipseAction*>(pCurrAct)->GetRect() );
1966 if( rRect.IsEmpty() )
1967 break;
1969 const ::basegfx::B2DRange aRange(
1970 vcl::unotools::b2DPointFromPoint( rRect.TopLeft() ),
1971 vcl::unotools::b2DPointFromPoint( rRect.BottomRight() ) +
1972 ::basegfx::B2DPoint(1,1) );
1974 ::basegfx::B2DPolygon aPoly(
1975 ::basegfx::utils::createPolygonFromEllipse(
1976 aRange.getCenter(),
1977 aRange.getWidth() / 2, // divide by 2 since createPolygonFromEllipse
1978 aRange.getHeight() / 2 )); // expects the radius and NOT the diameter!
1979 aPoly.transform( rStates.getState().mapModeTransform );
1981 createFillAndStroke( aPoly,
1982 rFactoryParms );
1984 break;
1986 case MetaActionType::ARC:
1988 // TODO(F1): Missing basegfx functionality. Mind empty rects!
1989 const ::tools::Polygon aToolsPoly( static_cast<MetaArcAction*>(pCurrAct)->GetRect(),
1990 static_cast<MetaArcAction*>(pCurrAct)->GetStartPoint(),
1991 static_cast<MetaArcAction*>(pCurrAct)->GetEndPoint(), PolyStyle::Arc );
1992 ::basegfx::B2DPolygon aPoly( aToolsPoly.getB2DPolygon() );
1993 aPoly.transform( rStates.getState().mapModeTransform );
1995 createFillAndStroke( aPoly,
1996 rFactoryParms );
1998 break;
2000 case MetaActionType::PIE:
2002 // TODO(F1): Missing basegfx functionality. Mind empty rects!
2003 const ::tools::Polygon aToolsPoly( static_cast<MetaPieAction*>(pCurrAct)->GetRect(),
2004 static_cast<MetaPieAction*>(pCurrAct)->GetStartPoint(),
2005 static_cast<MetaPieAction*>(pCurrAct)->GetEndPoint(), PolyStyle::Pie );
2006 ::basegfx::B2DPolygon aPoly( aToolsPoly.getB2DPolygon() );
2007 aPoly.transform( rStates.getState().mapModeTransform );
2009 createFillAndStroke( aPoly,
2010 rFactoryParms );
2012 break;
2014 case MetaActionType::CHORD:
2016 // TODO(F1): Missing basegfx functionality. Mind empty rects!
2017 const ::tools::Polygon aToolsPoly( static_cast<MetaChordAction*>(pCurrAct)->GetRect(),
2018 static_cast<MetaChordAction*>(pCurrAct)->GetStartPoint(),
2019 static_cast<MetaChordAction*>(pCurrAct)->GetEndPoint(), PolyStyle::Chord );
2020 ::basegfx::B2DPolygon aPoly( aToolsPoly.getB2DPolygon() );
2021 aPoly.transform( rStates.getState().mapModeTransform );
2023 createFillAndStroke( aPoly,
2024 rFactoryParms );
2026 break;
2028 case MetaActionType::POLYLINE:
2030 const OutDevState& rState( rStates.getState() );
2031 if( rState.lineColor.hasElements() ||
2032 rState.fillColor.hasElements() )
2034 MetaPolyLineAction* pPolyLineAct = static_cast<MetaPolyLineAction*>(pCurrAct);
2036 const LineInfo& rLineInfo( pPolyLineAct->GetLineInfo() );
2037 ::basegfx::B2DPolygon aPoly( pPolyLineAct->GetPolygon().getB2DPolygon() );
2038 aPoly.transform( rState.mapModeTransform );
2040 std::shared_ptr<Action> pLineAction;
2042 if( rLineInfo.IsDefault() )
2044 // plain hair line polygon
2045 pLineAction =
2046 internal::PolyPolyActionFactory::createLinePolyPolyAction(
2047 ::basegfx::B2DPolyPolygon(aPoly),
2048 rCanvas,
2049 rState );
2051 if( pLineAction )
2053 maActions.emplace_back(
2054 pLineAction,
2055 io_rCurrActionIndex );
2057 io_rCurrActionIndex += pLineAction->getActionCount()-1;
2060 else if( LineStyle::NONE != rLineInfo.GetStyle() )
2062 // 'thick' line polygon
2063 rendering::StrokeAttributes aStrokeAttributes;
2065 setupStrokeAttributes( aStrokeAttributes,
2066 rFactoryParms,
2067 rLineInfo );
2069 pLineAction =
2070 internal::PolyPolyActionFactory::createPolyPolyAction(
2071 ::basegfx::B2DPolyPolygon(aPoly),
2072 rCanvas,
2073 rState,
2074 aStrokeAttributes ) ;
2076 if( pLineAction )
2078 maActions.emplace_back(
2079 pLineAction,
2080 io_rCurrActionIndex );
2082 io_rCurrActionIndex += pLineAction->getActionCount()-1;
2085 // else: line style is default
2086 // (i.e. invisible), don't generate action
2089 break;
2091 case MetaActionType::POLYGON:
2093 ::basegfx::B2DPolygon aPoly( static_cast<MetaPolygonAction*>(pCurrAct)->GetPolygon().getB2DPolygon() );
2094 aPoly.transform( rStates.getState().mapModeTransform );
2095 createFillAndStroke( aPoly,
2096 rFactoryParms );
2098 break;
2100 case MetaActionType::POLYPOLYGON:
2102 ::basegfx::B2DPolyPolygon aPoly( static_cast<MetaPolyPolygonAction*>(pCurrAct)->GetPolyPolygon().getB2DPolyPolygon() );
2103 aPoly.transform( rStates.getState().mapModeTransform );
2104 createFillAndStroke( aPoly,
2105 rFactoryParms );
2107 break;
2109 case MetaActionType::BMP:
2111 MetaBmpAction* pAct = static_cast<MetaBmpAction*>(pCurrAct);
2113 std::shared_ptr<Action> pBmpAction(
2114 internal::BitmapActionFactory::createBitmapAction(
2115 BitmapEx(pAct->GetBitmap()),
2116 rStates.getState().mapModeTransform *
2117 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2118 rCanvas,
2119 rStates.getState() ) );
2121 if( pBmpAction )
2123 maActions.emplace_back(
2124 pBmpAction,
2125 io_rCurrActionIndex );
2127 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2130 break;
2132 case MetaActionType::BMPSCALE:
2134 MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pCurrAct);
2136 std::shared_ptr<Action> pBmpAction(
2137 internal::BitmapActionFactory::createBitmapAction(
2138 BitmapEx(pAct->GetBitmap()),
2139 rStates.getState().mapModeTransform * vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2140 rStates.getState().mapModeTransform * vcl::unotools::b2DVectorFromSize( pAct->GetSize() ),
2141 rCanvas,
2142 rStates.getState() ) );
2144 if( pBmpAction )
2146 maActions.emplace_back(
2147 pBmpAction,
2148 io_rCurrActionIndex );
2150 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2153 break;
2155 case MetaActionType::BMPSCALEPART:
2157 MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pCurrAct);
2159 // crop bitmap to given source rectangle (no
2160 // need to copy and convert the whole bitmap)
2161 ::Bitmap aBmp( pAct->GetBitmap() );
2162 const ::tools::Rectangle aCropRect( pAct->GetSrcPoint(),
2163 pAct->GetSrcSize() );
2164 aBmp.Crop( aCropRect );
2166 std::shared_ptr<Action> pBmpAction(
2167 internal::BitmapActionFactory::createBitmapAction(
2168 BitmapEx(aBmp),
2169 rStates.getState().mapModeTransform *
2170 vcl::unotools::b2DPointFromPoint( pAct->GetDestPoint() ),
2171 rStates.getState().mapModeTransform *
2172 vcl::unotools::b2DVectorFromSize( pAct->GetDestSize() ),
2173 rCanvas,
2174 rStates.getState() ) );
2176 if( pBmpAction )
2178 maActions.emplace_back(
2179 pBmpAction,
2180 io_rCurrActionIndex );
2182 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2185 break;
2187 case MetaActionType::BMPEX:
2189 MetaBmpExAction* pAct = static_cast<MetaBmpExAction*>(pCurrAct);
2191 std::shared_ptr<Action> pBmpAction(
2192 internal::BitmapActionFactory::createBitmapAction(
2193 pAct->GetBitmapEx(),
2194 rStates.getState().mapModeTransform *
2195 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2196 rCanvas,
2197 rStates.getState() ) );
2199 if( pBmpAction )
2201 maActions.emplace_back(
2202 pBmpAction,
2203 io_rCurrActionIndex );
2205 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2208 break;
2210 case MetaActionType::BMPEXSCALE:
2212 MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pCurrAct);
2214 std::shared_ptr<Action> pBmpAction(
2215 internal::BitmapActionFactory::createBitmapAction(
2216 pAct->GetBitmapEx(),
2217 rStates.getState().mapModeTransform *
2218 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2219 rStates.getState().mapModeTransform *
2220 vcl::unotools::b2DVectorFromSize( pAct->GetSize() ),
2221 rCanvas,
2222 rStates.getState() ) );
2224 if( pBmpAction )
2226 maActions.emplace_back(
2227 pBmpAction,
2228 io_rCurrActionIndex );
2230 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2233 break;
2235 case MetaActionType::BMPEXSCALEPART:
2237 MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pCurrAct);
2239 // crop bitmap to given source rectangle (no
2240 // need to copy and convert the whole bitmap)
2241 BitmapEx aBmp( pAct->GetBitmapEx() );
2242 const ::tools::Rectangle aCropRect( pAct->GetSrcPoint(),
2243 pAct->GetSrcSize() );
2244 aBmp.Crop( aCropRect );
2246 std::shared_ptr<Action> pBmpAction(
2247 internal::BitmapActionFactory::createBitmapAction(
2248 aBmp,
2249 rStates.getState().mapModeTransform *
2250 vcl::unotools::b2DPointFromPoint( pAct->GetDestPoint() ),
2251 rStates.getState().mapModeTransform *
2252 vcl::unotools::b2DVectorFromSize( pAct->GetDestSize() ),
2253 rCanvas,
2254 rStates.getState() ) );
2256 if( pBmpAction )
2258 maActions.emplace_back(
2259 pBmpAction,
2260 io_rCurrActionIndex );
2262 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2265 break;
2267 case MetaActionType::MASK:
2269 MetaMaskAction* pAct = static_cast<MetaMaskAction*>(pCurrAct);
2271 // create masked BitmapEx right here, as the
2272 // canvas does not provide equivalent
2273 // functionality
2274 BitmapEx aBmp( createMaskBmpEx( pAct->GetBitmap(),
2275 pAct->GetColor() ));
2277 std::shared_ptr<Action> pBmpAction(
2278 internal::BitmapActionFactory::createBitmapAction(
2279 aBmp,
2280 rStates.getState().mapModeTransform *
2281 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2282 rCanvas,
2283 rStates.getState() ) );
2285 if( pBmpAction )
2287 maActions.emplace_back(
2288 pBmpAction,
2289 io_rCurrActionIndex );
2291 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2294 break;
2296 case MetaActionType::MASKSCALE:
2298 MetaMaskScaleAction* pAct = static_cast<MetaMaskScaleAction*>(pCurrAct);
2300 // create masked BitmapEx right here, as the
2301 // canvas does not provide equivalent
2302 // functionality
2303 BitmapEx aBmp( createMaskBmpEx( pAct->GetBitmap(),
2304 pAct->GetColor() ));
2306 std::shared_ptr<Action> pBmpAction(
2307 internal::BitmapActionFactory::createBitmapAction(
2308 aBmp,
2309 rStates.getState().mapModeTransform *
2310 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2311 rStates.getState().mapModeTransform *
2312 vcl::unotools::b2DVectorFromSize( pAct->GetSize() ),
2313 rCanvas,
2314 rStates.getState() ) );
2316 if( pBmpAction )
2318 maActions.emplace_back(
2319 pBmpAction,
2320 io_rCurrActionIndex );
2322 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2325 break;
2327 case MetaActionType::MASKSCALEPART:
2329 MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pCurrAct);
2331 // create masked BitmapEx right here, as the
2332 // canvas does not provide equivalent
2333 // functionality
2334 BitmapEx aBmp( createMaskBmpEx( pAct->GetBitmap(),
2335 pAct->GetColor() ));
2337 // crop bitmap to given source rectangle (no
2338 // need to copy and convert the whole bitmap)
2339 const ::tools::Rectangle aCropRect( pAct->GetSrcPoint(),
2340 pAct->GetSrcSize() );
2341 aBmp.Crop( aCropRect );
2343 std::shared_ptr<Action> pBmpAction(
2344 internal::BitmapActionFactory::createBitmapAction(
2345 aBmp,
2346 rStates.getState().mapModeTransform *
2347 vcl::unotools::b2DPointFromPoint( pAct->GetDestPoint() ),
2348 rStates.getState().mapModeTransform *
2349 vcl::unotools::b2DVectorFromSize( pAct->GetDestSize() ),
2350 rCanvas,
2351 rStates.getState() ) );
2353 if( pBmpAction )
2355 maActions.emplace_back(
2356 pBmpAction,
2357 io_rCurrActionIndex );
2359 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2362 break;
2364 case MetaActionType::GRADIENTEX:
2365 // TODO(F1): use native Canvas gradients here
2366 // action is ignored here, because redundant to MetaActionType::GRADIENT
2367 break;
2369 case MetaActionType::WALLPAPER:
2370 // TODO(F2): NYI
2371 break;
2373 case MetaActionType::Transparent:
2375 const OutDevState& rState( rStates.getState() );
2376 if( rState.lineColor.hasElements() ||
2377 rState.fillColor.hasElements() )
2379 MetaTransparentAction* pAct = static_cast<MetaTransparentAction*>(pCurrAct);
2380 ::basegfx::B2DPolyPolygon aPoly( pAct->GetPolyPolygon().getB2DPolyPolygon() );
2381 aPoly.transform( rState.mapModeTransform );
2383 std::shared_ptr<Action> pPolyAction(
2384 internal::PolyPolyActionFactory::createPolyPolyAction(
2385 aPoly,
2386 rCanvas,
2387 rState,
2388 pAct->GetTransparence() ) );
2390 if( pPolyAction )
2392 maActions.emplace_back(
2393 pPolyAction,
2394 io_rCurrActionIndex );
2396 io_rCurrActionIndex += pPolyAction->getActionCount()-1;
2400 break;
2402 case MetaActionType::FLOATTRANSPARENT:
2404 MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pCurrAct);
2406 std::unique_ptr< GDIMetaFile > pMtf(
2407 new ::GDIMetaFile( pAct->GetGDIMetaFile() ) );
2409 // TODO(P2): Use native canvas gradients here (saves a lot of UNO calls)
2410 std::optional< Gradient > pGradient( pAct->GetGradient() );
2412 DBG_TESTSOLARMUTEX();
2414 std::shared_ptr<Action> pFloatTransAction(
2415 internal::TransparencyGroupActionFactory::createTransparencyGroupAction(
2416 std::move(pMtf),
2417 std::move(pGradient),
2418 rStates.getState().mapModeTransform *
2419 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2420 rStates.getState().mapModeTransform *
2421 vcl::unotools::b2DVectorFromSize( pAct->GetSize() ),
2422 rCanvas,
2423 rStates.getState() ) );
2425 if( pFloatTransAction )
2427 maActions.emplace_back(
2428 pFloatTransAction,
2429 io_rCurrActionIndex );
2431 io_rCurrActionIndex += pFloatTransAction->getActionCount()-1;
2434 break;
2436 case MetaActionType::TEXT:
2438 MetaTextAction* pAct = static_cast<MetaTextAction*>(pCurrAct);
2439 OUString sText = pAct->GetText();
2441 if (rVDev.GetDigitLanguage())
2442 sText = convertToLocalizedNumerals(sText, rVDev.GetDigitLanguage());
2444 const sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex());
2446 createTextAction(
2447 pAct->GetPoint(),
2448 sText,
2449 pAct->GetIndex(),
2450 nLen,
2453 rFactoryParms,
2454 bSubsettableActions );
2456 break;
2458 case MetaActionType::TEXTARRAY:
2460 MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pCurrAct);
2461 OUString sText = pAct->GetText();
2463 if (rVDev.GetDigitLanguage())
2464 sText = convertToLocalizedNumerals(sText, rVDev.GetDigitLanguage());
2466 const sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex());
2468 createTextAction(
2469 pAct->GetPoint(),
2470 sText,
2471 pAct->GetIndex(),
2472 nLen,
2473 pAct->GetDXArray(),
2474 pAct->GetKashidaArray(),
2475 rFactoryParms,
2476 bSubsettableActions );
2478 break;
2480 case MetaActionType::TEXTLINE:
2482 MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pCurrAct);
2484 const OutDevState& rState( rStates.getState() );
2485 const ::Size aBaselineOffset( tools::getBaselineOffset( rState,
2486 rVDev ) );
2487 const ::basegfx::B2DSize aSize( rState.mapModeTransform *
2488 ::basegfx::B2DSize(pAct->GetWidth(),
2489 0 ));
2491 std::shared_ptr<Action> pPolyAction(
2492 PolyPolyActionFactory::createPolyPolyAction(
2493 tools::createTextLinesPolyPolygon(
2494 rState.mapModeTransform *
2495 ::basegfx::B2DPoint(
2496 vcl::unotools::b2DPointFromPoint(pAct->GetStartPoint()) +
2497 vcl::unotools::b2DVectorFromSize(aBaselineOffset)),
2498 aSize.getWidth(),
2499 tools::createTextLineInfo( rVDev,
2500 rState )),
2501 rCanvas,
2502 rState ) );
2504 if( pPolyAction )
2506 maActions.emplace_back(
2507 pPolyAction,
2508 io_rCurrActionIndex );
2510 io_rCurrActionIndex += pPolyAction->getActionCount()-1;
2513 break;
2515 case MetaActionType::TEXTRECT:
2517 MetaTextRectAction* pAct = static_cast<MetaTextRectAction*>(pCurrAct);
2519 rStates.pushState(vcl::PushFlags::ALL);
2521 // use the VDev to break up the text rect
2522 // action into readily formatted lines
2523 GDIMetaFile aTmpMtf;
2524 rVDev.AddTextRectActions( pAct->GetRect(),
2525 pAct->GetText(),
2526 pAct->GetStyle(),
2527 aTmpMtf );
2529 createActions( aTmpMtf,
2530 rFactoryParms,
2531 bSubsettableActions );
2533 rStates.popState();
2535 break;
2538 case MetaActionType::STRETCHTEXT:
2540 MetaStretchTextAction* pAct = static_cast<MetaStretchTextAction*>(pCurrAct);
2541 OUString sText = pAct->GetText();
2543 if (rVDev.GetDigitLanguage())
2544 sText = convertToLocalizedNumerals(sText, rVDev.GetDigitLanguage());
2546 const sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex());
2548 // #i70897# Nothing to do, actually...
2549 if( nLen == 0 )
2550 break;
2552 // have to fit the text into the given
2553 // width. This is achieved by internally
2554 // generating a DX array, and uniformly
2555 // distributing the excess/insufficient width
2556 // to every logical character.
2557 KernArray aDXArray;
2559 rVDev.GetTextArray( pAct->GetText(), &aDXArray,
2560 pAct->GetIndex(), pAct->GetLen() );
2562 const double nWidthDifferencePerDx = ( pAct->GetWidth() - aDXArray[ nLen-1 ] ) / nLen;
2564 // Last entry of pDXArray contains total width of the text
2565 for (sal_Int32 i = 1; i <= nLen; ++i)
2567 // calc ratio for every array entry, to
2568 // distribute rounding errors 'evenly'
2569 // across the characters. Note that each
2570 // entry represents the 'end' position of
2571 // the corresponding character, thus, we
2572 // let i run from 1 to nLen.
2573 aDXArray[i - 1] += i * nWidthDifferencePerDx;
2576 createTextAction(
2577 pAct->GetPoint(),
2578 sText,
2579 pAct->GetIndex(),
2580 nLen,
2581 aDXArray,
2583 rFactoryParms,
2584 bSubsettableActions );
2586 break;
2588 default:
2589 SAL_WARN( "cppcanvas.emf", "Unknown meta action type encountered" );
2590 break;
2593 // increment action index (each mtf action counts _at
2594 // least_ one. Some count for more, therefore,
2595 // io_rCurrActionIndex is sometimes incremented by
2596 // pAct->getActionCount()-1 above, the -1 being the
2597 // correction for the unconditional increment here).
2598 ++io_rCurrActionIndex;
2603 namespace
2605 class ActionRenderer
2607 public:
2608 explicit ActionRenderer( ::basegfx::B2DHomMatrix aTransformation ) :
2609 maTransformation(std::move( aTransformation )),
2610 mbRet( true )
2614 bool result() const
2616 return mbRet;
2619 void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction )
2621 // ANDing the result. We want to fail if at least
2622 // one action failed.
2623 mbRet &= rAction.mpAction->render( maTransformation );
2626 void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction,
2627 const Action::Subset& rSubset )
2629 // ANDing the result. We want to fail if at least
2630 // one action failed.
2631 mbRet &= rAction.mpAction->renderSubset( maTransformation,
2632 rSubset );
2635 private:
2636 ::basegfx::B2DHomMatrix maTransformation;
2637 bool mbRet;
2640 class AreaQuery
2642 public:
2643 explicit AreaQuery( ::basegfx::B2DHomMatrix aTransformation ) :
2644 maTransformation(std::move( aTransformation ))
2648 static bool result()
2650 return true; // nothing can fail here
2653 void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction )
2655 maBounds.expand( rAction.mpAction->getBounds( maTransformation ) );
2658 void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction,
2659 const Action::Subset& rSubset )
2661 maBounds.expand( rAction.mpAction->getBounds( maTransformation,
2662 rSubset ) );
2665 const ::basegfx::B2DRange& getBounds() const
2667 return maBounds;
2670 private:
2671 ::basegfx::B2DHomMatrix maTransformation;
2672 ::basegfx::B2DRange maBounds;
2675 // Doing that via inline class. Compilers tend to not inline free
2676 // functions.
2677 struct UpperBoundActionIndexComparator
2679 bool operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rLHS,
2680 const ::cppcanvas::internal::ImplRenderer::MtfAction& rRHS )
2682 const sal_Int32 nLHSCount( rLHS.mpAction ?
2683 rLHS.mpAction->getActionCount() : 0 );
2684 const sal_Int32 nRHSCount( rRHS.mpAction ?
2685 rRHS.mpAction->getActionCount() : 0 );
2687 // compare end of action range, to have an action selected
2688 // by lower_bound even if the requested index points in
2689 // the middle of the action's range
2690 return rLHS.mnOrigIndex + nLHSCount < rRHS.mnOrigIndex + nRHSCount;
2694 /** Algorithm to apply given functor to a subset range
2696 @tpl Functor
2698 Functor to call for each element of the subset
2699 range. Must provide the following method signatures:
2700 bool result() (returning false if operation failed)
2703 template< typename Functor > bool
2704 forSubsetRange( Functor& rFunctor,
2705 ImplRenderer::ActionVector::const_iterator aRangeBegin,
2706 const ImplRenderer::ActionVector::const_iterator& aRangeEnd,
2707 sal_Int32 nStartIndex,
2708 sal_Int32 nEndIndex,
2709 const ImplRenderer::ActionVector::const_iterator& rEnd )
2711 if( aRangeBegin == aRangeEnd )
2713 // only a single action. Setup subset, and call functor
2714 Action::Subset aSubset;
2715 aSubset.mnSubsetBegin = std::max( sal_Int32( 0 ),
2716 nStartIndex - aRangeBegin->mnOrigIndex );
2717 aSubset.mnSubsetEnd = std::min( aRangeBegin->mpAction->getActionCount(),
2718 nEndIndex - aRangeBegin->mnOrigIndex );
2720 ENSURE_OR_RETURN_FALSE( aSubset.mnSubsetBegin >= 0 && aSubset.mnSubsetEnd >= 0,
2721 "ImplRenderer::forSubsetRange(): Invalid indices" );
2723 rFunctor( *aRangeBegin, aSubset );
2725 else
2727 // more than one action.
2729 // render partial first, full intermediate, and
2730 // partial last action
2731 Action::Subset aSubset;
2732 aSubset.mnSubsetBegin = std::max( sal_Int32( 0 ),
2733 nStartIndex - aRangeBegin->mnOrigIndex );
2734 aSubset.mnSubsetEnd = aRangeBegin->mpAction->getActionCount();
2736 ENSURE_OR_RETURN_FALSE( aSubset.mnSubsetBegin >= 0 && aSubset.mnSubsetEnd >= 0,
2737 "ImplRenderer::forSubsetRange(): Invalid indices" );
2739 rFunctor( *aRangeBegin, aSubset );
2741 // first action rendered, skip to next
2742 ++aRangeBegin;
2744 // render full middle actions
2745 while( aRangeBegin != aRangeEnd )
2746 rFunctor( *aRangeBegin++ );
2748 if( aRangeEnd == rEnd ||
2749 aRangeEnd->mnOrigIndex > nEndIndex )
2751 // aRangeEnd denotes end of action vector,
2753 // or
2755 // nEndIndex references something _after_
2756 // aRangeBegin, but _before_ aRangeEnd
2758 // either way: no partial action left
2759 return rFunctor.result();
2762 aSubset.mnSubsetBegin = 0;
2763 aSubset.mnSubsetEnd = nEndIndex - aRangeEnd->mnOrigIndex;
2765 ENSURE_OR_RETURN_FALSE(aSubset.mnSubsetEnd >= 0,
2766 "ImplRenderer::forSubsetRange(): Invalid indices" );
2768 rFunctor( *aRangeEnd, aSubset );
2771 return rFunctor.result();
2775 bool ImplRenderer::getSubsetIndices( sal_Int32& io_rStartIndex,
2776 sal_Int32& io_rEndIndex,
2777 ActionVector::const_iterator& o_rRangeBegin,
2778 ActionVector::const_iterator& o_rRangeEnd ) const
2780 ENSURE_OR_RETURN_FALSE( io_rStartIndex<=io_rEndIndex,
2781 "ImplRenderer::getSubsetIndices(): invalid action range" );
2783 ENSURE_OR_RETURN_FALSE( !maActions.empty(),
2784 "ImplRenderer::getSubsetIndices(): no actions to render" );
2786 const sal_Int32 nMinActionIndex( maActions.front().mnOrigIndex );
2787 const sal_Int32 nMaxActionIndex( maActions.back().mnOrigIndex +
2788 maActions.back().mpAction->getActionCount() );
2790 // clip given range to permissible values (there might be
2791 // ranges before and behind the valid indices)
2792 io_rStartIndex = std::max( nMinActionIndex,
2793 io_rStartIndex );
2794 io_rEndIndex = std::min( nMaxActionIndex,
2795 io_rEndIndex );
2797 if( io_rStartIndex == io_rEndIndex ||
2798 io_rStartIndex > io_rEndIndex )
2800 // empty range, don't render anything. The second
2801 // condition e.g. happens if the requested range lies
2802 // fully before or behind the valid action indices.
2803 return false;
2807 const ActionVector::const_iterator aBegin( maActions.begin() );
2808 const ActionVector::const_iterator aEnd( maActions.end() );
2811 // find start and end action
2812 // =========================
2813 o_rRangeBegin = std::lower_bound( aBegin, aEnd,
2814 MtfAction( std::shared_ptr<Action>(), io_rStartIndex ),
2815 UpperBoundActionIndexComparator() );
2816 o_rRangeEnd = std::lower_bound( aBegin, aEnd,
2817 MtfAction( std::shared_ptr<Action>(), io_rEndIndex ),
2818 UpperBoundActionIndexComparator() );
2819 return true;
2823 // Public methods
2826 ImplRenderer::ImplRenderer( const CanvasSharedPtr& rCanvas,
2827 const GDIMetaFile& rMtf,
2828 const Parameters& rParams )
2829 : CanvasGraphicHelper(rCanvas)
2830 , nFrameLeft(0)
2831 , nFrameTop(0)
2832 , nFrameRight(0)
2833 , nFrameBottom(0)
2834 , nPixX(0)
2835 , nPixY(0)
2836 , nMmX(0)
2837 , nMmY(0)
2839 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::ImplRenderer(mtf)" );
2841 OSL_ENSURE( rCanvas && rCanvas->getUNOCanvas().is(),
2842 "ImplRenderer::ImplRenderer(): Invalid canvas" );
2843 OSL_ENSURE( rCanvas->getUNOCanvas()->getDevice().is(),
2844 "ImplRenderer::ImplRenderer(): Invalid graphic device" );
2846 // make sure canvas and graphic device are valid; action
2847 // creation don't check that every time
2848 if( !rCanvas ||
2849 !rCanvas->getUNOCanvas().is() ||
2850 !rCanvas->getUNOCanvas()->getDevice().is() )
2852 // leave actions empty
2853 return;
2856 VectorOfOutDevStates aStateStack;
2858 ScopedVclPtrInstance< VirtualDevice > aVDev;
2859 aVDev->EnableOutput( false );
2861 // Setup VDev for state tracking and mapping
2862 // =========================================
2864 aVDev->SetMapMode( rMtf.GetPrefMapMode() );
2866 const Size aMtfSize( rMtf.GetPrefSize() );
2867 const Size aMtfSizePixPre( aVDev->LogicToPixel( aMtfSize,
2868 rMtf.GetPrefMapMode() ) );
2870 // #i44110# correct null-sized output - there are shapes
2871 // which have zero size in at least one dimension
2872 // Remark the 1L cannot be replaced, that would cause max to compare long/int
2873 const Size aMtfSizePix( std::max( aMtfSizePixPre.Width(), ::tools::Long(1) ),
2874 std::max( aMtfSizePixPre.Height(), ::tools::Long(1) ) );
2876 sal_Int32 nCurrActions(0);
2877 ActionFactoryParameters aParms(aStateStack,
2878 rCanvas,
2879 *aVDev,
2880 rParams,
2881 nCurrActions );
2883 // init state stack
2884 aStateStack.clearStateStack();
2886 // Setup local state, such that the metafile renders
2887 // itself into a one-by-one square at the origin for
2888 // identity view and render transformations
2889 aStateStack.getState().transform.scale( 1.0 / aMtfSizePix.Width(),
2890 1.0 / aMtfSizePix.Height() );
2892 tools::calcLogic2PixelAffineTransform( aStateStack.getState().mapModeTransform,
2893 *aVDev );
2896 ::cppcanvas::internal::OutDevState& rState = aStateStack.getState();
2897 // setup default text color to black
2898 rState.textColor =
2899 rState.textFillColor =
2900 rState.textOverlineColor =
2901 rState.textLineColor = tools::intSRGBAToDoubleSequence( 0x000000FF );
2904 // apply overrides from the Parameters struct
2905 if( rParams.maFillColor )
2907 ::cppcanvas::internal::OutDevState& rState = aStateStack.getState();
2908 rState.isFillColorSet = true;
2909 rState.fillColor = tools::intSRGBAToDoubleSequence( *rParams.maFillColor );
2911 if( rParams.maLineColor )
2913 ::cppcanvas::internal::OutDevState& rState = aStateStack.getState();
2914 rState.isLineColorSet = true;
2915 rState.lineColor = tools::intSRGBAToDoubleSequence( *rParams.maLineColor );
2917 if( rParams.maTextColor )
2919 ::cppcanvas::internal::OutDevState& rState = aStateStack.getState();
2920 rState.isTextFillColorSet = true;
2921 rState.isTextOverlineColorSet = true;
2922 rState.isTextLineColorSet = true;
2923 rState.textColor =
2924 rState.textFillColor =
2925 rState.textOverlineColor =
2926 rState.textLineColor = tools::intSRGBAToDoubleSequence( *rParams.maTextColor );
2928 if( rParams.maFontName ||
2929 rParams.maFontWeight ||
2930 rParams.maFontLetterForm ||
2931 rParams.maFontUnderline.has_value() )
2933 ::cppcanvas::internal::OutDevState& rState = aStateStack.getState();
2935 rState.xFont = createFont( rState.fontRotation,
2936 vcl::Font(), // default font
2937 aParms );
2940 /* EMF+ */
2941 createActions( const_cast<GDIMetaFile&>(rMtf), // HACK(Q2):
2942 // we're
2943 // changing
2944 // the
2945 // current
2946 // action
2947 // in
2948 // createActions!
2949 aParms,
2950 true // TODO(P1): make subsettability configurable
2954 ImplRenderer::~ImplRenderer()
2958 bool ImplRenderer::drawSubset( sal_Int32 nStartIndex,
2959 sal_Int32 nEndIndex ) const
2961 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::drawSubset()" );
2963 ActionVector::const_iterator aRangeBegin;
2964 ActionVector::const_iterator aRangeEnd;
2968 if( !getSubsetIndices( nStartIndex, nEndIndex,
2969 aRangeBegin, aRangeEnd ) )
2970 return true; // nothing to render (but _that_ was successful)
2972 // now, aRangeBegin references the action in which the
2973 // subset rendering must start, and aRangeEnd references
2974 // the action in which the subset rendering must end (it
2975 // might also end right at the start of the referenced
2976 // action, such that zero of that action needs to be
2977 // rendered).
2980 // render subset of actions
2981 // ========================
2983 ::basegfx::B2DHomMatrix aMatrix = ::canvas::tools::getRenderStateTransform( getRenderState() );
2985 ActionRenderer aRenderer( aMatrix );
2987 return forSubsetRange( aRenderer,
2988 aRangeBegin,
2989 aRangeEnd,
2990 nStartIndex,
2991 nEndIndex,
2992 maActions.end() );
2994 catch( uno::Exception& )
2996 DBG_UNHANDLED_EXCEPTION("cppcanvas.emf");
2997 // convert error to return value
2998 return false;
3002 ::basegfx::B2DRange ImplRenderer::getSubsetArea( sal_Int32 nStartIndex,
3003 sal_Int32 nEndIndex ) const
3005 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::getSubsetArea()" );
3007 ActionVector::const_iterator aRangeBegin;
3008 ActionVector::const_iterator aRangeEnd;
3010 if( !getSubsetIndices( nStartIndex, nEndIndex,
3011 aRangeBegin, aRangeEnd ) )
3012 return ::basegfx::B2DRange(); // nothing to render -> empty range
3014 // now, aRangeBegin references the action in which the
3015 // subset querying must start, and aRangeEnd references
3016 // the action in which the subset querying must end (it
3017 // might also end right at the start of the referenced
3018 // action, such that zero of that action needs to be
3019 // queried).
3022 // query bounds for subset of actions
3023 // ==================================
3025 ::basegfx::B2DHomMatrix aMatrix = ::canvas::tools::getRenderStateTransform(
3026 getRenderState() );
3028 AreaQuery aQuery( aMatrix );
3029 forSubsetRange( aQuery,
3030 aRangeBegin,
3031 aRangeEnd,
3032 nStartIndex,
3033 nEndIndex,
3034 maActions.end() );
3036 return aQuery.getBounds();
3039 bool ImplRenderer::draw() const
3041 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::draw()" );
3043 ::basegfx::B2DHomMatrix aMatrix = ::canvas::tools::getRenderStateTransform(
3044 getRenderState() );
3048 return std::for_each( maActions.begin(), maActions.end(), ActionRenderer( aMatrix ) ).result();
3050 catch( uno::Exception& )
3052 DBG_UNHANDLED_EXCEPTION( "cppcanvas.emf");
3053 return false;
3058 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */