summaryrefslogtreecommitdiffstats
path: root/drawinglayer/source/tools
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /drawinglayer/source/tools
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drawinglayer/source/tools')
-rw-r--r--drawinglayer/source/tools/converters.cxx382
-rw-r--r--drawinglayer/source/tools/emfpbrush.cxx317
-rw-r--r--drawinglayer/source/tools/emfpbrush.hxx128
-rw-r--r--drawinglayer/source/tools/emfpcustomlinecap.cxx121
-rw-r--r--drawinglayer/source/tools/emfpcustomlinecap.hxx41
-rw-r--r--drawinglayer/source/tools/emfpfont.cxx76
-rw-r--r--drawinglayer/source/tools/emfpfont.hxx48
-rw-r--r--drawinglayer/source/tools/emfphelperdata.cxx2292
-rw-r--r--drawinglayer/source/tools/emfphelperdata.hxx270
-rw-r--r--drawinglayer/source/tools/emfpimage.cxx80
-rw-r--r--drawinglayer/source/tools/emfpimage.hxx48
-rw-r--r--drawinglayer/source/tools/emfpimageattributes.cxx73
-rw-r--r--drawinglayer/source/tools/emfpimageattributes.hxx33
-rw-r--r--drawinglayer/source/tools/emfplushelper.cxx45
-rw-r--r--drawinglayer/source/tools/emfppath.cxx320
-rw-r--r--drawinglayer/source/tools/emfppath.hxx47
-rw-r--r--drawinglayer/source/tools/emfppen.cxx382
-rw-r--r--drawinglayer/source/tools/emfppen.hxx130
-rw-r--r--drawinglayer/source/tools/emfpregion.cxx134
-rw-r--r--drawinglayer/source/tools/emfpregion.hxx50
-rw-r--r--drawinglayer/source/tools/emfpstringformat.cxx195
-rw-r--r--drawinglayer/source/tools/emfpstringformat.hxx104
-rw-r--r--drawinglayer/source/tools/primitive2dxmldump.cxx1245
-rw-r--r--drawinglayer/source/tools/wmfemfhelper.cxx3011
24 files changed, 9572 insertions, 0 deletions
diff --git a/drawinglayer/source/tools/converters.cxx b/drawinglayer/source/tools/converters.cxx
new file mode 100644
index 0000000000..3e32af49c5
--- /dev/null
+++ b/drawinglayer/source/tools/converters.cxx
@@ -0,0 +1,382 @@
+/* -*- 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 <drawinglayer/geometry/viewinformation2d.hxx>
+#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/processor2d/baseprocessor2d.hxx>
+#include <drawinglayer/processor2d/processor2dtools.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <com/sun/star/geometry/RealRectangle2D.hpp>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <drawinglayer/converters.hxx>
+
+#ifdef DBG_UTIL
+#include <tools/stream.hxx>
+// #include <vcl/filter/PngImageWriter.hxx>
+#include <vcl/dibtools.hxx>
+#endif
+
+// #include <vcl/BitmapReadAccess.hxx>
+
+namespace
+{
+bool implPrepareConversion(drawinglayer::primitive2d::Primitive2DContainer& rSequence,
+ sal_uInt32& rnDiscreteWidth, sal_uInt32& rnDiscreteHeight,
+ const sal_uInt32 nMaxSquarePixels)
+{
+ if (rSequence.empty())
+ return false;
+
+ if (rnDiscreteWidth <= 0 || rnDiscreteHeight <= 0)
+ return false;
+
+ const sal_uInt32 nViewVisibleArea(rnDiscreteWidth * rnDiscreteHeight);
+
+ if (nViewVisibleArea > nMaxSquarePixels)
+ {
+ // reduce render size
+ double fReduceFactor
+ = sqrt(static_cast<double>(nMaxSquarePixels) / static_cast<double>(nViewVisibleArea));
+ rnDiscreteWidth = basegfx::fround(static_cast<double>(rnDiscreteWidth) * fReduceFactor);
+ rnDiscreteHeight = basegfx::fround(static_cast<double>(rnDiscreteHeight) * fReduceFactor);
+
+ const drawinglayer::primitive2d::Primitive2DReference aEmbed(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ basegfx::utils::createScaleB2DHomMatrix(fReduceFactor, fReduceFactor),
+ std::move(rSequence)));
+
+ rSequence = drawinglayer::primitive2d::Primitive2DContainer{ aEmbed };
+ }
+
+ return true;
+}
+
+AlphaMask implcreateAlphaMask(drawinglayer::primitive2d::Primitive2DContainer& rSequence,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation2D,
+ const Size& rSizePixel, bool bUseLuminance)
+{
+ ScopedVclPtrInstance<VirtualDevice> pContent;
+
+ // prepare vdev
+ if (!pContent->SetOutputSizePixel(rSizePixel, false))
+ {
+ SAL_WARN("vcl", "Cannot set VirtualDevice to size : " << rSizePixel.Width() << "x"
+ << rSizePixel.Height());
+ return AlphaMask();
+ }
+
+ // create pixel processor, also already takes care of AAing and
+ // checking the getOptionsDrawinglayer().IsAntiAliasing() switch. If
+ // not wanted, change after this call as needed
+ std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pContentProcessor
+ = drawinglayer::processor2d::createPixelProcessor2DFromOutputDevice(*pContent,
+ rViewInformation2D);
+
+ // prepare for mask creation
+ pContent->SetMapMode(MapMode(MapUnit::MapPixel));
+
+ // set transparency to all white (fully transparent)
+ pContent->Erase();
+
+ basegfx::BColorModifierSharedPtr aBColorModifier;
+ if (bUseLuminance)
+ {
+ // new mode: bUseLuminance allows simple creation of alpha channels
+ // for any content (e.g. gradients)
+ aBColorModifier = std::make_shared<basegfx::BColorModifier_luminance_to_alpha>();
+ }
+ else
+ {
+ // Embed primitives to paint them black (fully opaque)
+ aBColorModifier
+ = std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor(0.0, 0.0, 0.0));
+ }
+ const drawinglayer::primitive2d::Primitive2DReference xRef(
+ new drawinglayer::primitive2d::ModifiedColorPrimitive2D(std::move(rSequence),
+ aBColorModifier));
+ const drawinglayer::primitive2d::Primitive2DContainer xSeq{ xRef };
+
+ // render
+ pContentProcessor->process(xSeq);
+ pContentProcessor.reset();
+
+ // get alpha channel from vdev
+ pContent->EnableMapMode(false);
+ const Point aEmptyPoint;
+
+ // Convert from transparency->alpha.
+ // FIXME in theory I should be able to directly construct alpha by using black as background
+ // and white as foreground, but that doesn't work for some reason.
+ Bitmap aContentBitmap = pContent->GetBitmap(aEmptyPoint, rSizePixel);
+ aContentBitmap.Invert();
+
+ return AlphaMask(aContentBitmap);
+}
+}
+
+namespace drawinglayer
+{
+AlphaMask createAlphaMask(drawinglayer::primitive2d::Primitive2DContainer&& rSeq,
+ const geometry::ViewInformation2D& rViewInformation2D,
+ sal_uInt32 nDiscreteWidth, sal_uInt32 nDiscreteHeight,
+ sal_uInt32 nMaxSquarePixels, bool bUseLuminance)
+{
+ drawinglayer::primitive2d::Primitive2DContainer aSequence(std::move(rSeq));
+
+ if (!implPrepareConversion(aSequence, nDiscreteWidth, nDiscreteHeight, nMaxSquarePixels))
+ {
+ return AlphaMask();
+ }
+
+ const Size aSizePixel(nDiscreteWidth, nDiscreteHeight);
+
+ return implcreateAlphaMask(aSequence, rViewInformation2D, aSizePixel, bUseLuminance);
+}
+
+BitmapEx convertToBitmapEx(drawinglayer::primitive2d::Primitive2DContainer&& rSeq,
+ const geometry::ViewInformation2D& rViewInformation2D,
+ sal_uInt32 nDiscreteWidth, sal_uInt32 nDiscreteHeight,
+ sal_uInt32 nMaxSquarePixels, bool bForceAlphaMaskCreation)
+{
+ drawinglayer::primitive2d::Primitive2DContainer aSequence(std::move(rSeq));
+
+ if (!implPrepareConversion(aSequence, nDiscreteWidth, nDiscreteHeight, nMaxSquarePixels))
+ {
+ return BitmapEx();
+ }
+
+ const Point aEmptyPoint;
+ const Size aSizePixel(nDiscreteWidth, nDiscreteHeight);
+
+ // Create target VirtualDevice. Go back to using a simple RGB
+ // target version (compared with former version, see history).
+ // Reasons are manyfold:
+ // - Avoid the RGBA mode for VirtualDevice (two VDevs)
+ // - It's not suggested to be used outside presentation engine
+ // - It only works *by chance* with VCLPrimitiveRenderer
+ // - Usage of two-VDev alpha-VDev avoided alpha blending against
+ // COL_WHITE in the 1st layer of targets (not in buffers below)
+ // but is kind of a 'hack' doing so
+ // - Other renderers (system-dependent PrimitiveRenderers, other
+ // than the VCL-based ones) will probably not support splitted
+ // VDevs for content/alpha, so require a method that works with
+ // RGB targeting (for now)
+ // - Less resource usage, better speed (no 2 VDevs, no merge of
+ // AlphaChannels)
+ // As long as not all our mechanisms are changed to RGBA completely,
+ // mixing these is just too dangerous and expensive and may to wrong
+ // or deliver bad quality results.
+ // Nonetheless we need a RGBA result here. Luckily we are able to
+ // create a complete and valid AlphaChannel using 'createAlphaMask'
+ // above.
+ // When we know the content (RGB result from renderer), alpha
+ // (result from createAlphaMask) and the start condition (content
+ // rendered against COL_WHITE), it is possible to calculate back
+ // the content, quasi 'remove' that initial blending against
+ // COL_WHITE.
+ // That is what the helper Bitmap::RemoveBlendedStartColor does.
+ // Luckily we only need it for this 'convertToBitmapEx', not in
+ // any other rendering. It could be further optimized, too.
+ // This gives good results, it is in principle comparable with
+ // the results using pre-multiplied alpha tooling, also reducing
+ // the range of values where high alpha values are used.
+ ScopedVclPtrInstance<VirtualDevice> pContent(*Application::GetDefaultDevice());
+
+ // prepare vdev
+ if (!pContent->SetOutputSizePixel(aSizePixel, false))
+ {
+ SAL_WARN("vcl", "Cannot set VirtualDevice to size : " << aSizePixel.Width() << "x"
+ << aSizePixel.Height());
+ return BitmapEx();
+ }
+
+ // We map to pixel, use that MapMode. Init by erasing.
+ pContent->SetMapMode(MapMode(MapUnit::MapPixel));
+ pContent->Erase();
+
+ // create pixel processor, also already takes care of AAing and
+ // checking the getOptionsDrawinglayer().IsAntiAliasing() switch. If
+ // not wanted, change after this call as needed
+ std::unique_ptr<processor2d::BaseProcessor2D> pContentProcessor
+ = processor2d::createPixelProcessor2DFromOutputDevice(*pContent, rViewInformation2D);
+
+ // render content
+ pContentProcessor->process(aSequence);
+
+ // create final BitmapEx result (content)
+ Bitmap aRetval(pContent->GetBitmap(aEmptyPoint, aSizePixel));
+
+#ifdef DBG_UTIL
+ static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
+ if (bDoSaveForVisualControl)
+ {
+ // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/
+ static const OUString sDumpPath(
+ OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
+ if (!sDumpPath.isEmpty())
+ {
+ SvFileStream aNew(sDumpPath + "test_content.bmp",
+ StreamMode::WRITE | StreamMode::TRUNC);
+ WriteDIB(aRetval, aNew, false, true);
+ }
+ }
+#endif
+
+ // Create the AlphaMask using a method that does this always correct (also used
+ // now in GlowPrimitive2D and ShadowPrimitive2D which both only need the
+ // AlphaMask to do their job, so speeding that up, too).
+ AlphaMask aAlpha(implcreateAlphaMask(aSequence, rViewInformation2D, aSizePixel, false));
+
+#ifdef DBG_UTIL
+ if (bDoSaveForVisualControl)
+ {
+ // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/
+ static const OUString sDumpPath(
+ OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
+ if (!sDumpPath.isEmpty())
+ {
+ SvFileStream aNew(sDumpPath + "test_alpha.bmp", StreamMode::WRITE | StreamMode::TRUNC);
+ WriteDIB(aAlpha.GetBitmap(), aNew, false, true);
+ }
+ }
+#endif
+
+ if (bForceAlphaMaskCreation || aAlpha.hasAlpha())
+ {
+ // Need to correct content using known alpha to get to background-free
+ // RGBA result, usable e.g. in PNG export(s) or convert-to-bitmap.
+ // Now that vcl supports bitmaps with an alpha channel, only apply
+ // this correction to bitmaps without an alpha channel.
+ if (pContent->GetBitCount() < 32)
+ {
+ aRetval.RemoveBlendedStartColor(COL_BLACK, aAlpha);
+ }
+ else
+ {
+ // tdf#157558 invert and remove blended white color
+ // Before commit 81994cb2b8b32453a92bcb011830fcb884f22ff3,
+ // RemoveBlendedStartColor(COL_BLACK, aAlpha) would darken
+ // the bitmap when running a slideshow, printing, or exporting
+ // to PDF. To get the same effect, the alpha mask must be
+ // inverted, RemoveBlendedStartColor(COL_WHITE, aAlpha)
+ // called, and the alpha mask uninverted.
+ aAlpha.Invert();
+ aRetval.RemoveBlendedStartColor(COL_WHITE, aAlpha);
+ aAlpha.Invert();
+ }
+ // return combined result
+ return BitmapEx(aRetval, aAlpha);
+ }
+ else
+ return BitmapEx(aRetval);
+}
+
+BitmapEx convertPrimitive2DContainerToBitmapEx(primitive2d::Primitive2DContainer&& rSequence,
+ const basegfx::B2DRange& rTargetRange,
+ sal_uInt32 nMaximumQuadraticPixels,
+ const o3tl::Length eTargetUnit,
+ const std::optional<Size>& rTargetDPI)
+{
+ if (rSequence.empty())
+ return BitmapEx();
+
+ try
+ {
+ css::geometry::RealRectangle2D aRealRect;
+ aRealRect.X1 = rTargetRange.getMinX();
+ aRealRect.Y1 = rTargetRange.getMinY();
+ aRealRect.X2 = rTargetRange.getMaxX();
+ aRealRect.Y2 = rTargetRange.getMaxY();
+
+ // get system DPI
+ Size aDPI(
+ Application::GetDefaultDevice()->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)));
+ if (rTargetDPI.has_value())
+ {
+ aDPI = *rTargetDPI;
+ }
+
+ ::sal_uInt32 DPI_X = aDPI.getWidth();
+ ::sal_uInt32 DPI_Y = aDPI.getHeight();
+ const basegfx::B2DRange aRange(aRealRect.X1, aRealRect.Y1, aRealRect.X2, aRealRect.Y2);
+ const double fWidth(aRange.getWidth());
+ const double fHeight(aRange.getHeight());
+
+ if (!(basegfx::fTools::more(fWidth, 0.0) && basegfx::fTools::more(fHeight, 0.0)))
+ return BitmapEx();
+
+ if (0 == DPI_X)
+ {
+ DPI_X = 75;
+ }
+
+ if (0 == DPI_Y)
+ {
+ DPI_Y = 75;
+ }
+
+ if (0 == nMaximumQuadraticPixels)
+ {
+ nMaximumQuadraticPixels = 500000;
+ }
+
+ const auto aViewInformation2D = geometry::createViewInformation2D({});
+ const sal_uInt32 nDiscreteWidth(
+ basegfx::fround(o3tl::convert(fWidth, eTargetUnit, o3tl::Length::in) * DPI_X));
+ const sal_uInt32 nDiscreteHeight(
+ basegfx::fround(o3tl::convert(fHeight, eTargetUnit, o3tl::Length::in) * DPI_Y));
+
+ basegfx::B2DHomMatrix aEmbedding(
+ basegfx::utils::createTranslateB2DHomMatrix(-aRange.getMinX(), -aRange.getMinY()));
+
+ aEmbedding.scale(nDiscreteWidth / fWidth, nDiscreteHeight / fHeight);
+
+ const primitive2d::Primitive2DReference xEmbedRef(
+ new primitive2d::TransformPrimitive2D(aEmbedding, std::move(rSequence)));
+ primitive2d::Primitive2DContainer xEmbedSeq{ xEmbedRef };
+
+ BitmapEx aBitmapEx(convertToBitmapEx(std::move(xEmbedSeq), aViewInformation2D,
+ nDiscreteWidth, nDiscreteHeight,
+ nMaximumQuadraticPixels));
+
+ if (aBitmapEx.IsEmpty())
+ return BitmapEx();
+ aBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM));
+ aBitmapEx.SetPrefSize(Size(basegfx::fround(fWidth), basegfx::fround(fHeight)));
+
+ return aBitmapEx;
+ }
+ catch (const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("vcl", "Got no graphic::XPrimitive2DRenderer!");
+ }
+ catch (const std::exception& e)
+ {
+ SAL_WARN("vcl", "Got no graphic::XPrimitive2DRenderer! : " << e.what());
+ }
+
+ return BitmapEx();
+}
+} // end of namespace drawinglayer
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfpbrush.cxx b/drawinglayer/source/tools/emfpbrush.cxx
new file mode 100644
index 0000000000..c79b0ded07
--- /dev/null
+++ b/drawinglayer/source/tools/emfpbrush.cxx
@@ -0,0 +1,317 @@
+/* -*- 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 <basegfx/range/b2drange.hxx>
+#include <basegfx/range/b2drectangle.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <o3tl/safeint.hxx>
+#include <sal/log.hxx>
+#include "emfpbrush.hxx"
+#include "emfppath.hxx"
+
+namespace emfplushelper
+{
+ EMFPBrush::EMFPBrush()
+ : type(0)
+ , additionalFlags(0)
+ , wrapMode(0)
+ , firstPointX(0.0)
+ , firstPointY(0.0)
+ , aWidth(0.0)
+ , aHeight(0.0)
+ , hasTransformation(false)
+ , blendPoints(0)
+ , blendFactors(nullptr)
+ , colorblendPoints(0)
+ , surroundColorsNumber(0)
+ , hatchStyle(HatchStyleHorizontal)
+ {
+ }
+
+ EMFPBrush::~EMFPBrush()
+ {
+ }
+
+ static OUString BrushTypeToString(sal_uInt32 type)
+ {
+ switch (type)
+ {
+ case BrushTypeSolidColor: return "BrushTypeSolidColor";
+ case BrushTypeHatchFill: return "BrushTypeHatchFill";
+ case BrushTypeTextureFill: return "BrushTypeTextureFill";
+ case BrushTypePathGradient: return "BrushTypePathGradient";
+ case BrushTypeLinearGradient: return "BrushTypeLinearGradient";
+ }
+ return "";
+ }
+
+ void EMFPBrush::Read(SvStream& s, EmfPlusHelperData const & rR)
+ {
+ sal_uInt32 header;
+
+ s.ReadUInt32(header).ReadUInt32(type);
+
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\tHeader: 0x" << std::hex << header);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\tType: " << BrushTypeToString(type) << "(0x" << type << ")" << std::dec);
+
+ switch (type)
+ {
+ case BrushTypeSolidColor:
+ {
+ sal_uInt32 color;
+ s.ReadUInt32(color);
+
+ solidColor = ::Color(ColorAlpha, (color >> 24), (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tSolid color: 0x" << std::hex << color << std::dec);
+ break;
+ }
+ case BrushTypeHatchFill:
+ {
+ sal_uInt32 style;
+ sal_uInt32 foregroundColor;
+ sal_uInt32 backgroundColor;
+ s.ReadUInt32(style);
+ s.ReadUInt32(foregroundColor);
+ s.ReadUInt32(backgroundColor);
+
+ hatchStyle = static_cast<EmfPlusHatchStyle>(style);
+ solidColor = ::Color(ColorAlpha, (foregroundColor >> 24), (foregroundColor >> 16) & 0xff, (foregroundColor >> 8) & 0xff, foregroundColor & 0xff);
+ secondColor = ::Color(ColorAlpha, (backgroundColor >> 24), (backgroundColor >> 16) & 0xff, (backgroundColor >> 8) & 0xff, backgroundColor & 0xff);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tHatch style: 0x" << std::hex << style);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tForeground color: 0x" << solidColor.AsRGBHexString());
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tBackground color: 0x" << secondColor.AsRGBHexString());
+ break;
+ }
+ case BrushTypeTextureFill:
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+\tTODO: implement BrushTypeTextureFill brush");
+ break;
+ }
+ case BrushTypePathGradient:
+ {
+ s.ReadUInt32(additionalFlags).ReadInt32(wrapMode);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tAdditional flags: 0x" << std::hex << additionalFlags << std::dec);
+ sal_uInt32 color;
+ s.ReadUInt32(color);
+ solidColor = ::Color(ColorAlpha, (color >> 24), (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tCenter color: 0x" << std::hex << color << std::dec);
+ s.ReadFloat(firstPointX).ReadFloat(firstPointY);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tCenter point: " << firstPointX << "," << firstPointY);
+ s.ReadUInt32(surroundColorsNumber);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\t number of surround colors: " << surroundColorsNumber);
+
+ surroundColors.reset( new ::Color[surroundColorsNumber] );
+
+ for (sal_uInt32 i = 0; i < surroundColorsNumber; i++)
+ {
+ s.ReadUInt32(color);
+ surroundColors[i] = ::Color(ColorAlpha, (color >> 24), (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff);
+ if (i == 0)
+ secondColor = surroundColors[0];
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tSurround color[" << i << "]: 0x" << std::hex << color << std::dec);
+ }
+
+ if (additionalFlags & 0x01) // BrushDataPath
+ {
+ sal_Int32 pathLength;
+
+ s.ReadInt32(pathLength);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tPath length: " << pathLength);
+
+ sal_uInt64 const pos = s.Tell();
+
+ sal_uInt32 pathHeader;
+ sal_Int32 pathPoints, pathFlags;
+ s.ReadUInt32(pathHeader).ReadInt32(pathPoints).ReadInt32(pathFlags);
+
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tPath (brush path gradient)");
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\t\tHeader: 0x" << std::hex << pathHeader);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\t\tPoints: " << std::dec << pathPoints);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\t\tAdditional flags: 0x" << std::hex << pathFlags << std::dec);
+
+ path.reset( new EMFPPath(pathPoints) );
+ path->Read(s, pathFlags);
+
+ s.Seek(pos + pathLength);
+
+ const ::basegfx::B2DRectangle aBounds(::basegfx::utils::getRange(path->GetPolygon(rR, false)));
+ aWidth = aBounds.getWidth();
+ aHeight = aBounds.getHeight();
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tPolygon bounding box: " << aBounds.getMinX() << "," << aBounds.getMinY() << " "
+ << aBounds.getWidth() << "x" << aBounds.getHeight());
+ }
+ else
+ {
+ sal_Int32 boundaryPointCount;
+ s.ReadInt32(boundaryPointCount);
+
+ sal_uInt64 const pos = s.Tell();
+ SAL_INFO("drawinglayer.emf", "EMF+\t use boundary, points: " << boundaryPointCount);
+ path.reset( new EMFPPath(boundaryPointCount) );
+ path->Read(s, 0x0);
+
+ s.Seek(pos + 8 * boundaryPointCount);
+
+ const ::basegfx::B2DRectangle aBounds(::basegfx::utils::getRange(path->GetPolygon(rR, false)));
+ aWidth = aBounds.getWidth();
+ aHeight = aBounds.getHeight();
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tPolygon bounding box: " << aBounds.getMinX() << "," << aBounds.getMinY() << " "
+ << aBounds.getWidth() << "x" << aBounds.getHeight());
+ }
+
+ if (additionalFlags & 0x02) // BrushDataTransform
+ {
+ EmfPlusHelperData::readXForm(s, brush_transformation);
+ hasTransformation = true;
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tUse brush transformation: " << brush_transformation);
+ }
+
+ // BrushDataPresetColors and BrushDataBlendFactorsH
+ if ((additionalFlags & 0x04) && (additionalFlags & 0x08))
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+\t Brush must not contain both BrushDataPresetColors and BrushDataBlendFactorsH");
+ return;
+ }
+ if (additionalFlags & 0x08) // BrushDataBlendFactorsH
+ {
+ s.ReadUInt32(blendPoints);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tuse blend, points: " << blendPoints);
+ blendPositions.reset( new float[2 * blendPoints] );
+ blendFactors = blendPositions.get() + blendPoints;
+
+ for (sal_uInt32 i = 0; i < blendPoints; i++)
+ {
+ s.ReadFloat(blendPositions[i]);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tposition[" << i << "]: " << blendPositions[i]);
+ }
+
+ for (sal_uInt32 i = 0; i < blendPoints; i++)
+ {
+ s.ReadFloat(blendFactors[i]);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tFactor[" << i << "]: " << blendFactors[i]);
+ }
+ }
+
+ if (additionalFlags & 0x04) // BrushDataPresetColors
+ {
+ s.ReadUInt32(colorblendPoints);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tUse color blend, points: " << colorblendPoints);
+ colorblendPositions.reset( new float[colorblendPoints] );
+ colorblendColors.reset( new ::Color[colorblendPoints] );
+
+ for (sal_uInt32 i = 0; i < colorblendPoints; i++)
+ {
+ s.ReadFloat(colorblendPositions[i]);
+ SAL_INFO("drawinglayer.emf", "EMF+\tposition[" << i << "]: " << colorblendPositions[i]);
+ }
+
+ for (sal_uInt32 i = 0; i < colorblendPoints; i++)
+ {
+ s.ReadUInt32(color);
+ colorblendColors[i] = ::Color(ColorAlpha, (color >> 24), (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tColor[" << i << "]: 0x" << std::hex << color << std::dec);
+ }
+ }
+
+ break;
+ }
+ case BrushTypeLinearGradient:
+ {
+ s.ReadUInt32(additionalFlags).ReadInt32(wrapMode);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tLinear gradient, additional flags: 0x" << std::hex << additionalFlags << std::dec << ", wrapMode: " << wrapMode);
+ s.ReadFloat(firstPointX).ReadFloat(firstPointY).ReadFloat(aWidth).ReadFloat(aHeight);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tFirst gradient point: " << firstPointX << ":" << firstPointY
+ << ", size " << aWidth << "x" << aHeight);
+ sal_uInt32 color;
+ s.ReadUInt32(color);
+ solidColor = ::Color(ColorAlpha, (color >> 24), (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tfirst color: 0x" << std::hex << color << std::dec);
+ s.ReadUInt32(color);
+ secondColor = ::Color(ColorAlpha, (color >> 24), (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tsecond color: 0x" << std::hex << color << std::dec);
+
+ // repeated colors, unknown meaning, see http://www.aces.uiuc.edu/~jhtodd/Metafile/MetafileRecords/ObjectBrush.html
+ s.ReadUInt32(color);
+ s.ReadUInt32(color);
+
+ if (additionalFlags & 0x02) //BrushDataTransform
+ {
+ EmfPlusHelperData::readXForm(s, brush_transformation);
+ hasTransformation = true;
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tUse brush transformation: " << brush_transformation);
+ }
+ // BrushDataPresetColors and BrushDataBlendFactorsH
+ if ((additionalFlags & 0x04) && (additionalFlags & 0x08))
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+\t Brush must not contain both BrushDataPresetColors and BrushDataBlendFactorsH");
+ return;
+ }
+
+ if (additionalFlags & 0x08) // BrushDataBlendFactorsH
+ {
+ s.ReadUInt32(blendPoints);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tUse blend, points: " << blendPoints);
+ blendPositions.reset( new float[2 * blendPoints] );
+ blendFactors = blendPositions.get() + blendPoints;
+
+ for (sal_uInt32 i = 0; i < blendPoints; i++)
+ {
+ s.ReadFloat(blendPositions[i]);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tPosition[" << i << "]: " << blendPositions[i]);
+ }
+
+ for (sal_uInt32 i = 0; i < blendPoints; i++)
+ {
+ s.ReadFloat(blendFactors[i]);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tFactor[" << i << "]: " << blendFactors[i]);
+ }
+ }
+
+ if (additionalFlags & 0x04)
+ {
+ s.ReadUInt32(colorblendPoints);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tUse color blend, points: " << colorblendPoints);
+ colorblendPositions.reset( new float[colorblendPoints] );
+ colorblendColors.reset( new ::Color[colorblendPoints] );
+
+ for (sal_uInt32 i = 0; i < colorblendPoints; i++)
+ {
+ s.ReadFloat(colorblendPositions[i]);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tPosition[" << i << "]: " << colorblendPositions[i]);
+ }
+
+ for (sal_uInt32 i = 0; i < colorblendPoints; i++)
+ {
+ s.ReadUInt32(color);
+ colorblendColors[i] = ::Color(ColorAlpha, (color >> 24), (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tColor[" << i << "]: 0x" << std::hex << color << std::dec);
+ }
+ }
+
+ break;
+ }
+ default:
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+\tunhandled brush type: " << std::hex << type << std::dec);
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfpbrush.hxx b/drawinglayer/source/tools/emfpbrush.hxx
new file mode 100644
index 0000000000..aee3fe02f6
--- /dev/null
+++ b/drawinglayer/source/tools/emfpbrush.hxx
@@ -0,0 +1,128 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include "emfphelperdata.hxx"
+#include <tools/color.hxx>
+
+namespace emfplushelper
+{
+ enum EmfPlusHatchStyle
+ {
+ HatchStyleHorizontal = 0x00000000,
+ HatchStyleVertical = 0x00000001,
+ HatchStyleForwardDiagonal = 0x00000002,
+ HatchStyleBackwardDiagonal = 0x00000003,
+ HatchStyleLargeGrid = 0x00000004,
+ HatchStyleDiagonalCross = 0x00000005,
+ HatchStyle05Percent = 0x00000006,
+ HatchStyle10Percent = 0x00000007,
+ HatchStyle20Percent = 0x00000008,
+ HatchStyle25Percent = 0x00000009,
+ HatchStyle30Percent = 0x0000000A,
+ HatchStyle40Percent = 0x0000000B,
+ HatchStyle50Percent = 0x0000000C,
+ HatchStyle60Percent = 0x0000000D,
+ HatchStyle70Percent = 0x0000000E,
+ HatchStyle75Percent = 0x0000000F,
+ HatchStyle80Percent = 0x00000010,
+ HatchStyle90Percent = 0x00000011,
+ HatchStyleLightDownwardDiagonal = 0x00000012,
+ HatchStyleLightUpwardDiagonal = 0x00000013,
+ HatchStyleDarkDownwardDiagonal = 0x00000014,
+ HatchStyleDarkUpwardDiagonal = 0x00000015,
+ HatchStyleWideDownwardDiagonal = 0x00000016,
+ HatchStyleWideUpwardDiagonal = 0x00000017,
+ HatchStyleLightVertical = 0x00000018,
+ HatchStyleLightHorizontal = 0x00000019,
+ HatchStyleNarrowVertical = 0x0000001A,
+ HatchStyleNarrowHorizontal = 0x0000001B,
+ HatchStyleDarkVertical = 0x0000001C,
+ HatchStyleDarkHorizontal = 0x0000001D,
+ HatchStyleDashedDownwardDiagonal = 0x0000001E,
+ HatchStyleDashedUpwardDiagonal = 0x0000001F,
+ HatchStyleDashedHorizontal = 0x00000020,
+ HatchStyleDashedVertical = 0x00000021,
+ HatchStyleSmallConfetti = 0x00000022,
+ HatchStyleLargeConfetti = 0x00000023,
+ HatchStyleZigZag = 0x00000024,
+ HatchStyleWave = 0x00000025,
+ HatchStyleDiagonalBrick = 0x00000026,
+ HatchStyleHorizontalBrick = 0x00000027,
+ HatchStyleWeave = 0x00000028,
+ HatchStylePlaid = 0x00000029,
+ HatchStyleDivot = 0x0000002A,
+ HatchStyleDottedGrid = 0x0000002B,
+ HatchStyleDottedDiamond = 0x0000002C,
+ HatchStyleShingle = 0x0000002D,
+ HatchStyleTrellis = 0x0000002E,
+ HatchStyleSphere = 0x0000002F,
+ HatchStyleSmallGrid = 0x00000030,
+ HatchStyleSmallCheckerBoard = 0x00000031,
+ HatchStyleLargeCheckerBoard = 0x00000032,
+ HatchStyleOutlinedDiamond = 0x00000033,
+ HatchStyleSolidDiamond = 0x00000034
+ };
+
+ enum EmfPlusBrushType
+ {
+ BrushTypeSolidColor = 0x00000000,
+ BrushTypeHatchFill = 0x00000001,
+ BrushTypeTextureFill = 0x00000002,
+ BrushTypePathGradient = 0x00000003,
+ BrushTypeLinearGradient = 0x00000004
+ };
+
+ class EMFPPath;
+
+ struct EMFPBrush : public EMFPObject
+ {
+ ::Color solidColor;
+ sal_uInt32 type;
+ sal_uInt32 additionalFlags;
+
+ /* linear gradient */
+ sal_Int32 wrapMode;
+ float firstPointX, firstPointY, aWidth, aHeight;
+ ::Color secondColor; // first color is stored in solidColor;
+ basegfx::B2DHomMatrix brush_transformation;
+ bool hasTransformation;
+ sal_uInt32 blendPoints;
+ std::unique_ptr<float[]> blendPositions;
+ float* blendFactors;
+ sal_uInt32 colorblendPoints;
+ std::unique_ptr<float[]> colorblendPositions;
+ std::unique_ptr<::Color[]> colorblendColors;
+ sal_uInt32 surroundColorsNumber;
+ std::unique_ptr<::Color[]> surroundColors;
+ std::unique_ptr<EMFPPath> path;
+ EmfPlusHatchStyle hatchStyle;
+
+ EMFPBrush();
+ virtual ~EMFPBrush() override;
+
+ sal_uInt32 GetType() const { return type; }
+ const ::Color& GetColor() const { return solidColor; }
+
+ void Read(SvStream& s, EmfPlusHelperData const & rR);
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfpcustomlinecap.cxx b/drawinglayer/source/tools/emfpcustomlinecap.cxx
new file mode 100644
index 0000000000..e457a36cc2
--- /dev/null
+++ b/drawinglayer/source/tools/emfpcustomlinecap.cxx
@@ -0,0 +1,121 @@
+/* -*- 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 <sal/log.hxx>
+#include "emfpcustomlinecap.hxx"
+#include "emfppath.hxx"
+#include "emfppen.hxx"
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::basegfx;
+
+namespace emfplushelper
+{
+ const sal_uInt32 EmfPlusCustomLineCapDataTypeDefault = 0x00000000;
+ const sal_uInt32 EmfPlusCustomLineCapDataTypeAdjustableArrow = 0x00000001;
+ const sal_uInt32 EmfPlusCustomLineCapDataFillPath = 0x00000001;
+ const sal_uInt32 EmfPlusCustomLineCapDataLinePath = 0x00000002;
+
+ EMFPCustomLineCap::EMFPCustomLineCap()
+ : EMFPObject()
+ , type(0)
+ , strokeStartCap(0)
+ , strokeEndCap(0)
+ , strokeJoin(0)
+ , miterLimit(0.0)
+ , widthScale(0.0)
+ , mbIsFilled(false)
+ {
+ }
+
+ void EMFPCustomLineCap::ReadPath(SvStream& s, EmfPlusHelperData const & rR, bool bFill)
+ {
+ sal_Int32 pathLength;
+ s.ReadInt32(pathLength);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tpath length: " << pathLength);
+ sal_uInt32 pathHeader;
+ sal_Int32 pathPoints, pathFlags;
+ s.ReadUInt32(pathHeader).ReadInt32(pathPoints).ReadInt32(pathFlags);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tpath (custom cap line path)");
+ SAL_INFO("drawinglayer.emf", "EMF+\t\theader: 0x" << std::hex << pathHeader << " points: " << std::dec << pathPoints << " additional flags: 0x" << std::hex << pathFlags << std::dec);
+
+ EMFPPath path(pathPoints);
+ path.Read(s, pathFlags);
+ polygon = path.GetPolygon(rR, false);
+ // rotate polygon by 180 degrees
+ polygon.transform(basegfx::utils::createRotateB2DHomMatrix(M_PI));
+ mbIsFilled = bFill;
+ }
+
+ void EMFPCustomLineCap::Read(SvStream& s, EmfPlusHelperData const & rR)
+ {
+ sal_uInt32 header;
+ s.ReadUInt32(header).ReadUInt32(type);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tcustom cap");
+ SAL_INFO("drawinglayer.emf", "EMF+\t\theader: 0x" << std::hex << header << " type: " << type << std::dec);
+
+ if (type == EmfPlusCustomLineCapDataTypeDefault)
+ {
+ sal_uInt32 customLineCapDataFlags, baseCap;
+ float baseInset;
+ float fillHotSpotX, fillHotSpotY, strokeHotSpotX, strokeHotSpotY;
+
+ s.ReadUInt32(customLineCapDataFlags).ReadUInt32(baseCap).ReadFloat(baseInset)
+ .ReadUInt32(strokeStartCap).ReadUInt32(strokeEndCap).ReadUInt32(strokeJoin)
+ .ReadFloat(miterLimit).ReadFloat(widthScale)
+ .ReadFloat(fillHotSpotX).ReadFloat(fillHotSpotY).ReadFloat(strokeHotSpotX).ReadFloat(strokeHotSpotY);
+
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tcustomLineCapDataFlags: 0x" << std::hex << customLineCapDataFlags);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tbaseCap: 0x" << std::hex << baseCap);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tbaseInset: " << baseInset);
+
+ if (customLineCapDataFlags & EmfPlusCustomLineCapDataFillPath)
+ {
+ ReadPath(s, rR, true);
+ }
+
+ if (customLineCapDataFlags & EmfPlusCustomLineCapDataLinePath)
+ {
+ ReadPath(s, rR, false);
+ }
+ }
+ else if (type == EmfPlusCustomLineCapDataTypeAdjustableArrow)
+ {
+ // TODO only reads the data, does not use them [I've had
+ // no test document to be able to implement it]
+
+ sal_Int32 fillState;
+ float width, height, middleInset, unusedHotSpot;
+
+ s.ReadFloat(width).ReadFloat(height).ReadFloat(middleInset).ReadInt32(fillState).ReadUInt32(strokeStartCap)
+ .ReadUInt32(strokeEndCap).ReadUInt32(strokeJoin).ReadFloat(miterLimit).ReadFloat(widthScale)
+ .ReadFloat(unusedHotSpot).ReadFloat(unusedHotSpot).ReadFloat(unusedHotSpot).ReadFloat(unusedHotSpot);
+
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tTODO - actually read EmfPlusCustomLineCapArrowData object (section 2.2.2.12)");
+ }
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tstrokeStartCap: 0x" << std::hex << strokeStartCap);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tstrokeEndCap: 0x" << std::hex << strokeEndCap);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tstrokeJoin: 0x" << std::hex << strokeJoin);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tmiterLimit: " << miterLimit);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\twidthScale: " << widthScale);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfpcustomlinecap.hxx b/drawinglayer/source/tools/emfpcustomlinecap.hxx
new file mode 100644
index 0000000000..22ed6be6dd
--- /dev/null
+++ b/drawinglayer/source/tools/emfpcustomlinecap.hxx
@@ -0,0 +1,41 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include "emfphelperdata.hxx"
+
+namespace emfplushelper
+{
+ struct EMFPCustomLineCap : public EMFPObject
+ {
+ sal_uInt32 type;
+ sal_uInt32 strokeStartCap, strokeEndCap, strokeJoin;
+ float miterLimit, widthScale;
+ basegfx::B2DPolyPolygon polygon;
+ bool mbIsFilled;
+
+ EMFPCustomLineCap();
+
+ void ReadPath(SvStream& s, EmfPlusHelperData const & rR, bool bFill);
+ void Read(SvStream& s, EmfPlusHelperData const & rR);
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfpfont.cxx b/drawinglayer/source/tools/emfpfont.cxx
new file mode 100644
index 0000000000..3fd6537e75
--- /dev/null
+++ b/drawinglayer/source/tools/emfpfont.cxx
@@ -0,0 +1,76 @@
+/* -*- 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 <sal/log.hxx>
+#include <rtl/ustrbuf.hxx>
+#include "emfpfont.hxx"
+
+namespace emfplushelper
+{
+ static OUString FontStyleToString(sal_uInt32 style)
+ {
+ OUStringBuffer sStyle;
+
+ if (style & FontStyleBold)
+ sStyle = "\n\t\t\tFontStyleBold";
+
+ if (style & FontStyleItalic)
+ sStyle.append("\n\t\t\tFontStyleItalic");
+
+ if (style & FontStyleUnderline)
+ sStyle.append("\n\t\t\tFontStyleUnderline");
+
+ if (style & FontStyleStrikeout)
+ sStyle.append("\n\t\t\tFontStyleStrikeout");
+
+ return sStyle.makeStringAndClear();
+ }
+
+ void EMFPFont::Read(SvMemoryStream &s)
+ {
+ sal_uInt32 header;
+ sal_uInt32 reserved;
+ sal_uInt32 length;
+ s.ReadUInt32(header).ReadFloat(emSize).ReadUInt32(sizeUnit).ReadInt32(fontFlags).ReadUInt32(reserved).ReadUInt32(length);
+ SAL_WARN_IF((header >> 12) != 0xdbc01, "drawinglayer.emf", "Invalid header - not 0xdbc01");
+ SAL_INFO("drawinglayer.emf", "EMF+\tHeader: 0x" << std::hex << (header >> 12));
+ SAL_INFO("drawinglayer.emf", "EMF+\tVersion: 0x" << (header & 0x1fff));
+ SAL_INFO("drawinglayer.emf", "EMF+\tSize: " << std::dec << emSize);
+ SAL_INFO("drawinglayer.emf", "EMF+\tUnit: " << UnitTypeToString(sizeUnit) << " (0x" << std::hex << sizeUnit << ")" << std::dec);
+ SAL_INFO("drawinglayer.emf", "EMF+\tFlags: " << FontStyleToString(fontFlags) << " (0x" << std::hex << fontFlags << ")");
+ SAL_INFO("drawinglayer.emf", "EMF+\tReserved: 0x" << reserved << std::dec);
+ SAL_INFO("drawinglayer.emf", "EMF+\tLength: " << length);
+
+ if (length <= 0 || length >= 0x4000)
+ return;
+
+ rtl_uString *pStr = rtl_uString_alloc(length);
+ sal_Unicode *chars = pStr->buffer;
+
+ for (sal_uInt32 i = 0; i < length; ++i)
+ {
+ s.ReadUtf16(chars[i]);
+ }
+
+ family = OUString(pStr, SAL_NO_ACQUIRE);
+ SAL_INFO("drawinglayer.emf", "EMF+\tFamily: " << family);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfpfont.hxx b/drawinglayer/source/tools/emfpfont.hxx
new file mode 100644
index 0000000000..3f9e241668
--- /dev/null
+++ b/drawinglayer/source/tools/emfpfont.hxx
@@ -0,0 +1,48 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include "emfphelperdata.hxx"
+
+namespace emfplushelper
+{
+ const sal_uInt32 FontStyleBold = 0x00000001;
+ const sal_uInt32 FontStyleItalic = 0x00000002;
+ const sal_uInt32 FontStyleUnderline = 0x00000004;
+ const sal_uInt32 FontStyleStrikeout = 0x00000008;
+
+
+ struct EMFPFont : public EMFPObject
+ {
+ float emSize;
+ sal_uInt32 sizeUnit;
+ sal_Int32 fontFlags;
+ OUString family;
+
+ void Read(SvMemoryStream &s);
+
+ bool Bold() const { return fontFlags & FontStyleBold; }
+ bool Italic() const { return fontFlags & FontStyleItalic; }
+ bool Underline() const { return fontFlags & FontStyleUnderline; }
+ bool Strikeout() const { return fontFlags & FontStyleStrikeout; }
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfphelperdata.cxx b/drawinglayer/source/tools/emfphelperdata.cxx
new file mode 100644
index 0000000000..26b7563fec
--- /dev/null
+++ b/drawinglayer/source/tools/emfphelperdata.cxx
@@ -0,0 +1,2292 @@
+/* -*- 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 "emfpcustomlinecap.hxx"
+#include "emfphelperdata.hxx"
+#include "emfpbrush.hxx"
+#include "emfppen.hxx"
+#include "emfppath.hxx"
+#include "emfpregion.hxx"
+#include "emfpimage.hxx"
+#include "emfpimageattributes.hxx"
+#include "emfpfont.hxx"
+#include "emfpstringformat.hxx"
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <wmfemfhelper.hxx>
+#include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
+#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
+#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
+#include <drawinglayer/primitive2d/textprimitive2d.hxx>
+#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
+#include <drawinglayer/primitive2d/metafileprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/attribute/fontattribute.hxx>
+#include <basegfx/color/bcolor.hxx>
+#include <basegfx/color/bcolormodifier.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dpolygonclipper.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <sal/log.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+
+#include <algorithm>
+
+namespace emfplushelper
+{
+
+ enum
+ {
+ WrapModeTile = 0x00000000,
+ WrapModeTileFlipX = 0x00000001,
+ WrapModeTileFlipY = 0x00000002,
+ WrapModeTileFlipXY = 0x00000003,
+ WrapModeClamp = 0x00000004
+ };
+
+ const char* emfTypeToName(sal_uInt16 type)
+ {
+ switch (type)
+ {
+ case EmfPlusRecordTypeHeader: return "EmfPlusRecordTypeHeader";
+ case EmfPlusRecordTypeEndOfFile: return "EmfPlusRecordTypeEndOfFile";
+ case EmfPlusRecordTypeComment: return "EmfPlusRecordTypeComment";
+ case EmfPlusRecordTypeGetDC: return "EmfPlusRecordTypeGetDC";
+ case EmfPlusRecordTypeObject: return "EmfPlusRecordTypeObject";
+ case EmfPlusRecordTypeFillRects: return "EmfPlusRecordTypeFillRects";
+ case EmfPlusRecordTypeDrawRects: return "EmfPlusRecordTypeDrawRects";
+ case EmfPlusRecordTypeFillPolygon: return "EmfPlusRecordTypeFillPolygon";
+ case EmfPlusRecordTypeDrawLines: return "EmfPlusRecordTypeDrawLines";
+ case EmfPlusRecordTypeFillClosedCurve: return "EmfPlusRecordTypeFillClosedCurve";
+ case EmfPlusRecordTypeDrawClosedCurve: return "EmfPlusRecordTypeDrawClosedCurve";
+ case EmfPlusRecordTypeDrawCurve: return "EmfPlusRecordTypeDrawCurve";
+ case EmfPlusRecordTypeFillEllipse: return "EmfPlusRecordTypeFillEllipse";
+ case EmfPlusRecordTypeDrawEllipse: return "EmfPlusRecordTypeDrawEllipse";
+ case EmfPlusRecordTypeFillPie: return "EmfPlusRecordTypeFillPie";
+ case EmfPlusRecordTypeDrawPie: return "EmfPlusRecordTypeDrawPie";
+ case EmfPlusRecordTypeDrawArc: return "EmfPlusRecordTypeDrawArc";
+ case EmfPlusRecordTypeFillRegion: return "EmfPlusRecordTypeFillRegion";
+ case EmfPlusRecordTypeFillPath: return "EmfPlusRecordTypeFillPath";
+ case EmfPlusRecordTypeDrawPath: return "EmfPlusRecordTypeDrawPath";
+ case EmfPlusRecordTypeDrawBeziers: return "EmfPlusRecordTypeDrawBeziers";
+ case EmfPlusRecordTypeDrawImage: return "EmfPlusRecordTypeDrawImage";
+ case EmfPlusRecordTypeDrawImagePoints: return "EmfPlusRecordTypeDrawImagePoints";
+ case EmfPlusRecordTypeDrawString: return "EmfPlusRecordTypeDrawString";
+ case EmfPlusRecordTypeSetRenderingOrigin: return "EmfPlusRecordTypeSetRenderingOrigin";
+ case EmfPlusRecordTypeSetAntiAliasMode: return "EmfPlusRecordTypeSetAntiAliasMode";
+ case EmfPlusRecordTypeSetTextRenderingHint: return "EmfPlusRecordTypeSetTextRenderingHint";
+ case EmfPlusRecordTypeSetTextContrast: return "EmfPlusRecordTypeSetTextContrast";
+ case EmfPlusRecordTypeSetInterpolationMode: return "EmfPlusRecordTypeSetInterpolationMode";
+ case EmfPlusRecordTypeSetPixelOffsetMode: return "EmfPlusRecordTypeSetPixelOffsetMode";
+ case EmfPlusRecordTypeSetCompositingQuality: return "EmfPlusRecordTypeSetCompositingQuality";
+ case EmfPlusRecordTypeSave: return "EmfPlusRecordTypeSave";
+ case EmfPlusRecordTypeRestore: return "EmfPlusRecordTypeRestore";
+ case EmfPlusRecordTypeBeginContainer: return "EmfPlusRecordTypeBeginContainer";
+ case EmfPlusRecordTypeBeginContainerNoParams: return "EmfPlusRecordTypeBeginContainerNoParams";
+ case EmfPlusRecordTypeEndContainer: return "EmfPlusRecordTypeEndContainer";
+ case EmfPlusRecordTypeSetWorldTransform: return "EmfPlusRecordTypeSetWorldTransform";
+ case EmfPlusRecordTypeResetWorldTransform: return "EmfPlusRecordTypeResetWorldTransform";
+ case EmfPlusRecordTypeMultiplyWorldTransform: return "EmfPlusRecordTypeMultiplyWorldTransform";
+ case EmfPlusRecordTypeTranslateWorldTransform: return "EmfPlusRecordTypeTranslateWorldTransform";
+ case EmfPlusRecordTypeScaleWorldTransform: return "EmfPlusRecordTypeScaleWorldTransform";
+ case EmfPlusRecordTypeSetPageTransform: return "EmfPlusRecordTypeSetPageTransform";
+ case EmfPlusRecordTypeResetClip: return "EmfPlusRecordTypeResetClip";
+ case EmfPlusRecordTypeSetClipRect: return "EmfPlusRecordTypeSetClipRect";
+ case EmfPlusRecordTypeSetClipPath: return "EmfPlusRecordTypeSetClipPath";
+ case EmfPlusRecordTypeSetClipRegion: return "EmfPlusRecordTypeSetClipRegion";
+ case EmfPlusRecordTypeOffsetClip: return "EmfPlusRecordTypeOffsetClip";
+ case EmfPlusRecordTypeDrawDriverString: return "EmfPlusRecordTypeDrawDriverString";
+ }
+ return "";
+ }
+
+ static OUString emfObjectToName(sal_uInt16 type)
+ {
+ switch (type)
+ {
+ case EmfPlusObjectTypeBrush: return "EmfPlusObjectTypeBrush";
+ case EmfPlusObjectTypePen: return "EmfPlusObjectTypePen";
+ case EmfPlusObjectTypePath: return "EmfPlusObjectTypePath";
+ case EmfPlusObjectTypeRegion: return "EmfPlusObjectTypeRegion";
+ case EmfPlusObjectTypeImage: return "EmfPlusObjectTypeImage";
+ case EmfPlusObjectTypeFont: return "EmfPlusObjectTypeFont";
+ case EmfPlusObjectTypeStringFormat: return "EmfPlusObjectTypeStringFormat";
+ case EmfPlusObjectTypeImageAttributes: return "EmfPlusObjectTypeImageAttributes";
+ case EmfPlusObjectTypeCustomLineCap: return "EmfPlusObjectTypeCustomLineCap";
+ }
+ return "";
+ }
+
+ static OUString PixelOffsetModeToString(sal_uInt16 nPixelOffset)
+ {
+ switch (nPixelOffset)
+ {
+ case PixelOffsetMode::PixelOffsetModeDefault: return "PixelOffsetModeDefault";
+ case PixelOffsetMode::PixelOffsetModeHighSpeed: return "PixelOffsetModeHighSpeed";
+ case PixelOffsetMode::PixelOffsetModeHighQuality: return "PixelOffsetModeHighQuality";
+ case PixelOffsetMode::PixelOffsetModeNone: return "PixelOffsetModeNone";
+ case PixelOffsetMode::PixelOffsetModeHalf: return "PixelOffsetModeHalf";
+ }
+ return "";
+ }
+
+ static OUString SmoothingModeToString(sal_uInt16 nSmoothMode)
+ {
+ switch (nSmoothMode)
+ {
+ case SmoothingMode::SmoothingModeDefault: return "SmoothingModeDefault";
+ case SmoothingMode::SmoothingModeHighSpeed: return "SmoothModeHighSpeed";
+ case SmoothingMode::SmoothingModeHighQuality: return "SmoothingModeHighQuality";
+ case SmoothingMode::SmoothingModeNone: return "SmoothingModeNone";
+ case SmoothingMode::SmoothingModeAntiAlias8x4: return "SmoothingModeAntiAlias8x4";
+ case SmoothingMode::SmoothingModeAntiAlias8x8: return "SmoothingModeAntiAlias8x8";
+ }
+ return "";
+ }
+
+ static OUString TextRenderingHintToString(sal_uInt16 nHint)
+ {
+ switch (nHint)
+ {
+ case TextRenderingHint::TextRenderingHintSystemDefault: return "TextRenderingHintSystemDefault";
+ case TextRenderingHint::TextRenderingHintSingleBitPerPixelGridFit: return "TextRenderingHintSingleBitPerPixelGridFit";
+ case TextRenderingHint::TextRenderingHintSingleBitPerPixel: return "TextRenderingHintSingleBitPerPixel";
+ case TextRenderingHint::TextRenderingHintAntialiasGridFit: return "TextRenderingHintAntialiasGridFit";
+ case TextRenderingHint::TextRenderingHintAntialias: return "TextRenderingHintAntialias";
+ case TextRenderingHint::TextRenderingHintClearTypeGridFit: return "TextRenderingHintClearTypeGridFit";
+ }
+ return "";
+ }
+
+ static OUString InterpolationModeToString(sal_uInt16 nMode)
+ {
+ switch (nMode)
+ {
+ case InterpolationMode::InterpolationModeDefault: return "InterpolationModeDefault";
+ case InterpolationMode::InterpolationModeLowQuality: return "InterpolationModeLowQuality";
+ case InterpolationMode::InterpolationModeHighQuality: return "InterpolationModeHighQuality";
+ case InterpolationMode::InterpolationModeBilinear: return "InterpolationModeBilinear";
+ case InterpolationMode::InterpolationModeBicubic: return "InterpolationModeBicubic";
+ case InterpolationMode::InterpolationModeNearestNeighbor: return "InterpolationModeNearestNeighbor";
+ case InterpolationMode::InterpolationModeHighQualityBilinear: return "InterpolationModeHighQualityBilinear";
+ case InterpolationMode::InterpolationModeHighQualityBicubic: return "InterpolationModeHighQualityBicubic";
+ }
+ return "";
+ }
+
+ OUString UnitTypeToString(sal_uInt16 nType)
+ {
+ switch (nType)
+ {
+ case UnitTypeWorld: return "UnitTypeWorld";
+ case UnitTypeDisplay: return "UnitTypeDisplay";
+ case UnitTypePixel: return "UnitTypePixel";
+ case UnitTypePoint: return "UnitTypePoint";
+ case UnitTypeInch: return "UnitTypeInch";
+ case UnitTypeDocument: return "UnitTypeDocument";
+ case UnitTypeMillimeter: return "UnitTypeMillimeter";
+ }
+ return "";
+ }
+
+ static bool IsBrush(sal_uInt16 flags)
+ {
+ return (!((flags >> 15) & 0x0001));
+ }
+
+ static OUString BrushIDToString(sal_uInt16 flags, sal_uInt32 brushid)
+ {
+ if (IsBrush(flags))
+ return "EmfPlusBrush ID: " + OUString::number(brushid);
+ else
+ return "ARGB: 0x" + OUString::number(brushid, 16);
+ }
+
+ EMFPObject::~EMFPObject()
+ {
+ }
+
+ float EmfPlusHelperData::getUnitToPixelMultiplier(const UnitType aUnitType, const sal_uInt32 aDPI)
+ {
+ switch (aUnitType)
+ {
+ case UnitTypePixel:
+ return 1.0f;
+
+ case UnitTypePoint:
+ return aDPI / 72.0;
+
+ case UnitTypeInch:
+ return aDPI;
+
+ case UnitTypeMillimeter:
+ return aDPI / 25.4;
+
+ case UnitTypeDocument:
+ return aDPI / 300.0;
+
+ case UnitTypeWorld:
+ case UnitTypeDisplay:
+ SAL_WARN("drawinglayer.emf", "EMF+\t Converting to World/Display.");
+ return 1.0f;
+
+ default:
+ SAL_WARN("drawinglayer.emf", "EMF+\tTODO Unimplemented support of Unit Type: 0x" << std::hex << aUnitType);
+ return 1.0f;
+ }
+ }
+
+ void EmfPlusHelperData::processObjectRecord(SvMemoryStream& rObjectStream, sal_uInt16 flags, sal_uInt32 dataSize, bool bUseWholeStream)
+ {
+ sal_uInt16 objecttype = flags & 0x7f00;
+ sal_uInt16 index = flags & 0xff;
+ SAL_INFO("drawinglayer.emf", "EMF+ Object: " << emfObjectToName(objecttype) << " (0x" << objecttype << ")");
+ SAL_INFO("drawinglayer.emf", "EMF+\tObject slot: " << index);
+ SAL_INFO("drawinglayer.emf", "EMF+\tFlags: " << (flags & 0xff00));
+
+ switch (objecttype)
+ {
+ case EmfPlusObjectTypeBrush:
+ {
+ EMFPBrush *brush = new EMFPBrush();
+ maEMFPObjects[index].reset(brush);
+ brush->Read(rObjectStream, *this);
+ break;
+ }
+ case EmfPlusObjectTypePen:
+ {
+ EMFPPen *pen = new EMFPPen();
+ maEMFPObjects[index].reset(pen);
+ pen->Read(rObjectStream, *this);
+ pen->penWidth = pen->penWidth * getUnitToPixelMultiplier(static_cast<UnitType>(pen->penUnit), mnHDPI);
+ break;
+ }
+ case EmfPlusObjectTypePath:
+ {
+ sal_uInt32 aVersion, aPathPointCount, aPathPointFlags;
+
+ rObjectStream.ReadUInt32(aVersion).ReadUInt32(aPathPointCount).ReadUInt32(aPathPointFlags);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tVersion: 0x" << std::hex << aVersion);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tNumber of points: " << std::dec << aPathPointCount);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tPath point flags: 0x" << std::hex << aPathPointFlags << std::dec);
+ EMFPPath *path = new EMFPPath(aPathPointCount);
+ maEMFPObjects[index].reset(path);
+ path->Read(rObjectStream, aPathPointFlags);
+ break;
+ }
+ case EmfPlusObjectTypeRegion:
+ {
+ EMFPRegion *region = new EMFPRegion();
+ maEMFPObjects[index].reset(region);
+ region->ReadRegion(rObjectStream, *this);
+ break;
+ }
+ case EmfPlusObjectTypeImage:
+ {
+ EMFPImage *image = new EMFPImage;
+ maEMFPObjects[index].reset(image);
+ image->type = 0;
+ image->width = 0;
+ image->height = 0;
+ image->stride = 0;
+ image->pixelFormat = 0;
+ image->Read(rObjectStream, dataSize, bUseWholeStream);
+ break;
+ }
+ case EmfPlusObjectTypeFont:
+ {
+ EMFPFont *font = new EMFPFont;
+ maEMFPObjects[index].reset(font);
+ font->emSize = 0;
+ font->sizeUnit = 0;
+ font->fontFlags = 0;
+ font->Read(rObjectStream);
+ // tdf#113624 Convert unit to Pixels
+ font->emSize = font->emSize * getUnitToPixelMultiplier(static_cast<UnitType>(font->sizeUnit), mnHDPI);
+
+ break;
+ }
+ case EmfPlusObjectTypeStringFormat:
+ {
+ EMFPStringFormat *stringFormat = new EMFPStringFormat();
+ maEMFPObjects[index].reset(stringFormat);
+ stringFormat->Read(rObjectStream);
+ break;
+ }
+ case EmfPlusObjectTypeImageAttributes:
+ {
+ EMFPImageAttributes *imageAttributes = new EMFPImageAttributes();
+ maEMFPObjects[index].reset(imageAttributes);
+ imageAttributes->Read(rObjectStream);
+ break;
+ }
+ case EmfPlusObjectTypeCustomLineCap:
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+\t TODO Object type 'custom line cap' not yet implemented");
+ break;
+ }
+ default:
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+\t TODO Object unhandled flags: 0x" << std::hex << (flags & 0xff00) << std::dec);
+ }
+ }
+ }
+
+ void EmfPlusHelperData::ReadPoint(SvStream& s, float& x, float& y, sal_uInt32 flags)
+ {
+ if (flags & 0x800)
+ {
+ // specifies a location in the coordinate space that is relative to
+ // the location specified by the previous element in the array. In the case of the first element in
+ // PointData, a previous location at coordinates (0,0) is assumed.
+ SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO Relative coordinates bit detected. Implement parse EMFPlusPointR");
+ }
+
+ if (flags & 0x4000)
+ {
+ sal_Int16 ix, iy;
+
+ s.ReadInt16(ix).ReadInt16(iy);
+
+ x = ix;
+ y = iy;
+ }
+ else
+ {
+ s.ReadFloat(x).ReadFloat(y);
+ }
+ }
+
+ void EmfPlusHelperData::ReadRectangle(SvStream& s, float& x, float& y, float &width, float& height, bool bCompressed)
+ {
+ if (bCompressed)
+ {
+ sal_Int16 ix, iy, iw, ih;
+
+ s.ReadInt16(ix).ReadInt16(iy).ReadInt16(iw).ReadInt16(ih);
+
+ x = ix;
+ y = iy;
+ width = iw;
+ height = ih;
+ }
+ else
+ {
+ s.ReadFloat(x).ReadFloat(y).ReadFloat(width).ReadFloat(height);
+ }
+ }
+
+ bool EmfPlusHelperData::readXForm(SvStream& rIn, basegfx::B2DHomMatrix& rTarget)
+ {
+ rTarget.identity();
+
+ if (sizeof(float) != 4)
+ {
+ OSL_FAIL("EnhWMFReader::sizeof( float ) != 4");
+ return false;
+ }
+ else
+ {
+ float eM11(0.0);
+ float eM12(0.0);
+ float eM21(0.0);
+ float eM22(0.0);
+ float eDx(0.0);
+ float eDy(0.0);
+ rIn.ReadFloat(eM11).ReadFloat(eM12).ReadFloat(eM21).ReadFloat(eM22).ReadFloat(eDx).ReadFloat(eDy);
+ rTarget = basegfx::B2DHomMatrix(
+ eM11, eM21, eDx,
+ eM12, eM22, eDy);
+ }
+
+ return true;
+ }
+
+ void EmfPlusHelperData::mappingChanged()
+ {
+ if (mnPixX == 0 || mnPixY == 0)
+ {
+ SAL_WARN("drawinglayer.emf", "dimensions in pixels is 0");
+ return;
+ }
+ // Call when mnMmX/mnMmY/mnPixX/mnPixY/mnFrameLeft/mnFrameTop/maWorldTransform/ changes.
+ // Currently not used are mnHDPI/mnVDPI/mnFrameRight/mnFrameBottom. *If* these should
+ // be used in the future, this method will need to be called.
+ //
+ // Re-calculate maMapTransform to contain the complete former transformation so that
+ // it can be applied by a single matrix multiplication or be added to an encapsulated
+ // primitive later
+ //
+ // To evtl. correct and see where this came from, please compare with the implementations
+ // of EmfPlusHelperData::MapToDevice and EmfPlusHelperData::Map* in prev versions
+ maMapTransform = maWorldTransform;
+ maMapTransform *= basegfx::utils::createScaleTranslateB2DHomMatrix(100.0 * mnMmX / mnPixX, 100.0 * mnMmY / mnPixY,
+ double(-mnFrameLeft), double(-mnFrameTop));
+ maMapTransform *= maBaseTransform;
+
+ // Used only for performance optimization, to do not calculate it every line draw
+ mdExtractedXScale = std::hypot(maMapTransform.a(), maMapTransform.b());
+ mdExtractedYScale = std::hypot(maMapTransform.c(), maMapTransform.d());
+ }
+
+ ::basegfx::B2DPoint EmfPlusHelperData::Map(double ix, double iy) const
+ {
+ // map in one step using complete MapTransform (see mappingChanged)
+ return maMapTransform * ::basegfx::B2DPoint(ix, iy);
+ }
+
+ Color EmfPlusHelperData::EMFPGetBrushColorOrARGBColor(const sal_uInt16 flags, const sal_uInt32 brushIndexOrColor) const {
+ Color color;
+ if (flags & 0x8000) // we use a color
+ {
+ color = Color(ColorAlpha, (brushIndexOrColor >> 24), (brushIndexOrColor >> 16) & 0xff,
+ (brushIndexOrColor >> 8) & 0xff, brushIndexOrColor & 0xff);
+ }
+ else // we use a brush
+ {
+ const EMFPBrush* brush = dynamic_cast<EMFPBrush*>(maEMFPObjects[brushIndexOrColor & 0xff].get());
+ if (brush)
+ {
+ color = brush->GetColor();
+ if (brush->type != BrushTypeSolidColor)
+ SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO Brush other than solid color is not supported");
+ }
+ }
+ return color;
+ }
+
+ void EmfPlusHelperData::GraphicStatePush(GraphicStateMap& map, sal_Int32 index)
+ {
+ GraphicStateMap::iterator iter = map.find( index );
+
+ if ( iter != map.end() )
+ {
+ map.erase( iter );
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tStack index: " << index << " found and erased");
+ }
+
+ wmfemfhelper::PropertyHolder state = mrPropertyHolders.Current();
+ // tdf#112500 We need to save world transform somehow, during graphic state push
+ state.setTransformation(maWorldTransform);
+ map[ index ] = state;
+ }
+
+ void EmfPlusHelperData::GraphicStatePop(GraphicStateMap& map, sal_Int32 index)
+ {
+ GraphicStateMap::iterator iter = map.find(index);
+
+ if (iter != map.end())
+ {
+ wmfemfhelper::PropertyHolder state = iter->second;
+
+ maWorldTransform = state.getTransformation();
+ if (state.getClipPolyPolygonActive())
+ {
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t Restore clipping region to saved in index: " << index);
+ wmfemfhelper::HandleNewClipRegion(state.getClipPolyPolygon(), mrTargetHolders,
+ mrPropertyHolders);
+ }
+ else
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t Disable clipping");
+ wmfemfhelper::HandleNewClipRegion(::basegfx::B2DPolyPolygon(), mrTargetHolders,
+ mrPropertyHolders);
+ }
+ mappingChanged();
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t\tStack index: " << index
+ << " found, maWorldTransform: " << maWorldTransform);
+ }
+ }
+
+ drawinglayer::attribute::LineStartEndAttribute
+ EmfPlusHelperData::CreateLineEnd(const sal_Int32 aCap, const float aPenWidth) const
+ {
+ const double pw = mdExtractedYScale * aPenWidth;
+ if (aCap == LineCapTypeSquare)
+ {
+ basegfx::B2DPolygon aCapPolygon(
+ { {-1.0, -1.0}, {1.0, -1.0}, {1.0, 1.0}, {-1.0, 1.0} });
+ aCapPolygon.setClosed(true);
+ return drawinglayer::attribute::LineStartEndAttribute(
+ pw, basegfx::B2DPolyPolygon(aCapPolygon), true);
+ }
+ else if (aCap == LineCapTypeRound)
+ {
+ basegfx::B2DPolygon aCapPolygon(
+ { {-1.0, 1.0}, {1.0, 1.0}, {1.0, 0.0}, {0.9236, -0.3827},
+ {0.7071, -0.7071}, {0.3827, -0.9236}, {0.0, -1.0}, {-0.3827, -0.9236},
+ {-0.7071, -0.7071}, {-0.9236, -0.3827}, {-1.0, 0.0} });
+ aCapPolygon.setClosed(true);
+ return drawinglayer::attribute::LineStartEndAttribute(
+ pw, basegfx::B2DPolyPolygon(aCapPolygon), true);
+ }
+ else if (aCap == LineCapTypeTriangle)
+ {
+ basegfx::B2DPolygon aCapPolygon(
+ { {-1.0, 1.0}, {1.0, 1.0}, {1.0, 0.0}, {0.0, -1.0}, {-1.0, 0.0} });
+ aCapPolygon.setClosed(true);
+ return drawinglayer::attribute::LineStartEndAttribute(
+ pw, basegfx::B2DPolyPolygon(aCapPolygon), true);
+ }
+ else if (aCap == LineCapTypeSquareAnchor)
+ {
+ basegfx::B2DPolygon aCapPolygon(
+ { {-1.0, -1.0}, {1.0, -1.0}, {1.0, 1.0}, {-1.0, 1.0} });
+ aCapPolygon.setClosed(true);
+ return drawinglayer::attribute::LineStartEndAttribute(
+ 1.5 * pw, basegfx::B2DPolyPolygon(aCapPolygon), true);
+ }
+ else if (aCap == LineCapTypeRoundAnchor)
+ {
+ const basegfx::B2DPolygon aCapPolygon
+ = ::basegfx::utils::createPolygonFromEllipse(::basegfx::B2DPoint(0.0, 0.0), 1.0, 1.0);
+ return drawinglayer::attribute::LineStartEndAttribute(
+ 2.0 * pw, basegfx::B2DPolyPolygon(aCapPolygon), true);
+ }
+ else if (aCap == LineCapTypeDiamondAnchor)
+ {
+ basegfx::B2DPolygon aCapPolygon({ {0.0, -1.0}, {1.0, 0.0}, {0.5, 0.5},
+ {0.5, 1.0}, {-0.5, 1.0}, {-0.5, 0.5},
+ {-1.0, 0.0} });
+ aCapPolygon.setClosed(true);
+ return drawinglayer::attribute::LineStartEndAttribute(
+ 2.0 * pw, basegfx::B2DPolyPolygon(aCapPolygon), true);
+ }
+ else if (aCap == LineCapTypeArrowAnchor)
+ {
+ basegfx::B2DPolygon aCapPolygon({ {0.0, -1.0}, {1.0, 1.0}, {-1.0, 1.0} });
+ aCapPolygon.setClosed(true);
+ return drawinglayer::attribute::LineStartEndAttribute(
+ 2.0 * pw, basegfx::B2DPolyPolygon(aCapPolygon), true);
+ }
+ return drawinglayer::attribute::LineStartEndAttribute();
+ }
+
+ void EmfPlusHelperData::EMFPPlusDrawPolygon(const ::basegfx::B2DPolyPolygon& polygon,
+ sal_uInt32 penIndex)
+ {
+ const EMFPPen* pen = dynamic_cast<EMFPPen*>(maEMFPObjects[penIndex & 0xff].get());
+ SAL_WARN_IF(!pen, "drawinglayer.emf", "emf+ missing pen");
+
+ if (!(pen && polygon.count()))
+ return;
+
+ const double transformedPenWidth = mdExtractedYScale * pen->penWidth;
+ drawinglayer::attribute::LineAttribute lineAttribute(
+ pen->GetColor().getBColor(), transformedPenWidth, pen->maLineJoin,
+ css::drawing::LineCap_BUTT, //TODO implement PenDataDashedLineCap support here
+ pen->fMiterMinimumAngle);
+
+ drawinglayer::attribute::LineStartEndAttribute aStart;
+ if (pen->penDataFlags & EmfPlusPenDataStartCap)
+ {
+ if ((pen->penDataFlags & EmfPlusPenDataCustomStartCap)
+ && (pen->customStartCap->polygon.begin()->count() > 1))
+ aStart = drawinglayer::attribute::LineStartEndAttribute(
+ pen->customStartCap->polygon.getB2DRange().getRange().getX() * mdExtractedXScale
+ * pen->customStartCap->widthScale * pen->penWidth,
+ pen->customStartCap->polygon, false);
+ else
+ aStart = EmfPlusHelperData::CreateLineEnd(pen->startCap, pen->penWidth);
+ }
+
+ drawinglayer::attribute::LineStartEndAttribute aEnd;
+ if (pen->penDataFlags & EmfPlusPenDataEndCap)
+ {
+ if ((pen->penDataFlags & EmfPlusPenDataCustomEndCap)
+ && (pen->customEndCap->polygon.begin()->count() > 1))
+ aEnd = drawinglayer::attribute::LineStartEndAttribute(
+ pen->customEndCap->polygon.getB2DRange().getRange().getX() * mdExtractedXScale
+ * pen->customEndCap->widthScale * pen->penWidth,
+ pen->customEndCap->polygon, false);
+ else
+ aEnd = EmfPlusHelperData::CreateLineEnd(pen->endCap, pen->penWidth);
+ }
+
+ if (pen->GetColor().IsTransparent())
+ {
+ drawinglayer::primitive2d::Primitive2DContainer aContainer;
+ if (aStart.isDefault() && aEnd.isDefault())
+ aContainer.append(drawinglayer::primitive2d::Primitive2DReference(
+ new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
+ polygon, lineAttribute, pen->GetStrokeAttribute(mdExtractedXScale))));
+ else
+ {
+ aContainer.resize(polygon.count());
+ for (sal_uInt32 i = 0; i < polygon.count(); i++)
+ aContainer[i] = drawinglayer::primitive2d::Primitive2DReference(
+ new drawinglayer::primitive2d::PolygonStrokeArrowPrimitive2D(
+ polygon.getB2DPolygon(i), lineAttribute,
+ pen->GetStrokeAttribute(mdExtractedXScale), aStart, aEnd));
+ }
+ mrTargetHolders.Current().append(
+ new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
+ std::move(aContainer), (255 - pen->GetColor().GetAlpha()) / 255.0));
+ }
+ else
+ {
+ if (aStart.isDefault() && aEnd.isDefault())
+ mrTargetHolders.Current().append(
+ new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
+ polygon, lineAttribute, pen->GetStrokeAttribute(mdExtractedXScale)));
+ else
+ for (sal_uInt32 i = 0; i < polygon.count(); i++)
+ {
+ mrTargetHolders.Current().append(
+ new drawinglayer::primitive2d::PolygonStrokeArrowPrimitive2D(
+ polygon.getB2DPolygon(i), lineAttribute,
+ pen->GetStrokeAttribute(mdExtractedXScale), aStart, aEnd));
+ }
+ }
+ mrPropertyHolders.Current().setLineColor(pen->GetColor().getBColor());
+ mrPropertyHolders.Current().setLineColorActive(true);
+ mrPropertyHolders.Current().setFillColorActive(false);
+ }
+
+ void EmfPlusHelperData::EMFPPlusFillPolygonSolidColor(const ::basegfx::B2DPolyPolygon& polygon, Color const& color)
+ {
+ if (color.GetAlpha() == 0)
+ return;
+
+ if (!color.IsTransparent())
+ {
+ // not transparent
+ mrTargetHolders.Current().append(
+ new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
+ polygon,
+ color.getBColor()));
+ }
+ else
+ {
+ const drawinglayer::primitive2d::Primitive2DReference aPrimitive(
+ new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
+ polygon,
+ color.getBColor()));
+
+ mrTargetHolders.Current().append(
+ new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
+ drawinglayer::primitive2d::Primitive2DContainer { aPrimitive },
+ (255 - color.GetAlpha()) / 255.0));
+ }
+ }
+
+ void EmfPlusHelperData::EMFPPlusFillPolygon(const ::basegfx::B2DPolyPolygon& polygon, const bool isColor, const sal_uInt32 brushIndexOrColor)
+ {
+ if (!polygon.count())
+ return;
+
+ if (isColor) // use Color
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t Fill polygon, ARGB color: 0x" << std::hex << brushIndexOrColor << std::dec);
+
+ // EMF Alpha (1 byte): An 8-bit unsigned integer that specifies the transparency of the background,
+ // ranging from 0 for completely transparent to 0xFF for completely opaque.
+ const Color color(ColorAlpha, (brushIndexOrColor >> 24), (brushIndexOrColor >> 16) & 0xff, (brushIndexOrColor >> 8) & 0xff, brushIndexOrColor & 0xff);
+ EMFPPlusFillPolygonSolidColor(polygon, color);
+
+ mrPropertyHolders.Current().setFillColor(color.getBColor());
+ mrPropertyHolders.Current().setFillColorActive(true);
+ mrPropertyHolders.Current().setLineColorActive(false);
+ }
+ else // use Brush
+ {
+ EMFPBrush* brush = dynamic_cast<EMFPBrush*>(maEMFPObjects[brushIndexOrColor & 0xff].get());
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t Fill polygon, brush slot: " << brushIndexOrColor << " (brush type: " << (brush ? brush->GetType() : -1) << ")");
+
+ // give up in case something wrong happened
+ if( !brush )
+ return;
+
+ mrPropertyHolders.Current().setFillColorActive(false);
+ mrPropertyHolders.Current().setLineColorActive(false);
+
+ if (brush->type == BrushTypeSolidColor)
+ {
+ Color fillColor = brush->solidColor;
+ EMFPPlusFillPolygonSolidColor(polygon, fillColor);
+ }
+ else if (brush->type == BrushTypeHatchFill)
+ {
+ // EMF+ like hatching is currently not supported. These are just color blends which serve as an approximation for some of them
+ // for the others the hatch "background" color (secondColor in brush) is used.
+
+ bool isHatchBlend = true;
+ double blendFactor = 0.0;
+
+ switch (brush->hatchStyle)
+ {
+ case HatchStyle05Percent: blendFactor = 0.05; break;
+ case HatchStyle10Percent: blendFactor = 0.10; break;
+ case HatchStyle20Percent: blendFactor = 0.20; break;
+ case HatchStyle25Percent: blendFactor = 0.25; break;
+ case HatchStyle30Percent: blendFactor = 0.30; break;
+ case HatchStyle40Percent: blendFactor = 0.40; break;
+ case HatchStyle50Percent: blendFactor = 0.50; break;
+ case HatchStyle60Percent: blendFactor = 0.60; break;
+ case HatchStyle70Percent: blendFactor = 0.70; break;
+ case HatchStyle75Percent: blendFactor = 0.75; break;
+ case HatchStyle80Percent: blendFactor = 0.80; break;
+ case HatchStyle90Percent: blendFactor = 0.90; break;
+ default:
+ isHatchBlend = false;
+ break;
+ }
+ Color fillColor;
+ if (isHatchBlend)
+ {
+ fillColor = brush->solidColor;
+ fillColor.Merge(brush->secondColor, static_cast<sal_uInt8>(255 * blendFactor));
+ }
+ else
+ {
+ fillColor = brush->secondColor;
+ }
+ // temporal solution: create a solid colored polygon
+ // TODO create a 'real' hatching primitive
+ mrTargetHolders.Current().append(
+ new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
+ polygon,
+ fillColor.getBColor()));
+ }
+ else if (brush->type == BrushTypeTextureFill)
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+\tTODO: implement BrushTypeTextureFill brush");
+ }
+ else if (brush->type == BrushTypePathGradient || brush->type == BrushTypeLinearGradient)
+
+ {
+ if (brush->type == BrushTypePathGradient && !(brush->additionalFlags & 0x1))
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+\t TODO Implement displaying BrushTypePathGradient with Boundary: ");
+ }
+ ::basegfx::B2DHomMatrix aTextureTransformation;
+
+ if (brush->hasTransformation) {
+ aTextureTransformation = brush->brush_transformation;
+
+ // adjust aTextureTransformation for our world space:
+ // -> revert the mapping -> apply the transformation -> map back
+ basegfx::B2DHomMatrix aInvertedMapTrasform(maMapTransform);
+ aInvertedMapTrasform.invert();
+ aTextureTransformation = maMapTransform * aTextureTransformation * aInvertedMapTrasform;
+ }
+
+ // select the stored colors
+ const basegfx::BColor aStartColor = brush->solidColor.getBColor();
+ const basegfx::BColor aEndColor = brush->secondColor.getBColor();
+ drawinglayer::primitive2d::SvgGradientEntryVector aVector;
+
+ if (brush->blendPositions)
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tUse blend");
+
+ // store the blendpoints in the vector
+ for (sal_uInt32 i = 0; i < brush->blendPoints; i++)
+ {
+ const double aBlendPoint = brush->blendPositions[i];
+ basegfx::BColor aColor;
+ aColor.setGreen(aStartColor.getGreen() + brush->blendFactors[i] * (aEndColor.getGreen() - aStartColor.getGreen()));
+ aColor.setBlue (aStartColor.getBlue() + brush->blendFactors[i] * (aEndColor.getBlue() - aStartColor.getBlue()));
+ aColor.setRed (aStartColor.getRed() + brush->blendFactors[i] * (aEndColor.getRed() - aStartColor.getRed()));
+ const double aAlpha = brush->solidColor.GetAlpha() + brush->blendFactors[i] * (brush->secondColor.GetAlpha() - brush->solidColor.GetAlpha());
+ aVector.emplace_back(aBlendPoint, aColor, aAlpha / 255.0);
+ }
+ }
+ else if (brush->colorblendPositions)
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tUse color blend");
+
+ // store the colorBlends in the vector
+ for (sal_uInt32 i = 0; i < brush->colorblendPoints; i++)
+ {
+ const double aBlendPoint = brush->colorblendPositions[i];
+ const basegfx::BColor aColor = brush->colorblendColors[i].getBColor();
+ aVector.emplace_back(aBlendPoint, aColor, brush->colorblendColors[i].GetAlpha() / 255.0);
+ }
+ }
+ else // ok, no extra points: just start and end
+ {
+ aVector.emplace_back(0.0, aStartColor, brush->solidColor.GetAlpha() / 255.0);
+ aVector.emplace_back(1.0, aEndColor, brush->secondColor.GetAlpha() / 255.0);
+ }
+
+ // get the polygon range to be able to map the start/end/center point correctly
+ // therefore, create a mapping and invert it
+ basegfx::B2DRange aPolygonRange= polygon.getB2DRange();
+ basegfx::B2DHomMatrix aPolygonTransformation = basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aPolygonRange.getWidth(),aPolygonRange.getHeight(),
+ aPolygonRange.getMinX(), aPolygonRange.getMinY());
+ aPolygonTransformation.invert();
+
+ if (brush->type == BrushTypeLinearGradient)
+ {
+ // support for public enum EmfPlusWrapMode
+ basegfx::B2DPoint aStartPoint = Map(brush->firstPointX, 0.0);
+ aStartPoint = aPolygonTransformation * aStartPoint;
+ basegfx::B2DPoint aEndPoint = Map(brush->firstPointX + brush->aWidth, 0.0);
+ aEndPoint = aPolygonTransformation * aEndPoint;
+
+ // support for public enum EmfPlusWrapMode
+ drawinglayer::primitive2d::SpreadMethod aSpreadMethod(drawinglayer::primitive2d::SpreadMethod::Pad);
+ switch(brush->wrapMode)
+ {
+ case WrapModeTile:
+ case WrapModeTileFlipY:
+ {
+ aSpreadMethod = drawinglayer::primitive2d::SpreadMethod::Repeat;
+ break;
+ }
+ case WrapModeTileFlipX:
+ case WrapModeTileFlipXY:
+ {
+ aSpreadMethod = drawinglayer::primitive2d::SpreadMethod::Reflect;
+ break;
+ }
+ default:
+ break;
+ }
+
+ // create the same one used for SVG
+ mrTargetHolders.Current().append(
+ new drawinglayer::primitive2d::SvgLinearGradientPrimitive2D(
+ aTextureTransformation,
+ polygon,
+ std::move(aVector),
+ aStartPoint,
+ aEndPoint,
+ false, // do not use UnitCoordinates
+ aSpreadMethod));
+ }
+ else // BrushTypePathGradient
+ { // TODO The PathGradient is not implemented, and Radial Gradient is used instead
+ basegfx::B2DPoint aCenterPoint = Map(brush->firstPointX, brush->firstPointY);
+ aCenterPoint = aPolygonTransformation * aCenterPoint;
+
+ // create the same one used for SVG
+ mrTargetHolders.Current().append(
+ new drawinglayer::primitive2d::SvgRadialGradientPrimitive2D(
+ aTextureTransformation,
+ polygon,
+ std::move(aVector),
+ aCenterPoint,
+ 0.7, // relative radius little bigger to cover all elements
+ true, // use UnitCoordinates to stretch the gradient
+ drawinglayer::primitive2d::SpreadMethod::Pad,
+ nullptr));
+ }
+ }
+ }
+ }
+
+ EmfPlusHelperData::EmfPlusHelperData(
+ SvMemoryStream& rMS,
+ wmfemfhelper::TargetHolders& rTargetHolders,
+ wmfemfhelper::PropertyHolders& rPropertyHolders)
+ : mfPageScale(0.0),
+ mnOriginX(0),
+ mnOriginY(0),
+ mnHDPI(0),
+ mnVDPI(0),
+ mbSetTextContrast(false),
+ mnTextContrast(0),
+ mnFrameLeft(0),
+ mnFrameTop(0),
+ mnFrameRight(0),
+ mnFrameBottom(0),
+ mnPixX(0),
+ mnPixY(0),
+ mnMmX(0),
+ mnMmY(0),
+ mbMultipart(false),
+ mMFlags(0),
+ mdExtractedXScale(1.0),
+ mdExtractedYScale(1.0),
+ mrTargetHolders(rTargetHolders),
+ mrPropertyHolders(rPropertyHolders),
+ bIsGetDCProcessing(false)
+ {
+ rMS.ReadInt32(mnFrameLeft).ReadInt32(mnFrameTop).ReadInt32(mnFrameRight).ReadInt32(mnFrameBottom);
+ SAL_INFO("drawinglayer.emf", "EMF+ picture frame: " << mnFrameLeft << "," << mnFrameTop << " - " << mnFrameRight << "," << mnFrameBottom);
+ rMS.ReadInt32(mnPixX).ReadInt32(mnPixY).ReadInt32(mnMmX).ReadInt32(mnMmY);
+ SAL_INFO("drawinglayer.emf", "EMF+ ref device pixel size: " << mnPixX << "x" << mnPixY << " mm size: " << mnMmX << "x" << mnMmY);
+ readXForm(rMS, maBaseTransform);
+ SAL_INFO("drawinglayer.emf", "EMF+ base transform: " << maBaseTransform);
+ mappingChanged();
+ }
+
+ EmfPlusHelperData::~EmfPlusHelperData()
+ {
+ }
+
+ ::basegfx::B2DPolyPolygon EmfPlusHelperData::combineClip(::basegfx::B2DPolyPolygon const & leftPolygon, int combineMode, ::basegfx::B2DPolyPolygon const & rightPolygon)
+ {
+ basegfx::B2DPolyPolygon aClippedPolyPolygon;
+ switch (combineMode)
+ {
+ case EmfPlusCombineModeReplace:
+ {
+ aClippedPolyPolygon = rightPolygon;
+ break;
+ }
+ case EmfPlusCombineModeIntersect:
+ {
+ aClippedPolyPolygon = basegfx::utils::clipPolyPolygonOnPolyPolygon(
+ leftPolygon, rightPolygon, true, false);
+ break;
+ }
+ case EmfPlusCombineModeUnion:
+ {
+ aClippedPolyPolygon = ::basegfx::utils::solvePolygonOperationOr(leftPolygon, rightPolygon);
+ break;
+ }
+ case EmfPlusCombineModeXOR:
+ {
+ aClippedPolyPolygon = ::basegfx::utils::solvePolygonOperationXor(leftPolygon, rightPolygon);
+ break;
+ }
+ case EmfPlusCombineModeExclude:
+ {
+ // Replaces the existing region with the part of itself that is not in the new region.
+ aClippedPolyPolygon = ::basegfx::utils::solvePolygonOperationDiff(leftPolygon, rightPolygon);
+ break;
+ }
+ case EmfPlusCombineModeComplement:
+ {
+ // Replaces the existing region with the part of the new region that is not in the existing region.
+ aClippedPolyPolygon = ::basegfx::utils::solvePolygonOperationDiff(rightPolygon, leftPolygon);
+ break;
+ }
+ }
+ return aClippedPolyPolygon;
+ }
+
+ void EmfPlusHelperData::processEmfPlusData(
+ SvMemoryStream& rMS,
+ const drawinglayer::geometry::ViewInformation2D& /*rViewInformation*/)
+ {
+ sal_uInt64 length = rMS.GetSize();
+
+ if (length < 12)
+ SAL_WARN("drawinglayer.emf", "length is less than required header size");
+
+ // 12 is minimal valid EMF+ record size; remaining bytes are padding
+ while (length >= 12)
+ {
+ sal_uInt16 type, flags;
+ sal_uInt32 size, dataSize;
+ sal_uInt64 next;
+
+ rMS.ReadUInt16(type).ReadUInt16(flags).ReadUInt32(size).ReadUInt32(dataSize);
+
+ next = rMS.Tell() + (size - 12);
+
+ if (size < 12)
+ {
+ SAL_WARN("drawinglayer.emf", "Size field is less than 12 bytes");
+ break;
+ }
+ else if (size > length)
+ {
+ SAL_WARN("drawinglayer.emf", "Size field is greater than bytes left");
+ break;
+ }
+
+ if (dataSize > (size - 12))
+ {
+ SAL_WARN("drawinglayer.emf", "DataSize field is greater than Size-12");
+ break;
+ }
+
+ SAL_INFO("drawinglayer.emf", "EMF+ " << emfTypeToName(type) << " (0x" << std::hex << type << ")" << std::dec);
+ SAL_INFO("drawinglayer.emf", "EMF+\t record size: " << size);
+ SAL_INFO("drawinglayer.emf", "EMF+\t flags: 0x" << std::hex << flags << std::dec);
+ SAL_INFO("drawinglayer.emf", "EMF+\t data size: " << dataSize);
+
+ if (bIsGetDCProcessing)
+ {
+ if (aGetDCState.getClipPolyPolygonActive())
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t Restore region to GetDC saved");
+ wmfemfhelper::HandleNewClipRegion(aGetDCState.getClipPolyPolygon(), mrTargetHolders,
+ mrPropertyHolders);
+ }
+ else
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t Disable clipping");
+ wmfemfhelper::HandleNewClipRegion(::basegfx::B2DPolyPolygon(), mrTargetHolders,
+ mrPropertyHolders);
+ }
+ bIsGetDCProcessing = false;
+ }
+ if (type == EmfPlusRecordTypeObject && ((mbMultipart && (flags & 0x7fff) == (mMFlags & 0x7fff)) || (flags & 0x8000)))
+ {
+ if (!mbMultipart)
+ {
+ mbMultipart = true;
+ mMFlags = flags;
+ mMStream.Seek(0);
+ }
+
+ OSL_ENSURE(dataSize >= 4, "No room for TotalObjectSize in EmfPlusContinuedObjectRecord");
+
+ // 1st 4 bytes are TotalObjectSize
+ mMStream.WriteBytes(static_cast<const char *>(rMS.GetData()) + rMS.Tell() + 4, dataSize - 4);
+ SAL_INFO("drawinglayer.emf", "EMF+ read next object part size: " << size << " type: " << type << " flags: " << flags << " data size: " << dataSize);
+ }
+ else
+ {
+ if (mbMultipart)
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+ multipart record flags: " << mMFlags);
+ mMStream.Seek(0);
+ processObjectRecord(mMStream, mMFlags, 0, true);
+ }
+
+ mbMultipart = false;
+ }
+
+ if (type != EmfPlusRecordTypeObject || !(flags & 0x8000))
+ {
+ switch (type)
+ {
+ case EmfPlusRecordTypeHeader:
+ {
+ sal_uInt32 version, emfPlusFlags;
+ SAL_INFO("drawinglayer.emf", "EMF+\tDual: " << ((flags & 1) ? "true" : "false"));
+
+ rMS.ReadUInt32(version).ReadUInt32(emfPlusFlags).ReadUInt32(mnHDPI).ReadUInt32(mnVDPI);
+ SAL_INFO("drawinglayer.emf", "EMF+\tVersion: 0x" << std::hex << version);
+ SAL_INFO("drawinglayer.emf", "EMF+\tEmf+ Flags: 0x" << emfPlusFlags << std::dec);
+ SAL_INFO("drawinglayer.emf", "EMF+\tMetafile was recorded with a reference device context for " << ((emfPlusFlags & 1) ? "video display" : "printer"));
+ SAL_INFO("drawinglayer.emf", "EMF+\tHorizontal DPI: " << mnHDPI);
+ SAL_INFO("drawinglayer.emf", "EMF+\tVertical DPI: " << mnVDPI);
+ break;
+ }
+ case EmfPlusRecordTypeEndOfFile:
+ {
+ break;
+ }
+ case EmfPlusRecordTypeComment:
+ {
+#if OSL_DEBUG_LEVEL > 1
+ unsigned char data;
+ OUString hexdata;
+
+ SAL_INFO("drawinglayer.emf", "EMF+\tDatasize: 0x" << std::hex << dataSize << std::dec);
+
+ for (sal_uInt32 i=0; i<dataSize; i++)
+ {
+ rMS.ReadUChar(data);
+
+ if (i % 16 == 0)
+ hexdata += "\n";
+
+ OUString padding;
+ if ((data & 0xF0) == 0)
+ padding = "0";
+
+ hexdata += "0x" + padding + OUString::number(data, 16) + " ";
+ }
+
+ SAL_INFO("drawinglayer.emf", "EMF+\t" << hexdata);
+#endif
+ break;
+ }
+ case EmfPlusRecordTypeGetDC:
+ {
+ bIsGetDCProcessing = true;
+ aGetDCState = mrPropertyHolders.Current();
+ SAL_INFO("drawinglayer.emf", "EMF+\tAlready used in svtools wmf/emf filter parser");
+ break;
+ }
+ case EmfPlusRecordTypeObject:
+ {
+ processObjectRecord(rMS, flags, dataSize);
+ break;
+ }
+ case EmfPlusRecordTypeFillPie:
+ case EmfPlusRecordTypeDrawPie:
+ case EmfPlusRecordTypeDrawArc:
+ {
+ float startAngle, sweepAngle;
+
+ // Silent MSVC warning C4701: potentially uninitialized local variable 'brushIndexOrColor' used
+ sal_uInt32 brushIndexOrColor = 999;
+
+ if (type == EmfPlusRecordTypeFillPie)
+ {
+ rMS.ReadUInt32(brushIndexOrColor);
+ SAL_INFO("drawinglayer.emf", "EMF+\t FillPie colorOrIndex: " << brushIndexOrColor);
+ }
+ else if (type == EmfPlusRecordTypeDrawPie)
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t DrawPie");
+ }
+ else
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t DrawArc");
+ }
+
+ rMS.ReadFloat(startAngle).ReadFloat(sweepAngle);
+ float dx, dy, dw, dh;
+ ReadRectangle(rMS, dx, dy, dw, dh, bool(flags & 0x4000));
+ SAL_INFO("drawinglayer.emf", "EMF+\t RectData: " << dx << "," << dy << " " << dw << "x" << dh);
+ startAngle = basegfx::deg2rad(startAngle);
+ sweepAngle = basegfx::deg2rad(sweepAngle);
+ float endAngle = startAngle + sweepAngle;
+ startAngle = fmodf(startAngle, static_cast<float>(M_PI * 2));
+
+ if (startAngle < 0.0)
+ {
+ startAngle += static_cast<float>(M_PI * 2.0);
+ }
+ endAngle = fmodf(endAngle, static_cast<float>(M_PI * 2.0));
+
+ if (endAngle < 0.0)
+ {
+ endAngle += static_cast<float>(M_PI * 2.0);
+ }
+ if (sweepAngle < 0)
+ {
+ std::swap(endAngle, startAngle);
+ }
+
+ SAL_INFO("drawinglayer.emf", "EMF+\t Adjusted angles: start " <<
+ basegfx::rad2deg(startAngle) << ", end: " << basegfx::rad2deg(endAngle) <<
+ " startAngle: " << startAngle << " sweepAngle: " << sweepAngle);
+ const ::basegfx::B2DPoint centerPoint(dx + 0.5 * dw, dy + 0.5 * dh);
+ ::basegfx::B2DPolygon polygon(
+ ::basegfx::utils::createPolygonFromEllipseSegment(centerPoint,
+ 0.5 * dw, 0.5 * dh,
+ startAngle, endAngle));
+ if (type != EmfPlusRecordTypeDrawArc)
+ {
+ polygon.append(centerPoint);
+ polygon.setClosed(true);
+ }
+ ::basegfx::B2DPolyPolygon polyPolygon(polygon);
+ polyPolygon.transform(maMapTransform);
+ if (type == EmfPlusRecordTypeFillPie)
+ EMFPPlusFillPolygon(polyPolygon, flags & 0x8000, brushIndexOrColor);
+ else
+ EMFPPlusDrawPolygon(polyPolygon, flags & 0xff);
+ }
+ break;
+ case EmfPlusRecordTypeFillPath:
+ {
+ sal_uInt32 index = flags & 0xff;
+ sal_uInt32 brushIndexOrColor;
+ rMS.ReadUInt32(brushIndexOrColor);
+ SAL_INFO("drawinglayer.emf", "EMF+ FillPath slot: " << index);
+
+ EMFPPath* path = dynamic_cast<EMFPPath*>(maEMFPObjects[index].get());
+ if (path)
+ EMFPPlusFillPolygon(path->GetPolygon(*this), flags & 0x8000, brushIndexOrColor);
+ else
+ SAL_WARN("drawinglayer.emf", "EMF+\tEmfPlusRecordTypeFillPath missing path");
+ }
+ break;
+ case EmfPlusRecordTypeFillRegion:
+ {
+ sal_uInt32 index = flags & 0xff;
+ sal_uInt32 brushIndexOrColor;
+ rMS.ReadUInt32(brushIndexOrColor);
+ SAL_INFO("drawinglayer.emf", "EMF+\t FillRegion slot: " << index);
+
+ EMFPRegion* region = dynamic_cast<EMFPRegion*>(maEMFPObjects[flags & 0xff].get());
+ if (region)
+ EMFPPlusFillPolygon(region->regionPolyPolygon, flags & 0x8000, brushIndexOrColor);
+ else
+ SAL_WARN("drawinglayer.emf", "EMF+\tEmfPlusRecordTypeFillRegion missing region");
+ }
+ break;
+ case EmfPlusRecordTypeDrawEllipse:
+ case EmfPlusRecordTypeFillEllipse:
+ {
+ // Intentionally very bogus initial value to avoid MSVC complaining about potentially uninitialized local
+ // variable. As long as the code stays as intended, this variable will be assigned a (real) value in the case
+ // when it is later used.
+ sal_uInt32 brushIndexOrColor = 1234567;
+
+ if (type == EmfPlusRecordTypeFillEllipse)
+ {
+ rMS.ReadUInt32(brushIndexOrColor);
+ }
+
+ SAL_INFO("drawinglayer.emf", "EMF+\t " << (type == EmfPlusRecordTypeFillEllipse ? "Fill" : "Draw") << "Ellipse slot: " << (flags & 0xff));
+ float dx, dy, dw, dh;
+ ReadRectangle(rMS, dx, dy, dw, dh, bool(flags & 0x4000));
+ SAL_INFO("drawinglayer.emf", "EMF+\t RectData: " << dx << "," << dy << " " << dw << "x" << dh);
+ ::basegfx::B2DPolyPolygon polyPolygon(
+ ::basegfx::utils::createPolygonFromEllipse(::basegfx::B2DPoint(dx + 0.5 * dw, dy + 0.5 * dh),
+ 0.5 * dw, 0.5 * dh));
+ polyPolygon.transform(maMapTransform);
+ if (type == EmfPlusRecordTypeFillEllipse)
+ EMFPPlusFillPolygon(polyPolygon, flags & 0x8000, brushIndexOrColor);
+ else
+ EMFPPlusDrawPolygon(polyPolygon, flags & 0xff);
+ }
+ break;
+ case EmfPlusRecordTypeFillRects:
+ case EmfPlusRecordTypeDrawRects:
+ {
+ // Silent MSVC warning C4701: potentially uninitialized local variable 'brushIndexOrColor' used
+ sal_uInt32 brushIndexOrColor = 999;
+ ::basegfx::B2DPolyPolygon polyPolygon;
+ sal_uInt32 rectangles;
+ float x, y, width, height;
+ const bool isColor = (flags & 0x8000);
+ ::basegfx::B2DPolygon polygon;
+
+ if (EmfPlusRecordTypeFillRects == type)
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t FillRects");
+ rMS.ReadUInt32(brushIndexOrColor);
+ SAL_INFO("drawinglayer.emf", "EMF+\t" << (isColor ? "color" : "brush index") << ": 0x" << std::hex << brushIndexOrColor << std::dec);
+ }
+ else
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t DrawRects");
+ }
+
+ rMS.ReadUInt32(rectangles);
+ for (sal_uInt32 i = 0; i < rectangles; i++)
+ {
+ ReadRectangle(rMS, x, y, width, height, bool(flags & 0x4000));
+ polygon.clear();
+ polygon.append(Map(x, y));
+ polygon.append(Map(x + width, y));
+ polygon.append(Map(x + width, y + height));
+ polygon.append(Map(x, y + height));
+ polygon.setClosed(true);
+
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t rectangle: " << x << ", "<< y << " " << width << "x" << height);
+ polyPolygon.append(polygon);
+ }
+ if (type == EmfPlusRecordTypeFillRects)
+ EMFPPlusFillPolygon(polyPolygon, isColor, brushIndexOrColor);
+ else
+ EMFPPlusDrawPolygon(polyPolygon, flags & 0xff);
+ break;
+ }
+ case EmfPlusRecordTypeFillPolygon:
+ {
+ sal_uInt32 brushIndexOrColor, points;
+
+ rMS.ReadUInt32(brushIndexOrColor);
+ rMS.ReadUInt32(points);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Points: " << points);
+ SAL_INFO("drawinglayer.emf", "EMF+\t " << ((flags & 0x8000) ? "Color" : "Brush index") << " : 0x" << std::hex << brushIndexOrColor << std::dec);
+
+ EMFPPath path(points, true);
+ path.Read(rMS, flags);
+
+ EMFPPlusFillPolygon(path.GetPolygon(*this), flags & 0x8000, brushIndexOrColor);
+ break;
+ }
+ case EmfPlusRecordTypeDrawLines:
+ {
+ sal_uInt32 points;
+ rMS.ReadUInt32(points);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Points: " << points);
+ EMFPPath path(points, true);
+ path.Read(rMS, flags);
+
+ // 0x2000 bit indicates whether to draw an extra line between the last point
+ // and the first point, to close the shape.
+ EMFPPlusDrawPolygon(path.GetPolygon(*this, true, (flags & 0x2000)), flags);
+
+ break;
+ }
+ case EmfPlusRecordTypeDrawPath:
+ {
+ sal_uInt32 penIndex;
+ rMS.ReadUInt32(penIndex);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Pen: " << penIndex);
+
+ EMFPPath* path = dynamic_cast<EMFPPath*>(maEMFPObjects[flags & 0xff].get());
+ if (path)
+ EMFPPlusDrawPolygon(path->GetPolygon(*this), penIndex);
+ else
+ SAL_WARN("drawinglayer.emf", "\t\tEmfPlusRecordTypeDrawPath missing path");
+
+ break;
+ }
+ case EmfPlusRecordTypeDrawBeziers:
+ {
+ sal_uInt32 aCount;
+ float x1, y1, x2, y2, x3, y3, x4, y4;
+ ::basegfx::B2DPolygon aPolygon;
+ rMS.ReadUInt32(aCount);
+ SAL_INFO("drawinglayer.emf", "EMF+\t DrawBeziers slot: " << (flags & 0xff));
+ SAL_INFO("drawinglayer.emf", "EMF+\t Number of points: " << aCount);
+ SAL_WARN_IF((aCount - 1) % 3 != 0, "drawinglayer.emf",
+ "EMF+\t Bezier Draw not support number of points other than 4, 7, "
+ "10, 13, 16...");
+
+ if (aCount < 4)
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+\t Bezier Draw does not support less "
+ "than 4 points. Number of points: "
+ << aCount);
+ break;
+ }
+
+ ReadPoint(rMS, x1, y1, flags);
+ // We need to add first starting point
+ aPolygon.append(Map(x1, y1));
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t Bezier starting point: " << x1 << "," << y1);
+ for (sal_uInt32 i = 4; i <= aCount; i += 3)
+ {
+ ReadPoint(rMS, x2, y2, flags);
+ ReadPoint(rMS, x3, y3, flags);
+ ReadPoint(rMS, x4, y4, flags);
+
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t Bezier points: " << x2 << "," << y2 << " " << x3 << ","
+ << y3 << " " << x4 << "," << y4);
+ aPolygon.appendBezierSegment(Map(x2, y2), Map(x3, y3), Map(x4, y4));
+ }
+ EMFPPlusDrawPolygon(::basegfx::B2DPolyPolygon(aPolygon), flags & 0xff);
+ break;
+ }
+ case EmfPlusRecordTypeDrawCurve:
+ {
+ sal_uInt32 aOffset, aNumSegments, points;
+ float aTension;
+ rMS.ReadFloat(aTension);
+ rMS.ReadUInt32(aOffset);
+ rMS.ReadUInt32(aNumSegments);
+ rMS.ReadUInt32(points);
+ SAL_WARN("drawinglayer.emf",
+ "EMF+\t Tension: " << aTension << " Offset: " << aOffset
+ << " NumSegments: " << aNumSegments
+ << " Points: " << points);
+
+ EMFPPath path(points, true);
+ path.Read(rMS, flags);
+
+ if (points >= 2)
+ EMFPPlusDrawPolygon(
+ path.GetCardinalSpline(*this, aTension, aOffset, aNumSegments),
+ flags & 0xff);
+ else
+ SAL_WARN("drawinglayer.emf", "Not enough number of points");
+ break;
+ }
+ case EmfPlusRecordTypeDrawClosedCurve:
+ case EmfPlusRecordTypeFillClosedCurve:
+ {
+ // Silent MSVC warning C4701: potentially uninitialized local variable 'brushIndexOrColor' used
+ sal_uInt32 brushIndexOrColor = 999, points;
+ float aTension;
+ if (type == EmfPlusRecordTypeFillClosedCurve)
+ {
+ rMS.ReadUInt32(brushIndexOrColor);
+ SAL_INFO(
+ "drawinglayer.emf",
+ "EMF+\t Fill Mode: " << (flags & 0x2000 ? "Winding" : "Alternate"));
+ }
+ rMS.ReadFloat(aTension);
+ rMS.ReadUInt32(points);
+ SAL_WARN("drawinglayer.emf",
+ "EMF+\t Tension: " << aTension << " Points: " << points);
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t " << (flags & 0x8000 ? "Color" : "Brush index") << " : 0x"
+ << std::hex << brushIndexOrColor << std::dec);
+ if (points < 3)
+ {
+ SAL_WARN("drawinglayer.emf", "Not enough number of points");
+ break;
+ }
+ EMFPPath path(points, true);
+ path.Read(rMS, flags);
+ if (type == EmfPlusRecordTypeFillClosedCurve)
+ EMFPPlusFillPolygon(path.GetClosedCardinalSpline(*this, aTension),
+ flags & 0x8000, brushIndexOrColor);
+ else
+ EMFPPlusDrawPolygon(path.GetClosedCardinalSpline(*this, aTension),
+ flags & 0xff);
+ break;
+ }
+ case EmfPlusRecordTypeDrawImage:
+ case EmfPlusRecordTypeDrawImagePoints:
+ {
+ sal_uInt32 imageAttributesId;
+ sal_Int32 sourceUnit;
+ rMS.ReadUInt32(imageAttributesId).ReadInt32(sourceUnit);
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t " << (type == EmfPlusRecordTypeDrawImage ? "DrawImage"
+ : "DrawImagePoints")
+ << " image attributes Id: " << imageAttributesId
+ << " source unit: " << sourceUnit);
+ SAL_INFO("drawinglayer.emf", "EMF+\t TODO: use image attributes");
+
+ // Source unit of measurement type must be 1 pixel
+ if (EMFPImage* image = sourceUnit == UnitTypePixel ?
+ dynamic_cast<EMFPImage*>(maEMFPObjects[flags & 0xff].get()) :
+ nullptr)
+ {
+ float sx, sy, sw, sh;
+ ReadRectangle(rMS, sx, sy, sw, sh);
+
+ ::tools::Rectangle aSource(Point(sx, sy), Size(sw + 1, sh + 1));
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t "
+ << (type == EmfPlusRecordTypeDrawImage ? "DrawImage"
+ : "DrawImagePoints")
+ << " source rectangle: " << sx << "," << sy << " " << sw << "x"
+ << sh);
+
+ float dx(0.), dy(0.), dw(0.), dh(0.);
+ double fShearX = 0.0;
+ double fShearY = 0.0;
+ if (type == EmfPlusRecordTypeDrawImagePoints)
+ {
+ sal_uInt32 aCount;
+ rMS.ReadUInt32(aCount);
+
+ // Number of points used by DrawImagePoints. Exactly 3 points must be specified.
+ if (aCount != 3)
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+\t Wrong EMF+ file. Expected "
+ "3 points, received: "
+ << aCount);
+ break;
+ }
+ float x1, y1, x2, y2, x3, y3;
+
+ ReadPoint(rMS, x1, y1, flags); // upper-left point
+ ReadPoint(rMS, x2, y2, flags); // upper-right
+ ReadPoint(rMS, x3, y3, flags); // lower-left
+
+ SAL_INFO("drawinglayer.emf", "EMF+\t destination points: "
+ << x1 << "," << y1 << " " << x2 << ","
+ << y2 << " " << x3 << "," << y3);
+ dx = x1;
+ dy = y2;
+ dw = x2 - x1;
+ dh = y3 - y1;
+ fShearX = x3 - x1;
+ fShearY = y2 - y1;
+ }
+ else if (type == EmfPlusRecordTypeDrawImage)
+ ReadRectangle(rMS, dx, dy, dw, dh, bool(flags & 0x4000));
+
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t Rectangle: " << dx << "," << dy << " " << dw << "x" << dh);
+ Size aSize;
+ if (image->type == ImageDataTypeBitmap)
+ {
+ aSize = image->graphic.GetBitmapEx().GetSizePixel();
+ SAL_INFO("drawinglayer.emf", "EMF+\t Bitmap size: " << aSize.Width()
+ << "x"
+ << aSize.Height());
+ if (sx < 0)
+ {
+ // If src position is negative then we need shift image to right
+ dx = dx + ((-sx) / sw) * dw;
+ if (sx + sw <= aSize.Width())
+ dw = ((sw + sx) / sw) * dw;
+ else
+ dw = (aSize.Width() / sw) * dw;
+ }
+ else if (sx + sw > aSize.Width())
+ // If the src image is smaller that what we want to cut, then we need to scale down
+ dw = ((aSize.Width() - sx) / sw) * dw;
+
+ if (sy < 0)
+ {
+ dy = dy + ((-sy) / sh) * dh;
+ if (sy + sh <= aSize.Height())
+ dh = ((sh + sy) / sh) * dh;
+ else
+ dh = (aSize.Height() / sh) * dh;
+ }
+ else if (sy + sh > aSize.Height())
+ dh = ((aSize.Height() - sy) / sh) * dh;
+ }
+ else
+ SAL_INFO(
+ "drawinglayer.emf",
+ "EMF+\t TODO: Add support for SrcRect to ImageDataTypeMetafile");
+ const ::basegfx::B2DPoint aDstPoint(dx, dy);
+ const ::basegfx::B2DSize aDstSize(dw, dh);
+
+ const basegfx::B2DHomMatrix aTransformMatrix
+ = maMapTransform
+ * basegfx::B2DHomMatrix(
+ /* Row 0, Column 0 */ aDstSize.getWidth(),
+ /* Row 0, Column 1 */ fShearX,
+ /* Row 0, Column 2 */ aDstPoint.getX(),
+ /* Row 1, Column 0 */ fShearY,
+ /* Row 1, Column 1 */ aDstSize.getHeight(),
+ /* Row 1, Column 2 */ aDstPoint.getY());
+
+ if (image->type == ImageDataTypeBitmap)
+ {
+ BitmapEx aBmp(image->graphic.GetBitmapEx());
+ aBmp.Crop(aSource);
+ aSize = aBmp.GetSizePixel();
+ if (aSize.Width() > 0 && aSize.Height() > 0)
+ {
+ mrTargetHolders.Current().append(
+ new drawinglayer::primitive2d::BitmapPrimitive2D(
+ aBmp, aTransformMatrix));
+ }
+ else
+ SAL_WARN("drawinglayer.emf", "EMF+\t warning: empty bitmap");
+ }
+ else if (image->type == ImageDataTypeMetafile)
+ {
+ GDIMetaFile aGDI(image->graphic.GetGDIMetaFile());
+ aGDI.Clip(aSource);
+ mrTargetHolders.Current().append(
+ new drawinglayer::primitive2d::MetafilePrimitive2D(aTransformMatrix,
+ aGDI));
+ }
+ }
+ else
+ {
+ SAL_WARN("drawinglayer.emf",
+ "EMF+\tDrawImage(Points) Wrong EMF+ file. Only Unit Type Pixel is "
+ "support by EMF+ specification for DrawImage(Points)");
+ }
+ break;
+ }
+ case EmfPlusRecordTypeDrawString:
+ {
+ sal_uInt32 brushId, formatId, stringLength;
+ rMS.ReadUInt32(brushId).ReadUInt32(formatId).ReadUInt32(stringLength);
+ SAL_INFO("drawinglayer.emf", "EMF+\t FontId: " << OUString::number(flags & 0xFF));
+ SAL_INFO("drawinglayer.emf", "EMF+\t BrushId: " << BrushIDToString(flags, brushId));
+ SAL_INFO("drawinglayer.emf", "EMF+\t FormatId: " << formatId);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Length: " << stringLength);
+
+ // read the layout rectangle
+ float lx, ly, lw, lh;
+ rMS.ReadFloat(lx).ReadFloat(ly).ReadFloat(lw).ReadFloat(lh);
+
+ SAL_INFO("drawinglayer.emf", "EMF+\t DrawString layoutRect: " << lx << "," << ly << " - " << lw << "x" << lh);
+ // parse the string
+ const OUString text = read_uInt16s_ToOUString(rMS, stringLength);
+ SAL_INFO("drawinglayer.emf", "EMF+\t DrawString string: " << text);
+ // get the stringFormat from the Object table ( this is OPTIONAL and may be nullptr )
+ const EMFPStringFormat *stringFormat = dynamic_cast<EMFPStringFormat*>(maEMFPObjects[formatId & 0xff].get());
+ // get the font from the flags
+ const EMFPFont *font = dynamic_cast<EMFPFont*>(maEMFPObjects[flags & 0xff].get());
+ if (!font)
+ {
+ break;
+ }
+ mrPropertyHolders.Current().setFont(vcl::Font(font->family, Size(font->emSize, font->emSize)));
+
+ drawinglayer::attribute::FontAttribute fontAttribute(
+ font->family, // font family
+ "", // (no) font style
+ font->Bold() ? 8u : 1u, // weight: 8 = bold
+ font->family == "SYMBOL", // symbol
+ stringFormat && stringFormat->DirectionVertical(), // vertical
+ font->Italic(), // italic
+ false, // monospaced
+ false, // outline = false, no such thing in MS-EMFPLUS
+ stringFormat && stringFormat->DirectionRightToLeft(), // right-to-left
+ false); // BiDiStrong
+
+ css::lang::Locale locale;
+ double stringAlignmentHorizontalOffset = 0.0;
+ double stringAlignmentVerticalOffset = font->emSize;
+ if (stringFormat)
+ {
+ LanguageTag aLanguageTag(static_cast<LanguageType>(stringFormat->language));
+ locale = aLanguageTag.getLocale();
+ drawinglayer::primitive2d::TextLayouterDevice aTextLayouter;
+
+ aTextLayouter.setFontAttribute(fontAttribute, font->emSize,
+ font->emSize, locale);
+
+ double fTextWidth = aTextLayouter.getTextWidth(text, 0, stringLength);
+ SAL_WARN_IF(stringFormat->DirectionRightToLeft(), "drawinglayer.emf",
+ "EMF+\t DrawString Alignment TODO For a right-to-left layout rectangle, the origin should be at the upper right.");
+ if (stringFormat->stringAlignment == StringAlignmentNear)
+ // Alignment is to the left side of the layout rectangle (lx, ly, lw, lh)
+ stringAlignmentHorizontalOffset = stringFormat->leadingMargin * font->emSize;
+ else if (stringFormat->stringAlignment == StringAlignmentCenter)
+ // Alignment is centered between the origin and extent of the layout rectangle
+ stringAlignmentHorizontalOffset = 0.5 * lw + (stringFormat->leadingMargin - stringFormat->trailingMargin) * font->emSize - 0.5 * fTextWidth;
+ else if (stringFormat->stringAlignment == StringAlignmentFar)
+ // Alignment is to the right side of the layout rectangle
+ stringAlignmentHorizontalOffset = lw - stringFormat->trailingMargin * font->emSize - fTextWidth;
+
+ if (stringFormat->lineAlign == StringAlignmentNear)
+ stringAlignmentVerticalOffset = font->emSize;
+ else if (stringFormat->lineAlign == StringAlignmentCenter)
+ stringAlignmentVerticalOffset = 0.5 * lh + 0.5 * font->emSize;
+ else if (stringFormat->lineAlign == StringAlignmentFar)
+ stringAlignmentVerticalOffset = lh;
+ }
+ else
+ {
+ // By default LeadingMargin is 1/6 inch
+ // TODO for typographic fonts set value to 0.
+ stringAlignmentHorizontalOffset = 16.0;
+
+ // use system default
+ locale = Application::GetSettings().GetLanguageTag().getLocale();
+ }
+
+ const basegfx::B2DHomMatrix transformMatrix = basegfx::utils::createScaleTranslateB2DHomMatrix(
+ ::basegfx::B2DVector(font->emSize, font->emSize),
+ ::basegfx::B2DPoint(lx + stringAlignmentHorizontalOffset,
+ ly + stringAlignmentVerticalOffset));
+
+ Color uncorrectedColor = EMFPGetBrushColorOrARGBColor(flags, brushId);
+ Color color;
+
+ if (mbSetTextContrast)
+ {
+ const auto gammaVal = mnTextContrast / 1000;
+ const basegfx::BColorModifier_gamma gamma(gammaVal);
+
+ // gamma correct transparency color
+ sal_uInt16 alpha = uncorrectedColor.GetAlpha();
+ alpha = std::clamp(std::pow(alpha, 1.0 / gammaVal), 0.0, 1.0) * 255;
+
+ basegfx::BColor modifiedColor(gamma.getModifiedColor(uncorrectedColor.getBColor()));
+ color.SetRed(modifiedColor.getRed() * 255);
+ color.SetGreen(modifiedColor.getGreen() * 255);
+ color.SetBlue(modifiedColor.getBlue() * 255);
+ color.SetAlpha(alpha);
+ }
+ else
+ {
+ color = uncorrectedColor;
+ }
+
+ mrPropertyHolders.Current().setTextColor(color.getBColor());
+ mrPropertyHolders.Current().setTextColorActive(true);
+
+ if (color.GetAlpha() > 0)
+ {
+ std::vector<double> emptyVector;
+ rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pBaseText;
+ if (font->Underline() || font->Strikeout())
+ {
+ pBaseText = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
+ transformMatrix,
+ text,
+ 0, // text always starts at 0
+ stringLength,
+ std::move(emptyVector), // EMF-PLUS has no DX-array
+ {},
+ fontAttribute,
+ locale,
+ color.getBColor(), // Font Color
+ COL_TRANSPARENT, // Fill Color
+ color.getBColor(), // OverlineColor
+ color.getBColor(), // TextlineColor
+ drawinglayer::primitive2d::TEXT_LINE_NONE,
+ font->Underline() ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
+ false,
+ font->Strikeout() ? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE : drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE);
+ }
+ else
+ {
+ pBaseText = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
+ transformMatrix,
+ text,
+ 0, // text always starts at 0
+ stringLength,
+ std::move(emptyVector), // EMF-PLUS has no DX-array
+ {},
+ fontAttribute,
+ locale,
+ color.getBColor());
+ }
+ drawinglayer::primitive2d::Primitive2DReference aPrimitiveText(pBaseText);
+ if (color.IsTransparent())
+ {
+ aPrimitiveText = new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
+ drawinglayer::primitive2d::Primitive2DContainer { aPrimitiveText },
+ (255 - color.GetAlpha()) / 255.0);
+ }
+
+ mrTargetHolders.Current().append(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ maMapTransform,
+ drawinglayer::primitive2d::Primitive2DContainer { aPrimitiveText } ));
+ }
+ break;
+ }
+ case EmfPlusRecordTypeSetPageTransform:
+ {
+ rMS.ReadFloat(mfPageScale);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Scale: " << mfPageScale << " unit: " << UnitTypeToString(flags));
+
+ if ((flags == UnitTypeDisplay) || (flags == UnitTypeWorld))
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+\t file error. UnitTypeDisplay and UnitTypeWorld are not supported by SetPageTransform in EMF+ specification.");
+ }
+ else
+ {
+ mnMmX *= mfPageScale * getUnitToPixelMultiplier(static_cast<UnitType>(flags), mnHDPI);
+ mnMmY *= mfPageScale * getUnitToPixelMultiplier(static_cast<UnitType>(flags), mnVDPI);
+ mappingChanged();
+ }
+ break;
+ }
+ case EmfPlusRecordTypeSetRenderingOrigin:
+ {
+ rMS.ReadInt32(mnOriginX).ReadInt32(mnOriginY);
+ SAL_INFO("drawinglayer.emf", "EMF+\t SetRenderingOrigin, [x,y]: " << mnOriginX << "," << mnOriginY);
+ break;
+ }
+ case EmfPlusRecordTypeSetTextContrast:
+ {
+ const sal_uInt16 LOWERGAMMA = 1000;
+ const sal_uInt16 UPPERGAMMA = 2200;
+
+ mbSetTextContrast = true;
+ mnTextContrast = flags & 0xFFF;
+ SAL_WARN_IF(mnTextContrast > UPPERGAMMA || mnTextContrast < LOWERGAMMA,
+ "drawinglayer.emf", "EMF+\t Gamma value is not with bounds 1000 to 2200, value is " << mnTextContrast);
+ mnTextContrast = std::min(mnTextContrast, UPPERGAMMA);
+ mnTextContrast = std::max(mnTextContrast, LOWERGAMMA);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Text contrast: " << (mnTextContrast / 1000) << " gamma");
+ break;
+ }
+ case EmfPlusRecordTypeSetTextRenderingHint:
+ {
+ sal_uInt8 nTextRenderingHint = (flags & 0xFF) >> 1;
+ SAL_INFO("drawinglayer.emf", "EMF+\t Text rendering hint: " << TextRenderingHintToString(nTextRenderingHint));
+ SAL_WARN("drawinglayer.emf", "EMF+\t TODO SetTextRenderingHint");
+ break;
+ }
+ case EmfPlusRecordTypeSetAntiAliasMode:
+ {
+ bool bUseAntiAlias = (flags & 0x0001);
+ sal_uInt8 nSmoothingMode = (flags & 0xFE00) >> 1;
+ SAL_INFO("drawinglayer.emf", "EMF+\t Antialiasing: " << (bUseAntiAlias ? "enabled" : "disabled"));
+ SAL_INFO("drawinglayer.emf", "EMF+\t Smoothing mode: " << SmoothingModeToString(nSmoothingMode));
+ SAL_WARN("drawinglayer.emf", "EMF+\t TODO SetAntiAliasMode");
+ break;
+ }
+ case EmfPlusRecordTypeSetInterpolationMode:
+ {
+ sal_uInt16 nInterpolationMode = flags & 0xFF;
+ SAL_INFO("drawinglayer.emf", "EMF+\t Interpolation mode: " << InterpolationModeToString(nInterpolationMode));
+ SAL_WARN("drawinglayer.emf", "EMF+\t TODO InterpolationMode");
+ break;
+ }
+ case EmfPlusRecordTypeSetPixelOffsetMode:
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t Pixel offset mode: " << PixelOffsetModeToString(flags));
+ SAL_WARN("drawinglayer.emf", "EMF+\t TODO SetPixelOffsetMode");
+ break;
+ }
+ case EmfPlusRecordTypeSetCompositingQuality:
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t TODO SetCompositingQuality");
+ break;
+ }
+ case EmfPlusRecordTypeSave:
+ {
+ sal_uInt32 stackIndex;
+ rMS.ReadUInt32(stackIndex);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Save stack index: " << stackIndex);
+
+ GraphicStatePush(mGSStack, stackIndex);
+
+ break;
+ }
+ case EmfPlusRecordTypeRestore:
+ {
+ sal_uInt32 stackIndex;
+ rMS.ReadUInt32(stackIndex);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Restore stack index: " << stackIndex);
+
+ GraphicStatePop(mGSStack, stackIndex);
+ break;
+ }
+ case EmfPlusRecordTypeBeginContainer:
+ {
+ float dx, dy, dw, dh;
+ ReadRectangle(rMS, dx, dy, dw, dh);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Dest RectData: " << dx << "," << dy << " " << dw << "x" << dh);
+
+ float sx, sy, sw, sh;
+ ReadRectangle(rMS, sx, sy, sw, sh);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Source RectData: " << sx << "," << sy << " " << sw << "x" << sh);
+
+ sal_uInt32 stackIndex;
+ rMS.ReadUInt32(stackIndex);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Begin Container stack index: " << stackIndex << ", PageUnit: " << flags);
+
+ if ((flags == UnitTypeDisplay) || (flags == UnitTypeWorld))
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+\t file error. UnitTypeDisplay and UnitTypeWorld are not supported by BeginContainer in EMF+ specification.");
+ break;
+ }
+ const float aPageScaleX = getUnitToPixelMultiplier(static_cast<UnitType>(flags), mnHDPI);
+ const float aPageScaleY = getUnitToPixelMultiplier(static_cast<UnitType>(flags), mnVDPI);
+ GraphicStatePush(mGSContainerStack, stackIndex);
+ const basegfx::B2DHomMatrix transform = basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aPageScaleX * ( dw / sw ), aPageScaleY * ( dh / sh ),
+ aPageScaleX * ( dx - sx ), aPageScaleY * ( dy - sy) );
+ maWorldTransform *= transform;
+ mappingChanged();
+ break;
+ }
+ case EmfPlusRecordTypeBeginContainerNoParams:
+ {
+ sal_uInt32 stackIndex;
+ rMS.ReadUInt32(stackIndex);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Begin Container No Params stack index: " << stackIndex);
+
+ GraphicStatePush(mGSContainerStack, stackIndex);
+ break;
+ }
+ case EmfPlusRecordTypeEndContainer:
+ {
+ sal_uInt32 stackIndex;
+ rMS.ReadUInt32(stackIndex);
+ SAL_INFO("drawinglayer.emf", "EMF+\t End Container stack index: " << stackIndex);
+
+ GraphicStatePop(mGSContainerStack, stackIndex);
+ break;
+ }
+ case EmfPlusRecordTypeSetWorldTransform:
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t SetWorldTransform, Post multiply: " << bool(flags & 0x2000));
+ readXForm(rMS, maWorldTransform);
+ mappingChanged();
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t: " << maWorldTransform);
+ break;
+ }
+ case EmfPlusRecordTypeResetWorldTransform:
+ {
+ maWorldTransform.identity();
+ SAL_INFO("drawinglayer.emf", "EMF+\t World transform: " << maWorldTransform);
+ mappingChanged();
+ break;
+ }
+ case EmfPlusRecordTypeMultiplyWorldTransform:
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t MultiplyWorldTransform, post multiply: " << bool(flags & 0x2000));
+ basegfx::B2DHomMatrix transform;
+ readXForm(rMS, transform);
+
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t Transform matrix: " << transform);
+
+ if (flags & 0x2000)
+ {
+ // post multiply
+ maWorldTransform *= transform;
+ }
+ else
+ {
+ // pre multiply
+ transform *= maWorldTransform;
+ maWorldTransform = transform;
+ }
+
+ mappingChanged();
+
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t World transform matrix: " << maWorldTransform);
+ break;
+ }
+ case EmfPlusRecordTypeTranslateWorldTransform:
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t TranslateWorldTransform, Post multiply: " << bool(flags & 0x2000));
+
+ basegfx::B2DHomMatrix transform;
+ float eDx, eDy;
+ rMS.ReadFloat(eDx).ReadFloat(eDy);
+ transform.set(0, 2, eDx);
+ transform.set(1, 2, eDy);
+
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t Translate matrix: " << transform);
+
+ if (flags & 0x2000)
+ {
+ // post multiply
+ maWorldTransform *= transform;
+ }
+ else
+ {
+ // pre multiply
+ transform *= maWorldTransform;
+ maWorldTransform = transform;
+ }
+
+ mappingChanged();
+
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t World transform matrix: " << maWorldTransform);
+ break;
+ }
+ case EmfPlusRecordTypeScaleWorldTransform:
+ {
+ basegfx::B2DHomMatrix transform;
+ float eSx, eSy;
+ rMS.ReadFloat(eSx).ReadFloat(eSy);
+ transform.set(0, 0, eSx);
+ transform.set(1, 1, eSy);
+
+ SAL_INFO("drawinglayer.emf", "EMF+\t ScaleWorldTransform Sx: " << eSx <<
+ " Sy: " << eSy << ", Post multiply:" << bool(flags & 0x2000));
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t World transform matrix: " << maWorldTransform);
+
+ if (flags & 0x2000)
+ {
+ // post multiply
+ maWorldTransform *= transform;
+ }
+ else
+ {
+ // pre multiply
+ transform *= maWorldTransform;
+ maWorldTransform = transform;
+ }
+
+ mappingChanged();
+
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t World transform matrix: " << maWorldTransform);
+ break;
+ }
+ case EmfPlusRecordTypeRotateWorldTransform:
+ {
+ // Angle of rotation in degrees
+ float eAngle;
+ rMS.ReadFloat(eAngle);
+
+ SAL_INFO("drawinglayer.emf", "EMF+\t RotateWorldTransform Angle: " << eAngle <<
+ ", post multiply: " << bool(flags & 0x2000));
+ // Skipping flags & 0x2000
+ // For rotation transformation there is no difference between post and pre multiply
+ maWorldTransform.rotate(basegfx::deg2rad(eAngle));
+ mappingChanged();
+
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t " << maWorldTransform);
+ break;
+ }
+ case EmfPlusRecordTypeResetClip:
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+ ResetClip");
+ // We don't need to read anything more, as Size needs to be set 0x0000000C
+ // and DataSize must be set to 0.
+
+ // Resets the current clipping region for the world space to infinity.
+ HandleNewClipRegion(::basegfx::B2DPolyPolygon(), mrTargetHolders, mrPropertyHolders);
+ break;
+ }
+ case EmfPlusRecordTypeSetClipRect:
+ case EmfPlusRecordTypeSetClipPath:
+ case EmfPlusRecordTypeSetClipRegion:
+ {
+ int combineMode = (flags >> 8) & 0xf;
+ ::basegfx::B2DPolyPolygon polyPolygon;
+ if (type == EmfPlusRecordTypeSetClipRect)
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t SetClipRect");
+
+ float dx, dy, dw, dh;
+ ReadRectangle(rMS, dx, dy, dw, dh);
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t RectData: " << dx << "," << dy << " " << dw << "x" << dh);
+ ::basegfx::B2DPoint mappedPoint1(Map(dx, dy));
+ ::basegfx::B2DPoint mappedPoint2(Map(dx + dw, dy + dh));
+
+ polyPolygon
+ = ::basegfx::B2DPolyPolygon(::basegfx::utils::createPolygonFromRect(
+ ::basegfx::B2DRectangle(mappedPoint1.getX(), mappedPoint1.getY(),
+ mappedPoint2.getX(), mappedPoint2.getY())));
+ }
+ else if (type == EmfPlusRecordTypeSetClipPath)
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\tSetClipPath " << (flags & 0xff));
+
+ EMFPPath* path = dynamic_cast<EMFPPath*>(maEMFPObjects[flags & 0xff].get());
+ if (!path)
+ {
+ SAL_WARN("drawinglayer.emf",
+ "EMF+\t TODO Unable to find path in slot: " << (flags & 0xff));
+ break;
+ }
+ polyPolygon = path->GetPolygon(*this);
+ }
+ else if (type == EmfPlusRecordTypeSetClipRegion)
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t Region in slot: " << (flags & 0xff));
+ EMFPRegion* region
+ = dynamic_cast<EMFPRegion*>(maEMFPObjects[flags & 0xff].get());
+ if (!region)
+ {
+ SAL_WARN(
+ "drawinglayer.emf",
+ "EMF+\t TODO Unable to find region in slot: " << (flags & 0xff));
+ break;
+ }
+ polyPolygon = region->regionPolyPolygon;
+ }
+ SAL_INFO("drawinglayer.emf", "EMF+\t Combine mode: " << combineMode);
+ ::basegfx::B2DPolyPolygon aClippedPolyPolygon;
+ if (mrPropertyHolders.Current().getClipPolyPolygonActive())
+ {
+ aClippedPolyPolygon
+ = combineClip(mrPropertyHolders.Current().getClipPolyPolygon(),
+ combineMode, polyPolygon);
+ }
+ else
+ {
+ //Combine with infinity
+ switch (combineMode)
+ {
+ case EmfPlusCombineModeReplace:
+ case EmfPlusCombineModeIntersect:
+ {
+ aClippedPolyPolygon = polyPolygon;
+ break;
+ }
+ case EmfPlusCombineModeUnion:
+ {
+ // Disable clipping as the clipping is infinity
+ aClippedPolyPolygon = ::basegfx::B2DPolyPolygon();
+ break;
+ }
+ case EmfPlusCombineModeXOR:
+ case EmfPlusCombineModeComplement:
+ {
+ //TODO It is not correct and it should be fixed
+ aClippedPolyPolygon = polyPolygon;
+ break;
+ }
+ case EmfPlusCombineModeExclude:
+ {
+ //TODO It is not correct and it should be fixed
+ aClippedPolyPolygon = ::basegfx::B2DPolyPolygon();
+ break;
+ }
+ }
+ }
+ HandleNewClipRegion(aClippedPolyPolygon, mrTargetHolders, mrPropertyHolders);
+ break;
+ }
+ case EmfPlusRecordTypeOffsetClip:
+ {
+ float dx, dy;
+ rMS.ReadFloat(dx).ReadFloat(dy);
+ SAL_INFO("drawinglayer.emf", "EMF+\tOffset x:" << dx << ", y:" << dy);
+
+ basegfx::B2DPolyPolygon aPolyPolygon(
+ mrPropertyHolders.Current().getClipPolyPolygon());
+
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t PolyPolygon before translate: " << aPolyPolygon);
+
+ basegfx::B2DPoint aOffset = Map(dx, dy);
+ basegfx::B2DHomMatrix transformMatrix;
+ transformMatrix.set(0, 2, aOffset.getX());
+ transformMatrix.set(1, 2, aOffset.getY());
+ aPolyPolygon.transform(transformMatrix);
+
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t PolyPolygon after translate: " << aPolyPolygon <<
+ ", mapped offset x" << aOffset.getX() << ", mapped offset y" << aOffset.getY());
+ HandleNewClipRegion(aPolyPolygon, mrTargetHolders, mrPropertyHolders);
+ break;
+ }
+ case EmfPlusRecordTypeDrawDriverString:
+ {
+ sal_uInt32 brushIndexOrColor;
+ sal_uInt32 optionFlags;
+ sal_uInt32 hasMatrix;
+ sal_uInt32 glyphsCount;
+ rMS.ReadUInt32(brushIndexOrColor).ReadUInt32(optionFlags).ReadUInt32(hasMatrix).ReadUInt32(glyphsCount);
+ SAL_INFO("drawinglayer.emf", "EMF+\t " << ((flags & 0x8000) ? "Color" : "Brush index") << ": 0x" << std::hex << brushIndexOrColor << std::dec);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Option flags: 0x" << std::hex << optionFlags << std::dec);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Has matrix: " << hasMatrix);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Glyphs: " << glyphsCount);
+
+ if ((optionFlags & 1) && glyphsCount > 0)
+ {
+ std::unique_ptr<float[]> charsPosX(new float[glyphsCount]);
+ std::unique_ptr<float[]> charsPosY(new float[glyphsCount]);
+ OUString text = read_uInt16s_ToOUString(rMS, glyphsCount);
+ SAL_INFO("drawinglayer.emf", "EMF+\t DrawDriverString string: " << text);
+
+ for (sal_uInt32 i = 0; i<glyphsCount; i++)
+ {
+ rMS.ReadFloat(charsPosX[i]).ReadFloat(charsPosY[i]);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t glyphPosition[" << i << "]: " << charsPosX[i] << "," << charsPosY[i]);
+ }
+
+ basegfx::B2DHomMatrix transform;
+
+ if (hasMatrix)
+ {
+ readXForm(rMS, transform);
+ SAL_INFO("drawinglayer.emf", "EMF+\tmatrix: " << transform);
+ }
+
+ // get the font from the flags
+ EMFPFont *font = dynamic_cast<EMFPFont*>(maEMFPObjects[flags & 0xff].get());
+ if (!font)
+ {
+ break;
+ }
+ // done reading
+
+ drawinglayer::attribute::FontAttribute fontAttribute(
+ font->family, // font family
+ "", // (no) font style
+ font->Bold() ? 8u : 1u, // weight: 8 = bold
+ font->family == "SYMBOL", // symbol
+ optionFlags & 0x2, // vertical
+ font->Italic(), // italic
+ false, // monospaced
+ false, // outline = false, no such thing in MS-EMFPLUS
+ false, // right-to-left
+ false); // BiDiStrong
+
+ const Color color = EMFPGetBrushColorOrARGBColor(flags, brushIndexOrColor);
+
+ // generate TextSimplePortionPrimitive2Ds or TextDecoratedPortionPrimitive2D
+ // for all portions of text with the same charsPosY values
+ sal_uInt32 pos = 0;
+ while (pos < glyphsCount)
+ {
+ //determine the current length
+ sal_uInt32 aLength = 1;
+ while (pos + aLength < glyphsCount && std::abs( charsPosY[pos + aLength] - charsPosY[pos] ) < std::numeric_limits< float >::epsilon())
+ aLength++;
+
+ // generate the DX-Array
+ std::vector<double> aDXArray;
+ for (size_t i = 0; i < aLength - 1; i++)
+ {
+ aDXArray.push_back(charsPosX[pos + i + 1] - charsPosX[pos]);
+ }
+ // last entry
+ aDXArray.push_back(0);
+
+ basegfx::B2DHomMatrix transformMatrix = basegfx::utils::createScaleTranslateB2DHomMatrix(
+ ::basegfx::B2DVector(font->emSize, font->emSize),
+ ::basegfx::B2DPoint(charsPosX[pos], charsPosY[pos]));
+ if (hasMatrix)
+ transformMatrix *= transform;
+ if (color.GetAlpha() > 0)
+ {
+ rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pBaseText;
+ if (font->Underline() || font->Strikeout())
+ {
+ pBaseText = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
+ transformMatrix,
+ text,
+ pos, // take character at current pos
+ aLength, // use determined length
+ std::move(aDXArray), // generated DXArray
+ {},
+ fontAttribute,
+ Application::GetSettings().GetLanguageTag().getLocale(),
+ color.getBColor(),
+ COL_TRANSPARENT,
+ color.getBColor(),
+ color.getBColor(),
+ drawinglayer::primitive2d::TEXT_LINE_NONE,
+ font->Underline() ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
+ false,
+ font->Strikeout() ? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE : drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE);
+ }
+ else
+ {
+ pBaseText = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
+ transformMatrix,
+ text,
+ pos, // take character at current pos
+ aLength, // use determined length
+ std::move(aDXArray), // generated DXArray
+ {},
+ fontAttribute,
+ Application::GetSettings().GetLanguageTag().getLocale(),
+ color.getBColor());
+ }
+ drawinglayer::primitive2d::Primitive2DReference aPrimitiveText(pBaseText);
+ if (color.IsTransparent())
+ {
+ aPrimitiveText = new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
+ drawinglayer::primitive2d::Primitive2DContainer { aPrimitiveText },
+ (255 - color.GetAlpha()) / 255.0);
+ }
+ mrTargetHolders.Current().append(
+ new drawinglayer::primitive2d::TransformPrimitive2D(
+ maMapTransform,
+ drawinglayer::primitive2d::Primitive2DContainer { aPrimitiveText } ));
+ }
+
+ // update pos
+ pos += aLength;
+ }
+ }
+ else
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+\tTODO: fonts (non-unicode glyphs chars)");
+ }
+ break;
+ }
+ default:
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+ TODO unhandled record type: 0x" << std::hex << type << std::dec);
+ }
+ }
+ }
+
+ rMS.Seek(next);
+
+ if (size <= length)
+ {
+ length -= size;
+ }
+ else
+ {
+ SAL_WARN("drawinglayer.emf", "ImplRenderer::processEMFPlus: "
+ "size " << size << " > length " << length);
+ length = 0;
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfphelperdata.hxx b/drawinglayer/source/tools/emfphelperdata.hxx
new file mode 100644
index 0000000000..cf9e3b8855
--- /dev/null
+++ b/drawinglayer/source/tools/emfphelperdata.hxx
@@ -0,0 +1,270 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <wmfemfhelper.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <drawinglayer/attribute/linestartendattribute.hxx>
+#include <tools/stream.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <map>
+
+// predefines
+class SvStream;
+namespace basegfx { class B2DPolyPolygon; }
+
+namespace emfplushelper
+{
+ // EMF+ commands
+ #define EmfPlusRecordTypeHeader 0x4001
+ #define EmfPlusRecordTypeEndOfFile 0x4002
+ #define EmfPlusRecordTypeComment 0x4003
+ #define EmfPlusRecordTypeGetDC 0x4004
+ //TODO EmfPlusRecordTypeMultiFormatStart 0x4005
+ //TODO EmfPlusRecordTypeMultiFormatSection 0x4006
+ //TODO EmfPlusRecordTypeMultiFormatEnd 0x4007
+ #define EmfPlusRecordTypeObject 0x4008
+ //TODO EmfPlusRecordTypeClear 0x4009
+ #define EmfPlusRecordTypeFillRects 0x400A
+ #define EmfPlusRecordTypeDrawRects 0x400B
+ #define EmfPlusRecordTypeFillPolygon 0x400C
+ #define EmfPlusRecordTypeDrawLines 0x400D
+ #define EmfPlusRecordTypeFillEllipse 0x400E
+ #define EmfPlusRecordTypeDrawEllipse 0x400F
+ #define EmfPlusRecordTypeFillPie 0x4010
+ #define EmfPlusRecordTypeDrawPie 0x4011
+ #define EmfPlusRecordTypeDrawArc 0x4012
+ #define EmfPlusRecordTypeFillRegion 0x4013
+ #define EmfPlusRecordTypeFillPath 0x4014
+ #define EmfPlusRecordTypeDrawPath 0x4015
+ #define EmfPlusRecordTypeFillClosedCurve 0x4016
+ #define EmfPlusRecordTypeDrawClosedCurve 0x4017
+ #define EmfPlusRecordTypeDrawCurve 0x4018
+ #define EmfPlusRecordTypeDrawBeziers 0x4019
+ #define EmfPlusRecordTypeDrawImage 0x401A
+ #define EmfPlusRecordTypeDrawImagePoints 0x401B
+ #define EmfPlusRecordTypeDrawString 0x401C
+ #define EmfPlusRecordTypeSetRenderingOrigin 0x401D
+ #define EmfPlusRecordTypeSetAntiAliasMode 0x401E
+ #define EmfPlusRecordTypeSetTextRenderingHint 0x401F
+ #define EmfPlusRecordTypeSetTextContrast 0x4020
+ #define EmfPlusRecordTypeSetInterpolationMode 0x4021
+ #define EmfPlusRecordTypeSetPixelOffsetMode 0x4022
+ //TODO EmfPlusRecordTypeSetCompositingMode 0x4023
+ #define EmfPlusRecordTypeSetCompositingQuality 0x4024
+ #define EmfPlusRecordTypeSave 0x4025
+ #define EmfPlusRecordTypeRestore 0x4026
+ #define EmfPlusRecordTypeBeginContainer 0x4027
+ #define EmfPlusRecordTypeBeginContainerNoParams 0x4028
+ #define EmfPlusRecordTypeEndContainer 0x4029
+ #define EmfPlusRecordTypeSetWorldTransform 0x402A
+ #define EmfPlusRecordTypeResetWorldTransform 0x402B
+ #define EmfPlusRecordTypeMultiplyWorldTransform 0x402C
+ #define EmfPlusRecordTypeTranslateWorldTransform 0x402D
+ #define EmfPlusRecordTypeScaleWorldTransform 0x402E
+ #define EmfPlusRecordTypeRotateWorldTransform 0x402F
+ #define EmfPlusRecordTypeSetPageTransform 0x4030
+ #define EmfPlusRecordTypeResetClip 0x4031
+ #define EmfPlusRecordTypeSetClipRect 0x4032
+ #define EmfPlusRecordTypeSetClipPath 0x4033
+ #define EmfPlusRecordTypeSetClipRegion 0x4034
+ #define EmfPlusRecordTypeOffsetClip 0x4035
+ #define EmfPlusRecordTypeDrawDriverString 0x4036
+ //TODO EmfPlusRecordTypeStrokeFillPath 0x4037
+ //TODO EmfPlusRecordTypeSerializableObject 0x4038
+ //TODO EmfPlusRecordTypeSetTSGraphics 0x4039
+ //TODO EmfPlusRecordTypeSetTSClip 0x403A
+
+ // EMF+object types
+ #define EmfPlusObjectTypeBrush 0x100
+ #define EmfPlusObjectTypePen 0x200
+ #define EmfPlusObjectTypePath 0x300
+ #define EmfPlusObjectTypeRegion 0x400
+ #define EmfPlusObjectTypeImage 0x500
+ #define EmfPlusObjectTypeFont 0x600
+ #define EmfPlusObjectTypeStringFormat 0x700
+ #define EmfPlusObjectTypeImageAttributes 0x800
+ #define EmfPlusObjectTypeCustomLineCap 0x900
+
+ enum PixelOffsetMode
+ {
+ PixelOffsetModeDefault = 0x00,
+ PixelOffsetModeHighSpeed = 0x01,
+ PixelOffsetModeHighQuality = 0x02,
+ PixelOffsetModeNone = 0x03,
+ PixelOffsetModeHalf = 0x04
+ };
+
+ enum SmoothingMode
+ {
+ SmoothingModeDefault = 0x00,
+ SmoothingModeHighSpeed = 0x01,
+ SmoothingModeHighQuality = 0x02,
+ SmoothingModeNone = 0x03,
+ SmoothingModeAntiAlias8x4 = 0x04,
+ SmoothingModeAntiAlias8x8 = 0x05
+ };
+
+ enum InterpolationMode
+ {
+ InterpolationModeDefault = 0x00,
+ InterpolationModeLowQuality = 0x01,
+ InterpolationModeHighQuality = 0x02,
+ InterpolationModeBilinear = 0x03,
+ InterpolationModeBicubic = 0x04,
+ InterpolationModeNearestNeighbor = 0x05,
+ InterpolationModeHighQualityBilinear = 0x06,
+ InterpolationModeHighQualityBicubic = 0x07
+ };
+
+ enum TextRenderingHint
+ {
+ TextRenderingHintSystemDefault = 0x00,
+ TextRenderingHintSingleBitPerPixelGridFit = 0x01,
+ TextRenderingHintSingleBitPerPixel = 0x02,
+ TextRenderingHintAntialiasGridFit = 0x03,
+ TextRenderingHintAntialias = 0x04,
+ TextRenderingHintClearTypeGridFit = 0x05
+ };
+
+ enum UnitType
+ {
+ UnitTypeWorld = 0x00,
+ UnitTypeDisplay = 0x01,
+ UnitTypePixel = 0x02,
+ UnitTypePoint = 0x03,
+ UnitTypeInch = 0x04,
+ UnitTypeDocument = 0x05,
+ UnitTypeMillimeter = 0x06
+ };
+
+ enum EmfPlusCombineMode
+ {
+ EmfPlusCombineModeReplace = 0x00000000,
+ EmfPlusCombineModeIntersect = 0x00000001,
+ EmfPlusCombineModeUnion = 0x00000002,
+ EmfPlusCombineModeXOR = 0x00000003,
+ EmfPlusCombineModeExclude = 0x00000004,
+ EmfPlusCombineModeComplement = 0x00000005
+ };
+
+ const char* emfTypeToName(sal_uInt16 type);
+ OUString UnitTypeToString(sal_uInt16 nType);
+
+ struct EMFPObject
+ {
+ virtual ~EMFPObject();
+ };
+
+ typedef std::map<int, wmfemfhelper::PropertyHolder> GraphicStateMap;
+
+ struct EmfPlusHelperData
+ {
+ private:
+ /* EMF+ */
+ basegfx::B2DHomMatrix maBaseTransform;
+ basegfx::B2DHomMatrix maWorldTransform;
+ basegfx::B2DHomMatrix maMapTransform;
+
+ std::unique_ptr<EMFPObject> maEMFPObjects[256];
+ float mfPageScale;
+ sal_Int32 mnOriginX;
+ sal_Int32 mnOriginY;
+ sal_uInt32 mnHDPI;
+ sal_uInt32 mnVDPI;
+ bool mbSetTextContrast;
+ sal_uInt16 mnTextContrast;
+
+ /* EMF+ emf header info */
+ sal_Int32 mnFrameLeft;
+ sal_Int32 mnFrameTop;
+ sal_Int32 mnFrameRight;
+ sal_Int32 mnFrameBottom;
+ sal_Int32 mnPixX;
+ sal_Int32 mnPixY;
+ sal_Int32 mnMmX;
+ sal_Int32 mnMmY;
+
+ /* multipart object data */
+ bool mbMultipart;
+ sal_uInt16 mMFlags;
+ SvMemoryStream mMStream;
+
+ /* emf+ graphic state stack */
+ GraphicStateMap mGSStack;
+ GraphicStateMap mGSContainerStack;
+
+ /* Performance optimizators */
+ /* Extracted Scale values from Transformation Matrix */
+ double mdExtractedXScale;
+ double mdExtractedYScale;
+
+ /// data holders
+ wmfemfhelper::TargetHolders& mrTargetHolders;
+ wmfemfhelper::PropertyHolders& mrPropertyHolders;
+ wmfemfhelper::PropertyHolder aGetDCState;
+ bool bIsGetDCProcessing;
+
+ // readers
+ void processObjectRecord(SvMemoryStream& rObjectStream, sal_uInt16 flags, sal_uInt32 dataSize, bool bUseWholeStream = false);
+ static void ReadPoint(SvStream& s, float& x, float& y, sal_uInt32 flags);
+
+ // internal mapper
+ void mappingChanged();
+
+ // stack actions
+ void GraphicStatePush(GraphicStateMap& map, sal_Int32 index);
+ void GraphicStatePop(GraphicStateMap& map, sal_Int32 index);
+
+ drawinglayer::attribute::LineStartEndAttribute CreateLineEnd(const sal_Int32 aCap,
+ const float aPenWidth) const;
+
+ // primitive creators
+ void EMFPPlusDrawPolygon(const ::basegfx::B2DPolyPolygon& polygon, sal_uInt32 penIndex);
+ void EMFPPlusFillPolygon(const ::basegfx::B2DPolyPolygon& polygon, const bool isColor, const sal_uInt32 brushIndexOrColor);
+ void EMFPPlusFillPolygonSolidColor(const ::basegfx::B2DPolyPolygon& polygon, Color const& color);
+
+ // helper functions
+ Color EMFPGetBrushColorOrARGBColor(const sal_uInt16 flags, const sal_uInt32 brushIndexOrColor) const;
+
+ public:
+ EmfPlusHelperData(
+ SvMemoryStream& rMS,
+ wmfemfhelper::TargetHolders& rTargetHolders,
+ wmfemfhelper::PropertyHolders& rPropertyHolders);
+ ~EmfPlusHelperData();
+
+ void processEmfPlusData(
+ SvMemoryStream& rMS,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation);
+
+ // mappers
+ ::basegfx::B2DPoint Map(double ix, double iy) const;
+
+ // readers
+ static void ReadRectangle(SvStream& s, float& x, float& y, float &width, float& height, bool bCompressed = false);
+ static bool readXForm(SvStream& rIn, basegfx::B2DHomMatrix& rTarget);
+ static ::basegfx::B2DPolyPolygon combineClip(::basegfx::B2DPolyPolygon const & leftPolygon, int combineMode, ::basegfx::B2DPolyPolygon const & rightPolygon);
+
+ static float getUnitToPixelMultiplier(const UnitType aUnitType, const sal_uInt32 aDPI);
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfpimage.cxx b/drawinglayer/source/tools/emfpimage.cxx
new file mode 100644
index 0000000000..67a0cef99e
--- /dev/null
+++ b/drawinglayer/source/tools/emfpimage.cxx
@@ -0,0 +1,80 @@
+/* -*- 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 <vcl/graphicfilter.hxx>
+#include <sal/log.hxx>
+#include "emfpimage.hxx"
+
+namespace emfplushelper
+{
+ void EMFPImage::Read(SvMemoryStream &s, sal_uInt32 dataSize, bool bUseWholeStream)
+ {
+ sal_uInt32 header, bitmapType;
+ s.ReadUInt32(header).ReadUInt32(type);
+ SAL_INFO("drawinglayer.emf", "EMF+\timage\nEMF+\theader: 0x" << std::hex << header << " type: " << type << std::dec);
+
+ if (ImageDataTypeBitmap == type)
+ {
+ // bitmap
+ s.ReadInt32(width).ReadInt32(height).ReadInt32(stride).ReadUInt32(pixelFormat).ReadUInt32(bitmapType);
+ SAL_INFO("drawinglayer.emf", "EMF+\tbitmap width: " << width << " height: " << height << " stride: " << stride << " pixelFormat: 0x" << std::hex << pixelFormat << " bitmapType: 0x" << bitmapType << std::dec);
+
+ if ((bitmapType != 0) || (width == 0))
+ {
+ // non native formats
+ GraphicFilter filter;
+ filter.ImportGraphic(graphic, u"", s);
+ SAL_INFO("drawinglayer.emf", "EMF+\tbitmap width: " << graphic.GetSizePixel().Width() << " height: " << graphic.GetSizePixel().Height());
+ }
+ }
+ else if (ImageDataTypeMetafile == type)
+ {
+ // metafile
+ sal_uInt32 mfType, mfSize;
+ s.ReadUInt32(mfType).ReadUInt32(mfSize);
+
+ if (bUseWholeStream)
+ dataSize = s.remainingSize();
+ else
+ dataSize -= 16;
+
+ SAL_INFO("drawinglayer.emf", "EMF+\tmetafile type: " << mfType << " dataSize: " << mfSize << " real size calculated from record dataSize: " << dataSize);
+
+ GraphicFilter filter;
+ // workaround buggy metafiles, which have wrong mfSize set (n#705956 for example)
+ SvMemoryStream mfStream(const_cast<char *>(static_cast<char const *>(s.GetData()) + s.Tell()), dataSize, StreamMode::READ);
+ filter.ImportGraphic(graphic, u"", mfStream);
+
+ // debug code - write the stream to debug file /tmp/emf-stream.emf
+#if OSL_DEBUG_LEVEL > 1
+ mfStream.Seek(0);
+ static sal_Int32 emfp_debug_stream_number = 0;
+ OUString emfp_debug_filename = "/tmp/emf-embedded-stream" +
+ OUString::number(emfp_debug_stream_number++) + ".emf";
+
+ SvFileStream file(emfp_debug_filename, StreamMode::WRITE | StreamMode::TRUNC);
+
+ mfStream.WriteStream(file);
+ file.Flush();
+ file.Close();
+#endif
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfpimage.hxx b/drawinglayer/source/tools/emfpimage.hxx
new file mode 100644
index 0000000000..75e42e7d21
--- /dev/null
+++ b/drawinglayer/source/tools/emfpimage.hxx
@@ -0,0 +1,48 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include "emfphelperdata.hxx"
+#include <vcl/graph.hxx>
+
+namespace emfplushelper
+{
+
+ typedef enum
+ {
+ ImageDataTypeUnknown = 0x00000000,
+ ImageDataTypeBitmap = 0x00000001,
+ ImageDataTypeMetafile = 0x00000002
+ } ImageDataType;
+
+ struct EMFPImage : public EMFPObject
+ {
+ sal_uInt32 type;
+ sal_Int32 width;
+ sal_Int32 height;
+ sal_Int32 stride;
+ sal_uInt32 pixelFormat;
+ Graphic graphic;
+
+ void Read(SvMemoryStream &s, sal_uInt32 dataSize, bool bUseWholeStream);
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfpimageattributes.cxx b/drawinglayer/source/tools/emfpimageattributes.cxx
new file mode 100644
index 0000000000..c13da361bf
--- /dev/null
+++ b/drawinglayer/source/tools/emfpimageattributes.cxx
@@ -0,0 +1,73 @@
+/* -*- 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 <sal/log.hxx>
+
+#include "emfpimageattributes.hxx"
+
+using namespace ::com::sun::star;
+using namespace ::basegfx;
+
+namespace emfplushelper
+{
+EMFPImageAttributes::EMFPImageAttributes()
+ : EMFPObject()
+ , wrapMode(0)
+ , objectClamp(0)
+{
+}
+
+EMFPImageAttributes::~EMFPImageAttributes() {}
+
+void EMFPImageAttributes::Read(SvStream& s)
+{
+ sal_uInt32 graphicsVersion, reserved1, reserved2, tempClampColor;
+ sal_uInt8 clampColorBlue, clampColorGreen, clampColorRed, clampColorAlpha;
+
+ s.ReadUInt32(graphicsVersion)
+ .ReadUInt32(reserved1)
+ .ReadUInt32(wrapMode)
+ .ReadUInt32(tempClampColor)
+ .ReadUInt32(objectClamp)
+ .ReadUInt32(reserved2);
+
+ clampColorBlue = tempClampColor >> 24;
+ clampColorGreen = (tempClampColor & 0x00FFFFFF) >> 16;
+ clampColorRed = (tempClampColor & 0x0000FFFF) >> 8;
+ clampColorAlpha = tempClampColor & 0x000000FF;
+
+ clampColor.SetRed(clampColorRed);
+ clampColor.SetGreen(clampColorGreen);
+ clampColor.SetBlue(clampColorBlue);
+ clampColor.SetAlpha(255 - clampColorAlpha);
+
+ SAL_INFO("drawinglayer.emf", "EMF+\timage attributes");
+ SAL_WARN_IF((reserved1 != 0) || (reserved2 != 0), "drawinglayer.emf",
+ "Reserved field(s) not zero - reserved1: " << std::hex << reserved1
+ << " reserved2: " << reserved2);
+ SAL_WARN_IF((objectClamp != EmpPlusRectClamp) && (objectClamp != EmpPlusBitmapClamp),
+ "drawinglayer.emf", "Invalid object clamp - set to" << std::hex << objectClamp);
+ SAL_INFO("drawinglayer.emf", "EMF+\t image graphics version: 0x"
+ << std::hex << graphicsVersion << " wrap mode: " << wrapMode
+ << " clamp color: " << clampColor
+ << " object clamp: " << objectClamp);
+}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfpimageattributes.hxx b/drawinglayer/source/tools/emfpimageattributes.hxx
new file mode 100644
index 0000000000..a8ba375538
--- /dev/null
+++ b/drawinglayer/source/tools/emfpimageattributes.hxx
@@ -0,0 +1,33 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include "emfphelperdata.hxx"
+
+namespace emfplushelper
+{
+const sal_uInt32 EmpPlusRectClamp = 0x00000000;
+const sal_uInt32 EmpPlusBitmapClamp = 0x00000001;
+
+struct EMFPImageAttributes : public EMFPObject
+{
+ sal_uInt32 wrapMode;
+ Color clampColor;
+ sal_uInt32 objectClamp;
+
+ EMFPImageAttributes();
+
+ virtual ~EMFPImageAttributes() override;
+
+ void Read(SvStream& s);
+};
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfplushelper.cxx b/drawinglayer/source/tools/emfplushelper.cxx
new file mode 100644
index 0000000000..87276d9988
--- /dev/null
+++ b/drawinglayer/source/tools/emfplushelper.cxx
@@ -0,0 +1,45 @@
+/* -*- 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 <emfplushelper.hxx>
+#include "emfphelperdata.hxx"
+
+namespace emfplushelper
+{
+ EmfPlusHelper::EmfPlusHelper(
+ SvMemoryStream& rMS,
+ wmfemfhelper::TargetHolders& rTargetHolders,
+ wmfemfhelper::PropertyHolders& rPropertyHolders)
+ : mpD(new EmfPlusHelperData(rMS, rTargetHolders, rPropertyHolders))
+ {
+ }
+
+ EmfPlusHelper::~EmfPlusHelper()
+ {
+ }
+
+ void EmfPlusHelper::processEmfPlusData(
+ SvMemoryStream& rMS,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation)
+ {
+ mpD->processEmfPlusData(rMS, rViewInformation);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfppath.cxx b/drawinglayer/source/tools/emfppath.cxx
new file mode 100644
index 0000000000..e7c4a5512c
--- /dev/null
+++ b/drawinglayer/source/tools/emfppath.cxx
@@ -0,0 +1,320 @@
+/* -*- 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 <basegfx/point/b2dpoint.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <sal/log.hxx>
+#include "emfppath.hxx"
+
+namespace
+{
+ const unsigned char nTopBitInt7 = 0x80;
+ const unsigned char nSignBitInt7 = 0x40;
+ // include the sign bit so if it's negative we get
+ // that "missing" bit pre-set to 1
+ const unsigned char nValueMaskInt7 = 0x7F;
+}
+
+namespace emfplushelper
+{
+ typedef double matrix [4][4];
+
+ constexpr sal_uInt32 nDetails = 8;
+ constexpr double alpha[nDetails]
+ = { 1. / nDetails, 2. / nDetails, 3. / nDetails, 4. / nDetails,
+ 5. / nDetails, 6. / nDetails, 7. / nDetails, 8. / nDetails };
+
+ // see 2.2.2.21 EmfPlusInteger7
+ // 2.2.2.22 EmfPlusInteger15
+ // and 2.2.2.37 EmfPlusPointR Object
+ static sal_Int16 GetEmfPlusInteger(SvStream& s)
+ {
+ unsigned char u8(0);
+ s.ReadUChar(u8);
+
+ bool bIsEmfPlusInteger15 = u8 & nTopBitInt7;
+ bool bNegative = u8 & nSignBitInt7;
+ unsigned char val1 = u8 & nValueMaskInt7;
+ if (bNegative)
+ val1 |= nTopBitInt7;
+ if (!bIsEmfPlusInteger15)
+ {
+ return static_cast<signed char>(val1);
+ }
+
+ s.ReadUChar(u8);
+ sal_uInt16 nRet = (val1 << 8) | u8;
+ return static_cast<sal_Int16>(nRet);
+ }
+
+ EMFPPath::EMFPPath (sal_uInt32 _nPoints, bool bLines)
+ {
+ if (_nPoints > SAL_MAX_UINT32 / (2 * sizeof(float)))
+ {
+ _nPoints = SAL_MAX_UINT32 / (2 * sizeof(float));
+ }
+
+ nPoints = _nPoints;
+
+ if (!bLines)
+ pPointTypes.reset( new sal_uInt8 [_nPoints] );
+ }
+
+ EMFPPath::~EMFPPath ()
+ {
+ }
+
+ void EMFPPath::Read (SvStream& s, sal_uInt32 pathFlags)
+ {
+ float fx, fy;
+ for (sal_uInt32 i = 0; i < nPoints; i++)
+ {
+ if (pathFlags & 0x800)
+ {
+ // EMFPlusPointR: points are stored in EMFPlusInteger7 or
+ // EMFPlusInteger15 objects, see section 2.2.2.21/22
+ // If 0x800 bit is set, the 0x4000 bit is undefined and must be ignored
+ sal_Int32 x = GetEmfPlusInteger(s);
+ sal_Int32 y = GetEmfPlusInteger(s);
+ xPoints.push_back(x);
+ yPoints.push_back(y);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t" << i << ". EmfPlusPointR [x,y]: " << x << ", " << y);
+ }
+ else if (pathFlags & 0x4000)
+ {
+ // EMFPlusPoint: stored in signed short 16bit integer format
+ sal_Int16 x, y;
+
+ s.ReadInt16(x).ReadInt16(y);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t" << i << ". EmfPlusPoint [x,y]: " << x << ", " << y);
+ xPoints.push_back(x);
+ yPoints.push_back(y);
+ }
+ else
+ {
+ // EMFPlusPointF: stored in Single (float) format
+ s.ReadFloat(fx).ReadFloat(fy);
+ SAL_INFO("drawinglayer.emf", "EMF+\t" << i << ". EMFPlusPointF [x,y]: " << fx << ", " << fy);
+ xPoints.push_back(fx);
+ yPoints.push_back(fy);
+ }
+ }
+
+ if (pPointTypes)
+ {
+ for (sal_uInt32 i = 0; i < nPoints; i++)
+ {
+ s.ReadUChar(pPointTypes[i]);
+ SAL_INFO("drawinglayer.emf", "EMF+\tpoint type: 0x" << std::hex << static_cast<int>(pPointTypes[i]) << std::dec);
+ }
+ }
+
+ aPolygon.clear();
+ }
+
+ ::basegfx::B2DPolyPolygon& EMFPPath::GetPolygon (EmfPlusHelperData const & rR, bool bMapIt, bool bAddLineToCloseShape)
+ {
+ ::basegfx::B2DPolygon polygon;
+ aPolygon.clear ();
+ sal_uInt32 last_normal = 0, p = 0;
+ ::basegfx::B2DPoint prev, mapped;
+ bool hasPrev = false;
+
+ for (sal_uInt32 i = 0; i < nPoints; i++)
+ {
+ if (p && pPointTypes && (pPointTypes [i] == 0))
+ {
+ aPolygon.append (polygon);
+ last_normal = i;
+ p = 0;
+ polygon.clear ();
+ }
+
+ if (bMapIt)
+ mapped = rR.Map(xPoints[i], yPoints [i]);
+ else
+ mapped = ::basegfx::B2DPoint(xPoints[i], yPoints[i]);
+
+ if (pPointTypes)
+ {
+ if ((pPointTypes [i] & 0x07) == 3)
+ {
+ if (((i - last_normal )% 3) == 1)
+ {
+ polygon.setNextControlPoint (p - 1, mapped);
+ SAL_INFO ("drawinglayer.emf", "EMF+\t\tPolygon append next: " << p - 1 << " mapped: " << mapped.getX () << "," << mapped.getY ());
+ continue;
+ }
+ else if (((i - last_normal) % 3) == 2)
+ {
+ prev = mapped;
+ hasPrev = true;
+ continue;
+ }
+ }
+ else
+ {
+ last_normal = i;
+ }
+ }
+
+ polygon.append (mapped);
+ SAL_INFO ("drawinglayer.emf", "EMF+\t\tPoint: " << xPoints[i] << "," << yPoints[i] << " mapped: " << mapped.getX () << ":" << mapped.getY ());
+
+ if (hasPrev)
+ {
+ polygon.setPrevControlPoint (p, prev);
+ SAL_INFO ("drawinglayer.emf", "EMF+\t\tPolygon append prev: " << p << " mapped: " << prev.getX () << "," << prev.getY ());
+ hasPrev = false;
+ }
+
+ p++;
+
+ if (pPointTypes && (pPointTypes [i] & 0x80)) // closed polygon
+ {
+ polygon.setClosed (true);
+ aPolygon.append (polygon);
+ SAL_INFO ("drawinglayer.emf", "EMF+\t\tClose polygon");
+ last_normal = i + 1;
+ p = 0;
+ polygon.clear ();
+ }
+ }
+
+ // Draw an extra line between the last point and the first point, to close the shape.
+ if (bAddLineToCloseShape)
+ {
+ polygon.setClosed (true);
+ }
+
+ if (polygon.count ())
+ {
+ aPolygon.append (polygon);
+
+#if OSL_DEBUG_LEVEL > 1
+ for (unsigned int i=0; i<aPolygon.count(); i++) {
+ polygon = aPolygon.getB2DPolygon(i);
+ SAL_INFO ("drawinglayer.emf", "EMF+\t\tPolygon: " << i);
+ for (unsigned int j=0; j<polygon.count(); j++) {
+ ::basegfx::B2DPoint point = polygon.getB2DPoint(j);
+ SAL_INFO ("drawinglayer.emf", "EMF+\t\t\tPoint: " << point.getX() << "," << point.getY());
+ if (polygon.isPrevControlPointUsed(j)) {
+ point = polygon.getPrevControlPoint(j);
+ SAL_INFO ("drawinglayer.emf", "EMF+\t\t\tPrev: " << point.getX() << "," << point.getY());
+ }
+ if (polygon.isNextControlPointUsed(j)) {
+ point = polygon.getNextControlPoint(j);
+ SAL_INFO ("drawinglayer.emf", "EMF+\t\t\tNext: " << point.getX() << "," << point.getY());
+ }
+ }
+ }
+#endif
+ }
+
+ return aPolygon;
+ }
+
+ static void GetCardinalMatrix(float tension, matrix& m)
+ {
+ m[0][1] = 2. - tension;
+ m[0][2] = tension - 2.;
+ m[1][0] = 2. * tension;
+ m[1][1] = tension - 3.;
+ m[1][2] = 3. - 2. * tension;
+ m[3][1] = 1.;
+ m[0][3] = m[2][2] = tension;
+ m[0][0] = m[1][3] = m[2][0] = -tension;
+ m[2][1] = m[2][3] = m[3][0] = m[3][2] = m[3][3] = 0.;
+ }
+
+ static double calculateSplineCoefficients(float p0, float p1, float p2, float p3, sal_uInt32 step, matrix m)
+ {
+ double a = m[0][0] * p0 + m[0][1] * p1 + m[0][2] * p2 + m[0][3] * p3;
+ double b = m[1][0] * p0 + m[1][1] * p1 + m[1][2] * p2 + m[1][3] * p3;
+ double c = m[2][0] * p0 + m[2][2] * p2;
+ double d = p1;
+ return (d + alpha[step] * (c + alpha[step] * (b + alpha[step] * a)));
+ }
+
+ ::basegfx::B2DPolyPolygon& EMFPPath::GetCardinalSpline(EmfPlusHelperData const& rR, float fTension,
+ sal_uInt32 aOffset, sal_uInt32 aNumSegments)
+ {
+ ::basegfx::B2DPolygon polygon;
+ matrix mat;
+ double x, y;
+ if (aNumSegments >= nPoints)
+ aNumSegments = nPoints - 1;
+ GetCardinalMatrix(fTension, mat);
+ // duplicate first point
+ xPoints.push_front(xPoints.front());
+ yPoints.push_front(yPoints.front());
+ // duplicate last point
+ xPoints.push_back(xPoints.back());
+ yPoints.push_back(yPoints.back());
+
+ for (sal_uInt32 i = 3 + aOffset; i < aNumSegments + 3; i++)
+ {
+ for (sal_uInt32 s = 0; s < nDetails; s++)
+ {
+ x = calculateSplineCoefficients(xPoints[i - 3], xPoints[i - 2], xPoints[i - 1],
+ xPoints[i], s, mat);
+ y = calculateSplineCoefficients(yPoints[i - 3], yPoints[i - 2], yPoints[i - 1],
+ yPoints[i], s, mat);
+ polygon.append(rR.Map(x, y));
+ }
+ }
+ if (polygon.count())
+ aPolygon.append(polygon);
+ return aPolygon;
+ }
+
+ ::basegfx::B2DPolyPolygon& EMFPPath::GetClosedCardinalSpline(EmfPlusHelperData const& rR, float fTension)
+ {
+ ::basegfx::B2DPolygon polygon;
+ matrix mat;
+ double x, y;
+ GetCardinalMatrix(fTension, mat);
+ // add three first points at the end
+ xPoints.push_back(xPoints[0]);
+ yPoints.push_back(yPoints[0]);
+ xPoints.push_back(xPoints[1]);
+ yPoints.push_back(yPoints[1]);
+ xPoints.push_back(xPoints[2]);
+ yPoints.push_back(yPoints[2]);
+
+ for (sal_uInt32 i = 3; i < nPoints + 3; i++)
+ {
+ for (sal_uInt32 s = 0; s < nDetails; s++)
+ {
+ x = calculateSplineCoefficients(xPoints[i - 3], xPoints[i - 2], xPoints[i - 1],
+ xPoints[i], s, mat);
+ y = calculateSplineCoefficients(yPoints[i - 3], yPoints[i - 2], yPoints[i - 1],
+ yPoints[i], s, mat);
+ polygon.append(rR.Map(x, y));
+ }
+ }
+ polygon.setClosed(true);
+ if (polygon.count())
+ aPolygon.append(polygon);
+ return aPolygon;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfppath.hxx b/drawinglayer/source/tools/emfppath.hxx
new file mode 100644
index 0000000000..e6104fcb1f
--- /dev/null
+++ b/drawinglayer/source/tools/emfppath.hxx
@@ -0,0 +1,47 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include "emfphelperdata.hxx"
+
+namespace emfplushelper
+{
+ class EMFPPath : public EMFPObject
+ {
+ ::basegfx::B2DPolyPolygon aPolygon;
+ sal_uInt32 nPoints;
+ std::deque<float> xPoints, yPoints;
+ std::unique_ptr<sal_uInt8[]> pPointTypes;
+
+ public:
+ EMFPPath(sal_uInt32 _nPoints, bool bLines = false);
+
+ virtual ~EMFPPath() override;
+
+ void Read(SvStream& s, sal_uInt32 pathFlags);
+
+ ::basegfx::B2DPolyPolygon& GetPolygon(EmfPlusHelperData const & rR, bool bMapIt = true, bool bAddLineToCloseShape = false);
+ ::basegfx::B2DPolyPolygon& GetCardinalSpline(EmfPlusHelperData const& rR, float fTension,
+ sal_uInt32 aOffset, sal_uInt32 aNumSegments);
+ ::basegfx::B2DPolyPolygon& GetClosedCardinalSpline(EmfPlusHelperData const& rR, float fTension);
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfppen.cxx b/drawinglayer/source/tools/emfppen.cxx
new file mode 100644
index 0000000000..adfee3bd37
--- /dev/null
+++ b/drawinglayer/source/tools/emfppen.cxx
@@ -0,0 +1,382 @@
+/* -*- 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 <com/sun/star/rendering/PathCapType.hpp>
+#include <o3tl/safeint.hxx>
+#include <sal/log.hxx>
+#include <rtl/ustrbuf.hxx>
+
+#include "emfppen.hxx"
+#include "emfpcustomlinecap.hxx"
+
+using namespace ::com::sun::star;
+using namespace ::basegfx;
+
+namespace emfplushelper
+{
+
+ EMFPPen::EMFPPen()
+ : penDataFlags(0)
+ , penUnit(0)
+ , penWidth(0.0)
+ , startCap(0)
+ , endCap(0)
+ , maLineJoin(basegfx::B2DLineJoin::Miter)
+ , fMiterMinimumAngle(basegfx::deg2rad(5.0))
+ , dashStyle(0)
+ , dashCap(0)
+ , dashOffset(0.0)
+ , alignment(0)
+ , customStartCapLen(0)
+ , customEndCapLen(0)
+ {
+ }
+
+ EMFPPen::~EMFPPen()
+ {
+ }
+
+ static OUString PenDataFlagsToString(sal_uInt32 flags)
+ {
+ rtl::OUStringBuffer sFlags;
+
+ if (flags & EmfPlusPenDataTransform)
+ sFlags.append("\nEMF+\t\t\tEmfPlusPenDataTransform");
+
+ if (flags & EmfPlusPenDataStartCap)
+ sFlags.append("\nEMF+\t\t\tEmfPlusPenDataStartCap");
+
+ if (flags & EmfPlusPenDataEndCap)
+ sFlags.append("\nEMF+\t\t\tEmfPlusPenDataEndCap");
+
+ if (flags & EmfPlusPenDataJoin)
+ sFlags.append("\nEMF+\t\t\tEmfPlusPenDataJoin");
+
+ if (flags & EmfPlusPenDataMiterLimit)
+ sFlags.append("\nEMF+\t\t\tEmfPlusPenDataMiterLimit");
+
+ if (flags & EmfPlusPenDataLineStyle)
+ sFlags.append("\nEMF+\t\t\tEmfPlusPenDataLineStyle");
+
+ if (flags & EmfPlusPenDataDashedLineCap)
+ sFlags.append("\nEMF+\t\t\tEmfPlusPenDataDashedLineCap");
+
+ if (flags & EmfPlusPenDataDashedLineOffset)
+ sFlags.append("\nEMF+\t\t\tEmfPlusPenDataDashedLineOffset");
+
+ if (flags & EmfPlusPenDataDashedLine)
+ sFlags.append("\nEMF+\t\t\tEmfPlusPenDataDashedLine");
+
+ if (flags & EmfPlusPenDataAlignment)
+ sFlags.append("\nEMF+\t\t\tEmfPlusPenDataAlignment");
+
+ if (flags & EmfPlusPenDataCompoundLine)
+ sFlags.append("\nEMF+\t\t\tEmfPlusPenDataCompoundLine");
+
+ if (flags & EmfPlusPenDataCustomStartCap)
+ sFlags.append("\nEMF+\t\t\tEmfPlusPenDataCustomStartCap");
+
+ if (flags & EmfPlusPenDataCustomEndCap)
+ sFlags.append("\nEMF+\t\t\tEmfPlusPenDataCustomEndCap");
+
+ return sFlags.makeStringAndClear();
+ }
+
+ static OUString LineCapTypeToString(sal_uInt32 linecap)
+ {
+ switch (linecap)
+ {
+ case LineCapTypeFlat: return "LineCapTypeFlat";
+ case LineCapTypeSquare: return "LineCapTypeSquare";
+ case LineCapTypeRound: return "LineCapTypeRound";
+ case LineCapTypeTriangle: return "LineCapTypeTriangle";
+ case LineCapTypeNoAnchor: return "LineCapTypeNoAnchor";
+ case LineCapTypeSquareAnchor: return "LineCapTypeSquareAnchor";
+ case LineCapTypeRoundAnchor: return "LineCapTypeRoundAchor";
+ case LineCapTypeDiamondAnchor: return "LineCapTypeDiamondAnchor";
+ case LineCapTypeArrowAnchor: return "LineCapTypeArrowAnchor";
+ case LineCapTypeAnchorMask: return "LineCapTypeAnchorMask";
+ case LineCapTypeCustom: return "LineCapTypeCustom";
+ }
+ return "";
+ }
+
+ static OUString DashedLineCapTypeToString(sal_uInt32 dashedlinecaptype)
+ {
+ switch (dashedlinecaptype)
+ {
+ case DashedLineCapTypeFlat: return "DashedLineCapTypeFlat";
+ case DashedLineCapTypeRound: return "DashedLineCapTypeRound";
+ case DashedLineCapTypeTriangle: return "DashedLineCapTypeTriangle";
+ }
+ return "";
+ }
+
+ static OUString PenAlignmentToString(sal_uInt32 alignment)
+ {
+ switch (alignment)
+ {
+ case PenAlignmentCenter: return "PenAlignmentCenter";
+ case PenAlignmentInset: return "PenAlignmentInset";
+ case PenAlignmentLeft: return "PenAlignmentLeft";
+ case PenAlignmentOutset: return "PenAlignmentOutset";
+ case PenAlignmentRight: return "PenAlignmentRight";
+ }
+ return "";
+ }
+
+ drawinglayer::attribute::StrokeAttribute
+ EMFPPen::GetStrokeAttribute(const double aTransformation) const
+ {
+ if (penDataFlags & EmfPlusPenDataLineStyle // pen has a predefined line style
+ && dashStyle != EmfPlusLineStyleCustom)
+ {
+ const double pw = aTransformation * penWidth;
+ switch (dashStyle)
+ {
+ case EmfPlusLineStyleDash:
+ // [-loplugin:redundantfcast] false positive:
+ return drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw });
+ case EmfPlusLineStyleDot:
+ // [-loplugin:redundantfcast] false positive:
+ return drawinglayer::attribute::StrokeAttribute({ pw, pw });
+ case EmfPlusLineStyleDashDot:
+ // [-loplugin:redundantfcast] false positive:
+ return drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw, pw, pw });
+ case EmfPlusLineStyleDashDotDot:
+ // [-loplugin:redundantfcast] false positive:
+ return drawinglayer::attribute::StrokeAttribute({ 3 * pw, pw, pw, pw, pw, pw });
+ }
+ }
+ else if (penDataFlags & EmfPlusPenDataDashedLine) // pen has a custom dash line
+ {
+ const double pw = aTransformation * penWidth;
+ // StrokeAttribute needs a double vector while the pen provides a float vector
+ std::vector<double> aPattern(dashPattern.size());
+ for (size_t i = 0; i < aPattern.size(); i++)
+ {
+ // convert from float to double and multiply with the adjusted pen width
+ aPattern[i] = pw * dashPattern[i];
+ }
+ return drawinglayer::attribute::StrokeAttribute(std::move(aPattern));
+ }
+ // EmfPlusLineStyleSolid: - do nothing special, use default stroke attribute
+ return drawinglayer::attribute::StrokeAttribute();
+ }
+
+ void EMFPPen::Read(SvStream& s, EmfPlusHelperData const & rR)
+ {
+ sal_Int32 lineJoin = EmfPlusLineJoinTypeMiter;
+ sal_uInt32 graphicsVersion, penType;
+ s.ReadUInt32(graphicsVersion).ReadUInt32(penType).ReadUInt32(penDataFlags).ReadUInt32(penUnit).ReadFloat(penWidth);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tGraphics version: 0x" << std::hex << graphicsVersion);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tType: " << penType);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tPen data flags: 0x" << penDataFlags << PenDataFlagsToString(penDataFlags));
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tUnit: " << UnitTypeToString(penUnit));
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tWidth: " << std::dec << penWidth);
+
+ // If a zero width is specified, a minimum value must be used, which is determined by the units
+ if (penWidth == 0.0)
+ { //TODO Check if these values is correct
+ penWidth = penUnit == 0 ? 0.18f
+ : 0.05f; // 0.05f is taken from old EMF+ implementation (case of Unit == Pixel etc.)
+ }
+
+ if (penDataFlags & EmfPlusPenDataTransform)
+ {
+ EmfPlusHelperData::readXForm(s, pen_transformation);
+ SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO PenDataTransform: " << pen_transformation);
+ }
+
+ if (penDataFlags & EmfPlusPenDataStartCap)
+ {
+ s.ReadInt32(startCap);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tstartCap: " << LineCapTypeToString(startCap) << " (0x" << std::hex << startCap << ")");
+ }
+ else
+ {
+ startCap = 0;
+ }
+
+ if (penDataFlags & EmfPlusPenDataEndCap)
+ {
+ s.ReadInt32(endCap);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tendCap: " << LineCapTypeToString(endCap) << " (0x" << std::hex << startCap << ")");
+ }
+ else
+ {
+ endCap = 0;
+ }
+
+ if (penDataFlags & EmfPlusPenDataJoin)
+ {
+ s.ReadInt32(lineJoin);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t LineJoin: " << lineJoin);
+ switch (lineJoin)
+ {
+ case EmfPlusLineJoinTypeBevel:
+ maLineJoin = basegfx::B2DLineJoin::Bevel;
+ break;
+ case EmfPlusLineJoinTypeRound:
+ maLineJoin = basegfx::B2DLineJoin::Round;
+ break;
+ case EmfPlusLineJoinTypeMiter:
+ case EmfPlusLineJoinTypeMiterClipped:
+ default: // If nothing set, then apply Miter (based on MS Paint)
+ maLineJoin = basegfx::B2DLineJoin::Miter;
+ break;
+ }
+ }
+ else
+ maLineJoin = basegfx::B2DLineJoin::Miter;
+
+ if (penDataFlags & EmfPlusPenDataMiterLimit)
+ {
+ float miterLimit;
+ s.ReadFloat(miterLimit);
+
+ // EMF+ JoinTypeMiterClipped is working as our B2DLineJoin::Miter
+ // For EMF+ LineJoinTypeMiter we are simulating it by changing angle
+ if (lineJoin == EmfPlusLineJoinTypeMiter)
+ miterLimit = 3.0 * miterLimit;
+ // asin angle must be in range [-1, 1]
+ if (abs(miterLimit) > 1.0)
+ fMiterMinimumAngle = 2.0 * asin(1.0 / miterLimit);
+ else
+ // enable miter limit for all angles
+ fMiterMinimumAngle = basegfx::deg2rad(180.0);
+ SAL_INFO("drawinglayer.emf",
+ "EMF+\t\t MiterLimit: " << std::dec << miterLimit
+ << ", Miter minimum angle (rad): " << fMiterMinimumAngle);
+ }
+ else
+ fMiterMinimumAngle = basegfx::deg2rad(5.0);
+
+
+ if (penDataFlags & EmfPlusPenDataLineStyle)
+ {
+ s.ReadInt32(dashStyle);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tdashStyle: " << DashedLineCapTypeToString(dashStyle) << " (0x" << std::hex << dashStyle << ")");
+ }
+ else
+ {
+ dashStyle = 0;
+ }
+
+ if (penDataFlags & EmfPlusPenDataDashedLineCap)
+ {
+ s.ReadInt32(dashCap);
+ SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO PenDataDashedLineCap: 0x" << std::hex << dashCap);
+ }
+ else
+ {
+ dashCap = 0;
+ }
+
+ if (penDataFlags & EmfPlusPenDataDashedLineOffset)
+ {
+ s.ReadFloat(dashOffset);
+ SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO PenDataDashedLineOffset: 0x" << std::hex << dashOffset);
+ }
+ else
+ {
+ dashOffset = 0;
+ }
+
+ if (penDataFlags & EmfPlusPenDataDashedLine)
+ {
+ dashStyle = EmfPlusLineStyleCustom;
+ sal_uInt32 dashPatternLen;
+
+ s.ReadUInt32(dashPatternLen);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\tdashPatternLen: " << dashPatternLen);
+
+ dashPattern.resize( dashPatternLen );
+
+ for (sal_uInt32 i = 0; i < dashPatternLen; i++)
+ {
+ s.ReadFloat(dashPattern[i]);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tdashPattern[" << i << "]: " << dashPattern[i]);
+ }
+ }
+
+ if (penDataFlags & EmfPlusPenDataAlignment)
+ {
+ s.ReadInt32(alignment);
+ SAL_WARN("drawinglayer.emf", "EMF+\t\t\tTODO PenDataAlignment: " << PenAlignmentToString(alignment) << " (0x" << std::hex << alignment << ")");
+ }
+ else
+ {
+ alignment = 0;
+ }
+
+ if (penDataFlags & EmfPlusPenDataCompoundLine)
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+\t\t\tTODO PenDataCompoundLine");
+ sal_uInt32 compoundArrayLen;
+ s.ReadUInt32(compoundArrayLen);
+
+ compoundArray.resize(compoundArrayLen);
+
+ for (sal_uInt32 i = 0; i < compoundArrayLen; i++)
+ {
+ s.ReadFloat(compoundArray[i]);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\t\tcompoundArray[" << i << "]: " << compoundArray[i]);
+ }
+ }
+
+ if (penDataFlags & EmfPlusPenDataCustomStartCap)
+ {
+ s.ReadUInt32(customStartCapLen);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\tcustomStartCapLen: " << customStartCapLen);
+ sal_uInt64 const pos = s.Tell();
+
+ customStartCap.reset( new EMFPCustomLineCap() );
+ customStartCap->Read(s, rR);
+
+ // maybe we don't read everything yet, play it safe ;-)
+ s.Seek(pos + customStartCapLen);
+ }
+ else
+ {
+ customStartCapLen = 0;
+ }
+
+ if (penDataFlags & EmfPlusPenDataCustomEndCap)
+ {
+ s.ReadUInt32(customEndCapLen);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t\tcustomEndCapLen: " << customEndCapLen);
+ sal_uInt64 const pos = s.Tell();
+
+ customEndCap.reset( new EMFPCustomLineCap() );
+ customEndCap->Read(s, rR);
+
+ // maybe we don't read everything yet, play it safe ;-)
+ s.Seek(pos + customEndCapLen);
+ }
+ else
+ {
+ customEndCapLen = 0;
+ }
+
+ EMFPBrush::Read(s, rR);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfppen.hxx b/drawinglayer/source/tools/emfppen.hxx
new file mode 100644
index 0000000000..31812c8b0c
--- /dev/null
+++ b/drawinglayer/source/tools/emfppen.hxx
@@ -0,0 +1,130 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <drawinglayer/attribute/strokeattribute.hxx>
+#include "emfpbrush.hxx"
+#include <vector>
+
+namespace emfplushelper
+{
+ const sal_uInt32 EmfPlusLineCapTypeSquare = 0x00000001;
+ const sal_uInt32 EmfPlusLineCapTypeRound = 0x00000002;
+ const sal_uInt32 EmfPlusLineCapTypeTriangle = 0x00000003;
+
+ const sal_uInt32 EmfPlusLineJoinTypeMiter = 0x00000000;
+ const sal_uInt32 EmfPlusLineJoinTypeBevel = 0x00000001;
+ const sal_uInt32 EmfPlusLineJoinTypeRound = 0x00000002;
+ const sal_uInt32 EmfPlusLineJoinTypeMiterClipped = 0x00000003;
+
+ const sal_Int32 EmfPlusLineStyleSolid = 0x00000000;
+ const sal_Int32 EmfPlusLineStyleDash = 0x00000001;
+ const sal_Int32 EmfPlusLineStyleDot = 0x00000002;
+ const sal_Int32 EmfPlusLineStyleDashDot = 0x00000003;
+ const sal_Int32 EmfPlusLineStyleDashDotDot = 0x00000004;
+ const sal_Int32 EmfPlusLineStyleCustom = 0x00000005;
+
+ const sal_uInt32 EmfPlusPenDataTransform = 0x00000001;
+ const sal_uInt32 EmfPlusPenDataStartCap = 0x00000002;
+ const sal_uInt32 EmfPlusPenDataEndCap = 0x00000004;
+ const sal_uInt32 EmfPlusPenDataJoin = 0x00000008;
+ const sal_uInt32 EmfPlusPenDataMiterLimit = 0x00000010;
+ const sal_uInt32 EmfPlusPenDataLineStyle = 0x00000020;
+ const sal_uInt32 EmfPlusPenDataDashedLineCap = 0x00000040;
+ const sal_uInt32 EmfPlusPenDataDashedLineOffset = 0x00000080;
+ const sal_uInt32 EmfPlusPenDataDashedLine = 0x00000100;
+ const sal_uInt32 EmfPlusPenDataAlignment = 0x00000200;
+ const sal_uInt32 EmfPlusPenDataCompoundLine = 0x00000400;
+ const sal_uInt32 EmfPlusPenDataCustomStartCap = 0x00000800;
+ const sal_uInt32 EmfPlusPenDataCustomEndCap = 0x000001000;
+
+ enum LineCapType
+ {
+ LineCapTypeFlat = 0x00000000,
+ LineCapTypeSquare = 0x00000001,
+ LineCapTypeRound = 0x00000002,
+ LineCapTypeTriangle = 0x00000003,
+ LineCapTypeNoAnchor = 0x00000010,
+ LineCapTypeSquareAnchor = 0x00000011,
+ LineCapTypeRoundAnchor = 0x00000012,
+ LineCapTypeDiamondAnchor = 0x00000013,
+ LineCapTypeArrowAnchor = 0x00000014,
+ LineCapTypeAnchorMask = 0x000000F0,
+ LineCapTypeCustom = 0x000000FF
+ };
+
+ enum LineJoinType
+ {
+ LineJoinTypeMiter = 0x00000000,
+ LineJoinTypeBevel = 0x00000001,
+ LineJoinTypeRound = 0x00000002,
+ LineJoinTypeMiterClipped = 0x00000003
+ };
+
+ enum DashedLineCapType
+ {
+ DashedLineCapTypeFlat = 0x00000000,
+ DashedLineCapTypeRound = 0x00000002,
+ DashedLineCapTypeTriangle = 0x00000003
+ };
+
+ enum PenAlignment
+ {
+ PenAlignmentCenter = 0x00000000,
+ PenAlignmentInset = 0x00000001,
+ PenAlignmentLeft = 0x00000002,
+ PenAlignmentOutset = 0x00000003,
+ PenAlignmentRight = 0x00000004
+ };
+
+ struct EMFPCustomLineCap;
+
+ struct EMFPPen : public EMFPBrush
+ {
+ basegfx::B2DHomMatrix pen_transformation; //TODO: This isn't used
+ sal_uInt32 penDataFlags;
+ sal_uInt32 penUnit;
+ float penWidth;
+ sal_Int32 startCap;
+ sal_Int32 endCap;
+ basegfx::B2DLineJoin maLineJoin;
+ double fMiterMinimumAngle;
+ sal_Int32 dashStyle;
+ sal_Int32 dashCap;
+ float dashOffset;
+ std::vector<float> dashPattern;
+ sal_Int32 alignment;
+ std::vector<float> compoundArray;
+ sal_uInt32 customStartCapLen;
+ std::unique_ptr<EMFPCustomLineCap> customStartCap;
+ sal_uInt32 customEndCapLen;
+ std::unique_ptr<EMFPCustomLineCap> customEndCap;
+
+ EMFPPen();
+
+ virtual ~EMFPPen() override;
+
+ void Read(SvStream& s, EmfPlusHelperData const & rR);
+
+ drawinglayer::attribute::StrokeAttribute GetStrokeAttribute(const double aTransformation) const;
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfpregion.cxx b/drawinglayer/source/tools/emfpregion.cxx
new file mode 100644
index 0000000000..9fcced3377
--- /dev/null
+++ b/drawinglayer/source/tools/emfpregion.cxx
@@ -0,0 +1,134 @@
+/* -*- 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 <basegfx/point/b2dpoint.hxx>
+#include <basegfx/range/b2drectangle.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <sal/log.hxx>
+#include "emfpregion.hxx"
+#include "emfppath.hxx"
+
+namespace emfplushelper
+{
+ EMFPRegion::EMFPRegion()
+ {
+ }
+
+ EMFPRegion::~EMFPRegion()
+ {
+ }
+
+ ::basegfx::B2DPolyPolygon EMFPRegion::ReadRegionNode(SvStream& s, EmfPlusHelperData& rR)
+ {
+ // Regions are specified as a binary tree of region nodes, and each node must either be a terminal node
+ // (RegionNodeDataTypeRect, RegionNodeDataTypePath, RegionNodeDataTypeEmpty, RegionNodeDataTypeInfinite)
+ // or specify one or two child nodes
+ // (RegionNodeDataTypeAnd, RegionNodeDataTypeOr, RegionNodeDataTypeXor,
+ // RegionNodeDataTypeExclude, RegionNodeDataTypeComplement).
+ sal_uInt32 dataType;
+ ::basegfx::B2DPolyPolygon polygon;
+ s.ReadUInt32(dataType);
+ SAL_INFO("drawinglayer.emf", "EMF+\t Region node data type 0x" << std::hex << dataType << std::dec);
+
+ switch (dataType)
+ {
+ case RegionNodeDataTypeAnd: // CombineModeIntersect
+ case RegionNodeDataTypeOr: // CombineModeUnion
+ case RegionNodeDataTypeXor: // CombineModeXOR
+ case RegionNodeDataTypeExclude: // CombineModeExclude
+ case RegionNodeDataTypeComplement: // CombineModeComplement
+ {
+ ::basegfx::B2DPolyPolygon leftPolygon = ReadRegionNode(s, rR);
+ ::basegfx::B2DPolyPolygon rightPolygon = ReadRegionNode(s, rR);
+ polygon = EmfPlusHelperData::combineClip(leftPolygon, dataType, rightPolygon);
+ break;
+ }
+ case RegionNodeDataTypeRect:
+ {
+ float dx, dy, dw, dh;
+ s.ReadFloat(dx).ReadFloat(dy).ReadFloat(dw).ReadFloat(dh);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t RegionNodeDataTypeRect x:" << dx << ", y:" << dy <<
+ ", width:" << dw << ", height:" << dh);
+
+ const ::basegfx::B2DPoint mappedStartPoint(rR.Map(dx, dy));
+ const ::basegfx::B2DPoint mappedEndPoint(rR.Map(dx + dw, dy + dh));
+ polygon = ::basegfx::B2DPolyPolygon(
+ ::basegfx::utils::createPolygonFromRect(
+ ::basegfx::B2DRectangle(
+ mappedStartPoint.getX(),
+ mappedStartPoint.getY(),
+ mappedEndPoint.getX(),
+ mappedEndPoint.getY())));
+ break;
+ }
+ case RegionNodeDataTypePath:
+ {
+ sal_Int32 pathLength;
+ s.ReadInt32(pathLength);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t RegionNodeDataTypePath, Path Length: " << pathLength << " bytes");
+
+ sal_uInt32 header, pathFlags;
+ sal_Int32 points;
+
+ s.ReadUInt32(header).ReadInt32(points).ReadUInt32(pathFlags);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t header: 0x" << std::hex << header <<
+ " points: " << std::dec << points << " additional flags: 0x" << std::hex << pathFlags << std::dec);
+
+ EMFPPath path(points);
+ path.Read(s, pathFlags);
+ polygon = path.GetPolygon(rR);
+ break;
+ }
+ case RegionNodeDataTypeEmpty:
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t RegionNodeDataTypeEmpty");
+ SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO we need to set empty polygon here");
+ polygon = ::basegfx::B2DPolyPolygon();
+
+ break;
+ }
+ case RegionNodeDataTypeInfinite:
+ {
+ SAL_INFO("drawinglayer.emf", "EMF+\t\t RegionNodeDataTypeInfinite");
+ polygon = ::basegfx::B2DPolyPolygon();
+ break;
+ }
+ default:
+ {
+ SAL_WARN("drawinglayer.emf", "EMF+\t\t Unhandled region type: 0x" << std::hex << dataType << std::dec);
+ polygon = ::basegfx::B2DPolyPolygon();
+ }
+ }
+ return polygon;
+ }
+
+ void EMFPRegion::ReadRegion(SvStream& s, EmfPlusHelperData& rR)
+ {
+ sal_uInt32 header, count;
+ s.ReadUInt32(header).ReadUInt32(count);
+ // An array should be RegionNodeCount+1 of EmfPlusRegionNode objects.
+ SAL_INFO("drawinglayer.emf", "EMF+\t version: 0x" << std::hex << header << std::dec << ", region node count: " << count);
+
+ regionPolyPolygon = ReadRegionNode(s, rR);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfpregion.hxx b/drawinglayer/source/tools/emfpregion.hxx
new file mode 100644
index 0000000000..a234abbb55
--- /dev/null
+++ b/drawinglayer/source/tools/emfpregion.hxx
@@ -0,0 +1,50 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include "emfphelperdata.hxx"
+
+namespace emfplushelper
+{
+ typedef enum
+ {
+ RegionNodeDataTypeAnd = 0x00000001,
+ RegionNodeDataTypeOr = 0x00000002,
+ RegionNodeDataTypeXor = 0x00000003,
+ RegionNodeDataTypeExclude = 0x00000004,
+ RegionNodeDataTypeComplement = 0x00000005,
+ RegionNodeDataTypeRect = 0x10000000,
+ RegionNodeDataTypePath = 0x10000001,
+ RegionNodeDataTypeEmpty = 0x10000002,
+ RegionNodeDataTypeInfinite = 0x10000003
+ } RegionNodeDataType;
+
+ struct EMFPRegion : public EMFPObject
+ {
+ ::basegfx::B2DPolyPolygon regionPolyPolygon;
+
+ EMFPRegion();
+ virtual ~EMFPRegion() override;
+ void ReadRegion(SvStream& s, EmfPlusHelperData& rR);
+ ::basegfx::B2DPolyPolygon ReadRegionNode(SvStream& s, EmfPlusHelperData& rR);
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfpstringformat.cxx b/drawinglayer/source/tools/emfpstringformat.cxx
new file mode 100644
index 0000000000..0a053201b4
--- /dev/null
+++ b/drawinglayer/source/tools/emfpstringformat.cxx
@@ -0,0 +1,195 @@
+/* -*- 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 <sal/log.hxx>
+#include <rtl/ustrbuf.hxx>
+#include "emfpstringformat.hxx"
+
+namespace emfplushelper
+{
+ EMFPStringFormat::EMFPStringFormat()
+ : header(0)
+ , stringFormatFlags(0)
+ , language(0)
+ , stringAlignment(0)
+ , lineAlign(0)
+ , digitSubstitution(0)
+ , digitLanguage(0)
+ , firstTabOffset(0.0)
+ , hotkeyPrefix(0)
+ , leadingMargin(0.0)
+ , trailingMargin(0.0)
+ , tracking(1.0)
+ , trimming(0)
+ , tabStopCount(0)
+ , rangeCount(0)
+ {
+ }
+
+ static OUString StringFormatFlags(sal_uInt32 flag)
+ {
+ OUStringBuffer sFlags;
+
+ // These are extracted from enum in emfpstringformat.hxx
+ if (flag & StringFormatDirectionRightToLeft)
+ sFlags.append("StringFormatDirectionRightToLeft ");
+
+ if (flag & StringFormatDirectionVertical)
+ sFlags.append("StringFormatDirectionVertical ");
+
+ if (flag & StringFormatNoFitBlackBox)
+ sFlags.append("StringFormatNoFitBlackBox ");
+
+ if (flag & StringFormatDisplayFormatControl)
+ sFlags.append("StringFormatDisplayFormatControl ");
+
+ if (flag & StringFormatNoFontFallback)
+ sFlags.append("StringFormatNoFontFallback ");
+
+ if (flag & StringFormatMeasureTrailingSpaces)
+ sFlags.append("StringFormatMeasureTrailingSpaces ");
+
+ if (flag & StringFormatNoWrap)
+ sFlags.append("StringFormatNoWrap ");
+
+ if (flag & StringFormatLineLimit)
+ sFlags.append("StringFormatLineLimit ");
+
+ if (flag & StringFormatNoClip)
+ sFlags.append("StringFormatNoClip ");
+
+ if (flag & StringFormatBypassGDI)
+ sFlags.append("StringFormatBypassGDI ");
+
+ // There will be 1 extra space in the end. It could be truncated, but
+ // as it is for SAL_INFO() only, it would not be important
+ return sFlags.makeStringAndClear();
+ }
+
+ static OUString StringAlignmentString(sal_uInt32 nAlignment)
+ {
+ switch(nAlignment)
+ {
+ case StringAlignment::StringAlignmentNear:
+ return "StringAlignmentNear";
+ case StringAlignment::StringAlignmentCenter:
+ return "StringAlignmentCenter";
+ case StringAlignment::StringAlignmentFar:
+ return "StringAlignmentFar";
+ default:
+ assert(false && nAlignment && "invalid string alignment value");
+ return "INVALID";
+ }
+ }
+
+ static OUString DigitSubstitutionString(sal_uInt32 nSubst)
+ {
+ switch(nSubst)
+ {
+ case StringDigitSubstitution::StringDigitSubstitutionUser:
+ return "StringDigitSubstitutionUser";
+ case StringDigitSubstitution::StringDigitSubstitutionNone:
+ return "StringDigitSubstitutionNone";
+ case StringDigitSubstitution::StringDigitSubstitutionNational:
+ return "StringDigitSubstitutionNational";
+ case StringDigitSubstitution::StringDigitSubstitutionTraditional:
+ return "StringDigitSubstitutionTraditional";
+ default:
+ assert(false && nSubst && "invalid string digit substitution value");
+ return "INVALID";
+ }
+ }
+
+ static OUString HotkeyPrefixString(sal_uInt32 nHotkey)
+ {
+ switch(nHotkey)
+ {
+ case HotkeyPrefix::HotkeyPrefixNone:
+ return "HotkeyPrefixNone";
+ case HotkeyPrefix::HotkeyPrefixShow:
+ return "HotkeyPrefixShow";
+ case HotkeyPrefix::HotkeyPrefixHide:
+ return "HotkeyPrefixHide";
+ default:
+ assert(false && nHotkey && "invalid hotkey prefix value");
+ return "INVALID";
+ }
+ }
+
+ static OUString StringTrimmingString(sal_uInt32 nTrimming)
+ {
+ switch(nTrimming)
+ {
+ case StringTrimming::StringTrimmingNone:
+ return "StringTrimmingNone";
+ case StringTrimming::StringTrimmingCharacter:
+ return "StringTrimmingCharacter";
+ case StringTrimming::StringTrimmingWord:
+ return "StringTrimmingWord";
+ case StringTrimming::StringTrimmingEllipsisCharacter:
+ return "StringTrimmingEllipsisCharacter";
+ case StringTrimming::StringTrimmingEllipsisWord:
+ return "StringTrimmingEllipsisWord";
+ case StringTrimming::StringTrimmingEllipsisPath:
+ return "StringTrimmingEllipsisPath";
+ default:
+ assert(false && nTrimming && "invalid trim value");
+ return "INVALID";
+ }
+ }
+
+ void EMFPStringFormat::Read(SvMemoryStream &s)
+ {
+ s.ReadUInt32(header).ReadUInt32(stringFormatFlags).ReadUInt32(language);
+ s.ReadUInt32(stringAlignment).ReadUInt32(lineAlign).ReadUInt32(digitSubstitution).ReadUInt32(digitLanguage);
+ s.ReadFloat(firstTabOffset).ReadInt32(hotkeyPrefix).ReadFloat(leadingMargin).ReadFloat(trailingMargin).ReadFloat(tracking);
+ s.ReadInt32(trimming).ReadInt32(tabStopCount).ReadInt32(rangeCount);
+ // keep only the last 16 bits of language
+ language >>= 16;
+ digitLanguage >>= 16;
+ SAL_WARN_IF((header >> 12) != 0xdbc01, "drawinglayer.emf", "Invalid header - not 0xdbc01");
+ SAL_INFO("drawinglayer.emf", "EMF+\tString format");
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tHeader: 0x" << std::hex << (header >> 12));
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tVersion: 0x" << (header & 0x1fff) << std::dec);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tStringFormatFlags: " << StringFormatFlags(stringFormatFlags));
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tLanguage: sublangid: 0x" << std::hex << (language >> 10) << ", primarylangid: 0x" << (language & 0xF800));
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tLineAlign: " << StringAlignmentString(lineAlign));
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tDigitSubstitution: " << DigitSubstitutionString(digitSubstitution));
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tDigitLanguage: sublangid: 0x" << std::hex << (digitLanguage >> 10) << ", primarylangid: 0x" << (digitLanguage & 0xF800));
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tFirstTabOffset: " << firstTabOffset);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tHotkeyPrefix: " << HotkeyPrefixString(hotkeyPrefix));
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tLeadingMargin: " << leadingMargin);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tTrailingMargin: " << trailingMargin);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tTracking: " << tracking);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tTrimming: " << StringTrimmingString(trimming));
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tTabStopCount: " << tabStopCount);
+ SAL_INFO("drawinglayer.emf", "EMF+\t\tRangeCount: " << rangeCount);
+
+ SAL_WARN_IF(digitSubstitution != StringDigitSubstitution::StringDigitSubstitutionNone,
+ "drawinglayer.emf", "EMF+\t TODO EMFPStringFormat:digitSubstitution");
+ SAL_WARN_IF(firstTabOffset != 0.0, "drawinglayer.emf", "EMF+\t TODO EMFPStringFormat:firstTabOffset");
+ SAL_WARN_IF(hotkeyPrefix != HotkeyPrefix::HotkeyPrefixNone, "drawinglayer.emf", "EMF+\t TODO EMFPStringFormat:hotkeyPrefix");
+ SAL_WARN_IF(tracking != 1.0, "drawinglayer.emf", "EMF+\t TODO EMFPStringFormat:tracking");
+ SAL_WARN_IF(trimming != StringTrimming::StringTrimmingNone, "drawinglayer.emf", "EMF+\t TODO EMFPStringFormat:trimming");
+ SAL_WARN_IF(tabStopCount, "drawinglayer.emf", "EMF+\t TODO EMFPStringFormat:tabStopCount");
+ SAL_WARN_IF(rangeCount != 0, "drawinglayer.emf", "EMF+\t TODO EMFPStringFormat:StringFormatData");
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/emfpstringformat.hxx b/drawinglayer/source/tools/emfpstringformat.hxx
new file mode 100644
index 0000000000..b4d8cb380e
--- /dev/null
+++ b/drawinglayer/source/tools/emfpstringformat.hxx
@@ -0,0 +1,104 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include "emfphelperdata.hxx"
+
+namespace emfplushelper
+{
+ const sal_uInt32 StringFormatDirectionRightToLeft = 0x00000001;
+ const sal_uInt32 StringFormatDirectionVertical = 0x00000002;
+ const sal_uInt32 StringFormatNoFitBlackBox = 0x00000004;
+ const sal_uInt32 StringFormatDisplayFormatControl = 0x00000020;
+ const sal_uInt32 StringFormatNoFontFallback = 0x00000400;
+ const sal_uInt32 StringFormatMeasureTrailingSpaces = 0x00000800;
+ const sal_uInt32 StringFormatNoWrap = 0x00001000;
+ const sal_uInt32 StringFormatLineLimit = 0x00002000;
+ const sal_uInt32 StringFormatNoClip = 0x00004000;
+ const sal_uInt32 StringFormatBypassGDI = 0x80000000;
+
+ enum StringAlignment
+ {
+ StringAlignmentNear = 0x00000000,
+ StringAlignmentCenter = 0x00000001,
+ StringAlignmentFar = 0x00000002
+ };
+
+ enum StringDigitSubstitution
+ {
+ StringDigitSubstitutionUser = 0x00000000,
+ StringDigitSubstitutionNone = 0x00000001,
+ StringDigitSubstitutionNational = 0x00000002,
+ StringDigitSubstitutionTraditional = 0x00000003
+ };
+
+ enum HotkeyPrefix
+ {
+ HotkeyPrefixNone = 0x00000000,
+ HotkeyPrefixShow = 0x00000001,
+ HotkeyPrefixHide = 0x00000002
+ };
+
+ enum StringTrimming
+ {
+ StringTrimmingNone = 0x00000000,
+ StringTrimmingCharacter = 0x00000001,
+ StringTrimmingWord = 0x00000002,
+ StringTrimmingEllipsisCharacter = 0x00000003,
+ StringTrimmingEllipsisWord = 0x00000004,
+ StringTrimmingEllipsisPath = 0x00000005
+ };
+
+ struct EMFPStringFormat : public EMFPObject
+ {
+ sal_uInt32 header;
+ sal_uInt32 stringFormatFlags;
+ sal_uInt32 language;
+ sal_uInt32 stringAlignment; // Horizontal alignment
+ sal_uInt32 lineAlign; // Vertical alignment
+ sal_uInt32 digitSubstitution;
+ sal_uInt32 digitLanguage;
+ float firstTabOffset;
+ sal_Int32 hotkeyPrefix;
+ float leadingMargin; // Length of the space to add to the starting position of a string.
+ float trailingMargin; // Length of the space to leave following a string.
+ float tracking;
+ sal_Int32 trimming;
+ sal_Int32 tabStopCount;
+ sal_Int32 rangeCount;
+
+ EMFPStringFormat();
+ void Read(SvMemoryStream &s);
+
+ // flags table from MS-EMFPLUS doc
+ bool DirectionRightToLeft() const { return stringFormatFlags & 0x00000001;}
+ bool DirectionVertical() const { return stringFormatFlags & 0x00000002;}
+ bool NoFitBlackBox() const { return stringFormatFlags & 0x00000004;}
+ bool DisplayFormatControl() const { return stringFormatFlags & 0x00000020;}
+ bool NoFontFallback() const { return stringFormatFlags & 0x00000400;}
+ bool MeasureTrailingSpaces() const { return stringFormatFlags & 0x00000800;}
+ bool NoWrap() const { return stringFormatFlags & 0x00001000;}
+ bool LineLimit() const { return stringFormatFlags & 0x00002000;}
+ bool NoClip() const { return stringFormatFlags & 0x00004000;}
+ bool BypassGDI() const { return stringFormatFlags & 0x80000000;}
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/primitive2dxmldump.cxx b/drawinglayer/source/tools/primitive2dxmldump.cxx
new file mode 100644
index 0000000000..f3b9ef1bc9
--- /dev/null
+++ b/drawinglayer/source/tools/primitive2dxmldump.cxx
@@ -0,0 +1,1245 @@
+/* -*- 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/.
+ */
+
+#include <drawinglayer/tools/primitive2dxmldump.hxx>
+
+#include <rtl/string.hxx>
+#include <tools/stream.hxx>
+#include <tools/XmlWriter.hxx>
+
+#include <math.h>
+#include <memory>
+#include <libxml/parser.h>
+#include <sal/log.hxx>
+
+#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
+#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
+#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/Tools.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx>
+#include <drawinglayer/primitive2d/softedgeprimitive2d.hxx>
+#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
+#include <primitive2d/textlineprimitive2d.hxx>
+#include <drawinglayer/primitive2d/textprimitive2d.hxx>
+#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
+#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx>
+#include <drawinglayer/primitive2d/structuretagprimitive2d.hxx>
+#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
+#include <drawinglayer/primitive2d/metafileprimitive2d.hxx>
+#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
+#include <drawinglayer/primitive2d/sceneprimitive2d.hxx>
+#include <drawinglayer/primitive2d/shadowprimitive2d.hxx>
+#include <drawinglayer/geometry/viewinformation2d.hxx>
+#include <drawinglayer/attribute/lineattribute.hxx>
+#include <drawinglayer/attribute/fontattribute.hxx>
+
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/utils/gradienttools.hxx>
+#include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+
+#include <drawinglayer/primitive3d/baseprimitive3d.hxx>
+#include <drawinglayer/primitive3d/Tools.hxx>
+#include <drawinglayer/primitive3d/drawinglayer_primitivetypes3d.hxx>
+#include <drawinglayer/primitive3d/sdrextrudeprimitive3d.hxx>
+#include <drawinglayer/attribute/sdrlightattribute3d.hxx>
+#include <drawinglayer/attribute/sdrfillattribute.hxx>
+#include <drawinglayer/attribute/fillhatchattribute.hxx>
+#include <drawinglayer/attribute/fillgradientattribute.hxx>
+#include <drawinglayer/attribute/sdrfillgraphicattribute.hxx>
+#include <drawinglayer/attribute/materialattribute3d.hxx>
+
+using namespace drawinglayer::primitive2d;
+
+namespace drawinglayer
+{
+namespace
+{
+const size_t constMaxActionType = 513;
+
+OUString convertColorToString(const basegfx::BColor& rColor)
+{
+ OUString aRGBString = Color(rColor).AsRGBHexString();
+ return "#" + aRGBString;
+}
+
+void writeMatrix(::tools::XmlWriter& rWriter, const basegfx::B2DHomMatrix& rMatrix)
+{
+ rWriter.attribute("xy11", rMatrix.get(0, 0));
+ rWriter.attribute("xy12", rMatrix.get(0, 1));
+ rWriter.attribute("xy13", rMatrix.get(0, 2));
+ rWriter.attribute("xy21", rMatrix.get(1, 0));
+ rWriter.attribute("xy22", rMatrix.get(1, 1));
+ rWriter.attribute("xy23", rMatrix.get(1, 2));
+ rWriter.attribute("xy31", 0);
+ rWriter.attribute("xy32", 0);
+ rWriter.attribute("xy33", 1);
+}
+
+void writeMatrix3D(::tools::XmlWriter& rWriter, const basegfx::B3DHomMatrix& rMatrix)
+{
+ rWriter.attribute("xy11", rMatrix.get(0, 0));
+ rWriter.attribute("xy12", rMatrix.get(0, 1));
+ rWriter.attribute("xy13", rMatrix.get(0, 2));
+ rWriter.attribute("xy14", rMatrix.get(0, 3));
+ rWriter.attribute("xy21", rMatrix.get(1, 0));
+ rWriter.attribute("xy22", rMatrix.get(1, 1));
+ rWriter.attribute("xy23", rMatrix.get(1, 2));
+ rWriter.attribute("xy24", rMatrix.get(1, 3));
+ rWriter.attribute("xy31", rMatrix.get(2, 0));
+ rWriter.attribute("xy32", rMatrix.get(2, 1));
+ rWriter.attribute("xy33", rMatrix.get(2, 2));
+ rWriter.attribute("xy34", rMatrix.get(2, 3));
+ rWriter.attribute("xy41", rMatrix.get(3, 0));
+ rWriter.attribute("xy42", rMatrix.get(3, 1));
+ rWriter.attribute("xy43", rMatrix.get(3, 2));
+ rWriter.attribute("xy44", rMatrix.get(3, 3));
+}
+
+void writePolyPolygon(::tools::XmlWriter& rWriter, const basegfx::B2DPolyPolygon& rB2DPolyPolygon)
+{
+ rWriter.startElement("polypolygon");
+ const basegfx::B2DRange aB2DRange(rB2DPolyPolygon.getB2DRange());
+ rWriter.attributeDouble("height", aB2DRange.getHeight());
+ rWriter.attributeDouble("width", aB2DRange.getWidth());
+ rWriter.attributeDouble("minx", aB2DRange.getMinX());
+ rWriter.attributeDouble("miny", aB2DRange.getMinY());
+ rWriter.attributeDouble("maxx", aB2DRange.getMaxX());
+ rWriter.attributeDouble("maxy", aB2DRange.getMaxY());
+ rWriter.attribute("path", basegfx::utils::exportToSvgD(rB2DPolyPolygon, true, true, false));
+
+ for (basegfx::B2DPolygon const& rPolygon : rB2DPolyPolygon)
+ {
+ rWriter.startElement("polygon");
+ for (sal_uInt32 i = 0; i < rPolygon.count(); ++i)
+ {
+ basegfx::B2DPoint const& rPoint = rPolygon.getB2DPoint(i);
+
+ rWriter.startElement("point");
+ rWriter.attribute("x", OUString::number(rPoint.getX()));
+ rWriter.attribute("y", OUString::number(rPoint.getY()));
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+
+ rWriter.endElement();
+}
+
+void writeStrokeAttribute(::tools::XmlWriter& rWriter,
+ const drawinglayer::attribute::StrokeAttribute& rStrokeAttribute)
+{
+ if (!rStrokeAttribute.getDotDashArray().empty())
+ {
+ rWriter.startElement("stroke");
+
+ OUString sDotDash;
+ for (double fDotDash : rStrokeAttribute.getDotDashArray())
+ {
+ sDotDash += OUString::number(lround(fDotDash)) + " ";
+ }
+ rWriter.attribute("dotDashArray", sDotDash);
+ rWriter.attribute("fullDotDashLength", rStrokeAttribute.getFullDotDashLen());
+ rWriter.endElement();
+ }
+}
+
+void writeLineAttribute(::tools::XmlWriter& rWriter,
+ const drawinglayer::attribute::LineAttribute& rLineAttribute)
+{
+ rWriter.startElement("line");
+ rWriter.attribute("color", convertColorToString(rLineAttribute.getColor()));
+ rWriter.attribute("width", rLineAttribute.getWidth());
+ switch (rLineAttribute.getLineJoin())
+ {
+ case basegfx::B2DLineJoin::NONE:
+ rWriter.attribute("linejoin", "NONE"_ostr);
+ break;
+ case basegfx::B2DLineJoin::Bevel:
+ rWriter.attribute("linejoin", "Bevel"_ostr);
+ break;
+ case basegfx::B2DLineJoin::Miter:
+ {
+ rWriter.attribute("linejoin", "Miter"_ostr);
+ rWriter.attribute("miterangle",
+ basegfx::rad2deg(rLineAttribute.getMiterMinimumAngle()));
+ break;
+ }
+ case basegfx::B2DLineJoin::Round:
+ rWriter.attribute("linejoin", "Round"_ostr);
+ break;
+ default:
+ rWriter.attribute("linejoin", "Unknown"_ostr);
+ break;
+ }
+ switch (rLineAttribute.getLineCap())
+ {
+ case css::drawing::LineCap::LineCap_BUTT:
+ rWriter.attribute("linecap", "BUTT"_ostr);
+ break;
+ case css::drawing::LineCap::LineCap_ROUND:
+ rWriter.attribute("linecap", "ROUND"_ostr);
+ break;
+ case css::drawing::LineCap::LineCap_SQUARE:
+ rWriter.attribute("linecap", "SQUARE"_ostr);
+ break;
+ default:
+ rWriter.attribute("linecap", "Unknown"_ostr);
+ break;
+ }
+
+ rWriter.endElement();
+}
+
+void writeSdrLineAttribute(::tools::XmlWriter& rWriter,
+ const drawinglayer::attribute::SdrLineAttribute& rLineAttribute)
+{
+ if (rLineAttribute.isDefault())
+ return;
+
+ rWriter.startElement("line");
+ rWriter.attribute("color", convertColorToString(rLineAttribute.getColor()));
+ rWriter.attribute("width", rLineAttribute.getWidth());
+ rWriter.attribute("transparence", rLineAttribute.getTransparence());
+
+ switch (rLineAttribute.getJoin())
+ {
+ case basegfx::B2DLineJoin::NONE:
+ rWriter.attribute("linejoin", "NONE"_ostr);
+ break;
+ case basegfx::B2DLineJoin::Bevel:
+ rWriter.attribute("linejoin", "Bevel"_ostr);
+ break;
+ case basegfx::B2DLineJoin::Miter:
+ rWriter.attribute("linejoin", "Miter"_ostr);
+ break;
+ case basegfx::B2DLineJoin::Round:
+ rWriter.attribute("linejoin", "Round"_ostr);
+ break;
+ default:
+ rWriter.attribute("linejoin", "Unknown"_ostr);
+ break;
+ }
+ switch (rLineAttribute.getCap())
+ {
+ case css::drawing::LineCap::LineCap_BUTT:
+ rWriter.attribute("linecap", "BUTT"_ostr);
+ break;
+ case css::drawing::LineCap::LineCap_ROUND:
+ rWriter.attribute("linecap", "ROUND"_ostr);
+ break;
+ case css::drawing::LineCap::LineCap_SQUARE:
+ rWriter.attribute("linecap", "SQUARE"_ostr);
+ break;
+ default:
+ rWriter.attribute("linecap", "Unknown"_ostr);
+ break;
+ }
+
+ if (!rLineAttribute.getDotDashArray().empty())
+ {
+ OUString sDotDash;
+ for (double fDotDash : rLineAttribute.getDotDashArray())
+ {
+ sDotDash += OUString::number(fDotDash) + " ";
+ }
+ rWriter.attribute("dotDashArray", sDotDash);
+ rWriter.attribute("fullDotDashLength", rLineAttribute.getFullDotDashLen());
+ }
+
+ rWriter.endElement();
+}
+
+void writeSdrFillAttribute(::tools::XmlWriter& rWriter,
+ const drawinglayer::attribute::SdrFillAttribute& rFillAttribute)
+{
+ if (rFillAttribute.isDefault())
+ return;
+
+ rWriter.startElement("fill");
+ rWriter.attribute("color", convertColorToString(rFillAttribute.getColor()));
+ rWriter.attribute("transparence", rFillAttribute.getTransparence());
+
+ auto const& rGradient = rFillAttribute.getGradient();
+ if (!rGradient.isDefault())
+ {
+ rWriter.startElement("gradient");
+ switch (rGradient.getStyle())
+ {
+ default: // GradientStyle_MAKE_FIXED_SIZE
+ case css::awt::GradientStyle_LINEAR:
+ rWriter.attribute("style", "Linear"_ostr);
+ break;
+ case css::awt::GradientStyle_AXIAL:
+ rWriter.attribute("style", "Axial"_ostr);
+ break;
+ case css::awt::GradientStyle_RADIAL:
+ rWriter.attribute("style", "Radial"_ostr);
+ break;
+ case css::awt::GradientStyle_ELLIPTICAL:
+ rWriter.attribute("style", "Elliptical"_ostr);
+ break;
+ case css::awt::GradientStyle_SQUARE:
+ rWriter.attribute("style", "Square"_ostr);
+ break;
+ case css::awt::GradientStyle_RECT:
+ rWriter.attribute("style", "Rect"_ostr);
+ break;
+ }
+ rWriter.attribute("border", rGradient.getBorder());
+ rWriter.attribute("offsetX", rGradient.getOffsetX());
+ rWriter.attribute("offsetY", rGradient.getOffsetY());
+ rWriter.attribute("angle", rGradient.getAngle());
+ rWriter.attribute("steps", rGradient.getSteps());
+
+ auto const& rColorStops(rGradient.getColorStops());
+ for (size_t a(0); a < rColorStops.size(); a++)
+ {
+ if (0 == a)
+ rWriter.attribute("startColor",
+ convertColorToString(rColorStops[a].getStopColor()));
+ else if (rColorStops.size() == a + 1)
+ rWriter.attribute("endColor", convertColorToString(rColorStops[a].getStopColor()));
+ else
+ {
+ rWriter.startElement("colorStop");
+ rWriter.attribute("stopOffset", rColorStops[a].getStopOffset());
+ rWriter.attribute("stopColor", convertColorToString(rColorStops[a].getStopColor()));
+ rWriter.endElement();
+ }
+ }
+ rWriter.endElement();
+ }
+
+ auto const& rHatch = rFillAttribute.getHatch();
+ if (!rHatch.isDefault())
+ {
+ rWriter.startElement("hatch");
+ switch (rHatch.getStyle())
+ {
+ case drawinglayer::attribute::HatchStyle::Single:
+ rWriter.attribute("style", "Single"_ostr);
+ break;
+ case drawinglayer::attribute::HatchStyle::Double:
+ rWriter.attribute("style", "Double"_ostr);
+ break;
+ case drawinglayer::attribute::HatchStyle::Triple:
+ rWriter.attribute("style", "Triple"_ostr);
+ break;
+ }
+ rWriter.attribute("distance", rHatch.getDistance());
+ rWriter.attribute("angle", rHatch.getAngle());
+ rWriter.attribute("color", convertColorToString(rHatch.getColor()));
+ rWriter.attribute("minimalDescreteDistance", rHatch.getMinimalDiscreteDistance());
+ rWriter.attribute("isFillBackground", sal_Int32(rHatch.isFillBackground()));
+ rWriter.endElement();
+ }
+
+ auto const& rGraphic = rFillAttribute.getFillGraphic();
+ if (!rGraphic.isDefault())
+ {
+ rWriter.startElement("graphic");
+ // TODO
+ rWriter.endElement();
+ }
+
+ rWriter.endElement();
+}
+
+void writeShadeMode(::tools::XmlWriter& rWriter, const css::drawing::ShadeMode& rMode)
+{
+ switch (rMode)
+ {
+ case css::drawing::ShadeMode_FLAT:
+ rWriter.attribute("shadeMode", "Flat"_ostr);
+ break;
+ case css::drawing::ShadeMode_SMOOTH:
+ rWriter.attribute("shadeMode", "Smooth"_ostr);
+ break;
+ case css::drawing::ShadeMode_PHONG:
+ rWriter.attribute("shadeMode", "Phong"_ostr);
+ break;
+ case css::drawing::ShadeMode_DRAFT:
+ rWriter.attribute("shadeMode", "Draft"_ostr);
+ break;
+ default:
+ rWriter.attribute("shadeMode", "Undefined"_ostr);
+ break;
+ }
+}
+
+void writeProjectionMode(::tools::XmlWriter& rWriter, const css::drawing::ProjectionMode& rMode)
+{
+ switch (rMode)
+ {
+ case css::drawing::ProjectionMode_PARALLEL:
+ rWriter.attribute("projectionMode", "Parallel"_ostr);
+ break;
+ case css::drawing::ProjectionMode_PERSPECTIVE:
+ rWriter.attribute("projectionMode", "Perspective"_ostr);
+ break;
+ default:
+ rWriter.attribute("projectionMode", "Undefined"_ostr);
+ break;
+ }
+}
+
+void writeNormalsKind(::tools::XmlWriter& rWriter, const css::drawing::NormalsKind& rKind)
+{
+ switch (rKind)
+ {
+ case css::drawing::NormalsKind_SPECIFIC:
+ rWriter.attribute("normalsKind", "Specific"_ostr);
+ break;
+ case css::drawing::NormalsKind_FLAT:
+ rWriter.attribute("normalsKind", "Flat"_ostr);
+ break;
+ case css::drawing::NormalsKind_SPHERE:
+ rWriter.attribute("normalsKind", "Sphere"_ostr);
+ break;
+ default:
+ rWriter.attribute("normalsKind", "Undefined"_ostr);
+ break;
+ }
+}
+
+void writeTextureProjectionMode(::tools::XmlWriter& rWriter, const char* pElement,
+ const css::drawing::TextureProjectionMode& rMode)
+{
+ switch (rMode)
+ {
+ case css::drawing::TextureProjectionMode_OBJECTSPECIFIC:
+ rWriter.attribute(pElement, "Specific"_ostr);
+ break;
+ case css::drawing::TextureProjectionMode_PARALLEL:
+ rWriter.attribute(pElement, "Parallel"_ostr);
+ break;
+ case css::drawing::TextureProjectionMode_SPHERE:
+ rWriter.attribute(pElement, "Sphere"_ostr);
+ break;
+ default:
+ rWriter.attribute(pElement, "Undefined"_ostr);
+ break;
+ }
+}
+
+void writeTextureKind(::tools::XmlWriter& rWriter, const css::drawing::TextureKind2& rKind)
+{
+ switch (rKind)
+ {
+ case css::drawing::TextureKind2_LUMINANCE:
+ rWriter.attribute("textureKind", "Luminance"_ostr);
+ break;
+ case css::drawing::TextureKind2_INTENSITY:
+ rWriter.attribute("textureKind", "Intensity"_ostr);
+ break;
+ case css::drawing::TextureKind2_COLOR:
+ rWriter.attribute("textureKind", "Color"_ostr);
+ break;
+ default:
+ rWriter.attribute("textureKind", "Undefined"_ostr);
+ break;
+ }
+}
+
+void writeTextureMode(::tools::XmlWriter& rWriter, const css::drawing::TextureMode& rMode)
+{
+ switch (rMode)
+ {
+ case css::drawing::TextureMode_REPLACE:
+ rWriter.attribute("textureMode", "Replace"_ostr);
+ break;
+ case css::drawing::TextureMode_MODULATE:
+ rWriter.attribute("textureMode", "Modulate"_ostr);
+ break;
+ case css::drawing::TextureMode_BLEND:
+ rWriter.attribute("textureMode", "Blend"_ostr);
+ break;
+ default:
+ rWriter.attribute("textureMode", "Undefined"_ostr);
+ break;
+ }
+}
+
+void writeMaterialAttribute(::tools::XmlWriter& rWriter,
+ const drawinglayer::attribute::MaterialAttribute3D& rMaterial)
+{
+ rWriter.startElement("material");
+ rWriter.attribute("color", convertColorToString(rMaterial.getColor()));
+ rWriter.attribute("specular", convertColorToString(rMaterial.getSpecular()));
+ rWriter.attribute("emission", convertColorToString(rMaterial.getEmission()));
+ rWriter.attribute("specularIntensity", rMaterial.getSpecularIntensity());
+ rWriter.endElement();
+}
+
+void writeSpreadMethod(::tools::XmlWriter& rWriter,
+ const drawinglayer::primitive2d::SpreadMethod& rSpreadMethod)
+{
+ switch (rSpreadMethod)
+ {
+ case drawinglayer::primitive2d::SpreadMethod::Pad:
+ rWriter.attribute("spreadmethod", "pad"_ostr);
+ break;
+ case drawinglayer::primitive2d::SpreadMethod::Reflect:
+ rWriter.attribute("spreadmethod", "reflect"_ostr);
+ break;
+ case drawinglayer::primitive2d::SpreadMethod::Repeat:
+ rWriter.attribute("spreadmethod", "repeat"_ostr);
+ break;
+ default:
+ rWriter.attribute("spreadmethod", "unknown"_ostr);
+ }
+}
+
+} // end anonymous namespace
+
+Primitive2dXmlDump::Primitive2dXmlDump()
+ : maFilter(constMaxActionType, false)
+{
+}
+
+Primitive2dXmlDump::~Primitive2dXmlDump() = default;
+
+void Primitive2dXmlDump::dump(
+ const drawinglayer::primitive2d::Primitive2DContainer& rPrimitive2DSequence,
+ const OUString& rStreamName)
+{
+ std::unique_ptr<SvStream> pStream;
+
+ if (rStreamName.isEmpty())
+ pStream.reset(new SvMemoryStream());
+ else
+ pStream.reset(new SvFileStream(rStreamName, StreamMode::STD_READWRITE | StreamMode::TRUNC));
+
+ ::tools::XmlWriter aWriter(pStream.get());
+ aWriter.startDocument();
+ aWriter.startElement("primitive2D");
+
+ decomposeAndWrite(rPrimitive2DSequence, aWriter);
+
+ aWriter.endElement();
+ aWriter.endDocument();
+
+ pStream->Seek(STREAM_SEEK_TO_BEGIN);
+}
+
+namespace
+{
+class Primitive3DXmlDump
+{
+public:
+ void decomposeAndWrite(const drawinglayer::primitive3d::Primitive3DContainer& rSequence,
+ ::tools::XmlWriter& rWriter)
+ {
+ for (size_t i = 0; i < rSequence.size(); i++)
+ {
+ drawinglayer::primitive3d::Primitive3DReference xReference = rSequence[i];
+ const auto* pBasePrimitive
+ = static_cast<const drawinglayer::primitive3d::BasePrimitive3D*>(xReference.get());
+ sal_uInt32 nId = pBasePrimitive->getPrimitive3DID();
+ OUString sCurrentElementTag = drawinglayer::primitive3d::idToString(nId);
+ switch (nId)
+ {
+ case PRIMITIVE3D_ID_SDREXTRUDEPRIMITIVE3D:
+ {
+ const auto* pExtrudePrimitive3D
+ = static_cast<const drawinglayer::primitive3d::SdrExtrudePrimitive3D*>(
+ xReference.get());
+ rWriter.startElement("extrude3D");
+
+ rWriter.startElement("matrix3D");
+ writeMatrix3D(rWriter, pExtrudePrimitive3D->getTransform());
+ rWriter.endElement();
+
+ rWriter.attribute("textureSizeX", pExtrudePrimitive3D->getTextureSize().getX());
+ rWriter.attribute("textureSizeY", pExtrudePrimitive3D->getTextureSize().getY());
+ auto const& rLFSAttribute = pExtrudePrimitive3D->getSdrLFSAttribute();
+ writeSdrLineAttribute(rWriter, rLFSAttribute.getLine());
+ writeSdrFillAttribute(rWriter, rLFSAttribute.getFill());
+
+ rWriter.startElement("object3Dattributes");
+ {
+ auto const& r3DObjectAttributes
+ = pExtrudePrimitive3D->getSdr3DObjectAttribute();
+
+ writeNormalsKind(rWriter, r3DObjectAttributes.getNormalsKind());
+ writeTextureProjectionMode(rWriter, "textureProjectionX",
+ r3DObjectAttributes.getTextureProjectionX());
+ writeTextureProjectionMode(rWriter, "textureProjectionY",
+ r3DObjectAttributes.getTextureProjectionY());
+ writeTextureKind(rWriter, r3DObjectAttributes.getTextureKind());
+ writeTextureMode(rWriter, r3DObjectAttributes.getTextureMode());
+ writeMaterialAttribute(rWriter, r3DObjectAttributes.getMaterial());
+
+ rWriter.attribute("normalsInvert",
+ sal_Int32(r3DObjectAttributes.getNormalsInvert()));
+ rWriter.attribute("doubleSided",
+ sal_Int32(r3DObjectAttributes.getDoubleSided()));
+ rWriter.attribute("shadow3D", sal_Int32(r3DObjectAttributes.getShadow3D()));
+ rWriter.attribute("textureFilter",
+ sal_Int32(r3DObjectAttributes.getTextureFilter()));
+ rWriter.attribute("reducedGeometry",
+ sal_Int32(r3DObjectAttributes.getReducedLineGeometry()));
+ }
+ rWriter.endElement();
+
+ rWriter.attribute("depth", pExtrudePrimitive3D->getDepth());
+ rWriter.attribute("diagonal", pExtrudePrimitive3D->getDiagonal());
+ rWriter.attribute("backScale", pExtrudePrimitive3D->getBackScale());
+ rWriter.attribute("smoothNormals",
+ sal_Int32(pExtrudePrimitive3D->getSmoothNormals()));
+ rWriter.attribute("smoothLids",
+ sal_Int32(pExtrudePrimitive3D->getSmoothLids()));
+ rWriter.attribute("characterMode",
+ sal_Int32(pExtrudePrimitive3D->getCharacterMode()));
+ rWriter.attribute("closeFront",
+ sal_Int32(pExtrudePrimitive3D->getCloseFront()));
+ rWriter.attribute("closeBack", sal_Int32(pExtrudePrimitive3D->getCloseBack()));
+ writePolyPolygon(rWriter, pExtrudePrimitive3D->getPolyPolygon());
+ rWriter.endElement();
+ }
+ break;
+
+ default:
+ {
+ rWriter.startElement("unhandled");
+ rWriter.attribute("id", sCurrentElementTag);
+ rWriter.attribute("idNumber", nId);
+
+ drawinglayer::geometry::ViewInformation3D aViewInformation3D;
+ drawinglayer::primitive3d::Primitive3DContainer aContainer;
+ aContainer = pBasePrimitive->get3DDecomposition(aViewInformation3D);
+ decomposeAndWrite(aContainer, rWriter);
+ rWriter.endElement();
+ }
+ break;
+ }
+ }
+ }
+};
+}
+xmlDocUniquePtr Primitive2dXmlDump::dumpAndParse(
+ const drawinglayer::primitive2d::Primitive2DContainer& rPrimitive2DSequence,
+ const OUString& rStreamName)
+{
+ std::unique_ptr<SvStream> pStream;
+
+ if (rStreamName.isEmpty())
+ pStream.reset(new SvMemoryStream());
+ else
+ pStream.reset(new SvFileStream(rStreamName, StreamMode::STD_READWRITE | StreamMode::TRUNC));
+
+ ::tools::XmlWriter aWriter(pStream.get());
+ aWriter.startDocument();
+ aWriter.startElement("primitive2D");
+
+ decomposeAndWrite(rPrimitive2DSequence, aWriter);
+
+ aWriter.endElement();
+ aWriter.endDocument();
+
+ pStream->Seek(STREAM_SEEK_TO_BEGIN);
+
+ std::size_t nSize = pStream->remainingSize();
+ std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nSize + 1]);
+ pStream->ReadBytes(pBuffer.get(), nSize);
+ pBuffer[nSize] = 0;
+ SAL_INFO("drawinglayer", "Parsed XML: " << pBuffer.get());
+
+ return xmlDocUniquePtr(xmlParseDoc(reinterpret_cast<xmlChar*>(pBuffer.get())));
+}
+
+void Primitive2dXmlDump::decomposeAndWrite(
+ const drawinglayer::primitive2d::Primitive2DContainer& rPrimitive2DSequence,
+ ::tools::XmlWriter& rWriter)
+{
+ for (size_t i = 0; i < rPrimitive2DSequence.size(); i++)
+ {
+ const BasePrimitive2D* pBasePrimitive = rPrimitive2DSequence[i].get();
+ sal_uInt32 nId = pBasePrimitive->getPrimitive2DID();
+ if (nId < maFilter.size() && maFilter[nId])
+ continue;
+
+ OUString sCurrentElementTag = drawinglayer::primitive2d::idToString(nId);
+
+ switch (nId)
+ {
+ case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D:
+ {
+ const BitmapPrimitive2D& rBitmapPrimitive2D
+ = dynamic_cast<const BitmapPrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("bitmap");
+ writeMatrix(rWriter, rBitmapPrimitive2D.getTransform());
+
+ const BitmapEx aBitmapEx(rBitmapPrimitive2D.getBitmap());
+ const Size& rSizePixel(aBitmapEx.GetSizePixel());
+
+ rWriter.attribute("height", rSizePixel.getHeight());
+ rWriter.attribute("width", rSizePixel.getWidth());
+ rWriter.attribute("checksum", OString(std::to_string(aBitmapEx.GetChecksum())));
+
+ for (tools::Long y = 0; y < rSizePixel.getHeight(); y++)
+ {
+ rWriter.startElement("data");
+ OUString aBitmapData = "";
+ for (tools::Long x = 0; x < rSizePixel.getHeight(); x++)
+ {
+ if (x != 0)
+ aBitmapData = aBitmapData + ",";
+ aBitmapData = aBitmapData + aBitmapEx.GetPixelColor(x, y).AsRGBHexString();
+ }
+ rWriter.attribute("row", aBitmapData);
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+ }
+ break;
+ case PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D:
+ {
+ const HiddenGeometryPrimitive2D& rHiddenGeometryPrimitive2D
+ = dynamic_cast<const HiddenGeometryPrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("hiddengeometry");
+ decomposeAndWrite(rHiddenGeometryPrimitive2D.getChildren(), rWriter);
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D:
+ {
+ const TransformPrimitive2D& rTransformPrimitive2D
+ = dynamic_cast<const TransformPrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("transform");
+ writeMatrix(rWriter, rTransformPrimitive2D.getTransformation());
+ decomposeAndWrite(rTransformPrimitive2D.getChildren(), rWriter);
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
+ {
+ const PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D
+ = dynamic_cast<const PolyPolygonColorPrimitive2D&>(*pBasePrimitive);
+
+ rWriter.startElement("polypolygoncolor");
+ rWriter.attribute("color",
+ convertColorToString(rPolyPolygonColorPrimitive2D.getBColor()));
+
+ const basegfx::B2DPolyPolygon& aB2DPolyPolygon(
+ rPolyPolygonColorPrimitive2D.getB2DPolyPolygon());
+ writePolyPolygon(rWriter, aB2DPolyPolygon);
+
+ rWriter.endElement();
+ }
+ break;
+ case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D:
+ {
+ const PointArrayPrimitive2D& rPointArrayPrimitive2D
+ = dynamic_cast<const PointArrayPrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("pointarray");
+
+ rWriter.attribute("color",
+ convertColorToString(rPointArrayPrimitive2D.getRGBColor()));
+
+ const std::vector<basegfx::B2DPoint> aPositions
+ = rPointArrayPrimitive2D.getPositions();
+ for (std::vector<basegfx::B2DPoint>::const_iterator iter = aPositions.begin();
+ iter != aPositions.end(); ++iter)
+ {
+ rWriter.startElement("point");
+ rWriter.attribute("x", OUString::number(iter->getX()));
+ rWriter.attribute("y", OUString::number(iter->getY()));
+ rWriter.endElement();
+ }
+
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D:
+ {
+ const PolygonStrokeArrowPrimitive2D& rPolygonStrokeArrowPrimitive2D
+ = dynamic_cast<const PolygonStrokeArrowPrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("polygonstrokearrow");
+
+ rWriter.startElement("polygon");
+ rWriter.content(basegfx::utils::exportToSvgPoints(
+ rPolygonStrokeArrowPrimitive2D.getB2DPolygon()));
+ rWriter.endElement();
+
+ if (rPolygonStrokeArrowPrimitive2D.getStart().getB2DPolyPolygon().count())
+ {
+ rWriter.startElement("linestartattribute");
+ rWriter.attribute("width",
+ rPolygonStrokeArrowPrimitive2D.getStart().getWidth());
+ rWriter.attribute("centered",
+ static_cast<sal_Int32>(
+ rPolygonStrokeArrowPrimitive2D.getStart().isCentered()));
+ writePolyPolygon(rWriter,
+ rPolygonStrokeArrowPrimitive2D.getStart().getB2DPolyPolygon());
+ rWriter.endElement();
+ }
+
+ if (rPolygonStrokeArrowPrimitive2D.getEnd().getB2DPolyPolygon().count())
+ {
+ rWriter.startElement("lineendattribute");
+ rWriter.attribute("width", rPolygonStrokeArrowPrimitive2D.getEnd().getWidth());
+ rWriter.attribute("centered",
+ static_cast<sal_Int32>(
+ rPolygonStrokeArrowPrimitive2D.getEnd().isCentered()));
+ writePolyPolygon(rWriter,
+ rPolygonStrokeArrowPrimitive2D.getEnd().getB2DPolyPolygon());
+ rWriter.endElement();
+ }
+
+ writeLineAttribute(rWriter, rPolygonStrokeArrowPrimitive2D.getLineAttribute());
+ writeStrokeAttribute(rWriter, rPolygonStrokeArrowPrimitive2D.getStrokeAttribute());
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D:
+ {
+ const PolygonStrokePrimitive2D& rPolygonStrokePrimitive2D
+ = dynamic_cast<const PolygonStrokePrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("polygonstroke");
+
+ rWriter.startElement("polygon");
+ rWriter.content(
+ basegfx::utils::exportToSvgPoints(rPolygonStrokePrimitive2D.getB2DPolygon()));
+ rWriter.endElement();
+
+ writeLineAttribute(rWriter, rPolygonStrokePrimitive2D.getLineAttribute());
+ writeStrokeAttribute(rWriter, rPolygonStrokePrimitive2D.getStrokeAttribute());
+ rWriter.endElement();
+ }
+ break;
+ case PRIMITIVE2D_ID_POLYPOLYGONSTROKEPRIMITIVE2D:
+ {
+ const PolyPolygonStrokePrimitive2D& rPolyPolygonStrokePrimitive2D
+ = dynamic_cast<const PolyPolygonStrokePrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("polypolygonstroke");
+
+ writeLineAttribute(rWriter, rPolyPolygonStrokePrimitive2D.getLineAttribute());
+ writeStrokeAttribute(rWriter, rPolyPolygonStrokePrimitive2D.getStrokeAttribute());
+ writePolyPolygon(rWriter, rPolyPolygonStrokePrimitive2D.getB2DPolyPolygon());
+
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
+ {
+ const PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D
+ = dynamic_cast<const PolygonHairlinePrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("polygonhairline");
+
+ rWriter.attribute("color",
+ convertColorToString(rPolygonHairlinePrimitive2D.getBColor()));
+
+ rWriter.startElement("polygon");
+ rWriter.content(
+ basegfx::utils::exportToSvgPoints(rPolygonHairlinePrimitive2D.getB2DPolygon()));
+ rWriter.endElement();
+
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D:
+ {
+ const TextDecoratedPortionPrimitive2D& rTextDecoratedPortionPrimitive2D
+ = dynamic_cast<const TextDecoratedPortionPrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("textdecoratedportion");
+ writeMatrix(rWriter, rTextDecoratedPortionPrimitive2D.getTextTransform());
+
+ rWriter.attribute("text", rTextDecoratedPortionPrimitive2D.getText());
+ rWriter.attribute(
+ "fontcolor",
+ convertColorToString(rTextDecoratedPortionPrimitive2D.getFontColor()));
+
+ const drawinglayer::attribute::FontAttribute& aFontAttribute
+ = rTextDecoratedPortionPrimitive2D.getFontAttribute();
+ rWriter.attribute("familyname", aFontAttribute.getFamilyName());
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_TEXTLINEPRIMITIVE2D:
+ {
+ const TextLinePrimitive2D& rTextLinePrimitive2D
+ = dynamic_cast<const TextLinePrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("textline");
+ writeMatrix(rWriter, rTextLinePrimitive2D.getObjectTransformation());
+
+ rWriter.attribute("width", rTextLinePrimitive2D.getWidth());
+ rWriter.attribute("offset", rTextLinePrimitive2D.getOffset());
+ rWriter.attribute("height", rTextLinePrimitive2D.getHeight());
+ rWriter.attribute("color",
+ convertColorToString(rTextLinePrimitive2D.getLineColor()));
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D:
+ {
+ const TextSimplePortionPrimitive2D& rTextSimplePortionPrimitive2D
+ = dynamic_cast<const TextSimplePortionPrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("textsimpleportion");
+
+ basegfx::B2DVector aScale, aTranslate;
+ double fRotate, fShearX;
+ if (rTextSimplePortionPrimitive2D.getTextTransform().decompose(aScale, aTranslate,
+ fRotate, fShearX))
+ {
+ rWriter.attribute("width", aScale.getX());
+ rWriter.attribute("height", aScale.getY());
+ }
+ rWriter.attribute("x", aTranslate.getX());
+ rWriter.attribute("y", aTranslate.getY());
+ OUString aText = rTextSimplePortionPrimitive2D.getText();
+ // TODO share code with sax_fastparser::FastSaxSerializer::write().
+ rWriter.attribute("text", aText.replaceAll("", "&#9;"));
+ rWriter.attribute("fontcolor", convertColorToString(
+ rTextSimplePortionPrimitive2D.getFontColor()));
+
+ const drawinglayer::attribute::FontAttribute& aFontAttribute
+ = rTextSimplePortionPrimitive2D.getFontAttribute();
+ rWriter.attribute("familyname", aFontAttribute.getFamilyName());
+ const std::vector<double> aDx = rTextSimplePortionPrimitive2D.getDXArray();
+ if (aDx.size())
+ {
+ for (size_t iDx = 0; iDx < aDx.size(); ++iDx)
+ {
+ OString sName = "dx" + OString::number(iDx);
+ rWriter.attribute(sName, OString::number(aDx[iDx]));
+ }
+ }
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_GROUPPRIMITIVE2D:
+ {
+ const GroupPrimitive2D& rGroupPrimitive2D
+ = dynamic_cast<const GroupPrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("group");
+ decomposeAndWrite(rGroupPrimitive2D.getChildren(), rWriter);
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_MASKPRIMITIVE2D:
+ {
+ const MaskPrimitive2D& rMaskPrimitive2D
+ = dynamic_cast<const MaskPrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("mask");
+ writePolyPolygon(rWriter, rMaskPrimitive2D.getMask());
+ decomposeAndWrite(rMaskPrimitive2D.getChildren(), rWriter);
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D:
+ {
+ const UnifiedTransparencePrimitive2D& rUnifiedTransparencePrimitive2D
+ = dynamic_cast<const UnifiedTransparencePrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("unifiedtransparence");
+ rWriter.attribute(
+ "transparence",
+ std::lround(100 * rUnifiedTransparencePrimitive2D.getTransparence()));
+ decomposeAndWrite(rUnifiedTransparencePrimitive2D.getChildren(), rWriter);
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D:
+ {
+ const ObjectInfoPrimitive2D& rObjectInfoPrimitive2D
+ = dynamic_cast<const ObjectInfoPrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("objectinfo");
+
+ decomposeAndWrite(rObjectInfoPrimitive2D.getChildren(), rWriter);
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_STRUCTURETAGPRIMITIVE2D:
+ {
+ const StructureTagPrimitive2D& rStructureTagPrimitive2D
+ = dynamic_cast<const StructureTagPrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("structuretag");
+ rWriter.attribute("structureelement",
+ rStructureTagPrimitive2D.getStructureElement());
+
+ decomposeAndWrite(rStructureTagPrimitive2D.getChildren(), rWriter);
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_SVGRADIALGRADIENTPRIMITIVE2D:
+ {
+ const SvgRadialGradientPrimitive2D& rSvgRadialGradientPrimitive2D
+ = dynamic_cast<const SvgRadialGradientPrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("svgradialgradient");
+ if (rSvgRadialGradientPrimitive2D.isFocalSet())
+ {
+ basegfx::B2DPoint aFocalAttribute = rSvgRadialGradientPrimitive2D.getFocal();
+ rWriter.attribute("focalx", aFocalAttribute.getX());
+ rWriter.attribute("focaly", aFocalAttribute.getY());
+ }
+
+ basegfx::B2DPoint aStartPoint = rSvgRadialGradientPrimitive2D.getStart();
+ rWriter.attribute("startx", aStartPoint.getX());
+ rWriter.attribute("starty", aStartPoint.getY());
+ rWriter.attribute("radius",
+ OString::number(rSvgRadialGradientPrimitive2D.getRadius()));
+ writeSpreadMethod(rWriter, rSvgRadialGradientPrimitive2D.getSpreadMethod());
+ rWriter.attributeDouble(
+ "opacity",
+ rSvgRadialGradientPrimitive2D.getGradientEntries().front().getOpacity());
+
+ rWriter.startElement("transform");
+ writeMatrix(rWriter, rSvgRadialGradientPrimitive2D.getGradientTransform());
+ rWriter.endElement();
+
+ writePolyPolygon(rWriter, rSvgRadialGradientPrimitive2D.getPolyPolygon());
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_SVGLINEARGRADIENTPRIMITIVE2D:
+ {
+ const SvgLinearGradientPrimitive2D& rSvgLinearGradientPrimitive2D
+ = dynamic_cast<const SvgLinearGradientPrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("svglineargradient");
+ basegfx::B2DPoint aStartAttribute = rSvgLinearGradientPrimitive2D.getStart();
+ basegfx::B2DPoint aEndAttribute = rSvgLinearGradientPrimitive2D.getEnd();
+
+ rWriter.attribute("startx", aStartAttribute.getX());
+ rWriter.attribute("starty", aStartAttribute.getY());
+ rWriter.attribute("endx", aEndAttribute.getX());
+ rWriter.attribute("endy", aEndAttribute.getY());
+ writeSpreadMethod(rWriter, rSvgLinearGradientPrimitive2D.getSpreadMethod());
+ rWriter.attributeDouble(
+ "opacity",
+ rSvgLinearGradientPrimitive2D.getGradientEntries().front().getOpacity());
+
+ rWriter.startElement("transform");
+ writeMatrix(rWriter, rSvgLinearGradientPrimitive2D.getGradientTransform());
+ rWriter.endElement();
+
+ writePolyPolygon(rWriter, rSvgLinearGradientPrimitive2D.getPolyPolygon());
+
+ rWriter.endElement();
+ }
+ break;
+
+ case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D:
+ {
+ const MetafilePrimitive2D& rMetafilePrimitive2D
+ = dynamic_cast<const MetafilePrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("metafile");
+ drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer;
+ // since the graphic is not rendered in a document, we do not need a concrete view information
+ rMetafilePrimitive2D.get2DDecomposition(
+ aPrimitiveContainer, drawinglayer::geometry::ViewInformation2D());
+ decomposeAndWrite(aPrimitiveContainer, rWriter);
+ rWriter.endElement();
+ }
+
+ break;
+
+ case PRIMITIVE2D_ID_SDRRECTANGLEPRIMITIVE2D:
+ {
+ // SdrRectanglePrimitive2D is private to us.
+ rWriter.startElement("sdrrectangle");
+ drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer;
+ pBasePrimitive->get2DDecomposition(aPrimitiveContainer,
+ drawinglayer::geometry::ViewInformation2D());
+ decomposeAndWrite(aPrimitiveContainer, rWriter);
+ rWriter.endElement();
+ break;
+ }
+
+ case PRIMITIVE2D_ID_SDRBLOCKTEXTPRIMITIVE2D:
+ {
+ // SdrBlockTextPrimitive2D is private to us.
+ rWriter.startElement("sdrblocktext");
+ drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer;
+ pBasePrimitive->get2DDecomposition(aPrimitiveContainer,
+ drawinglayer::geometry::ViewInformation2D());
+ decomposeAndWrite(aPrimitiveContainer, rWriter);
+ rWriter.endElement();
+ break;
+ }
+
+ case PRIMITIVE2D_ID_TEXTHIERARCHYBLOCKPRIMITIVE2D:
+ {
+ // TextHierarchyBlockPrimitive2D.
+ rWriter.startElement("texthierarchyblock");
+ drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer;
+ pBasePrimitive->get2DDecomposition(aPrimitiveContainer,
+ drawinglayer::geometry::ViewInformation2D());
+ decomposeAndWrite(aPrimitiveContainer, rWriter);
+ rWriter.endElement();
+ break;
+ }
+
+ case PRIMITIVE2D_ID_TEXTHIERARCHYPARAGRAPHPRIMITIVE2D:
+ {
+ // TextHierarchyParagraphPrimitive2D.
+ rWriter.startElement("texthierarchyparagraph");
+ drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer;
+ pBasePrimitive->get2DDecomposition(aPrimitiveContainer,
+ drawinglayer::geometry::ViewInformation2D());
+ decomposeAndWrite(aPrimitiveContainer, rWriter);
+ rWriter.endElement();
+ break;
+ }
+
+ case PRIMITIVE2D_ID_TEXTHIERARCHYLINEPRIMITIVE2D:
+ {
+ // TextHierarchyLinePrimitive2D.
+ rWriter.startElement("texthierarchyline");
+ drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer;
+ pBasePrimitive->get2DDecomposition(aPrimitiveContainer,
+ drawinglayer::geometry::ViewInformation2D());
+ decomposeAndWrite(aPrimitiveContainer, rWriter);
+ rWriter.endElement();
+ break;
+ }
+
+ case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D:
+ {
+ // ShadowPrimitive2D.
+ const ShadowPrimitive2D& rShadowPrimitive2D
+ = dynamic_cast<const ShadowPrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("shadow");
+ rWriter.attribute("color",
+ convertColorToString(rShadowPrimitive2D.getShadowColor()));
+ rWriter.attributeDouble("blur", rShadowPrimitive2D.getShadowBlur());
+
+ rWriter.startElement("transform");
+ writeMatrix(rWriter, rShadowPrimitive2D.getShadowTransform());
+ rWriter.endElement();
+
+ rWriter.endElement();
+ break;
+ }
+
+ case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D:
+ {
+ // ModifiedColorPrimitive2D.
+ const ModifiedColorPrimitive2D& rModifiedColorPrimitive2D
+ = dynamic_cast<const ModifiedColorPrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("modifiedColor");
+ const basegfx::BColorModifierSharedPtr& aColorModifier
+ = rModifiedColorPrimitive2D.getColorModifier();
+ rWriter.attribute("modifier", aColorModifier->getModifierName());
+
+ decomposeAndWrite(rModifiedColorPrimitive2D.getChildren(), rWriter);
+ rWriter.endElement();
+ break;
+ }
+ case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D:
+ {
+ // SoftEdgePrimitive2D.
+ const SoftEdgePrimitive2D& rSoftEdgePrimitive2D
+ = dynamic_cast<const SoftEdgePrimitive2D&>(*pBasePrimitive);
+ rWriter.startElement("softedge");
+ rWriter.attribute("radius", OUString::number(rSoftEdgePrimitive2D.getRadius()));
+
+ decomposeAndWrite(rSoftEdgePrimitive2D.getChildren(), rWriter);
+ rWriter.endElement();
+ break;
+ }
+
+ case PRIMITIVE2D_ID_SCENEPRIMITIVE2D:
+ {
+ const auto& rScenePrimitive2D
+ = dynamic_cast<const drawinglayer::primitive2d::ScenePrimitive2D&>(
+ *pBasePrimitive);
+ rWriter.startElement("scene");
+
+ auto const& rSceneAttribute = rScenePrimitive2D.getSdrSceneAttribute();
+
+ rWriter.attribute("shadowSlant", rSceneAttribute.getShadowSlant());
+ rWriter.attribute("isTwoSidedLighting",
+ sal_Int32(rSceneAttribute.getTwoSidedLighting()));
+ writeShadeMode(rWriter, rSceneAttribute.getShadeMode());
+ writeProjectionMode(rWriter, rSceneAttribute.getProjectionMode());
+
+ auto const& rLightingAttribute = rScenePrimitive2D.getSdrLightingAttribute();
+ rWriter.attribute("ambientLightColor",
+ convertColorToString(rLightingAttribute.getAmbientLightColor()));
+ rWriter.startElement("lights");
+ for (auto const& rLight : rLightingAttribute.getLightVector())
+ {
+ rWriter.startElement("light");
+ rWriter.attribute("color", convertColorToString(rLight.getColor()));
+ rWriter.attribute("directionVectorX", rLight.getDirection().getX());
+ rWriter.attribute("directionVectorY", rLight.getDirection().getY());
+ rWriter.attribute("specular", sal_Int32(rLight.getSpecular()));
+ rWriter.endElement();
+ }
+ rWriter.endElement();
+
+ Primitive3DXmlDump aPrimitive3DXmlDump;
+ aPrimitive3DXmlDump.decomposeAndWrite(rScenePrimitive2D.getChildren3D(), rWriter);
+
+ rWriter.endElement();
+ break;
+ }
+
+ default:
+ {
+ const char* aName = "unhandled";
+ switch (nId)
+ {
+ case PRIMITIVE2D_ID_RANGE_SVX | 14: // PRIMITIVE2D_ID_SDRCELLPRIMITIVE2D
+ {
+ aName = "sdrCell";
+ break;
+ }
+ }
+ rWriter.startElement(aName);
+ rWriter.attribute("id", sCurrentElementTag);
+ rWriter.attribute("idNumber", nId);
+
+ auto pBufferedDecomposition
+ = dynamic_cast<const BufferedDecompositionPrimitive2D*>(pBasePrimitive);
+ if (pBufferedDecomposition)
+ {
+ rWriter.attribute(
+ "transparenceForShadow",
+ OString::number(pBufferedDecomposition->getTransparenceForShadow()));
+ }
+
+ drawinglayer::primitive2d::Primitive2DContainer aPrimitiveContainer;
+ pBasePrimitive->get2DDecomposition(aPrimitiveContainer,
+ drawinglayer::geometry::ViewInformation2D());
+ decomposeAndWrite(aPrimitiveContainer, rWriter);
+ rWriter.endElement();
+ }
+ break;
+ }
+ }
+}
+
+} // end namespace drawinglayer
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/tools/wmfemfhelper.cxx b/drawinglayer/source/tools/wmfemfhelper.cxx
new file mode 100644
index 0000000000..31bad2a0ab
--- /dev/null
+++ b/drawinglayer/source/tools/wmfemfhelper.cxx
@@ -0,0 +1,3011 @@
+/* -*- 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 <basegfx/utils/gradienttools.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(
+ std::move(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(
+ std::move(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());
+ 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));
+ drawinglayer::attribute::StrokeAttribute aStrokeAttribute(
+ std::move(fDotDashArray),
+ fAccumulated);
+
+ rTarget.append(
+ new drawinglayer::primitive2d::PolygonStrokePrimitive2D(
+ std::move(aLinePolygon),
+ std::move(aLineAttribute),
+ std::move(aStrokeAttribute)));
+ }
+ else
+ {
+ rTarget.append(
+ new drawinglayer::primitive2d::PolygonStrokePrimitive2D(
+ std::move(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(
+ 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
+ };
+
+ AlphaMask aMask(rBitmap.CreateAlphaMask(aWhite));
+ Bitmap aSolid(rBitmap.GetSizePixel(), vcl::PixelFormat::N8_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);
+ }
+
+ return drawinglayer::attribute::FillGradientAttribute(
+ rGradient.GetStyle(),
+ static_cast<double>(rGradient.GetBorder()) * 0.01,
+ static_cast<double>(rGradient.GetOfsX()) * 0.01,
+ static_cast<double>(rGradient.GetOfsY()) * 0.01,
+ toRadians(rGradient.GetAngle()),
+ basegfx::BColorStops(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)
+ {
+ drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient));
+ basegfx::BColor aSingleColor;
+
+ if (aAttribute.getColorStops().isSingleColor(aSingleColor))
+ {
+ // not really a gradient. Create filled rectangle
+ return CreateColorWallpaper(rRange, aSingleColor, rPropertyHolder);
+ }
+ else
+ {
+ // really a gradient
+ rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pRetval(
+ new drawinglayer::primitive2d::FillGradientPrimitive2D(
+ rRange,
+ std::move(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,
+ std::vector< sal_Bool >&& rKashidaArray,
+ 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());
+ 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),
+ std::move(rKashidaArray),
+ 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),
+ std::vector(rKashidaArray),
+ std::move(aFontAttribute),
+ std::move(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');
+ css::lang::Locale aLocale(LanguageTag(
+ rProperty.getLanguageType()).getLocale());
+
+ aTargets.push_back(
+ new drawinglayer::primitive2d::TextCharacterStrikeoutPrimitive2D(
+ aTextTransform,
+ fLineWidth,
+ rProperty.getTextColor(),
+ aStrikeoutChar,
+ std::move(aFontAttribute),
+ std::move(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 KernArray& rDXArray = pA->GetDXArray();
+ std::vector< sal_Bool > aKashidaArray = pA->GetKashidaArray();
+
+ 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),
+ std::move(aKashidaArray),
+ 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();
+ drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient));
+ basegfx::B2DPolyPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange));
+ basegfx::BColor aSingleColor;
+
+ if (aAttribute.getColorStops().isSingleColor(aSingleColor))
+ {
+ // 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,
+ std::move(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(
+ std::move(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();
+ 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(),
+ std::move(aAttribute)));
+
+ rTargetHolders.Current().append(
+ new drawinglayer::primitive2d::MaskPrimitive2D(
+ std::move(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();
+ drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient));
+ basegfx::BColor aSingleColor;
+
+ if (aAttribute.getColorStops().isSingleColor(aSingleColor))
+ {
+ // not really a gradient; create UnifiedTransparencePrimitive2D
+ rTargetHolders.Current().append(
+ new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
+ std::move(xSubContent),
+ aSingleColor.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,
+ std::move(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();
+ drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient));
+ basegfx::BColor aSingleColor;
+
+ if (aAttribute.getColorStops().isSingleColor(aSingleColor))
+ {
+ // not really a gradient
+ rTargetHolders.Current().append(
+ new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
+ std::move(aPolyPolygon),
+ aSingleColor));
+ }
+ else
+ {
+ // really a gradient
+ rTargetHolders.Current().append(
+ new drawinglayer::primitive2d::PolyPolygonGradientPrimitive2D(
+ aPolyPolygon,
+ std::move(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: */