diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /drawinglayer/source/tools | |
parent | Initial commit. (diff) | |
download | libreoffice-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')
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("", "	")); + 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: */ |