Bump version to 21.06.18.1
[LibreOffice.git] / cppcanvas / source / mtfrenderer / implrenderer.cxx
blob0aecf685097bbae434c86755d477ea2b582b5877
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 <tools/diagnose_ex.h>
21 #include <tools/debug.hxx>
22 #include <vcl/svapp.hxx>
23 #include <comphelper/propertysequence.hxx>
24 #include <cppcanvas/canvas.hxx>
25 #include <com/sun/star/rendering/XGraphicDevice.hpp>
26 #include <com/sun/star/rendering/TexturingMode.hpp>
27 #include <com/sun/star/uno/Sequence.hxx>
28 #include <com/sun/star/rendering/PanoseProportion.hpp>
29 #include <com/sun/star/rendering/XCanvasFont.hpp>
30 #include <com/sun/star/rendering/XCanvas.hpp>
31 #include <com/sun/star/rendering/PathCapType.hpp>
32 #include <com/sun/star/rendering/PathJoinType.hpp>
33 #include <basegfx/utils/canvastools.hxx>
34 #include <basegfx/utils/gradienttools.hxx>
35 #include <basegfx/numeric/ftools.hxx>
36 #include <basegfx/polygon/b2dpolypolygontools.hxx>
37 #include <basegfx/polygon/b2dpolygontools.hxx>
38 #include <basegfx/polygon/b2dpolygon.hxx>
39 #include <basegfx/polygon/b2dpolypolygon.hxx>
40 #include <basegfx/matrix/b2dhommatrix.hxx>
41 #include <basegfx/vector/b2dsize.hxx>
42 #include <basegfx/range/b2drectangle.hxx>
43 #include <basegfx/point/b2dpoint.hxx>
44 #include <basegfx/tuple/b2dtuple.hxx>
45 #include <basegfx/polygon/b2dpolygonclipper.hxx>
46 #include <canvas/canvastools.hxx>
47 #include <rtl/ustrbuf.hxx>
48 #include <vcl/canvastools.hxx>
49 #include <vcl/gdimtf.hxx>
50 #include <vcl/metaact.hxx>
51 #include <vcl/virdev.hxx>
52 #include <vcl/metric.hxx>
53 #include <vcl/graphictools.hxx>
54 #include <vcl/BitmapPalette.hxx>
55 #include <tools/poly.hxx>
56 #include <i18nlangtag/languagetag.hxx>
57 #include <implrenderer.hxx>
58 #include <tools.hxx>
59 #include <outdevstate.hxx>
60 #include <action.hxx>
61 #include <sal/log.hxx>
62 #include "bitmapaction.hxx"
63 #include "lineaction.hxx"
64 #include "pointaction.hxx"
65 #include "polypolyaction.hxx"
66 #include "textaction.hxx"
67 #include "transparencygroupaction.hxx"
68 #include <utility>
69 #include <vector>
70 #include <algorithm>
71 #include <memory>
72 #include "mtftools.hxx"
74 using namespace ::com::sun::star;
77 // free support functions
78 // ======================
79 namespace
81 template < class MetaActionType > void setStateColor( MetaActionType* pAct,
82 bool& rIsColorSet,
83 uno::Sequence< double >& rColorSequence,
84 const cppcanvas::CanvasSharedPtr& rCanvas )
86 rIsColorSet = pAct->IsSetting();
87 if (!rIsColorSet)
88 return;
90 ::Color aColor( pAct->GetColor() );
92 // force alpha part of color to
93 // opaque. transparent painting is done
94 // explicitly via MetaActionType::Transparent
95 aColor.SetTransparency(0);
96 //aColor.SetTransparency(128);
98 rColorSequence = vcl::unotools::colorToDoubleSequence(
99 aColor,
100 rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() );
103 void setupStrokeAttributes( rendering::StrokeAttributes& o_rStrokeAttributes,
104 const ::cppcanvas::internal::ActionFactoryParameters& rParms,
105 const LineInfo& rLineInfo )
107 const ::basegfx::B2DSize aWidth( rLineInfo.GetWidth(), 0 );
108 o_rStrokeAttributes.StrokeWidth =
109 (rParms.mrStates.getState().mapModeTransform * aWidth).getLength();
111 // setup reasonable defaults
112 o_rStrokeAttributes.MiterLimit = 15.0; // 1.0 was no good default; GDI+'s limit is 10.0, our's is 15.0
113 o_rStrokeAttributes.StartCapType = rendering::PathCapType::BUTT;
114 o_rStrokeAttributes.EndCapType = rendering::PathCapType::BUTT;
116 switch (rLineInfo.GetLineJoin())
118 case basegfx::B2DLineJoin::NONE:
119 o_rStrokeAttributes.JoinType = rendering::PathJoinType::NONE;
120 break;
121 case basegfx::B2DLineJoin::Bevel:
122 o_rStrokeAttributes.JoinType = rendering::PathJoinType::BEVEL;
123 break;
124 case basegfx::B2DLineJoin::Miter:
125 o_rStrokeAttributes.JoinType = rendering::PathJoinType::MITER;
126 break;
127 case basegfx::B2DLineJoin::Round:
128 o_rStrokeAttributes.JoinType = rendering::PathJoinType::ROUND;
129 break;
132 switch(rLineInfo.GetLineCap())
134 default: /* css::drawing::LineCap_BUTT */
136 o_rStrokeAttributes.StartCapType = rendering::PathCapType::BUTT;
137 o_rStrokeAttributes.EndCapType = rendering::PathCapType::BUTT;
138 break;
140 case css::drawing::LineCap_ROUND:
142 o_rStrokeAttributes.StartCapType = rendering::PathCapType::ROUND;
143 o_rStrokeAttributes.EndCapType = rendering::PathCapType::ROUND;
144 break;
146 case css::drawing::LineCap_SQUARE:
148 o_rStrokeAttributes.StartCapType = rendering::PathCapType::SQUARE;
149 o_rStrokeAttributes.EndCapType = rendering::PathCapType::SQUARE;
150 break;
154 if( LineStyle::Dash != rLineInfo.GetStyle() )
155 return;
157 const ::cppcanvas::internal::OutDevState& rState( rParms.mrStates.getState() );
159 // TODO(F1): Interpret OutDev::GetRefPoint() for the start of the dashing.
161 // interpret dash info only if explicitly enabled as
162 // style
163 const ::basegfx::B2DSize aDistance( rLineInfo.GetDistance(), 0 );
164 const double nDistance( (rState.mapModeTransform * aDistance).getLength() );
166 const ::basegfx::B2DSize aDashLen( rLineInfo.GetDashLen(), 0 );
167 const double nDashLen( (rState.mapModeTransform * aDashLen).getLength() );
169 const ::basegfx::B2DSize aDotLen( rLineInfo.GetDotLen(), 0 );
170 const double nDotLen( (rState.mapModeTransform * aDotLen).getLength() );
172 const sal_Int32 nNumArryEntries( 2*rLineInfo.GetDashCount() +
173 2*rLineInfo.GetDotCount() );
175 o_rStrokeAttributes.DashArray.realloc( nNumArryEntries );
176 double* pDashArray = o_rStrokeAttributes.DashArray.getArray();
179 // iteratively fill dash array, first with dashes, then
180 // with dots.
183 sal_Int32 nCurrEntry=0;
185 for( sal_Int32 i=0; i<rLineInfo.GetDashCount(); ++i )
187 pDashArray[nCurrEntry++] = nDashLen;
188 pDashArray[nCurrEntry++] = nDistance;
190 for( sal_Int32 i=0; i<rLineInfo.GetDotCount(); ++i )
192 pDashArray[nCurrEntry++] = nDotLen;
193 pDashArray[nCurrEntry++] = nDistance;
198 /** Create masked BitmapEx, where the white areas of rBitmap are
199 transparent, and the other appear in rMaskColor.
201 BitmapEx createMaskBmpEx( const Bitmap& rBitmap,
202 const ::Color& rMaskColor )
204 const ::Color aWhite( COL_WHITE );
205 BitmapPalette aBiLevelPalette(2);
206 aBiLevelPalette[0] = aWhite;
207 aBiLevelPalette[1] = rMaskColor;
209 Bitmap aMask( rBitmap.CreateMask( aWhite ));
210 Bitmap aSolid( rBitmap.GetSizePixel(),
212 &aBiLevelPalette );
213 aSolid.Erase( rMaskColor );
215 return BitmapEx( aSolid, aMask );
218 OUString convertToLocalizedNumerals(const OUString& rStr,
219 LanguageType eTextLanguage)
221 OUStringBuffer aBuf(rStr);
222 for (sal_Int32 i = 0; i < aBuf.getLength(); ++i)
224 sal_Unicode nChar = aBuf[i];
225 if (nChar >= '0' && nChar <= '9')
226 aBuf[i] = GetLocalizedChar(nChar, eTextLanguage);
228 return aBuf.makeStringAndClear();
232 namespace cppcanvas::internal
234 // state stack manipulators
236 void VectorOfOutDevStates::clearStateStack()
238 m_aStates.clear();
239 const OutDevState aDefaultState;
240 m_aStates.push_back(aDefaultState);
243 OutDevState& VectorOfOutDevStates::getState()
245 return m_aStates.back();
248 const OutDevState& VectorOfOutDevStates::getState() const
250 return m_aStates.back();
253 void VectorOfOutDevStates::pushState(PushFlags nFlags)
255 m_aStates.push_back( getState() );
256 getState().pushFlags = nFlags;
259 void VectorOfOutDevStates::popState()
261 if( getState().pushFlags != PushFlags::ALL )
263 // a state is pushed which is incomplete, i.e. does not
264 // restore everything to the previous stack level when
265 // popped.
266 // That means, we take the old state, and restore every
267 // OutDevState member whose flag is set, from the new to the
268 // old state. Then the new state gets overwritten by the
269 // calculated state
271 // preset to-be-calculated new state with old state
272 OutDevState aCalculatedNewState( getState() );
274 // selectively copy to-be-restored content over saved old
275 // state
276 m_aStates.pop_back();
278 const OutDevState& rNewState( getState() );
280 if( aCalculatedNewState.pushFlags & PushFlags::LINECOLOR )
282 aCalculatedNewState.lineColor = rNewState.lineColor;
283 aCalculatedNewState.isLineColorSet = rNewState.isLineColorSet;
286 if( aCalculatedNewState.pushFlags & PushFlags::FILLCOLOR )
288 aCalculatedNewState.fillColor = rNewState.fillColor;
289 aCalculatedNewState.isFillColorSet = rNewState.isFillColorSet;
292 if( aCalculatedNewState.pushFlags & PushFlags::FONT )
294 aCalculatedNewState.xFont = rNewState.xFont;
295 aCalculatedNewState.fontRotation = rNewState.fontRotation;
296 aCalculatedNewState.textReliefStyle = rNewState.textReliefStyle;
297 aCalculatedNewState.textOverlineStyle = rNewState.textOverlineStyle;
298 aCalculatedNewState.textUnderlineStyle = rNewState.textUnderlineStyle;
299 aCalculatedNewState.textStrikeoutStyle = rNewState.textStrikeoutStyle;
300 aCalculatedNewState.textEmphasisMark = rNewState.textEmphasisMark;
301 aCalculatedNewState.isTextEffectShadowSet = rNewState.isTextEffectShadowSet;
302 aCalculatedNewState.isTextWordUnderlineSet = rNewState.isTextWordUnderlineSet;
303 aCalculatedNewState.isTextOutlineModeSet = rNewState.isTextOutlineModeSet;
306 if( aCalculatedNewState.pushFlags & PushFlags::TEXTCOLOR )
308 aCalculatedNewState.textColor = rNewState.textColor;
311 if( aCalculatedNewState.pushFlags & PushFlags::MAPMODE )
313 aCalculatedNewState.mapModeTransform = rNewState.mapModeTransform;
316 if( aCalculatedNewState.pushFlags & PushFlags::CLIPREGION )
318 aCalculatedNewState.clip = rNewState.clip;
319 aCalculatedNewState.clipRect = rNewState.clipRect;
320 aCalculatedNewState.xClipPoly = rNewState.xClipPoly;
323 // TODO(F2): Raster ops NYI
324 // if( (aCalculatedNewState.pushFlags & PushFlags::RASTEROP) )
325 // {
326 // }
328 if( aCalculatedNewState.pushFlags & PushFlags::TEXTFILLCOLOR )
330 aCalculatedNewState.textFillColor = rNewState.textFillColor;
331 aCalculatedNewState.isTextFillColorSet = rNewState.isTextFillColorSet;
334 if( aCalculatedNewState.pushFlags & PushFlags::TEXTALIGN )
336 aCalculatedNewState.textReferencePoint = rNewState.textReferencePoint;
339 // TODO(F1): Refpoint handling NYI
340 // if( (aCalculatedNewState.pushFlags & PushFlags::REFPOINT) )
341 // {
342 // }
344 if( aCalculatedNewState.pushFlags & PushFlags::TEXTLINECOLOR )
346 aCalculatedNewState.textLineColor = rNewState.textLineColor;
347 aCalculatedNewState.isTextLineColorSet = rNewState.isTextLineColorSet;
350 if( aCalculatedNewState.pushFlags & PushFlags::OVERLINECOLOR )
352 aCalculatedNewState.textOverlineColor = rNewState.textOverlineColor;
353 aCalculatedNewState.isTextOverlineColorSet = rNewState.isTextOverlineColorSet;
356 if( aCalculatedNewState.pushFlags & PushFlags::TEXTLAYOUTMODE )
358 aCalculatedNewState.textAlignment = rNewState.textAlignment;
359 aCalculatedNewState.textDirection = rNewState.textDirection;
362 // TODO(F2): Text language handling NYI
363 // if( (aCalculatedNewState.pushFlags & PushFlags::TEXTLANGUAGE) )
364 // {
365 // }
367 // always copy push mode
368 aCalculatedNewState.pushFlags = rNewState.pushFlags;
370 // flush to stack
371 getState() = aCalculatedNewState;
373 else
375 m_aStates.pop_back();
379 bool ImplRenderer::createFillAndStroke( const ::basegfx::B2DPolyPolygon& rPolyPoly,
380 const ActionFactoryParameters& rParms )
382 const OutDevState& rState( rParms.mrStates.getState() );
383 if( (!rState.isLineColorSet &&
384 !rState.isFillColorSet) ||
385 (!rState.lineColor.hasElements() &&
386 !rState.fillColor.hasElements()) )
388 return false;
391 std::shared_ptr<Action> pPolyAction(
392 internal::PolyPolyActionFactory::createPolyPolyAction(
393 rPolyPoly, rParms.mrCanvas, rState ) );
395 if( pPolyAction )
397 maActions.emplace_back(
398 pPolyAction,
399 rParms.mrCurrActionIndex );
401 rParms.mrCurrActionIndex += pPolyAction->getActionCount()-1;
404 return true;
407 bool ImplRenderer::createFillAndStroke( const ::basegfx::B2DPolygon& rPoly,
408 const ActionFactoryParameters& rParms )
410 return createFillAndStroke( ::basegfx::B2DPolyPolygon( rPoly ),
411 rParms );
414 void ImplRenderer::skipContent( GDIMetaFile& rMtf,
415 const char* pCommentString,
416 sal_Int32& io_rCurrActionIndex )
418 ENSURE_OR_THROW( pCommentString,
419 "ImplRenderer::skipContent(): NULL string given" );
421 MetaAction* pCurrAct;
422 while( (pCurrAct=rMtf.NextAction()) != nullptr )
424 // increment action index, we've skipped an action.
425 ++io_rCurrActionIndex;
427 if( pCurrAct->GetType() == MetaActionType::COMMENT &&
428 static_cast<MetaCommentAction*>(pCurrAct)->GetComment().equalsIgnoreAsciiCase(
429 pCommentString) )
431 // requested comment found, done
432 return;
436 // EOF
439 bool ImplRenderer::isActionContained( GDIMetaFile& rMtf,
440 const char* pCommentString,
441 MetaActionType nType )
443 ENSURE_OR_THROW( pCommentString,
444 "ImplRenderer::isActionContained(): NULL string given" );
446 bool bRet( false );
448 // at least _one_ call to GDIMetaFile::NextAction() is
449 // executed
450 size_t nPos( 1 );
452 MetaAction* pCurrAct;
453 while( (pCurrAct=rMtf.NextAction()) != nullptr )
455 if( pCurrAct->GetType() == nType )
457 bRet = true; // action type found
458 break;
461 if( pCurrAct->GetType() == MetaActionType::COMMENT &&
462 static_cast<MetaCommentAction*>(pCurrAct)->GetComment().equalsIgnoreAsciiCase(
463 pCommentString) )
465 // delimiting end comment found, done
466 bRet = false; // not yet found
467 break;
470 ++nPos;
473 // rewind metafile to previous position (this method must
474 // not change the current metaaction)
475 while( nPos-- )
476 rMtf.WindPrev();
478 if( !pCurrAct )
480 // EOF, and not yet found
481 bRet = false;
484 return bRet;
487 void ImplRenderer::createGradientAction( const ::tools::PolyPolygon& rPoly,
488 const ::Gradient& rGradient,
489 const ActionFactoryParameters& rParms,
490 bool bIsPolygonRectangle,
491 bool bSubsettableActions )
493 DBG_TESTSOLARMUTEX();
495 ::basegfx::B2DPolyPolygon aDevicePoly( rPoly.getB2DPolyPolygon() );
496 aDevicePoly.transform( rParms.mrStates.getState().mapModeTransform );
498 // decide, whether this gradient can be rendered natively
499 // by the canvas, or must be emulated via VCL gradient
500 // action extraction.
501 const sal_uInt16 nSteps( rGradient.GetSteps() );
503 if( // step count is infinite, can use native canvas
504 // gradients here
505 nSteps == 0 ||
506 // step count is sufficiently high, such that no
507 // discernible difference should be visible.
508 nSteps > 64 )
510 uno::Reference< lang::XMultiServiceFactory> xFactory(
511 rParms.mrCanvas->getUNOCanvas()->getDevice()->getParametricPolyPolygonFactory() );
513 if( xFactory.is() )
515 rendering::Texture aTexture;
517 aTexture.RepeatModeX = rendering::TexturingMode::CLAMP;
518 aTexture.RepeatModeY = rendering::TexturingMode::CLAMP;
519 aTexture.Alpha = 1.0;
522 // setup start/end color values
525 // scale color coefficients with gradient intensities
526 const sal_uInt16 nStartIntensity( rGradient.GetStartIntensity() );
527 ::Color aVCLStartColor( rGradient.GetStartColor() );
528 aVCLStartColor.SetRed( static_cast<sal_uInt8>(aVCLStartColor.GetRed() * nStartIntensity / 100) );
529 aVCLStartColor.SetGreen( static_cast<sal_uInt8>(aVCLStartColor.GetGreen() * nStartIntensity / 100) );
530 aVCLStartColor.SetBlue( static_cast<sal_uInt8>(aVCLStartColor.GetBlue() * nStartIntensity / 100) );
532 const sal_uInt16 nEndIntensity( rGradient.GetEndIntensity() );
533 ::Color aVCLEndColor( rGradient.GetEndColor() );
534 aVCLEndColor.SetRed( static_cast<sal_uInt8>(aVCLEndColor.GetRed() * nEndIntensity / 100) );
535 aVCLEndColor.SetGreen( static_cast<sal_uInt8>(aVCLEndColor.GetGreen() * nEndIntensity / 100) );
536 aVCLEndColor.SetBlue( static_cast<sal_uInt8>(aVCLEndColor.GetBlue() * nEndIntensity / 100) );
538 uno::Reference<rendering::XColorSpace> xColorSpace(
539 rParms.mrCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace());
540 const uno::Sequence< double > aStartColor(
541 vcl::unotools::colorToDoubleSequence( aVCLStartColor,
542 xColorSpace ));
543 const uno::Sequence< double > aEndColor(
544 vcl::unotools::colorToDoubleSequence( aVCLEndColor,
545 xColorSpace ));
547 uno::Sequence< uno::Sequence < double > > aColors(2);
548 uno::Sequence< double > aStops(2);
550 if( rGradient.GetStyle() == GradientStyle::Axial )
552 aStops.realloc(3);
553 aColors.realloc(3);
555 aStops[0] = 0.0;
556 aStops[1] = 0.5;
557 aStops[2] = 1.0;
559 aColors[0] = aEndColor;
560 aColors[1] = aStartColor;
561 aColors[2] = aEndColor;
563 else
565 aStops[0] = 0.0;
566 aStops[1] = 1.0;
568 aColors[0] = aStartColor;
569 aColors[1] = aEndColor;
572 const ::basegfx::B2DRectangle aBounds(
573 ::basegfx::utils::getRange(aDevicePoly) );
574 const ::basegfx::B2DVector aOffset(
575 rGradient.GetOfsX() / 100.0,
576 rGradient.GetOfsY() / 100.0);
577 double fRotation( rGradient.GetAngle().get() * M_PI / 1800.0 );
578 const double fBorder( rGradient.GetBorder() / 100.0 );
580 basegfx::B2DHomMatrix aRot90;
581 aRot90.rotate(M_PI_2);
583 basegfx::ODFGradientInfo aGradInfo;
584 OUString aGradientService;
585 switch( rGradient.GetStyle() )
587 case GradientStyle::Linear:
588 aGradInfo = basegfx::utils::createLinearODFGradientInfo(
589 aBounds,
590 nSteps,
591 fBorder,
592 fRotation);
593 // map ODF to svg gradient orientation - x
594 // instead of y direction
595 aGradInfo.setTextureTransform(aGradInfo.getTextureTransform() * aRot90);
596 aGradientService = "LinearGradient";
597 break;
599 case GradientStyle::Axial:
601 // Adapt the border so that it is suitable
602 // for the axial gradient. An axial
603 // gradient consists of two linear
604 // gradients. Each of those covers half
605 // of the total size. In order to
606 // compensate for the condensed display of
607 // the linear gradients, we have to
608 // enlarge the area taken up by the actual
609 // gradient (1-fBorder). After that we
610 // have to turn the result back into a
611 // border value, hence the second (left
612 // most 1-...
613 const double fAxialBorder (1-2*(1-fBorder));
614 aGradInfo = basegfx::utils::createAxialODFGradientInfo(
615 aBounds,
616 nSteps,
617 fAxialBorder,
618 fRotation);
619 // map ODF to svg gradient orientation - x
620 // instead of y direction
621 aGradInfo.setTextureTransform(aGradInfo.getTextureTransform() * aRot90);
623 // map ODF axial gradient to 3-stop linear
624 // gradient - shift left by 0.5
625 basegfx::B2DHomMatrix aShift;
627 aShift.translate(-0.5,0);
628 aGradInfo.setTextureTransform(aGradInfo.getTextureTransform() * aShift);
629 aGradientService = "LinearGradient";
630 break;
633 case GradientStyle::Radial:
634 aGradInfo = basegfx::utils::createRadialODFGradientInfo(
635 aBounds,
636 aOffset,
637 nSteps,
638 fBorder);
639 aGradientService = "EllipticalGradient";
640 break;
642 case GradientStyle::Elliptical:
643 aGradInfo = basegfx::utils::createEllipticalODFGradientInfo(
644 aBounds,
645 aOffset,
646 nSteps,
647 fBorder,
648 fRotation);
649 aGradientService = "EllipticalGradient";
650 break;
652 case GradientStyle::Square:
653 aGradInfo = basegfx::utils::createSquareODFGradientInfo(
654 aBounds,
655 aOffset,
656 nSteps,
657 fBorder,
658 fRotation);
659 aGradientService = "RectangularGradient";
660 break;
662 case GradientStyle::Rect:
663 aGradInfo = basegfx::utils::createRectangularODFGradientInfo(
664 aBounds,
665 aOffset,
666 nSteps,
667 fBorder,
668 fRotation);
669 aGradientService = "RectangularGradient";
670 break;
672 default:
673 ENSURE_OR_THROW( false,
674 "ImplRenderer::createGradientAction(): Unexpected gradient type" );
675 break;
678 ::basegfx::unotools::affineMatrixFromHomMatrix( aTexture.AffineTransform,
679 aGradInfo.getTextureTransform() );
681 uno::Sequence<uno::Any> args(comphelper::InitAnyPropertySequence(
683 {"Colors", uno::Any(aColors)},
684 {"Stops", uno::Any(aStops)},
685 {"AspectRatio", uno::Any(aGradInfo.getAspectRatio())},
686 }));
687 aTexture.Gradient.set(
688 xFactory->createInstanceWithArguments(aGradientService,
689 args),
690 uno::UNO_QUERY);
691 if( aTexture.Gradient.is() )
693 std::shared_ptr<Action> pPolyAction(
694 internal::PolyPolyActionFactory::createPolyPolyAction(
695 aDevicePoly,
696 rParms.mrCanvas,
697 rParms.mrStates.getState(),
698 aTexture ) );
700 if( pPolyAction )
702 maActions.emplace_back(
703 pPolyAction,
704 rParms.mrCurrActionIndex );
706 rParms.mrCurrActionIndex += pPolyAction->getActionCount()-1;
709 // done, using native gradients
710 return;
715 // cannot currently use native canvas gradients, as a
716 // finite step size is given (this funny feature is not
717 // supported by the XCanvas API)
718 rParms.mrStates.pushState(PushFlags::ALL);
720 if( !bIsPolygonRectangle )
722 // only clip, if given polygon is not a rectangle in
723 // the first place (the gradient is always limited to
724 // the given bound rect)
725 updateClipping(
726 aDevicePoly,
727 rParms,
728 true );
731 GDIMetaFile aTmpMtf;
732 rParms.mrVDev.AddGradientActions( rPoly.GetBoundRect(),
733 rGradient,
734 aTmpMtf );
736 createActions( aTmpMtf, rParms, bSubsettableActions );
738 rParms.mrStates.popState();
741 uno::Reference< rendering::XCanvasFont > ImplRenderer::createFont( double& o_rFontRotation,
742 const vcl::Font& rFont,
743 const ActionFactoryParameters& rParms )
745 rendering::FontRequest aFontRequest;
747 if( rParms.mrParms.maFontName )
748 aFontRequest.FontDescription.FamilyName = *rParms.mrParms.maFontName;
749 else
750 aFontRequest.FontDescription.FamilyName = rFont.GetFamilyName();
752 aFontRequest.FontDescription.StyleName = rFont.GetStyleName();
754 aFontRequest.FontDescription.IsSymbolFont = (rFont.GetCharSet() == RTL_TEXTENCODING_SYMBOL) ? util::TriState_YES : util::TriState_NO;
755 aFontRequest.FontDescription.IsVertical = rFont.IsVertical() ? util::TriState_YES : util::TriState_NO;
757 // TODO(F2): improve vclenum->panose conversion
758 aFontRequest.FontDescription.FontDescription.Weight =
759 rParms.mrParms.maFontWeight ?
760 *rParms.mrParms.maFontWeight :
761 ::canvas::tools::numeric_cast<sal_Int8>( ::basegfx::fround( rFont.GetWeight() ) );
762 aFontRequest.FontDescription.FontDescription.Letterform =
763 rParms.mrParms.maFontLetterForm ?
764 *rParms.mrParms.maFontLetterForm :
765 (rFont.GetItalic() == ITALIC_NONE) ? 0 : 9;
766 aFontRequest.FontDescription.FontDescription.Proportion =
767 (rFont.GetPitch() == PITCH_FIXED)
768 ? rendering::PanoseProportion::MONO_SPACED
769 : rendering::PanoseProportion::ANYTHING;
771 LanguageType aLang = rFont.GetLanguage();
772 aFontRequest.Locale = LanguageTag::convertToLocale( aLang, false);
774 // setup state-local text transformation,
775 // if the font be rotated
776 const short nFontAngle( rFont.GetOrientation() );
777 if( nFontAngle != 0 )
779 // set to unity transform rotated by font angle
780 const double nAngle( nFontAngle * (F_PI / 1800.0) );
781 o_rFontRotation = -nAngle;
783 else
785 o_rFontRotation = 0.0;
788 geometry::Matrix2D aFontMatrix;
789 ::canvas::tools::setIdentityMatrix2D( aFontMatrix );
791 // TODO(F2): use correct scale direction, font
792 // height might be width or anything else
794 // TODO(Q3): This code smells of programming by
795 // coincidence (the next two if statements)
797 ::Size rFontSizeLog( rFont.GetFontSize() );
799 if (rFontSizeLog.Height() == 0)
801 // guess 16 pixel (as in VCL)
802 rFontSizeLog = ::Size(0, 16);
804 // convert to target MapUnit if not pixels
805 rFontSizeLog = OutputDevice::LogicToLogic(rFontSizeLog, MapMode(MapUnit::MapPixel), rParms.mrVDev.GetMapMode());
808 const sal_Int32 nFontWidthLog = rFontSizeLog.Width();
809 if( nFontWidthLog != 0 )
811 vcl::Font aTestFont = rFont;
812 aTestFont.SetAverageFontWidth( 0 );
813 sal_Int32 nNormalWidth = rParms.mrVDev.GetFontMetric( aTestFont ).GetAverageFontWidth();
814 if( nNormalWidth != nFontWidthLog )
815 if( nNormalWidth )
816 aFontMatrix.m00 = static_cast<double>(nFontWidthLog) / nNormalWidth;
819 // #i52608# apply map mode scale also to font matrix - an
820 // anisotrophic mapmode must be reflected in an
821 // anisotrophic font matrix scale.
822 const OutDevState& rState( rParms.mrStates.getState() );
823 if( !::basegfx::fTools::equal(
824 rState.mapModeTransform.get(0,0),
825 rState.mapModeTransform.get(1,1)) )
827 const double nScaleX( rState.mapModeTransform.get(0,0) );
828 const double nScaleY( rState.mapModeTransform.get(1,1) );
830 // note: no reason to check for division by zero, we
831 // always have the value closer (or equal) to zero as
832 // the nominator.
833 if( fabs(nScaleX) < fabs(nScaleY) )
834 aFontMatrix.m00 *= nScaleX / nScaleY;
835 else
836 aFontMatrix.m11 *= nScaleY / nScaleX;
838 aFontRequest.CellSize = (rState.mapModeTransform * vcl::unotools::b2DSizeFromSize(rFontSizeLog)).getY();
840 if (rFont.GetEmphasisMark() != FontEmphasisMark::NONE)
842 uno::Sequence< beans::PropertyValue > aProperties(1);
843 aProperties[0].Name = "EmphasisMark";
844 aProperties[0].Value <<= sal_uInt32(rFont.GetEmphasisMark());
845 return rParms.mrCanvas->getUNOCanvas()->createFont(aFontRequest,
846 aProperties,
847 aFontMatrix);
850 return rParms.mrCanvas->getUNOCanvas()->createFont( aFontRequest,
851 uno::Sequence< beans::PropertyValue >(),
852 aFontMatrix );
855 // create text effects such as shadow/relief/embossed
856 void ImplRenderer::createTextAction( const ::Point& rStartPoint,
857 const OUString& rString,
858 int nIndex,
859 int nLength,
860 const ::tools::Long* pCharWidths,
861 const ActionFactoryParameters& rParms,
862 bool bSubsettableActions )
864 ENSURE_OR_THROW( nIndex >= 0 && nLength <= rString.getLength() + nIndex,
865 "ImplRenderer::createTextWithEffectsAction(): Invalid text index" );
867 if( !nLength )
868 return; // zero-length text, no visible output
870 const OutDevState& rState( rParms.mrStates.getState() );
872 // TODO(F2): implement all text effects
873 // if( rState.textAlignment ); // TODO(F2): NYI
875 ::Color aTextFillColor( COL_AUTO );
876 ::Color aShadowColor( COL_AUTO );
877 ::Color aReliefColor( COL_AUTO );
878 ::Size aShadowOffset;
879 ::Size aReliefOffset;
881 uno::Reference<rendering::XColorSpace> xColorSpace(
882 rParms.mrCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() );
884 if( rState.isTextEffectShadowSet )
886 // calculate shadow offset (similar to outdev3.cxx)
887 // TODO(F3): better match with outdev3.cxx
888 sal_Int32 nShadowOffset = static_cast<sal_Int32>(1.5 + ((rParms.mrVDev.GetFont().GetFontHeight()-24.0)/24.0));
889 if( nShadowOffset < 1 )
890 nShadowOffset = 1;
892 aShadowOffset.setWidth( nShadowOffset );
893 aShadowOffset.setHeight( nShadowOffset );
895 // determine shadow color (from outdev3.cxx)
896 ::Color aTextColor = vcl::unotools::doubleSequenceToColor(
897 rState.textColor, xColorSpace );
898 bool bIsDark = (aTextColor == COL_BLACK)
899 || (aTextColor.GetLuminance() < 8);
901 aShadowColor = bIsDark ? COL_LIGHTGRAY : COL_BLACK;
902 aShadowColor.SetTransparency( aTextColor.GetTransparency() );
905 if( rState.textReliefStyle != FontRelief::NONE )
907 // calculate relief offset (similar to outdev3.cxx)
908 sal_Int32 nReliefOffset = rParms.mrVDev.PixelToLogic( Size( 1, 1 ) ).Height();
909 nReliefOffset += nReliefOffset/2;
910 if( nReliefOffset < 1 )
911 nReliefOffset = 1;
913 if( rState.textReliefStyle == FontRelief::Engraved )
914 nReliefOffset = -nReliefOffset;
916 aReliefOffset.setWidth( nReliefOffset );
917 aReliefOffset.setHeight( nReliefOffset );
919 // determine relief color (from outdev3.cxx)
920 ::Color aTextColor = vcl::unotools::doubleSequenceToColor(
921 rState.textColor, xColorSpace );
923 aReliefColor = COL_LIGHTGRAY;
925 // we don't have an automatic color, so black is always
926 // drawn on white (literally copied from
927 // vcl/source/gdi/outdev3.cxx)
928 if( aTextColor == COL_BLACK )
930 aTextColor = COL_WHITE;
931 rParms.mrStates.getState().textColor =
932 vcl::unotools::colorToDoubleSequence(
933 aTextColor, xColorSpace );
936 if( aTextColor == COL_WHITE )
937 aReliefColor = COL_BLACK;
938 aReliefColor.SetTransparency( aTextColor.GetTransparency() );
941 if (rState.isTextFillColorSet)
942 aTextFillColor = vcl::unotools::doubleSequenceToColor(rState.textFillColor, xColorSpace);
944 // create the actual text action
945 std::shared_ptr<Action> pTextAction(
946 TextActionFactory::createTextAction(
947 rStartPoint,
948 aReliefOffset,
949 aReliefColor,
950 aShadowOffset,
951 aShadowColor,
952 aTextFillColor,
953 rString,
954 nIndex,
955 nLength,
956 pCharWidths,
957 rParms.mrVDev,
958 rParms.mrCanvas,
959 rState,
960 rParms.mrParms,
961 bSubsettableActions ) );
963 std::shared_ptr<Action> pStrikeoutTextAction;
965 if ( rState.textStrikeoutStyle == STRIKEOUT_X || rState.textStrikeoutStyle == STRIKEOUT_SLASH )
967 ::tools::Long nWidth = rParms.mrVDev.GetTextWidth( rString,nIndex,nLength );
969 sal_Unicode pChars[4];
970 if ( rState.textStrikeoutStyle == STRIKEOUT_X )
971 pChars[0] = 'X';
972 else
973 pChars[0] = '/';
974 pChars[3]=pChars[2]=pChars[1]=pChars[0];
976 ::tools::Long nStrikeoutWidth = (rParms.mrVDev.GetTextWidth(
977 OUString(pChars, SAL_N_ELEMENTS(pChars))) + 2) / 4;
979 if( nStrikeoutWidth <= 0 )
980 nStrikeoutWidth = 1;
982 ::tools::Long nMaxWidth = nStrikeoutWidth/2;
983 if ( nMaxWidth < 2 )
984 nMaxWidth = 2;
985 nMaxWidth += nWidth + 1;
987 ::tools::Long nFullStrikeoutWidth = 0;
988 OUStringBuffer aStrikeoutText;
989 while( (nFullStrikeoutWidth+=nStrikeoutWidth ) < nMaxWidth+1 )
990 aStrikeoutText.append(pChars[0]);
992 sal_Int32 nLen = aStrikeoutText.getLength();
994 if( nLen )
996 ::tools::Long nInterval = ( nWidth - nStrikeoutWidth * nLen ) / nLen;
997 nStrikeoutWidth += nInterval;
998 ::tools::Long* pStrikeoutCharWidths = new ::tools::Long[nLen];
1000 for ( int i = 0;i<nLen; i++)
1002 pStrikeoutCharWidths[i] = nStrikeoutWidth;
1005 for ( int i = 1;i< nLen; i++ )
1007 pStrikeoutCharWidths[ i ] += pStrikeoutCharWidths[ i-1 ];
1010 pStrikeoutTextAction =
1011 TextActionFactory::createTextAction(
1012 rStartPoint,
1013 aReliefOffset,
1014 aReliefColor,
1015 aShadowOffset,
1016 aShadowColor,
1017 aTextFillColor,
1018 aStrikeoutText.makeStringAndClear(),
1019 0/*nStartPos*/,
1020 nLen,
1021 pStrikeoutCharWidths,
1022 rParms.mrVDev,
1023 rParms.mrCanvas,
1024 rState,
1025 rParms.mrParms,
1026 bSubsettableActions ) ;
1030 if( !pTextAction )
1031 return;
1033 maActions.emplace_back(
1034 pTextAction,
1035 rParms.mrCurrActionIndex );
1037 if ( pStrikeoutTextAction )
1039 maActions.emplace_back(
1040 pStrikeoutTextAction,
1041 rParms.mrCurrActionIndex );
1044 rParms.mrCurrActionIndex += pTextAction->getActionCount()-1;
1047 void ImplRenderer::updateClipping( const ::basegfx::B2DPolyPolygon& rClipPoly,
1048 const ActionFactoryParameters& rParms,
1049 bool bIntersect )
1051 ::cppcanvas::internal::OutDevState& rState( rParms.mrStates.getState() );
1053 const bool bEmptyClipRect( rState.clipRect.IsEmpty() );
1054 const bool bEmptyClipPoly( rState.clip.count() == 0 );
1056 ENSURE_OR_THROW( bEmptyClipPoly || bEmptyClipRect,
1057 "ImplRenderer::updateClipping(): Clip rect and polygon are both set!" );
1059 if( !bIntersect ||
1060 (bEmptyClipRect && bEmptyClipPoly) )
1062 rState.clip = rClipPoly;
1064 else
1066 if( !bEmptyClipRect )
1068 // TODO(P3): Use Liang-Barsky polygon clip here,
1069 // after all, one object is just a rectangle!
1071 // convert rect to polygon beforehand, must revert
1072 // to general polygon clipping here.
1073 ::tools::Rectangle aRect = rState.clipRect;
1074 // #121100# VCL rectangular clips always
1075 // include one more pixel to the right
1076 // and the bottom
1077 aRect.AdjustRight(1);
1078 aRect.AdjustBottom(1);
1079 rState.clip = ::basegfx::B2DPolyPolygon(
1080 ::basegfx::utils::createPolygonFromRect(
1081 vcl::unotools::b2DRectangleFromRectangle(aRect) ) );
1084 // AW: Simplified
1085 rState.clip = basegfx::utils::clipPolyPolygonOnPolyPolygon(
1086 rClipPoly, rState.clip, true, false);
1089 // by now, our clip resides in the OutDevState::clip
1090 // poly-polygon.
1091 rState.clipRect.SetEmpty();
1093 if( rState.clip.count() == 0 )
1095 if( rState.clipRect.IsEmpty() )
1097 rState.xClipPoly.clear();
1099 else
1101 ::tools::Rectangle aRect = rState.clipRect;
1102 // #121100# VCL rectangular clips
1103 // always include one more pixel to
1104 // the right and the bottom
1105 aRect.AdjustRight(1);
1106 aRect.AdjustBottom(1);
1107 rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
1108 rParms.mrCanvas->getUNOCanvas()->getDevice(),
1109 ::basegfx::B2DPolyPolygon(
1110 ::basegfx::utils::createPolygonFromRect(
1111 vcl::unotools::b2DRectangleFromRectangle(aRect) ) ) );
1114 else
1116 rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
1117 rParms.mrCanvas->getUNOCanvas()->getDevice(),
1118 rState.clip );
1122 void ImplRenderer::updateClipping( const ::tools::Rectangle& rClipRect,
1123 const ActionFactoryParameters& rParms,
1124 bool bIntersect )
1126 ::cppcanvas::internal::OutDevState& rState( rParms.mrStates.getState() );
1128 const bool bEmptyClipRect( rState.clipRect.IsEmpty() );
1129 const bool bEmptyClipPoly( rState.clip.count() == 0 );
1131 ENSURE_OR_THROW( bEmptyClipPoly || bEmptyClipRect,
1132 "ImplRenderer::updateClipping(): Clip rect and polygon are both set!" );
1134 if( !bIntersect ||
1135 (bEmptyClipRect && bEmptyClipPoly) )
1137 rState.clipRect = rClipRect;
1138 rState.clip.clear();
1140 else if( bEmptyClipPoly )
1142 rState.clipRect.Intersection( rClipRect );
1143 rState.clip.clear();
1145 else
1147 // TODO(P3): Handle a fourth case here, when all clip
1148 // polygons are rectangular, once B2DMultiRange's
1149 // sweep line implementation is done.
1151 // general case: convert to polygon and clip
1154 // convert rect to polygon beforehand, must revert
1155 // to general polygon clipping here.
1156 ::basegfx::B2DPolyPolygon aClipPoly(
1157 ::basegfx::utils::createPolygonFromRect(
1158 vcl::unotools::b2DRectangleFromRectangle(rClipRect) ) );
1160 rState.clipRect.SetEmpty();
1162 // AW: Simplified
1163 rState.clip = basegfx::utils::clipPolyPolygonOnPolyPolygon(
1164 aClipPoly, rState.clip, true, false);
1167 if( rState.clip.count() == 0 )
1169 if( rState.clipRect.IsEmpty() )
1171 rState.xClipPoly.clear();
1173 else
1175 // #121100# VCL rectangular clips
1176 // always include one more pixel to
1177 // the right and the bottom
1178 ::tools::Rectangle aRect = rState.clipRect;
1179 aRect.AdjustRight(1);
1180 aRect.AdjustBottom(1);
1181 rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
1182 rParms.mrCanvas->getUNOCanvas()->getDevice(),
1183 ::basegfx::B2DPolyPolygon(
1184 ::basegfx::utils::createPolygonFromRect(
1185 vcl::unotools::b2DRectangleFromRectangle(aRect) ) ) );
1188 else
1190 rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
1191 rParms.mrCanvas->getUNOCanvas()->getDevice(),
1192 rState.clip );
1196 void ImplRenderer::createActions( GDIMetaFile& rMtf,
1197 const ActionFactoryParameters& rFactoryParms,
1198 bool bSubsettableActions )
1200 /* TODO(P2): interpret mtf-comments
1201 ================================
1203 - gradient fillings (do that via comments)
1205 - think about mapping. _If_ we do everything in logical
1206 coordinates (which would solve the probs for stroke
1207 widths and text offsets), then we would have to
1208 recalc scaling for every drawing operation. This is
1209 because the outdev map mode might change at any time.
1210 Also keep in mind, that, although we've double precision
1211 float arithmetic now, different offsets might still
1212 generate different roundings (aka
1213 'OutputDevice::SetPixelOffset())
1217 // alias common parameters
1218 VectorOfOutDevStates& rStates(rFactoryParms.mrStates);
1219 const CanvasSharedPtr& rCanvas(rFactoryParms.mrCanvas);
1220 ::VirtualDevice& rVDev(rFactoryParms.mrVDev);
1221 const Parameters& rParms(rFactoryParms.mrParms);
1222 sal_Int32& io_rCurrActionIndex(rFactoryParms.mrCurrActionIndex);
1225 // Loop over every metaaction
1226 // ==========================
1227 MetaAction* pCurrAct;
1229 // TODO(P1): think about caching
1230 for( pCurrAct=rMtf.FirstAction();
1231 pCurrAct;
1232 pCurrAct = rMtf.NextAction() )
1234 // execute every action, to keep VDev state up-to-date
1235 // currently used only for
1236 // - the map mode
1237 // - the line/fill color when processing a MetaActionType::Transparent
1238 // - SetFont to process font metric specific actions
1239 pCurrAct->Execute( &rVDev );
1241 SAL_INFO("cppcanvas.emf", "MTF\trecord type: 0x" << static_cast<sal_uInt16>(pCurrAct->GetType()) << " (" << static_cast<sal_uInt16>(pCurrAct->GetType()) << ")");
1243 switch( pCurrAct->GetType() )
1247 // In the first part of this monster-switch, we
1248 // handle all state-changing meta actions. These
1249 // are all handled locally.
1252 case MetaActionType::PUSH:
1254 MetaPushAction* pPushAction = static_cast<MetaPushAction*>(pCurrAct);
1255 rStates.pushState(pPushAction->GetFlags());
1257 break;
1259 case MetaActionType::POP:
1260 rStates.popState();
1261 break;
1263 case MetaActionType::TEXTLANGUAGE:
1264 case MetaActionType::REFPOINT:
1265 // handled via pCurrAct->Execute( &rVDev )
1266 break;
1268 case MetaActionType::MAPMODE:
1269 // modify current mapModeTransformation
1270 // transformation, such that subsequent
1271 // coordinates map correctly
1272 tools::calcLogic2PixelAffineTransform( rStates.getState().mapModeTransform,
1273 rVDev );
1274 break;
1276 // monitor clip regions, to assemble clip polygon on our own
1277 case MetaActionType::CLIPREGION:
1279 MetaClipRegionAction* pClipAction = static_cast<MetaClipRegionAction*>(pCurrAct);
1281 if( !pClipAction->IsClipping() )
1283 // clear clipping
1284 rStates.getState().clip.clear();
1286 else
1288 if( !pClipAction->GetRegion().HasPolyPolygonOrB2DPolyPolygon() )
1290 SAL_INFO( "cppcanvas.emf", "ImplRenderer::createActions(): non-polygonal clip "
1291 "region encountered, falling back to bounding box!" );
1293 // #121806# explicitly kept integer
1294 ::tools::Rectangle aClipRect(
1295 rVDev.LogicToPixel(
1296 pClipAction->GetRegion().GetBoundRect() ) );
1298 // intersect current clip with given rect
1299 updateClipping(
1300 aClipRect,
1301 rFactoryParms,
1302 false );
1304 else
1306 // set new clip polygon (don't intersect
1307 // with old one, just set it)
1309 // #121806# explicitly kept integer
1310 basegfx::B2DPolyPolygon aPolyPolygon(pClipAction->GetRegion().GetAsB2DPolyPolygon());
1312 aPolyPolygon.transform(rVDev.GetViewTransformation());
1313 updateClipping(
1314 aPolyPolygon,
1315 rFactoryParms,
1316 false );
1320 break;
1323 case MetaActionType::ISECTRECTCLIPREGION:
1325 MetaISectRectClipRegionAction* pClipAction = static_cast<MetaISectRectClipRegionAction*>(pCurrAct);
1327 // #121806# explicitly kept integer
1328 ::tools::Rectangle aClipRect(
1329 rVDev.LogicToPixel( pClipAction->GetRect() ) );
1331 // intersect current clip with given rect
1332 updateClipping(
1333 aClipRect,
1334 rFactoryParms,
1335 true );
1337 break;
1340 case MetaActionType::ISECTREGIONCLIPREGION:
1342 MetaISectRegionClipRegionAction* pClipAction = static_cast<MetaISectRegionClipRegionAction*>(pCurrAct);
1344 if( !pClipAction->GetRegion().HasPolyPolygonOrB2DPolyPolygon() )
1346 SAL_INFO( "cppcanvas.emf", "ImplRenderer::createActions(): non-polygonal clip "
1347 "region encountered, falling back to bounding box!" );
1349 // #121806# explicitly kept integer
1350 ::tools::Rectangle aClipRect(
1351 rVDev.LogicToPixel( pClipAction->GetRegion().GetBoundRect() ) );
1353 // intersect current clip with given rect
1354 updateClipping(
1355 aClipRect,
1356 rFactoryParms,
1357 true );
1359 else
1361 // intersect current clip with given clip polygon
1363 // #121806# explicitly kept integer
1364 basegfx::B2DPolyPolygon aPolyPolygon(pClipAction->GetRegion().GetAsB2DPolyPolygon());
1366 aPolyPolygon.transform(rVDev.GetViewTransformation());
1367 updateClipping(
1368 aPolyPolygon,
1369 rFactoryParms,
1370 true );
1373 break;
1376 case MetaActionType::MOVECLIPREGION:
1377 // TODO(F2): NYI
1378 break;
1380 case MetaActionType::LINECOLOR:
1381 if( !rParms.maLineColor )
1383 setStateColor( static_cast<MetaLineColorAction*>(pCurrAct),
1384 rStates.getState().isLineColorSet,
1385 rStates.getState().lineColor,
1386 rCanvas );
1388 else
1390 // #120994# Do switch on/off LineColor, even when an overriding one is set
1391 bool bSetting(static_cast<MetaLineColorAction*>(pCurrAct)->IsSetting());
1393 rStates.getState().isLineColorSet = bSetting;
1395 break;
1397 case MetaActionType::FILLCOLOR:
1398 if( !rParms.maFillColor )
1400 setStateColor( static_cast<MetaFillColorAction*>(pCurrAct),
1401 rStates.getState().isFillColorSet,
1402 rStates.getState().fillColor,
1403 rCanvas );
1405 else
1407 // #120994# Do switch on/off FillColor, even when an overriding one is set
1408 bool bSetting(static_cast<MetaFillColorAction*>(pCurrAct)->IsSetting());
1410 rStates.getState().isFillColorSet = bSetting;
1412 break;
1414 case MetaActionType::TEXTCOLOR:
1416 if( !rParms.maTextColor )
1418 // Text color is set unconditionally, thus, no
1419 // use of setStateColor here
1420 ::Color aColor( static_cast<MetaTextColorAction*>(pCurrAct)->GetColor() );
1422 // force alpha part of color to
1423 // opaque. transparent painting is done
1424 // explicitly via MetaActionType::Transparent
1425 aColor.SetTransparency(0);
1427 rStates.getState().textColor =
1428 vcl::unotools::colorToDoubleSequence(
1429 aColor,
1430 rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() );
1433 break;
1435 case MetaActionType::TEXTFILLCOLOR:
1436 if( !rParms.maTextColor )
1438 setStateColor( static_cast<MetaTextFillColorAction*>(pCurrAct),
1439 rStates.getState().isTextFillColorSet,
1440 rStates.getState().textFillColor,
1441 rCanvas );
1443 else
1445 // #120994# Do switch on/off TextFillColor, even when an overriding one is set
1446 bool bSetting(static_cast<MetaTextFillColorAction*>(pCurrAct)->IsSetting());
1448 rStates.getState().isTextFillColorSet = bSetting;
1450 break;
1452 case MetaActionType::TEXTLINECOLOR:
1453 if( !rParms.maTextColor )
1455 setStateColor( static_cast<MetaTextLineColorAction*>(pCurrAct),
1456 rStates.getState().isTextLineColorSet,
1457 rStates.getState().textLineColor,
1458 rCanvas );
1460 else
1462 // #120994# Do switch on/off TextLineColor, even when an overriding one is set
1463 bool bSetting(static_cast<MetaTextLineColorAction*>(pCurrAct)->IsSetting());
1465 rStates.getState().isTextLineColorSet = bSetting;
1467 break;
1469 case MetaActionType::OVERLINECOLOR:
1470 if( !rParms.maTextColor )
1472 setStateColor( static_cast<MetaOverlineColorAction*>(pCurrAct),
1473 rStates.getState().isTextOverlineColorSet,
1474 rStates.getState().textOverlineColor,
1475 rCanvas );
1477 else
1479 bool bSetting(static_cast<MetaOverlineColorAction*>(pCurrAct)->IsSetting());
1481 rStates.getState().isTextOverlineColorSet = bSetting;
1483 break;
1485 case MetaActionType::TEXTALIGN:
1487 ::cppcanvas::internal::OutDevState& rState = rStates.getState();
1488 const TextAlign eTextAlign( static_cast<MetaTextAlignAction*>(pCurrAct)->GetTextAlign() );
1490 rState.textReferencePoint = eTextAlign;
1492 break;
1494 case MetaActionType::FONT:
1496 ::cppcanvas::internal::OutDevState& rState = rStates.getState();
1497 const vcl::Font& rFont( static_cast<MetaFontAction*>(pCurrAct)->GetFont() );
1499 rState.xFont = createFont( rState.fontRotation,
1500 rFont,
1501 rFactoryParms );
1503 // TODO(Q2): define and use appropriate enumeration types
1504 rState.textReliefStyle = rFont.GetRelief();
1505 rState.textOverlineStyle = static_cast<sal_Int8>(rFont.GetOverline());
1506 rState.textUnderlineStyle = rParms.maFontUnderline ?
1507 (*rParms.maFontUnderline ? sal_Int8(LINESTYLE_SINGLE) : sal_Int8(LINESTYLE_NONE)) :
1508 static_cast<sal_Int8>(rFont.GetUnderline());
1509 rState.textStrikeoutStyle = static_cast<sal_Int8>(rFont.GetStrikeout());
1510 rState.textEmphasisMark = rFont.GetEmphasisMark();
1511 rState.isTextEffectShadowSet = rFont.IsShadow();
1512 rState.isTextWordUnderlineSet = rFont.IsWordLineMode();
1513 rState.isTextOutlineModeSet = rFont.IsOutline();
1515 break;
1517 case MetaActionType::RASTEROP:
1518 // TODO(F2): NYI
1519 break;
1521 case MetaActionType::LAYOUTMODE:
1523 // TODO(F2): A lot is missing here
1524 ComplexTextLayoutFlags nLayoutMode = static_cast<MetaLayoutModeAction*>(pCurrAct)->GetLayoutMode();
1525 ::cppcanvas::internal::OutDevState& rState = rStates.getState();
1527 ComplexTextLayoutFlags nBidiLayoutMode = nLayoutMode & (ComplexTextLayoutFlags::BiDiRtl|ComplexTextLayoutFlags::BiDiStrong);
1528 if( nBidiLayoutMode == ComplexTextLayoutFlags::Default)
1529 rState.textDirection = rendering::TextDirection::WEAK_LEFT_TO_RIGHT;
1530 else if( nBidiLayoutMode == ComplexTextLayoutFlags::BiDiStrong)
1531 rState.textDirection = rendering::TextDirection::STRONG_LEFT_TO_RIGHT;
1532 else if( nBidiLayoutMode == ComplexTextLayoutFlags::BiDiRtl)
1533 rState.textDirection = rendering::TextDirection::WEAK_RIGHT_TO_LEFT;
1534 else if( nBidiLayoutMode == (ComplexTextLayoutFlags::BiDiRtl | ComplexTextLayoutFlags::BiDiStrong))
1535 rState.textDirection = rendering::TextDirection::STRONG_RIGHT_TO_LEFT;
1537 rState.textAlignment = 0; // TODO(F2): rendering::TextAlignment::LEFT_ALIGNED;
1538 if( (nLayoutMode & (ComplexTextLayoutFlags::BiDiRtl | ComplexTextLayoutFlags::TextOriginRight) )
1539 && !(nLayoutMode & ComplexTextLayoutFlags::TextOriginLeft ) )
1541 rState.textAlignment = 1; // TODO(F2): rendering::TextAlignment::RIGHT_ALIGNED;
1544 break;
1547 // In the second part of this monster-switch, we
1548 // handle all recursing meta actions. These are the
1549 // ones generating a metafile by themselves, which is
1550 // then processed by recursively calling this method.
1553 case MetaActionType::GRADIENT:
1555 MetaGradientAction* pGradAct = static_cast<MetaGradientAction*>(pCurrAct);
1556 createGradientAction( ::tools::Polygon( pGradAct->GetRect() ),
1557 pGradAct->GetGradient(),
1558 rFactoryParms,
1559 true,
1560 bSubsettableActions );
1562 break;
1564 case MetaActionType::HATCH:
1566 // TODO(F2): use native Canvas hatches here
1567 GDIMetaFile aTmpMtf;
1569 rVDev.AddHatchActions( static_cast<MetaHatchAction*>(pCurrAct)->GetPolyPolygon(),
1570 static_cast<MetaHatchAction*>(pCurrAct)->GetHatch(),
1571 aTmpMtf );
1572 createActions( aTmpMtf, rFactoryParms,
1573 bSubsettableActions );
1575 break;
1577 case MetaActionType::EPS:
1579 MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pCurrAct);
1580 const GDIMetaFile& rSubstitute = pAct->GetSubstitute();
1582 // #121806# explicitly kept integer
1583 const Size aMtfSize( rSubstitute.GetPrefSize() );
1584 const Size aMtfSizePixPre( rVDev.LogicToPixel( aMtfSize,
1585 rSubstitute.GetPrefMapMode() ) );
1587 // #i44110# correct null-sized output - there
1588 // are metafiles which have zero size in at
1589 // least one dimension
1591 // Remark the 1L cannot be replaced, that would cause max to compare long/int
1592 const Size aMtfSizePix( std::max( aMtfSizePixPre.Width(), ::tools::Long(1) ),
1593 std::max( aMtfSizePixPre.Height(), ::tools::Long(1) ) );
1595 // Setup local transform, such that the
1596 // metafile renders itself into the given
1597 // output rectangle
1598 rStates.pushState(PushFlags::ALL);
1600 rVDev.Push();
1601 rVDev.SetMapMode( rSubstitute.GetPrefMapMode() );
1603 const ::Point& rPos( rVDev.LogicToPixel( pAct->GetPoint() ) );
1604 const ::Size& rSize( rVDev.LogicToPixel( pAct->GetSize() ) );
1606 rStates.getState().transform.translate( rPos.X(),
1607 rPos.Y() );
1608 rStates.getState().transform.scale( static_cast<double>(rSize.Width()) / aMtfSizePix.Width(),
1609 static_cast<double>(rSize.Height()) / aMtfSizePix.Height() );
1611 createActions( const_cast<GDIMetaFile&>(pAct->GetSubstitute()),
1612 rFactoryParms,
1613 bSubsettableActions );
1615 rVDev.Pop();
1616 rStates.popState();
1618 break;
1620 // handle metafile comments, to retrieve
1621 // meta-information for gradients, fills and
1622 // strokes. May skip actions, and may recurse.
1623 case MetaActionType::COMMENT:
1625 MetaCommentAction* pAct = static_cast<MetaCommentAction*>(pCurrAct);
1627 // Handle gradients
1628 if (pAct->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN"))
1630 MetaGradientExAction* pGradAction = nullptr;
1631 bool bDone( false );
1632 while( !bDone )
1634 pCurrAct=rMtf.NextAction();
1635 if (!pCurrAct)
1636 break;
1637 switch( pCurrAct->GetType() )
1639 // extract gradient info
1640 case MetaActionType::GRADIENTEX:
1641 pGradAction = static_cast<MetaGradientExAction*>(pCurrAct);
1642 break;
1644 // skip broken-down rendering, output gradient when sequence is ended
1645 case MetaActionType::COMMENT:
1646 if( static_cast<MetaCommentAction*>(pCurrAct)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END") )
1648 bDone = true;
1650 if( pGradAction )
1652 createGradientAction( pGradAction->GetPolyPolygon(),
1653 pGradAction->GetGradient(),
1654 rFactoryParms,
1655 false,
1656 bSubsettableActions );
1659 break;
1660 default: break;
1664 // TODO(P2): Handle drawing layer strokes, via
1665 // XPATHSTROKE_SEQ_BEGIN comment
1667 // Handle drawing layer fills
1668 else if( pAct->GetComment() == "XPATHFILL_SEQ_BEGIN" )
1670 const sal_uInt8* pData = pAct->GetData();
1671 if ( pData )
1673 SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pData), pAct->GetDataSize(), StreamMode::READ );
1675 SvtGraphicFill aFill;
1676 ReadSvtGraphicFill( aMemStm, aFill );
1678 // TODO(P2): Also handle gradients and
1679 // hatches like this
1681 // only evaluate comment for pure
1682 // bitmap fills. If a transparency
1683 // gradient is involved (denoted by
1684 // the FloatTransparent action), take
1685 // the normal meta actions.
1686 if( aFill.getFillType() == SvtGraphicFill::fillTexture &&
1687 !isActionContained( rMtf,
1688 "XPATHFILL_SEQ_END",
1689 MetaActionType::FLOATTRANSPARENT ) )
1691 rendering::Texture aTexture;
1693 // TODO(F1): the SvtGraphicFill
1694 // can also transport metafiles
1695 // here, handle that case, too
1696 Graphic aGraphic;
1697 aFill.getGraphic( aGraphic );
1699 BitmapEx aBmpEx( aGraphic.GetBitmapEx() );
1700 const ::Size aBmpSize( aBmpEx.GetSizePixel() );
1702 ::SvtGraphicFill::Transform aTransform;
1703 aFill.getTransform( aTransform );
1705 ::basegfx::B2DHomMatrix aMatrix;
1707 // convert to basegfx matrix
1708 aMatrix.set(0,0, aTransform.matrix[ 0 ] );
1709 aMatrix.set(0,1, aTransform.matrix[ 1 ] );
1710 aMatrix.set(0,2, aTransform.matrix[ 2 ] );
1711 aMatrix.set(1,0, aTransform.matrix[ 3 ] );
1712 aMatrix.set(1,1, aTransform.matrix[ 4 ] );
1713 aMatrix.set(1,2, aTransform.matrix[ 5 ] );
1715 ::basegfx::B2DHomMatrix aScale;
1716 aScale.scale( aBmpSize.Width(),
1717 aBmpSize.Height() );
1719 // post-multiply with the bitmap
1720 // size (XCanvas' texture assumes
1721 // the given bitmap to be
1722 // normalized to [0,1]x[0,1]
1723 // rectangle)
1724 aMatrix = aMatrix * aScale;
1726 // pre-multiply with the
1727 // logic-to-pixel scale factor
1728 // (the metafile comment works in
1729 // logical coordinates).
1730 ::basegfx::B2DHomMatrix aLogic2PixelTransform;
1731 aMatrix *= tools::calcLogic2PixelLinearTransform( aLogic2PixelTransform,
1732 rVDev );
1734 ::basegfx::unotools::affineMatrixFromHomMatrix(
1735 aTexture.AffineTransform,
1736 aMatrix );
1738 aTexture.Alpha = 1.0 - aFill.getTransparency();
1739 aTexture.Bitmap = vcl::unotools::xBitmapFromBitmapEx( aBmpEx );
1740 if( aFill.isTiling() )
1742 aTexture.RepeatModeX = rendering::TexturingMode::REPEAT;
1743 aTexture.RepeatModeY = rendering::TexturingMode::REPEAT;
1745 else
1747 aTexture.RepeatModeX = rendering::TexturingMode::NONE;
1748 aTexture.RepeatModeY = rendering::TexturingMode::NONE;
1751 ::tools::PolyPolygon aPath;
1752 aFill.getPath( aPath );
1754 ::basegfx::B2DPolyPolygon aPoly( aPath.getB2DPolyPolygon() );
1755 aPoly.transform( rStates.getState().mapModeTransform );
1756 std::shared_ptr<Action> pPolyAction(
1757 internal::PolyPolyActionFactory::createPolyPolyAction(
1758 aPoly,
1759 rCanvas,
1760 rStates.getState(),
1761 aTexture ) );
1763 if( pPolyAction )
1765 maActions.emplace_back(
1766 pPolyAction,
1767 io_rCurrActionIndex );
1769 io_rCurrActionIndex += pPolyAction->getActionCount()-1;
1772 // skip broken-down render output
1773 skipContent( rMtf,
1774 "XPATHFILL_SEQ_END",
1775 io_rCurrActionIndex );
1779 // Handle drawing layer fills
1780 else if( pAct->GetComment() == "EMF_PLUS" ) {
1781 SAL_WARN ("cppcanvas.emf", "EMF+ code was refactored and removed");
1782 } else if( pAct->GetComment() == "EMF_PLUS_HEADER_INFO" ) {
1783 SAL_INFO ("cppcanvas.emf", "EMF+ passed to canvas mtf renderer - header info, size: " << pAct->GetDataSize ());
1785 SvMemoryStream rMF (const_cast<sal_uInt8 *>(pAct->GetData ()), pAct->GetDataSize (), StreamMode::READ);
1787 rMF.ReadInt32( nFrameLeft ).ReadInt32( nFrameTop ).ReadInt32( nFrameRight ).ReadInt32( nFrameBottom );
1788 SAL_INFO ("cppcanvas.emf", "EMF+ picture frame: " << nFrameLeft << "," << nFrameTop << " - " << nFrameRight << "," << nFrameBottom);
1789 rMF.ReadInt32( nPixX ).ReadInt32( nPixY ).ReadInt32( nMmX ).ReadInt32( nMmY );
1790 SAL_INFO ("cppcanvas.emf", "EMF+ ref device pixel size: " << nPixX << "x" << nPixY << " mm size: " << nMmX << "x" << nMmY);
1792 ReadXForm( rMF, aBaseTransform );
1793 //aWorldTransform.Set (aBaseTransform);
1796 break;
1799 // In the third part of this monster-switch, we
1800 // handle all 'acting' meta actions. These are all
1801 // processed by constructing function objects for
1802 // them, which will later ease caching.
1805 case MetaActionType::POINT:
1807 const OutDevState& rState( rStates.getState() );
1808 if( rState.lineColor.hasElements() )
1810 std::shared_ptr<Action> pPointAction(
1811 internal::PointActionFactory::createPointAction(
1812 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint(
1813 static_cast<MetaPointAction*>(pCurrAct)->GetPoint() ),
1814 rCanvas,
1815 rState ) );
1817 if( pPointAction )
1819 maActions.emplace_back(
1820 pPointAction,
1821 io_rCurrActionIndex );
1823 io_rCurrActionIndex += pPointAction->getActionCount()-1;
1827 break;
1829 case MetaActionType::PIXEL:
1831 const OutDevState& rState( rStates.getState() );
1832 if( rState.lineColor.hasElements() )
1834 std::shared_ptr<Action> pPointAction(
1835 internal::PointActionFactory::createPointAction(
1836 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint(
1837 static_cast<MetaPixelAction*>(pCurrAct)->GetPoint() ),
1838 rCanvas,
1839 rState,
1840 static_cast<MetaPixelAction*>(pCurrAct)->GetColor() ) );
1842 if( pPointAction )
1844 maActions.emplace_back(
1845 pPointAction,
1846 io_rCurrActionIndex );
1848 io_rCurrActionIndex += pPointAction->getActionCount()-1;
1852 break;
1854 case MetaActionType::LINE:
1856 const OutDevState& rState( rStates.getState() );
1857 if( rState.lineColor.hasElements() )
1859 MetaLineAction* pLineAct = static_cast<MetaLineAction*>(pCurrAct);
1861 const LineInfo& rLineInfo( pLineAct->GetLineInfo() );
1863 const ::basegfx::B2DPoint aStartPoint(
1864 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( pLineAct->GetStartPoint() ));
1865 const ::basegfx::B2DPoint aEndPoint(
1866 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( pLineAct->GetEndPoint() ));
1868 std::shared_ptr<Action> pLineAction;
1870 if( rLineInfo.IsDefault() )
1872 // plain hair line
1873 pLineAction =
1874 internal::LineActionFactory::createLineAction(
1875 aStartPoint,
1876 aEndPoint,
1877 rCanvas,
1878 rState );
1880 if( pLineAction )
1882 maActions.emplace_back(
1883 pLineAction,
1884 io_rCurrActionIndex );
1886 io_rCurrActionIndex += pLineAction->getActionCount()-1;
1889 else if( LineStyle::NONE != rLineInfo.GetStyle() )
1891 // 'thick' line
1892 rendering::StrokeAttributes aStrokeAttributes;
1894 setupStrokeAttributes( aStrokeAttributes,
1895 rFactoryParms,
1896 rLineInfo );
1898 // XCanvas can only stroke polygons,
1899 // not simple lines - thus, handle
1900 // this case via the polypolygon
1901 // action
1902 ::basegfx::B2DPolygon aPoly;
1903 aPoly.append( aStartPoint );
1904 aPoly.append( aEndPoint );
1905 pLineAction =
1906 internal::PolyPolyActionFactory::createPolyPolyAction(
1907 ::basegfx::B2DPolyPolygon( aPoly ),
1908 rCanvas, rState, aStrokeAttributes );
1910 if( pLineAction )
1912 maActions.emplace_back(
1913 pLineAction,
1914 io_rCurrActionIndex );
1916 io_rCurrActionIndex += pLineAction->getActionCount()-1;
1919 // else: line style is default
1920 // (i.e. invisible), don't generate action
1923 break;
1925 case MetaActionType::RECT:
1927 const ::tools::Rectangle& rRect(
1928 static_cast<MetaRectAction*>(pCurrAct)->GetRect() );
1930 if( rRect.IsEmpty() )
1931 break;
1933 const OutDevState& rState( rStates.getState() );
1934 const ::basegfx::B2DPoint aTopLeftPixel(
1935 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( rRect.TopLeft() ) );
1936 const ::basegfx::B2DPoint aBottomRightPixel(
1937 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( rRect.BottomRight() ) +
1938 // #121100# OutputDevice::DrawRect() fills
1939 // rectangles Apple-like, i.e. with one
1940 // additional pixel to the right and bottom.
1941 ::basegfx::B2DPoint(1,1) );
1943 createFillAndStroke( ::basegfx::utils::createPolygonFromRect(
1944 ::basegfx::B2DRange( aTopLeftPixel,
1945 aBottomRightPixel )),
1946 rFactoryParms );
1947 break;
1950 case MetaActionType::ROUNDRECT:
1952 const ::tools::Rectangle& rRect(
1953 static_cast<MetaRoundRectAction*>(pCurrAct)->GetRect());
1955 if( rRect.IsEmpty() )
1956 break;
1958 ::basegfx::B2DPolygon aPoly(
1959 ::basegfx::utils::createPolygonFromRect(
1960 ::basegfx::B2DRange(
1961 vcl::unotools::b2DPointFromPoint( rRect.TopLeft() ),
1962 vcl::unotools::b2DPointFromPoint( rRect.BottomRight() ) +
1963 ::basegfx::B2DPoint(1,1) ),
1964 static_cast<double>(static_cast<MetaRoundRectAction*>(pCurrAct)->GetHorzRound()) / rRect.GetWidth(),
1965 static_cast<double>(static_cast<MetaRoundRectAction*>(pCurrAct)->GetVertRound()) / rRect.GetHeight() ) );
1966 aPoly.transform( rStates.getState().mapModeTransform );
1968 createFillAndStroke( aPoly,
1969 rFactoryParms );
1971 break;
1973 case MetaActionType::ELLIPSE:
1975 const ::tools::Rectangle& rRect(
1976 static_cast<MetaEllipseAction*>(pCurrAct)->GetRect() );
1978 if( rRect.IsEmpty() )
1979 break;
1981 const ::basegfx::B2DRange aRange(
1982 vcl::unotools::b2DPointFromPoint( rRect.TopLeft() ),
1983 vcl::unotools::b2DPointFromPoint( rRect.BottomRight() ) +
1984 ::basegfx::B2DPoint(1,1) );
1986 ::basegfx::B2DPolygon aPoly(
1987 ::basegfx::utils::createPolygonFromEllipse(
1988 aRange.getCenter(),
1989 aRange.getWidth() / 2, // divide by 2 since createPolygonFromEllipse
1990 aRange.getHeight() / 2 )); // expects the radius and NOT the diameter!
1991 aPoly.transform( rStates.getState().mapModeTransform );
1993 createFillAndStroke( aPoly,
1994 rFactoryParms );
1996 break;
1998 case MetaActionType::ARC:
2000 // TODO(F1): Missing basegfx functionality. Mind empty rects!
2001 const ::tools::Polygon aToolsPoly( static_cast<MetaArcAction*>(pCurrAct)->GetRect(),
2002 static_cast<MetaArcAction*>(pCurrAct)->GetStartPoint(),
2003 static_cast<MetaArcAction*>(pCurrAct)->GetEndPoint(), PolyStyle::Arc );
2004 ::basegfx::B2DPolygon aPoly( aToolsPoly.getB2DPolygon() );
2005 aPoly.transform( rStates.getState().mapModeTransform );
2007 createFillAndStroke( aPoly,
2008 rFactoryParms );
2010 break;
2012 case MetaActionType::PIE:
2014 // TODO(F1): Missing basegfx functionality. Mind empty rects!
2015 const ::tools::Polygon aToolsPoly( static_cast<MetaPieAction*>(pCurrAct)->GetRect(),
2016 static_cast<MetaPieAction*>(pCurrAct)->GetStartPoint(),
2017 static_cast<MetaPieAction*>(pCurrAct)->GetEndPoint(), PolyStyle::Pie );
2018 ::basegfx::B2DPolygon aPoly( aToolsPoly.getB2DPolygon() );
2019 aPoly.transform( rStates.getState().mapModeTransform );
2021 createFillAndStroke( aPoly,
2022 rFactoryParms );
2024 break;
2026 case MetaActionType::CHORD:
2028 // TODO(F1): Missing basegfx functionality. Mind empty rects!
2029 const ::tools::Polygon aToolsPoly( static_cast<MetaChordAction*>(pCurrAct)->GetRect(),
2030 static_cast<MetaChordAction*>(pCurrAct)->GetStartPoint(),
2031 static_cast<MetaChordAction*>(pCurrAct)->GetEndPoint(), PolyStyle::Chord );
2032 ::basegfx::B2DPolygon aPoly( aToolsPoly.getB2DPolygon() );
2033 aPoly.transform( rStates.getState().mapModeTransform );
2035 createFillAndStroke( aPoly,
2036 rFactoryParms );
2038 break;
2040 case MetaActionType::POLYLINE:
2042 const OutDevState& rState( rStates.getState() );
2043 if( rState.lineColor.hasElements() ||
2044 rState.fillColor.hasElements() )
2046 MetaPolyLineAction* pPolyLineAct = static_cast<MetaPolyLineAction*>(pCurrAct);
2048 const LineInfo& rLineInfo( pPolyLineAct->GetLineInfo() );
2049 ::basegfx::B2DPolygon aPoly( pPolyLineAct->GetPolygon().getB2DPolygon() );
2050 aPoly.transform( rState.mapModeTransform );
2052 std::shared_ptr<Action> pLineAction;
2054 if( rLineInfo.IsDefault() )
2056 // plain hair line polygon
2057 pLineAction =
2058 internal::PolyPolyActionFactory::createLinePolyPolyAction(
2059 ::basegfx::B2DPolyPolygon(aPoly),
2060 rCanvas,
2061 rState );
2063 if( pLineAction )
2065 maActions.emplace_back(
2066 pLineAction,
2067 io_rCurrActionIndex );
2069 io_rCurrActionIndex += pLineAction->getActionCount()-1;
2072 else if( LineStyle::NONE != rLineInfo.GetStyle() )
2074 // 'thick' line polygon
2075 rendering::StrokeAttributes aStrokeAttributes;
2077 setupStrokeAttributes( aStrokeAttributes,
2078 rFactoryParms,
2079 rLineInfo );
2081 pLineAction =
2082 internal::PolyPolyActionFactory::createPolyPolyAction(
2083 ::basegfx::B2DPolyPolygon(aPoly),
2084 rCanvas,
2085 rState,
2086 aStrokeAttributes ) ;
2088 if( pLineAction )
2090 maActions.emplace_back(
2091 pLineAction,
2092 io_rCurrActionIndex );
2094 io_rCurrActionIndex += pLineAction->getActionCount()-1;
2097 // else: line style is default
2098 // (i.e. invisible), don't generate action
2101 break;
2103 case MetaActionType::POLYGON:
2105 ::basegfx::B2DPolygon aPoly( static_cast<MetaPolygonAction*>(pCurrAct)->GetPolygon().getB2DPolygon() );
2106 aPoly.transform( rStates.getState().mapModeTransform );
2107 createFillAndStroke( aPoly,
2108 rFactoryParms );
2110 break;
2112 case MetaActionType::POLYPOLYGON:
2114 ::basegfx::B2DPolyPolygon aPoly( static_cast<MetaPolyPolygonAction*>(pCurrAct)->GetPolyPolygon().getB2DPolyPolygon() );
2115 aPoly.transform( rStates.getState().mapModeTransform );
2116 createFillAndStroke( aPoly,
2117 rFactoryParms );
2119 break;
2121 case MetaActionType::BMP:
2123 MetaBmpAction* pAct = static_cast<MetaBmpAction*>(pCurrAct);
2125 std::shared_ptr<Action> pBmpAction(
2126 internal::BitmapActionFactory::createBitmapAction(
2127 BitmapEx(pAct->GetBitmap()),
2128 rStates.getState().mapModeTransform *
2129 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2130 rCanvas,
2131 rStates.getState() ) );
2133 if( pBmpAction )
2135 maActions.emplace_back(
2136 pBmpAction,
2137 io_rCurrActionIndex );
2139 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2142 break;
2144 case MetaActionType::BMPSCALE:
2146 MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pCurrAct);
2148 std::shared_ptr<Action> pBmpAction(
2149 internal::BitmapActionFactory::createBitmapAction(
2150 BitmapEx(pAct->GetBitmap()),
2151 rStates.getState().mapModeTransform *
2152 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2153 rStates.getState().mapModeTransform *
2154 vcl::unotools::b2DSizeFromSize( pAct->GetSize() ),
2155 rCanvas,
2156 rStates.getState() ) );
2158 if( pBmpAction )
2160 maActions.emplace_back(
2161 pBmpAction,
2162 io_rCurrActionIndex );
2164 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2167 break;
2169 case MetaActionType::BMPSCALEPART:
2171 MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pCurrAct);
2173 // crop bitmap to given source rectangle (no
2174 // need to copy and convert the whole bitmap)
2175 ::Bitmap aBmp( pAct->GetBitmap() );
2176 const ::tools::Rectangle aCropRect( pAct->GetSrcPoint(),
2177 pAct->GetSrcSize() );
2178 aBmp.Crop( aCropRect );
2180 std::shared_ptr<Action> pBmpAction(
2181 internal::BitmapActionFactory::createBitmapAction(
2182 BitmapEx(aBmp),
2183 rStates.getState().mapModeTransform *
2184 vcl::unotools::b2DPointFromPoint( pAct->GetDestPoint() ),
2185 rStates.getState().mapModeTransform *
2186 vcl::unotools::b2DSizeFromSize( pAct->GetDestSize() ),
2187 rCanvas,
2188 rStates.getState() ) );
2190 if( pBmpAction )
2192 maActions.emplace_back(
2193 pBmpAction,
2194 io_rCurrActionIndex );
2196 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2199 break;
2201 case MetaActionType::BMPEX:
2203 MetaBmpExAction* pAct = static_cast<MetaBmpExAction*>(pCurrAct);
2205 std::shared_ptr<Action> pBmpAction(
2206 internal::BitmapActionFactory::createBitmapAction(
2207 pAct->GetBitmapEx(),
2208 rStates.getState().mapModeTransform *
2209 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2210 rCanvas,
2211 rStates.getState() ) );
2213 if( pBmpAction )
2215 maActions.emplace_back(
2216 pBmpAction,
2217 io_rCurrActionIndex );
2219 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2222 break;
2224 case MetaActionType::BMPEXSCALE:
2226 MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pCurrAct);
2228 std::shared_ptr<Action> pBmpAction(
2229 internal::BitmapActionFactory::createBitmapAction(
2230 pAct->GetBitmapEx(),
2231 rStates.getState().mapModeTransform *
2232 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2233 rStates.getState().mapModeTransform *
2234 vcl::unotools::b2DSizeFromSize( pAct->GetSize() ),
2235 rCanvas,
2236 rStates.getState() ) );
2238 if( pBmpAction )
2240 maActions.emplace_back(
2241 pBmpAction,
2242 io_rCurrActionIndex );
2244 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2247 break;
2249 case MetaActionType::BMPEXSCALEPART:
2251 MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pCurrAct);
2253 // crop bitmap to given source rectangle (no
2254 // need to copy and convert the whole bitmap)
2255 BitmapEx aBmp( pAct->GetBitmapEx() );
2256 const ::tools::Rectangle aCropRect( pAct->GetSrcPoint(),
2257 pAct->GetSrcSize() );
2258 aBmp.Crop( aCropRect );
2260 std::shared_ptr<Action> pBmpAction(
2261 internal::BitmapActionFactory::createBitmapAction(
2262 aBmp,
2263 rStates.getState().mapModeTransform *
2264 vcl::unotools::b2DPointFromPoint( pAct->GetDestPoint() ),
2265 rStates.getState().mapModeTransform *
2266 vcl::unotools::b2DSizeFromSize( pAct->GetDestSize() ),
2267 rCanvas,
2268 rStates.getState() ) );
2270 if( pBmpAction )
2272 maActions.emplace_back(
2273 pBmpAction,
2274 io_rCurrActionIndex );
2276 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2279 break;
2281 case MetaActionType::MASK:
2283 MetaMaskAction* pAct = static_cast<MetaMaskAction*>(pCurrAct);
2285 // create masked BitmapEx right here, as the
2286 // canvas does not provide equivalent
2287 // functionality
2288 BitmapEx aBmp( createMaskBmpEx( pAct->GetBitmap(),
2289 pAct->GetColor() ));
2291 std::shared_ptr<Action> pBmpAction(
2292 internal::BitmapActionFactory::createBitmapAction(
2293 aBmp,
2294 rStates.getState().mapModeTransform *
2295 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2296 rCanvas,
2297 rStates.getState() ) );
2299 if( pBmpAction )
2301 maActions.emplace_back(
2302 pBmpAction,
2303 io_rCurrActionIndex );
2305 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2308 break;
2310 case MetaActionType::MASKSCALE:
2312 MetaMaskScaleAction* pAct = static_cast<MetaMaskScaleAction*>(pCurrAct);
2314 // create masked BitmapEx right here, as the
2315 // canvas does not provide equivalent
2316 // functionality
2317 BitmapEx aBmp( createMaskBmpEx( pAct->GetBitmap(),
2318 pAct->GetColor() ));
2320 std::shared_ptr<Action> pBmpAction(
2321 internal::BitmapActionFactory::createBitmapAction(
2322 aBmp,
2323 rStates.getState().mapModeTransform *
2324 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2325 rStates.getState().mapModeTransform *
2326 vcl::unotools::b2DSizeFromSize( pAct->GetSize() ),
2327 rCanvas,
2328 rStates.getState() ) );
2330 if( pBmpAction )
2332 maActions.emplace_back(
2333 pBmpAction,
2334 io_rCurrActionIndex );
2336 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2339 break;
2341 case MetaActionType::MASKSCALEPART:
2343 MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pCurrAct);
2345 // create masked BitmapEx right here, as the
2346 // canvas does not provide equivalent
2347 // functionality
2348 BitmapEx aBmp( createMaskBmpEx( pAct->GetBitmap(),
2349 pAct->GetColor() ));
2351 // crop bitmap to given source rectangle (no
2352 // need to copy and convert the whole bitmap)
2353 const ::tools::Rectangle aCropRect( pAct->GetSrcPoint(),
2354 pAct->GetSrcSize() );
2355 aBmp.Crop( aCropRect );
2357 std::shared_ptr<Action> pBmpAction(
2358 internal::BitmapActionFactory::createBitmapAction(
2359 aBmp,
2360 rStates.getState().mapModeTransform *
2361 vcl::unotools::b2DPointFromPoint( pAct->GetDestPoint() ),
2362 rStates.getState().mapModeTransform *
2363 vcl::unotools::b2DSizeFromSize( pAct->GetDestSize() ),
2364 rCanvas,
2365 rStates.getState() ) );
2367 if( pBmpAction )
2369 maActions.emplace_back(
2370 pBmpAction,
2371 io_rCurrActionIndex );
2373 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2376 break;
2378 case MetaActionType::GRADIENTEX:
2379 // TODO(F1): use native Canvas gradients here
2380 // action is ignored here, because redundant to MetaActionType::GRADIENT
2381 break;
2383 case MetaActionType::WALLPAPER:
2384 // TODO(F2): NYI
2385 break;
2387 case MetaActionType::Transparent:
2389 const OutDevState& rState( rStates.getState() );
2390 if( rState.lineColor.hasElements() ||
2391 rState.fillColor.hasElements() )
2393 MetaTransparentAction* pAct = static_cast<MetaTransparentAction*>(pCurrAct);
2394 ::basegfx::B2DPolyPolygon aPoly( pAct->GetPolyPolygon().getB2DPolyPolygon() );
2395 aPoly.transform( rState.mapModeTransform );
2397 std::shared_ptr<Action> pPolyAction(
2398 internal::PolyPolyActionFactory::createPolyPolyAction(
2399 aPoly,
2400 rCanvas,
2401 rState,
2402 pAct->GetTransparence() ) );
2404 if( pPolyAction )
2406 maActions.emplace_back(
2407 pPolyAction,
2408 io_rCurrActionIndex );
2410 io_rCurrActionIndex += pPolyAction->getActionCount()-1;
2414 break;
2416 case MetaActionType::FLOATTRANSPARENT:
2418 MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pCurrAct);
2420 internal::MtfAutoPtr pMtf(
2421 new ::GDIMetaFile( pAct->GetGDIMetaFile() ) );
2423 // TODO(P2): Use native canvas gradients here (saves a lot of UNO calls)
2424 internal::GradientAutoPtr pGradient(
2425 new Gradient( pAct->GetGradient() ) );
2427 DBG_TESTSOLARMUTEX();
2429 std::shared_ptr<Action> pFloatTransAction(
2430 internal::TransparencyGroupActionFactory::createTransparencyGroupAction(
2431 std::move(pMtf),
2432 std::move(pGradient),
2433 rStates.getState().mapModeTransform *
2434 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2435 rStates.getState().mapModeTransform *
2436 vcl::unotools::b2DSizeFromSize( pAct->GetSize() ),
2437 rCanvas,
2438 rStates.getState() ) );
2440 if( pFloatTransAction )
2442 maActions.emplace_back(
2443 pFloatTransAction,
2444 io_rCurrActionIndex );
2446 io_rCurrActionIndex += pFloatTransAction->getActionCount()-1;
2449 break;
2451 case MetaActionType::TEXT:
2453 MetaTextAction* pAct = static_cast<MetaTextAction*>(pCurrAct);
2454 OUString sText = pAct->GetText();
2456 if (rVDev.GetDigitLanguage())
2457 sText = convertToLocalizedNumerals(sText, rVDev.GetDigitLanguage());
2459 const sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex());
2461 createTextAction(
2462 pAct->GetPoint(),
2463 sText,
2464 pAct->GetIndex(),
2465 nLen,
2466 nullptr,
2467 rFactoryParms,
2468 bSubsettableActions );
2470 break;
2472 case MetaActionType::TEXTARRAY:
2474 MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pCurrAct);
2475 OUString sText = pAct->GetText();
2477 if (rVDev.GetDigitLanguage())
2478 sText = convertToLocalizedNumerals(sText, rVDev.GetDigitLanguage());
2480 const sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex());
2482 createTextAction(
2483 pAct->GetPoint(),
2484 sText,
2485 pAct->GetIndex(),
2486 nLen,
2487 pAct->GetDXArray(),
2488 rFactoryParms,
2489 bSubsettableActions );
2491 break;
2493 case MetaActionType::TEXTLINE:
2495 MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pCurrAct);
2497 const OutDevState& rState( rStates.getState() );
2498 const ::Size aBaselineOffset( tools::getBaselineOffset( rState,
2499 rVDev ) );
2500 const ::basegfx::B2DSize aSize( rState.mapModeTransform *
2501 ::basegfx::B2DSize(pAct->GetWidth(),
2502 0 ));
2504 std::shared_ptr<Action> pPolyAction(
2505 PolyPolyActionFactory::createPolyPolyAction(
2506 tools::createTextLinesPolyPolygon(
2507 rState.mapModeTransform *
2508 ::basegfx::B2DPoint(
2509 vcl::unotools::b2DPointFromPoint(pAct->GetStartPoint()) +
2510 vcl::unotools::b2DSizeFromSize(aBaselineOffset)),
2511 aSize.getX(),
2512 tools::createTextLineInfo( rVDev,
2513 rState )),
2514 rCanvas,
2515 rState ) );
2517 if( pPolyAction )
2519 maActions.emplace_back(
2520 pPolyAction,
2521 io_rCurrActionIndex );
2523 io_rCurrActionIndex += pPolyAction->getActionCount()-1;
2526 break;
2528 case MetaActionType::TEXTRECT:
2530 MetaTextRectAction* pAct = static_cast<MetaTextRectAction*>(pCurrAct);
2532 rStates.pushState(PushFlags::ALL);
2534 // use the VDev to break up the text rect
2535 // action into readily formatted lines
2536 GDIMetaFile aTmpMtf;
2537 rVDev.AddTextRectActions( pAct->GetRect(),
2538 pAct->GetText(),
2539 pAct->GetStyle(),
2540 aTmpMtf );
2542 createActions( aTmpMtf,
2543 rFactoryParms,
2544 bSubsettableActions );
2546 rStates.popState();
2548 break;
2551 case MetaActionType::STRETCHTEXT:
2553 MetaStretchTextAction* pAct = static_cast<MetaStretchTextAction*>(pCurrAct);
2554 OUString sText = pAct->GetText();
2556 if (rVDev.GetDigitLanguage())
2557 sText = convertToLocalizedNumerals(sText, rVDev.GetDigitLanguage());
2559 const sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex());
2561 // #i70897# Nothing to do, actually...
2562 if( nLen == 0 )
2563 break;
2565 // have to fit the text into the given
2566 // width. This is achieved by internally
2567 // generating a DX array, and uniformly
2568 // distributing the excess/insufficient width
2569 // to every logical character.
2570 std::unique_ptr< ::tools::Long []> pDXArray( new ::tools::Long[nLen] );
2572 rVDev.GetTextArray( pAct->GetText(), pDXArray.get(),
2573 pAct->GetIndex(), pAct->GetLen() );
2575 const sal_Int32 nWidthDifference( pAct->GetWidth() - pDXArray[ nLen-1 ] );
2577 // Last entry of pDXArray contains total width of the text
2578 ::tools::Long* p = pDXArray.get();
2579 for (sal_Int32 i = 1; i <= nLen; ++i)
2581 // calc ratio for every array entry, to
2582 // distribute rounding errors 'evenly'
2583 // across the characters. Note that each
2584 // entry represents the 'end' position of
2585 // the corresponding character, thus, we
2586 // let i run from 1 to nLen.
2587 *p++ += static_cast<::tools::Long>(i)*nWidthDifference/nLen;
2590 createTextAction(
2591 pAct->GetPoint(),
2592 sText,
2593 pAct->GetIndex(),
2594 nLen,
2595 pDXArray.get(),
2596 rFactoryParms,
2597 bSubsettableActions );
2599 break;
2601 default:
2602 SAL_WARN( "cppcanvas.emf", "Unknown meta action type encountered" );
2603 break;
2606 // increment action index (each mtf action counts _at
2607 // least_ one. Some count for more, therefore,
2608 // io_rCurrActionIndex is sometimes incremented by
2609 // pAct->getActionCount()-1 above, the -1 being the
2610 // correction for the unconditional increment here).
2611 ++io_rCurrActionIndex;
2616 namespace
2618 class ActionRenderer
2620 public:
2621 explicit ActionRenderer( const ::basegfx::B2DHomMatrix& rTransformation ) :
2622 maTransformation( rTransformation ),
2623 mbRet( true )
2627 bool result() const
2629 return mbRet;
2632 void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction )
2634 // ANDing the result. We want to fail if at least
2635 // one action failed.
2636 mbRet &= rAction.mpAction->render( maTransformation );
2639 void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction,
2640 const Action::Subset& rSubset )
2642 // ANDing the result. We want to fail if at least
2643 // one action failed.
2644 mbRet &= rAction.mpAction->renderSubset( maTransformation,
2645 rSubset );
2648 private:
2649 ::basegfx::B2DHomMatrix maTransformation;
2650 bool mbRet;
2653 class AreaQuery
2655 public:
2656 explicit AreaQuery( const ::basegfx::B2DHomMatrix& rTransformation ) :
2657 maTransformation( rTransformation ),
2658 maBounds()
2662 static bool result()
2664 return true; // nothing can fail here
2667 void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction )
2669 maBounds.expand( rAction.mpAction->getBounds( maTransformation ) );
2672 void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction,
2673 const Action::Subset& rSubset )
2675 maBounds.expand( rAction.mpAction->getBounds( maTransformation,
2676 rSubset ) );
2679 const ::basegfx::B2DRange& getBounds() const
2681 return maBounds;
2684 private:
2685 ::basegfx::B2DHomMatrix maTransformation;
2686 ::basegfx::B2DRange maBounds;
2689 // Doing that via inline class. Compilers tend to not inline free
2690 // functions.
2691 struct UpperBoundActionIndexComparator
2693 bool operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rLHS,
2694 const ::cppcanvas::internal::ImplRenderer::MtfAction& rRHS )
2696 const sal_Int32 nLHSCount( rLHS.mpAction ?
2697 rLHS.mpAction->getActionCount() : 0 );
2698 const sal_Int32 nRHSCount( rRHS.mpAction ?
2699 rRHS.mpAction->getActionCount() : 0 );
2701 // compare end of action range, to have an action selected
2702 // by lower_bound even if the requested index points in
2703 // the middle of the action's range
2704 return rLHS.mnOrigIndex + nLHSCount < rRHS.mnOrigIndex + nRHSCount;
2708 /** Algorithm to apply given functor to a subset range
2710 @tpl Functor
2712 Functor to call for each element of the subset
2713 range. Must provide the following method signatures:
2714 bool result() (returning false if operation failed)
2717 template< typename Functor > bool
2718 forSubsetRange( Functor& rFunctor,
2719 ImplRenderer::ActionVector::const_iterator aRangeBegin,
2720 const ImplRenderer::ActionVector::const_iterator& aRangeEnd,
2721 sal_Int32 nStartIndex,
2722 sal_Int32 nEndIndex,
2723 const ImplRenderer::ActionVector::const_iterator& rEnd )
2725 if( aRangeBegin == aRangeEnd )
2727 // only a single action. Setup subset, and call functor
2728 Action::Subset aSubset;
2729 aSubset.mnSubsetBegin = std::max( sal_Int32( 0 ),
2730 nStartIndex - aRangeBegin->mnOrigIndex );
2731 aSubset.mnSubsetEnd = std::min( aRangeBegin->mpAction->getActionCount(),
2732 nEndIndex - aRangeBegin->mnOrigIndex );
2734 ENSURE_OR_RETURN_FALSE( aSubset.mnSubsetBegin >= 0 && aSubset.mnSubsetEnd >= 0,
2735 "ImplRenderer::forSubsetRange(): Invalid indices" );
2737 rFunctor( *aRangeBegin, aSubset );
2739 else
2741 // more than one action.
2743 // render partial first, full intermediate, and
2744 // partial last action
2745 Action::Subset aSubset;
2746 aSubset.mnSubsetBegin = std::max( sal_Int32( 0 ),
2747 nStartIndex - aRangeBegin->mnOrigIndex );
2748 aSubset.mnSubsetEnd = aRangeBegin->mpAction->getActionCount();
2750 ENSURE_OR_RETURN_FALSE( aSubset.mnSubsetBegin >= 0 && aSubset.mnSubsetEnd >= 0,
2751 "ImplRenderer::forSubsetRange(): Invalid indices" );
2753 rFunctor( *aRangeBegin, aSubset );
2755 // first action rendered, skip to next
2756 ++aRangeBegin;
2758 // render full middle actions
2759 while( aRangeBegin != aRangeEnd )
2760 rFunctor( *aRangeBegin++ );
2762 if( aRangeEnd == rEnd ||
2763 aRangeEnd->mnOrigIndex > nEndIndex )
2765 // aRangeEnd denotes end of action vector,
2767 // or
2769 // nEndIndex references something _after_
2770 // aRangeBegin, but _before_ aRangeEnd
2772 // either way: no partial action left
2773 return rFunctor.result();
2776 aSubset.mnSubsetBegin = 0;
2777 aSubset.mnSubsetEnd = nEndIndex - aRangeEnd->mnOrigIndex;
2779 ENSURE_OR_RETURN_FALSE(aSubset.mnSubsetEnd >= 0,
2780 "ImplRenderer::forSubsetRange(): Invalid indices" );
2782 rFunctor( *aRangeEnd, aSubset );
2785 return rFunctor.result();
2789 bool ImplRenderer::getSubsetIndices( sal_Int32& io_rStartIndex,
2790 sal_Int32& io_rEndIndex,
2791 ActionVector::const_iterator& o_rRangeBegin,
2792 ActionVector::const_iterator& o_rRangeEnd ) const
2794 ENSURE_OR_RETURN_FALSE( io_rStartIndex<=io_rEndIndex,
2795 "ImplRenderer::getSubsetIndices(): invalid action range" );
2797 ENSURE_OR_RETURN_FALSE( !maActions.empty(),
2798 "ImplRenderer::getSubsetIndices(): no actions to render" );
2800 const sal_Int32 nMinActionIndex( maActions.front().mnOrigIndex );
2801 const sal_Int32 nMaxActionIndex( maActions.back().mnOrigIndex +
2802 maActions.back().mpAction->getActionCount() );
2804 // clip given range to permissible values (there might be
2805 // ranges before and behind the valid indices)
2806 io_rStartIndex = std::max( nMinActionIndex,
2807 io_rStartIndex );
2808 io_rEndIndex = std::min( nMaxActionIndex,
2809 io_rEndIndex );
2811 if( io_rStartIndex == io_rEndIndex ||
2812 io_rStartIndex > io_rEndIndex )
2814 // empty range, don't render anything. The second
2815 // condition e.g. happens if the requested range lies
2816 // fully before or behind the valid action indices.
2817 return false;
2821 const ActionVector::const_iterator aBegin( maActions.begin() );
2822 const ActionVector::const_iterator aEnd( maActions.end() );
2825 // find start and end action
2826 // =========================
2827 o_rRangeBegin = std::lower_bound( aBegin, aEnd,
2828 MtfAction( std::shared_ptr<Action>(), io_rStartIndex ),
2829 UpperBoundActionIndexComparator() );
2830 o_rRangeEnd = std::lower_bound( aBegin, aEnd,
2831 MtfAction( std::shared_ptr<Action>(), io_rEndIndex ),
2832 UpperBoundActionIndexComparator() );
2833 return true;
2837 // Public methods
2840 ImplRenderer::ImplRenderer( const CanvasSharedPtr& rCanvas,
2841 const GDIMetaFile& rMtf,
2842 const Parameters& rParams )
2843 : CanvasGraphicHelper(rCanvas)
2844 , maActions()
2845 , nFrameLeft(0)
2846 , nFrameTop(0)
2847 , nFrameRight(0)
2848 , nFrameBottom(0)
2849 , nPixX(0)
2850 , nPixY(0)
2851 , nMmX(0)
2852 , nMmY(0)
2854 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::ImplRenderer(mtf)" );
2856 OSL_ENSURE( rCanvas && rCanvas->getUNOCanvas().is(),
2857 "ImplRenderer::ImplRenderer(): Invalid canvas" );
2858 OSL_ENSURE( rCanvas->getUNOCanvas()->getDevice().is(),
2859 "ImplRenderer::ImplRenderer(): Invalid graphic device" );
2861 // make sure canvas and graphic device are valid; action
2862 // creation don't check that every time
2863 if( !rCanvas ||
2864 !rCanvas->getUNOCanvas().is() ||
2865 !rCanvas->getUNOCanvas()->getDevice().is() )
2867 // leave actions empty
2868 return;
2871 VectorOfOutDevStates aStateStack;
2873 ScopedVclPtrInstance< VirtualDevice > aVDev;
2874 aVDev->EnableOutput( false );
2876 // Setup VDev for state tracking and mapping
2877 // =========================================
2879 aVDev->SetMapMode( rMtf.GetPrefMapMode() );
2881 const Size aMtfSize( rMtf.GetPrefSize() );
2882 const Size aMtfSizePixPre( aVDev->LogicToPixel( aMtfSize,
2883 rMtf.GetPrefMapMode() ) );
2885 // #i44110# correct null-sized output - there are shapes
2886 // which have zero size in at least one dimension
2887 // Remark the 1L cannot be replaced, that would cause max to compare long/int
2888 const Size aMtfSizePix( std::max( aMtfSizePixPre.Width(), ::tools::Long(1) ),
2889 std::max( aMtfSizePixPre.Height(), ::tools::Long(1) ) );
2891 sal_Int32 nCurrActions(0);
2892 ActionFactoryParameters aParms(aStateStack,
2893 rCanvas,
2894 *aVDev,
2895 rParams,
2896 nCurrActions );
2898 // init state stack
2899 aStateStack.clearStateStack();
2901 // Setup local state, such that the metafile renders
2902 // itself into a one-by-one square at the origin for
2903 // identity view and render transformations
2904 aStateStack.getState().transform.scale( 1.0 / aMtfSizePix.Width(),
2905 1.0 / aMtfSizePix.Height() );
2907 tools::calcLogic2PixelAffineTransform( aStateStack.getState().mapModeTransform,
2908 *aVDev );
2911 ::cppcanvas::internal::OutDevState& rState = aStateStack.getState();
2912 // setup default text color to black
2913 rState.textColor =
2914 rState.textFillColor =
2915 rState.textOverlineColor =
2916 rState.textLineColor = tools::intSRGBAToDoubleSequence( 0x000000FF );
2919 // apply overrides from the Parameters struct
2920 if( rParams.maFillColor )
2922 ::cppcanvas::internal::OutDevState& rState = aStateStack.getState();
2923 rState.isFillColorSet = true;
2924 rState.fillColor = tools::intSRGBAToDoubleSequence( *rParams.maFillColor );
2926 if( rParams.maLineColor )
2928 ::cppcanvas::internal::OutDevState& rState = aStateStack.getState();
2929 rState.isLineColorSet = true;
2930 rState.lineColor = tools::intSRGBAToDoubleSequence( *rParams.maLineColor );
2932 if( rParams.maTextColor )
2934 ::cppcanvas::internal::OutDevState& rState = aStateStack.getState();
2935 rState.isTextFillColorSet = true;
2936 rState.isTextOverlineColorSet = true;
2937 rState.isTextLineColorSet = true;
2938 rState.textColor =
2939 rState.textFillColor =
2940 rState.textOverlineColor =
2941 rState.textLineColor = tools::intSRGBAToDoubleSequence( *rParams.maTextColor );
2943 if( rParams.maFontName ||
2944 rParams.maFontWeight ||
2945 rParams.maFontLetterForm ||
2946 rParams.maFontUnderline )
2948 ::cppcanvas::internal::OutDevState& rState = aStateStack.getState();
2950 rState.xFont = createFont( rState.fontRotation,
2951 vcl::Font(), // default font
2952 aParms );
2955 /* EMF+ */
2956 createActions( const_cast<GDIMetaFile&>(rMtf), // HACK(Q2):
2957 // we're
2958 // changing
2959 // the
2960 // current
2961 // action
2962 // in
2963 // createActions!
2964 aParms,
2965 true // TODO(P1): make subsettability configurable
2969 ImplRenderer::~ImplRenderer()
2973 bool ImplRenderer::drawSubset( sal_Int32 nStartIndex,
2974 sal_Int32 nEndIndex ) const
2976 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::drawSubset()" );
2978 ActionVector::const_iterator aRangeBegin;
2979 ActionVector::const_iterator aRangeEnd;
2983 if( !getSubsetIndices( nStartIndex, nEndIndex,
2984 aRangeBegin, aRangeEnd ) )
2985 return true; // nothing to render (but _that_ was successful)
2987 // now, aRangeBegin references the action in which the
2988 // subset rendering must start, and aRangeEnd references
2989 // the action in which the subset rendering must end (it
2990 // might also end right at the start of the referenced
2991 // action, such that zero of that action needs to be
2992 // rendered).
2995 // render subset of actions
2996 // ========================
2998 ::basegfx::B2DHomMatrix aMatrix;
2999 ::canvas::tools::getRenderStateTransform( aMatrix,
3000 getRenderState() );
3002 ActionRenderer aRenderer( aMatrix );
3004 return forSubsetRange( aRenderer,
3005 aRangeBegin,
3006 aRangeEnd,
3007 nStartIndex,
3008 nEndIndex,
3009 maActions.end() );
3011 catch( uno::Exception& )
3013 DBG_UNHANDLED_EXCEPTION("cppcanvas.emf");
3014 // convert error to return value
3015 return false;
3019 ::basegfx::B2DRange ImplRenderer::getSubsetArea( sal_Int32 nStartIndex,
3020 sal_Int32 nEndIndex ) const
3022 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::getSubsetArea()" );
3024 ActionVector::const_iterator aRangeBegin;
3025 ActionVector::const_iterator aRangeEnd;
3027 if( !getSubsetIndices( nStartIndex, nEndIndex,
3028 aRangeBegin, aRangeEnd ) )
3029 return ::basegfx::B2DRange(); // nothing to render -> empty range
3031 // now, aRangeBegin references the action in which the
3032 // subset querying must start, and aRangeEnd references
3033 // the action in which the subset querying must end (it
3034 // might also end right at the start of the referenced
3035 // action, such that zero of that action needs to be
3036 // queried).
3039 // query bounds for subset of actions
3040 // ==================================
3042 ::basegfx::B2DHomMatrix aMatrix;
3043 ::canvas::tools::getRenderStateTransform( aMatrix,
3044 getRenderState() );
3046 AreaQuery aQuery( aMatrix );
3047 forSubsetRange( aQuery,
3048 aRangeBegin,
3049 aRangeEnd,
3050 nStartIndex,
3051 nEndIndex,
3052 maActions.end() );
3054 return aQuery.getBounds();
3057 bool ImplRenderer::draw() const
3059 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::draw()" );
3061 ::basegfx::B2DHomMatrix aMatrix;
3062 ::canvas::tools::getRenderStateTransform( aMatrix,
3063 getRenderState() );
3067 return std::for_each( maActions.begin(), maActions.end(), ActionRenderer( aMatrix ) ).result();
3069 catch( uno::Exception& )
3071 DBG_UNHANDLED_EXCEPTION( "cppcanvas.emf");
3072 return false;
3077 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */