diff options
Diffstat (limited to '')
-rw-r--r-- | drawinglayer/source/tools/emfphelperdata.cxx | 2268 |
1 files changed, 2268 insertions, 0 deletions
diff --git a/drawinglayer/source/tools/emfphelperdata.cxx b/drawinglayer/source/tools/emfphelperdata.cxx new file mode 100644 index 000000000..aaa9b1bf7 --- /dev/null +++ b/drawinglayer/source/tools/emfphelperdata.cxx @@ -0,0 +1,2268 @@ +/* -*- 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 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 EmfPlusRecordTypeDrawClosedCurve: return "EmfPlusRecordTypeDrawClosedCurve"; + 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), + 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 header, version; + + rMS.ReadUInt32(header).ReadUInt32(version).ReadUInt32(mnHDPI).ReadUInt32(mnVDPI); + SAL_INFO("drawinglayer.emf", "EMF+\tHeader: 0x" << std::hex << header); + SAL_INFO("drawinglayer.emf", "EMF+\tVersion: " << std::dec << version); + SAL_INFO("drawinglayer.emf", "EMF+\tHorizontal DPI: " << mnHDPI); + SAL_INFO("drawinglayer.emf", "EMF+\tVertical DPI: " << mnVDPI); + SAL_INFO("drawinglayer.emf", "EMF+\tDual: " << ((flags & 1) ? "true" : "false")); + 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; + sal_Int32 rectangles; + 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.ReadInt32(rectangles); + + for (int i = 0; i < rectangles; i++) + { + float x, y, width, height; + 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); + + ::basegfx::B2DPolyPolygon polyPolygon(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::B2DPoint aStartPoint, aControlPointA, aControlPointB, aEndPoint; + ::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 + aStartPoint = Map(x1, y1); + aPolygon.append(aStartPoint); + 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); + + aControlPointA = Map(x2, y2); + aControlPointB = Map(x3, y3); + aEndPoint = Map(x4, y4); + aPolygon.appendBezierSegment(aControlPointA, aControlPointB, aEndPoint); + // The ending coordinate of one Bezier curve is the starting coordinate of the next. + aStartPoint = aEndPoint; + } + EMFPPlusDrawPolygon(::basegfx::B2DPolyPolygon(aPolygon), flags & 0xff); + 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_WARN_IF(aTension != 0, "drawinglayer.emf", + "EMF+\t TODO Add support for tension different than 0"); + 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); + if (type == EmfPlusRecordTypeFillClosedCurve) + EMFPPlusFillPolygon(path.GetPolygon(*this, /* bMapIt */ true, + /*bAddLineToCloseShape */ true), + flags & 0x8000, brushIndexOrColor); + else + EMFPPlusDrawPolygon(path.GetPolygon(*this, /* bMapIt */ true, + /*bAddLineToCloseShape */ true), + 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"); + ::basegfx::B2DPoint aDstPoint(dx, dy); + ::basegfx::B2DSize aDstSize(dw, dh); + + const basegfx::B2DHomMatrix aTransformMatrix + = maMapTransform + * basegfx::B2DHomMatrix( + /* Row 0, Column 0 */ aDstSize.getX(), + /* Row 0, Column 1 */ fShearX, + /* Row 0, Column 2 */ aDstPoint.getX(), + /* Row 1, Column 0 */ fShearY, + /* Row 1, Column 1 */ aDstSize.getY(), + /* 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( + VCLUnoHelper::CreateVCLXBitmap(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::B2DSize(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::B2DSize(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: */ |