calc: on editing invalidation of view with different zoom is wrong
[LibreOffice.git] / drawinglayer / source / tools / wmfemfhelper.cxx
blobe17f3d47c47e76b110cc7918714efea3fd8a3505
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 <wmfemfhelper.hxx>
21 #include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
22 #include <vcl/lineinfo.hxx>
23 #include <vcl/metaact.hxx>
24 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
25 #include <basegfx/matrix/b2dhommatrixtools.hxx>
26 #include <basegfx/utils/gradienttools.hxx>
27 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
28 #include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
29 #include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
30 #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
31 #include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx>
32 #include <basegfx/polygon/b2dpolygontools.hxx>
33 #include <drawinglayer/primitive2d/discretebitmapprimitive2d.hxx>
34 #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
35 #include <vcl/BitmapPalette.hxx>
36 #include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
37 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
38 #include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx>
39 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
40 #include <basegfx/polygon/b2dpolygonclipper.hxx>
41 #include <drawinglayer/primitive2d/invertprimitive2d.hxx>
42 #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
43 #include <primitive2d/wallpaperprimitive2d.hxx>
44 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
45 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
46 #include <primitive2d/textlineprimitive2d.hxx>
47 #include <primitive2d/textstrikeoutprimitive2d.hxx>
48 #include <drawinglayer/primitive2d/epsprimitive2d.hxx>
49 #include <sal/log.hxx>
50 #include <tools/fract.hxx>
51 #include <tools/stream.hxx>
52 #include <tools/UnitConversion.hxx>
53 #include <vcl/canvastools.hxx>
54 #include <vcl/gradient.hxx>
55 #include <vcl/hatch.hxx>
56 #include <vcl/outdev.hxx>
57 #include <i18nlangtag/languagetag.hxx>
58 #include <emfplushelper.hxx>
59 #include <numeric>
60 #include <toolkit/helper/vclunohelper.hxx>
62 namespace drawinglayer::primitive2d
64 namespace {
66 /** NonOverlappingFillGradientPrimitive2D class
68 This is a special version of the FillGradientPrimitive2D which decomposes
69 to a non-overlapping geometry version of the gradient. This needs to be
70 used to support the old XOR paint-'trick'.
72 It does not need an own identifier since a renderer who wants to interpret
73 it itself may do so. It just overrides the decomposition of the C++
74 implementation class to do an alternative decomposition.
76 class NonOverlappingFillGradientPrimitive2D : public FillGradientPrimitive2D
78 protected:
79 /// local decomposition.
80 virtual void create2DDecomposition(Primitive2DContainer& rContainer,
81 const geometry::ViewInformation2D& rViewInformation) const override;
83 public:
84 /// constructor
85 NonOverlappingFillGradientPrimitive2D(
86 const basegfx::B2DRange& rObjectRange,
87 const attribute::FillGradientAttribute& rFillGradient)
88 : FillGradientPrimitive2D(rObjectRange, rFillGradient)
95 void NonOverlappingFillGradientPrimitive2D::create2DDecomposition(
96 Primitive2DContainer& rContainer,
97 const geometry::ViewInformation2D& /*rViewInformation*/) const
99 if (!getFillGradient().isDefault())
101 createFill(rContainer, false);
105 } // end of namespace
107 namespace wmfemfhelper
109 /** helper class for graphic context
111 This class allows to hold a complete representation of classic
112 VCL OutputDevice state. This data is needed for correct
113 interpretation of the MetaFile action flow.
115 PropertyHolder::PropertyHolder()
116 : maMapUnit(MapUnit::Map100thMM),
117 maTextColor(sal_uInt32(COL_BLACK)),
118 maRasterOp(RasterOp::OverPaint),
119 mnLayoutMode(vcl::text::ComplexTextLayoutFlags::Default),
120 maLanguageType(0),
121 mnPushFlags(vcl::PushFlags::NONE),
122 mbLineColor(false),
123 mbFillColor(false),
124 mbTextColor(true),
125 mbTextFillColor(false),
126 mbTextLineColor(false),
127 mbOverlineColor(false),
128 mbClipPolyPolygonActive(false)
133 namespace wmfemfhelper
135 /** stack for properties
137 This class builds a stack based on the PropertyHolder
138 class. It encapsulates the pointer/new/delete usage to
139 make it safe and implements the push/pop as needed by a
140 VCL Metafile interpreter. The critical part here are the
141 flag values VCL OutputDevice uses here; not all stuff is
142 pushed and thus needs to be copied at pop.
144 PropertyHolders::PropertyHolders()
146 maPropertyHolders.push_back(new PropertyHolder());
149 void PropertyHolders::PushDefault()
151 PropertyHolder* pNew = new PropertyHolder();
152 maPropertyHolders.push_back(pNew);
155 void PropertyHolders::Push(vcl::PushFlags nPushFlags)
157 if (bool(nPushFlags))
159 OSL_ENSURE(maPropertyHolders.size(), "PropertyHolders: PUSH with no property holders (!)");
160 if (!maPropertyHolders.empty())
162 PropertyHolder* pNew = new PropertyHolder(*maPropertyHolders.back());
163 pNew->setPushFlags(nPushFlags);
164 maPropertyHolders.push_back(pNew);
169 void PropertyHolders::Pop()
171 OSL_ENSURE(maPropertyHolders.size(), "PropertyHolders: POP with no property holders (!)");
172 const sal_uInt32 nSize(maPropertyHolders.size());
174 if (!nSize)
175 return;
177 const PropertyHolder* pTip = maPropertyHolders.back();
178 const vcl::PushFlags nPushFlags(pTip->getPushFlags());
180 if (nPushFlags != vcl::PushFlags::NONE)
182 if (nSize > 1)
184 // copy back content for all non-set flags
185 PropertyHolder* pLast = maPropertyHolders[nSize - 2];
187 if (vcl::PushFlags::ALL != nPushFlags)
189 if (!(nPushFlags & vcl::PushFlags::LINECOLOR))
191 pLast->setLineColor(pTip->getLineColor());
192 pLast->setLineColorActive(pTip->getLineColorActive());
194 if (!(nPushFlags & vcl::PushFlags::FILLCOLOR))
196 pLast->setFillColor(pTip->getFillColor());
197 pLast->setFillColorActive(pTip->getFillColorActive());
199 if (!(nPushFlags & vcl::PushFlags::FONT))
201 pLast->setFont(pTip->getFont());
203 if (!(nPushFlags & vcl::PushFlags::TEXTCOLOR))
205 pLast->setTextColor(pTip->getTextColor());
206 pLast->setTextColorActive(pTip->getTextColorActive());
208 if (!(nPushFlags & vcl::PushFlags::MAPMODE))
210 pLast->setTransformation(pTip->getTransformation());
211 pLast->setMapUnit(pTip->getMapUnit());
213 if (!(nPushFlags & vcl::PushFlags::CLIPREGION))
215 pLast->setClipPolyPolygon(pTip->getClipPolyPolygon());
216 pLast->setClipPolyPolygonActive(pTip->getClipPolyPolygonActive());
218 if (!(nPushFlags & vcl::PushFlags::RASTEROP))
220 pLast->setRasterOp(pTip->getRasterOp());
222 if (!(nPushFlags & vcl::PushFlags::TEXTFILLCOLOR))
224 pLast->setTextFillColor(pTip->getTextFillColor());
225 pLast->setTextFillColorActive(pTip->getTextFillColorActive());
227 if (!(nPushFlags & vcl::PushFlags::TEXTALIGN))
229 if (pLast->getFont().GetAlignment() != pTip->getFont().GetAlignment())
231 vcl::Font aFont(pLast->getFont());
232 aFont.SetAlignment(pTip->getFont().GetAlignment());
233 pLast->setFont(aFont);
236 if (!(nPushFlags & vcl::PushFlags::REFPOINT))
238 // not supported
240 if (!(nPushFlags & vcl::PushFlags::TEXTLINECOLOR))
242 pLast->setTextLineColor(pTip->getTextLineColor());
243 pLast->setTextLineColorActive(pTip->getTextLineColorActive());
245 if (!(nPushFlags & vcl::PushFlags::TEXTLAYOUTMODE))
247 pLast->setLayoutMode(pTip->getLayoutMode());
249 if (!(nPushFlags & vcl::PushFlags::TEXTLANGUAGE))
251 pLast->setLanguageType(pTip->getLanguageType());
253 if (!(nPushFlags & vcl::PushFlags::OVERLINECOLOR))
255 pLast->setOverlineColor(pTip->getOverlineColor());
256 pLast->setOverlineColorActive(pTip->getOverlineColorActive());
262 // execute the pop
263 delete maPropertyHolders.back();
264 maPropertyHolders.pop_back();
267 PropertyHolder& PropertyHolders::Current()
269 static PropertyHolder aDummy;
270 OSL_ENSURE(maPropertyHolders.size(), "PropertyHolders: CURRENT with no property holders (!)");
271 return maPropertyHolders.empty() ? aDummy : *maPropertyHolders.back();
274 PropertyHolders::~PropertyHolders()
276 while (!maPropertyHolders.empty())
278 delete maPropertyHolders.back();
279 maPropertyHolders.pop_back();
284 namespace
286 /** helper to convert a vcl::Region to a B2DPolyPolygon
287 when it does not yet contain one. In the future
288 this may be expanded to merge the polygons created
289 from rectangles or use a special algo to directly turn
290 the spans of regions to a single, already merged
291 PolyPolygon.
293 basegfx::B2DPolyPolygon getB2DPolyPolygonFromRegion(const vcl::Region& rRegion)
295 basegfx::B2DPolyPolygon aRetval;
297 if (!rRegion.IsEmpty())
299 aRetval = rRegion.GetAsB2DPolyPolygon();
302 return aRetval;
306 namespace wmfemfhelper
308 /** Helper class to buffer and hold a Primitive target vector. It
309 encapsulates the new/delete functionality and allows to work
310 on pointers of the implementation classes. All data will
311 be converted to uno sequences of uno references when accessing the
312 data.
314 TargetHolder::TargetHolder()
318 TargetHolder::~TargetHolder()
322 sal_uInt32 TargetHolder::size() const
324 return aTargets.size();
327 void TargetHolder::append(drawinglayer::primitive2d::BasePrimitive2D* pCandidate)
329 if (pCandidate)
331 aTargets.push_back(pCandidate);
335 drawinglayer::primitive2d::Primitive2DContainer TargetHolder::getPrimitive2DSequence(const PropertyHolder& rPropertyHolder)
337 drawinglayer::primitive2d::Primitive2DContainer xRetval = std::move(aTargets);
340 if (!xRetval.empty() && rPropertyHolder.getClipPolyPolygonActive())
342 const basegfx::B2DPolyPolygon& rClipPolyPolygon = rPropertyHolder.getClipPolyPolygon();
344 if (rClipPolyPolygon.count())
346 drawinglayer::primitive2d::Primitive2DReference xMask(
347 new drawinglayer::primitive2d::MaskPrimitive2D(
348 rClipPolyPolygon,
349 std::move(xRetval)));
351 xRetval = drawinglayer::primitive2d::Primitive2DContainer{ xMask };
355 return xRetval;
359 namespace wmfemfhelper
361 /** Helper class which builds a stack on the TargetHolder class */
362 TargetHolders::TargetHolders()
364 maTargetHolders.push_back(new TargetHolder());
367 sal_uInt32 TargetHolders::size() const
369 return maTargetHolders.size();
372 void TargetHolders::Push()
374 maTargetHolders.push_back(new TargetHolder());
377 void TargetHolders::Pop()
379 OSL_ENSURE(maTargetHolders.size(), "TargetHolders: POP with no property holders (!)");
380 if (!maTargetHolders.empty())
382 delete maTargetHolders.back();
383 maTargetHolders.pop_back();
387 TargetHolder& TargetHolders::Current()
389 static TargetHolder aDummy;
390 OSL_ENSURE(maTargetHolders.size(), "TargetHolders: CURRENT with no property holders (!)");
391 return maTargetHolders.empty() ? aDummy : *maTargetHolders.back();
394 TargetHolders::~TargetHolders()
396 while (!maTargetHolders.empty())
398 delete maTargetHolders.back();
399 maTargetHolders.pop_back();
404 namespace
406 /** helper to convert a MapMode to a transformation */
407 basegfx::B2DHomMatrix getTransformFromMapMode(const MapMode& rMapMode)
409 basegfx::B2DHomMatrix aMapping;
410 const Fraction aNoScale(1, 1);
411 const Point& rOrigin(rMapMode.GetOrigin());
413 if(0 != rOrigin.X() || 0 != rOrigin.Y())
415 aMapping.translate(rOrigin.X(), rOrigin.Y());
418 if(rMapMode.GetScaleX() != aNoScale || rMapMode.GetScaleY() != aNoScale)
420 aMapping.scale(
421 double(rMapMode.GetScaleX()),
422 double(rMapMode.GetScaleY()));
425 return aMapping;
429 namespace wmfemfhelper
431 /** helper to create a PointArrayPrimitive2D based on current context */
432 static void createPointArrayPrimitive(
433 std::vector< basegfx::B2DPoint >&& rPositions,
434 TargetHolder& rTarget,
435 PropertyHolder const & rProperties,
436 const basegfx::BColor& rBColor)
438 if(rPositions.empty())
439 return;
441 if(rProperties.getTransformation().isIdentity())
443 rTarget.append(
444 new drawinglayer::primitive2d::PointArrayPrimitive2D(
445 std::move(rPositions),
446 rBColor));
448 else
450 for(basegfx::B2DPoint & aPosition : rPositions)
452 aPosition = rProperties.getTransformation() * aPosition;
455 rTarget.append(
456 new drawinglayer::primitive2d::PointArrayPrimitive2D(
457 std::move(rPositions),
458 rBColor));
462 /** helper to create a PolygonHairlinePrimitive2D based on current context */
463 static void createHairlinePrimitive(
464 const basegfx::B2DPolygon& rLinePolygon,
465 TargetHolder& rTarget,
466 PropertyHolder const & rProperties)
468 if(rLinePolygon.count())
470 basegfx::B2DPolygon aLinePolygon(rLinePolygon);
471 aLinePolygon.transform(rProperties.getTransformation());
472 rTarget.append(
473 new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(
474 std::move(aLinePolygon),
475 rProperties.getLineColor()));
479 /** helper to create a PolyPolygonColorPrimitive2D based on current context */
480 static void createFillPrimitive(
481 const basegfx::B2DPolyPolygon& rFillPolyPolygon,
482 TargetHolder& rTarget,
483 PropertyHolder const & rProperties)
485 if(rFillPolyPolygon.count())
487 basegfx::B2DPolyPolygon aFillPolyPolygon(rFillPolyPolygon);
488 aFillPolyPolygon.transform(rProperties.getTransformation());
489 rTarget.append(
490 new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
491 std::move(aFillPolyPolygon),
492 rProperties.getFillColor()));
496 /** helper to create a PolygonStrokePrimitive2D based on current context */
497 static void createLinePrimitive(
498 const basegfx::B2DPolygon& rLinePolygon,
499 const LineInfo& rLineInfo,
500 TargetHolder& rTarget,
501 PropertyHolder const & rProperties)
503 if(!rLinePolygon.count())
504 return;
506 const bool bDashDotUsed(LineStyle::Dash == rLineInfo.GetStyle());
507 const bool bWidthUsed(rLineInfo.GetWidth() > 1);
509 if(bDashDotUsed || bWidthUsed)
511 basegfx::B2DPolygon aLinePolygon(rLinePolygon);
512 aLinePolygon.transform(rProperties.getTransformation());
513 drawinglayer::attribute::LineAttribute aLineAttribute(
514 rProperties.getLineColor(),
515 bWidthUsed ? rLineInfo.GetWidth() : 0.0,
516 rLineInfo.GetLineJoin(),
517 rLineInfo.GetLineCap());
519 if(bDashDotUsed)
521 std::vector< double > fDotDashArray = rLineInfo.GetDotDashArray();
522 const double fAccumulated(std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0));
523 drawinglayer::attribute::StrokeAttribute aStrokeAttribute(
524 std::move(fDotDashArray),
525 fAccumulated);
527 rTarget.append(
528 new drawinglayer::primitive2d::PolygonStrokePrimitive2D(
529 std::move(aLinePolygon),
530 std::move(aLineAttribute),
531 std::move(aStrokeAttribute)));
533 else
535 rTarget.append(
536 new drawinglayer::primitive2d::PolygonStrokePrimitive2D(
537 std::move(aLinePolygon),
538 aLineAttribute));
541 else
543 createHairlinePrimitive(rLinePolygon, rTarget, rProperties);
547 /** helper to create needed line and fill primitives based on current context */
548 static void createHairlineAndFillPrimitive(
549 const basegfx::B2DPolygon& rPolygon,
550 TargetHolder& rTarget,
551 PropertyHolder const & rProperties)
553 if(rProperties.getFillColorActive())
555 createFillPrimitive(basegfx::B2DPolyPolygon(rPolygon), rTarget, rProperties);
558 if(rProperties.getLineColorActive())
560 createHairlinePrimitive(rPolygon, rTarget, rProperties);
564 /** helper to create needed line and fill primitives based on current context */
565 static void createHairlineAndFillPrimitive(
566 const basegfx::B2DPolyPolygon& rPolyPolygon,
567 TargetHolder& rTarget,
568 PropertyHolder const & rProperties)
570 if(rProperties.getFillColorActive())
572 createFillPrimitive(rPolyPolygon, rTarget, rProperties);
575 if(rProperties.getLineColorActive())
577 for(sal_uInt32 a(0); a < rPolyPolygon.count(); a++)
579 createHairlinePrimitive(rPolyPolygon.getB2DPolygon(a), rTarget, rProperties);
584 /** helper to create DiscreteBitmapPrimitive2D based on current context.
585 The DiscreteBitmapPrimitive2D is especially created for this usage
586 since no other usage defines a bitmap visualisation based on top-left
587 position and size in pixels. At the end it will create a view-dependent
588 transformed embedding of a BitmapPrimitive2D.
590 static void createBitmapExPrimitive(
591 const BitmapEx& rBitmapEx,
592 const Point& rPoint,
593 TargetHolder& rTarget,
594 PropertyHolder const & rProperties)
596 if(!rBitmapEx.IsEmpty())
598 basegfx::B2DPoint aPoint(rPoint.X(), rPoint.Y());
599 aPoint = rProperties.getTransformation() * aPoint;
601 rTarget.append(
602 new drawinglayer::primitive2d::DiscreteBitmapPrimitive2D(
603 rBitmapEx,
604 aPoint));
608 /** helper to create BitmapPrimitive2D based on current context */
609 static void createBitmapExPrimitive(
610 const BitmapEx& rBitmapEx,
611 const Point& rPoint,
612 const Size& rSize,
613 TargetHolder& rTarget,
614 PropertyHolder const & rProperties)
616 if(rBitmapEx.IsEmpty())
617 return;
619 basegfx::B2DHomMatrix aObjectTransform;
621 aObjectTransform.set(0, 0, rSize.Width());
622 aObjectTransform.set(1, 1, rSize.Height());
623 aObjectTransform.set(0, 2, rPoint.X());
624 aObjectTransform.set(1, 2, rPoint.Y());
626 aObjectTransform = rProperties.getTransformation() * aObjectTransform;
628 rTarget.append(
629 new drawinglayer::primitive2d::BitmapPrimitive2D(
630 rBitmapEx,
631 aObjectTransform));
634 /** helper to create a regular BotmapEx from a MaskAction (definitions
635 which use a bitmap without transparence but define one of the colors as
636 transparent)
638 static BitmapEx createMaskBmpEx(const Bitmap& rBitmap, const Color& rMaskColor)
640 const Color aWhite(COL_WHITE);
641 BitmapPalette aBiLevelPalette {
642 aWhite, rMaskColor
645 Bitmap aMask(rBitmap.CreateMask(aWhite));
646 Bitmap aSolid(rBitmap.GetSizePixel(), vcl::PixelFormat::N1_BPP, &aBiLevelPalette);
648 aSolid.Erase(rMaskColor);
650 return BitmapEx(aSolid, aMask);
653 /** helper to convert from a VCL Gradient definition to the corresponding
654 data for primitive representation
656 static drawinglayer::attribute::FillGradientAttribute createFillGradientAttribute(const Gradient& rGradient)
658 const Color aStartColor(rGradient.GetStartColor());
659 const sal_uInt16 nStartIntens(rGradient.GetStartIntensity());
660 basegfx::BColor aStart(aStartColor.getBColor());
662 if(nStartIntens != 100)
664 const basegfx::BColor aBlack;
665 aStart = interpolate(aBlack, aStart, static_cast<double>(nStartIntens) * 0.01);
668 const Color aEndColor(rGradient.GetEndColor());
669 const sal_uInt16 nEndIntens(rGradient.GetEndIntensity());
670 basegfx::BColor aEnd(aEndColor.getBColor());
672 if(nEndIntens != 100)
674 const basegfx::BColor aBlack;
675 aEnd = interpolate(aBlack, aEnd, static_cast<double>(nEndIntens) * 0.01);
678 return drawinglayer::attribute::FillGradientAttribute(
679 rGradient.GetStyle(),
680 static_cast<double>(rGradient.GetBorder()) * 0.01,
681 static_cast<double>(rGradient.GetOfsX()) * 0.01,
682 static_cast<double>(rGradient.GetOfsY()) * 0.01,
683 toRadians(rGradient.GetAngle()),
684 basegfx::BColorStops(aStart, aEnd),
685 rGradient.GetSteps());
688 /** helper to convert from a VCL Hatch definition to the corresponding
689 data for primitive representation
691 static drawinglayer::attribute::FillHatchAttribute createFillHatchAttribute(const Hatch& rHatch)
693 drawinglayer::attribute::HatchStyle aHatchStyle(drawinglayer::attribute::HatchStyle::Single);
695 switch(rHatch.GetStyle())
697 default : // case HatchStyle::Single :
699 aHatchStyle = drawinglayer::attribute::HatchStyle::Single;
700 break;
702 case HatchStyle::Double :
704 aHatchStyle = drawinglayer::attribute::HatchStyle::Double;
705 break;
707 case HatchStyle::Triple :
709 aHatchStyle = drawinglayer::attribute::HatchStyle::Triple;
710 break;
714 return drawinglayer::attribute::FillHatchAttribute(
715 aHatchStyle,
716 static_cast<double>(rHatch.GetDistance()),
717 toRadians(rHatch.GetAngle()),
718 rHatch.GetColor().getBColor(),
719 3, // same default as VCL, a minimum of three discrete units (pixels) offset
720 false);
723 /** helper to take needed action on ClipRegion change. This method needs to be called
724 on any vcl::Region change, e.g. at the obvious actions doing this, but also at pop-calls
725 which change the vcl::Region of the current context. It takes care of creating the
726 current embedded context, set the new vcl::Region at the context and possibly prepare
727 a new target for including new geometry into the current region
729 void HandleNewClipRegion(
730 const basegfx::B2DPolyPolygon& rClipPolyPolygon,
731 TargetHolders& rTargetHolders,
732 PropertyHolders& rPropertyHolders)
734 const bool bNewActive(rClipPolyPolygon.count());
736 // #i108636# The handling of new ClipPolyPolygons was not done as good as possible
737 // in the first version of this interpreter; e.g. when a ClipPolyPolygon was set
738 // initially and then using a lot of push/pop actions, the pop always leads
739 // to setting a 'new' ClipPolyPolygon which indeed is the return to the ClipPolyPolygon
740 // of the properties next on the stack.
742 // This ClipPolyPolygon is identical to the current one, so there is no need to
743 // create a MaskPrimitive2D containing the up-to-now created primitives, but
744 // this was done before. While this does not lead to wrong primitive
745 // representations of the metafile data, it creates unnecessarily expensive
746 // representations. Just detecting when no really 'new' ClipPolyPolygon gets set
747 // solves the problem.
749 if(!rPropertyHolders.Current().getClipPolyPolygonActive() && !bNewActive)
751 // no active ClipPolyPolygon exchanged by no new one, done
752 return;
755 if(rPropertyHolders.Current().getClipPolyPolygonActive() && bNewActive)
757 // active ClipPolyPolygon and new active ClipPolyPolygon
758 if(rPropertyHolders.Current().getClipPolyPolygon() == rClipPolyPolygon)
760 // new is the same as old, done
761 return;
765 // Here the old and the new are definitively different, maybe
766 // old one and/or new one is not active.
768 // Handle deletion of old ClipPolyPolygon. The process evtl. created primitives which
769 // belong to this active ClipPolyPolygon. These need to be embedded to a
770 // MaskPrimitive2D accordingly.
771 if(rPropertyHolders.Current().getClipPolyPolygonActive() && rTargetHolders.size() > 1)
773 drawinglayer::primitive2d::Primitive2DContainer aSubContent;
775 if(rPropertyHolders.Current().getClipPolyPolygon().count()
776 && rTargetHolders.Current().size())
778 aSubContent = rTargetHolders.Current().getPrimitive2DSequence(
779 rPropertyHolders.Current());
782 rTargetHolders.Pop();
784 if(!aSubContent.empty())
786 rTargetHolders.Current().append(
787 new drawinglayer::primitive2d::GroupPrimitive2D(
788 std::move(aSubContent)));
792 // apply new settings to current properties by setting
793 // the new region now
794 rPropertyHolders.Current().setClipPolyPolygonActive(bNewActive);
796 if(bNewActive)
798 rPropertyHolders.Current().setClipPolyPolygon(rClipPolyPolygon);
800 // prepare new content holder for new active region
801 rTargetHolders.Push();
805 /** helper to handle the change of RasterOp. It takes care of encapsulating all current
806 geometry to the current RasterOp (if changed) and needs to be called on any RasterOp
807 change. It will also start a new geometry target to embrace to the new RasterOp if
808 a changing RasterOp is used. Currently, RasterOp::Xor and RasterOp::Invert are supported using
809 InvertPrimitive2D, and RasterOp::N0 by using a ModifiedColorPrimitive2D to force to black paint
811 static void HandleNewRasterOp(
812 RasterOp aRasterOp,
813 TargetHolders& rTargetHolders,
814 PropertyHolders& rPropertyHolders)
816 // check if currently active
817 if(rPropertyHolders.Current().isRasterOpActive() && rTargetHolders.size() > 1)
819 drawinglayer::primitive2d::Primitive2DContainer aSubContent;
821 if(rTargetHolders.Current().size())
823 aSubContent = rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current());
826 rTargetHolders.Pop();
828 if(!aSubContent.empty())
830 if(rPropertyHolders.Current().isRasterOpForceBlack())
832 // force content to black
833 rTargetHolders.Current().append(
834 new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
835 std::move(aSubContent),
836 std::make_shared<basegfx::BColorModifier_replace>(
837 basegfx::BColor(0.0, 0.0, 0.0))));
839 else // if(rPropertyHolders.Current().isRasterOpInvert())
841 // invert content
842 rTargetHolders.Current().append(
843 new drawinglayer::primitive2d::InvertPrimitive2D(
844 std::move(aSubContent)));
849 // apply new settings
850 rPropertyHolders.Current().setRasterOp(aRasterOp);
852 // check if now active
853 if(rPropertyHolders.Current().isRasterOpActive())
855 // prepare new content holder for new invert
856 rTargetHolders.Push();
860 /** helper to create needed data to emulate the VCL Wallpaper Metafile action.
861 It is a quite mighty action. This helper is for simple color filled background.
863 static rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> CreateColorWallpaper(
864 const basegfx::B2DRange& rRange,
865 const basegfx::BColor& rColor,
866 PropertyHolder const & rPropertyHolder)
868 basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(rRange));
869 aOutline.transform(rPropertyHolder.getTransformation());
871 return new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
872 basegfx::B2DPolyPolygon(aOutline),
873 rColor);
876 /** helper to create needed data to emulate the VCL Wallpaper Metafile action.
877 It is a quite mighty action. This helper is for gradient filled background.
879 static rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> CreateGradientWallpaper(
880 const basegfx::B2DRange& rRange,
881 const Gradient& rGradient,
882 PropertyHolder const & rPropertyHolder)
884 drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient));
885 basegfx::BColor aSingleColor;
887 if (aAttribute.getColorStops().isSingleColor(aSingleColor))
889 // not really a gradient. Create filled rectangle
890 return CreateColorWallpaper(rRange, aSingleColor, rPropertyHolder);
892 else
894 // really a gradient
895 rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pRetval(
896 new drawinglayer::primitive2d::FillGradientPrimitive2D(
897 rRange,
898 std::move(aAttribute)));
900 if(!rPropertyHolder.getTransformation().isIdentity())
902 const drawinglayer::primitive2d::Primitive2DReference xPrim(pRetval);
903 drawinglayer::primitive2d::Primitive2DContainer xSeq { xPrim };
905 pRetval = new drawinglayer::primitive2d::TransformPrimitive2D(
906 rPropertyHolder.getTransformation(),
907 std::move(xSeq));
910 return pRetval;
914 /** helper to create needed data to emulate the VCL Wallpaper Metafile action.
915 It is a quite mighty action. This helper decides if color and/or gradient
916 background is needed for the wanted bitmap fill and then creates the needed
917 WallpaperBitmapPrimitive2D. This primitive was created for this purpose and
918 takes over all needed logic of orientations and tiling.
920 static void CreateAndAppendBitmapWallpaper(
921 basegfx::B2DRange aWallpaperRange,
922 const Wallpaper& rWallpaper,
923 TargetHolder& rTarget,
924 PropertyHolder const & rProperty)
926 const BitmapEx aBitmapEx(rWallpaper.GetBitmap());
927 const WallpaperStyle eWallpaperStyle(rWallpaper.GetStyle());
929 // if bitmap visualisation is transparent, maybe background
930 // needs to be filled. Create background
931 if(aBitmapEx.IsAlpha()
932 || (WallpaperStyle::Tile != eWallpaperStyle && WallpaperStyle::Scale != eWallpaperStyle))
934 if(rWallpaper.IsGradient())
936 rTarget.append(
937 CreateGradientWallpaper(
938 aWallpaperRange,
939 rWallpaper.GetGradient(),
940 rProperty));
942 else if(!rWallpaper.GetColor().IsTransparent())
944 rTarget.append(
945 CreateColorWallpaper(
946 aWallpaperRange,
947 rWallpaper.GetColor().getBColor(),
948 rProperty));
952 // use wallpaper rect if set
953 if(rWallpaper.IsRect() && !rWallpaper.GetRect().IsEmpty())
955 aWallpaperRange = vcl::unotools::b2DRectangleFromRectangle(rWallpaper.GetRect());
958 rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pBitmapWallpaperFill =
959 new drawinglayer::primitive2d::WallpaperBitmapPrimitive2D(
960 aWallpaperRange,
961 aBitmapEx,
962 eWallpaperStyle);
964 if(rProperty.getTransformation().isIdentity())
966 // add directly
967 rTarget.append(pBitmapWallpaperFill);
969 else
971 // when a transformation is set, embed to it
972 const drawinglayer::primitive2d::Primitive2DReference xPrim(pBitmapWallpaperFill);
974 rTarget.append(
975 new drawinglayer::primitive2d::TransformPrimitive2D(
976 rProperty.getTransformation(),
977 drawinglayer::primitive2d::Primitive2DContainer { xPrim }));
981 /** helper to decide UnderlineAbove for text primitives */
982 static bool isUnderlineAbove(const vcl::Font& rFont)
984 if(!rFont.IsVertical())
986 return false;
989 // the underline is right for Japanese only
990 return (LANGUAGE_JAPANESE == rFont.GetLanguage()) || (LANGUAGE_JAPANESE == rFont.GetCJKContextLanguage());
993 static void createFontAttributeTransformAndAlignment(
994 drawinglayer::attribute::FontAttribute& rFontAttribute,
995 basegfx::B2DHomMatrix& rTextTransform,
996 basegfx::B2DVector& rAlignmentOffset,
997 PropertyHolder const & rProperty)
999 const vcl::Font& rFont = rProperty.getFont();
1000 basegfx::B2DVector aFontScaling;
1002 rFontAttribute = drawinglayer::primitive2d::getFontAttributeFromVclFont(
1003 aFontScaling,
1004 rFont,
1005 bool(rProperty.getLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiRtl),
1006 bool(rProperty.getLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiStrong));
1008 // add FontScaling
1009 rTextTransform.scale(aFontScaling.getX(), aFontScaling.getY());
1011 // take text align into account
1012 if(ALIGN_BASELINE != rFont.GetAlignment())
1014 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
1015 aTextLayouterDevice.setFont(rFont);
1017 if(ALIGN_TOP == rFont.GetAlignment())
1019 rAlignmentOffset.setY(aTextLayouterDevice.getFontAscent());
1021 else // ALIGN_BOTTOM
1023 rAlignmentOffset.setY(-aTextLayouterDevice.getFontDescent());
1026 rTextTransform.translate(rAlignmentOffset.getX(), rAlignmentOffset.getY());
1029 // add FontRotation (if used)
1030 if(rFont.GetOrientation())
1032 rTextTransform.rotate(-toRadians(rFont.GetOrientation()));
1036 /** helper which takes complete care for creating the needed text primitives. It
1037 takes care of decorated stuff and all the geometry adaptations needed
1039 static void processMetaTextAction(
1040 const Point& rTextStartPosition,
1041 const OUString& rText,
1042 sal_uInt16 nTextStart,
1043 sal_uInt16 nTextLength,
1044 std::vector< double >&& rDXArray,
1045 std::vector< sal_Bool >&& rKashidaArray,
1046 TargetHolder& rTarget,
1047 PropertyHolder const & rProperty)
1049 rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pResult;
1050 const vcl::Font& rFont = rProperty.getFont();
1051 basegfx::B2DVector aAlignmentOffset(0.0, 0.0);
1053 if(nTextLength)
1055 drawinglayer::attribute::FontAttribute aFontAttribute;
1056 basegfx::B2DHomMatrix aTextTransform;
1058 // fill parameters derived from current font
1059 createFontAttributeTransformAndAlignment(
1060 aFontAttribute,
1061 aTextTransform,
1062 aAlignmentOffset,
1063 rProperty);
1065 // add TextStartPosition
1066 aTextTransform.translate(rTextStartPosition.X(), rTextStartPosition.Y());
1068 // prepare FontColor and Locale
1069 const basegfx::BColor aFontColor(rProperty.getTextColor());
1070 const Color aFillColor(rFont.GetFillColor());
1071 css::lang::Locale aLocale(LanguageTag(rProperty.getLanguageType()).getLocale());
1072 const bool bWordLineMode(rFont.IsWordLineMode());
1074 const bool bDecoratedIsNeeded(
1075 LINESTYLE_NONE != rFont.GetOverline()
1076 || LINESTYLE_NONE != rFont.GetUnderline()
1077 || STRIKEOUT_NONE != rFont.GetStrikeout()
1078 || FontEmphasisMark::NONE != (rFont.GetEmphasisMark() & FontEmphasisMark::Style)
1079 || FontRelief::NONE != rFont.GetRelief()
1080 || rFont.IsShadow()
1081 || bWordLineMode);
1083 if(bDecoratedIsNeeded)
1085 // prepare overline, underline and strikeout data
1086 const drawinglayer::primitive2d::TextLine eFontOverline(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rFont.GetOverline()));
1087 const drawinglayer::primitive2d::TextLine eFontLineStyle(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rFont.GetUnderline()));
1088 const drawinglayer::primitive2d::TextStrikeout eTextStrikeout(drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rFont.GetStrikeout()));
1090 // check UndelineAbove
1091 const bool bUnderlineAbove(drawinglayer::primitive2d::TEXT_LINE_NONE != eFontLineStyle && isUnderlineAbove(rFont));
1093 // prepare emphasis mark data
1094 drawinglayer::primitive2d::TextEmphasisMark eTextEmphasisMark(drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE);
1096 switch(rFont.GetEmphasisMark() & FontEmphasisMark::Style)
1098 case FontEmphasisMark::Dot : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DOT; break;
1099 case FontEmphasisMark::Circle : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_CIRCLE; break;
1100 case FontEmphasisMark::Disc : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DISC; break;
1101 case FontEmphasisMark::Accent : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_ACCENT; break;
1102 default: break;
1105 const bool bEmphasisMarkAbove(rFont.GetEmphasisMark() & FontEmphasisMark::PosAbove);
1106 const bool bEmphasisMarkBelow(rFont.GetEmphasisMark() & FontEmphasisMark::PosBelow);
1108 // prepare font relief data
1109 drawinglayer::primitive2d::TextRelief eTextRelief(drawinglayer::primitive2d::TEXT_RELIEF_NONE);
1111 switch(rFont.GetRelief())
1113 case FontRelief::Embossed : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_EMBOSSED; break;
1114 case FontRelief::Engraved : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_ENGRAVED; break;
1115 default : break; // RELIEF_NONE, FontRelief_FORCE_EQUAL_SIZE
1118 // prepare shadow/outline data
1119 const bool bShadow(rFont.IsShadow());
1121 // TextDecoratedPortionPrimitive2D is needed, create one
1122 pResult = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
1124 // attributes for TextSimplePortionPrimitive2D
1125 aTextTransform,
1126 rText,
1127 nTextStart,
1128 nTextLength,
1129 std::move(rDXArray),
1130 std::move(rKashidaArray),
1131 aFontAttribute,
1132 aLocale,
1133 aFontColor,
1134 aFillColor,
1136 // attributes for TextDecoratedPortionPrimitive2D
1137 rProperty.getOverlineColorActive() ? rProperty.getOverlineColor() : aFontColor,
1138 rProperty.getTextLineColorActive() ? rProperty.getTextLineColor() : aFontColor,
1139 eFontOverline,
1140 eFontLineStyle,
1141 bUnderlineAbove,
1142 eTextStrikeout,
1143 bWordLineMode,
1144 eTextEmphasisMark,
1145 bEmphasisMarkAbove,
1146 bEmphasisMarkBelow,
1147 eTextRelief,
1148 bShadow);
1150 else
1152 // TextSimplePortionPrimitive2D is enough
1153 pResult = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
1154 aTextTransform,
1155 rText,
1156 nTextStart,
1157 nTextLength,
1158 std::vector(rDXArray),
1159 std::vector(rKashidaArray),
1160 std::move(aFontAttribute),
1161 std::move(aLocale),
1162 aFontColor);
1166 if(pResult && rProperty.getTextFillColorActive())
1168 // text background is requested, add and encapsulate both to new primitive
1169 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
1170 aTextLayouterDevice.setFont(rFont);
1172 // get text width
1173 double fTextWidth(0.0);
1175 if(rDXArray.empty())
1177 fTextWidth = aTextLayouterDevice.getTextWidth(rText, nTextStart, nTextLength);
1179 else
1181 fTextWidth = rDXArray.back();
1184 if(basegfx::fTools::more(fTextWidth, 0.0))
1186 // build text range
1187 const basegfx::B2DRange aTextRange(
1188 0.0, -aTextLayouterDevice.getFontAscent(),
1189 fTextWidth, aTextLayouterDevice.getFontDescent());
1191 // create Transform
1192 basegfx::B2DHomMatrix aTextTransform;
1194 aTextTransform.translate(aAlignmentOffset.getX(), aAlignmentOffset.getY());
1196 if(rFont.GetOrientation())
1198 aTextTransform.rotate(-toRadians(rFont.GetOrientation()));
1201 aTextTransform.translate(rTextStartPosition.X(), rTextStartPosition.Y());
1203 // prepare Primitive2DSequence, put text in foreground
1204 drawinglayer::primitive2d::Primitive2DContainer aSequence(2);
1205 aSequence[1] = pResult;
1207 // prepare filled polygon
1208 basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aTextRange));
1209 aOutline.transform(aTextTransform);
1211 aSequence[0] = drawinglayer::primitive2d::Primitive2DReference(
1212 new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
1213 basegfx::B2DPolyPolygon(aOutline),
1214 rProperty.getTextFillColor()));
1216 // set as group at pResult
1217 pResult = new drawinglayer::primitive2d::GroupPrimitive2D(std::move(aSequence));
1221 if(!pResult)
1222 return;
1224 // add created text primitive to target
1225 if(rProperty.getTransformation().isIdentity())
1227 rTarget.append(pResult);
1229 else
1231 // when a transformation is set, embed to it
1232 const drawinglayer::primitive2d::Primitive2DReference aReference(pResult);
1234 rTarget.append(
1235 new drawinglayer::primitive2d::TransformPrimitive2D(
1236 rProperty.getTransformation(),
1237 drawinglayer::primitive2d::Primitive2DContainer { aReference }));
1241 /** helper which takes complete care for creating the needed textLine primitives */
1242 static void processMetaTextLineAction(
1243 const MetaTextLineAction& rAction,
1244 TargetHolder& rTarget,
1245 PropertyHolder const & rProperty)
1247 const double fLineWidth(fabs(static_cast<double>(rAction.GetWidth())));
1249 if(fLineWidth <= 0.0)
1250 return;
1252 const drawinglayer::primitive2d::TextLine aOverlineMode(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rAction.GetOverline()));
1253 const drawinglayer::primitive2d::TextLine aUnderlineMode(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rAction.GetUnderline()));
1254 const drawinglayer::primitive2d::TextStrikeout aTextStrikeout(drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rAction.GetStrikeout()));
1256 const bool bOverlineUsed(drawinglayer::primitive2d::TEXT_LINE_NONE != aOverlineMode);
1257 const bool bUnderlineUsed(drawinglayer::primitive2d::TEXT_LINE_NONE != aUnderlineMode);
1258 const bool bStrikeoutUsed(drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE != aTextStrikeout);
1260 if(!(bUnderlineUsed || bStrikeoutUsed || bOverlineUsed))
1261 return;
1263 drawinglayer::primitive2d::Primitive2DContainer aTargets;
1264 basegfx::B2DVector aAlignmentOffset(0.0, 0.0);
1265 drawinglayer::attribute::FontAttribute aFontAttribute;
1266 basegfx::B2DHomMatrix aTextTransform;
1268 // fill parameters derived from current font
1269 createFontAttributeTransformAndAlignment(
1270 aFontAttribute,
1271 aTextTransform,
1272 aAlignmentOffset,
1273 rProperty);
1275 // add TextStartPosition
1276 aTextTransform.translate(rAction.GetStartPoint().X(), rAction.GetStartPoint().Y());
1278 // prepare TextLayouter (used in most cases)
1279 drawinglayer::primitive2d::TextLayouterDevice aTextLayouter;
1280 aTextLayouter.setFont(rProperty.getFont());
1282 if(bOverlineUsed)
1284 // create primitive geometry for overline
1285 aTargets.push_back(
1286 new drawinglayer::primitive2d::TextLinePrimitive2D(
1287 aTextTransform,
1288 fLineWidth,
1289 aTextLayouter.getOverlineOffset(),
1290 aTextLayouter.getOverlineHeight(),
1291 aOverlineMode,
1292 rProperty.getOverlineColor()));
1295 if(bUnderlineUsed)
1297 // create primitive geometry for underline
1298 aTargets.push_back(
1299 new drawinglayer::primitive2d::TextLinePrimitive2D(
1300 aTextTransform,
1301 fLineWidth,
1302 aTextLayouter.getUnderlineOffset(),
1303 aTextLayouter.getUnderlineHeight(),
1304 aUnderlineMode,
1305 rProperty.getTextLineColor()));
1308 if(bStrikeoutUsed)
1310 // create primitive geometry for strikeout
1311 if(drawinglayer::primitive2d::TEXT_STRIKEOUT_SLASH == aTextStrikeout
1312 || drawinglayer::primitive2d::TEXT_STRIKEOUT_X == aTextStrikeout)
1314 // strikeout with character
1315 const sal_Unicode aStrikeoutChar(
1316 drawinglayer::primitive2d::TEXT_STRIKEOUT_SLASH == aTextStrikeout ? '/' : 'X');
1317 css::lang::Locale aLocale(LanguageTag(
1318 rProperty.getLanguageType()).getLocale());
1320 aTargets.push_back(
1321 new drawinglayer::primitive2d::TextCharacterStrikeoutPrimitive2D(
1322 aTextTransform,
1323 fLineWidth,
1324 rProperty.getTextColor(),
1325 aStrikeoutChar,
1326 std::move(aFontAttribute),
1327 std::move(aLocale)));
1329 else
1331 // strikeout with geometry
1332 aTargets.push_back(
1333 new drawinglayer::primitive2d::TextGeometryStrikeoutPrimitive2D(
1334 aTextTransform,
1335 fLineWidth,
1336 rProperty.getTextColor(),
1337 aTextLayouter.getUnderlineHeight(),
1338 aTextLayouter.getStrikeoutOffset(),
1339 aTextStrikeout));
1343 if(aTargets.empty())
1344 return;
1346 // add created text primitive to target
1347 if(rProperty.getTransformation().isIdentity())
1349 rTarget.append(std::move(aTargets));
1351 else
1353 // when a transformation is set, embed to it
1354 rTarget.append(
1355 new drawinglayer::primitive2d::TransformPrimitive2D(
1356 rProperty.getTransformation(),
1357 std::move(aTargets)));
1361 /** This is the main interpreter method. It is designed to handle the given Metafile
1362 completely inside the given context and target. It may use and modify the context and
1363 target. This design allows to call itself recursively which adapted contexts and
1364 targets as e.g. needed for the MetaActionType::FLOATTRANSPARENT where the content is expressed
1365 as a metafile as sub-content.
1367 This interpreter is as free of VCL functionality as possible. It uses VCL data classes
1368 (else reading the data would not be possible), but e.g. does NOT use a local OutputDevice
1369 as most other MetaFile interpreters/exporters do to hold and work with the current context.
1370 This is necessary to be able to get away from the strong internal VCL-binding.
1372 It tries to combine e.g. pixel and/or point actions and to stitch together single line primitives
1373 where possible (which is not trivial with the possible line geometry definitions).
1375 It tries to handle clipping no longer as Regions and spans of Rectangles, but as PolyPolygon
1376 ClipRegions with (where possible) high precision by using the best possible data quality
1377 from the Region. The vcl::Region is unavoidable as data container, but nowadays allows the transport
1378 of Polygon-based clip regions. Where this is not used, a Polygon is constructed from the
1379 vcl::Region ranges. All primitive clipping uses the MaskPrimitive2D with Polygon-based clipping.
1381 I have marked the single MetaActions with:
1383 SIMPLE, DONE:
1384 Simple, e.g nothing to do or value setting in the context
1386 CHECKED, WORKS WELL:
1387 Thoroughly tested with extra written test code which created a replacement
1388 Metafile just to test this action in various combinations
1390 NEEDS IMPLEMENTATION:
1391 Not implemented and asserted, but also no usage found, neither in own Metafile
1392 creations, nor in EMF/WMF imports (checked with a whole bunch of critical EMF/WMF
1393 bugdocs)
1395 For more comments, see the single action implementations.
1397 static void implInterpretMetafile(
1398 const GDIMetaFile& rMetaFile,
1399 TargetHolders& rTargetHolders,
1400 PropertyHolders& rPropertyHolders,
1401 const drawinglayer::geometry::ViewInformation2D& rViewInformation)
1403 const size_t nCount(rMetaFile.GetActionSize());
1404 std::unique_ptr<emfplushelper::EmfPlusHelper> aEMFPlus;
1406 for(size_t nAction(0); nAction < nCount; nAction++)
1408 MetaAction* pAction = rMetaFile.GetAction(nAction);
1410 switch(pAction->GetType())
1412 case MetaActionType::NONE :
1414 /** SIMPLE, DONE */
1415 break;
1417 case MetaActionType::PIXEL :
1419 /** CHECKED, WORKS WELL */
1420 std::vector< basegfx::B2DPoint > aPositions;
1421 Color aLastColor(COL_BLACK);
1423 while(MetaActionType::PIXEL == pAction->GetType() && nAction < nCount)
1425 const MetaPixelAction* pA = static_cast<const MetaPixelAction*>(pAction);
1427 if(pA->GetColor() != aLastColor)
1429 if(!aPositions.empty())
1431 createPointArrayPrimitive(std::move(aPositions), rTargetHolders.Current(), rPropertyHolders.Current(), aLastColor.getBColor());
1432 aPositions.clear();
1435 aLastColor = pA->GetColor();
1438 const Point& rPoint = pA->GetPoint();
1439 aPositions.emplace_back(rPoint.X(), rPoint.Y());
1440 nAction++; if(nAction < nCount) pAction = rMetaFile.GetAction(nAction);
1443 nAction--;
1445 if(!aPositions.empty())
1447 createPointArrayPrimitive(std::move(aPositions), rTargetHolders.Current(), rPropertyHolders.Current(), aLastColor.getBColor());
1450 break;
1452 case MetaActionType::POINT :
1454 /** CHECKED, WORKS WELL */
1455 if(rPropertyHolders.Current().getLineColorActive())
1457 std::vector< basegfx::B2DPoint > aPositions;
1459 while(MetaActionType::POINT == pAction->GetType() && nAction < nCount)
1461 const MetaPointAction* pA = static_cast<const MetaPointAction*>(pAction);
1462 const Point& rPoint = pA->GetPoint();
1463 aPositions.emplace_back(rPoint.X(), rPoint.Y());
1464 nAction++; if(nAction < nCount) pAction = rMetaFile.GetAction(nAction);
1467 nAction--;
1469 if(!aPositions.empty())
1471 createPointArrayPrimitive(std::move(aPositions), rTargetHolders.Current(), rPropertyHolders.Current(), rPropertyHolders.Current().getLineColor());
1475 break;
1477 case MetaActionType::LINE :
1479 /** CHECKED, WORKS WELL */
1480 if(rPropertyHolders.Current().getLineColorActive())
1482 basegfx::B2DPolygon aLinePolygon;
1483 LineInfo aLineInfo;
1485 while(MetaActionType::LINE == pAction->GetType() && nAction < nCount)
1487 const MetaLineAction* pA = static_cast<const MetaLineAction*>(pAction);
1488 const Point& rStartPoint = pA->GetStartPoint();
1489 const Point& rEndPoint = pA->GetEndPoint();
1490 const basegfx::B2DPoint aStart(rStartPoint.X(), rStartPoint.Y());
1491 const basegfx::B2DPoint aEnd(rEndPoint.X(), rEndPoint.Y());
1493 if(aLinePolygon.count())
1495 if(pA->GetLineInfo() == aLineInfo
1496 && aStart == aLinePolygon.getB2DPoint(aLinePolygon.count() - 1))
1498 aLinePolygon.append(aEnd);
1500 else
1502 createLinePrimitive(aLinePolygon, aLineInfo, rTargetHolders.Current(), rPropertyHolders.Current());
1503 aLinePolygon.clear();
1504 aLineInfo = pA->GetLineInfo();
1505 aLinePolygon.append(aStart);
1506 aLinePolygon.append(aEnd);
1509 else
1511 aLineInfo = pA->GetLineInfo();
1512 aLinePolygon.append(aStart);
1513 aLinePolygon.append(aEnd);
1516 nAction++;
1517 if (nAction < nCount)
1518 pAction = rMetaFile.GetAction(nAction);
1521 nAction--;
1522 if (aLinePolygon.count())
1523 createLinePrimitive(aLinePolygon, aLineInfo, rTargetHolders.Current(), rPropertyHolders.Current());
1526 break;
1528 case MetaActionType::RECT :
1530 /** CHECKED, WORKS WELL */
1531 if(rPropertyHolders.Current().getLineOrFillActive())
1533 const MetaRectAction* pA = static_cast<const MetaRectAction*>(pAction);
1534 const tools::Rectangle& rRectangle = pA->GetRect();
1536 if(!rRectangle.IsEmpty())
1538 const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle);
1540 if(!aRange.isEmpty())
1542 const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange));
1543 createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
1548 break;
1550 case MetaActionType::ROUNDRECT :
1552 /** CHECKED, WORKS WELL */
1553 /** The original OutputDevice::DrawRect paints nothing when nHor or nVer is zero; but just
1554 because the tools::Polygon operator creating the rounding does produce nonsense. I assume
1555 this an error and create an unrounded rectangle in that case (implicit in
1556 createPolygonFromRect)
1558 if(rPropertyHolders.Current().getLineOrFillActive())
1560 const MetaRoundRectAction* pA = static_cast<const MetaRoundRectAction*>(pAction);
1561 const tools::Rectangle& rRectangle = pA->GetRect();
1563 if(!rRectangle.IsEmpty())
1565 const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle);
1567 if(!aRange.isEmpty())
1569 const sal_uInt32 nHor(pA->GetHorzRound());
1570 const sal_uInt32 nVer(pA->GetVertRound());
1571 basegfx::B2DPolygon aOutline;
1573 if(nHor || nVer)
1575 double fRadiusX((nHor * 2.0) / (aRange.getWidth() > 0.0 ? aRange.getWidth() : 1.0));
1576 double fRadiusY((nVer * 2.0) / (aRange.getHeight() > 0.0 ? aRange.getHeight() : 1.0));
1577 fRadiusX = std::clamp(fRadiusX, 0.0, 1.0);
1578 fRadiusY = std::clamp(fRadiusY, 0.0, 1.0);
1580 aOutline = basegfx::utils::createPolygonFromRect(aRange, fRadiusX, fRadiusY);
1582 else
1584 aOutline = basegfx::utils::createPolygonFromRect(aRange);
1587 createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
1592 break;
1594 case MetaActionType::ELLIPSE :
1596 /** CHECKED, WORKS WELL */
1597 if(rPropertyHolders.Current().getLineOrFillActive())
1599 const MetaEllipseAction* pA = static_cast<const MetaEllipseAction*>(pAction);
1600 const tools::Rectangle& rRectangle = pA->GetRect();
1602 if(!rRectangle.IsEmpty())
1604 const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle);
1606 if(!aRange.isEmpty())
1608 const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromEllipse(
1609 aRange.getCenter(), aRange.getWidth() * 0.5, aRange.getHeight() * 0.5));
1611 createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
1616 break;
1618 case MetaActionType::ARC :
1620 /** CHECKED, WORKS WELL */
1621 if(rPropertyHolders.Current().getLineColorActive())
1623 const MetaArcAction* pA = static_cast<const MetaArcAction*>(pAction);
1624 const tools::Polygon aToolsPoly(pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Arc);
1625 const basegfx::B2DPolygon aOutline(aToolsPoly.getB2DPolygon());
1627 createHairlinePrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
1630 break;
1632 case MetaActionType::PIE :
1634 /** CHECKED, WORKS WELL */
1635 if(rPropertyHolders.Current().getLineOrFillActive())
1637 const MetaPieAction* pA = static_cast<const MetaPieAction*>(pAction);
1638 const tools::Polygon aToolsPoly(pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Pie);
1639 const basegfx::B2DPolygon aOutline(aToolsPoly.getB2DPolygon());
1641 createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
1644 break;
1646 case MetaActionType::CHORD :
1648 /** CHECKED, WORKS WELL */
1649 if(rPropertyHolders.Current().getLineOrFillActive())
1651 const MetaChordAction* pA = static_cast<const MetaChordAction*>(pAction);
1652 const tools::Polygon aToolsPoly(pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Chord);
1653 const basegfx::B2DPolygon aOutline(aToolsPoly.getB2DPolygon());
1655 createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
1658 break;
1660 case MetaActionType::POLYLINE :
1662 /** CHECKED, WORKS WELL */
1663 if(rPropertyHolders.Current().getLineColorActive())
1665 const MetaPolyLineAction* pA = static_cast<const MetaPolyLineAction*>(pAction);
1666 createLinePrimitive(pA->GetPolygon().getB2DPolygon(), pA->GetLineInfo(), rTargetHolders.Current(), rPropertyHolders.Current());
1669 break;
1671 case MetaActionType::POLYGON :
1673 /** CHECKED, WORKS WELL */
1674 if(rPropertyHolders.Current().getLineOrFillActive())
1676 const MetaPolygonAction* pA = static_cast<const MetaPolygonAction*>(pAction);
1677 basegfx::B2DPolygon aOutline(pA->GetPolygon().getB2DPolygon());
1679 // the metafile play interprets the polygons from MetaPolygonAction
1680 // always as closed and always paints an edge from last to first point,
1681 // so force to closed here to emulate that
1682 if(aOutline.count() > 1 && !aOutline.isClosed())
1684 aOutline.setClosed(true);
1687 createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
1690 break;
1692 case MetaActionType::POLYPOLYGON :
1694 /** CHECKED, WORKS WELL */
1695 if(rPropertyHolders.Current().getLineOrFillActive())
1697 const MetaPolyPolygonAction* pA = static_cast<const MetaPolyPolygonAction*>(pAction);
1698 basegfx::B2DPolyPolygon aPolyPolygonOutline(pA->GetPolyPolygon().getB2DPolyPolygon());
1700 // the metafile play interprets the single polygons from MetaPolyPolygonAction
1701 // always as closed and always paints an edge from last to first point,
1702 // so force to closed here to emulate that
1703 for(sal_uInt32 b(0); b < aPolyPolygonOutline.count(); b++)
1705 basegfx::B2DPolygon aPolygonOutline(aPolyPolygonOutline.getB2DPolygon(b));
1707 if(aPolygonOutline.count() > 1 && !aPolygonOutline.isClosed())
1709 aPolygonOutline.setClosed(true);
1710 aPolyPolygonOutline.setB2DPolygon(b, aPolygonOutline);
1714 createHairlineAndFillPrimitive(aPolyPolygonOutline, rTargetHolders.Current(), rPropertyHolders.Current());
1717 break;
1719 case MetaActionType::TEXT :
1721 /** CHECKED, WORKS WELL */
1722 const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction);
1723 sal_uInt32 nTextLength(pA->GetLen());
1724 const sal_uInt32 nTextIndex(pA->GetIndex());
1725 const sal_uInt32 nStringLength(pA->GetText().getLength());
1727 if(nTextLength + nTextIndex > nStringLength)
1729 nTextLength = nStringLength - nTextIndex;
1732 if(nTextLength && rPropertyHolders.Current().getTextColorActive())
1734 std::vector< double > aDXArray{};
1735 processMetaTextAction(
1736 pA->GetPoint(),
1737 pA->GetText(),
1738 nTextIndex,
1739 nTextLength,
1740 std::move(aDXArray),
1742 rTargetHolders.Current(),
1743 rPropertyHolders.Current());
1746 break;
1748 case MetaActionType::TEXTARRAY :
1750 /** CHECKED, WORKS WELL */
1751 const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction);
1752 sal_uInt32 nTextLength(pA->GetLen());
1753 const sal_uInt32 nTextIndex(pA->GetIndex());
1754 const sal_uInt32 nStringLength(pA->GetText().getLength());
1756 if(nTextLength + nTextIndex > nStringLength)
1758 nTextLength = nTextIndex > nStringLength ? 0 : nStringLength - nTextIndex;
1761 if(nTextLength && rPropertyHolders.Current().getTextColorActive())
1763 // prepare DXArray (if used)
1764 std::vector< double > aDXArray;
1765 const KernArray& rDXArray = pA->GetDXArray();
1766 std::vector< sal_Bool > aKashidaArray = pA->GetKashidaArray();
1768 if(!rDXArray.empty())
1770 aDXArray.reserve(nTextLength);
1772 for(sal_uInt32 a(0); a < nTextLength; a++)
1774 aDXArray.push_back(static_cast<double>(rDXArray[a]));
1778 processMetaTextAction(
1779 pA->GetPoint(),
1780 pA->GetText(),
1781 nTextIndex,
1782 nTextLength,
1783 std::move(aDXArray),
1784 std::move(aKashidaArray),
1785 rTargetHolders.Current(),
1786 rPropertyHolders.Current());
1789 break;
1791 case MetaActionType::STRETCHTEXT :
1793 // #i108440# StarMath uses MetaStretchTextAction, thus support is needed.
1794 // It looks as if it pretty never really uses a width different from
1795 // the default text-layout width, but it's not possible to be sure.
1796 // Implemented getting the DXArray and checking for scale at all. If
1797 // scale is more than 3.5% different, scale the DXArray before usage.
1798 // New status:
1800 /** CHECKED, WORKS WELL */
1801 const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction);
1802 sal_uInt32 nTextLength(pA->GetLen());
1803 const sal_uInt32 nTextIndex(pA->GetIndex());
1804 const sal_uInt32 nStringLength(pA->GetText().getLength());
1806 if(nTextLength + nTextIndex > nStringLength)
1808 nTextLength = nStringLength - nTextIndex;
1811 if(nTextLength && rPropertyHolders.Current().getTextColorActive())
1813 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
1814 aTextLayouterDevice.setFont(rPropertyHolders.Current().getFont());
1816 std::vector< double > aTextArray(
1817 aTextLayouterDevice.getTextArray(
1818 pA->GetText(),
1819 nTextIndex,
1820 nTextLength));
1822 if(!aTextArray.empty())
1824 const double fTextLength(aTextArray.back());
1826 if(0.0 != fTextLength && pA->GetWidth())
1828 const double fRelative(pA->GetWidth() / fTextLength);
1830 if(fabs(fRelative - 1.0) >= 0.035)
1832 // when derivation is more than 3,5% from default text size,
1833 // scale the DXArray
1834 for(double & a : aTextArray)
1836 a *= fRelative;
1842 processMetaTextAction(
1843 pA->GetPoint(),
1844 pA->GetText(),
1845 nTextIndex,
1846 nTextLength,
1847 std::move(aTextArray),
1849 rTargetHolders.Current(),
1850 rPropertyHolders.Current());
1853 break;
1855 case MetaActionType::TEXTRECT :
1857 /** CHECKED, WORKS WELL */
1858 // OSL_FAIL("MetaActionType::TEXTRECT requested (!)");
1859 const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction);
1860 const tools::Rectangle& rRectangle = pA->GetRect();
1861 const sal_uInt32 nStringLength(pA->GetText().getLength());
1863 if(!rRectangle.IsEmpty() && 0 != nStringLength)
1865 // The problem with this action is that it describes unlayouted text
1866 // and the layout capabilities are in EditEngine/Outliner in SVX. The
1867 // same problem is true for VCL which internally has implementations
1868 // to layout text in this case. There exists even a call
1869 // OutputDevice::AddTextRectActions(...) to create the needed actions
1870 // as 'sub-content' of a Metafile. Unfortunately i do not have an
1871 // OutputDevice here since this interpreter tries to work without
1872 // VCL AFAP.
1873 // Since AddTextRectActions is the only way as long as we do not have
1874 // a simple text layouter available, i will try to add it to the
1875 // TextLayouterDevice isolation.
1876 drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
1877 aTextLayouterDevice.setFont(rPropertyHolders.Current().getFont());
1878 GDIMetaFile aGDIMetaFile;
1880 aTextLayouterDevice.addTextRectActions(
1881 rRectangle, pA->GetText(), pA->GetStyle(), aGDIMetaFile);
1883 if(aGDIMetaFile.GetActionSize())
1885 // create sub-content
1886 drawinglayer::primitive2d::Primitive2DContainer xSubContent;
1888 rTargetHolders.Push();
1890 // for sub-Mteafile contents, do start with new, default render state
1891 // #i124686# ...but copy font, this is already set accordingly
1892 vcl::Font aTargetFont = rPropertyHolders.Current().getFont();
1893 rPropertyHolders.PushDefault();
1894 rPropertyHolders.Current().setFont(aTargetFont);
1896 implInterpretMetafile(aGDIMetaFile, rTargetHolders, rPropertyHolders, rViewInformation);
1897 xSubContent = rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current());
1898 rPropertyHolders.Pop();
1899 rTargetHolders.Pop();
1902 if(!xSubContent.empty())
1904 // add with transformation
1905 rTargetHolders.Current().append(
1906 new drawinglayer::primitive2d::TransformPrimitive2D(
1907 rPropertyHolders.Current().getTransformation(),
1908 std::move(xSubContent)));
1913 break;
1915 case MetaActionType::BMP :
1917 /** CHECKED, WORKS WELL */
1918 const MetaBmpAction* pA = static_cast<const MetaBmpAction*>(pAction);
1919 const BitmapEx aBitmapEx(pA->GetBitmap());
1921 createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), rTargetHolders.Current(), rPropertyHolders.Current());
1923 break;
1925 case MetaActionType::BMPSCALE :
1927 /** CHECKED, WORKS WELL */
1928 const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
1929 const BitmapEx aBitmapEx(pA->GetBitmap());
1931 createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), pA->GetSize(), rTargetHolders.Current(), rPropertyHolders.Current());
1933 break;
1935 case MetaActionType::BMPSCALEPART :
1937 /** CHECKED, WORKS WELL */
1938 const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pAction);
1939 const Bitmap& rBitmap = pA->GetBitmap();
1941 if(!rBitmap.IsEmpty())
1943 Bitmap aCroppedBitmap(rBitmap);
1944 const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize());
1946 if(!aCropRectangle.IsEmpty())
1948 aCroppedBitmap.Crop(aCropRectangle);
1951 const BitmapEx aCroppedBitmapEx(aCroppedBitmap);
1952 createBitmapExPrimitive(aCroppedBitmapEx, pA->GetDestPoint(), pA->GetDestSize(), rTargetHolders.Current(), rPropertyHolders.Current());
1955 break;
1957 case MetaActionType::BMPEX :
1959 /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMP */
1960 const MetaBmpExAction* pA = static_cast<const MetaBmpExAction*>(pAction);
1961 const BitmapEx& rBitmapEx = pA->GetBitmapEx();
1963 createBitmapExPrimitive(rBitmapEx, pA->GetPoint(), rTargetHolders.Current(), rPropertyHolders.Current());
1965 break;
1967 case MetaActionType::BMPEXSCALE :
1969 /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALE */
1970 const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
1971 const BitmapEx& rBitmapEx = pA->GetBitmapEx();
1973 createBitmapExPrimitive(rBitmapEx, pA->GetPoint(), pA->GetSize(), rTargetHolders.Current(), rPropertyHolders.Current());
1975 break;
1977 case MetaActionType::BMPEXSCALEPART :
1979 /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALEPART */
1980 const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction);
1981 const BitmapEx& rBitmapEx = pA->GetBitmapEx();
1983 if(!rBitmapEx.IsEmpty())
1985 BitmapEx aCroppedBitmapEx(rBitmapEx);
1986 const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize());
1988 if(!aCropRectangle.IsEmpty())
1990 aCroppedBitmapEx.Crop(aCropRectangle);
1993 createBitmapExPrimitive(aCroppedBitmapEx, pA->GetDestPoint(), pA->GetDestSize(), rTargetHolders.Current(), rPropertyHolders.Current());
1996 break;
1998 case MetaActionType::MASK :
2000 /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMP */
2001 /** Huh, no it isn't!? */
2002 const MetaMaskAction* pA = static_cast<const MetaMaskAction*>(pAction);
2003 const BitmapEx aBitmapEx(createMaskBmpEx(pA->GetBitmap(), pA->GetColor()));
2005 createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), rTargetHolders.Current(), rPropertyHolders.Current());
2007 break;
2009 case MetaActionType::MASKSCALE :
2011 /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALE */
2012 const MetaMaskScaleAction* pA = static_cast<const MetaMaskScaleAction*>(pAction);
2013 const BitmapEx aBitmapEx(createMaskBmpEx(pA->GetBitmap(), pA->GetColor()));
2015 createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), pA->GetSize(), rTargetHolders.Current(), rPropertyHolders.Current());
2017 break;
2019 case MetaActionType::MASKSCALEPART :
2021 /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALEPART */
2022 const MetaMaskScalePartAction* pA = static_cast<const MetaMaskScalePartAction*>(pAction);
2023 const Bitmap& rBitmap = pA->GetBitmap();
2025 if(!rBitmap.IsEmpty())
2027 Bitmap aCroppedBitmap(rBitmap);
2028 const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize());
2030 if(!aCropRectangle.IsEmpty())
2032 aCroppedBitmap.Crop(aCropRectangle);
2035 const BitmapEx aCroppedBitmapEx(createMaskBmpEx(aCroppedBitmap, pA->GetColor()));
2036 createBitmapExPrimitive(aCroppedBitmapEx, pA->GetDestPoint(), pA->GetDestSize(), rTargetHolders.Current(), rPropertyHolders.Current());
2039 break;
2041 case MetaActionType::GRADIENT :
2043 /** CHECKED, WORKS WELL */
2044 const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pAction);
2045 const tools::Rectangle& rRectangle = pA->GetRect();
2047 if(!rRectangle.IsEmpty())
2049 basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle);
2051 if(!aRange.isEmpty())
2053 const Gradient& rGradient = pA->GetGradient();
2054 drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient));
2055 basegfx::B2DPolyPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange));
2056 basegfx::BColor aSingleColor;
2058 if (aAttribute.getColorStops().isSingleColor(aSingleColor))
2060 // not really a gradient. Create filled rectangle
2061 createFillPrimitive(
2062 aOutline,
2063 rTargetHolders.Current(),
2064 rPropertyHolders.Current());
2066 else
2068 // really a gradient
2069 aRange.transform(rPropertyHolders.Current().getTransformation());
2070 drawinglayer::primitive2d::Primitive2DContainer xGradient(1);
2072 if(rPropertyHolders.Current().isRasterOpInvert())
2074 // use a special version of FillGradientPrimitive2D which creates
2075 // non-overlapping geometry on decomposition to make the old XOR
2076 // paint 'trick' work.
2077 xGradient[0] = drawinglayer::primitive2d::Primitive2DReference(
2078 new drawinglayer::primitive2d::NonOverlappingFillGradientPrimitive2D(
2079 aRange,
2080 aAttribute));
2082 else
2084 xGradient[0] = drawinglayer::primitive2d::Primitive2DReference(
2085 new drawinglayer::primitive2d::FillGradientPrimitive2D(
2086 aRange,
2087 std::move(aAttribute)));
2090 // #i112300# clip against polygon representing the rectangle from
2091 // the action. This is implicitly done using a temp Clipping in VCL
2092 // when a MetaGradientAction is executed
2093 aOutline.transform(rPropertyHolders.Current().getTransformation());
2094 rTargetHolders.Current().append(
2095 new drawinglayer::primitive2d::MaskPrimitive2D(
2096 std::move(aOutline),
2097 std::move(xGradient)));
2102 break;
2104 case MetaActionType::HATCH :
2106 /** CHECKED, WORKS WELL */
2107 const MetaHatchAction* pA = static_cast<const MetaHatchAction*>(pAction);
2108 basegfx::B2DPolyPolygon aOutline(pA->GetPolyPolygon().getB2DPolyPolygon());
2110 if(aOutline.count())
2112 const Hatch& rHatch = pA->GetHatch();
2113 drawinglayer::attribute::FillHatchAttribute aAttribute(createFillHatchAttribute(rHatch));
2115 aOutline.transform(rPropertyHolders.Current().getTransformation());
2117 const basegfx::B2DRange aObjectRange(aOutline.getB2DRange());
2118 const drawinglayer::primitive2d::Primitive2DReference aFillHatch(
2119 new drawinglayer::primitive2d::FillHatchPrimitive2D(
2120 aObjectRange,
2121 basegfx::BColor(),
2122 std::move(aAttribute)));
2124 rTargetHolders.Current().append(
2125 new drawinglayer::primitive2d::MaskPrimitive2D(
2126 std::move(aOutline),
2127 drawinglayer::primitive2d::Primitive2DContainer { aFillHatch }));
2130 break;
2132 case MetaActionType::WALLPAPER :
2134 /** CHECKED, WORKS WELL */
2135 const MetaWallpaperAction* pA = static_cast<const MetaWallpaperAction*>(pAction);
2136 tools::Rectangle aWallpaperRectangle(pA->GetRect());
2138 if(!aWallpaperRectangle.IsEmpty())
2140 const Wallpaper& rWallpaper = pA->GetWallpaper();
2141 const WallpaperStyle eWallpaperStyle(rWallpaper.GetStyle());
2142 basegfx::B2DRange aWallpaperRange = vcl::unotools::b2DRectangleFromRectangle(aWallpaperRectangle);
2144 if(WallpaperStyle::NONE != eWallpaperStyle)
2146 if(rWallpaper.IsBitmap())
2148 // create bitmap background. Caution: This
2149 // also will create gradient/color background(s)
2150 // when the bitmap is transparent or not tiled
2151 CreateAndAppendBitmapWallpaper(
2152 aWallpaperRange,
2153 rWallpaper,
2154 rTargetHolders.Current(),
2155 rPropertyHolders.Current());
2157 else if(rWallpaper.IsGradient())
2159 // create gradient background
2160 rTargetHolders.Current().append(
2161 CreateGradientWallpaper(
2162 aWallpaperRange,
2163 rWallpaper.GetGradient(),
2164 rPropertyHolders.Current()));
2166 else if(!rWallpaper.GetColor().IsTransparent())
2168 // create color background
2169 rTargetHolders.Current().append(
2170 CreateColorWallpaper(
2171 aWallpaperRange,
2172 rWallpaper.GetColor().getBColor(),
2173 rPropertyHolders.Current()));
2178 break;
2180 case MetaActionType::CLIPREGION :
2182 /** CHECKED, WORKS WELL */
2183 const MetaClipRegionAction* pA = static_cast<const MetaClipRegionAction*>(pAction);
2185 if(pA->IsClipping())
2187 // new clipping. Get tools::PolyPolygon and transform with current transformation
2188 basegfx::B2DPolyPolygon aNewClipPolyPolygon(getB2DPolyPolygonFromRegion(pA->GetRegion()));
2190 aNewClipPolyPolygon.transform(rPropertyHolders.Current().getTransformation());
2191 HandleNewClipRegion(aNewClipPolyPolygon, rTargetHolders, rPropertyHolders);
2193 else
2195 // end clipping
2196 const basegfx::B2DPolyPolygon aEmptyPolyPolygon;
2198 HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders);
2201 break;
2203 case MetaActionType::ISECTRECTCLIPREGION :
2205 /** CHECKED, WORKS WELL */
2206 const MetaISectRectClipRegionAction* pA = static_cast<const MetaISectRectClipRegionAction*>(pAction);
2207 const tools::Rectangle& rRectangle = pA->GetRect();
2209 if(rRectangle.IsEmpty())
2211 // intersect with empty rectangle will always give empty
2212 // ClipPolyPolygon; start new clipping with empty PolyPolygon
2213 const basegfx::B2DPolyPolygon aEmptyPolyPolygon;
2215 HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders);
2217 else
2219 // create transformed ClipRange
2220 basegfx::B2DRange aClipRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle);
2222 aClipRange.transform(rPropertyHolders.Current().getTransformation());
2224 if(rPropertyHolders.Current().getClipPolyPolygonActive())
2226 if(0 == rPropertyHolders.Current().getClipPolyPolygon().count())
2228 // nothing to do, empty active clipPolyPolygon will stay
2229 // empty when intersecting
2231 else
2233 // AND existing region and new ClipRange
2234 const basegfx::B2DPolyPolygon aOriginalPolyPolygon(
2235 rPropertyHolders.Current().getClipPolyPolygon());
2236 basegfx::B2DPolyPolygon aClippedPolyPolygon;
2238 if(aOriginalPolyPolygon.count())
2240 aClippedPolyPolygon = basegfx::utils::clipPolyPolygonOnRange(
2241 aOriginalPolyPolygon,
2242 aClipRange,
2243 true,
2244 false);
2247 if(aClippedPolyPolygon != aOriginalPolyPolygon)
2249 // start new clipping with intersected region
2250 HandleNewClipRegion(
2251 aClippedPolyPolygon,
2252 rTargetHolders,
2253 rPropertyHolders);
2257 else
2259 // start new clipping with ClipRange
2260 const basegfx::B2DPolyPolygon aNewClipPolyPolygon(
2261 basegfx::utils::createPolygonFromRect(aClipRange));
2263 HandleNewClipRegion(aNewClipPolyPolygon, rTargetHolders, rPropertyHolders);
2267 break;
2269 case MetaActionType::ISECTREGIONCLIPREGION :
2271 /** CHECKED, WORKS WELL */
2272 const MetaISectRegionClipRegionAction* pA = static_cast<const MetaISectRegionClipRegionAction*>(pAction);
2273 const vcl::Region& rNewRegion = pA->GetRegion();
2275 if(rNewRegion.IsEmpty())
2277 // intersect with empty region will always give empty
2278 // region; start new clipping with empty PolyPolygon
2279 const basegfx::B2DPolyPolygon aEmptyPolyPolygon;
2281 HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders);
2283 else
2285 // get new ClipPolyPolygon, transform it with current transformation
2286 basegfx::B2DPolyPolygon aNewClipPolyPolygon(getB2DPolyPolygonFromRegion(rNewRegion));
2287 aNewClipPolyPolygon.transform(rPropertyHolders.Current().getTransformation());
2289 if(rPropertyHolders.Current().getClipPolyPolygonActive())
2291 if(0 == rPropertyHolders.Current().getClipPolyPolygon().count())
2293 // nothing to do, empty active clipPolyPolygon will stay empty
2294 // when intersecting with any region
2296 else
2298 // AND existing and new region
2299 const basegfx::B2DPolyPolygon aOriginalPolyPolygon(
2300 rPropertyHolders.Current().getClipPolyPolygon());
2301 basegfx::B2DPolyPolygon aClippedPolyPolygon;
2303 if(aOriginalPolyPolygon.count())
2305 aClippedPolyPolygon = basegfx::utils::clipPolyPolygonOnPolyPolygon(
2306 aOriginalPolyPolygon, aNewClipPolyPolygon, true, false);
2309 if(aClippedPolyPolygon != aOriginalPolyPolygon)
2311 // start new clipping with intersected ClipPolyPolygon
2312 HandleNewClipRegion(aClippedPolyPolygon, rTargetHolders, rPropertyHolders);
2316 else
2318 // start new clipping with new ClipPolyPolygon
2319 HandleNewClipRegion(aNewClipPolyPolygon, rTargetHolders, rPropertyHolders);
2323 break;
2325 case MetaActionType::MOVECLIPREGION :
2327 /** CHECKED, WORKS WELL */
2328 const MetaMoveClipRegionAction* pA = static_cast<const MetaMoveClipRegionAction*>(pAction);
2330 if(rPropertyHolders.Current().getClipPolyPolygonActive())
2332 if(0 == rPropertyHolders.Current().getClipPolyPolygon().count())
2334 // nothing to do
2336 else
2338 const sal_Int32 nHor(pA->GetHorzMove());
2339 const sal_Int32 nVer(pA->GetVertMove());
2341 if(0 != nHor || 0 != nVer)
2343 // prepare translation, add current transformation
2344 basegfx::B2DVector aVector(pA->GetHorzMove(), pA->GetVertMove());
2345 aVector *= rPropertyHolders.Current().getTransformation();
2346 basegfx::B2DHomMatrix aTransform(
2347 basegfx::utils::createTranslateB2DHomMatrix(aVector));
2349 // transform existing region
2350 basegfx::B2DPolyPolygon aClipPolyPolygon(
2351 rPropertyHolders.Current().getClipPolyPolygon());
2353 aClipPolyPolygon.transform(aTransform);
2354 HandleNewClipRegion(aClipPolyPolygon, rTargetHolders, rPropertyHolders);
2359 break;
2361 case MetaActionType::LINECOLOR :
2363 /** CHECKED, WORKS WELL */
2364 const MetaLineColorAction* pA = static_cast<const MetaLineColorAction*>(pAction);
2365 // tdf#89901 do as OutDev does: COL_TRANSPARENT deactivates line draw
2366 const bool bActive(pA->IsSetting() && COL_TRANSPARENT != pA->GetColor());
2368 rPropertyHolders.Current().setLineColorActive(bActive);
2369 if(bActive)
2370 rPropertyHolders.Current().setLineColor(pA->GetColor().getBColor());
2372 break;
2374 case MetaActionType::FILLCOLOR :
2376 /** CHECKED, WORKS WELL */
2377 const MetaFillColorAction* pA = static_cast<const MetaFillColorAction*>(pAction);
2378 // tdf#89901 do as OutDev does: COL_TRANSPARENT deactivates polygon fill
2379 const bool bActive(pA->IsSetting() && COL_TRANSPARENT != pA->GetColor());
2381 rPropertyHolders.Current().setFillColorActive(bActive);
2382 if(bActive)
2383 rPropertyHolders.Current().setFillColor(pA->GetColor().getBColor());
2385 break;
2387 case MetaActionType::TEXTCOLOR :
2389 /** SIMPLE, DONE */
2390 const MetaTextColorAction* pA = static_cast<const MetaTextColorAction*>(pAction);
2391 const bool bActivate(COL_TRANSPARENT != pA->GetColor());
2393 rPropertyHolders.Current().setTextColorActive(bActivate);
2394 rPropertyHolders.Current().setTextColor(pA->GetColor().getBColor());
2396 break;
2398 case MetaActionType::TEXTFILLCOLOR :
2400 /** SIMPLE, DONE */
2401 const MetaTextFillColorAction* pA = static_cast<const MetaTextFillColorAction*>(pAction);
2402 const bool bWithColorArgument(pA->IsSetting());
2404 if(bWithColorArgument)
2406 // emulate OutputDevice::SetTextFillColor(...) WITH argument
2407 const Color& rFontFillColor = pA->GetColor();
2408 rPropertyHolders.Current().setTextFillColor(rFontFillColor.getBColor());
2409 rPropertyHolders.Current().setTextFillColorActive(COL_TRANSPARENT != rFontFillColor);
2411 else
2413 // emulate SetFillColor() <- NO argument (!)
2414 rPropertyHolders.Current().setTextFillColorActive(false);
2417 break;
2419 case MetaActionType::TEXTALIGN :
2421 /** SIMPLE, DONE */
2422 const MetaTextAlignAction* pA = static_cast<const MetaTextAlignAction*>(pAction);
2423 const TextAlign aNewTextAlign = pA->GetTextAlign();
2425 // TextAlign is applied to the current font (as in
2426 // OutputDevice::SetTextAlign which would be used when
2427 // playing the Metafile)
2428 if(rPropertyHolders.Current().getFont().GetAlignment() != aNewTextAlign)
2430 vcl::Font aNewFont(rPropertyHolders.Current().getFont());
2431 aNewFont.SetAlignment(aNewTextAlign);
2432 rPropertyHolders.Current().setFont(aNewFont);
2435 break;
2437 case MetaActionType::MAPMODE :
2439 /** CHECKED, WORKS WELL */
2440 // the most necessary MapMode to be interpreted is MapUnit::MapRelative,
2441 // but also the others may occur. Even not yet supported ones
2442 // may need to be added here later
2443 const MetaMapModeAction* pA = static_cast<const MetaMapModeAction*>(pAction);
2444 const MapMode& rMapMode = pA->GetMapMode();
2445 basegfx::B2DHomMatrix aMapping;
2447 if(MapUnit::MapRelative == rMapMode.GetMapUnit())
2449 aMapping = getTransformFromMapMode(rMapMode);
2451 else
2453 const auto eFrom = MapToO3tlLength(rPropertyHolders.Current().getMapUnit()),
2454 eTo = MapToO3tlLength(rMapMode.GetMapUnit());
2455 if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid)
2457 const double fConvert(o3tl::convert(1.0, eFrom, eTo));
2458 aMapping.scale(fConvert, fConvert);
2460 else
2461 OSL_FAIL("implInterpretMetafile: MetaActionType::MAPMODE with unsupported MapUnit (!)");
2463 aMapping = getTransformFromMapMode(rMapMode) * aMapping;
2464 rPropertyHolders.Current().setMapUnit(rMapMode.GetMapUnit());
2467 if(!aMapping.isIdentity())
2469 aMapping = aMapping * rPropertyHolders.Current().getTransformation();
2470 rPropertyHolders.Current().setTransformation(aMapping);
2473 break;
2475 case MetaActionType::FONT :
2477 /** SIMPLE, DONE */
2478 const MetaFontAction* pA = static_cast<const MetaFontAction*>(pAction);
2479 rPropertyHolders.Current().setFont(pA->GetFont());
2480 Size aFontSize(pA->GetFont().GetFontSize());
2482 if(0 == aFontSize.Height())
2484 // this should not happen but i got Metafiles where this was the
2485 // case. A height needs to be guessed (similar to OutputDevice::ImplNewFont())
2486 vcl::Font aCorrectedFont(pA->GetFont());
2488 // guess 16 pixel (as in VCL)
2489 aFontSize = Size(0, 16);
2491 // convert to target MapUnit if not pixels
2492 aFontSize = OutputDevice::LogicToLogic(
2493 aFontSize, MapMode(MapUnit::MapPixel), MapMode(rPropertyHolders.Current().getMapUnit()));
2495 aCorrectedFont.SetFontSize(aFontSize);
2496 rPropertyHolders.Current().setFont(aCorrectedFont);
2499 // older Metafiles have no MetaActionType::TEXTCOLOR which defines
2500 // the FontColor now, so use the Font's color when not transparent
2501 const Color& rFontColor = pA->GetFont().GetColor();
2502 const bool bActivate(COL_TRANSPARENT != rFontColor);
2504 if(bActivate)
2506 rPropertyHolders.Current().setTextColor(rFontColor.getBColor());
2509 // caution: do NOT deactivate here on transparent, see
2510 // OutputDevice::SetFont(..) for more info
2511 // rPropertyHolders.Current().setTextColorActive(bActivate);
2513 // for fill color emulate a MetaTextFillColorAction with !transparent as bool,
2514 // see OutputDevice::SetFont(..) the if(mpMetaFile) case
2515 if(bActivate)
2517 const Color& rFontFillColor = pA->GetFont().GetFillColor();
2518 rPropertyHolders.Current().setTextFillColor(rFontFillColor.getBColor());
2519 rPropertyHolders.Current().setTextFillColorActive(COL_TRANSPARENT != rFontFillColor);
2521 else
2523 rPropertyHolders.Current().setTextFillColorActive(false);
2526 break;
2528 case MetaActionType::PUSH :
2530 /** CHECKED, WORKS WELL */
2531 const MetaPushAction* pA = static_cast<const MetaPushAction*>(pAction);
2532 rPropertyHolders.Push(pA->GetFlags());
2534 break;
2536 case MetaActionType::POP :
2538 /** CHECKED, WORKS WELL */
2539 const bool bRegionMayChange(rPropertyHolders.Current().getPushFlags() & vcl::PushFlags::CLIPREGION);
2540 const bool bRasterOpMayChange(rPropertyHolders.Current().getPushFlags() & vcl::PushFlags::RASTEROP);
2542 if(bRegionMayChange && rPropertyHolders.Current().getClipPolyPolygonActive())
2544 // end evtl. clipping
2545 const basegfx::B2DPolyPolygon aEmptyPolyPolygon;
2547 HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders);
2550 if(bRasterOpMayChange && rPropertyHolders.Current().isRasterOpActive())
2552 // end evtl. RasterOp
2553 HandleNewRasterOp(RasterOp::OverPaint, rTargetHolders, rPropertyHolders);
2556 rPropertyHolders.Pop();
2558 if(bRasterOpMayChange && rPropertyHolders.Current().isRasterOpActive())
2560 // start evtl. RasterOp
2561 HandleNewRasterOp(rPropertyHolders.Current().getRasterOp(), rTargetHolders, rPropertyHolders);
2564 if(bRegionMayChange && rPropertyHolders.Current().getClipPolyPolygonActive())
2566 // start evtl. clipping
2567 HandleNewClipRegion(
2568 rPropertyHolders.Current().getClipPolyPolygon(), rTargetHolders, rPropertyHolders);
2571 break;
2573 case MetaActionType::RASTEROP :
2575 /** CHECKED, WORKS WELL */
2576 const MetaRasterOpAction* pA = static_cast<const MetaRasterOpAction*>(pAction);
2577 const RasterOp aRasterOp = pA->GetRasterOp();
2579 HandleNewRasterOp(aRasterOp, rTargetHolders, rPropertyHolders);
2581 break;
2583 case MetaActionType::Transparent :
2585 /** CHECKED, WORKS WELL */
2586 const MetaTransparentAction* pA = static_cast<const MetaTransparentAction*>(pAction);
2587 const basegfx::B2DPolyPolygon aOutline(pA->GetPolyPolygon().getB2DPolyPolygon());
2589 if(aOutline.count())
2591 const sal_uInt16 nTransparence(pA->GetTransparence());
2593 if(0 == nTransparence)
2595 // not transparent
2596 createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
2598 else if(nTransparence >= 100)
2600 // fully or more than transparent
2602 else
2604 // transparent. Create new target
2605 rTargetHolders.Push();
2607 // create primitives there and get them
2608 createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
2609 drawinglayer::primitive2d::Primitive2DContainer aSubContent(
2610 rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current()));
2612 // back to old target
2613 rTargetHolders.Pop();
2615 if(!aSubContent.empty())
2617 rTargetHolders.Current().append(
2618 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
2619 std::move(aSubContent),
2620 nTransparence * 0.01));
2625 break;
2627 case MetaActionType::EPS :
2629 /** CHECKED, WORKS WELL */
2630 // To support this action, I have added a EpsPrimitive2D which will
2631 // by default decompose to the Metafile replacement data. To support
2632 // this EPS on screen, the renderer visualizing this has to support
2633 // that primitive and visualize the Eps file (e.g. printing)
2634 const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pAction);
2635 const tools::Rectangle aRectangle(pA->GetPoint(), pA->GetSize());
2637 if(!aRectangle.IsEmpty())
2639 // create object transform
2640 basegfx::B2DHomMatrix aObjectTransform;
2642 aObjectTransform.set(0, 0, aRectangle.GetWidth());
2643 aObjectTransform.set(1, 1, aRectangle.GetHeight());
2644 aObjectTransform.set(0, 2, aRectangle.Left());
2645 aObjectTransform.set(1, 2, aRectangle.Top());
2647 // add current transformation
2648 aObjectTransform = rPropertyHolders.Current().getTransformation() * aObjectTransform;
2650 // embed using EpsPrimitive
2651 rTargetHolders.Current().append(
2652 new drawinglayer::primitive2d::EpsPrimitive2D(
2653 aObjectTransform,
2654 pA->GetLink(),
2655 pA->GetSubstitute()));
2658 break;
2660 case MetaActionType::REFPOINT :
2662 /** SIMPLE, DONE */
2663 // only used for hatch and line pattern offsets, pretty much no longer
2664 // supported today
2665 // const MetaRefPointAction* pA = (const MetaRefPointAction*)pAction;
2666 break;
2668 case MetaActionType::TEXTLINECOLOR :
2670 /** SIMPLE, DONE */
2671 const MetaTextLineColorAction* pA = static_cast<const MetaTextLineColorAction*>(pAction);
2672 const bool bActive(pA->IsSetting());
2674 rPropertyHolders.Current().setTextLineColorActive(bActive);
2675 if(bActive)
2676 rPropertyHolders.Current().setTextLineColor(pA->GetColor().getBColor());
2678 break;
2680 case MetaActionType::TEXTLINE :
2682 /** CHECKED, WORKS WELL */
2683 // actually creates overline, underline and strikeouts, so
2684 // these should be isolated from TextDecoratedPortionPrimitive2D
2685 // to own primitives. Done, available now.
2687 // This Metaaction seems not to be used (was not used in any
2688 // checked files). It's used in combination with the current
2689 // Font.
2690 const MetaTextLineAction* pA = static_cast<const MetaTextLineAction*>(pAction);
2692 processMetaTextLineAction(
2693 *pA,
2694 rTargetHolders.Current(),
2695 rPropertyHolders.Current());
2697 break;
2699 case MetaActionType::FLOATTRANSPARENT :
2701 /** CHECKED, WORKS WELL */
2702 const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pAction);
2703 const basegfx::B2DRange aTargetRange(
2704 pA->GetPoint().X(),
2705 pA->GetPoint().Y(),
2706 pA->GetPoint().X() + pA->GetSize().Width(),
2707 pA->GetPoint().Y() + pA->GetSize().Height());
2709 if(!aTargetRange.isEmpty())
2711 const GDIMetaFile& rContent = pA->GetGDIMetaFile();
2713 if(rContent.GetActionSize())
2715 // create the sub-content with no embedding specific to the
2716 // sub-metafile, this seems not to be used.
2717 drawinglayer::primitive2d::Primitive2DContainer xSubContent;
2719 rTargetHolders.Push();
2720 // #i# for sub-Mteafile contents, do start with new, default render state
2721 rPropertyHolders.PushDefault();
2722 implInterpretMetafile(rContent, rTargetHolders, rPropertyHolders, rViewInformation);
2723 xSubContent = rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current());
2724 rPropertyHolders.Pop();
2725 rTargetHolders.Pop();
2728 if(!xSubContent.empty())
2730 // prepare sub-content transform
2731 basegfx::B2DHomMatrix aSubTransform;
2733 // create SourceRange
2734 const basegfx::B2DRange aSourceRange(
2735 rContent.GetPrefMapMode().GetOrigin().X(),
2736 rContent.GetPrefMapMode().GetOrigin().Y(),
2737 rContent.GetPrefMapMode().GetOrigin().X() + rContent.GetPrefSize().Width(),
2738 rContent.GetPrefMapMode().GetOrigin().Y() + rContent.GetPrefSize().Height());
2740 // apply mapping if aTargetRange and aSourceRange are not equal
2741 if(!aSourceRange.equal(aTargetRange))
2743 aSubTransform.translate(-aSourceRange.getMinX(), -aSourceRange.getMinY());
2744 aSubTransform.scale(
2745 aTargetRange.getWidth() / (basegfx::fTools::equalZero(aSourceRange.getWidth()) ? 1.0 : aSourceRange.getWidth()),
2746 aTargetRange.getHeight() / (basegfx::fTools::equalZero(aSourceRange.getHeight()) ? 1.0 : aSourceRange.getHeight()));
2747 aSubTransform.translate(aTargetRange.getMinX(), aTargetRange.getMinY());
2750 // apply general current transformation
2751 aSubTransform = rPropertyHolders.Current().getTransformation() * aSubTransform;
2753 // evtl. embed sub-content to its transformation
2754 if(!aSubTransform.isIdentity())
2756 const drawinglayer::primitive2d::Primitive2DReference aEmbeddedTransform(
2757 new drawinglayer::primitive2d::TransformPrimitive2D(
2758 aSubTransform,
2759 std::move(xSubContent)));
2761 xSubContent = drawinglayer::primitive2d::Primitive2DContainer { aEmbeddedTransform };
2764 // check if gradient is a real gradient
2765 const Gradient& rGradient = pA->GetGradient();
2766 drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient));
2767 basegfx::BColor aSingleColor;
2769 if (aAttribute.getColorStops().isSingleColor(aSingleColor))
2771 // not really a gradient; create UnifiedTransparencePrimitive2D
2772 rTargetHolders.Current().append(
2773 new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
2774 std::move(xSubContent),
2775 aSingleColor.luminance()));
2777 else
2779 // really a gradient. Create gradient sub-content (with correct scaling)
2780 basegfx::B2DRange aRange(aTargetRange);
2781 aRange.transform(rPropertyHolders.Current().getTransformation());
2783 // prepare gradient for transparent content
2784 const drawinglayer::primitive2d::Primitive2DReference xTransparence(
2785 new drawinglayer::primitive2d::FillGradientPrimitive2D(
2786 aRange,
2787 std::move(aAttribute)));
2789 // create transparence primitive
2790 rTargetHolders.Current().append(
2791 new drawinglayer::primitive2d::TransparencePrimitive2D(
2792 std::move(xSubContent),
2793 drawinglayer::primitive2d::Primitive2DContainer { xTransparence }));
2799 break;
2801 case MetaActionType::GRADIENTEX :
2803 /** SIMPLE, DONE */
2804 // This is only a data holder which is interpreted inside comment actions,
2805 // see MetaActionType::COMMENT for more info
2806 // const MetaGradientExAction* pA = (const MetaGradientExAction*)pAction;
2807 break;
2809 case MetaActionType::LAYOUTMODE :
2811 /** SIMPLE, DONE */
2812 const MetaLayoutModeAction* pA = static_cast<const MetaLayoutModeAction*>(pAction);
2813 rPropertyHolders.Current().setLayoutMode(pA->GetLayoutMode());
2814 break;
2816 case MetaActionType::TEXTLANGUAGE :
2818 /** SIMPLE, DONE */
2819 const MetaTextLanguageAction* pA = static_cast<const MetaTextLanguageAction*>(pAction);
2820 rPropertyHolders.Current().setLanguageType(pA->GetTextLanguage());
2821 break;
2823 case MetaActionType::OVERLINECOLOR :
2825 /** SIMPLE, DONE */
2826 const MetaOverlineColorAction* pA = static_cast<const MetaOverlineColorAction*>(pAction);
2827 const bool bActive(pA->IsSetting());
2829 rPropertyHolders.Current().setOverlineColorActive(bActive);
2830 if(bActive)
2831 rPropertyHolders.Current().setOverlineColor(pA->GetColor().getBColor());
2833 break;
2835 case MetaActionType::COMMENT :
2837 /** CHECKED, WORKS WELL */
2838 // I already implemented
2839 // XPATHFILL_SEQ_BEGIN, XPATHFILL_SEQ_END
2840 // XPATHSTROKE_SEQ_BEGIN, XPATHSTROKE_SEQ_END,
2841 // but opted to remove these again; it works well without them
2842 // and makes the code less dependent from those Metafile Add-Ons
2843 const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
2845 if (pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN"))
2847 // XGRAD_SEQ_BEGIN, XGRAD_SEQ_END should be supported since the
2848 // pure recorded paint of the gradients uses the XOR paint functionality
2849 // ('trick'). This is (and will be) problematic with AntiAliasing, so it's
2850 // better to use this info
2851 const MetaGradientExAction* pMetaGradientExAction = nullptr;
2852 bool bDone(false);
2853 size_t b(nAction + 1);
2855 for(; !bDone && b < nCount; b++)
2857 pAction = rMetaFile.GetAction(b);
2859 if(MetaActionType::GRADIENTEX == pAction->GetType())
2861 pMetaGradientExAction = static_cast<const MetaGradientExAction*>(pAction);
2863 else if(MetaActionType::COMMENT == pAction->GetType())
2865 if (static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END"))
2867 bDone = true;
2872 if(bDone && pMetaGradientExAction)
2874 // consume actions and skip forward
2875 nAction = b - 1;
2877 // get geometry data
2878 basegfx::B2DPolyPolygon aPolyPolygon(pMetaGradientExAction->GetPolyPolygon().getB2DPolyPolygon());
2880 if(aPolyPolygon.count())
2882 // transform geometry
2883 aPolyPolygon.transform(rPropertyHolders.Current().getTransformation());
2885 // get and check if gradient is a real gradient
2886 const Gradient& rGradient = pMetaGradientExAction->GetGradient();
2887 drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient));
2888 basegfx::BColor aSingleColor;
2890 if (aAttribute.getColorStops().isSingleColor(aSingleColor))
2892 // not really a gradient
2893 rTargetHolders.Current().append(
2894 new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
2895 std::move(aPolyPolygon),
2896 aSingleColor));
2898 else
2900 // really a gradient
2901 rTargetHolders.Current().append(
2902 new drawinglayer::primitive2d::PolyPolygonGradientPrimitive2D(
2903 aPolyPolygon,
2904 std::move(aAttribute)));
2909 else if (pA->GetComment().equalsIgnoreAsciiCase("EMF_PLUS_HEADER_INFO"))
2911 if (aEMFPlus)
2913 // error: should not yet exist
2914 SAL_INFO("drawinglayer.emf", "Error: multiple EMF_PLUS_HEADER_INFO");
2916 else
2918 SAL_INFO("drawinglayer.emf", "EMF+ passed to canvas mtf renderer - header info, size: " << pA->GetDataSize());
2919 SvMemoryStream aMemoryStream(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(), StreamMode::READ);
2921 aEMFPlus.reset(
2922 new emfplushelper::EmfPlusHelper(
2923 aMemoryStream,
2924 rTargetHolders,
2925 rPropertyHolders));
2928 else if (pA->GetComment().equalsIgnoreAsciiCase("EMF_PLUS"))
2930 if (!aEMFPlus)
2932 // error: should exist
2933 SAL_INFO("drawinglayer.emf", "Error: EMF_PLUS before EMF_PLUS_HEADER_INFO");
2935 else
2937 static int count = -1, limit = 0x7fffffff;
2939 if (count == -1)
2941 count = 0;
2943 if (char *env = getenv("EMF_PLUS_LIMIT"))
2945 limit = atoi(env);
2946 SAL_INFO("drawinglayer.emf", "EMF+ records limit: " << limit);
2950 SAL_INFO("drawinglayer.emf", "EMF+ passed to canvas mtf renderer, size: " << pA->GetDataSize());
2952 if (count < limit)
2954 SvMemoryStream aMemoryStream(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(), StreamMode::READ);
2956 aEMFPlus->processEmfPlusData(
2957 aMemoryStream,
2958 rViewInformation);
2961 count++;
2965 break;
2967 default:
2969 OSL_FAIL("Unknown MetaFile Action (!)");
2970 break;
2977 namespace wmfemfhelper
2979 drawinglayer::primitive2d::Primitive2DContainer interpretMetafile(
2980 const GDIMetaFile& rMetaFile,
2981 const drawinglayer::geometry::ViewInformation2D& rViewInformation)
2983 // prepare target and properties; each will have one default entry
2984 drawinglayer::primitive2d::Primitive2DContainer xRetval;
2985 TargetHolders aTargetHolders;
2986 PropertyHolders aPropertyHolders;
2988 // set target MapUnit at Properties
2989 aPropertyHolders.Current().setMapUnit(rMetaFile.GetPrefMapMode().GetMapUnit());
2991 // interpret the Metafile
2992 implInterpretMetafile(rMetaFile, aTargetHolders, aPropertyHolders, rViewInformation);
2994 // get the content. There should be only one target, as in the start condition,
2995 // but iterating will be the right thing to do when some push/pop is not closed
2996 while (aTargetHolders.size() > 1)
2998 xRetval.append(
2999 aTargetHolders.Current().getPrimitive2DSequence(aPropertyHolders.Current()));
3000 aTargetHolders.Pop();
3003 xRetval.append(
3004 aTargetHolders.Current().getPrimitive2DSequence(aPropertyHolders.Current()));
3006 return xRetval;
3009 } // end of namespace wmfemfhelper
3011 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */