diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /drawinglayer/source/tools/wmfemfhelper.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | drawinglayer/source/tools/wmfemfhelper.cxx | 3036 |
1 files changed, 3036 insertions, 0 deletions
diff --git a/drawinglayer/source/tools/wmfemfhelper.cxx b/drawinglayer/source/tools/wmfemfhelper.cxx new file mode 100644 index 000000000..8a5dbab74 --- /dev/null +++ b/drawinglayer/source/tools/wmfemfhelper.cxx @@ -0,0 +1,3036 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <wmfemfhelper.hxx> +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/metaact.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/discretebitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <vcl/BitmapPalette.hxx> +#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <drawinglayer/primitive2d/invertprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <primitive2d/wallpaperprimitive2d.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> +#include <primitive2d/textlineprimitive2d.hxx> +#include <primitive2d/textstrikeoutprimitive2d.hxx> +#include <drawinglayer/primitive2d/epsprimitive2d.hxx> +#include <sal/log.hxx> +#include <tools/fract.hxx> +#include <tools/stream.hxx> +#include <tools/UnitConversion.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/gradient.hxx> +#include <vcl/hatch.hxx> +#include <vcl/outdev.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <emfplushelper.hxx> +#include <numeric> +#include <toolkit/helper/vclunohelper.hxx> + +namespace drawinglayer::primitive2d +{ + namespace { + + /** NonOverlappingFillGradientPrimitive2D class + + This is a special version of the FillGradientPrimitive2D which decomposes + to a non-overlapping geometry version of the gradient. This needs to be + used to support the old XOR paint-'trick'. + + It does not need an own identifier since a renderer who wants to interpret + it itself may do so. It just overrides the decomposition of the C++ + implementation class to do an alternative decomposition. + */ + class NonOverlappingFillGradientPrimitive2D : public FillGradientPrimitive2D + { + protected: + /// local decomposition. + virtual void create2DDecomposition(Primitive2DContainer& rContainer, + const geometry::ViewInformation2D& rViewInformation) const override; + + public: + /// constructor + NonOverlappingFillGradientPrimitive2D( + const basegfx::B2DRange& rObjectRange, + const attribute::FillGradientAttribute& rFillGradient) + : FillGradientPrimitive2D(rObjectRange, rFillGradient) + { + } + }; + + } + + void NonOverlappingFillGradientPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, + const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if (!getFillGradient().isDefault()) + { + createFill(rContainer, false); + } + } + +} // end of namespace + +namespace wmfemfhelper +{ + /** helper class for graphic context + + This class allows to hold a complete representation of classic + VCL OutputDevice state. This data is needed for correct + interpretation of the MetaFile action flow. + */ + PropertyHolder::PropertyHolder() + : maMapUnit(MapUnit::Map100thMM), + maTextColor(sal_uInt32(COL_BLACK)), + maRasterOp(RasterOp::OverPaint), + mnLayoutMode(vcl::text::ComplexTextLayoutFlags::Default), + maLanguageType(0), + mnPushFlags(vcl::PushFlags::NONE), + mbLineColor(false), + mbFillColor(false), + mbTextColor(true), + mbTextFillColor(false), + mbTextLineColor(false), + mbOverlineColor(false), + mbClipPolyPolygonActive(false) + { + } +} + +namespace wmfemfhelper +{ + /** stack for properties + + This class builds a stack based on the PropertyHolder + class. It encapsulates the pointer/new/delete usage to + make it safe and implements the push/pop as needed by a + VCL Metafile interpreter. The critical part here are the + flag values VCL OutputDevice uses here; not all stuff is + pushed and thus needs to be copied at pop. + */ + PropertyHolders::PropertyHolders() + { + maPropertyHolders.push_back(new PropertyHolder()); + } + + void PropertyHolders::PushDefault() + { + PropertyHolder* pNew = new PropertyHolder(); + maPropertyHolders.push_back(pNew); + } + + void PropertyHolders::Push(vcl::PushFlags nPushFlags) + { + if (bool(nPushFlags)) + { + OSL_ENSURE(maPropertyHolders.size(), "PropertyHolders: PUSH with no property holders (!)"); + if (!maPropertyHolders.empty()) + { + PropertyHolder* pNew = new PropertyHolder(*maPropertyHolders.back()); + pNew->setPushFlags(nPushFlags); + maPropertyHolders.push_back(pNew); + } + } + } + + void PropertyHolders::Pop() + { + OSL_ENSURE(maPropertyHolders.size(), "PropertyHolders: POP with no property holders (!)"); + const sal_uInt32 nSize(maPropertyHolders.size()); + + if (!nSize) + return; + + const PropertyHolder* pTip = maPropertyHolders.back(); + const vcl::PushFlags nPushFlags(pTip->getPushFlags()); + + if (nPushFlags != vcl::PushFlags::NONE) + { + if (nSize > 1) + { + // copy back content for all non-set flags + PropertyHolder* pLast = maPropertyHolders[nSize - 2]; + + if (vcl::PushFlags::ALL != nPushFlags) + { + if (!(nPushFlags & vcl::PushFlags::LINECOLOR)) + { + pLast->setLineColor(pTip->getLineColor()); + pLast->setLineColorActive(pTip->getLineColorActive()); + } + if (!(nPushFlags & vcl::PushFlags::FILLCOLOR)) + { + pLast->setFillColor(pTip->getFillColor()); + pLast->setFillColorActive(pTip->getFillColorActive()); + } + if (!(nPushFlags & vcl::PushFlags::FONT)) + { + pLast->setFont(pTip->getFont()); + } + if (!(nPushFlags & vcl::PushFlags::TEXTCOLOR)) + { + pLast->setTextColor(pTip->getTextColor()); + pLast->setTextColorActive(pTip->getTextColorActive()); + } + if (!(nPushFlags & vcl::PushFlags::MAPMODE)) + { + pLast->setTransformation(pTip->getTransformation()); + pLast->setMapUnit(pTip->getMapUnit()); + } + if (!(nPushFlags & vcl::PushFlags::CLIPREGION)) + { + pLast->setClipPolyPolygon(pTip->getClipPolyPolygon()); + pLast->setClipPolyPolygonActive(pTip->getClipPolyPolygonActive()); + } + if (!(nPushFlags & vcl::PushFlags::RASTEROP)) + { + pLast->setRasterOp(pTip->getRasterOp()); + } + if (!(nPushFlags & vcl::PushFlags::TEXTFILLCOLOR)) + { + pLast->setTextFillColor(pTip->getTextFillColor()); + pLast->setTextFillColorActive(pTip->getTextFillColorActive()); + } + if (!(nPushFlags & vcl::PushFlags::TEXTALIGN)) + { + if (pLast->getFont().GetAlignment() != pTip->getFont().GetAlignment()) + { + vcl::Font aFont(pLast->getFont()); + aFont.SetAlignment(pTip->getFont().GetAlignment()); + pLast->setFont(aFont); + } + } + if (!(nPushFlags & vcl::PushFlags::REFPOINT)) + { + // not supported + } + if (!(nPushFlags & vcl::PushFlags::TEXTLINECOLOR)) + { + pLast->setTextLineColor(pTip->getTextLineColor()); + pLast->setTextLineColorActive(pTip->getTextLineColorActive()); + } + if (!(nPushFlags & vcl::PushFlags::TEXTLAYOUTMODE)) + { + pLast->setLayoutMode(pTip->getLayoutMode()); + } + if (!(nPushFlags & vcl::PushFlags::TEXTLANGUAGE)) + { + pLast->setLanguageType(pTip->getLanguageType()); + } + if (!(nPushFlags & vcl::PushFlags::OVERLINECOLOR)) + { + pLast->setOverlineColor(pTip->getOverlineColor()); + pLast->setOverlineColorActive(pTip->getOverlineColorActive()); + } + } + } + } + + // execute the pop + delete maPropertyHolders.back(); + maPropertyHolders.pop_back(); + } + + PropertyHolder& PropertyHolders::Current() + { + static PropertyHolder aDummy; + OSL_ENSURE(maPropertyHolders.size(), "PropertyHolders: CURRENT with no property holders (!)"); + return maPropertyHolders.empty() ? aDummy : *maPropertyHolders.back(); + } + + PropertyHolders::~PropertyHolders() + { + while (!maPropertyHolders.empty()) + { + delete maPropertyHolders.back(); + maPropertyHolders.pop_back(); + } + } +} + +namespace +{ + /** helper to convert a vcl::Region to a B2DPolyPolygon + when it does not yet contain one. In the future + this may be expanded to merge the polygons created + from rectangles or use a special algo to directly turn + the spans of regions to a single, already merged + PolyPolygon. + */ + basegfx::B2DPolyPolygon getB2DPolyPolygonFromRegion(const vcl::Region& rRegion) + { + basegfx::B2DPolyPolygon aRetval; + + if (!rRegion.IsEmpty()) + { + aRetval = rRegion.GetAsB2DPolyPolygon(); + } + + return aRetval; + } +} + +namespace wmfemfhelper +{ + /** Helper class to buffer and hold a Primitive target vector. It + encapsulates the new/delete functionality and allows to work + on pointers of the implementation classes. All data will + be converted to uno sequences of uno references when accessing the + data. + */ + TargetHolder::TargetHolder() + { + } + + TargetHolder::~TargetHolder() + { + } + + sal_uInt32 TargetHolder::size() const + { + return aTargets.size(); + } + + void TargetHolder::append(drawinglayer::primitive2d::BasePrimitive2D* pCandidate) + { + if (pCandidate) + { + aTargets.push_back(pCandidate); + } + } + + drawinglayer::primitive2d::Primitive2DContainer TargetHolder::getPrimitive2DSequence(const PropertyHolder& rPropertyHolder) + { + drawinglayer::primitive2d::Primitive2DContainer xRetval = std::move(aTargets); + + + if (!xRetval.empty() && rPropertyHolder.getClipPolyPolygonActive()) + { + const basegfx::B2DPolyPolygon& rClipPolyPolygon = rPropertyHolder.getClipPolyPolygon(); + + if (rClipPolyPolygon.count()) + { + drawinglayer::primitive2d::Primitive2DReference xMask( + new drawinglayer::primitive2d::MaskPrimitive2D( + rClipPolyPolygon, + std::move(xRetval))); + + xRetval = drawinglayer::primitive2d::Primitive2DContainer{ xMask }; + } + } + + return xRetval; + } +} + +namespace wmfemfhelper +{ + /** Helper class which builds a stack on the TargetHolder class */ + TargetHolders::TargetHolders() + { + maTargetHolders.push_back(new TargetHolder()); + } + + sal_uInt32 TargetHolders::size() const + { + return maTargetHolders.size(); + } + + void TargetHolders::Push() + { + maTargetHolders.push_back(new TargetHolder()); + } + + void TargetHolders::Pop() + { + OSL_ENSURE(maTargetHolders.size(), "TargetHolders: POP with no property holders (!)"); + if (!maTargetHolders.empty()) + { + delete maTargetHolders.back(); + maTargetHolders.pop_back(); + } + } + + TargetHolder& TargetHolders::Current() + { + static TargetHolder aDummy; + OSL_ENSURE(maTargetHolders.size(), "TargetHolders: CURRENT with no property holders (!)"); + return maTargetHolders.empty() ? aDummy : *maTargetHolders.back(); + } + + TargetHolders::~TargetHolders() + { + while (!maTargetHolders.empty()) + { + delete maTargetHolders.back(); + maTargetHolders.pop_back(); + } + } +} + +namespace +{ + /** helper to convert a MapMode to a transformation */ + basegfx::B2DHomMatrix getTransformFromMapMode(const MapMode& rMapMode) + { + basegfx::B2DHomMatrix aMapping; + const Fraction aNoScale(1, 1); + const Point& rOrigin(rMapMode.GetOrigin()); + + if(0 != rOrigin.X() || 0 != rOrigin.Y()) + { + aMapping.translate(rOrigin.X(), rOrigin.Y()); + } + + if(rMapMode.GetScaleX() != aNoScale || rMapMode.GetScaleY() != aNoScale) + { + aMapping.scale( + double(rMapMode.GetScaleX()), + double(rMapMode.GetScaleY())); + } + + return aMapping; + } +} + +namespace wmfemfhelper +{ + /** helper to create a PointArrayPrimitive2D based on current context */ + static void createPointArrayPrimitive( + std::vector< basegfx::B2DPoint >&& rPositions, + TargetHolder& rTarget, + PropertyHolder const & rProperties, + const basegfx::BColor& rBColor) + { + if(rPositions.empty()) + return; + + if(rProperties.getTransformation().isIdentity()) + { + rTarget.append( + new drawinglayer::primitive2d::PointArrayPrimitive2D( + std::move(rPositions), + rBColor)); + } + else + { + for(basegfx::B2DPoint & aPosition : rPositions) + { + aPosition = rProperties.getTransformation() * aPosition; + } + + rTarget.append( + new drawinglayer::primitive2d::PointArrayPrimitive2D( + std::move(rPositions), + rBColor)); + } + } + + /** helper to create a PolygonHairlinePrimitive2D based on current context */ + static void createHairlinePrimitive( + const basegfx::B2DPolygon& rLinePolygon, + TargetHolder& rTarget, + PropertyHolder const & rProperties) + { + if(rLinePolygon.count()) + { + basegfx::B2DPolygon aLinePolygon(rLinePolygon); + aLinePolygon.transform(rProperties.getTransformation()); + rTarget.append( + new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( + aLinePolygon, + rProperties.getLineColor())); + } + } + + /** helper to create a PolyPolygonColorPrimitive2D based on current context */ + static void createFillPrimitive( + const basegfx::B2DPolyPolygon& rFillPolyPolygon, + TargetHolder& rTarget, + PropertyHolder const & rProperties) + { + if(rFillPolyPolygon.count()) + { + basegfx::B2DPolyPolygon aFillPolyPolygon(rFillPolyPolygon); + aFillPolyPolygon.transform(rProperties.getTransformation()); + rTarget.append( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + aFillPolyPolygon, + rProperties.getFillColor())); + } + } + + /** helper to create a PolygonStrokePrimitive2D based on current context */ + static void createLinePrimitive( + const basegfx::B2DPolygon& rLinePolygon, + const LineInfo& rLineInfo, + TargetHolder& rTarget, + PropertyHolder const & rProperties) + { + if(!rLinePolygon.count()) + return; + + const bool bDashDotUsed(LineStyle::Dash == rLineInfo.GetStyle()); + const bool bWidthUsed(rLineInfo.GetWidth() > 1); + + if(bDashDotUsed || bWidthUsed) + { + basegfx::B2DPolygon aLinePolygon(rLinePolygon); + aLinePolygon.transform(rProperties.getTransformation()); + const drawinglayer::attribute::LineAttribute aLineAttribute( + rProperties.getLineColor(), + bWidthUsed ? rLineInfo.GetWidth() : 0.0, + rLineInfo.GetLineJoin(), + rLineInfo.GetLineCap()); + + if(bDashDotUsed) + { + std::vector< double > fDotDashArray = rLineInfo.GetDotDashArray(); + const double fAccumulated(std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0)); + const drawinglayer::attribute::StrokeAttribute aStrokeAttribute( + std::move(fDotDashArray), + fAccumulated); + + rTarget.append( + new drawinglayer::primitive2d::PolygonStrokePrimitive2D( + aLinePolygon, + aLineAttribute, + aStrokeAttribute)); + } + else + { + rTarget.append( + new drawinglayer::primitive2d::PolygonStrokePrimitive2D( + aLinePolygon, + aLineAttribute)); + } + } + else + { + createHairlinePrimitive(rLinePolygon, rTarget, rProperties); + } + } + + /** helper to create needed line and fill primitives based on current context */ + static void createHairlineAndFillPrimitive( + const basegfx::B2DPolygon& rPolygon, + TargetHolder& rTarget, + PropertyHolder const & rProperties) + { + if(rProperties.getFillColorActive()) + { + createFillPrimitive(basegfx::B2DPolyPolygon(rPolygon), rTarget, rProperties); + } + + if(rProperties.getLineColorActive()) + { + createHairlinePrimitive(rPolygon, rTarget, rProperties); + } + } + + /** helper to create needed line and fill primitives based on current context */ + static void createHairlineAndFillPrimitive( + const basegfx::B2DPolyPolygon& rPolyPolygon, + TargetHolder& rTarget, + PropertyHolder const & rProperties) + { + if(rProperties.getFillColorActive()) + { + createFillPrimitive(rPolyPolygon, rTarget, rProperties); + } + + if(rProperties.getLineColorActive()) + { + for(sal_uInt32 a(0); a < rPolyPolygon.count(); a++) + { + createHairlinePrimitive(rPolyPolygon.getB2DPolygon(a), rTarget, rProperties); + } + } + } + + /** helper to create DiscreteBitmapPrimitive2D based on current context. + The DiscreteBitmapPrimitive2D is especially created for this usage + since no other usage defines a bitmap visualisation based on top-left + position and size in pixels. At the end it will create a view-dependent + transformed embedding of a BitmapPrimitive2D. + */ + static void createBitmapExPrimitive( + const BitmapEx& rBitmapEx, + const Point& rPoint, + TargetHolder& rTarget, + PropertyHolder const & rProperties) + { + if(!rBitmapEx.IsEmpty()) + { + basegfx::B2DPoint aPoint(rPoint.X(), rPoint.Y()); + aPoint = rProperties.getTransformation() * aPoint; + + rTarget.append( + new drawinglayer::primitive2d::DiscreteBitmapPrimitive2D( + rBitmapEx, + aPoint)); + } + } + + /** helper to create BitmapPrimitive2D based on current context */ + static void createBitmapExPrimitive( + const BitmapEx& rBitmapEx, + const Point& rPoint, + const Size& rSize, + TargetHolder& rTarget, + PropertyHolder const & rProperties) + { + if(rBitmapEx.IsEmpty()) + return; + + basegfx::B2DHomMatrix aObjectTransform; + + aObjectTransform.set(0, 0, rSize.Width()); + aObjectTransform.set(1, 1, rSize.Height()); + aObjectTransform.set(0, 2, rPoint.X()); + aObjectTransform.set(1, 2, rPoint.Y()); + + aObjectTransform = rProperties.getTransformation() * aObjectTransform; + + rTarget.append( + new drawinglayer::primitive2d::BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(rBitmapEx), + aObjectTransform)); + } + + /** helper to create a regular BotmapEx from a MaskAction (definitions + which use a bitmap without transparence but define one of the colors as + transparent) + */ + static BitmapEx createMaskBmpEx(const Bitmap& rBitmap, const Color& rMaskColor) + { + const Color aWhite(COL_WHITE); + BitmapPalette aBiLevelPalette { + aWhite, rMaskColor + }; + + Bitmap aMask(rBitmap.CreateMask(aWhite)); + Bitmap aSolid(rBitmap.GetSizePixel(), vcl::PixelFormat::N1_BPP, &aBiLevelPalette); + + aSolid.Erase(rMaskColor); + + return BitmapEx(aSolid, aMask); + } + + /** helper to convert from a VCL Gradient definition to the corresponding + data for primitive representation + */ + static drawinglayer::attribute::FillGradientAttribute createFillGradientAttribute(const Gradient& rGradient) + { + const Color aStartColor(rGradient.GetStartColor()); + const sal_uInt16 nStartIntens(rGradient.GetStartIntensity()); + basegfx::BColor aStart(aStartColor.getBColor()); + + if(nStartIntens != 100) + { + const basegfx::BColor aBlack; + aStart = interpolate(aBlack, aStart, static_cast<double>(nStartIntens) * 0.01); + } + + const Color aEndColor(rGradient.GetEndColor()); + const sal_uInt16 nEndIntens(rGradient.GetEndIntensity()); + basegfx::BColor aEnd(aEndColor.getBColor()); + + if(nEndIntens != 100) + { + const basegfx::BColor aBlack; + aEnd = interpolate(aBlack, aEnd, static_cast<double>(nEndIntens) * 0.01); + } + + drawinglayer::attribute::GradientStyle aGradientStyle(drawinglayer::attribute::GradientStyle::Rect); + + switch(rGradient.GetStyle()) + { + case GradientStyle::Linear : + { + aGradientStyle = drawinglayer::attribute::GradientStyle::Linear; + break; + } + case GradientStyle::Axial : + { + aGradientStyle = drawinglayer::attribute::GradientStyle::Axial; + break; + } + case GradientStyle::Radial : + { + aGradientStyle = drawinglayer::attribute::GradientStyle::Radial; + break; + } + case GradientStyle::Elliptical : + { + aGradientStyle = drawinglayer::attribute::GradientStyle::Elliptical; + break; + } + case GradientStyle::Square : + { + aGradientStyle = drawinglayer::attribute::GradientStyle::Square; + break; + } + default : // GradientStyle::Rect + { + aGradientStyle = drawinglayer::attribute::GradientStyle::Rect; + break; + } + } + + return drawinglayer::attribute::FillGradientAttribute( + aGradientStyle, + static_cast<double>(rGradient.GetBorder()) * 0.01, + static_cast<double>(rGradient.GetOfsX()) * 0.01, + static_cast<double>(rGradient.GetOfsY()) * 0.01, + toRadians(rGradient.GetAngle()), + aStart, + aEnd, + rGradient.GetSteps()); + } + + /** helper to convert from a VCL Hatch definition to the corresponding + data for primitive representation + */ + static drawinglayer::attribute::FillHatchAttribute createFillHatchAttribute(const Hatch& rHatch) + { + drawinglayer::attribute::HatchStyle aHatchStyle(drawinglayer::attribute::HatchStyle::Single); + + switch(rHatch.GetStyle()) + { + default : // case HatchStyle::Single : + { + aHatchStyle = drawinglayer::attribute::HatchStyle::Single; + break; + } + case HatchStyle::Double : + { + aHatchStyle = drawinglayer::attribute::HatchStyle::Double; + break; + } + case HatchStyle::Triple : + { + aHatchStyle = drawinglayer::attribute::HatchStyle::Triple; + break; + } + } + + return drawinglayer::attribute::FillHatchAttribute( + aHatchStyle, + static_cast<double>(rHatch.GetDistance()), + toRadians(rHatch.GetAngle()), + rHatch.GetColor().getBColor(), + 3, // same default as VCL, a minimum of three discrete units (pixels) offset + false); + } + + /** helper to take needed action on ClipRegion change. This method needs to be called + on any vcl::Region change, e.g. at the obvious actions doing this, but also at pop-calls + which change the vcl::Region of the current context. It takes care of creating the + current embedded context, set the new vcl::Region at the context and possibly prepare + a new target for including new geometry into the current region + */ + void HandleNewClipRegion( + const basegfx::B2DPolyPolygon& rClipPolyPolygon, + TargetHolders& rTargetHolders, + PropertyHolders& rPropertyHolders) + { + const bool bNewActive(rClipPolyPolygon.count()); + + // #i108636# The handling of new ClipPolyPolygons was not done as good as possible + // in the first version of this interpreter; e.g. when a ClipPolyPolygon was set + // initially and then using a lot of push/pop actions, the pop always leads + // to setting a 'new' ClipPolyPolygon which indeed is the return to the ClipPolyPolygon + // of the properties next on the stack. + + // This ClipPolyPolygon is identical to the current one, so there is no need to + // create a MaskPrimitive2D containing the up-to-now created primitives, but + // this was done before. While this does not lead to wrong primitive + // representations of the metafile data, it creates unnecessarily expensive + // representations. Just detecting when no really 'new' ClipPolyPolygon gets set + // solves the problem. + + if(!rPropertyHolders.Current().getClipPolyPolygonActive() && !bNewActive) + { + // no active ClipPolyPolygon exchanged by no new one, done + return; + } + + if(rPropertyHolders.Current().getClipPolyPolygonActive() && bNewActive) + { + // active ClipPolyPolygon and new active ClipPolyPolygon + if(rPropertyHolders.Current().getClipPolyPolygon() == rClipPolyPolygon) + { + // new is the same as old, done + return; + } + } + + // Here the old and the new are definitively different, maybe + // old one and/or new one is not active. + + // Handle deletion of old ClipPolyPolygon. The process evtl. created primitives which + // belong to this active ClipPolyPolygon. These need to be embedded to a + // MaskPrimitive2D accordingly. + if(rPropertyHolders.Current().getClipPolyPolygonActive() && rTargetHolders.size() > 1) + { + drawinglayer::primitive2d::Primitive2DContainer aSubContent; + + if(rPropertyHolders.Current().getClipPolyPolygon().count() + && rTargetHolders.Current().size()) + { + aSubContent = rTargetHolders.Current().getPrimitive2DSequence( + rPropertyHolders.Current()); + } + + rTargetHolders.Pop(); + + if(!aSubContent.empty()) + { + rTargetHolders.Current().append( + new drawinglayer::primitive2d::GroupPrimitive2D( + std::move(aSubContent))); + } + } + + // apply new settings to current properties by setting + // the new region now + rPropertyHolders.Current().setClipPolyPolygonActive(bNewActive); + + if(bNewActive) + { + rPropertyHolders.Current().setClipPolyPolygon(rClipPolyPolygon); + + // prepare new content holder for new active region + rTargetHolders.Push(); + } + } + + /** helper to handle the change of RasterOp. It takes care of encapsulating all current + geometry to the current RasterOp (if changed) and needs to be called on any RasterOp + change. It will also start a new geometry target to embrace to the new RasterOp if + a changing RasterOp is used. Currently, RasterOp::Xor and RasterOp::Invert are supported using + InvertPrimitive2D, and RasterOp::N0 by using a ModifiedColorPrimitive2D to force to black paint + */ + static void HandleNewRasterOp( + RasterOp aRasterOp, + TargetHolders& rTargetHolders, + PropertyHolders& rPropertyHolders) + { + // check if currently active + if(rPropertyHolders.Current().isRasterOpActive() && rTargetHolders.size() > 1) + { + drawinglayer::primitive2d::Primitive2DContainer aSubContent; + + if(rTargetHolders.Current().size()) + { + aSubContent = rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current()); + } + + rTargetHolders.Pop(); + + if(!aSubContent.empty()) + { + if(rPropertyHolders.Current().isRasterOpForceBlack()) + { + // force content to black + rTargetHolders.Current().append( + new drawinglayer::primitive2d::ModifiedColorPrimitive2D( + std::move(aSubContent), + std::make_shared<basegfx::BColorModifier_replace>( + basegfx::BColor(0.0, 0.0, 0.0)))); + } + else // if(rPropertyHolders.Current().isRasterOpInvert()) + { + // invert content + rTargetHolders.Current().append( + new drawinglayer::primitive2d::InvertPrimitive2D( + std::move(aSubContent))); + } + } + } + + // apply new settings + rPropertyHolders.Current().setRasterOp(aRasterOp); + + // check if now active + if(rPropertyHolders.Current().isRasterOpActive()) + { + // prepare new content holder for new invert + rTargetHolders.Push(); + } + } + + /** helper to create needed data to emulate the VCL Wallpaper Metafile action. + It is a quite mighty action. This helper is for simple color filled background. + */ + static rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> CreateColorWallpaper( + const basegfx::B2DRange& rRange, + const basegfx::BColor& rColor, + PropertyHolder const & rPropertyHolder) + { + basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(rRange)); + aOutline.transform(rPropertyHolder.getTransformation()); + + return new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aOutline), + rColor); + } + + /** helper to create needed data to emulate the VCL Wallpaper Metafile action. + It is a quite mighty action. This helper is for gradient filled background. + */ + static rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> CreateGradientWallpaper( + const basegfx::B2DRange& rRange, + const Gradient& rGradient, + PropertyHolder const & rPropertyHolder) + { + const drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); + + if(aAttribute.getStartColor() == aAttribute.getEndColor()) + { + // not really a gradient. Create filled rectangle + return CreateColorWallpaper(rRange, aAttribute.getStartColor(), rPropertyHolder); + } + else + { + // really a gradient + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pRetval( + new drawinglayer::primitive2d::FillGradientPrimitive2D( + rRange, + aAttribute)); + + if(!rPropertyHolder.getTransformation().isIdentity()) + { + const drawinglayer::primitive2d::Primitive2DReference xPrim(pRetval); + drawinglayer::primitive2d::Primitive2DContainer xSeq { xPrim }; + + pRetval = new drawinglayer::primitive2d::TransformPrimitive2D( + rPropertyHolder.getTransformation(), + std::move(xSeq)); + } + + return pRetval; + } + } + + /** helper to create needed data to emulate the VCL Wallpaper Metafile action. + It is a quite mighty action. This helper decides if color and/or gradient + background is needed for the wanted bitmap fill and then creates the needed + WallpaperBitmapPrimitive2D. This primitive was created for this purpose and + takes over all needed logic of orientations and tiling. + */ + static void CreateAndAppendBitmapWallpaper( + basegfx::B2DRange aWallpaperRange, + const Wallpaper& rWallpaper, + TargetHolder& rTarget, + PropertyHolder const & rProperty) + { + const BitmapEx aBitmapEx(rWallpaper.GetBitmap()); + const WallpaperStyle eWallpaperStyle(rWallpaper.GetStyle()); + + // if bitmap visualisation is transparent, maybe background + // needs to be filled. Create background + if(aBitmapEx.IsAlpha() + || (WallpaperStyle::Tile != eWallpaperStyle && WallpaperStyle::Scale != eWallpaperStyle)) + { + if(rWallpaper.IsGradient()) + { + rTarget.append( + CreateGradientWallpaper( + aWallpaperRange, + rWallpaper.GetGradient(), + rProperty)); + } + else if(!rWallpaper.GetColor().IsTransparent()) + { + rTarget.append( + CreateColorWallpaper( + aWallpaperRange, + rWallpaper.GetColor().getBColor(), + rProperty)); + } + } + + // use wallpaper rect if set + if(rWallpaper.IsRect() && !rWallpaper.GetRect().IsEmpty()) + { + aWallpaperRange = vcl::unotools::b2DRectangleFromRectangle(rWallpaper.GetRect()); + } + + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pBitmapWallpaperFill = + new drawinglayer::primitive2d::WallpaperBitmapPrimitive2D( + aWallpaperRange, + aBitmapEx, + eWallpaperStyle); + + if(rProperty.getTransformation().isIdentity()) + { + // add directly + rTarget.append(pBitmapWallpaperFill); + } + else + { + // when a transformation is set, embed to it + const drawinglayer::primitive2d::Primitive2DReference xPrim(pBitmapWallpaperFill); + + rTarget.append( + new drawinglayer::primitive2d::TransformPrimitive2D( + rProperty.getTransformation(), + drawinglayer::primitive2d::Primitive2DContainer { xPrim })); + } + } + + /** helper to decide UnderlineAbove for text primitives */ + static bool isUnderlineAbove(const vcl::Font& rFont) + { + if(!rFont.IsVertical()) + { + return false; + } + + // the underline is right for Japanese only + return (LANGUAGE_JAPANESE == rFont.GetLanguage()) || (LANGUAGE_JAPANESE == rFont.GetCJKContextLanguage()); + } + + static void createFontAttributeTransformAndAlignment( + drawinglayer::attribute::FontAttribute& rFontAttribute, + basegfx::B2DHomMatrix& rTextTransform, + basegfx::B2DVector& rAlignmentOffset, + PropertyHolder const & rProperty) + { + const vcl::Font& rFont = rProperty.getFont(); + basegfx::B2DVector aFontScaling; + + rFontAttribute = drawinglayer::primitive2d::getFontAttributeFromVclFont( + aFontScaling, + rFont, + bool(rProperty.getLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiRtl), + bool(rProperty.getLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiStrong)); + + // add FontScaling + rTextTransform.scale(aFontScaling.getX(), aFontScaling.getY()); + + // take text align into account + if(ALIGN_BASELINE != rFont.GetAlignment()) + { + drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice; + aTextLayouterDevice.setFont(rFont); + + if(ALIGN_TOP == rFont.GetAlignment()) + { + rAlignmentOffset.setY(aTextLayouterDevice.getFontAscent()); + } + else // ALIGN_BOTTOM + { + rAlignmentOffset.setY(-aTextLayouterDevice.getFontDescent()); + } + + rTextTransform.translate(rAlignmentOffset.getX(), rAlignmentOffset.getY()); + } + + // add FontRotation (if used) + if(rFont.GetOrientation()) + { + rTextTransform.rotate(-toRadians(rFont.GetOrientation())); + } + } + + /** helper which takes complete care for creating the needed text primitives. It + takes care of decorated stuff and all the geometry adaptations needed + */ + static void processMetaTextAction( + const Point& rTextStartPosition, + const OUString& rText, + sal_uInt16 nTextStart, + sal_uInt16 nTextLength, + std::vector< double >&& rDXArray, + TargetHolder& rTarget, + PropertyHolder const & rProperty) + { + rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pResult; + const vcl::Font& rFont = rProperty.getFont(); + basegfx::B2DVector aAlignmentOffset(0.0, 0.0); + + if(nTextLength) + { + drawinglayer::attribute::FontAttribute aFontAttribute; + basegfx::B2DHomMatrix aTextTransform; + + // fill parameters derived from current font + createFontAttributeTransformAndAlignment( + aFontAttribute, + aTextTransform, + aAlignmentOffset, + rProperty); + + // add TextStartPosition + aTextTransform.translate(rTextStartPosition.X(), rTextStartPosition.Y()); + + // prepare FontColor and Locale + const basegfx::BColor aFontColor(rProperty.getTextColor()); + const Color aFillColor(rFont.GetFillColor()); + const css::lang::Locale aLocale(LanguageTag(rProperty.getLanguageType()).getLocale()); + const bool bWordLineMode(rFont.IsWordLineMode()); + + const bool bDecoratedIsNeeded( + LINESTYLE_NONE != rFont.GetOverline() + || LINESTYLE_NONE != rFont.GetUnderline() + || STRIKEOUT_NONE != rFont.GetStrikeout() + || FontEmphasisMark::NONE != (rFont.GetEmphasisMark() & FontEmphasisMark::Style) + || FontRelief::NONE != rFont.GetRelief() + || rFont.IsShadow() + || bWordLineMode); + + if(bDecoratedIsNeeded) + { + // prepare overline, underline and strikeout data + const drawinglayer::primitive2d::TextLine eFontOverline(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rFont.GetOverline())); + const drawinglayer::primitive2d::TextLine eFontLineStyle(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rFont.GetUnderline())); + const drawinglayer::primitive2d::TextStrikeout eTextStrikeout(drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rFont.GetStrikeout())); + + // check UndelineAbove + const bool bUnderlineAbove(drawinglayer::primitive2d::TEXT_LINE_NONE != eFontLineStyle && isUnderlineAbove(rFont)); + + // prepare emphasis mark data + drawinglayer::primitive2d::TextEmphasisMark eTextEmphasisMark(drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE); + + switch(rFont.GetEmphasisMark() & FontEmphasisMark::Style) + { + case FontEmphasisMark::Dot : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DOT; break; + case FontEmphasisMark::Circle : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_CIRCLE; break; + case FontEmphasisMark::Disc : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DISC; break; + case FontEmphasisMark::Accent : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_ACCENT; break; + default: break; + } + + const bool bEmphasisMarkAbove(rFont.GetEmphasisMark() & FontEmphasisMark::PosAbove); + const bool bEmphasisMarkBelow(rFont.GetEmphasisMark() & FontEmphasisMark::PosBelow); + + // prepare font relief data + drawinglayer::primitive2d::TextRelief eTextRelief(drawinglayer::primitive2d::TEXT_RELIEF_NONE); + + switch(rFont.GetRelief()) + { + case FontRelief::Embossed : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_EMBOSSED; break; + case FontRelief::Engraved : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_ENGRAVED; break; + default : break; // RELIEF_NONE, FontRelief_FORCE_EQUAL_SIZE + } + + // prepare shadow/outline data + const bool bShadow(rFont.IsShadow()); + + // TextDecoratedPortionPrimitive2D is needed, create one + pResult = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D( + + // attributes for TextSimplePortionPrimitive2D + aTextTransform, + rText, + nTextStart, + nTextLength, + std::move(rDXArray), + aFontAttribute, + aLocale, + aFontColor, + aFillColor, + + // attributes for TextDecoratedPortionPrimitive2D + rProperty.getOverlineColorActive() ? rProperty.getOverlineColor() : aFontColor, + rProperty.getTextLineColorActive() ? rProperty.getTextLineColor() : aFontColor, + eFontOverline, + eFontLineStyle, + bUnderlineAbove, + eTextStrikeout, + bWordLineMode, + eTextEmphasisMark, + bEmphasisMarkAbove, + bEmphasisMarkBelow, + eTextRelief, + bShadow); + } + else + { + // TextSimplePortionPrimitive2D is enough + pResult = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + aTextTransform, + rText, + nTextStart, + nTextLength, + std::vector(rDXArray), + aFontAttribute, + aLocale, + aFontColor); + } + } + + if(pResult && rProperty.getTextFillColorActive()) + { + // text background is requested, add and encapsulate both to new primitive + drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice; + aTextLayouterDevice.setFont(rFont); + + // get text width + double fTextWidth(0.0); + + if(rDXArray.empty()) + { + fTextWidth = aTextLayouterDevice.getTextWidth(rText, nTextStart, nTextLength); + } + else + { + fTextWidth = rDXArray.back(); + } + + if(basegfx::fTools::more(fTextWidth, 0.0)) + { + // build text range + const basegfx::B2DRange aTextRange( + 0.0, -aTextLayouterDevice.getFontAscent(), + fTextWidth, aTextLayouterDevice.getFontDescent()); + + // create Transform + basegfx::B2DHomMatrix aTextTransform; + + aTextTransform.translate(aAlignmentOffset.getX(), aAlignmentOffset.getY()); + + if(rFont.GetOrientation()) + { + aTextTransform.rotate(-toRadians(rFont.GetOrientation())); + } + + aTextTransform.translate(rTextStartPosition.X(), rTextStartPosition.Y()); + + // prepare Primitive2DSequence, put text in foreground + drawinglayer::primitive2d::Primitive2DContainer aSequence(2); + aSequence[1] = pResult; + + // prepare filled polygon + basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aTextRange)); + aOutline.transform(aTextTransform); + + aSequence[0] = drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aOutline), + rProperty.getTextFillColor())); + + // set as group at pResult + pResult = new drawinglayer::primitive2d::GroupPrimitive2D(std::move(aSequence)); + } + } + + if(!pResult) + return; + + // add created text primitive to target + if(rProperty.getTransformation().isIdentity()) + { + rTarget.append(pResult); + } + else + { + // when a transformation is set, embed to it + const drawinglayer::primitive2d::Primitive2DReference aReference(pResult); + + rTarget.append( + new drawinglayer::primitive2d::TransformPrimitive2D( + rProperty.getTransformation(), + drawinglayer::primitive2d::Primitive2DContainer { aReference })); + } + } + + /** helper which takes complete care for creating the needed textLine primitives */ + static void processMetaTextLineAction( + const MetaTextLineAction& rAction, + TargetHolder& rTarget, + PropertyHolder const & rProperty) + { + const double fLineWidth(fabs(static_cast<double>(rAction.GetWidth()))); + + if(fLineWidth <= 0.0) + return; + + const drawinglayer::primitive2d::TextLine aOverlineMode(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rAction.GetOverline())); + const drawinglayer::primitive2d::TextLine aUnderlineMode(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rAction.GetUnderline())); + const drawinglayer::primitive2d::TextStrikeout aTextStrikeout(drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rAction.GetStrikeout())); + + const bool bOverlineUsed(drawinglayer::primitive2d::TEXT_LINE_NONE != aOverlineMode); + const bool bUnderlineUsed(drawinglayer::primitive2d::TEXT_LINE_NONE != aUnderlineMode); + const bool bStrikeoutUsed(drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE != aTextStrikeout); + + if(!(bUnderlineUsed || bStrikeoutUsed || bOverlineUsed)) + return; + + drawinglayer::primitive2d::Primitive2DContainer aTargets; + basegfx::B2DVector aAlignmentOffset(0.0, 0.0); + drawinglayer::attribute::FontAttribute aFontAttribute; + basegfx::B2DHomMatrix aTextTransform; + + // fill parameters derived from current font + createFontAttributeTransformAndAlignment( + aFontAttribute, + aTextTransform, + aAlignmentOffset, + rProperty); + + // add TextStartPosition + aTextTransform.translate(rAction.GetStartPoint().X(), rAction.GetStartPoint().Y()); + + // prepare TextLayouter (used in most cases) + drawinglayer::primitive2d::TextLayouterDevice aTextLayouter; + aTextLayouter.setFont(rProperty.getFont()); + + if(bOverlineUsed) + { + // create primitive geometry for overline + aTargets.push_back( + new drawinglayer::primitive2d::TextLinePrimitive2D( + aTextTransform, + fLineWidth, + aTextLayouter.getOverlineOffset(), + aTextLayouter.getOverlineHeight(), + aOverlineMode, + rProperty.getOverlineColor())); + } + + if(bUnderlineUsed) + { + // create primitive geometry for underline + aTargets.push_back( + new drawinglayer::primitive2d::TextLinePrimitive2D( + aTextTransform, + fLineWidth, + aTextLayouter.getUnderlineOffset(), + aTextLayouter.getUnderlineHeight(), + aUnderlineMode, + rProperty.getTextLineColor())); + } + + if(bStrikeoutUsed) + { + // create primitive geometry for strikeout + if(drawinglayer::primitive2d::TEXT_STRIKEOUT_SLASH == aTextStrikeout + || drawinglayer::primitive2d::TEXT_STRIKEOUT_X == aTextStrikeout) + { + // strikeout with character + const sal_Unicode aStrikeoutChar( + drawinglayer::primitive2d::TEXT_STRIKEOUT_SLASH == aTextStrikeout ? '/' : 'X'); + const css::lang::Locale aLocale(LanguageTag( + rProperty.getLanguageType()).getLocale()); + + aTargets.push_back( + new drawinglayer::primitive2d::TextCharacterStrikeoutPrimitive2D( + aTextTransform, + fLineWidth, + rProperty.getTextColor(), + aStrikeoutChar, + aFontAttribute, + aLocale)); + } + else + { + // strikeout with geometry + aTargets.push_back( + new drawinglayer::primitive2d::TextGeometryStrikeoutPrimitive2D( + aTextTransform, + fLineWidth, + rProperty.getTextColor(), + aTextLayouter.getUnderlineHeight(), + aTextLayouter.getStrikeoutOffset(), + aTextStrikeout)); + } + } + + if(aTargets.empty()) + return; + + // add created text primitive to target + if(rProperty.getTransformation().isIdentity()) + { + rTarget.append(std::move(aTargets)); + } + else + { + // when a transformation is set, embed to it + rTarget.append( + new drawinglayer::primitive2d::TransformPrimitive2D( + rProperty.getTransformation(), + std::move(aTargets))); + } + } + + /** This is the main interpreter method. It is designed to handle the given Metafile + completely inside the given context and target. It may use and modify the context and + target. This design allows to call itself recursively which adapted contexts and + targets as e.g. needed for the MetaActionType::FLOATTRANSPARENT where the content is expressed + as a metafile as sub-content. + + This interpreter is as free of VCL functionality as possible. It uses VCL data classes + (else reading the data would not be possible), but e.g. does NOT use a local OutputDevice + as most other MetaFile interpreters/exporters do to hold and work with the current context. + This is necessary to be able to get away from the strong internal VCL-binding. + + It tries to combine e.g. pixel and/or point actions and to stitch together single line primitives + where possible (which is not trivial with the possible line geometry definitions). + + It tries to handle clipping no longer as Regions and spans of Rectangles, but as PolyPolygon + ClipRegions with (where possible) high precision by using the best possible data quality + from the Region. The vcl::Region is unavoidable as data container, but nowadays allows the transport + of Polygon-based clip regions. Where this is not used, a Polygon is constructed from the + vcl::Region ranges. All primitive clipping uses the MaskPrimitive2D with Polygon-based clipping. + + I have marked the single MetaActions with: + + SIMPLE, DONE: + Simple, e.g nothing to do or value setting in the context + + CHECKED, WORKS WELL: + Thoroughly tested with extra written test code which created a replacement + Metafile just to test this action in various combinations + + NEEDS IMPLEMENTATION: + Not implemented and asserted, but also no usage found, neither in own Metafile + creations, nor in EMF/WMF imports (checked with a whole bunch of critical EMF/WMF + bugdocs) + + For more comments, see the single action implementations. + */ + static void implInterpretMetafile( + const GDIMetaFile& rMetaFile, + TargetHolders& rTargetHolders, + PropertyHolders& rPropertyHolders, + const drawinglayer::geometry::ViewInformation2D& rViewInformation) + { + const size_t nCount(rMetaFile.GetActionSize()); + std::unique_ptr<emfplushelper::EmfPlusHelper> aEMFPlus; + + for(size_t nAction(0); nAction < nCount; nAction++) + { + MetaAction* pAction = rMetaFile.GetAction(nAction); + + switch(pAction->GetType()) + { + case MetaActionType::NONE : + { + /** SIMPLE, DONE */ + break; + } + case MetaActionType::PIXEL : + { + /** CHECKED, WORKS WELL */ + std::vector< basegfx::B2DPoint > aPositions; + Color aLastColor(COL_BLACK); + + while(MetaActionType::PIXEL == pAction->GetType() && nAction < nCount) + { + const MetaPixelAction* pA = static_cast<const MetaPixelAction*>(pAction); + + if(pA->GetColor() != aLastColor) + { + if(!aPositions.empty()) + { + createPointArrayPrimitive(std::move(aPositions), rTargetHolders.Current(), rPropertyHolders.Current(), aLastColor.getBColor()); + aPositions.clear(); + } + + aLastColor = pA->GetColor(); + } + + const Point& rPoint = pA->GetPoint(); + aPositions.emplace_back(rPoint.X(), rPoint.Y()); + nAction++; if(nAction < nCount) pAction = rMetaFile.GetAction(nAction); + } + + nAction--; + + if(!aPositions.empty()) + { + createPointArrayPrimitive(std::move(aPositions), rTargetHolders.Current(), rPropertyHolders.Current(), aLastColor.getBColor()); + } + + break; + } + case MetaActionType::POINT : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineColorActive()) + { + std::vector< basegfx::B2DPoint > aPositions; + + while(MetaActionType::POINT == pAction->GetType() && nAction < nCount) + { + const MetaPointAction* pA = static_cast<const MetaPointAction*>(pAction); + const Point& rPoint = pA->GetPoint(); + aPositions.emplace_back(rPoint.X(), rPoint.Y()); + nAction++; if(nAction < nCount) pAction = rMetaFile.GetAction(nAction); + } + + nAction--; + + if(!aPositions.empty()) + { + createPointArrayPrimitive(std::move(aPositions), rTargetHolders.Current(), rPropertyHolders.Current(), rPropertyHolders.Current().getLineColor()); + } + } + + break; + } + case MetaActionType::LINE : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineColorActive()) + { + basegfx::B2DPolygon aLinePolygon; + LineInfo aLineInfo; + + while(MetaActionType::LINE == pAction->GetType() && nAction < nCount) + { + const MetaLineAction* pA = static_cast<const MetaLineAction*>(pAction); + const Point& rStartPoint = pA->GetStartPoint(); + const Point& rEndPoint = pA->GetEndPoint(); + const basegfx::B2DPoint aStart(rStartPoint.X(), rStartPoint.Y()); + const basegfx::B2DPoint aEnd(rEndPoint.X(), rEndPoint.Y()); + + if(aLinePolygon.count()) + { + if(pA->GetLineInfo() == aLineInfo + && aStart == aLinePolygon.getB2DPoint(aLinePolygon.count() - 1)) + { + aLinePolygon.append(aEnd); + } + else + { + createLinePrimitive(aLinePolygon, aLineInfo, rTargetHolders.Current(), rPropertyHolders.Current()); + aLinePolygon.clear(); + aLineInfo = pA->GetLineInfo(); + aLinePolygon.append(aStart); + aLinePolygon.append(aEnd); + } + } + else + { + aLineInfo = pA->GetLineInfo(); + aLinePolygon.append(aStart); + aLinePolygon.append(aEnd); + } + + nAction++; + if (nAction < nCount) + pAction = rMetaFile.GetAction(nAction); + } + + nAction--; + if (aLinePolygon.count()) + createLinePrimitive(aLinePolygon, aLineInfo, rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::RECT : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineOrFillActive()) + { + const MetaRectAction* pA = static_cast<const MetaRectAction*>(pAction); + const tools::Rectangle& rRectangle = pA->GetRect(); + + if(!rRectangle.IsEmpty()) + { + const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle); + + if(!aRange.isEmpty()) + { + const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange)); + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + } + } + + break; + } + case MetaActionType::ROUNDRECT : + { + /** CHECKED, WORKS WELL */ + /** The original OutputDevice::DrawRect paints nothing when nHor or nVer is zero; but just + because the tools::Polygon operator creating the rounding does produce nonsense. I assume + this an error and create an unrounded rectangle in that case (implicit in + createPolygonFromRect) + */ + if(rPropertyHolders.Current().getLineOrFillActive()) + { + const MetaRoundRectAction* pA = static_cast<const MetaRoundRectAction*>(pAction); + const tools::Rectangle& rRectangle = pA->GetRect(); + + if(!rRectangle.IsEmpty()) + { + const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle); + + if(!aRange.isEmpty()) + { + const sal_uInt32 nHor(pA->GetHorzRound()); + const sal_uInt32 nVer(pA->GetVertRound()); + basegfx::B2DPolygon aOutline; + + if(nHor || nVer) + { + double fRadiusX((nHor * 2.0) / (aRange.getWidth() > 0.0 ? aRange.getWidth() : 1.0)); + double fRadiusY((nVer * 2.0) / (aRange.getHeight() > 0.0 ? aRange.getHeight() : 1.0)); + fRadiusX = std::clamp(fRadiusX, 0.0, 1.0); + fRadiusY = std::clamp(fRadiusY, 0.0, 1.0); + + aOutline = basegfx::utils::createPolygonFromRect(aRange, fRadiusX, fRadiusY); + } + else + { + aOutline = basegfx::utils::createPolygonFromRect(aRange); + } + + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + } + } + + break; + } + case MetaActionType::ELLIPSE : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineOrFillActive()) + { + const MetaEllipseAction* pA = static_cast<const MetaEllipseAction*>(pAction); + const tools::Rectangle& rRectangle = pA->GetRect(); + + if(!rRectangle.IsEmpty()) + { + const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle); + + if(!aRange.isEmpty()) + { + const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromEllipse( + aRange.getCenter(), aRange.getWidth() * 0.5, aRange.getHeight() * 0.5)); + + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + } + } + + break; + } + case MetaActionType::ARC : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineColorActive()) + { + const MetaArcAction* pA = static_cast<const MetaArcAction*>(pAction); + const tools::Polygon aToolsPoly(pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Arc); + const basegfx::B2DPolygon aOutline(aToolsPoly.getB2DPolygon()); + + createHairlinePrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::PIE : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineOrFillActive()) + { + const MetaPieAction* pA = static_cast<const MetaPieAction*>(pAction); + const tools::Polygon aToolsPoly(pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Pie); + const basegfx::B2DPolygon aOutline(aToolsPoly.getB2DPolygon()); + + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::CHORD : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineOrFillActive()) + { + const MetaChordAction* pA = static_cast<const MetaChordAction*>(pAction); + const tools::Polygon aToolsPoly(pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Chord); + const basegfx::B2DPolygon aOutline(aToolsPoly.getB2DPolygon()); + + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::POLYLINE : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineColorActive()) + { + const MetaPolyLineAction* pA = static_cast<const MetaPolyLineAction*>(pAction); + createLinePrimitive(pA->GetPolygon().getB2DPolygon(), pA->GetLineInfo(), rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::POLYGON : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineOrFillActive()) + { + const MetaPolygonAction* pA = static_cast<const MetaPolygonAction*>(pAction); + basegfx::B2DPolygon aOutline(pA->GetPolygon().getB2DPolygon()); + + // the metafile play interprets the polygons from MetaPolygonAction + // always as closed and always paints an edge from last to first point, + // so force to closed here to emulate that + if(aOutline.count() > 1 && !aOutline.isClosed()) + { + aOutline.setClosed(true); + } + + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::POLYPOLYGON : + { + /** CHECKED, WORKS WELL */ + if(rPropertyHolders.Current().getLineOrFillActive()) + { + const MetaPolyPolygonAction* pA = static_cast<const MetaPolyPolygonAction*>(pAction); + basegfx::B2DPolyPolygon aPolyPolygonOutline(pA->GetPolyPolygon().getB2DPolyPolygon()); + + // the metafile play interprets the single polygons from MetaPolyPolygonAction + // always as closed and always paints an edge from last to first point, + // so force to closed here to emulate that + for(sal_uInt32 b(0); b < aPolyPolygonOutline.count(); b++) + { + basegfx::B2DPolygon aPolygonOutline(aPolyPolygonOutline.getB2DPolygon(b)); + + if(aPolygonOutline.count() > 1 && !aPolygonOutline.isClosed()) + { + aPolygonOutline.setClosed(true); + aPolyPolygonOutline.setB2DPolygon(b, aPolygonOutline); + } + } + + createHairlineAndFillPrimitive(aPolyPolygonOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::TEXT : + { + /** CHECKED, WORKS WELL */ + const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction); + sal_uInt32 nTextLength(pA->GetLen()); + const sal_uInt32 nTextIndex(pA->GetIndex()); + const sal_uInt32 nStringLength(pA->GetText().getLength()); + + if(nTextLength + nTextIndex > nStringLength) + { + nTextLength = nStringLength - nTextIndex; + } + + if(nTextLength && rPropertyHolders.Current().getTextColorActive()) + { + std::vector< double > aDXArray{}; + processMetaTextAction( + pA->GetPoint(), + pA->GetText(), + nTextIndex, + nTextLength, + std::move(aDXArray), + rTargetHolders.Current(), + rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::TEXTARRAY : + { + /** CHECKED, WORKS WELL */ + const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction); + sal_uInt32 nTextLength(pA->GetLen()); + const sal_uInt32 nTextIndex(pA->GetIndex()); + const sal_uInt32 nStringLength(pA->GetText().getLength()); + + if(nTextLength + nTextIndex > nStringLength) + { + nTextLength = nTextIndex > nStringLength ? 0 : nStringLength - nTextIndex; + } + + if(nTextLength && rPropertyHolders.Current().getTextColorActive()) + { + // prepare DXArray (if used) + std::vector< double > aDXArray; + const std::vector<sal_Int32> & rDXArray = pA->GetDXArray(); + + if(!rDXArray.empty()) + { + aDXArray.reserve(nTextLength); + + for(sal_uInt32 a(0); a < nTextLength; a++) + { + aDXArray.push_back(static_cast<double>(rDXArray[a])); + } + } + + processMetaTextAction( + pA->GetPoint(), + pA->GetText(), + nTextIndex, + nTextLength, + std::move(aDXArray), + rTargetHolders.Current(), + rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::STRETCHTEXT : + { + // #i108440# StarMath uses MetaStretchTextAction, thus support is needed. + // It looks as if it pretty never really uses a width different from + // the default text-layout width, but it's not possible to be sure. + // Implemented getting the DXArray and checking for scale at all. If + // scale is more than 3.5% different, scale the DXArray before usage. + // New status: + + /** CHECKED, WORKS WELL */ + const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction); + sal_uInt32 nTextLength(pA->GetLen()); + const sal_uInt32 nTextIndex(pA->GetIndex()); + const sal_uInt32 nStringLength(pA->GetText().getLength()); + + if(nTextLength + nTextIndex > nStringLength) + { + nTextLength = nStringLength - nTextIndex; + } + + if(nTextLength && rPropertyHolders.Current().getTextColorActive()) + { + drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice; + aTextLayouterDevice.setFont(rPropertyHolders.Current().getFont()); + + std::vector< double > aTextArray( + aTextLayouterDevice.getTextArray( + pA->GetText(), + nTextIndex, + nTextLength)); + + if(!aTextArray.empty()) + { + const double fTextLength(aTextArray.back()); + + if(0.0 != fTextLength && pA->GetWidth()) + { + const double fRelative(pA->GetWidth() / fTextLength); + + if(fabs(fRelative - 1.0) >= 0.035) + { + // when derivation is more than 3,5% from default text size, + // scale the DXArray + for(double & a : aTextArray) + { + a *= fRelative; + } + } + } + } + + processMetaTextAction( + pA->GetPoint(), + pA->GetText(), + nTextIndex, + nTextLength, + std::move(aTextArray), + rTargetHolders.Current(), + rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::TEXTRECT : + { + /** CHECKED, WORKS WELL */ + // OSL_FAIL("MetaActionType::TEXTRECT requested (!)"); + const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction); + const tools::Rectangle& rRectangle = pA->GetRect(); + const sal_uInt32 nStringLength(pA->GetText().getLength()); + + if(!rRectangle.IsEmpty() && 0 != nStringLength) + { + // The problem with this action is that it describes unlayouted text + // and the layout capabilities are in EditEngine/Outliner in SVX. The + // same problem is true for VCL which internally has implementations + // to layout text in this case. There exists even a call + // OutputDevice::AddTextRectActions(...) to create the needed actions + // as 'sub-content' of a Metafile. Unfortunately i do not have an + // OutputDevice here since this interpreter tries to work without + // VCL AFAP. + // Since AddTextRectActions is the only way as long as we do not have + // a simple text layouter available, i will try to add it to the + // TextLayouterDevice isolation. + drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice; + aTextLayouterDevice.setFont(rPropertyHolders.Current().getFont()); + GDIMetaFile aGDIMetaFile; + + aTextLayouterDevice.addTextRectActions( + rRectangle, pA->GetText(), pA->GetStyle(), aGDIMetaFile); + + if(aGDIMetaFile.GetActionSize()) + { + // create sub-content + drawinglayer::primitive2d::Primitive2DContainer xSubContent; + { + rTargetHolders.Push(); + + // for sub-Mteafile contents, do start with new, default render state + // #i124686# ...but copy font, this is already set accordingly + vcl::Font aTargetFont = rPropertyHolders.Current().getFont(); + rPropertyHolders.PushDefault(); + rPropertyHolders.Current().setFont(aTargetFont); + + implInterpretMetafile(aGDIMetaFile, rTargetHolders, rPropertyHolders, rViewInformation); + xSubContent = rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current()); + rPropertyHolders.Pop(); + rTargetHolders.Pop(); + } + + if(!xSubContent.empty()) + { + // add with transformation + rTargetHolders.Current().append( + new drawinglayer::primitive2d::TransformPrimitive2D( + rPropertyHolders.Current().getTransformation(), + std::move(xSubContent))); + } + } + } + + break; + } + case MetaActionType::BMP : + { + /** CHECKED, WORKS WELL */ + const MetaBmpAction* pA = static_cast<const MetaBmpAction*>(pAction); + const BitmapEx aBitmapEx(pA->GetBitmap()); + + createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), rTargetHolders.Current(), rPropertyHolders.Current()); + + break; + } + case MetaActionType::BMPSCALE : + { + /** CHECKED, WORKS WELL */ + const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction); + const BitmapEx aBitmapEx(pA->GetBitmap()); + + createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), pA->GetSize(), rTargetHolders.Current(), rPropertyHolders.Current()); + + break; + } + case MetaActionType::BMPSCALEPART : + { + /** CHECKED, WORKS WELL */ + const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pAction); + const Bitmap& rBitmap = pA->GetBitmap(); + + if(!rBitmap.IsEmpty()) + { + Bitmap aCroppedBitmap(rBitmap); + const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize()); + + if(!aCropRectangle.IsEmpty()) + { + aCroppedBitmap.Crop(aCropRectangle); + } + + const BitmapEx aCroppedBitmapEx(aCroppedBitmap); + createBitmapExPrimitive(aCroppedBitmapEx, pA->GetDestPoint(), pA->GetDestSize(), rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::BMPEX : + { + /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMP */ + const MetaBmpExAction* pA = static_cast<const MetaBmpExAction*>(pAction); + const BitmapEx& rBitmapEx = pA->GetBitmapEx(); + + createBitmapExPrimitive(rBitmapEx, pA->GetPoint(), rTargetHolders.Current(), rPropertyHolders.Current()); + + break; + } + case MetaActionType::BMPEXSCALE : + { + /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALE */ + const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction); + const BitmapEx& rBitmapEx = pA->GetBitmapEx(); + + createBitmapExPrimitive(rBitmapEx, pA->GetPoint(), pA->GetSize(), rTargetHolders.Current(), rPropertyHolders.Current()); + + break; + } + case MetaActionType::BMPEXSCALEPART : + { + /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALEPART */ + const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction); + const BitmapEx& rBitmapEx = pA->GetBitmapEx(); + + if(!rBitmapEx.IsEmpty()) + { + BitmapEx aCroppedBitmapEx(rBitmapEx); + const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize()); + + if(!aCropRectangle.IsEmpty()) + { + aCroppedBitmapEx.Crop(aCropRectangle); + } + + createBitmapExPrimitive(aCroppedBitmapEx, pA->GetDestPoint(), pA->GetDestSize(), rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::MASK : + { + /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMP */ + /** Huh, no it isn't!? */ + const MetaMaskAction* pA = static_cast<const MetaMaskAction*>(pAction); + const BitmapEx aBitmapEx(createMaskBmpEx(pA->GetBitmap(), pA->GetColor())); + + createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), rTargetHolders.Current(), rPropertyHolders.Current()); + + break; + } + case MetaActionType::MASKSCALE : + { + /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALE */ + const MetaMaskScaleAction* pA = static_cast<const MetaMaskScaleAction*>(pAction); + const BitmapEx aBitmapEx(createMaskBmpEx(pA->GetBitmap(), pA->GetColor())); + + createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), pA->GetSize(), rTargetHolders.Current(), rPropertyHolders.Current()); + + break; + } + case MetaActionType::MASKSCALEPART : + { + /** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALEPART */ + const MetaMaskScalePartAction* pA = static_cast<const MetaMaskScalePartAction*>(pAction); + const Bitmap& rBitmap = pA->GetBitmap(); + + if(!rBitmap.IsEmpty()) + { + Bitmap aCroppedBitmap(rBitmap); + const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize()); + + if(!aCropRectangle.IsEmpty()) + { + aCroppedBitmap.Crop(aCropRectangle); + } + + const BitmapEx aCroppedBitmapEx(createMaskBmpEx(aCroppedBitmap, pA->GetColor())); + createBitmapExPrimitive(aCroppedBitmapEx, pA->GetDestPoint(), pA->GetDestSize(), rTargetHolders.Current(), rPropertyHolders.Current()); + } + + break; + } + case MetaActionType::GRADIENT : + { + /** CHECKED, WORKS WELL */ + const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pAction); + const tools::Rectangle& rRectangle = pA->GetRect(); + + if(!rRectangle.IsEmpty()) + { + basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle); + + if(!aRange.isEmpty()) + { + const Gradient& rGradient = pA->GetGradient(); + const drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); + basegfx::B2DPolyPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange)); + + if(aAttribute.getStartColor() == aAttribute.getEndColor()) + { + // not really a gradient. Create filled rectangle + createFillPrimitive( + aOutline, + rTargetHolders.Current(), + rPropertyHolders.Current()); + } + else + { + // really a gradient + aRange.transform(rPropertyHolders.Current().getTransformation()); + drawinglayer::primitive2d::Primitive2DContainer xGradient(1); + + if(rPropertyHolders.Current().isRasterOpInvert()) + { + // use a special version of FillGradientPrimitive2D which creates + // non-overlapping geometry on decomposition to make the old XOR + // paint 'trick' work. + xGradient[0] = drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::NonOverlappingFillGradientPrimitive2D( + aRange, + aAttribute)); + } + else + { + xGradient[0] = drawinglayer::primitive2d::Primitive2DReference( + new drawinglayer::primitive2d::FillGradientPrimitive2D( + aRange, + aAttribute)); + } + + // #i112300# clip against polygon representing the rectangle from + // the action. This is implicitly done using a temp Clipping in VCL + // when a MetaGradientAction is executed + aOutline.transform(rPropertyHolders.Current().getTransformation()); + rTargetHolders.Current().append( + new drawinglayer::primitive2d::MaskPrimitive2D( + aOutline, + std::move(xGradient))); + } + } + } + + break; + } + case MetaActionType::HATCH : + { + /** CHECKED, WORKS WELL */ + const MetaHatchAction* pA = static_cast<const MetaHatchAction*>(pAction); + basegfx::B2DPolyPolygon aOutline(pA->GetPolyPolygon().getB2DPolyPolygon()); + + if(aOutline.count()) + { + const Hatch& rHatch = pA->GetHatch(); + const drawinglayer::attribute::FillHatchAttribute aAttribute(createFillHatchAttribute(rHatch)); + + aOutline.transform(rPropertyHolders.Current().getTransformation()); + + const basegfx::B2DRange aObjectRange(aOutline.getB2DRange()); + const drawinglayer::primitive2d::Primitive2DReference aFillHatch( + new drawinglayer::primitive2d::FillHatchPrimitive2D( + aObjectRange, + basegfx::BColor(), + aAttribute)); + + rTargetHolders.Current().append( + new drawinglayer::primitive2d::MaskPrimitive2D( + aOutline, + drawinglayer::primitive2d::Primitive2DContainer { aFillHatch })); + } + + break; + } + case MetaActionType::WALLPAPER : + { + /** CHECKED, WORKS WELL */ + const MetaWallpaperAction* pA = static_cast<const MetaWallpaperAction*>(pAction); + tools::Rectangle aWallpaperRectangle(pA->GetRect()); + + if(!aWallpaperRectangle.IsEmpty()) + { + const Wallpaper& rWallpaper = pA->GetWallpaper(); + const WallpaperStyle eWallpaperStyle(rWallpaper.GetStyle()); + basegfx::B2DRange aWallpaperRange = vcl::unotools::b2DRectangleFromRectangle(aWallpaperRectangle); + + if(WallpaperStyle::NONE != eWallpaperStyle) + { + if(rWallpaper.IsBitmap()) + { + // create bitmap background. Caution: This + // also will create gradient/color background(s) + // when the bitmap is transparent or not tiled + CreateAndAppendBitmapWallpaper( + aWallpaperRange, + rWallpaper, + rTargetHolders.Current(), + rPropertyHolders.Current()); + } + else if(rWallpaper.IsGradient()) + { + // create gradient background + rTargetHolders.Current().append( + CreateGradientWallpaper( + aWallpaperRange, + rWallpaper.GetGradient(), + rPropertyHolders.Current())); + } + else if(!rWallpaper.GetColor().IsTransparent()) + { + // create color background + rTargetHolders.Current().append( + CreateColorWallpaper( + aWallpaperRange, + rWallpaper.GetColor().getBColor(), + rPropertyHolders.Current())); + } + } + } + + break; + } + case MetaActionType::CLIPREGION : + { + /** CHECKED, WORKS WELL */ + const MetaClipRegionAction* pA = static_cast<const MetaClipRegionAction*>(pAction); + + if(pA->IsClipping()) + { + // new clipping. Get tools::PolyPolygon and transform with current transformation + basegfx::B2DPolyPolygon aNewClipPolyPolygon(getB2DPolyPolygonFromRegion(pA->GetRegion())); + + aNewClipPolyPolygon.transform(rPropertyHolders.Current().getTransformation()); + HandleNewClipRegion(aNewClipPolyPolygon, rTargetHolders, rPropertyHolders); + } + else + { + // end clipping + const basegfx::B2DPolyPolygon aEmptyPolyPolygon; + + HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders); + } + + break; + } + case MetaActionType::ISECTRECTCLIPREGION : + { + /** CHECKED, WORKS WELL */ + const MetaISectRectClipRegionAction* pA = static_cast<const MetaISectRectClipRegionAction*>(pAction); + const tools::Rectangle& rRectangle = pA->GetRect(); + + if(rRectangle.IsEmpty()) + { + // intersect with empty rectangle will always give empty + // ClipPolyPolygon; start new clipping with empty PolyPolygon + const basegfx::B2DPolyPolygon aEmptyPolyPolygon; + + HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders); + } + else + { + // create transformed ClipRange + basegfx::B2DRange aClipRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle); + + aClipRange.transform(rPropertyHolders.Current().getTransformation()); + + if(rPropertyHolders.Current().getClipPolyPolygonActive()) + { + if(0 == rPropertyHolders.Current().getClipPolyPolygon().count()) + { + // nothing to do, empty active clipPolyPolygon will stay + // empty when intersecting + } + else + { + // AND existing region and new ClipRange + const basegfx::B2DPolyPolygon aOriginalPolyPolygon( + rPropertyHolders.Current().getClipPolyPolygon()); + basegfx::B2DPolyPolygon aClippedPolyPolygon; + + if(aOriginalPolyPolygon.count()) + { + aClippedPolyPolygon = basegfx::utils::clipPolyPolygonOnRange( + aOriginalPolyPolygon, + aClipRange, + true, + false); + } + + if(aClippedPolyPolygon != aOriginalPolyPolygon) + { + // start new clipping with intersected region + HandleNewClipRegion( + aClippedPolyPolygon, + rTargetHolders, + rPropertyHolders); + } + } + } + else + { + // start new clipping with ClipRange + const basegfx::B2DPolyPolygon aNewClipPolyPolygon( + basegfx::utils::createPolygonFromRect(aClipRange)); + + HandleNewClipRegion(aNewClipPolyPolygon, rTargetHolders, rPropertyHolders); + } + } + + break; + } + case MetaActionType::ISECTREGIONCLIPREGION : + { + /** CHECKED, WORKS WELL */ + const MetaISectRegionClipRegionAction* pA = static_cast<const MetaISectRegionClipRegionAction*>(pAction); + const vcl::Region& rNewRegion = pA->GetRegion(); + + if(rNewRegion.IsEmpty()) + { + // intersect with empty region will always give empty + // region; start new clipping with empty PolyPolygon + const basegfx::B2DPolyPolygon aEmptyPolyPolygon; + + HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders); + } + else + { + // get new ClipPolyPolygon, transform it with current transformation + basegfx::B2DPolyPolygon aNewClipPolyPolygon(getB2DPolyPolygonFromRegion(rNewRegion)); + aNewClipPolyPolygon.transform(rPropertyHolders.Current().getTransformation()); + + if(rPropertyHolders.Current().getClipPolyPolygonActive()) + { + if(0 == rPropertyHolders.Current().getClipPolyPolygon().count()) + { + // nothing to do, empty active clipPolyPolygon will stay empty + // when intersecting with any region + } + else + { + // AND existing and new region + const basegfx::B2DPolyPolygon aOriginalPolyPolygon( + rPropertyHolders.Current().getClipPolyPolygon()); + basegfx::B2DPolyPolygon aClippedPolyPolygon; + + if(aOriginalPolyPolygon.count()) + { + aClippedPolyPolygon = basegfx::utils::clipPolyPolygonOnPolyPolygon( + aOriginalPolyPolygon, aNewClipPolyPolygon, true, false); + } + + if(aClippedPolyPolygon != aOriginalPolyPolygon) + { + // start new clipping with intersected ClipPolyPolygon + HandleNewClipRegion(aClippedPolyPolygon, rTargetHolders, rPropertyHolders); + } + } + } + else + { + // start new clipping with new ClipPolyPolygon + HandleNewClipRegion(aNewClipPolyPolygon, rTargetHolders, rPropertyHolders); + } + } + + break; + } + case MetaActionType::MOVECLIPREGION : + { + /** CHECKED, WORKS WELL */ + const MetaMoveClipRegionAction* pA = static_cast<const MetaMoveClipRegionAction*>(pAction); + + if(rPropertyHolders.Current().getClipPolyPolygonActive()) + { + if(0 == rPropertyHolders.Current().getClipPolyPolygon().count()) + { + // nothing to do + } + else + { + const sal_Int32 nHor(pA->GetHorzMove()); + const sal_Int32 nVer(pA->GetVertMove()); + + if(0 != nHor || 0 != nVer) + { + // prepare translation, add current transformation + basegfx::B2DVector aVector(pA->GetHorzMove(), pA->GetVertMove()); + aVector *= rPropertyHolders.Current().getTransformation(); + basegfx::B2DHomMatrix aTransform( + basegfx::utils::createTranslateB2DHomMatrix(aVector)); + + // transform existing region + basegfx::B2DPolyPolygon aClipPolyPolygon( + rPropertyHolders.Current().getClipPolyPolygon()); + + aClipPolyPolygon.transform(aTransform); + HandleNewClipRegion(aClipPolyPolygon, rTargetHolders, rPropertyHolders); + } + } + } + + break; + } + case MetaActionType::LINECOLOR : + { + /** CHECKED, WORKS WELL */ + const MetaLineColorAction* pA = static_cast<const MetaLineColorAction*>(pAction); + // tdf#89901 do as OutDev does: COL_TRANSPARENT deactivates line draw + const bool bActive(pA->IsSetting() && COL_TRANSPARENT != pA->GetColor()); + + rPropertyHolders.Current().setLineColorActive(bActive); + if(bActive) + rPropertyHolders.Current().setLineColor(pA->GetColor().getBColor()); + + break; + } + case MetaActionType::FILLCOLOR : + { + /** CHECKED, WORKS WELL */ + const MetaFillColorAction* pA = static_cast<const MetaFillColorAction*>(pAction); + // tdf#89901 do as OutDev does: COL_TRANSPARENT deactivates polygon fill + const bool bActive(pA->IsSetting() && COL_TRANSPARENT != pA->GetColor()); + + rPropertyHolders.Current().setFillColorActive(bActive); + if(bActive) + rPropertyHolders.Current().setFillColor(pA->GetColor().getBColor()); + + break; + } + case MetaActionType::TEXTCOLOR : + { + /** SIMPLE, DONE */ + const MetaTextColorAction* pA = static_cast<const MetaTextColorAction*>(pAction); + const bool bActivate(COL_TRANSPARENT != pA->GetColor()); + + rPropertyHolders.Current().setTextColorActive(bActivate); + rPropertyHolders.Current().setTextColor(pA->GetColor().getBColor()); + + break; + } + case MetaActionType::TEXTFILLCOLOR : + { + /** SIMPLE, DONE */ + const MetaTextFillColorAction* pA = static_cast<const MetaTextFillColorAction*>(pAction); + const bool bWithColorArgument(pA->IsSetting()); + + if(bWithColorArgument) + { + // emulate OutputDevice::SetTextFillColor(...) WITH argument + const Color& rFontFillColor = pA->GetColor(); + rPropertyHolders.Current().setTextFillColor(rFontFillColor.getBColor()); + rPropertyHolders.Current().setTextFillColorActive(COL_TRANSPARENT != rFontFillColor); + } + else + { + // emulate SetFillColor() <- NO argument (!) + rPropertyHolders.Current().setTextFillColorActive(false); + } + + break; + } + case MetaActionType::TEXTALIGN : + { + /** SIMPLE, DONE */ + const MetaTextAlignAction* pA = static_cast<const MetaTextAlignAction*>(pAction); + const TextAlign aNewTextAlign = pA->GetTextAlign(); + + // TextAlign is applied to the current font (as in + // OutputDevice::SetTextAlign which would be used when + // playing the Metafile) + if(rPropertyHolders.Current().getFont().GetAlignment() != aNewTextAlign) + { + vcl::Font aNewFont(rPropertyHolders.Current().getFont()); + aNewFont.SetAlignment(aNewTextAlign); + rPropertyHolders.Current().setFont(aNewFont); + } + + break; + } + case MetaActionType::MAPMODE : + { + /** CHECKED, WORKS WELL */ + // the most necessary MapMode to be interpreted is MapUnit::MapRelative, + // but also the others may occur. Even not yet supported ones + // may need to be added here later + const MetaMapModeAction* pA = static_cast<const MetaMapModeAction*>(pAction); + const MapMode& rMapMode = pA->GetMapMode(); + basegfx::B2DHomMatrix aMapping; + + if(MapUnit::MapRelative == rMapMode.GetMapUnit()) + { + aMapping = getTransformFromMapMode(rMapMode); + } + else + { + const auto eFrom = MapToO3tlLength(rPropertyHolders.Current().getMapUnit()), + eTo = MapToO3tlLength(rMapMode.GetMapUnit()); + if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid) + { + const double fConvert(o3tl::convert(1.0, eFrom, eTo)); + aMapping.scale(fConvert, fConvert); + } + else + OSL_FAIL("implInterpretMetafile: MetaActionType::MAPMODE with unsupported MapUnit (!)"); + + aMapping = getTransformFromMapMode(rMapMode) * aMapping; + rPropertyHolders.Current().setMapUnit(rMapMode.GetMapUnit()); + } + + if(!aMapping.isIdentity()) + { + aMapping = aMapping * rPropertyHolders.Current().getTransformation(); + rPropertyHolders.Current().setTransformation(aMapping); + } + + break; + } + case MetaActionType::FONT : + { + /** SIMPLE, DONE */ + const MetaFontAction* pA = static_cast<const MetaFontAction*>(pAction); + rPropertyHolders.Current().setFont(pA->GetFont()); + Size aFontSize(pA->GetFont().GetFontSize()); + + if(0 == aFontSize.Height()) + { + // this should not happen but i got Metafiles where this was the + // case. A height needs to be guessed (similar to OutputDevice::ImplNewFont()) + vcl::Font aCorrectedFont(pA->GetFont()); + + // guess 16 pixel (as in VCL) + aFontSize = Size(0, 16); + + // convert to target MapUnit if not pixels + aFontSize = OutputDevice::LogicToLogic( + aFontSize, MapMode(MapUnit::MapPixel), MapMode(rPropertyHolders.Current().getMapUnit())); + + aCorrectedFont.SetFontSize(aFontSize); + rPropertyHolders.Current().setFont(aCorrectedFont); + } + + // older Metafiles have no MetaActionType::TEXTCOLOR which defines + // the FontColor now, so use the Font's color when not transparent + const Color& rFontColor = pA->GetFont().GetColor(); + const bool bActivate(COL_TRANSPARENT != rFontColor); + + if(bActivate) + { + rPropertyHolders.Current().setTextColor(rFontColor.getBColor()); + } + + // caution: do NOT deactivate here on transparent, see + // OutputDevice::SetFont(..) for more info + // rPropertyHolders.Current().setTextColorActive(bActivate); + + // for fill color emulate a MetaTextFillColorAction with !transparent as bool, + // see OutputDevice::SetFont(..) the if(mpMetaFile) case + if(bActivate) + { + const Color& rFontFillColor = pA->GetFont().GetFillColor(); + rPropertyHolders.Current().setTextFillColor(rFontFillColor.getBColor()); + rPropertyHolders.Current().setTextFillColorActive(COL_TRANSPARENT != rFontFillColor); + } + else + { + rPropertyHolders.Current().setTextFillColorActive(false); + } + + break; + } + case MetaActionType::PUSH : + { + /** CHECKED, WORKS WELL */ + const MetaPushAction* pA = static_cast<const MetaPushAction*>(pAction); + rPropertyHolders.Push(pA->GetFlags()); + + break; + } + case MetaActionType::POP : + { + /** CHECKED, WORKS WELL */ + const bool bRegionMayChange(rPropertyHolders.Current().getPushFlags() & vcl::PushFlags::CLIPREGION); + const bool bRasterOpMayChange(rPropertyHolders.Current().getPushFlags() & vcl::PushFlags::RASTEROP); + + if(bRegionMayChange && rPropertyHolders.Current().getClipPolyPolygonActive()) + { + // end evtl. clipping + const basegfx::B2DPolyPolygon aEmptyPolyPolygon; + + HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders); + } + + if(bRasterOpMayChange && rPropertyHolders.Current().isRasterOpActive()) + { + // end evtl. RasterOp + HandleNewRasterOp(RasterOp::OverPaint, rTargetHolders, rPropertyHolders); + } + + rPropertyHolders.Pop(); + + if(bRasterOpMayChange && rPropertyHolders.Current().isRasterOpActive()) + { + // start evtl. RasterOp + HandleNewRasterOp(rPropertyHolders.Current().getRasterOp(), rTargetHolders, rPropertyHolders); + } + + if(bRegionMayChange && rPropertyHolders.Current().getClipPolyPolygonActive()) + { + // start evtl. clipping + HandleNewClipRegion( + rPropertyHolders.Current().getClipPolyPolygon(), rTargetHolders, rPropertyHolders); + } + + break; + } + case MetaActionType::RASTEROP : + { + /** CHECKED, WORKS WELL */ + const MetaRasterOpAction* pA = static_cast<const MetaRasterOpAction*>(pAction); + const RasterOp aRasterOp = pA->GetRasterOp(); + + HandleNewRasterOp(aRasterOp, rTargetHolders, rPropertyHolders); + + break; + } + case MetaActionType::Transparent : + { + /** CHECKED, WORKS WELL */ + const MetaTransparentAction* pA = static_cast<const MetaTransparentAction*>(pAction); + const basegfx::B2DPolyPolygon aOutline(pA->GetPolyPolygon().getB2DPolyPolygon()); + + if(aOutline.count()) + { + const sal_uInt16 nTransparence(pA->GetTransparence()); + + if(0 == nTransparence) + { + // not transparent + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + } + else if(nTransparence >= 100) + { + // fully or more than transparent + } + else + { + // transparent. Create new target + rTargetHolders.Push(); + + // create primitives there and get them + createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current()); + drawinglayer::primitive2d::Primitive2DContainer aSubContent( + rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current())); + + // back to old target + rTargetHolders.Pop(); + + if(!aSubContent.empty()) + { + rTargetHolders.Current().append( + new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + std::move(aSubContent), + nTransparence * 0.01)); + } + } + } + + break; + } + case MetaActionType::EPS : + { + /** CHECKED, WORKS WELL */ + // To support this action, I have added a EpsPrimitive2D which will + // by default decompose to the Metafile replacement data. To support + // this EPS on screen, the renderer visualizing this has to support + // that primitive and visualize the Eps file (e.g. printing) + const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pAction); + const tools::Rectangle aRectangle(pA->GetPoint(), pA->GetSize()); + + if(!aRectangle.IsEmpty()) + { + // create object transform + basegfx::B2DHomMatrix aObjectTransform; + + aObjectTransform.set(0, 0, aRectangle.GetWidth()); + aObjectTransform.set(1, 1, aRectangle.GetHeight()); + aObjectTransform.set(0, 2, aRectangle.Left()); + aObjectTransform.set(1, 2, aRectangle.Top()); + + // add current transformation + aObjectTransform = rPropertyHolders.Current().getTransformation() * aObjectTransform; + + // embed using EpsPrimitive + rTargetHolders.Current().append( + new drawinglayer::primitive2d::EpsPrimitive2D( + aObjectTransform, + pA->GetLink(), + pA->GetSubstitute())); + } + + break; + } + case MetaActionType::REFPOINT : + { + /** SIMPLE, DONE */ + // only used for hatch and line pattern offsets, pretty much no longer + // supported today + // const MetaRefPointAction* pA = (const MetaRefPointAction*)pAction; + break; + } + case MetaActionType::TEXTLINECOLOR : + { + /** SIMPLE, DONE */ + const MetaTextLineColorAction* pA = static_cast<const MetaTextLineColorAction*>(pAction); + const bool bActive(pA->IsSetting()); + + rPropertyHolders.Current().setTextLineColorActive(bActive); + if(bActive) + rPropertyHolders.Current().setTextLineColor(pA->GetColor().getBColor()); + + break; + } + case MetaActionType::TEXTLINE : + { + /** CHECKED, WORKS WELL */ + // actually creates overline, underline and strikeouts, so + // these should be isolated from TextDecoratedPortionPrimitive2D + // to own primitives. Done, available now. + // + // This Metaaction seems not to be used (was not used in any + // checked files). It's used in combination with the current + // Font. + const MetaTextLineAction* pA = static_cast<const MetaTextLineAction*>(pAction); + + processMetaTextLineAction( + *pA, + rTargetHolders.Current(), + rPropertyHolders.Current()); + + break; + } + case MetaActionType::FLOATTRANSPARENT : + { + /** CHECKED, WORKS WELL */ + const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pAction); + const basegfx::B2DRange aTargetRange( + pA->GetPoint().X(), + pA->GetPoint().Y(), + pA->GetPoint().X() + pA->GetSize().Width(), + pA->GetPoint().Y() + pA->GetSize().Height()); + + if(!aTargetRange.isEmpty()) + { + const GDIMetaFile& rContent = pA->GetGDIMetaFile(); + + if(rContent.GetActionSize()) + { + // create the sub-content with no embedding specific to the + // sub-metafile, this seems not to be used. + drawinglayer::primitive2d::Primitive2DContainer xSubContent; + { + rTargetHolders.Push(); + // #i# for sub-Mteafile contents, do start with new, default render state + rPropertyHolders.PushDefault(); + implInterpretMetafile(rContent, rTargetHolders, rPropertyHolders, rViewInformation); + xSubContent = rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current()); + rPropertyHolders.Pop(); + rTargetHolders.Pop(); + } + + if(!xSubContent.empty()) + { + // prepare sub-content transform + basegfx::B2DHomMatrix aSubTransform; + + // create SourceRange + const basegfx::B2DRange aSourceRange( + rContent.GetPrefMapMode().GetOrigin().X(), + rContent.GetPrefMapMode().GetOrigin().Y(), + rContent.GetPrefMapMode().GetOrigin().X() + rContent.GetPrefSize().Width(), + rContent.GetPrefMapMode().GetOrigin().Y() + rContent.GetPrefSize().Height()); + + // apply mapping if aTargetRange and aSourceRange are not equal + if(!aSourceRange.equal(aTargetRange)) + { + aSubTransform.translate(-aSourceRange.getMinX(), -aSourceRange.getMinY()); + aSubTransform.scale( + aTargetRange.getWidth() / (basegfx::fTools::equalZero(aSourceRange.getWidth()) ? 1.0 : aSourceRange.getWidth()), + aTargetRange.getHeight() / (basegfx::fTools::equalZero(aSourceRange.getHeight()) ? 1.0 : aSourceRange.getHeight())); + aSubTransform.translate(aTargetRange.getMinX(), aTargetRange.getMinY()); + } + + // apply general current transformation + aSubTransform = rPropertyHolders.Current().getTransformation() * aSubTransform; + + // evtl. embed sub-content to its transformation + if(!aSubTransform.isIdentity()) + { + const drawinglayer::primitive2d::Primitive2DReference aEmbeddedTransform( + new drawinglayer::primitive2d::TransformPrimitive2D( + aSubTransform, + std::move(xSubContent))); + + xSubContent = drawinglayer::primitive2d::Primitive2DContainer { aEmbeddedTransform }; + } + + // check if gradient is a real gradient + const Gradient& rGradient = pA->GetGradient(); + const drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); + + if(aAttribute.getStartColor() == aAttribute.getEndColor()) + { + // not really a gradient; create UnifiedTransparencePrimitive2D + rTargetHolders.Current().append( + new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D( + std::move(xSubContent), + aAttribute.getStartColor().luminance())); + } + else + { + // really a gradient. Create gradient sub-content (with correct scaling) + basegfx::B2DRange aRange(aTargetRange); + aRange.transform(rPropertyHolders.Current().getTransformation()); + + // prepare gradient for transparent content + const drawinglayer::primitive2d::Primitive2DReference xTransparence( + new drawinglayer::primitive2d::FillGradientPrimitive2D( + aRange, + aAttribute)); + + // create transparence primitive + rTargetHolders.Current().append( + new drawinglayer::primitive2d::TransparencePrimitive2D( + std::move(xSubContent), + drawinglayer::primitive2d::Primitive2DContainer { xTransparence })); + } + } + } + } + + break; + } + case MetaActionType::GRADIENTEX : + { + /** SIMPLE, DONE */ + // This is only a data holder which is interpreted inside comment actions, + // see MetaActionType::COMMENT for more info + // const MetaGradientExAction* pA = (const MetaGradientExAction*)pAction; + break; + } + case MetaActionType::LAYOUTMODE : + { + /** SIMPLE, DONE */ + const MetaLayoutModeAction* pA = static_cast<const MetaLayoutModeAction*>(pAction); + rPropertyHolders.Current().setLayoutMode(pA->GetLayoutMode()); + break; + } + case MetaActionType::TEXTLANGUAGE : + { + /** SIMPLE, DONE */ + const MetaTextLanguageAction* pA = static_cast<const MetaTextLanguageAction*>(pAction); + rPropertyHolders.Current().setLanguageType(pA->GetTextLanguage()); + break; + } + case MetaActionType::OVERLINECOLOR : + { + /** SIMPLE, DONE */ + const MetaOverlineColorAction* pA = static_cast<const MetaOverlineColorAction*>(pAction); + const bool bActive(pA->IsSetting()); + + rPropertyHolders.Current().setOverlineColorActive(bActive); + if(bActive) + rPropertyHolders.Current().setOverlineColor(pA->GetColor().getBColor()); + + break; + } + case MetaActionType::COMMENT : + { + /** CHECKED, WORKS WELL */ + // I already implemented + // XPATHFILL_SEQ_BEGIN, XPATHFILL_SEQ_END + // XPATHSTROKE_SEQ_BEGIN, XPATHSTROKE_SEQ_END, + // but opted to remove these again; it works well without them + // and makes the code less dependent from those Metafile Add-Ons + const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction); + + if (pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN")) + { + // XGRAD_SEQ_BEGIN, XGRAD_SEQ_END should be supported since the + // pure recorded paint of the gradients uses the XOR paint functionality + // ('trick'). This is (and will be) problematic with AntiAliasing, so it's + // better to use this info + const MetaGradientExAction* pMetaGradientExAction = nullptr; + bool bDone(false); + size_t b(nAction + 1); + + for(; !bDone && b < nCount; b++) + { + pAction = rMetaFile.GetAction(b); + + if(MetaActionType::GRADIENTEX == pAction->GetType()) + { + pMetaGradientExAction = static_cast<const MetaGradientExAction*>(pAction); + } + else if(MetaActionType::COMMENT == pAction->GetType()) + { + if (static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END")) + { + bDone = true; + } + } + } + + if(bDone && pMetaGradientExAction) + { + // consume actions and skip forward + nAction = b - 1; + + // get geometry data + basegfx::B2DPolyPolygon aPolyPolygon(pMetaGradientExAction->GetPolyPolygon().getB2DPolyPolygon()); + + if(aPolyPolygon.count()) + { + // transform geometry + aPolyPolygon.transform(rPropertyHolders.Current().getTransformation()); + + // get and check if gradient is a real gradient + const Gradient& rGradient = pMetaGradientExAction->GetGradient(); + const drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient)); + + if(aAttribute.getStartColor() == aAttribute.getEndColor()) + { + // not really a gradient + rTargetHolders.Current().append( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D( + aPolyPolygon, + aAttribute.getStartColor())); + } + else + { + // really a gradient + rTargetHolders.Current().append( + new drawinglayer::primitive2d::PolyPolygonGradientPrimitive2D( + aPolyPolygon, + aAttribute)); + } + } + } + } + else if (pA->GetComment().equalsIgnoreAsciiCase("EMF_PLUS_HEADER_INFO")) + { + if (aEMFPlus) + { + // error: should not yet exist + SAL_INFO("drawinglayer.emf", "Error: multiple EMF_PLUS_HEADER_INFO"); + } + else + { + SAL_INFO("drawinglayer.emf", "EMF+ passed to canvas mtf renderer - header info, size: " << pA->GetDataSize()); + SvMemoryStream aMemoryStream(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(), StreamMode::READ); + + aEMFPlus.reset( + new emfplushelper::EmfPlusHelper( + aMemoryStream, + rTargetHolders, + rPropertyHolders)); + } + } + else if (pA->GetComment().equalsIgnoreAsciiCase("EMF_PLUS")) + { + if (!aEMFPlus) + { + // error: should exist + SAL_INFO("drawinglayer.emf", "Error: EMF_PLUS before EMF_PLUS_HEADER_INFO"); + } + else + { + static int count = -1, limit = 0x7fffffff; + + if (count == -1) + { + count = 0; + + if (char *env = getenv("EMF_PLUS_LIMIT")) + { + limit = atoi(env); + SAL_INFO("drawinglayer.emf", "EMF+ records limit: " << limit); + } + } + + SAL_INFO("drawinglayer.emf", "EMF+ passed to canvas mtf renderer, size: " << pA->GetDataSize()); + + if (count < limit) + { + SvMemoryStream aMemoryStream(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(), StreamMode::READ); + + aEMFPlus->processEmfPlusData( + aMemoryStream, + rViewInformation); + } + + count++; + } + } + + break; + } + default: + { + OSL_FAIL("Unknown MetaFile Action (!)"); + break; + } + } + } + } +} + +namespace wmfemfhelper +{ + drawinglayer::primitive2d::Primitive2DContainer interpretMetafile( + const GDIMetaFile& rMetaFile, + const drawinglayer::geometry::ViewInformation2D& rViewInformation) + { + // prepare target and properties; each will have one default entry + drawinglayer::primitive2d::Primitive2DContainer xRetval; + TargetHolders aTargetHolders; + PropertyHolders aPropertyHolders; + + // set target MapUnit at Properties + aPropertyHolders.Current().setMapUnit(rMetaFile.GetPrefMapMode().GetMapUnit()); + + // interpret the Metafile + implInterpretMetafile(rMetaFile, aTargetHolders, aPropertyHolders, rViewInformation); + + // get the content. There should be only one target, as in the start condition, + // but iterating will be the right thing to do when some push/pop is not closed + while (aTargetHolders.size() > 1) + { + xRetval.append( + aTargetHolders.Current().getPrimitive2DSequence(aPropertyHolders.Current())); + aTargetHolders.Pop(); + } + + xRetval.append( + aTargetHolders.Current().getPrimitive2DSequence(aPropertyHolders.Current())); + + return xRetval; + } + +} // end of namespace wmfemfhelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |