Version 5.2.6.1, tag libreoffice-5.2.6.1
[LibreOffice.git] / cppcanvas / source / mtfrenderer / implrenderer.cxx
blobbb64de5c1e27a21473cf8f4a7251c1f793a71367
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 <osl/mutex.hxx>
22 #include <vcl/svapp.hxx>
23 #include <comphelper/sequence.hxx>
24 #include <comphelper/anytostring.hxx>
25 #include <cppuhelper/exc_hlp.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/geometry/RealPoint2D.hpp>
31 #include <com/sun/star/rendering/PanoseProportion.hpp>
32 #include <com/sun/star/rendering/ViewState.hpp>
33 #include <com/sun/star/rendering/RenderState.hpp>
34 #include <com/sun/star/rendering/XCanvasFont.hpp>
35 #include <com/sun/star/rendering/XPolyPolygon2D.hpp>
36 #include <com/sun/star/rendering/XCanvas.hpp>
37 #include <com/sun/star/rendering/PathCapType.hpp>
38 #include <com/sun/star/rendering/PathJoinType.hpp>
39 #include <basegfx/tools/canvastools.hxx>
40 #include <basegfx/tools/gradienttools.hxx>
41 #include <basegfx/numeric/ftools.hxx>
42 #include <basegfx/polygon/b2dpolypolygontools.hxx>
43 #include <basegfx/polygon/b2dpolygontools.hxx>
44 #include <basegfx/polygon/b2dpolygon.hxx>
45 #include <basegfx/polygon/b2dpolypolygon.hxx>
46 #include <basegfx/matrix/b2dhommatrix.hxx>
47 #include <basegfx/vector/b2dsize.hxx>
48 #include <basegfx/range/b2drectangle.hxx>
49 #include <basegfx/point/b2dpoint.hxx>
50 #include <basegfx/tuple/b2dtuple.hxx>
51 #include <basegfx/polygon/b2dpolygonclipper.hxx>
52 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
53 #include <canvas/canvastools.hxx>
54 #include <vcl/canvastools.hxx>
55 #include <vcl/salbtype.hxx>
56 #include <vcl/gdimtf.hxx>
57 #include <vcl/metaact.hxx>
58 #include <vcl/virdev.hxx>
59 #include <vcl/metric.hxx>
60 #include <vcl/graphictools.hxx>
61 #include <tools/poly.hxx>
62 #include <i18nlangtag/languagetag.hxx>
63 #include <implrenderer.hxx>
64 #include <tools.hxx>
65 #include <outdevstate.hxx>
66 #include <action.hxx>
67 #include <bitmapaction.hxx>
68 #include <lineaction.hxx>
69 #include <pointaction.hxx>
70 #include <polypolyaction.hxx>
71 #include <textaction.hxx>
72 #include <transparencygroupaction.hxx>
73 #include <utility>
74 #include <vector>
75 #include <algorithm>
76 #include <iterator>
77 #include <memory>
78 #include "mtftools.hxx"
79 #include <basegfx/matrix/b2dhommatrixtools.hxx>
81 using namespace ::com::sun::star;
84 // free support functions
85 // ======================
86 namespace
88 template < class MetaActionType > void setStateColor( MetaActionType* pAct,
89 bool& rIsColorSet,
90 uno::Sequence< double >& rColorSequence,
91 const cppcanvas::CanvasSharedPtr& rCanvas )
93 rIsColorSet = pAct->IsSetting();
94 if (rIsColorSet)
96 ::Color aColor( pAct->GetColor() );
98 // force alpha part of color to
99 // opaque. transparent painting is done
100 // explicitly via MetaActionType::Transparent
101 aColor.SetTransparency(0);
102 //aColor.SetTransparency(128);
104 rColorSequence = vcl::unotools::colorToDoubleSequence(
105 aColor,
106 rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() );
110 void setupStrokeAttributes( rendering::StrokeAttributes& o_rStrokeAttributes,
111 const ::cppcanvas::internal::ActionFactoryParameters& rParms,
112 const LineInfo& rLineInfo )
114 const ::basegfx::B2DSize aWidth( rLineInfo.GetWidth(), 0 );
115 o_rStrokeAttributes.StrokeWidth =
116 (rParms.mrStates.getState().mapModeTransform * aWidth).getX();
118 // setup reasonable defaults
119 o_rStrokeAttributes.MiterLimit = 15.0; // 1.0 was no good default; GDI+'s limit is 10.0, our's is 15.0
120 o_rStrokeAttributes.StartCapType = rendering::PathCapType::BUTT;
121 o_rStrokeAttributes.EndCapType = rendering::PathCapType::BUTT;
123 switch (rLineInfo.GetLineJoin())
125 case basegfx::B2DLineJoin::NONE:
126 o_rStrokeAttributes.JoinType = rendering::PathJoinType::NONE;
127 break;
128 case basegfx::B2DLineJoin::Bevel:
129 o_rStrokeAttributes.JoinType = rendering::PathJoinType::BEVEL;
130 break;
131 case basegfx::B2DLineJoin::Miter:
132 o_rStrokeAttributes.JoinType = rendering::PathJoinType::MITER;
133 break;
134 case basegfx::B2DLineJoin::Round:
135 o_rStrokeAttributes.JoinType = rendering::PathJoinType::ROUND;
136 break;
139 switch(rLineInfo.GetLineCap())
141 default: /* css::drawing::LineCap_BUTT */
143 o_rStrokeAttributes.StartCapType = rendering::PathCapType::BUTT;
144 o_rStrokeAttributes.EndCapType = rendering::PathCapType::BUTT;
145 break;
147 case css::drawing::LineCap_ROUND:
149 o_rStrokeAttributes.StartCapType = rendering::PathCapType::ROUND;
150 o_rStrokeAttributes.EndCapType = rendering::PathCapType::ROUND;
151 break;
153 case css::drawing::LineCap_SQUARE:
155 o_rStrokeAttributes.StartCapType = rendering::PathCapType::SQUARE;
156 o_rStrokeAttributes.EndCapType = rendering::PathCapType::SQUARE;
157 break;
161 if( LINE_DASH == rLineInfo.GetStyle() )
163 const ::cppcanvas::internal::OutDevState& rState( rParms.mrStates.getState() );
165 // TODO(F1): Interpret OutDev::GetRefPoint() for the start of the dashing.
167 // interpret dash info only if explicitly enabled as
168 // style
169 const ::basegfx::B2DSize aDistance( rLineInfo.GetDistance(), 0 );
170 const double nDistance( (rState.mapModeTransform * aDistance).getX() );
172 const ::basegfx::B2DSize aDashLen( rLineInfo.GetDashLen(), 0 );
173 const double nDashLen( (rState.mapModeTransform * aDashLen).getX() );
175 const ::basegfx::B2DSize aDotLen( rLineInfo.GetDotLen(), 0 );
176 const double nDotLen( (rState.mapModeTransform * aDotLen).getX() );
178 const sal_Int32 nNumArryEntries( 2*rLineInfo.GetDashCount() +
179 2*rLineInfo.GetDotCount() );
181 o_rStrokeAttributes.DashArray.realloc( nNumArryEntries );
182 double* pDashArray = o_rStrokeAttributes.DashArray.getArray();
185 // iteratively fill dash array, first with dashes, then
186 // with dots.
189 sal_Int32 nCurrEntry=0;
191 for( sal_Int32 i=0; i<rLineInfo.GetDashCount(); ++i )
193 pDashArray[nCurrEntry++] = nDashLen;
194 pDashArray[nCurrEntry++] = nDistance;
196 for( sal_Int32 i=0; i<rLineInfo.GetDotCount(); ++i )
198 pDashArray[nCurrEntry++] = nDotLen;
199 pDashArray[nCurrEntry++] = nDistance;
205 /** Create masked BitmapEx, where the white areas of rBitmap are
206 transparent, and the other appear in rMaskColor.
208 BitmapEx createMaskBmpEx( const Bitmap& rBitmap,
209 const ::Color& rMaskColor )
211 const ::Color aWhite( COL_WHITE );
212 BitmapPalette aBiLevelPalette(2);
213 aBiLevelPalette[0] = aWhite;
214 aBiLevelPalette[1] = rMaskColor;
216 Bitmap aMask( rBitmap.CreateMask( aWhite ));
217 Bitmap aSolid( rBitmap.GetSizePixel(),
219 &aBiLevelPalette );
220 aSolid.Erase( rMaskColor );
222 return BitmapEx( aSolid, aMask );
225 OUString convertToLocalizedNumerals(const OUString& rStr,
226 LanguageType eTextLanguage)
228 OUStringBuffer aBuf(rStr);
229 for (sal_Int32 i = 0; i < aBuf.getLength(); ++i)
231 sal_Unicode nChar = aBuf[i];
232 if (nChar >= '0' && nChar <= '9')
233 aBuf[i] = GetLocalizedChar(nChar, eTextLanguage);
235 return aBuf.makeStringAndClear();
239 namespace cppcanvas
241 namespace internal
243 // state stack manipulators
245 void VectorOfOutDevStates::clearStateStack()
247 m_aStates.clear();
248 const OutDevState aDefaultState;
249 m_aStates.push_back(aDefaultState);
252 OutDevState& VectorOfOutDevStates::getState()
254 return m_aStates.back();
257 const OutDevState& VectorOfOutDevStates::getState() const
259 return m_aStates.back();
262 void VectorOfOutDevStates::pushState(PushFlags nFlags)
264 m_aStates.push_back( getState() );
265 getState().pushFlags = nFlags;
268 void VectorOfOutDevStates::popState()
270 if( getState().pushFlags != PushFlags::ALL )
272 // a state is pushed which is incomplete, i.e. does not
273 // restore everything to the previous stack level when
274 // popped.
275 // That means, we take the old state, and restore every
276 // OutDevState member whose flag is set, from the new to the
277 // old state. Then the new state gets overwritten by the
278 // calculated state
280 // preset to-be-calculated new state with old state
281 OutDevState aCalculatedNewState( getState() );
283 // selectively copy to-be-restored content over saved old
284 // state
285 m_aStates.pop_back();
287 const OutDevState& rNewState( getState() );
289 if( (aCalculatedNewState.pushFlags & PushFlags::LINECOLOR) )
291 aCalculatedNewState.lineColor = rNewState.lineColor;
292 aCalculatedNewState.isLineColorSet = rNewState.isLineColorSet;
295 if( (aCalculatedNewState.pushFlags & PushFlags::FILLCOLOR) )
297 aCalculatedNewState.fillColor = rNewState.fillColor;
298 aCalculatedNewState.isFillColorSet = rNewState.isFillColorSet;
301 if( (aCalculatedNewState.pushFlags & PushFlags::FONT) )
303 aCalculatedNewState.xFont = rNewState.xFont;
304 aCalculatedNewState.fontRotation = rNewState.fontRotation;
305 aCalculatedNewState.textReliefStyle = rNewState.textReliefStyle;
306 aCalculatedNewState.textOverlineStyle = rNewState.textOverlineStyle;
307 aCalculatedNewState.textUnderlineStyle = rNewState.textUnderlineStyle;
308 aCalculatedNewState.textStrikeoutStyle = rNewState.textStrikeoutStyle;
309 aCalculatedNewState.textEmphasisMarkStyle = rNewState.textEmphasisMarkStyle;
310 aCalculatedNewState.isTextEffectShadowSet = rNewState.isTextEffectShadowSet;
311 aCalculatedNewState.isTextWordUnderlineSet = rNewState.isTextWordUnderlineSet;
312 aCalculatedNewState.isTextOutlineModeSet = rNewState.isTextOutlineModeSet;
315 if( (aCalculatedNewState.pushFlags & PushFlags::TEXTCOLOR) )
317 aCalculatedNewState.textColor = rNewState.textColor;
320 if( (aCalculatedNewState.pushFlags & PushFlags::MAPMODE) )
322 aCalculatedNewState.mapModeTransform = rNewState.mapModeTransform;
325 if( (aCalculatedNewState.pushFlags & PushFlags::CLIPREGION) )
327 aCalculatedNewState.clip = rNewState.clip;
328 aCalculatedNewState.clipRect = rNewState.clipRect;
329 aCalculatedNewState.xClipPoly = rNewState.xClipPoly;
332 // TODO(F2): Raster ops NYI
333 // if( (aCalculatedNewState.pushFlags & PushFlags::RASTEROP) )
334 // {
335 // }
337 if( (aCalculatedNewState.pushFlags & PushFlags::TEXTFILLCOLOR) )
339 aCalculatedNewState.textFillColor = rNewState.textFillColor;
340 aCalculatedNewState.isTextFillColorSet = rNewState.isTextFillColorSet;
343 if( (aCalculatedNewState.pushFlags & PushFlags::TEXTALIGN) )
345 aCalculatedNewState.textReferencePoint = rNewState.textReferencePoint;
348 // TODO(F1): Refpoint handling NYI
349 // if( (aCalculatedNewState.pushFlags & PushFlags::REFPOINT) )
350 // {
351 // }
353 if( (aCalculatedNewState.pushFlags & PushFlags::TEXTLINECOLOR) )
355 aCalculatedNewState.textLineColor = rNewState.textLineColor;
356 aCalculatedNewState.isTextLineColorSet = rNewState.isTextLineColorSet;
359 if( (aCalculatedNewState.pushFlags & PushFlags::TEXTLAYOUTMODE) )
361 aCalculatedNewState.textAlignment = rNewState.textAlignment;
362 aCalculatedNewState.textDirection = rNewState.textDirection;
365 // TODO(F2): Text language handling NYI
366 // if( (aCalculatedNewState.pushFlags & PushFlags::TEXTLANGUAGE) )
367 // {
368 // }
370 // always copy push mode
371 aCalculatedNewState.pushFlags = rNewState.pushFlags;
373 // flush to stack
374 getState() = aCalculatedNewState;
376 else
378 m_aStates.pop_back();
382 bool ImplRenderer::createFillAndStroke( const ::basegfx::B2DPolyPolygon& rPolyPoly,
383 const ActionFactoryParameters& rParms )
385 const OutDevState& rState( rParms.mrStates.getState() );
386 if( (!rState.isLineColorSet &&
387 !rState.isFillColorSet) ||
388 (rState.lineColor.getLength() == 0 &&
389 rState.fillColor.getLength() == 0) )
391 return false;
394 ActionSharedPtr pPolyAction(
395 internal::PolyPolyActionFactory::createPolyPolyAction(
396 rPolyPoly, rParms.mrCanvas, rState ) );
398 if( pPolyAction )
400 maActions.push_back(
401 MtfAction(
402 pPolyAction,
403 rParms.mrCurrActionIndex ) );
405 rParms.mrCurrActionIndex += pPolyAction->getActionCount()-1;
408 return true;
411 bool ImplRenderer::createFillAndStroke( const ::basegfx::B2DPolygon& rPoly,
412 const ActionFactoryParameters& rParms )
414 return createFillAndStroke( ::basegfx::B2DPolyPolygon( rPoly ),
415 rParms );
418 void ImplRenderer::skipContent( GDIMetaFile& rMtf,
419 const char* pCommentString,
420 sal_Int32& io_rCurrActionIndex )
422 ENSURE_OR_THROW( pCommentString,
423 "ImplRenderer::skipContent(): NULL string given" );
425 MetaAction* pCurrAct;
426 while( (pCurrAct=rMtf.NextAction()) != nullptr )
428 // increment action index, we've skipped an action.
429 ++io_rCurrActionIndex;
431 if( pCurrAct->GetType() == MetaActionType::COMMENT &&
432 static_cast<MetaCommentAction*>(pCurrAct)->GetComment().equalsIgnoreAsciiCase(
433 pCommentString) )
435 // requested comment found, done
436 return;
440 // EOF
441 return;
444 bool ImplRenderer::isActionContained( GDIMetaFile& rMtf,
445 const char* pCommentString,
446 MetaActionType nType )
448 ENSURE_OR_THROW( pCommentString,
449 "ImplRenderer::isActionContained(): NULL string given" );
451 bool bRet( false );
453 // at least _one_ call to GDIMetaFile::NextAction() is
454 // executed
455 size_t nPos( 1 );
457 MetaAction* pCurrAct;
458 while( (pCurrAct=rMtf.NextAction()) != nullptr )
460 if( pCurrAct->GetType() == nType )
462 bRet = true; // action type found
463 break;
466 if( pCurrAct->GetType() == MetaActionType::COMMENT &&
467 static_cast<MetaCommentAction*>(pCurrAct)->GetComment().equalsIgnoreAsciiCase(
468 pCommentString) )
470 // delimiting end comment found, done
471 bRet = false; // not yet found
472 break;
475 ++nPos;
478 // rewind metafile to previous position (this method must
479 // not change the current metaaction)
480 while( nPos-- )
481 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( (sal_uInt8)(aVCLStartColor.GetRed() * nStartIntensity / 100) );
534 aVCLStartColor.SetGreen( (sal_uInt8)(aVCLStartColor.GetGreen() * nStartIntensity / 100) );
535 aVCLStartColor.SetBlue( (sal_uInt8)(aVCLStartColor.GetBlue() * nStartIntensity / 100) );
537 const sal_uInt16 nEndIntensity( rGradient.GetEndIntensity() );
538 ::Color aVCLEndColor( rGradient.GetEndColor() );
539 aVCLEndColor.SetRed( (sal_uInt8)(aVCLEndColor.GetRed() * nEndIntensity / 100) );
540 aVCLEndColor.SetGreen( (sal_uInt8)(aVCLEndColor.GetGreen() * nEndIntensity / 100) );
541 aVCLEndColor.SetBlue( (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(2);
553 uno::Sequence< double > aStops(2);
555 if( rGradient.GetStyle() == GradientStyle_AXIAL )
557 aStops.realloc(3);
558 aColors.realloc(3);
560 aStops[0] = 0.0;
561 aStops[1] = 0.5;
562 aStops[2] = 1.0;
564 aColors[0] = aEndColor;
565 aColors[1] = aStartColor;
566 aColors[2] = aEndColor;
568 else
570 aStops[0] = 0.0;
571 aStops[1] = 1.0;
573 aColors[0] = aStartColor;
574 aColors[1] = aEndColor;
577 const ::basegfx::B2DRectangle aBounds(
578 ::basegfx::tools::getRange(aDevicePoly) );
579 const ::basegfx::B2DVector aOffset(
580 rGradient.GetOfsX() / 100.0,
581 rGradient.GetOfsY() / 100.0);
582 double fRotation( rGradient.GetAngle() * M_PI / 1800.0 );
583 const double fBorder( rGradient.GetBorder() / 100.0 );
585 basegfx::B2DHomMatrix aRot90;
586 aRot90.rotate(M_PI_2);
588 basegfx::ODFGradientInfo aGradInfo;
589 OUString aGradientService;
590 switch( rGradient.GetStyle() )
592 case GradientStyle_LINEAR:
593 aGradInfo = basegfx::tools::createLinearODFGradientInfo(
594 aBounds,
595 nSteps,
596 fBorder,
597 fRotation);
598 // map ODF to svg gradient orientation - x
599 // instead of y direction
600 aGradInfo.setTextureTransform(aGradInfo.getTextureTransform() * aRot90);
601 aGradientService = "LinearGradient";
602 break;
604 case GradientStyle_AXIAL:
606 // Adapt the border so that it is suitable
607 // for the axial gradient. An axial
608 // gradient consists of two linear
609 // gradients. Each of those covers half
610 // of the total size. In order to
611 // compensate for the condensed display of
612 // the linear gradients, we have to
613 // enlarge the area taken up by the actual
614 // gradient (1-fBorder). After that we
615 // have to turn the result back into a
616 // border value, hence the second (left
617 // most 1-...
618 const double fAxialBorder (1-2*(1-fBorder));
619 aGradInfo = basegfx::tools::createAxialODFGradientInfo(
620 aBounds,
621 nSteps,
622 fAxialBorder,
623 fRotation);
624 // map ODF to svg gradient orientation - x
625 // instead of y direction
626 aGradInfo.setTextureTransform(aGradInfo.getTextureTransform() * aRot90);
628 // map ODF axial gradient to 3-stop linear
629 // gradient - shift left by 0.5
630 basegfx::B2DHomMatrix aShift;
632 aShift.translate(-0.5,0);
633 aGradInfo.setTextureTransform(aGradInfo.getTextureTransform() * aShift);
634 aGradientService = "LinearGradient";
635 break;
638 case GradientStyle_RADIAL:
639 aGradInfo = basegfx::tools::createRadialODFGradientInfo(
640 aBounds,
641 aOffset,
642 nSteps,
643 fBorder);
644 aGradientService = "EllipticalGradient";
645 break;
647 case GradientStyle_ELLIPTICAL:
648 aGradInfo = basegfx::tools::createEllipticalODFGradientInfo(
649 aBounds,
650 aOffset,
651 nSteps,
652 fBorder,
653 fRotation);
654 aGradientService = "EllipticalGradient";
655 break;
657 case GradientStyle_SQUARE:
658 aGradInfo = basegfx::tools::createSquareODFGradientInfo(
659 aBounds,
660 aOffset,
661 nSteps,
662 fBorder,
663 fRotation);
664 aGradientService = "RectangularGradient";
665 break;
667 case GradientStyle_RECT:
668 aGradInfo = basegfx::tools::createRectangularODFGradientInfo(
669 aBounds,
670 aOffset,
671 nSteps,
672 fBorder,
673 fRotation);
674 aGradientService = "RectangularGradient";
675 break;
677 default:
678 ENSURE_OR_THROW( false,
679 "ImplRenderer::createGradientAction(): Unexpected gradient type" );
680 break;
683 ::basegfx::unotools::affineMatrixFromHomMatrix( aTexture.AffineTransform,
684 aGradInfo.getTextureTransform() );
686 uno::Sequence<uno::Any> args(3);
687 beans::PropertyValue aProp;
688 aProp.Name = "Colors";
689 aProp.Value <<= aColors;
690 args[0] <<= aProp;
691 aProp.Name = "Stops";
692 aProp.Value <<= aStops;
693 args[1] <<= aProp;
694 aProp.Name = "AspectRatio";
695 aProp.Value <<= aGradInfo.getAspectRatio();
696 args[2] <<= aProp;
698 aTexture.Gradient.set(
699 xFactory->createInstanceWithArguments(aGradientService,
700 args),
701 uno::UNO_QUERY);
702 if( aTexture.Gradient.is() )
704 ActionSharedPtr pPolyAction(
705 internal::PolyPolyActionFactory::createPolyPolyAction(
706 aDevicePoly,
707 rParms.mrCanvas,
708 rParms.mrStates.getState(),
709 aTexture ) );
711 if( pPolyAction )
713 maActions.push_back(
714 MtfAction(
715 pPolyAction,
716 rParms.mrCurrActionIndex ) );
718 rParms.mrCurrActionIndex += pPolyAction->getActionCount()-1;
721 // done, using native gradients
722 return;
727 // cannot currently use native canvas gradients, as a
728 // finite step size is given (this funny feature is not
729 // supported by the XCanvas API)
730 rParms.mrStates.pushState(PushFlags::ALL);
732 if( !bIsPolygonRectangle )
734 // only clip, if given polygon is not a rectangle in
735 // the first place (the gradient is always limited to
736 // the given bound rect)
737 updateClipping(
738 aDevicePoly,
739 rParms,
740 true );
743 GDIMetaFile aTmpMtf;
744 rParms.mrVDev.AddGradientActions( rPoly.GetBoundRect(),
745 rGradient,
746 aTmpMtf );
748 createActions( aTmpMtf, rParms, bSubsettableActions );
750 rParms.mrStates.popState();
753 uno::Reference< rendering::XCanvasFont > ImplRenderer::createFont( double& o_rFontRotation,
754 const vcl::Font& rFont,
755 const ActionFactoryParameters& rParms )
757 rendering::FontRequest aFontRequest;
759 if( rParms.mrParms.maFontName.is_initialized() )
760 aFontRequest.FontDescription.FamilyName = *rParms.mrParms.maFontName;
761 else
762 aFontRequest.FontDescription.FamilyName = rFont.GetFamilyName();
764 aFontRequest.FontDescription.StyleName = rFont.GetStyleName();
766 aFontRequest.FontDescription.IsSymbolFont = (rFont.GetCharSet() == RTL_TEXTENCODING_SYMBOL) ? util::TriState_YES : util::TriState_NO;
767 aFontRequest.FontDescription.IsVertical = rFont.IsVertical() ? util::TriState_YES : util::TriState_NO;
769 // TODO(F2): improve vclenum->panose conversion
770 aFontRequest.FontDescription.FontDescription.Weight =
771 rParms.mrParms.maFontWeight.is_initialized() ?
772 *rParms.mrParms.maFontWeight :
773 ::canvas::tools::numeric_cast<sal_Int8>( ::basegfx::fround( rFont.GetWeight() ) );
774 aFontRequest.FontDescription.FontDescription.Letterform =
775 rParms.mrParms.maFontLetterForm.is_initialized() ?
776 *rParms.mrParms.maFontLetterForm :
777 (rFont.GetItalic() == ITALIC_NONE) ? 0 : 9;
778 aFontRequest.FontDescription.FontDescription.Proportion =
779 rParms.mrParms.maFontProportion.is_initialized() ?
780 *rParms.mrParms.maFontProportion :
781 (rFont.GetPitch() == PITCH_FIXED)
782 ? rendering::PanoseProportion::MONO_SPACED
783 : rendering::PanoseProportion::ANYTHING;
785 LanguageType aLang = rFont.GetLanguage();
786 aFontRequest.Locale = LanguageTag::convertToLocale( aLang, false);
788 // setup state-local text transformation,
789 // if the font be rotated
790 const short nFontAngle( rFont.GetOrientation() );
791 if( nFontAngle != 0 )
793 // set to unity transform rotated by font angle
794 const double nAngle( nFontAngle * (F_PI / 1800.0) );
795 o_rFontRotation = -nAngle;
797 else
799 o_rFontRotation = 0.0;
802 geometry::Matrix2D aFontMatrix;
803 ::canvas::tools::setIdentityMatrix2D( aFontMatrix );
805 // TODO(F2): use correct scale direction, font
806 // height might be width or anything else
808 // TODO(Q3): This code smells of programming by
809 // coincidence (the next two if statements)
811 ::Size rFontSizeLog( rFont.GetFontSize() );
813 if (rFontSizeLog.Height() == 0)
815 // guess 16 pixel (as in VCL)
816 rFontSizeLog = ::Size(0, 16);
818 // convert to target MapUnit if not pixels
819 rFontSizeLog = OutputDevice::LogicToLogic(rFontSizeLog, MAP_PIXEL, rParms.mrVDev.GetMapMode());
822 const sal_Int32 nFontWidthLog = rFontSizeLog.Width();
823 if( nFontWidthLog != 0 )
825 vcl::Font aTestFont = rFont;
826 aTestFont.SetAverageFontWidth( 0 );
827 sal_Int32 nNormalWidth = rParms.mrVDev.GetFontMetric( aTestFont ).GetAverageFontWidth();
828 if( nNormalWidth != nFontWidthLog )
829 if( nNormalWidth )
830 aFontMatrix.m00 = (double)nFontWidthLog / nNormalWidth;
833 // #i52608# apply map mode scale also to font matrix - an
834 // anisotrophic mapmode must be reflected in an
835 // anisotrophic font matrix scale.
836 const OutDevState& rState( rParms.mrStates.getState() );
837 if( !::basegfx::fTools::equal(
838 rState.mapModeTransform.get(0,0),
839 rState.mapModeTransform.get(1,1)) )
841 const double nScaleX( rState.mapModeTransform.get(0,0) );
842 const double nScaleY( rState.mapModeTransform.get(1,1) );
844 // note: no reason to check for division by zero, we
845 // always have the value closer (or equal) to zero as
846 // the nominator.
847 if( fabs(nScaleX) < fabs(nScaleY) )
848 aFontMatrix.m00 *= nScaleX / nScaleY;
849 else
850 aFontMatrix.m11 *= nScaleY / nScaleX;
852 aFontRequest.CellSize = (rState.mapModeTransform * vcl::unotools::b2DSizeFromSize(rFontSizeLog)).getY();
854 return rParms.mrCanvas->getUNOCanvas()->createFont( aFontRequest,
855 uno::Sequence< beans::PropertyValue >(),
856 aFontMatrix );
859 // create text effects such as shadow/relief/embossed
860 void ImplRenderer::createTextAction( const ::Point& rStartPoint,
861 const OUString& rString,
862 int nIndex,
863 int nLength,
864 const long* pCharWidths,
865 const ActionFactoryParameters& rParms,
866 bool bSubsettableActions )
868 ENSURE_OR_THROW( nIndex >= 0 && nLength <= rString.getLength() + nIndex,
869 "ImplRenderer::createTextWithEffectsAction(): Invalid text index" );
871 if( !nLength )
872 return; // zero-length text, no visible output
874 const OutDevState& rState( rParms.mrStates.getState() );
876 // TODO(F2): implement all text effects
877 // if( rState.textAlignment ); // TODO(F2): NYI
879 ::Color aShadowColor( COL_AUTO );
880 ::Color aReliefColor( COL_AUTO );
881 ::Size aShadowOffset;
882 ::Size aReliefOffset;
884 uno::Reference<rendering::XColorSpace> xColorSpace(
885 rParms.mrCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() );
887 if( rState.isTextEffectShadowSet )
889 // calculate shadow offset (similar to outdev3.cxx)
890 // TODO(F3): better match with outdev3.cxx
891 sal_Int32 nShadowOffset = static_cast<sal_Int32>(1.5 + ((rParms.mrVDev.GetFont().GetFontHeight()-24.0)/24.0));
892 if( nShadowOffset < 1 )
893 nShadowOffset = 1;
895 aShadowOffset.setWidth( nShadowOffset );
896 aShadowOffset.setHeight( nShadowOffset );
898 // determine shadow color (from outdev3.cxx)
899 ::Color aTextColor = vcl::unotools::doubleSequenceToColor(
900 rState.textColor, xColorSpace );
901 bool bIsDark = (aTextColor.GetColor() == COL_BLACK)
902 || (aTextColor.GetLuminance() < 8);
904 aShadowColor = bIsDark ? COL_LIGHTGRAY : COL_BLACK;
905 aShadowColor.SetTransparency( aTextColor.GetTransparency() );
908 if( rState.textReliefStyle )
910 // calculate relief offset (similar to outdev3.cxx)
911 sal_Int32 nReliefOffset = rParms.mrVDev.PixelToLogic( Size( 1, 1 ) ).Height();
912 nReliefOffset += nReliefOffset/2;
913 if( nReliefOffset < 1 )
914 nReliefOffset = 1;
916 if( rState.textReliefStyle == RELIEF_ENGRAVED )
917 nReliefOffset = -nReliefOffset;
919 aReliefOffset.setWidth( nReliefOffset );
920 aReliefOffset.setHeight( nReliefOffset );
922 // determine relief color (from outdev3.cxx)
923 ::Color aTextColor = vcl::unotools::doubleSequenceToColor(
924 rState.textColor, xColorSpace );
926 aReliefColor = ::Color( COL_LIGHTGRAY );
928 // we don't have a automatic color, so black is always
929 // drawn on white (literally copied from
930 // vcl/source/gdi/outdev3.cxx)
931 if( aTextColor.GetColor() == COL_BLACK )
933 aTextColor = ::Color( COL_WHITE );
934 rParms.mrStates.getState().textColor =
935 vcl::unotools::colorToDoubleSequence(
936 aTextColor, xColorSpace );
939 if( aTextColor.GetColor() == COL_WHITE )
940 aReliefColor = ::Color( COL_BLACK );
941 aReliefColor.SetTransparency( aTextColor.GetTransparency() );
944 // create the actual text action
945 ActionSharedPtr pTextAction(
946 TextActionFactory::createTextAction(
947 rStartPoint,
948 aReliefOffset,
949 aReliefColor,
950 aShadowOffset,
951 aShadowColor,
952 rString,
953 nIndex,
954 nLength,
955 pCharWidths,
956 rParms.mrVDev,
957 rParms.mrCanvas,
958 rState,
959 rParms.mrParms,
960 bSubsettableActions ) );
962 ActionSharedPtr pStrikeoutTextAction;
964 if ( rState.textStrikeoutStyle == STRIKEOUT_X || rState.textStrikeoutStyle == STRIKEOUT_SLASH )
966 long nWidth = rParms.mrVDev.GetTextWidth( rString,nIndex,nLength );
968 sal_Unicode pChars[4];
969 if ( rState.textStrikeoutStyle == STRIKEOUT_X )
970 pChars[0] = 'X';
971 else
972 pChars[0] = '/';
973 pChars[3]=pChars[2]=pChars[1]=pChars[0];
975 long nStrikeoutWidth = (rParms.mrVDev.GetTextWidth(
976 OUString(pChars, SAL_N_ELEMENTS(pChars))) + 2) / 4;
978 if( nStrikeoutWidth <= 0 )
979 nStrikeoutWidth = 1;
981 long nMaxWidth = nStrikeoutWidth/2;
982 if ( nMaxWidth < 2 )
983 nMaxWidth = 2;
984 nMaxWidth += nWidth + 1;
986 long nFullStrikeoutWidth = 0;
987 OUString aStrikeoutText;
988 while( (nFullStrikeoutWidth+=nStrikeoutWidth ) < nMaxWidth+1 )
989 aStrikeoutText += OUString(pChars[0]);
991 sal_Int32 nLen = aStrikeoutText.getLength();
993 if( nLen )
995 long nInterval = ( nWidth - nStrikeoutWidth * nLen ) / nLen;
996 nStrikeoutWidth += nInterval;
997 long* pStrikeoutCharWidths = new long[nLen];
999 for ( int i = 0;i<nLen; i++)
1001 pStrikeoutCharWidths[i] = nStrikeoutWidth;
1004 for ( int i = 1;i< nLen; i++ )
1006 pStrikeoutCharWidths[ i ] += pStrikeoutCharWidths[ i-1 ];
1009 sal_Int32 nStartPos = 0;
1011 pStrikeoutTextAction =
1012 TextActionFactory::createTextAction(
1013 rStartPoint,
1014 aReliefOffset,
1015 aReliefColor,
1016 aShadowOffset,
1017 aShadowColor,
1018 aStrikeoutText,
1019 nStartPos,
1020 aStrikeoutText.getLength(),
1021 pStrikeoutCharWidths,
1022 rParms.mrVDev,
1023 rParms.mrCanvas,
1024 rState,
1025 rParms.mrParms,
1026 bSubsettableActions ) ;
1030 if( pTextAction )
1032 maActions.push_back(
1033 MtfAction(
1034 pTextAction,
1035 rParms.mrCurrActionIndex ) );
1037 if ( pStrikeoutTextAction )
1039 maActions.push_back(
1040 MtfAction(
1041 pStrikeoutTextAction,
1042 rParms.mrCurrActionIndex ) );
1045 rParms.mrCurrActionIndex += pTextAction->getActionCount()-1;
1049 void ImplRenderer::updateClipping( const ::basegfx::B2DPolyPolygon& rClipPoly,
1050 const ActionFactoryParameters& rParms,
1051 bool bIntersect )
1053 ::cppcanvas::internal::OutDevState& rState( rParms.mrStates.getState() );
1055 const bool bEmptyClipRect( rState.clipRect.IsEmpty() );
1056 const bool bEmptyClipPoly( rState.clip.count() == 0 );
1058 ENSURE_OR_THROW( bEmptyClipPoly || bEmptyClipRect,
1059 "ImplRenderer::updateClipping(): Clip rect and polygon are both set!" );
1061 if( !bIntersect ||
1062 (bEmptyClipRect && bEmptyClipPoly) )
1064 rState.clip = rClipPoly;
1066 else
1068 if( !bEmptyClipRect )
1070 // TODO(P3): Use Liang-Barsky polygon clip here,
1071 // after all, one object is just a rectangle!
1073 // convert rect to polygon beforehand, must revert
1074 // to general polygon clipping here.
1075 rState.clip = ::basegfx::B2DPolyPolygon(
1076 ::basegfx::tools::createPolygonFromRect(
1077 // #121100# VCL rectangular clips always
1078 // include one more pixel to the right
1079 // and the bottom
1080 ::basegfx::B2DRectangle( rState.clipRect.Left(),
1081 rState.clipRect.Top(),
1082 rState.clipRect.Right()+1,
1083 rState.clipRect.Bottom()+1 ) ) );
1086 // AW: Simplified
1087 rState.clip = basegfx::tools::clipPolyPolygonOnPolyPolygon(
1088 rClipPoly, rState.clip, true, false);
1091 // by now, our clip resides in the OutDevState::clip
1092 // poly-polygon.
1093 rState.clipRect.SetEmpty();
1095 if( rState.clip.count() == 0 )
1097 if( rState.clipRect.IsEmpty() )
1099 rState.xClipPoly.clear();
1101 else
1103 rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
1104 rParms.mrCanvas->getUNOCanvas()->getDevice(),
1105 ::basegfx::B2DPolyPolygon(
1106 ::basegfx::tools::createPolygonFromRect(
1107 // #121100# VCL rectangular clips
1108 // always include one more pixel to
1109 // the right and the bottom
1110 ::basegfx::B2DRectangle( rState.clipRect.Left(),
1111 rState.clipRect.Top(),
1112 rState.clipRect.Right()+1,
1113 rState.clipRect.Bottom()+1 ) ) ) );
1116 else
1118 rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
1119 rParms.mrCanvas->getUNOCanvas()->getDevice(),
1120 rState.clip );
1124 void ImplRenderer::updateClipping( const ::Rectangle& rClipRect,
1125 const ActionFactoryParameters& rParms,
1126 bool bIntersect )
1128 ::cppcanvas::internal::OutDevState& rState( rParms.mrStates.getState() );
1130 const bool bEmptyClipRect( rState.clipRect.IsEmpty() );
1131 const bool bEmptyClipPoly( rState.clip.count() == 0 );
1133 ENSURE_OR_THROW( bEmptyClipPoly || bEmptyClipRect,
1134 "ImplRenderer::updateClipping(): Clip rect and polygon are both set!" );
1136 if( !bIntersect ||
1137 (bEmptyClipRect && bEmptyClipPoly) )
1139 rState.clipRect = rClipRect;
1140 rState.clip.clear();
1142 else if( bEmptyClipPoly )
1144 rState.clipRect.Intersection( rClipRect );
1145 rState.clip.clear();
1147 else
1149 // TODO(P3): Handle a fourth case here, when all clip
1150 // polygons are rectangular, once B2DMultiRange's
1151 // sweep line implementation is done.
1153 // general case: convert to polygon and clip
1156 // convert rect to polygon beforehand, must revert
1157 // to general polygon clipping here.
1158 ::basegfx::B2DPolyPolygon aClipPoly(
1159 ::basegfx::tools::createPolygonFromRect(
1160 ::basegfx::B2DRectangle( rClipRect.Left(),
1161 rClipRect.Top(),
1162 rClipRect.Right(),
1163 rClipRect.Bottom() ) ) );
1165 rState.clipRect.SetEmpty();
1167 // AW: Simplified
1168 rState.clip = basegfx::tools::clipPolyPolygonOnPolyPolygon(
1169 aClipPoly, rState.clip, true, false);
1172 if( rState.clip.count() == 0 )
1174 if( rState.clipRect.IsEmpty() )
1176 rState.xClipPoly.clear();
1178 else
1180 rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
1181 rParms.mrCanvas->getUNOCanvas()->getDevice(),
1182 ::basegfx::B2DPolyPolygon(
1183 ::basegfx::tools::createPolygonFromRect(
1184 // #121100# VCL rectangular clips
1185 // always include one more pixel to
1186 // the right and the bottom
1187 ::basegfx::B2DRectangle( rState.clipRect.Left(),
1188 rState.clipRect.Top(),
1189 rState.clipRect.Right()+1,
1190 rState.clipRect.Bottom()+1 ) ) ) );
1193 else
1195 rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
1196 rParms.mrCanvas->getUNOCanvas()->getDevice(),
1197 rState.clip );
1201 bool ImplRenderer::createActions( GDIMetaFile& rMtf,
1202 const ActionFactoryParameters& rFactoryParms,
1203 bool bSubsettableActions )
1205 /* TODO(P2): interpret mtf-comments
1206 ================================
1208 - gradient fillings (do that via comments)
1210 - think about mapping. _If_ we do everything in logical
1211 coordinates (which would solve the probs for stroke
1212 widths and text offsets), then we would have to
1213 recalc scaling for every drawing operation. This is
1214 because the outdev map mode might change at any time.
1215 Also keep in mind, that, although we've double precision
1216 float arithmetic now, different offsets might still
1217 generate different roundings (aka
1218 'OutputDevice::SetPixelOffset())
1222 // alias common parameters
1223 VectorOfOutDevStates& rStates(rFactoryParms.mrStates);
1224 const CanvasSharedPtr& rCanvas(rFactoryParms.mrCanvas);
1225 ::VirtualDevice& rVDev(rFactoryParms.mrVDev);
1226 const Parameters& rParms(rFactoryParms.mrParms);
1227 sal_Int32& io_rCurrActionIndex(rFactoryParms.mrCurrActionIndex);
1230 // Loop over every metaaction
1231 // ==========================
1232 MetaAction* pCurrAct;
1234 // TODO(P1): think about caching
1235 for( pCurrAct=rMtf.FirstAction();
1236 pCurrAct;
1237 pCurrAct = rMtf.NextAction() )
1239 // execute every action, to keep VDev state up-to-date
1240 // currently used only for
1241 // - the map mode
1242 // - the line/fill color when processing a MetaActionType::Transparent
1243 // - SetFont to process font metric specific actions
1244 pCurrAct->Execute( &rVDev );
1246 SAL_INFO("cppcanvas.emf", "MTF\trecord type: 0x" << static_cast<sal_uInt16>(pCurrAct->GetType()) << " (" << static_cast<sal_uInt16>(pCurrAct->GetType()) << ")");
1248 switch( pCurrAct->GetType() )
1252 // In the first part of this monster-switch, we
1253 // handle all state-changing meta actions. These
1254 // are all handled locally.
1257 case MetaActionType::PUSH:
1259 MetaPushAction* pPushAction = static_cast<MetaPushAction*>(pCurrAct);
1260 rStates.pushState(pPushAction->GetFlags());
1262 break;
1264 case MetaActionType::POP:
1265 rStates.popState();
1266 break;
1268 case MetaActionType::TEXTLANGUAGE:
1269 // FALLTHROUGH intended
1270 case MetaActionType::REFPOINT:
1271 // handled via pCurrAct->Execute( &rVDev )
1272 break;
1274 case MetaActionType::MAPMODE:
1275 // modify current mapModeTransformation
1276 // transformation, such that subsequent
1277 // coordinates map correctly
1278 tools::calcLogic2PixelAffineTransform( rStates.getState().mapModeTransform,
1279 rVDev );
1280 break;
1282 // monitor clip regions, to assemble clip polygon on our own
1283 case MetaActionType::CLIPREGION:
1285 MetaClipRegionAction* pClipAction = static_cast<MetaClipRegionAction*>(pCurrAct);
1287 if( !pClipAction->IsClipping() )
1289 // clear clipping
1290 rStates.getState().clip.clear();
1292 else
1294 if( !pClipAction->GetRegion().HasPolyPolygonOrB2DPolyPolygon() )
1296 SAL_INFO( "cppcanvas.emf", "ImplRenderer::createActions(): non-polygonal clip "
1297 "region encountered, falling back to bounding box!" );
1299 // #121806# explicitly kept integer
1300 Rectangle aClipRect(
1301 rVDev.LogicToPixel(
1302 pClipAction->GetRegion().GetBoundRect() ) );
1304 // intersect current clip with given rect
1305 updateClipping(
1306 aClipRect,
1307 rFactoryParms,
1308 false );
1310 else
1312 // set new clip polygon (don't intersect
1313 // with old one, just set it)
1315 // #121806# explicitly kept integer
1316 basegfx::B2DPolyPolygon aPolyPolygon(pClipAction->GetRegion().GetAsB2DPolyPolygon());
1318 aPolyPolygon.transform(rVDev.GetViewTransformation());
1319 updateClipping(
1320 aPolyPolygon,
1321 rFactoryParms,
1322 false );
1326 break;
1329 case MetaActionType::ISECTRECTCLIPREGION:
1331 MetaISectRectClipRegionAction* pClipAction = static_cast<MetaISectRectClipRegionAction*>(pCurrAct);
1333 // #121806# explicitly kept integer
1334 Rectangle aClipRect(
1335 rVDev.LogicToPixel( pClipAction->GetRect() ) );
1337 // intersect current clip with given rect
1338 updateClipping(
1339 aClipRect,
1340 rFactoryParms,
1341 true );
1343 break;
1346 case MetaActionType::ISECTREGIONCLIPREGION:
1348 MetaISectRegionClipRegionAction* pClipAction = static_cast<MetaISectRegionClipRegionAction*>(pCurrAct);
1350 if( !pClipAction->GetRegion().HasPolyPolygonOrB2DPolyPolygon() )
1352 SAL_INFO( "cppcanvas.emf", "ImplRenderer::createActions(): non-polygonal clip "
1353 "region encountered, falling back to bounding box!" );
1355 // #121806# explicitly kept integer
1356 Rectangle aClipRect(
1357 rVDev.LogicToPixel( pClipAction->GetRegion().GetBoundRect() ) );
1359 // intersect current clip with given rect
1360 updateClipping(
1361 aClipRect,
1362 rFactoryParms,
1363 true );
1365 else
1367 // intersect current clip with given clip polygon
1369 // #121806# explicitly kept integer
1370 basegfx::B2DPolyPolygon aPolyPolygon(pClipAction->GetRegion().GetAsB2DPolyPolygon());
1372 aPolyPolygon.transform(rVDev.GetViewTransformation());
1373 updateClipping(
1374 aPolyPolygon,
1375 rFactoryParms,
1376 true );
1379 break;
1382 case MetaActionType::MOVECLIPREGION:
1383 // TODO(F2): NYI
1384 break;
1386 case MetaActionType::LINECOLOR:
1387 if( !rParms.maLineColor.is_initialized() )
1389 setStateColor( static_cast<MetaLineColorAction*>(pCurrAct),
1390 rStates.getState().isLineColorSet,
1391 rStates.getState().lineColor,
1392 rCanvas );
1394 else
1396 // #120994# Do switch on/off LineColor, even when a overriding one is set
1397 bool bSetting(static_cast<MetaLineColorAction*>(pCurrAct)->IsSetting());
1399 rStates.getState().isLineColorSet = bSetting;
1401 break;
1403 case MetaActionType::FILLCOLOR:
1404 if( !rParms.maFillColor.is_initialized() )
1406 setStateColor( static_cast<MetaFillColorAction*>(pCurrAct),
1407 rStates.getState().isFillColorSet,
1408 rStates.getState().fillColor,
1409 rCanvas );
1411 else
1413 // #120994# Do switch on/off FillColor, even when a overriding one is set
1414 bool bSetting(static_cast<MetaFillColorAction*>(pCurrAct)->IsSetting());
1416 rStates.getState().isFillColorSet = bSetting;
1418 break;
1420 case MetaActionType::TEXTCOLOR:
1422 if( !rParms.maTextColor.is_initialized() )
1424 // Text color is set unconditionally, thus, no
1425 // use of setStateColor here
1426 ::Color aColor( static_cast<MetaTextColorAction*>(pCurrAct)->GetColor() );
1428 // force alpha part of color to
1429 // opaque. transparent painting is done
1430 // explicitly via MetaActionType::Transparent
1431 aColor.SetTransparency(0);
1433 rStates.getState().textColor =
1434 vcl::unotools::colorToDoubleSequence(
1435 aColor,
1436 rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() );
1439 break;
1441 case MetaActionType::TEXTFILLCOLOR:
1442 if( !rParms.maTextColor.is_initialized() )
1444 setStateColor( static_cast<MetaTextFillColorAction*>(pCurrAct),
1445 rStates.getState().isTextFillColorSet,
1446 rStates.getState().textFillColor,
1447 rCanvas );
1449 else
1451 // #120994# Do switch on/off TextFillColor, even when a overriding one is set
1452 bool bSetting(static_cast<MetaTextFillColorAction*>(pCurrAct)->IsSetting());
1454 rStates.getState().isTextFillColorSet = bSetting;
1456 break;
1458 case MetaActionType::TEXTLINECOLOR:
1459 if( !rParms.maTextColor.is_initialized() )
1461 setStateColor( static_cast<MetaTextLineColorAction*>(pCurrAct),
1462 rStates.getState().isTextLineColorSet,
1463 rStates.getState().textLineColor,
1464 rCanvas );
1466 else
1468 // #120994# Do switch on/off TextLineColor, even when a overriding one is set
1469 bool bSetting(static_cast<MetaTextLineColorAction*>(pCurrAct)->IsSetting());
1471 rStates.getState().isTextLineColorSet = bSetting;
1473 break;
1475 case MetaActionType::TEXTALIGN:
1477 ::cppcanvas::internal::OutDevState& rState = rStates.getState();
1478 const TextAlign eTextAlign( static_cast<MetaTextAlignAction*>(pCurrAct)->GetTextAlign() );
1480 rState.textReferencePoint = eTextAlign;
1482 break;
1484 case MetaActionType::FONT:
1486 ::cppcanvas::internal::OutDevState& rState = rStates.getState();
1487 const vcl::Font& rFont( static_cast<MetaFontAction*>(pCurrAct)->GetFont() );
1489 rState.xFont = createFont( rState.fontRotation,
1490 rFont,
1491 rFactoryParms );
1493 // TODO(Q2): define and use appropriate enumeration types
1494 rState.textReliefStyle = (sal_Int8)rFont.GetRelief();
1495 rState.textOverlineStyle = (sal_Int8)rFont.GetOverline();
1496 rState.textUnderlineStyle = rParms.maFontUnderline.is_initialized() ?
1497 (*rParms.maFontUnderline ? (sal_Int8)LINESTYLE_SINGLE : (sal_Int8)LINESTYLE_NONE) :
1498 (sal_Int8)rFont.GetUnderline();
1499 rState.textStrikeoutStyle = (sal_Int8)rFont.GetStrikeout();
1500 rState.textEmphasisMarkStyle = rFont.GetEmphasisMark() & FontEmphasisMark::Style;
1501 rState.isTextEffectShadowSet = rFont.IsShadow();
1502 rState.isTextWordUnderlineSet = rFont.IsWordLineMode();
1503 rState.isTextOutlineModeSet = rFont.IsOutline();
1505 break;
1507 case MetaActionType::RASTEROP:
1508 // TODO(F2): NYI
1509 break;
1511 case MetaActionType::LAYOUTMODE:
1513 // TODO(F2): A lot is missing here
1514 ComplexTextLayoutMode nLayoutMode = static_cast<MetaLayoutModeAction*>(pCurrAct)->GetLayoutMode();
1515 ::cppcanvas::internal::OutDevState& rState = rStates.getState();
1517 ComplexTextLayoutMode nBidiLayoutMode = nLayoutMode & (TEXT_LAYOUT_BIDI_RTL|TEXT_LAYOUT_BIDI_STRONG);
1518 if( nBidiLayoutMode == TEXT_LAYOUT_DEFAULT)
1519 rState.textDirection = rendering::TextDirection::WEAK_LEFT_TO_RIGHT;
1520 else if( nBidiLayoutMode == TEXT_LAYOUT_BIDI_STRONG)
1521 rState.textDirection = rendering::TextDirection::STRONG_LEFT_TO_RIGHT;
1522 else if( nBidiLayoutMode == TEXT_LAYOUT_BIDI_RTL)
1523 rState.textDirection = rendering::TextDirection::WEAK_RIGHT_TO_LEFT;
1524 else if( nBidiLayoutMode == (TEXT_LAYOUT_BIDI_RTL | TEXT_LAYOUT_BIDI_STRONG))
1525 rState.textDirection = rendering::TextDirection::STRONG_RIGHT_TO_LEFT;
1527 rState.textAlignment = 0; // TODO(F2): rendering::TextAlignment::LEFT_ALIGNED;
1528 if( (nLayoutMode & (TEXT_LAYOUT_BIDI_RTL | TEXT_LAYOUT_TEXTORIGIN_RIGHT) )
1529 && !(nLayoutMode & TEXT_LAYOUT_TEXTORIGIN_LEFT ) )
1531 rState.textAlignment = 1; // TODO(F2): rendering::TextAlignment::RIGHT_ALIGNED;
1534 break;
1537 // In the second part of this monster-switch, we
1538 // handle all recursing meta actions. These are the
1539 // ones generating a metafile by themselves, which is
1540 // then processed by recursively calling this method.
1543 case MetaActionType::GRADIENT:
1545 MetaGradientAction* pGradAct = static_cast<MetaGradientAction*>(pCurrAct);
1546 createGradientAction( ::tools::Polygon( pGradAct->GetRect() ),
1547 pGradAct->GetGradient(),
1548 rFactoryParms,
1549 true,
1550 bSubsettableActions );
1552 break;
1554 case MetaActionType::HATCH:
1556 // TODO(F2): use native Canvas hatches here
1557 GDIMetaFile aTmpMtf;
1559 rVDev.AddHatchActions( static_cast<MetaHatchAction*>(pCurrAct)->GetPolyPolygon(),
1560 static_cast<MetaHatchAction*>(pCurrAct)->GetHatch(),
1561 aTmpMtf );
1562 createActions( aTmpMtf, rFactoryParms,
1563 bSubsettableActions );
1565 break;
1567 case MetaActionType::EPS:
1569 MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pCurrAct);
1570 const GDIMetaFile& rSubstitute = pAct->GetSubstitute();
1572 // #121806# explicitly kept integer
1573 const Size aMtfSize( rSubstitute.GetPrefSize() );
1574 const Size aMtfSizePixPre( rVDev.LogicToPixel( aMtfSize,
1575 rSubstitute.GetPrefMapMode() ) );
1577 // #i44110# correct null-sized output - there
1578 // are metafiles which have zero size in at
1579 // least one dimension
1580 const Size aMtfSizePix( ::std::max( aMtfSizePixPre.Width(), 1L ),
1581 ::std::max( aMtfSizePixPre.Height(), 1L ) );
1583 // Setup local transform, such that the
1584 // metafile renders itself into the given
1585 // output rectangle
1586 rStates.pushState(PushFlags::ALL);
1588 rVDev.Push();
1589 rVDev.SetMapMode( rSubstitute.GetPrefMapMode() );
1591 const ::Point& rPos( rVDev.LogicToPixel( pAct->GetPoint() ) );
1592 const ::Size& rSize( rVDev.LogicToPixel( pAct->GetSize() ) );
1594 rStates.getState().transform.translate( rPos.X(),
1595 rPos.Y() );
1596 rStates.getState().transform.scale( (double)rSize.Width() / aMtfSizePix.Width(),
1597 (double)rSize.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 &&
1621 (pCurrAct=rMtf.NextAction()) != nullptr )
1623 switch( pCurrAct->GetType() )
1625 // extract gradient info
1626 case MetaActionType::GRADIENTEX:
1627 pGradAction = static_cast<MetaGradientExAction*>(pCurrAct);
1628 break;
1630 // skip broken-down rendering, output gradient when sequence is ended
1631 case MetaActionType::COMMENT:
1632 if( static_cast<MetaCommentAction*>(pCurrAct)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END") )
1634 bDone = true;
1636 if( pGradAction )
1638 createGradientAction( pGradAction->GetPolyPolygon(),
1639 pGradAction->GetGradient(),
1640 rFactoryParms,
1641 false,
1642 bSubsettableActions );
1645 break;
1646 default: break;
1650 // TODO(P2): Handle drawing layer strokes, via
1651 // XPATHSTROKE_SEQ_BEGIN comment
1653 // Handle drawing layer fills
1654 else if( pAct->GetComment() == "XPATHFILL_SEQ_BEGIN" )
1656 const sal_uInt8* pData = pAct->GetData();
1657 if ( pData )
1659 SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pData), pAct->GetDataSize(), StreamMode::READ );
1661 SvtGraphicFill aFill;
1662 ReadSvtGraphicFill( aMemStm, aFill );
1664 // TODO(P2): Also handle gradients and
1665 // hatches like this
1667 // only evaluate comment for pure
1668 // bitmap fills. If a transparency
1669 // gradient is involved (denoted by
1670 // the FloatTransparent action), take
1671 // the normal meta actions.
1672 if( aFill.getFillType() == SvtGraphicFill::fillTexture &&
1673 !isActionContained( rMtf,
1674 "XPATHFILL_SEQ_END",
1675 MetaActionType::FLOATTRANSPARENT ) )
1677 rendering::Texture aTexture;
1679 // TODO(F1): the SvtGraphicFill
1680 // can also transport metafiles
1681 // here, handle that case, too
1682 Graphic aGraphic;
1683 aFill.getGraphic( aGraphic );
1685 BitmapEx aBmpEx( aGraphic.GetBitmapEx() );
1686 const ::Size aBmpSize( aBmpEx.GetSizePixel() );
1688 ::SvtGraphicFill::Transform aTransform;
1689 aFill.getTransform( aTransform );
1691 ::basegfx::B2DHomMatrix aMatrix;
1693 // convert to basegfx matrix
1694 aMatrix.set(0,0, aTransform.matrix[ 0 ] );
1695 aMatrix.set(0,1, aTransform.matrix[ 1 ] );
1696 aMatrix.set(0,2, aTransform.matrix[ 2 ] );
1697 aMatrix.set(1,0, aTransform.matrix[ 3 ] );
1698 aMatrix.set(1,1, aTransform.matrix[ 4 ] );
1699 aMatrix.set(1,2, aTransform.matrix[ 5 ] );
1701 ::basegfx::B2DHomMatrix aScale;
1702 aScale.scale( aBmpSize.Width(),
1703 aBmpSize.Height() );
1705 // post-multiply with the bitmap
1706 // size (XCanvas' texture assumes
1707 // the given bitmap to be
1708 // normalized to [0,1]x[0,1]
1709 // rectangle)
1710 aMatrix = aMatrix * aScale;
1712 // pre-multiply with the
1713 // logic-to-pixel scale factor
1714 // (the metafile comment works in
1715 // logical coordinates).
1716 ::basegfx::B2DHomMatrix aLogic2PixelTransform;
1717 aMatrix *= tools::calcLogic2PixelLinearTransform( aLogic2PixelTransform,
1718 rVDev );
1720 ::basegfx::unotools::affineMatrixFromHomMatrix(
1721 aTexture.AffineTransform,
1722 aMatrix );
1724 aTexture.Alpha = 1.0 - aFill.getTransparency();
1725 aTexture.Bitmap =
1726 vcl::unotools::xBitmapFromBitmapEx(
1727 rCanvas->getUNOCanvas()->getDevice(),
1728 aBmpEx );
1729 if( aFill.isTiling() )
1731 aTexture.RepeatModeX = rendering::TexturingMode::REPEAT;
1732 aTexture.RepeatModeY = rendering::TexturingMode::REPEAT;
1734 else
1736 aTexture.RepeatModeX = rendering::TexturingMode::NONE;
1737 aTexture.RepeatModeY = rendering::TexturingMode::NONE;
1740 ::tools::PolyPolygon aPath;
1741 aFill.getPath( aPath );
1743 ::basegfx::B2DPolyPolygon aPoly( aPath.getB2DPolyPolygon() );
1744 aPoly.transform( rStates.getState().mapModeTransform );
1745 ActionSharedPtr pPolyAction(
1746 internal::PolyPolyActionFactory::createPolyPolyAction(
1747 aPoly,
1748 rCanvas,
1749 rStates.getState(),
1750 aTexture ) );
1752 if( pPolyAction )
1754 maActions.push_back(
1755 MtfAction(
1756 pPolyAction,
1757 io_rCurrActionIndex ) );
1759 io_rCurrActionIndex += pPolyAction->getActionCount()-1;
1762 // skip broken-down render output
1763 skipContent( rMtf,
1764 "XPATHFILL_SEQ_END",
1765 io_rCurrActionIndex );
1769 // Handle drawing layer fills
1770 else if( pAct->GetComment() == "EMF_PLUS" ) {
1771 static int count = -1, limit = 0x7fffffff;
1772 if (count == -1) {
1773 count = 0;
1774 if (char *env = getenv ("EMF_PLUS_LIMIT")) {
1775 limit = atoi (env);
1776 SAL_INFO ("cppcanvas.emf", "EMF+ records limit: " << limit);
1779 SAL_INFO ("cppcanvas.emf", "EMF+ passed to canvas mtf renderer, size: " << pAct->GetDataSize ());
1780 if (count < limit)
1781 processEMFPlus( pAct, rFactoryParms, rStates.getState(), rCanvas );
1782 count ++;
1783 } else if( pAct->GetComment() == "EMF_PLUS_HEADER_INFO" ) {
1784 SAL_INFO ("cppcanvas.emf", "EMF+ passed to canvas mtf renderer - header info, size: " << pAct->GetDataSize ());
1786 SvMemoryStream rMF (const_cast<sal_uInt8 *>(pAct->GetData ()), pAct->GetDataSize (), StreamMode::READ);
1788 rMF.ReadInt32( nFrameLeft ).ReadInt32( nFrameTop ).ReadInt32( nFrameRight ).ReadInt32( nFrameBottom );
1789 SAL_INFO ("cppcanvas.emf", "EMF+ picture frame: " << nFrameLeft << "," << nFrameTop << " - " << nFrameRight << "," << nFrameBottom);
1790 rMF.ReadInt32( nPixX ).ReadInt32( nPixY ).ReadInt32( nMmX ).ReadInt32( nMmY );
1791 SAL_INFO ("cppcanvas.emf", "EMF+ ref device pixel size: " << nPixX << "x" << nPixY << " mm size: " << nMmX << "x" << nMmY);
1793 ReadXForm( rMF, aBaseTransform );
1794 //aWorldTransform.Set (aBaseTransform);
1797 break;
1800 // In the third part of this monster-switch, we
1801 // handle all 'acting' meta actions. These are all
1802 // processed by constructing function objects for
1803 // them, which will later ease caching.
1806 case MetaActionType::POINT:
1808 const OutDevState& rState( rStates.getState() );
1809 if( rState.lineColor.getLength() )
1811 ActionSharedPtr pPointAction(
1812 internal::PointActionFactory::createPointAction(
1813 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint(
1814 static_cast<MetaPointAction*>(pCurrAct)->GetPoint() ),
1815 rCanvas,
1816 rState ) );
1818 if( pPointAction )
1820 maActions.push_back(
1821 MtfAction(
1822 pPointAction,
1823 io_rCurrActionIndex ) );
1825 io_rCurrActionIndex += pPointAction->getActionCount()-1;
1829 break;
1831 case MetaActionType::PIXEL:
1833 const OutDevState& rState( rStates.getState() );
1834 if( rState.lineColor.getLength() )
1836 ActionSharedPtr pPointAction(
1837 internal::PointActionFactory::createPointAction(
1838 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint(
1839 static_cast<MetaPixelAction*>(pCurrAct)->GetPoint() ),
1840 rCanvas,
1841 rState,
1842 static_cast<MetaPixelAction*>(pCurrAct)->GetColor() ) );
1844 if( pPointAction )
1846 maActions.push_back(
1847 MtfAction(
1848 pPointAction,
1849 io_rCurrActionIndex ) );
1851 io_rCurrActionIndex += pPointAction->getActionCount()-1;
1855 break;
1857 case MetaActionType::LINE:
1859 const OutDevState& rState( rStates.getState() );
1860 if( rState.lineColor.getLength() )
1862 MetaLineAction* pLineAct = static_cast<MetaLineAction*>(pCurrAct);
1864 const LineInfo& rLineInfo( pLineAct->GetLineInfo() );
1866 const ::basegfx::B2DPoint aStartPoint(
1867 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( pLineAct->GetStartPoint() ));
1868 const ::basegfx::B2DPoint aEndPoint(
1869 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( pLineAct->GetEndPoint() ));
1871 ActionSharedPtr pLineAction;
1873 if( rLineInfo.IsDefault() )
1875 // plain hair line
1876 pLineAction =
1877 internal::LineActionFactory::createLineAction(
1878 aStartPoint,
1879 aEndPoint,
1880 rCanvas,
1881 rState );
1883 if( pLineAction )
1885 maActions.push_back(
1886 MtfAction(
1887 pLineAction,
1888 io_rCurrActionIndex ) );
1890 io_rCurrActionIndex += pLineAction->getActionCount()-1;
1893 else if( LINE_NONE != rLineInfo.GetStyle() )
1895 // 'thick' line
1896 rendering::StrokeAttributes aStrokeAttributes;
1898 setupStrokeAttributes( aStrokeAttributes,
1899 rFactoryParms,
1900 rLineInfo );
1902 // XCanvas can only stroke polygons,
1903 // not simple lines - thus, handle
1904 // this case via the polypolygon
1905 // action
1906 ::basegfx::B2DPolygon aPoly;
1907 aPoly.append( aStartPoint );
1908 aPoly.append( aEndPoint );
1909 pLineAction =
1910 internal::PolyPolyActionFactory::createPolyPolyAction(
1911 ::basegfx::B2DPolyPolygon( aPoly ),
1912 rCanvas, rState, aStrokeAttributes );
1914 if( pLineAction )
1916 maActions.push_back(
1917 MtfAction(
1918 pLineAction,
1919 io_rCurrActionIndex ) );
1921 io_rCurrActionIndex += pLineAction->getActionCount()-1;
1924 // else: line style is default
1925 // (i.e. invisible), don't generate action
1928 break;
1930 case MetaActionType::RECT:
1932 const Rectangle& rRect(
1933 static_cast<MetaRectAction*>(pCurrAct)->GetRect() );
1935 if( rRect.IsEmpty() )
1936 break;
1938 const OutDevState& rState( rStates.getState() );
1939 const ::basegfx::B2DPoint aTopLeftPixel(
1940 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( rRect.TopLeft() ) );
1941 const ::basegfx::B2DPoint aBottomRightPixel(
1942 rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( rRect.BottomRight() ) +
1943 // #121100# OutputDevice::DrawRect() fills
1944 // rectangles Apple-like, i.e. with one
1945 // additional pixel to the right and bottom.
1946 ::basegfx::B2DPoint(1,1) );
1948 createFillAndStroke( ::basegfx::tools::createPolygonFromRect(
1949 ::basegfx::B2DRange( aTopLeftPixel,
1950 aBottomRightPixel )),
1951 rFactoryParms );
1952 break;
1955 case MetaActionType::ROUNDRECT:
1957 const Rectangle& rRect(
1958 static_cast<MetaRoundRectAction*>(pCurrAct)->GetRect());
1960 if( rRect.IsEmpty() )
1961 break;
1963 ::basegfx::B2DPolygon aPoly(
1964 ::basegfx::tools::createPolygonFromRect(
1965 ::basegfx::B2DRange(
1966 vcl::unotools::b2DPointFromPoint( rRect.TopLeft() ),
1967 vcl::unotools::b2DPointFromPoint( rRect.BottomRight() ) +
1968 ::basegfx::B2DPoint(1,1) ),
1969 ( (double) static_cast<MetaRoundRectAction*>(pCurrAct)->GetHorzRound() ) / rRect.GetWidth(),
1970 ( (double) static_cast<MetaRoundRectAction*>(pCurrAct)->GetVertRound() ) / rRect.GetHeight() ) );
1971 aPoly.transform( rStates.getState().mapModeTransform );
1973 createFillAndStroke( aPoly,
1974 rFactoryParms );
1976 break;
1978 case MetaActionType::ELLIPSE:
1980 const Rectangle& rRect(
1981 static_cast<MetaEllipseAction*>(pCurrAct)->GetRect() );
1983 if( rRect.IsEmpty() )
1984 break;
1986 const ::basegfx::B2DRange aRange(
1987 vcl::unotools::b2DPointFromPoint( rRect.TopLeft() ),
1988 vcl::unotools::b2DPointFromPoint( rRect.BottomRight() ) +
1989 ::basegfx::B2DPoint(1,1) );
1991 ::basegfx::B2DPolygon aPoly(
1992 ::basegfx::tools::createPolygonFromEllipse(
1993 aRange.getCenter(),
1994 aRange.getWidth(),
1995 aRange.getHeight() ));
1996 aPoly.transform( rStates.getState().mapModeTransform );
1998 createFillAndStroke( aPoly,
1999 rFactoryParms );
2001 break;
2003 case MetaActionType::ARC:
2005 // TODO(F1): Missing basegfx functionality. Mind empty rects!
2006 const ::tools::Polygon aToolsPoly( static_cast<MetaArcAction*>(pCurrAct)->GetRect(),
2007 static_cast<MetaArcAction*>(pCurrAct)->GetStartPoint(),
2008 static_cast<MetaArcAction*>(pCurrAct)->GetEndPoint(), POLY_ARC );
2009 ::basegfx::B2DPolygon aPoly( aToolsPoly.getB2DPolygon() );
2010 aPoly.transform( rStates.getState().mapModeTransform );
2012 createFillAndStroke( aPoly,
2013 rFactoryParms );
2015 break;
2017 case MetaActionType::PIE:
2019 // TODO(F1): Missing basegfx functionality. Mind empty rects!
2020 const ::tools::Polygon aToolsPoly( static_cast<MetaPieAction*>(pCurrAct)->GetRect(),
2021 static_cast<MetaPieAction*>(pCurrAct)->GetStartPoint(),
2022 static_cast<MetaPieAction*>(pCurrAct)->GetEndPoint(), POLY_PIE );
2023 ::basegfx::B2DPolygon aPoly( aToolsPoly.getB2DPolygon() );
2024 aPoly.transform( rStates.getState().mapModeTransform );
2026 createFillAndStroke( aPoly,
2027 rFactoryParms );
2029 break;
2031 case MetaActionType::CHORD:
2033 // TODO(F1): Missing basegfx functionality. Mind empty rects!
2034 const ::tools::Polygon aToolsPoly( static_cast<MetaChordAction*>(pCurrAct)->GetRect(),
2035 static_cast<MetaChordAction*>(pCurrAct)->GetStartPoint(),
2036 static_cast<MetaChordAction*>(pCurrAct)->GetEndPoint(), POLY_CHORD );
2037 ::basegfx::B2DPolygon aPoly( aToolsPoly.getB2DPolygon() );
2038 aPoly.transform( rStates.getState().mapModeTransform );
2040 createFillAndStroke( aPoly,
2041 rFactoryParms );
2043 break;
2045 case MetaActionType::POLYLINE:
2047 const OutDevState& rState( rStates.getState() );
2048 if( rState.lineColor.getLength() ||
2049 rState.fillColor.getLength() )
2051 MetaPolyLineAction* pPolyLineAct = static_cast<MetaPolyLineAction*>(pCurrAct);
2053 const LineInfo& rLineInfo( pPolyLineAct->GetLineInfo() );
2054 ::basegfx::B2DPolygon aPoly( pPolyLineAct->GetPolygon().getB2DPolygon() );
2055 aPoly.transform( rState.mapModeTransform );
2057 ActionSharedPtr pLineAction;
2059 if( rLineInfo.IsDefault() )
2061 // plain hair line polygon
2062 pLineAction =
2063 internal::PolyPolyActionFactory::createLinePolyPolyAction(
2064 ::basegfx::B2DPolyPolygon(aPoly),
2065 rCanvas,
2066 rState );
2068 if( pLineAction )
2070 maActions.push_back(
2071 MtfAction(
2072 pLineAction,
2073 io_rCurrActionIndex ) );
2075 io_rCurrActionIndex += pLineAction->getActionCount()-1;
2078 else if( LINE_NONE != rLineInfo.GetStyle() )
2080 // 'thick' line polygon
2081 rendering::StrokeAttributes aStrokeAttributes;
2083 setupStrokeAttributes( aStrokeAttributes,
2084 rFactoryParms,
2085 rLineInfo );
2087 pLineAction =
2088 internal::PolyPolyActionFactory::createPolyPolyAction(
2089 ::basegfx::B2DPolyPolygon(aPoly),
2090 rCanvas,
2091 rState,
2092 aStrokeAttributes ) ;
2094 if( pLineAction )
2096 maActions.push_back(
2097 MtfAction(
2098 pLineAction,
2099 io_rCurrActionIndex ) );
2101 io_rCurrActionIndex += pLineAction->getActionCount()-1;
2104 // else: line style is default
2105 // (i.e. invisible), don't generate action
2108 break;
2110 case MetaActionType::POLYGON:
2112 ::basegfx::B2DPolygon aPoly( static_cast<MetaPolygonAction*>(pCurrAct)->GetPolygon().getB2DPolygon() );
2113 aPoly.transform( rStates.getState().mapModeTransform );
2114 createFillAndStroke( aPoly,
2115 rFactoryParms );
2117 break;
2119 case MetaActionType::POLYPOLYGON:
2121 ::basegfx::B2DPolyPolygon aPoly( static_cast<MetaPolyPolygonAction*>(pCurrAct)->GetPolyPolygon().getB2DPolyPolygon() );
2122 aPoly.transform( rStates.getState().mapModeTransform );
2123 createFillAndStroke( aPoly,
2124 rFactoryParms );
2126 break;
2128 case MetaActionType::BMP:
2130 MetaBmpAction* pAct = static_cast<MetaBmpAction*>(pCurrAct);
2132 ActionSharedPtr pBmpAction(
2133 internal::BitmapActionFactory::createBitmapAction(
2134 pAct->GetBitmap(),
2135 rStates.getState().mapModeTransform *
2136 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2137 rCanvas,
2138 rStates.getState() ) );
2140 if( pBmpAction )
2142 maActions.push_back(
2143 MtfAction(
2144 pBmpAction,
2145 io_rCurrActionIndex ) );
2147 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2150 break;
2152 case MetaActionType::BMPSCALE:
2154 MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pCurrAct);
2156 ActionSharedPtr pBmpAction(
2157 internal::BitmapActionFactory::createBitmapAction(
2158 pAct->GetBitmap(),
2159 rStates.getState().mapModeTransform *
2160 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2161 rStates.getState().mapModeTransform *
2162 vcl::unotools::b2DSizeFromSize( pAct->GetSize() ),
2163 rCanvas,
2164 rStates.getState() ) );
2166 if( pBmpAction )
2168 maActions.push_back(
2169 MtfAction(
2170 pBmpAction,
2171 io_rCurrActionIndex ) );
2173 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2176 break;
2178 case MetaActionType::BMPSCALEPART:
2180 MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pCurrAct);
2182 // crop bitmap to given source rectangle (no
2183 // need to copy and convert the whole bitmap)
2184 ::Bitmap aBmp( pAct->GetBitmap() );
2185 const Rectangle aCropRect( pAct->GetSrcPoint(),
2186 pAct->GetSrcSize() );
2187 aBmp.Crop( aCropRect );
2189 ActionSharedPtr pBmpAction(
2190 internal::BitmapActionFactory::createBitmapAction(
2191 aBmp,
2192 rStates.getState().mapModeTransform *
2193 vcl::unotools::b2DPointFromPoint( pAct->GetDestPoint() ),
2194 rStates.getState().mapModeTransform *
2195 vcl::unotools::b2DSizeFromSize( pAct->GetDestSize() ),
2196 rCanvas,
2197 rStates.getState() ) );
2199 if( pBmpAction )
2201 maActions.push_back(
2202 MtfAction(
2203 pBmpAction,
2204 io_rCurrActionIndex ) );
2206 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2209 break;
2211 case MetaActionType::BMPEX:
2213 MetaBmpExAction* pAct = static_cast<MetaBmpExAction*>(pCurrAct);
2215 ActionSharedPtr pBmpAction(
2216 internal::BitmapActionFactory::createBitmapAction(
2217 pAct->GetBitmapEx(),
2218 rStates.getState().mapModeTransform *
2219 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2220 rCanvas,
2221 rStates.getState() ) );
2223 if( pBmpAction )
2225 maActions.push_back(
2226 MtfAction(
2227 pBmpAction,
2228 io_rCurrActionIndex ) );
2230 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2233 break;
2235 case MetaActionType::BMPEXSCALE:
2237 MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pCurrAct);
2239 ActionSharedPtr pBmpAction(
2240 internal::BitmapActionFactory::createBitmapAction(
2241 pAct->GetBitmapEx(),
2242 rStates.getState().mapModeTransform *
2243 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2244 rStates.getState().mapModeTransform *
2245 vcl::unotools::b2DSizeFromSize( pAct->GetSize() ),
2246 rCanvas,
2247 rStates.getState() ) );
2249 if( pBmpAction )
2251 maActions.push_back(
2252 MtfAction(
2253 pBmpAction,
2254 io_rCurrActionIndex ) );
2256 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2259 break;
2261 case MetaActionType::BMPEXSCALEPART:
2263 MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pCurrAct);
2265 // crop bitmap to given source rectangle (no
2266 // need to copy and convert the whole bitmap)
2267 BitmapEx aBmp( pAct->GetBitmapEx() );
2268 const Rectangle aCropRect( pAct->GetSrcPoint(),
2269 pAct->GetSrcSize() );
2270 aBmp.Crop( aCropRect );
2272 ActionSharedPtr pBmpAction(
2273 internal::BitmapActionFactory::createBitmapAction(
2274 aBmp,
2275 rStates.getState().mapModeTransform *
2276 vcl::unotools::b2DPointFromPoint( pAct->GetDestPoint() ),
2277 rStates.getState().mapModeTransform *
2278 vcl::unotools::b2DSizeFromSize( pAct->GetDestSize() ),
2279 rCanvas,
2280 rStates.getState() ) );
2282 if( pBmpAction )
2284 maActions.push_back(
2285 MtfAction(
2286 pBmpAction,
2287 io_rCurrActionIndex ) );
2289 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2292 break;
2294 case MetaActionType::MASK:
2296 MetaMaskAction* pAct = static_cast<MetaMaskAction*>(pCurrAct);
2298 // create masked BitmapEx right here, as the
2299 // canvas does not provide equivalent
2300 // functionality
2301 BitmapEx aBmp( createMaskBmpEx( pAct->GetBitmap(),
2302 pAct->GetColor() ));
2304 ActionSharedPtr pBmpAction(
2305 internal::BitmapActionFactory::createBitmapAction(
2306 aBmp,
2307 rStates.getState().mapModeTransform *
2308 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2309 rCanvas,
2310 rStates.getState() ) );
2312 if( pBmpAction )
2314 maActions.push_back(
2315 MtfAction(
2316 pBmpAction,
2317 io_rCurrActionIndex ) );
2319 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2322 break;
2324 case MetaActionType::MASKSCALE:
2326 MetaMaskScaleAction* pAct = static_cast<MetaMaskScaleAction*>(pCurrAct);
2328 // create masked BitmapEx right here, as the
2329 // canvas does not provide equivalent
2330 // functionality
2331 BitmapEx aBmp( createMaskBmpEx( pAct->GetBitmap(),
2332 pAct->GetColor() ));
2334 ActionSharedPtr pBmpAction(
2335 internal::BitmapActionFactory::createBitmapAction(
2336 aBmp,
2337 rStates.getState().mapModeTransform *
2338 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2339 rStates.getState().mapModeTransform *
2340 vcl::unotools::b2DSizeFromSize( pAct->GetSize() ),
2341 rCanvas,
2342 rStates.getState() ) );
2344 if( pBmpAction )
2346 maActions.push_back(
2347 MtfAction(
2348 pBmpAction,
2349 io_rCurrActionIndex ) );
2351 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2354 break;
2356 case MetaActionType::MASKSCALEPART:
2358 MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pCurrAct);
2360 // create masked BitmapEx right here, as the
2361 // canvas does not provide equivalent
2362 // functionality
2363 BitmapEx aBmp( createMaskBmpEx( pAct->GetBitmap(),
2364 pAct->GetColor() ));
2366 // crop bitmap to given source rectangle (no
2367 // need to copy and convert the whole bitmap)
2368 const Rectangle aCropRect( pAct->GetSrcPoint(),
2369 pAct->GetSrcSize() );
2370 aBmp.Crop( aCropRect );
2372 ActionSharedPtr pBmpAction(
2373 internal::BitmapActionFactory::createBitmapAction(
2374 aBmp,
2375 rStates.getState().mapModeTransform *
2376 vcl::unotools::b2DPointFromPoint( pAct->GetDestPoint() ),
2377 rStates.getState().mapModeTransform *
2378 vcl::unotools::b2DSizeFromSize( pAct->GetDestSize() ),
2379 rCanvas,
2380 rStates.getState() ) );
2382 if( pBmpAction )
2384 maActions.push_back(
2385 MtfAction(
2386 pBmpAction,
2387 io_rCurrActionIndex ) );
2389 io_rCurrActionIndex += pBmpAction->getActionCount()-1;
2392 break;
2394 case MetaActionType::GRADIENTEX:
2395 // TODO(F1): use native Canvas gradients here
2396 // action is ignored here, because redundant to MetaActionType::GRADIENT
2397 break;
2399 case MetaActionType::WALLPAPER:
2400 // TODO(F2): NYI
2401 break;
2403 case MetaActionType::Transparent:
2405 const OutDevState& rState( rStates.getState() );
2406 if( rState.lineColor.getLength() ||
2407 rState.fillColor.getLength() )
2409 MetaTransparentAction* pAct = static_cast<MetaTransparentAction*>(pCurrAct);
2410 ::basegfx::B2DPolyPolygon aPoly( pAct->GetPolyPolygon().getB2DPolyPolygon() );
2411 aPoly.transform( rState.mapModeTransform );
2413 ActionSharedPtr pPolyAction(
2414 internal::PolyPolyActionFactory::createPolyPolyAction(
2415 aPoly,
2416 rCanvas,
2417 rState,
2418 pAct->GetTransparence() ) );
2420 if( pPolyAction )
2422 maActions.push_back(
2423 MtfAction(
2424 pPolyAction,
2425 io_rCurrActionIndex ) );
2427 io_rCurrActionIndex += pPolyAction->getActionCount()-1;
2431 break;
2433 case MetaActionType::FLOATTRANSPARENT:
2435 MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pCurrAct);
2437 internal::MtfAutoPtr pMtf(
2438 new ::GDIMetaFile( pAct->GetGDIMetaFile() ) );
2440 // TODO(P2): Use native canvas gradients here (saves a lot of UNO calls)
2441 internal::GradientAutoPtr pGradient(
2442 new Gradient( pAct->GetGradient() ) );
2444 DBG_TESTSOLARMUTEX();
2446 ActionSharedPtr pFloatTransAction(
2447 internal::TransparencyGroupActionFactory::createTransparencyGroupAction(
2448 std::move(pMtf),
2449 std::move(pGradient),
2450 rStates.getState().mapModeTransform *
2451 vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ),
2452 rStates.getState().mapModeTransform *
2453 vcl::unotools::b2DSizeFromSize( pAct->GetSize() ),
2454 rCanvas,
2455 rStates.getState() ) );
2457 if( pFloatTransAction )
2459 maActions.push_back(
2460 MtfAction(
2461 pFloatTransAction,
2462 io_rCurrActionIndex ) );
2464 io_rCurrActionIndex += pFloatTransAction->getActionCount()-1;
2467 break;
2469 case MetaActionType::TEXT:
2471 MetaTextAction* pAct = static_cast<MetaTextAction*>(pCurrAct);
2472 OUString sText = pAct->GetText();
2474 if (rVDev.GetDigitLanguage())
2475 sText = convertToLocalizedNumerals(sText, rVDev.GetDigitLanguage());
2477 const sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex());
2479 createTextAction(
2480 pAct->GetPoint(),
2481 sText,
2482 pAct->GetIndex(),
2483 nLen,
2484 nullptr,
2485 rFactoryParms,
2486 bSubsettableActions );
2488 break;
2490 case MetaActionType::TEXTARRAY:
2492 MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pCurrAct);
2493 OUString sText = pAct->GetText();
2495 if (rVDev.GetDigitLanguage())
2496 sText = convertToLocalizedNumerals(sText, rVDev.GetDigitLanguage());
2498 const sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex());
2500 createTextAction(
2501 pAct->GetPoint(),
2502 sText,
2503 pAct->GetIndex(),
2504 nLen,
2505 pAct->GetDXArray(),
2506 rFactoryParms,
2507 bSubsettableActions );
2509 break;
2511 case MetaActionType::TEXTLINE:
2513 MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pCurrAct);
2515 const OutDevState& rState( rStates.getState() );
2516 const ::Size aBaselineOffset( tools::getBaselineOffset( rState,
2517 rVDev ) );
2518 const ::basegfx::B2DSize aSize( rState.mapModeTransform *
2519 ::basegfx::B2DSize(pAct->GetWidth(),
2520 0 ));
2522 ActionSharedPtr pPolyAction(
2523 PolyPolyActionFactory::createPolyPolyAction(
2524 tools::createTextLinesPolyPolygon(
2525 rState.mapModeTransform *
2526 ::basegfx::B2DPoint(
2527 vcl::unotools::b2DPointFromPoint(pAct->GetStartPoint()) +
2528 vcl::unotools::b2DSizeFromSize(aBaselineOffset)),
2529 aSize.getX(),
2530 tools::createTextLineInfo( rVDev,
2531 rState )),
2532 rCanvas,
2533 rState ) );
2535 if( pPolyAction.get() )
2537 maActions.push_back(
2538 MtfAction(
2539 pPolyAction,
2540 io_rCurrActionIndex ) );
2542 io_rCurrActionIndex += pPolyAction->getActionCount()-1;
2545 break;
2547 case MetaActionType::TEXTRECT:
2549 MetaTextRectAction* pAct = static_cast<MetaTextRectAction*>(pCurrAct);
2551 rStates.pushState(PushFlags::ALL);
2553 // use the VDev to break up the text rect
2554 // action into readily formatted lines
2555 GDIMetaFile aTmpMtf;
2556 rVDev.AddTextRectActions( pAct->GetRect(),
2557 pAct->GetText(),
2558 pAct->GetStyle(),
2559 aTmpMtf );
2561 createActions( aTmpMtf,
2562 rFactoryParms,
2563 bSubsettableActions );
2565 rStates.popState();
2567 break;
2570 case MetaActionType::STRETCHTEXT:
2572 MetaStretchTextAction* pAct = static_cast<MetaStretchTextAction*>(pCurrAct);
2573 OUString sText = pAct->GetText();
2575 if (rVDev.GetDigitLanguage())
2576 sText = convertToLocalizedNumerals(sText, rVDev.GetDigitLanguage());
2578 const sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex());
2580 // #i70897# Nothing to do, actually...
2581 if( nLen == 0 )
2582 break;
2584 // have to fit the text into the given
2585 // width. This is achieved by internally
2586 // generating a DX array, and uniformly
2587 // distributing the excess/insufficient width
2588 // to every logical character.
2589 ::std::unique_ptr< long []> pDXArray( new long[nLen] );
2591 rVDev.GetTextArray( pAct->GetText(), pDXArray.get(),
2592 pAct->GetIndex(), pAct->GetLen() );
2594 const sal_Int32 nWidthDifference( pAct->GetWidth() - pDXArray[ nLen-1 ] );
2596 // Last entry of pDXArray contains total width of the text
2597 long* p = pDXArray.get();
2598 for (sal_Int32 i = 1; i <= nLen; ++i)
2600 // calc ratio for every array entry, to
2601 // distribute rounding errors 'evenly'
2602 // across the characters. Note that each
2603 // entry represents the 'end' position of
2604 // the corresponding character, thus, we
2605 // let i run from 1 to nLen.
2606 *p++ += (long)i*nWidthDifference/nLen;
2609 createTextAction(
2610 pAct->GetPoint(),
2611 sText,
2612 pAct->GetIndex(),
2613 nLen,
2614 pDXArray.get(),
2615 rFactoryParms,
2616 bSubsettableActions );
2618 break;
2620 default:
2621 SAL_WARN( "cppcanvas.emf", "Unknown meta action type encountered" );
2622 break;
2625 // increment action index (each mtf action counts _at
2626 // least_ one. Some count for more, therefore,
2627 // io_rCurrActionIndex is sometimes incremented by
2628 // pAct->getActionCount()-1 above, the -1 being the
2629 // correction for the unconditional increment here).
2630 ++io_rCurrActionIndex;
2633 return true;
2637 namespace
2639 class ActionRenderer
2641 public:
2642 explicit ActionRenderer( const ::basegfx::B2DHomMatrix& rTransformation ) :
2643 maTransformation( rTransformation ),
2644 mbRet( true )
2648 bool result() const
2650 return mbRet;
2653 void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction )
2655 // ANDing the result. We want to fail if at least
2656 // one action failed.
2657 mbRet &= rAction.mpAction->render( maTransformation );
2660 void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction,
2661 const Action::Subset& rSubset )
2663 // ANDing the result. We want to fail if at least
2664 // one action failed.
2665 mbRet &= rAction.mpAction->renderSubset( maTransformation,
2666 rSubset );
2669 private:
2670 ::basegfx::B2DHomMatrix maTransformation;
2671 bool mbRet;
2674 class AreaQuery
2676 public:
2677 explicit AreaQuery( const ::basegfx::B2DHomMatrix& rTransformation ) :
2678 maTransformation( rTransformation ),
2679 maBounds()
2683 static bool result()
2685 return true; // nothing can fail here
2688 void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction )
2690 maBounds.expand( rAction.mpAction->getBounds( maTransformation ) );
2693 void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction,
2694 const Action::Subset& rSubset )
2696 maBounds.expand( rAction.mpAction->getBounds( maTransformation,
2697 rSubset ) );
2700 const ::basegfx::B2DRange& getBounds() const
2702 return maBounds;
2705 private:
2706 ::basegfx::B2DHomMatrix maTransformation;
2707 ::basegfx::B2DRange maBounds;
2710 // Doing that via inline class. Compilers tend to not inline free
2711 // functions.
2712 struct UpperBoundActionIndexComparator
2714 bool operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rLHS,
2715 const ::cppcanvas::internal::ImplRenderer::MtfAction& rRHS )
2717 const sal_Int32 nLHSCount( rLHS.mpAction ?
2718 rLHS.mpAction->getActionCount() : 0 );
2719 const sal_Int32 nRHSCount( rRHS.mpAction ?
2720 rRHS.mpAction->getActionCount() : 0 );
2722 // compare end of action range, to have an action selected
2723 // by lower_bound even if the requested index points in
2724 // the middle of the action's range
2725 return rLHS.mnOrigIndex + nLHSCount < rRHS.mnOrigIndex + nRHSCount;
2729 /** Algorithm to apply given functor to a subset range
2731 @tpl Functor
2733 Functor to call for each element of the subset
2734 range. Must provide the following method signatures:
2735 bool result() (returning false if operation failed)
2738 template< typename Functor > bool
2739 forSubsetRange( Functor& rFunctor,
2740 ImplRenderer::ActionVector::const_iterator aRangeBegin,
2741 const ImplRenderer::ActionVector::const_iterator& aRangeEnd,
2742 sal_Int32 nStartIndex,
2743 sal_Int32 nEndIndex,
2744 const ImplRenderer::ActionVector::const_iterator& rEnd )
2746 if( aRangeBegin == aRangeEnd )
2748 // only a single action. Setup subset, and call functor
2749 Action::Subset aSubset;
2750 aSubset.mnSubsetBegin = ::std::max( sal_Int32( 0 ),
2751 nStartIndex - aRangeBegin->mnOrigIndex );
2752 aSubset.mnSubsetEnd = ::std::min( aRangeBegin->mpAction->getActionCount(),
2753 nEndIndex - aRangeBegin->mnOrigIndex );
2755 ENSURE_OR_RETURN_FALSE( aSubset.mnSubsetBegin >= 0 && aSubset.mnSubsetEnd >= 0,
2756 "ImplRenderer::forSubsetRange(): Invalid indices" );
2758 rFunctor( *aRangeBegin, aSubset );
2760 else
2762 // more than one action.
2764 // render partial first, full intermediate, and
2765 // partial last action
2766 Action::Subset aSubset;
2767 aSubset.mnSubsetBegin = ::std::max( sal_Int32( 0 ),
2768 nStartIndex - aRangeBegin->mnOrigIndex );
2769 aSubset.mnSubsetEnd = aRangeBegin->mpAction->getActionCount();
2771 ENSURE_OR_RETURN_FALSE( aSubset.mnSubsetBegin >= 0 && aSubset.mnSubsetEnd >= 0,
2772 "ImplRenderer::forSubsetRange(): Invalid indices" );
2774 rFunctor( *aRangeBegin, aSubset );
2776 // first action rendered, skip to next
2777 ++aRangeBegin;
2779 // render full middle actions
2780 while( aRangeBegin != aRangeEnd )
2781 rFunctor( *aRangeBegin++ );
2783 if( aRangeEnd == rEnd ||
2784 aRangeEnd->mnOrigIndex > nEndIndex )
2786 // aRangeEnd denotes end of action vector,
2788 // or
2790 // nEndIndex references something _after_
2791 // aRangeBegin, but _before_ aRangeEnd
2793 // either way: no partial action left
2794 return rFunctor.result();
2797 aSubset.mnSubsetBegin = 0;
2798 aSubset.mnSubsetEnd = nEndIndex - aRangeEnd->mnOrigIndex;
2800 ENSURE_OR_RETURN_FALSE( aSubset.mnSubsetBegin >= 0 && aSubset.mnSubsetEnd >= 0,
2801 "ImplRenderer::forSubsetRange(): Invalid indices" );
2803 rFunctor( *aRangeEnd, aSubset );
2806 return rFunctor.result();
2810 bool ImplRenderer::getSubsetIndices( sal_Int32& io_rStartIndex,
2811 sal_Int32& io_rEndIndex,
2812 ActionVector::const_iterator& o_rRangeBegin,
2813 ActionVector::const_iterator& o_rRangeEnd ) const
2815 ENSURE_OR_RETURN_FALSE( io_rStartIndex<=io_rEndIndex,
2816 "ImplRenderer::getSubsetIndices(): invalid action range" );
2818 ENSURE_OR_RETURN_FALSE( !maActions.empty(),
2819 "ImplRenderer::getSubsetIndices(): no actions to render" );
2821 const sal_Int32 nMinActionIndex( maActions.front().mnOrigIndex );
2822 const sal_Int32 nMaxActionIndex( maActions.back().mnOrigIndex +
2823 maActions.back().mpAction->getActionCount() );
2825 // clip given range to permissible values (there might be
2826 // ranges before and behind the valid indices)
2827 io_rStartIndex = ::std::max( nMinActionIndex,
2828 io_rStartIndex );
2829 io_rEndIndex = ::std::min( nMaxActionIndex,
2830 io_rEndIndex );
2832 if( io_rStartIndex == io_rEndIndex ||
2833 io_rStartIndex > io_rEndIndex )
2835 // empty range, don't render anything. The second
2836 // condition e.g. happens if the requested range lies
2837 // fully before or behind the valid action indices.
2838 return false;
2842 const ActionVector::const_iterator aBegin( maActions.begin() );
2843 const ActionVector::const_iterator aEnd( maActions.end() );
2846 // find start and end action
2847 // =========================
2848 o_rRangeBegin = ::std::lower_bound( aBegin, aEnd,
2849 MtfAction( ActionSharedPtr(), io_rStartIndex ),
2850 UpperBoundActionIndexComparator() );
2851 o_rRangeEnd = ::std::lower_bound( aBegin, aEnd,
2852 MtfAction( ActionSharedPtr(), io_rEndIndex ),
2853 UpperBoundActionIndexComparator() );
2854 return true;
2858 // Public methods
2861 ImplRenderer::ImplRenderer( const CanvasSharedPtr& rCanvas,
2862 const GDIMetaFile& rMtf,
2863 const Parameters& rParams )
2864 : CanvasGraphicHelper(rCanvas)
2865 , maActions()
2866 , fPageScale(0.0)
2867 , nOriginX(0)
2868 , nOriginY(0)
2869 , nHDPI(0)
2870 , nVDPI(0)
2871 , nFrameLeft(0)
2872 , nFrameTop(0)
2873 , nFrameRight(0)
2874 , nFrameBottom(0)
2875 , nPixX(0)
2876 , nPixY(0)
2877 , nMmX(0)
2878 , nMmY(0)
2879 , mbMultipart(false)
2880 , mMFlags(0)
2882 memset (aObjects, 0, sizeof (aObjects));
2884 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::ImplRenderer(mtf)" );
2886 OSL_ENSURE( rCanvas.get() != nullptr && rCanvas->getUNOCanvas().is(),
2887 "ImplRenderer::ImplRenderer(): Invalid canvas" );
2888 OSL_ENSURE( rCanvas->getUNOCanvas()->getDevice().is(),
2889 "ImplRenderer::ImplRenderer(): Invalid graphic device" );
2891 // make sure canvas and graphic device are valid; action
2892 // creation don't check that every time
2893 if( rCanvas.get() == nullptr ||
2894 !rCanvas->getUNOCanvas().is() ||
2895 !rCanvas->getUNOCanvas()->getDevice().is() )
2897 // leave actions empty
2898 return;
2901 VectorOfOutDevStates aStateStack;
2903 ScopedVclPtrInstance< VirtualDevice > aVDev;
2904 aVDev->EnableOutput( false );
2906 // Setup VDev for state tracking and mapping
2907 // =========================================
2909 aVDev->SetMapMode( rMtf.GetPrefMapMode() );
2911 const Size aMtfSize( rMtf.GetPrefSize() );
2912 const Size aMtfSizePixPre( aVDev->LogicToPixel( aMtfSize,
2913 rMtf.GetPrefMapMode() ) );
2915 // #i44110# correct null-sized output - there are shapes
2916 // which have zero size in at least one dimension
2917 const Size aMtfSizePix( ::std::max( aMtfSizePixPre.Width(), 1L ),
2918 ::std::max( aMtfSizePixPre.Height(), 1L ) );
2920 sal_Int32 nCurrActions(0);
2921 ActionFactoryParameters aParms(aStateStack,
2922 rCanvas,
2923 *aVDev.get(),
2924 rParams,
2925 nCurrActions );
2927 // init state stack
2928 aStateStack.clearStateStack();
2930 // Setup local state, such that the metafile renders
2931 // itself into a one-by-one square at the origin for
2932 // identity view and render transformations
2933 aStateStack.getState().transform.scale( 1.0 / aMtfSizePix.Width(),
2934 1.0 / aMtfSizePix.Height() );
2936 tools::calcLogic2PixelAffineTransform( aStateStack.getState().mapModeTransform,
2937 *aVDev.get() );
2939 ColorSharedPtr pColor( getCanvas()->createColor() );
2942 ::cppcanvas::internal::OutDevState& rState = aStateStack.getState();
2943 // setup default text color to black
2944 rState.textColor =
2945 rState.textFillColor =
2946 rState.textLineColor = pColor->getDeviceColor( 0x000000FF );
2949 // apply overrides from the Parameters struct
2950 if( rParams.maFillColor.is_initialized() )
2952 ::cppcanvas::internal::OutDevState& rState = aStateStack.getState();
2953 rState.isFillColorSet = true;
2954 rState.fillColor = pColor->getDeviceColor( *rParams.maFillColor );
2956 if( rParams.maLineColor.is_initialized() )
2958 ::cppcanvas::internal::OutDevState& rState = aStateStack.getState();
2959 rState.isLineColorSet = true;
2960 rState.lineColor = pColor->getDeviceColor( *rParams.maLineColor );
2962 if( rParams.maTextColor.is_initialized() )
2964 ::cppcanvas::internal::OutDevState& rState = aStateStack.getState();
2965 rState.isTextFillColorSet = true;
2966 rState.isTextLineColorSet = true;
2967 rState.textColor =
2968 rState.textFillColor =
2969 rState.textLineColor = pColor->getDeviceColor( *rParams.maTextColor );
2971 if( rParams.maFontName.is_initialized() ||
2972 rParams.maFontWeight.is_initialized() ||
2973 rParams.maFontLetterForm.is_initialized() ||
2974 rParams.maFontUnderline.is_initialized() ||
2975 rParams.maFontProportion.is_initialized() )
2977 ::cppcanvas::internal::OutDevState& rState = aStateStack.getState();
2979 rState.xFont = createFont( rState.fontRotation,
2980 vcl::Font(), // default font
2981 aParms );
2984 /* EMF+ */
2985 createActions( const_cast<GDIMetaFile&>(rMtf), // HACK(Q2):
2986 // we're
2987 // changing
2988 // the
2989 // current
2990 // action
2991 // in
2992 // createActions!
2993 aParms,
2994 true // TODO(P1): make subsettability configurable
2998 ImplRenderer::~ImplRenderer()
3000 // don't leak EMFPObjects
3001 for(EMFPObject* aObject : aObjects)
3002 delete aObject;
3005 bool ImplRenderer::drawSubset( sal_Int32 nStartIndex,
3006 sal_Int32 nEndIndex ) const
3008 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::drawSubset()" );
3010 ActionVector::const_iterator aRangeBegin;
3011 ActionVector::const_iterator aRangeEnd;
3015 if( !getSubsetIndices( nStartIndex, nEndIndex,
3016 aRangeBegin, aRangeEnd ) )
3017 return true; // nothing to render (but _that_ was successful)
3019 // now, aRangeBegin references the action in which the
3020 // subset rendering must start, and aRangeEnd references
3021 // the action in which the subset rendering must end (it
3022 // might also end right at the start of the referenced
3023 // action, such that zero of that action needs to be
3024 // rendered).
3027 // render subset of actions
3028 // ========================
3030 ::basegfx::B2DHomMatrix aMatrix;
3031 ::canvas::tools::getRenderStateTransform( aMatrix,
3032 getRenderState() );
3034 ActionRenderer aRenderer( aMatrix );
3036 return forSubsetRange( aRenderer,
3037 aRangeBegin,
3038 aRangeEnd,
3039 nStartIndex,
3040 nEndIndex,
3041 maActions.end() );
3043 catch( uno::Exception& )
3045 SAL_WARN("cppcanvas.emf", "" << OUStringToOString(
3046 comphelper::anyToString( cppu::getCaughtException() ),
3047 RTL_TEXTENCODING_UTF8 ).getStr() );
3049 // convert error to return value
3050 return false;
3054 ::basegfx::B2DRange ImplRenderer::getSubsetArea( sal_Int32 nStartIndex,
3055 sal_Int32 nEndIndex ) const
3057 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::getSubsetArea()" );
3059 ActionVector::const_iterator aRangeBegin;
3060 ActionVector::const_iterator aRangeEnd;
3062 if( !getSubsetIndices( nStartIndex, nEndIndex,
3063 aRangeBegin, aRangeEnd ) )
3064 return ::basegfx::B2DRange(); // nothing to render -> empty range
3066 // now, aRangeBegin references the action in which the
3067 // subset querying must start, and aRangeEnd references
3068 // the action in which the subset querying must end (it
3069 // might also end right at the start of the referenced
3070 // action, such that zero of that action needs to be
3071 // queried).
3074 // query bounds for subset of actions
3075 // ==================================
3077 ::basegfx::B2DHomMatrix aMatrix;
3078 ::canvas::tools::getRenderStateTransform( aMatrix,
3079 getRenderState() );
3081 AreaQuery aQuery( aMatrix );
3082 forSubsetRange( aQuery,
3083 aRangeBegin,
3084 aRangeEnd,
3085 nStartIndex,
3086 nEndIndex,
3087 maActions.end() );
3089 return aQuery.getBounds();
3092 bool ImplRenderer::draw() const
3094 SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::draw()" );
3096 ::basegfx::B2DHomMatrix aMatrix;
3097 ::canvas::tools::getRenderStateTransform( aMatrix,
3098 getRenderState() );
3102 return ::std::for_each( maActions.begin(), maActions.end(), ActionRenderer( aMatrix ) ).result();
3104 catch( uno::Exception& )
3106 SAL_WARN( "cppcanvas.emf", "" << OUStringToOString(
3107 comphelper::anyToString( cppu::getCaughtException() ),
3108 RTL_TEXTENCODING_UTF8 ).getStr() );
3110 return false;
3116 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */