/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { #define EmfPlusRecordTypeHeader 0x4001 #define EmfPlusRecordTypeEndOfFile 0x4002 //TODO 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 //TODO EmfPlusRecordTypeFillRegion 0x4013 #define EmfPlusRecordTypeFillPath 0x4014 #define EmfPlusRecordTypeDrawPath 0x4015 //TODO EmfPlusRecordTypeFillClosedCurve 0x4016 //TODO EmfPlusRecordTypeDrawClosedCurve 0x4017 //TODO EmfPlusRecordTypeDrawCurve 0x4018 #define EmfPlusRecordTypeDrawBeziers 0x4019 #define EmfPlusRecordTypeDrawImage 0x401A #define EmfPlusRecordTypeDrawImagePoints 0x401B #define EmfPlusRecordTypeDrawString 0x401C #define EmfPlusRecordTypeSetRenderingOrigin 0x401D #define EmfPlusRecordTypeSetAntiAliasMode 0x401E #define EmfPlusRecordTypeSetTextRenderingHint 0x401F #define EmfPlusRecordTypeSetInterpolationMode 0x4021 #define EmfPlusRecordTypeSetPixelOffsetMode 0x4022 //TODO EmfPlusRecordTypeSetCompositingMode 0x4023 #define EmfPlusRecordTypeSetCompositingQuality 0x4024 #define EmfPlusRecordTypeSave 0x4025 #define EmfPlusRecordTypeRestore 0x4026 //TODO EmfPlusRecordTypeBeginContainer 0x4027 #define EmfPlusRecordTypeBeginContainerNoParams 0x4028 #define EmfPlusRecordTypeEndContainer 0x4027 #define EmfPlusRecordTypeSetWorldTransform 0x402A #define EmfPlusRecordTypeResetWorldTransform 0x402B #define EmfPlusRecordTypeMultiplyWorldTransform 0x402C #define EmfPlusRecordTypeTranslateWorldTransform 0x402D #define EmfPlusRecordTypeScaleWorldTransform 0x402E //TODO EmfPlusRecordTypeRotateWorldTransform 0x402F #define EmfPlusRecordTypeSetPageTransform 0x4030 //TODO EmfPlusRecordTypeResetClip 0x4031 #define EmfPlusRecordTypeSetClipRect 0x4032 #define EmfPlusRecordTypeSetClipPath 0x4033 #define EmfPlusRecordTypeSetClipRegion 0x4034 //TODO EmfPlusRecordTypeOffsetClip 0x4035 #define EmfPlusRecordTypeDrawDriverString 0x4036 //TODO EmfPlusRecordTypeStrokeFillPath 0x4037 //TODO EmfPlusRecordTypeSerializableObject 0x4038 //TODO EmfPlusRecordTypeSetTSGraphics 0x4039 //TODO EmfPlusRecordTypeSetTSClip 0x403A #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 #define EmfPlusRegionInitialStateInfinite 0x10000003 enum EmfPlusCombineMode { EmfPlusCombineModeReplace = 0x00000000, EmfPlusCombineModeIntersect = 0x00000001, EmfPlusCombineModeUnion = 0x00000002, EmfPlusCombineModeXOR = 0x00000003, EmfPlusCombineModeExclude = 0x00000004, EmfPlusCombineModeComplement = 0x00000005 }; enum EmfPlusFontStyle { FontStyleBold = 0x00000001, FontStyleItalic = 0x00000002, FontStyleUnderline = 0x00000004, FontStyleStrikeout = 0x00000008 }; enum UnitType { UnitTypeWorld = 0x00, UnitTypeDisplay = 0x01, UnitTypePixel = 0x02, UnitTypePoint = 0x03, UnitTypeInch = 0x04, UnitTypeDocument = 0x05, UnitTypeMillimeter = 0x06 }; const char* emfTypeToName(sal_uInt16 type) { switch(type) { case EmfPlusRecordTypeHeader: return "EmfPlusRecordTypeHeader"; case EmfPlusRecordTypeEndOfFile: return "EmfPlusRecordTypeEndOfFile"; 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 EmfPlusRecordTypeFillEllipse: return "EmfPlusRecordTypeFillEllipse"; case EmfPlusRecordTypeDrawEllipse: return "EmfPlusRecordTypeDrawEllipse"; case EmfPlusRecordTypeFillPie: return "EmfPlusRecordTypeFillPie"; case EmfPlusRecordTypeDrawPie: return "EmfPlusRecordTypeDrawPie"; case EmfPlusRecordTypeDrawArc: return "EmfPlusRecordTypeDrawArc"; 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 EmfPlusRecordTypeSetInterpolationMode: return "EmfPlusRecordTypeSetInterpolationMode"; case EmfPlusRecordTypeSetPixelOffsetMode: return "EmfPlusRecordTypeSetPixelOffsetMode"; case EmfPlusRecordTypeSetCompositingQuality: return "EmfPlusRecordTypeSetCompositingQuality"; case EmfPlusRecordTypeSave: return "EmfPlusRecordTypeSave"; case EmfPlusRecordTypeRestore: return "EmfPlusRecordTypeRestore"; 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 EmfPlusRecordTypeSetClipRect: return "EmfPlusRecordTypeSetClipRect"; case EmfPlusRecordTypeSetClipPath: return "EmfPlusRecordTypeSetClipPath"; case EmfPlusRecordTypeSetClipRegion: return "EmfPlusRecordTypeSetClipRegion"; case EmfPlusRecordTypeDrawDriverString: return "EmfPlusRecordTypeDrawDriverString"; } return ""; } } // anonymous namespace using namespace ::com::sun::star; using namespace ::basegfx; namespace cppcanvas { namespace internal { void ImplRenderer::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 ); } void ImplRenderer::ReadPoint (SvStream& s, float& x, float& y, sal_uInt32 flags) { if (flags & 0x4000) { sal_Int16 ix, iy; s.ReadInt16( ix ).ReadInt16( iy ); x = ix; y = iy; } else s.ReadFloat( x ).ReadFloat( y ); } void ImplRenderer::MapToDevice (double& x, double& y) { // TODO: other units x = 100*nMmX*x/nPixX; y = 100*nMmY*y/nPixY; } ::basegfx::B2DPoint ImplRenderer::Map (double ix, double iy) { double x, y; x = ix*aWorldTransform.eM11 + iy*aWorldTransform.eM21 + aWorldTransform.eDx; y = ix*aWorldTransform.eM12 + iy*aWorldTransform.eM22 + aWorldTransform.eDy; MapToDevice (x, y); x -= nFrameLeft; y -= nFrameTop; x *= aBaseTransform.eM11; y *= aBaseTransform.eM22; return ::basegfx::B2DPoint (x, y); } ::basegfx::B2DSize ImplRenderer::MapSize (double iwidth, double iheight) { double w, h; w = iwidth*aWorldTransform.eM11 + iheight*aWorldTransform.eM21; h = iwidth*aWorldTransform.eM12 + iheight*aWorldTransform.eM22; MapToDevice (w, h); w *= aBaseTransform.eM11; h *= aBaseTransform.eM22; return ::basegfx::B2DSize (w, h); } #define COLOR(x) \ vcl::unotools::colorToDoubleSequence( ::Color (0xff - (x >> 24), \ (x >> 16) & 0xff, \ (x >> 8) & 0xff, \ x & 0xff), \ rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace()); void ImplRenderer::EMFPPlusFillPolygon (::basegfx::B2DPolyPolygon& polygon, const ActionFactoryParameters& rParms, OutDevState& rState, const CanvasSharedPtr& rCanvas, bool isColor, sal_uInt32 brushIndexOrColor) { ::basegfx::B2DPolyPolygon localPolygon (polygon); SAL_INFO("cppcanvas.emf", "EMF+\tfill polygon"); localPolygon.transform( rState.mapModeTransform ); ActionSharedPtr pPolyAction; if (isColor) { SAL_INFO("cppcanvas.emf", "EMF+\t\tcolor fill:0x" << std::hex << brushIndexOrColor << std::dec); rState.isFillColorSet = true; rState.isLineColorSet = false; rState.fillColor = COLOR(brushIndexOrColor); pPolyAction = ActionSharedPtr ( internal::PolyPolyActionFactory::createPolyPolyAction( localPolygon, rParms.mrCanvas, rState ) ); } else { rState.isFillColorSet = true; // extract UseBrush EMFPBrush* brush = static_cast( aObjects [brushIndexOrColor & 0xff] ); SAL_INFO("cppcanvas.emf", "EMF+\tbrush fill slot: " << brushIndexOrColor << " (type: " << (brush ? brush->GetType() : -1) << ")"); // give up in case something wrong happened if( !brush ) return; rState.isFillColorSet = false; rState.isLineColorSet = false; if (brush->type == 1) { // 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; } rState.isFillColorSet = true; rState.isLineColorSet = false; ::Color fillColor; if (isHatchBlend) { fillColor = brush->solidColor; fillColor.Merge(brush->secondColor, static_cast(255 * blendFactor)); } else { fillColor = brush->secondColor; } rState.fillColor = vcl::unotools::colorToDoubleSequence(fillColor, rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace()); pPolyAction = ActionSharedPtr ( internal::PolyPolyActionFactory::createPolyPolyAction( localPolygon, rParms.mrCanvas, rState ) ); } else if (brush->type == 3 || brush->type == 4) { if (brush->type == 3 && !(brush->additionalFlags & 0x1)) return; // we are unable to parse these brushes yet ::basegfx::B2DHomMatrix aTextureTransformation; ::basegfx::B2DHomMatrix aWorldTransformation; ::basegfx::B2DHomMatrix aBaseTransformation; rendering::Texture aTexture; aWorldTransformation.set (0, 0, aWorldTransform.eM11); aWorldTransformation.set (0, 1, aWorldTransform.eM21); aWorldTransformation.set (0, 2, aWorldTransform.eDx); aWorldTransformation.set (1, 0, aWorldTransform.eM12); aWorldTransformation.set (1, 1, aWorldTransform.eM22); aWorldTransformation.set (1, 2, aWorldTransform.eDy); aBaseTransformation.set (0, 0, aBaseTransform.eM11); aBaseTransformation.set (0, 1, aBaseTransform.eM21); aBaseTransformation.set (0, 2, aBaseTransform.eDx); aBaseTransformation.set (1, 0, aBaseTransform.eM12); aBaseTransformation.set (1, 1, aBaseTransform.eM22); aBaseTransformation.set (1, 2, aBaseTransform.eDy); if (brush->type == 4) { aTextureTransformation.scale (brush->areaWidth, brush->areaHeight); aTextureTransformation.translate (brush->areaX, brush->areaY); } else { aTextureTransformation.translate (-0.5, -0.5); aTextureTransformation.scale (brush->areaWidth, brush->areaHeight); aTextureTransformation.translate (brush->areaX,brush->areaY); } if (brush->hasTransformation) { ::basegfx::B2DHomMatrix aTransformation; aTransformation.set (0, 0, brush->brush_transformation.eM11); aTransformation.set (0, 1, brush->brush_transformation.eM21); aTransformation.set (0, 2, brush->brush_transformation.eDx); aTransformation.set (1, 0, brush->brush_transformation.eM12); aTransformation.set (1, 1, brush->brush_transformation.eM22); aTransformation.set (1, 2, brush->brush_transformation.eDy); aTextureTransformation *= aTransformation; } aTextureTransformation *= aWorldTransformation; aTextureTransformation.scale (100.0*nMmX/nPixX, 100.0*nMmY/nPixY); aTextureTransformation.translate (-nFrameLeft, -nFrameTop); aTextureTransformation *= rState.mapModeTransform; aTextureTransformation *= aBaseTransformation; aTexture.RepeatModeX = rendering::TexturingMode::CLAMP; aTexture.RepeatModeY = rendering::TexturingMode::CLAMP; aTexture.Alpha = 1.0; basegfx::ODFGradientInfo aGradInfo; OUString aGradientService; const uno::Sequence< double > aStartColor( vcl::unotools::colorToDoubleSequence( brush->solidColor, rParms.mrCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() ) ); const uno::Sequence< double > aEndColor( vcl::unotools::colorToDoubleSequence( brush->secondColor, rParms.mrCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() ) ); uno::Sequence< uno::Sequence < double > > aColors (2); uno::Sequence< double > aStops (2); if (brush->blendPositions) { SAL_INFO("cppcanvas.emf", "EMF+\t\tuse blend"); aColors.realloc (brush->blendPoints); aStops.realloc (brush->blendPoints); int length = aStartColor.getLength (); uno::Sequence< double > aColor (length); OSL_ASSERT (length == aEndColor.getLength()); for (int i = 0; i < brush->blendPoints; i++) { aStops[i] = brush->blendPositions [i]; for (int j = 0; j < length; j++) { if (brush->type == 4) { aColor [j] = aStartColor [j]*(1 - brush->blendFactors[i]) + aEndColor [j]*brush->blendFactors[i]; } else aColor [j] = aStartColor [j]*brush->blendFactors[i] + aEndColor [j]*(1 - brush->blendFactors[i]); } aColors[i] = aColor; } } else if (brush->colorblendPositions) { SAL_INFO("cppcanvas.emf", "EMF+\t\tuse color blend"); aColors.realloc (brush->colorblendPoints); aStops.realloc (brush->colorblendPoints); for (int i = 0; i < brush->colorblendPoints; i++) { aStops[i] = brush->colorblendPositions [i]; aColors[(brush->type == 4) ? i : brush->colorblendPoints - 1 - i] = vcl::unotools::colorToDoubleSequence( brush->colorblendColors [i], rParms.mrCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() ); } } else { aStops[0] = 0.0; aStops[1] = 1.0; if (brush->type == 4) { aColors[0] = aStartColor; aColors[1] = aEndColor; } else { aColors[1] = aStartColor; aColors[0] = aEndColor; } } SAL_INFO("cppcanvas.emf", "EMF+\t\tset gradient"); basegfx::B2DRange aBoundsRectangle (0, 0, 1, 1); if (brush->type == 4) { aGradientService = "LinearGradient"; aGradInfo = basegfx::tools::createLinearODFGradientInfo( aBoundsRectangle, aStops.getLength(), 0, 0); } else { aGradientService = "EllipticalGradient"; aGradInfo = basegfx::tools::createEllipticalODFGradientInfo( aBoundsRectangle, ::basegfx::B2DVector( 0, 0 ), aStops.getLength(), 0, 0); } uno::Reference< lang::XMultiServiceFactory > xFactory( rParms.mrCanvas->getUNOCanvas()->getDevice()->getParametricPolyPolygonFactory() ); if( xFactory.is() ) { uno::Sequence args( 3 ); beans::PropertyValue aProp; aProp.Name = "Colors"; aProp.Value <<= aColors; args[0] <<= aProp; aProp.Name = "Stops"; aProp.Value <<= aStops; args[1] <<= aProp; aProp.Name = "AspectRatio"; aProp.Value <<= static_cast(1); args[2] <<= aProp; aTexture.Gradient.set( xFactory->createInstanceWithArguments( aGradientService, args ), uno::UNO_QUERY); } ::basegfx::unotools::affineMatrixFromHomMatrix( aTexture.AffineTransform, aTextureTransformation ); if( aTexture.Gradient.is() ) pPolyAction = ActionSharedPtr ( internal::PolyPolyActionFactory::createPolyPolyAction( localPolygon, rParms.mrCanvas, rState, aTexture ) ); } } if( pPolyAction ) { SAL_INFO("cppcanvas.emf", "EMF+\t\tadd poly action"); maActions.push_back( MtfAction( pPolyAction, rParms.mrCurrActionIndex ) ); rParms.mrCurrActionIndex += pPolyAction->getActionCount()-1; } } double ImplRenderer::EMFPPlusDrawLineCap(const ::basegfx::B2DPolygon& rPolygon, double fPolyLength, const ::basegfx::B2DPolyPolygon& rLineCap, bool bIsFilled, bool bStart, const rendering::StrokeAttributes& rAttributes, const ActionFactoryParameters& rParms, OutDevState& rState) { if (!rLineCap.count()) return 0.0; // createAreaGeometryForLineStartEnd normalises the arrows height // before scaling (i.e. scales down by rPolygon.height), hence // we pre-scale it (which means we can avoid changing the logic // that would affect arrows rendered outside of EMF+). const double fWidth = rAttributes.StrokeWidth*rLineCap.getB2DRange().getWidth(); // When drawing an outline (as opposed to a filled endCap), we also // need to take account that the brush width also adds to the area // of the polygon. const double fShift = bIsFilled ? 0 : rAttributes.StrokeWidth; double fConsumed = 0; basegfx::B2DPolyPolygon aArrow(basegfx::tools::createAreaGeometryForLineStartEnd( rPolygon, rLineCap, bStart, fWidth, fPolyLength, 0, &fConsumed, fShift)); // createAreaGeometryForLineStartEnd from some reason always sets // the path as closed, correct it aArrow.setClosed(rLineCap.isClosed()); // If the endcap is filled, we draw ONLY the filling, if it isn't // filled we draw ONLY the outline, but never both. if (bIsFilled) { bool bWasFillColorSet = rState.isFillColorSet; rState.isFillColorSet = true; rState.fillColor = rState.lineColor; ActionSharedPtr pAction2(internal::PolyPolyActionFactory::createPolyPolyAction(aArrow, rParms.mrCanvas, rState)); if (pAction2) { maActions.push_back(MtfAction(pAction2, rParms.mrCurrActionIndex)); rParms.mrCurrActionIndex += pAction2->getActionCount()-1; } rState.isFillColorSet = bWasFillColorSet; } else { ActionSharedPtr pAction(internal::PolyPolyActionFactory::createPolyPolyAction(aArrow, rParms.mrCanvas, rState, rAttributes)); if (pAction) { maActions.push_back(MtfAction(pAction, rParms.mrCurrActionIndex)); rParms.mrCurrActionIndex += pAction->getActionCount()-1; } } // There isn't any clear definition of how far the line should extend // for arrows, however the following values seem to give best results // (fConsumed/2 draws the line to the center-point of the endcap // for filled caps -- however it is likely this will need to be // changed once we start taking baseInset into account). if (bIsFilled) return fConsumed/2; else return rAttributes.StrokeWidth; } void ImplRenderer::EMFPPlusDrawPolygon (const ::basegfx::B2DPolyPolygon& polygon, const ActionFactoryParameters& rParms, OutDevState& rState, const CanvasSharedPtr& rCanvas, sal_uInt32 penIndex) { EMFPPen* pen = static_cast( aObjects [penIndex & 0xff] ); SAL_WARN_IF( !pen, "cppcanvas.emf", "emf+ missing pen" ); if (pen) { rState.isFillColorSet = false; rState.isLineColorSet = true; rState.lineColor = vcl::unotools::colorToDoubleSequence (pen->GetColor (), rCanvas->getUNOCanvas ()->getDevice()->getDeviceColorSpace()); basegfx::B2DPolyPolygon aPolyPolygon(polygon); aPolyPolygon.transform(rState.mapModeTransform); rendering::StrokeAttributes aCommonAttributes; // some attributes are common for the polygon, and the line // starts & ends - like the stroke width pen->SetStrokeWidth(aCommonAttributes, *this, rState); // but eg. dashing has to be additionally set only on the // polygon rendering::StrokeAttributes aPolygonAttributes(aCommonAttributes); pen->SetStrokeAttributes(aPolygonAttributes); basegfx::B2DPolyPolygon aFinalPolyPolygon; // render line starts & ends if present if (!pen->customStartCap && !pen->customEndCap) aFinalPolyPolygon = aPolyPolygon; else { for (sal_uInt32 i = 0; i < aPolyPolygon.count(); ++i) { basegfx::B2DPolygon aPolygon(aPolyPolygon.getB2DPolygon(i)); if (!aPolygon.isClosed()) { double fStart = 0.0; double fEnd = 0.0; double fPolyLength = basegfx::tools::getLength(aPolygon); // line start if (pen->customStartCap) { rendering::StrokeAttributes aAttributes(aCommonAttributes); pen->customStartCap->SetAttributes(aAttributes); fStart = EMFPPlusDrawLineCap(aPolygon, fPolyLength, pen->customStartCap->polygon, pen->customStartCap->mbIsFilled, true, aAttributes, rParms, rState); } // line end if (pen->customEndCap) { rendering::StrokeAttributes aAttributes(aCommonAttributes); pen->customEndCap->SetAttributes(aAttributes); fEnd = EMFPPlusDrawLineCap(aPolygon, fPolyLength, pen->customEndCap->polygon, pen->customEndCap->mbIsFilled, false, aAttributes, rParms, rState); } // build new poly, consume something from the old poly if (fStart != 0.0 || fEnd != 0.0) aPolygon = basegfx::tools::getSnippetAbsolute(aPolygon, fStart, fPolyLength - fEnd, fPolyLength); } aFinalPolyPolygon.append(aPolygon); } } // finally render the polygon ActionSharedPtr pPolyAction(internal::PolyPolyActionFactory::createPolyPolyAction(aFinalPolyPolygon, rParms.mrCanvas, rState, aPolygonAttributes)); if( pPolyAction ) { maActions.push_back(MtfAction(pPolyAction, rParms.mrCurrActionIndex)); rParms.mrCurrActionIndex += pPolyAction->getActionCount()-1; } } } void ImplRenderer::processObjectRecord(SvMemoryStream& rObjectStream, sal_uInt16 flags, sal_uInt32 dataSize, bool bUseWholeStream) { sal_uInt32 index; SAL_INFO("cppcanvas.emf", "EMF+ Object slot: " << (flags & 0xff) << " flags: " << (flags & 0xff00)); index = flags & 0xff; if (aObjects [index] != nullptr) { delete aObjects [index]; aObjects [index] = nullptr; } switch (flags & 0x7f00) { case EmfPlusObjectTypeBrush: { EMFPBrush *brush; aObjects [index] = brush = new EMFPBrush (); brush->Read (rObjectStream, *this); break; } case EmfPlusObjectTypePen: { EMFPPen *pen; aObjects [index] = pen = new EMFPPen (); pen->Read (rObjectStream, *this); break; } case EmfPlusObjectTypePath: sal_uInt32 header, pathFlags; sal_Int32 points; rObjectStream.ReadUInt32( header ).ReadInt32( points ).ReadUInt32( pathFlags ); SAL_INFO("cppcanvas.emf", "EMF+\tpath"); SAL_INFO("cppcanvas.emf", "EMF+\theader: 0x" << std::hex << header << " points: " << std::dec << points << " additional flags: 0x" << std::hex << pathFlags << std::dec); EMFPPath *path; aObjects [index] = path = new EMFPPath (points); path->Read (rObjectStream, pathFlags, *this); break; case EmfPlusObjectTypeRegion: { EMFPRegion *region; aObjects [index] = region = new EMFPRegion (); region->Read (rObjectStream); break; } case EmfPlusObjectTypeImage: { EMFPImage *image; aObjects [index] = image = new EMFPImage; 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; aObjects [index] = font = new EMFPFont; font->emSize = 0; font->sizeUnit = 0; font->fontFlags = 0; font->Read (rObjectStream); break; } case EmfPlusObjectTypeStringFormat: { EMFPStringFormat *stringFormat; aObjects [index] = stringFormat = new EMFPStringFormat(); stringFormat->Read (rObjectStream); break; } case EmfPlusObjectTypeImageAttributes: { SAL_INFO("cppcanvas.emf", "EMF+\t Object type 'image attributes' not yet implemented"); break; } case EmfPlusObjectTypeCustomLineCap: { SAL_INFO("cppcanvas.emf", "EMF+\t Object type 'custom line cap' not yet implemented"); break; } default: SAL_INFO("cppcanvas.emf", "EMF+\tObject unhandled flags: 0x" << std::hex << (flags & 0xff00) << std::dec); break; } } double ImplRenderer::setFont (css::rendering::FontRequest& aFontRequest, sal_uInt8 fontObjectId, const ActionFactoryParameters& rParms, OutDevState& rState) { EMFPFont *font = static_cast< EMFPFont* >( aObjects[ fontObjectId ] ); aFontRequest.FontDescription.FamilyName = font->family; double cellSize = font->emSize; if( font->fontFlags & FontStyleBold ) { aFontRequest.FontDescription.FontDescription.Weight = rendering::PanoseWeight::BOLD; } aFontRequest.CellSize = (rState.mapModeTransform*MapSize( cellSize, 0 )).getX(); rState.xFont = rParms.mrCanvas->getUNOCanvas()->createFont( aFontRequest, uno::Sequence< beans::PropertyValue >(), geometry::Matrix2D() ); return cellSize; } void ImplRenderer::GraphicStatePush(GraphicStateMap& map, sal_Int32 index, OutDevState& rState) { GraphicStateMap::iterator iter = map.find( index ); if ( iter != map.end() ) { EmfPlusGraphicState state = iter->second; map.erase( iter ); SAL_INFO("cppcanvas.emf", "stack index: " << index << " found and erased"); } EmfPlusGraphicState state; state.aWorldTransform = aWorldTransform; state.aDevState = rState; map[ index ] = state; } void ImplRenderer::GraphicStatePop(GraphicStateMap& map, sal_Int32 index, OutDevState& rState) { GraphicStateMap::iterator iter = map.find( index ); if ( iter != map.end() ) { SAL_INFO("cppcanvas.emf", "stack index: " << index << " found"); EmfPlusGraphicState state = iter->second; aWorldTransform = state.aWorldTransform; rState.clip = state.aDevState.clip; rState.clipRect = state.aDevState.clipRect; rState.xClipPoly = state.aDevState.xClipPoly; } } void ImplRenderer::processEMFPlus( MetaCommentAction* pAct, const ActionFactoryParameters& rFactoryParms, OutDevState& rState, const CanvasSharedPtr& rCanvas ) { sal_uInt32 length = pAct->GetDataSize (); SvMemoryStream rMF (const_cast(pAct->GetData ()), length, StreamMode::READ); if (length < 12) { SAL_INFO("cppcanvas.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; rMF.ReadUInt16( type ).ReadUInt16( flags ).ReadUInt32( size ).ReadUInt32( dataSize ); next = rMF.Tell() + ( size - 12 ); if (size < 12) { SAL_INFO("cppcanvas.emf", "Size field is less than 12 bytes"); } else if (size > length) { SAL_INFO("cppcanvas.emf", "Size field is greater than bytes left"); } if (dataSize > (size-12)) { SAL_INFO("cppcanvas.emf", "DataSize field is greater than Size-12"); } SAL_INFO("cppcanvas.emf", "EMF+ record size: " << size << " type: " << emfTypeToName(type) << " flags: " << flags << " data size: " << dataSize); 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(rMF.GetData()) + rMF.Tell() + 4, dataSize - 4); SAL_INFO("cppcanvas.emf", "EMF+ read next object part size: " << size << " type: " << type << " flags: " << flags << " data size: " << dataSize); } else { if (mbMultipart) { SAL_INFO("cppcanvas.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; rMF.ReadUInt32( header ).ReadUInt32( version ).ReadInt32( nHDPI ).ReadInt32( nVDPI ); SAL_INFO("cppcanvas.emf", "EMF+ Header"); SAL_INFO("cppcanvas.emf", "EMF+\theader: 0x" << std::hex << header << " version: " << std::dec << version << " horizontal DPI: " << nHDPI << " vertical DPI: " << nVDPI << " dual: " << (flags & 1)); break; case EmfPlusRecordTypeEndOfFile: SAL_INFO("cppcanvas.emf", "EMF+ EndOfFile"); break; case EmfPlusRecordTypeGetDC: SAL_INFO("cppcanvas.emf", "EMF+ GetDC"); SAL_INFO("cppcanvas.emf", "EMF+\talready used in svtools wmf/emf filter parser"); break; case EmfPlusRecordTypeObject: processObjectRecord (rMF, 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 ) { rMF.ReadUInt32( brushIndexOrColor ); SAL_INFO("cppcanvas.emf", "EMF+ FillPie colorOrIndex: " << brushIndexOrColor); } else if ( type == EmfPlusRecordTypeDrawPie ) { SAL_INFO("cppcanvas.emf", "EMF+ DrawPie"); } else { SAL_INFO("cppcanvas.emf", "EMF+ DrawArc"); } rMF.ReadFloat( startAngle ).ReadFloat( sweepAngle ); float dx, dy, dw, dh; ReadRectangle (rMF, dx, dy, dw, dh, bool(flags & 0x4000)); SAL_INFO("cppcanvas.emf", "EMF+\t RectData: " << dx << "," << dy << " " << dw << "x" << dh); startAngle = 2*M_PI*startAngle/360; sweepAngle = 2*M_PI*sweepAngle/360; B2DPoint mappedCenter (Map (dx + dw/2, dy + dh/2)); B2DSize mappedSize( MapSize (dw/2, dh/2)); float endAngle = startAngle + sweepAngle; startAngle = fmodf(startAngle, static_cast(M_PI*2)); if (startAngle < 0) startAngle += static_cast(M_PI*2); endAngle = fmodf(endAngle, static_cast(M_PI*2)); if (endAngle < 0) endAngle += static_cast(M_PI*2); if (sweepAngle < 0) std::swap (endAngle, startAngle); SAL_INFO("cppcanvas.emf", "EMF+\t adjusted angles: start " << (360.0*startAngle/M_PI) << ", end: " << (360.0*endAngle/M_PI) << " startAngle: " << startAngle << " sweepAngle: " << sweepAngle); B2DPolygon polygon = basegfx::tools::createPolygonFromEllipseSegment (mappedCenter, mappedSize.getX (), mappedSize.getY (), startAngle, endAngle); if ( type != EmfPlusRecordTypeDrawArc ) { polygon.append (mappedCenter); polygon.setClosed (true); } B2DPolyPolygon polyPolygon (polygon); if ( type == EmfPlusRecordTypeFillPie ) EMFPPlusFillPolygon( polyPolygon, rFactoryParms, rState, rCanvas, flags & 0x8000, brushIndexOrColor ); else EMFPPlusDrawPolygon( polyPolygon, rFactoryParms, rState, rCanvas, flags & 0xff ); } break; case EmfPlusRecordTypeFillPath: { sal_uInt32 index = flags & 0xff; sal_uInt32 brushIndexOrColor; rMF.ReadUInt32( brushIndexOrColor ); SAL_INFO("cppcanvas.emf", "EMF+ FillPath slot: " << index); EMFPPlusFillPolygon( static_cast( aObjects [index])->GetPolygon (*this), rFactoryParms, rState, rCanvas, flags & 0x8000, brushIndexOrColor); } 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 ) rMF.ReadUInt32( brushIndexOrColor ); SAL_INFO("cppcanvas.emf", "EMF+ " << (type == EmfPlusRecordTypeFillEllipse ? "Fill" : "Draw") << "Ellipse slot: " << (flags & 0xff)); float dx, dy, dw, dh; ReadRectangle (rMF, dx, dy, dw, dh, bool(flags & 0x4000)); SAL_INFO("cppcanvas.emf", "EMF+ RectData: " << dx << "," << dy << " " << dw << "x" << dh); B2DPoint mappedCenter (Map (dx + dw/2, dy + dh/2)); B2DSize mappedSize( MapSize (dw/2, dh/2)); ::basegfx::B2DPolyPolygon polyPolygon( ::basegfx::B2DPolygon( ::basegfx::tools::createPolygonFromEllipse( mappedCenter, mappedSize.getX (), mappedSize.getY () ) ) ); if ( type == EmfPlusRecordTypeFillEllipse ) EMFPPlusFillPolygon( polyPolygon, rFactoryParms, rState, rCanvas, flags & 0x8000, brushIndexOrColor ); else EMFPPlusDrawPolygon( polyPolygon, rFactoryParms, rState, rCanvas, flags & 0xff ); } break; case EmfPlusRecordTypeFillRects: case EmfPlusRecordTypeDrawRects: { // Silent MSVC warning C4701: potentially uninitialized local variable 'brushIndexOrColor' used sal_uInt32 brushIndexOrColor = 999; sal_Int32 rectangles; bool isColor = (flags & 0x8000); ::basegfx::B2DPolygon polygon; if ( type == EmfPlusRecordTypeFillRects ) { SAL_INFO("cppcanvas.emf", "EMF+ FillRects"); rMF.ReadUInt32( brushIndexOrColor ); SAL_INFO("cppcanvas.emf", "EMF+\t" << (isColor ? "color" : "brush index") << ": 0x" << std::hex << brushIndexOrColor << std::dec); } else { SAL_INFO("cppcanvas.emf", "EMF+ DrawRects"); } rMF.ReadInt32( rectangles ); for (int i=0; i < rectangles; i++) { float x, y, width, height; ReadRectangle (rMF, x, y, width, height, bool(flags & 0x4000)); 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.append (Map (x, y)); SAL_INFO("cppcanvas.emf", "EMF+\trectangle: " << x << ", " << width << "x" << height); ::basegfx::B2DPolyPolygon polyPolygon (polygon); if( type == EmfPlusRecordTypeFillRects ) EMFPPlusFillPolygon( polyPolygon, rFactoryParms, rState, rCanvas, isColor, brushIndexOrColor ); else EMFPPlusDrawPolygon( polyPolygon, rFactoryParms, rState, rCanvas, flags & 0xff ); } break; } case EmfPlusRecordTypeFillPolygon: { sal_uInt8 index = flags & 0xff; sal_uInt32 brushIndexOrColor; sal_Int32 points; rMF.ReadUInt32( brushIndexOrColor ); rMF.ReadInt32( points ); SAL_INFO("cppcanvas.emf", "EMF+ FillPolygon in slot: " << +index << " points: " << points); SAL_INFO("cppcanvas.emf", "EMF+\t: " << ((flags & 0x8000) ? "color" : "brush index") << " 0x" << std::hex << brushIndexOrColor << std::dec); EMFPPath path (points, true); path.Read (rMF, flags, *this); EMFPPlusFillPolygon (path.GetPolygon (*this), rFactoryParms, rState, rCanvas, flags & 0x8000, brushIndexOrColor); break; } case EmfPlusRecordTypeDrawLines: { sal_uInt32 points; rMF.ReadUInt32( points ); SAL_INFO("cppcanvas.emf", "EMF+ DrawLines in slot: " << (flags & 0xff) << " points: " << points); EMFPPath path (points, true); path.Read (rMF, flags, *this); // 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)), rFactoryParms, rState, rCanvas, flags); break; } case EmfPlusRecordTypeDrawPath: { sal_uInt32 penIndex; rMF.ReadUInt32( penIndex ); SAL_INFO("cppcanvas.emf", "EMF+ DrawPath"); SAL_INFO("cppcanvas.emf", "EMF+\tpen: " << penIndex); EMFPPath* path = static_cast( aObjects [flags & 0xff] ); SAL_WARN_IF( !path, "cppcanvas.emf", "EmfPlusRecordTypeDrawPath missing path" ); EMFPPlusDrawPolygon (path->GetPolygon (*this), rFactoryParms, rState, rCanvas, penIndex); break; } case EmfPlusRecordTypeDrawBeziers: { sal_uInt32 aCount; float x1, y1, x2, y2, x3, y3, x4, y4; ::basegfx::B2DPoint aStartPoint, aControlPointA, aControlPointB, aEndPoint; ::basegfx::B2DPolygon aPolygon; rMF.ReadUInt32( aCount ); SAL_INFO("cppcanvas.emf", "EMF+ DrawBeziers slot: " << (flags & 0xff) << "Number of points: " << aCount); SAL_WARN_IF( ( aCount - 1 ) % 3 != 0, "cppcanvas.emf", "EMF+\t Bezier Draw not support number of points other than 4, 7, 10, 13, 16..."); if( aCount < 4 ) { SAL_WARN("cppcanvas.emf", "EMF+\t Bezier Draw does not support less than 4 points. Number of points: " << aCount); break; } ReadPoint (rMF, x1, y1, flags); // We need to add first starting point aStartPoint = Map (x1, y1); aPolygon.append( aStartPoint ); for( sal_uInt32 i = 4; i <= aCount; i += 3 ) { ReadPoint (rMF, x2, y2, flags); ReadPoint (rMF, x3, y3, flags); ReadPoint (rMF, x4, y4, flags); SAL_INFO("cppcanvas.emf", "EMF+\t Bezier points: " << x1 << "," << y1 << " " << x2 << "," << y2 << " " << x3 << "," << y3 << " " << x4 << "," << y4); aStartPoint = Map (x1, y1); aControlPointA = Map (x2, y2); aControlPointB = Map (x3, y3); aEndPoint = Map (x4, y4); ::basegfx::B2DCubicBezier cubicBezier( aStartPoint, aControlPointA, aControlPointB, aEndPoint ); cubicBezier.adaptiveSubdivideByDistance( aPolygon, 10.0 ); EMFPPlusDrawPolygon( ::basegfx::B2DPolyPolygon( aPolygon ), rFactoryParms, rState, rCanvas, flags & 0xff ); // The ending coordinate of one Bezier curve is the starting coordinate of the next. x1 = x4; y1 = y4; } break; } case EmfPlusRecordTypeDrawImage: case EmfPlusRecordTypeDrawImagePoints: { sal_uInt32 attrIndex; sal_Int32 sourceUnit; rMF.ReadUInt32( attrIndex ).ReadInt32( sourceUnit ); SAL_INFO("cppcanvas.emf", "EMF+ " << (type == EmfPlusRecordTypeDrawImagePoints ? "DrawImagePoints" : "DrawImage") << "attributes index: " << attrIndex << "source unit: " << sourceUnit); SAL_INFO("cppcanvas.emf", "EMF+\tTODO: use image attributes"); if (sourceUnit == 2 && aObjects [flags & 0xff]) { // we handle only GraphicsUnit.Pixel now EMFPImage& image = *static_cast( aObjects [flags & 0xff]); float sx, sy, sw, sh; sal_Int32 aCount; ReadRectangle (rMF, sx, sy, sw, sh); ::tools::Rectangle aSource(Point(sx, sy), Size(sw, sh)); SAL_INFO("cppcanvas.emf", "EMF+ " << (type == EmfPlusRecordTypeDrawImagePoints ? "DrawImagePoints" : "DrawImage") << " source rectangle: " << sx << "," << sy << " " << sw << "x" << sh); ::basegfx::B2DPoint aDstPoint; ::basegfx::B2DSize aDstSize; bool bValid = false; if (type == EmfPlusRecordTypeDrawImagePoints) { rMF.ReadInt32( aCount ); if( aCount == 3) { // TODO: now that we now that this value is count we should support it better float x1, y1, x2, y2, x3, y3; ReadPoint (rMF, x1, y1, flags); ReadPoint (rMF, x2, y2, flags); ReadPoint (rMF, x3, y3, flags); SAL_INFO("cppcanvas.emf", "EMF+ destination points: " << x1 << "," << y1 << " " << x2 << "," << y2 << " " << x3 << "," << y3); SAL_INFO("cppcanvas.emf", "EMF+ destination rectangle: " << x1 << "," << y1 << " " << x2 - x1 << "x" << y3 - y1); aDstPoint = Map (x1, y1); aDstSize = MapSize(x2 - x1, y3 - y1); bValid = true; } } else if (type == EmfPlusRecordTypeDrawImage) { float dx, dy, dw, dh; ReadRectangle (rMF, dx, dy, dw, dh, bool(flags & 0x4000)); SAL_INFO("cppcanvas.emf", "EMF+ destination rectangle: " << dx << "," << dy << " " << dw << "x" << dh); aDstPoint = Map (dx, dy); aDstSize = MapSize(dw, dh); bValid = true; } if (bValid) { BitmapEx aBmp( image.graphic.GetBitmapEx () ); aBmp.Crop( aSource ); Size aSize( aBmp.GetSizePixel() ); SAL_INFO("cppcanvas.emf", "EMF+ bitmap size: " << aSize.Width() << "x" << aSize.Height()); if( aSize.Width() > 0 && aSize.Height() > 0 ) { ActionSharedPtr pBmpAction ( internal::BitmapActionFactory::createBitmapAction ( aBmp, rState.mapModeTransform * aDstPoint, rState.mapModeTransform * aDstSize, rCanvas, rState)); if( pBmpAction ) { maActions.push_back( MtfAction( pBmpAction, rFactoryParms.mrCurrActionIndex ) ); rFactoryParms.mrCurrActionIndex += pBmpAction->getActionCount()-1; } } else { SAL_INFO("cppcanvas.emf", "EMF+ warning: empty bitmap"); } } else { SAL_INFO("cppcanvas.emf", "EMF+ DrawImage(Points) TODO (fixme)"); } } else { SAL_INFO("cppcanvas.emf", "EMF+ DrawImage(Points) TODO (fixme) - possibly unsupported source units for crop rectangle"); } break; } case EmfPlusRecordTypeDrawString: { SAL_INFO("cppcanvas.emf", "EMF+ DrawString"); sal_uInt32 brushId; sal_uInt32 formatId; sal_uInt32 stringLength; rMF.ReadUInt32( brushId ).ReadUInt32( formatId ).ReadUInt32( stringLength ); SAL_INFO("cppcanvas.emf", "EMF+ DrawString brushId: " << brushId << " formatId: " << formatId << " length: " << stringLength); if (flags & 0x8000) { float lx, ly, lw, lh; rMF.ReadFloat( lx ).ReadFloat( ly ).ReadFloat( lw ).ReadFloat( lh ); SAL_INFO("cppcanvas.emf", "EMF+ DrawString layoutRect: " << lx << "," << ly << " - " << lw << "x" << lh); OUString text = read_uInt16s_ToOUString(rMF, stringLength); EMFPStringFormat *stringFormat = static_cast< EMFPStringFormat* >( aObjects[ formatId & 0xff ] ); css::rendering::FontRequest aFontRequest; if (stringFormat) { LanguageTag aLanguageTag( static_cast< LanguageType >( stringFormat->language ) ); aFontRequest.Locale = aLanguageTag.getLocale( false ); SAL_INFO("cppcanvas.emf", "EMF+\t\t Font locale, Country:" << aLanguageTag.getCountry() <<" Language:" << aLanguageTag.getLanguage() ); } SAL_INFO("cppcanvas.emf", "EMF+\t\t TODO Use all string formatting attributes during drawing"); double cellSize = setFont (aFontRequest, flags & 0xff, rFactoryParms, rState); rState.textColor = COLOR( brushId ); ::basegfx::B2DPoint point( Map( lx + 0.15*cellSize, ly + cellSize ) ); ActionSharedPtr pTextAction( TextActionFactory::createTextAction( // position is just rough guess for now // we should calculate it exactly from layoutRect or font vcl::unotools::pointFromB2DPoint ( point ), ::Size(), ::Color(), ::Size(), ::Color(), text, 0, stringLength, nullptr, rFactoryParms.mrVDev, rFactoryParms.mrCanvas, rState, rFactoryParms.mrParms, false ) ); if( pTextAction ) { SAL_INFO("cppcanvas.emf", "EMF+\t\tadd text action"); maActions.push_back( MtfAction( pTextAction, rFactoryParms.mrCurrActionIndex ) ); rFactoryParms.mrCurrActionIndex += pTextAction->getActionCount()-1; } } else { SAL_INFO("cppcanvas.emf", "EMF+ DrawString TODO - drawing with brush not yet supported"); } } break; case EmfPlusRecordTypeSetPageTransform: { rMF.ReadFloat( fPageScale ); SAL_INFO("cppcanvas.emf", "EMF+ SetPageTransform"); SAL_INFO("cppcanvas.emf", "EMF+\tscale: " << fPageScale << " unit: " << flags); if (flags != UnitTypePixel) SAL_WARN("cppcanvas.emf", "EMF+\t TODO Only UnitTypePixel is supported. "); else { nMmX *= fPageScale; nMmY *= fPageScale; } } break; case EmfPlusRecordTypeSetRenderingOrigin: rMF.ReadInt32( nOriginX ).ReadInt32( nOriginY ); SAL_INFO("cppcanvas.emf", "EMF+ SetRenderingOrigin"); SAL_INFO("cppcanvas.emf", "EMF+\torigin [x,y]: " << nOriginX << "," << nOriginY); break; case EmfPlusRecordTypeSetTextRenderingHint: SAL_INFO("cppcanvas.emf", "EMF+ SetTextRenderingHint"); SAL_INFO("cppcanvas.emf", "EMF+\tTODO"); break; case EmfPlusRecordTypeSetAntiAliasMode: SAL_INFO("cppcanvas.emf", "EMF+ SetAntiAliasMode"); SAL_INFO("cppcanvas.emf", "EMF+\tTODO"); break; case EmfPlusRecordTypeSetInterpolationMode: SAL_INFO("cppcanvas.emf", "EMF+ InterpolationMode"); SAL_INFO("cppcanvas.emf", "EMF+\tTODO"); break; case EmfPlusRecordTypeSetPixelOffsetMode: SAL_INFO("cppcanvas.emf", "EMF+ SetPixelOffsetMode"); SAL_INFO("cppcanvas.emf", "EMF+\tTODO"); break; case EmfPlusRecordTypeSetCompositingQuality: SAL_INFO("cppcanvas.emf", "EMF+ SetCompositingQuality"); SAL_INFO("cppcanvas.emf", "EMF+\tTODO"); break; case EmfPlusRecordTypeSave: { sal_uInt32 stackIndex; rMF.ReadUInt32( stackIndex ); SAL_INFO("cppcanvas.emf", "EMF+ Save stack index: " << stackIndex); GraphicStatePush( mGSStack, stackIndex, rState ); break; } case EmfPlusRecordTypeRestore: { sal_uInt32 stackIndex; rMF.ReadUInt32( stackIndex ); SAL_INFO("cppcanvas.emf", "EMF+ Restore stack index: " << stackIndex); GraphicStatePop( mGSStack, stackIndex, rState ); break; } case EmfPlusRecordTypeBeginContainerNoParams: { sal_uInt32 stackIndex; rMF.ReadUInt32( stackIndex ); SAL_INFO("cppcanvas.emf", "EMF+ Begin Container No Params stack index: " << stackIndex); GraphicStatePush( mGSContainerStack, stackIndex, rState ); } break; case EmfPlusRecordTypeEndContainer: { sal_uInt32 stackIndex; rMF.ReadUInt32( stackIndex ); SAL_INFO("cppcanvas.emf", "EMF+ End Container stack index: " << stackIndex); GraphicStatePop( mGSContainerStack, stackIndex, rState ); } break; case EmfPlusRecordTypeSetWorldTransform: { SAL_INFO("cppcanvas.emf", "EMF+ SetWorldTransform"); XForm transform; ReadXForm( rMF, transform ); aWorldTransform.Set (transform); SAL_INFO("cppcanvas.emf", "EMF+\tm11: " << aWorldTransform.eM11 << "\tm12: " << aWorldTransform.eM12 << "\tm21: " << aWorldTransform.eM21 << "\tm22: " << aWorldTransform.eM22 << "\tdx: " << aWorldTransform.eDx << "\tdy: " << aWorldTransform.eDy); break; } case EmfPlusRecordTypeResetWorldTransform: SAL_INFO("cppcanvas.emf", "EMF+ ResetWorldTransform"); aWorldTransform.SetIdentity (); break; case EmfPlusRecordTypeMultiplyWorldTransform: { SAL_INFO("cppcanvas.emf", "EMF+ MultiplyWorldTransform"); XForm transform; ReadXForm( rMF, transform ); SAL_INFO("cppcanvas.emf", "EMF+\tmatrix m11: " << transform.eM11 << "m12: " << transform.eM12 << "EMF+\tm21: " << transform.eM21 << "m22: " << transform.eM22 << "EMF+\tdx: " << transform.eDx << "dy: " << transform.eDy); if (flags & 0x2000) // post multiply aWorldTransform.Multiply (transform); else { // pre multiply transform.Multiply (aWorldTransform); aWorldTransform.Set (transform); } SAL_INFO("cppcanvas.emf", "EMF+\tm11: " << aWorldTransform.eM11 << "m12: " << aWorldTransform.eM12 << "EMF+\tm21: " << aWorldTransform.eM21 << "m22: " << aWorldTransform.eM22 << "EMF+\tdx: " << aWorldTransform.eDx << "dy: " << aWorldTransform.eDy); break; } case EmfPlusRecordTypeTranslateWorldTransform: { SAL_INFO("cppcanvas.emf", "EMF+ TranslateWorldTransform"); XForm transform = XForm(); rMF.ReadFloat( transform.eDx ).ReadFloat( transform.eDy ); SAL_INFO("cppcanvas.emf", "EMF+\t m11: " << transform.eM11 << ", m12: " << transform.eM12 << "EMF+\t m21: " << transform.eM21 << ", m22: " << transform.eM22 << "EMF+\t dx: " << transform.eDx << ", dy: " << transform.eDy); if (flags & 0x2000) // post multiply aWorldTransform.Multiply (transform); else { // pre multiply transform.Multiply (aWorldTransform); aWorldTransform.Set (transform); } SAL_INFO("cppcanvas.emf", "EMF+\t m11: " << aWorldTransform.eM11 << ", m12: " << aWorldTransform.eM12 << "EMF+\t m21: " << aWorldTransform.eM21 << ", m22: " << aWorldTransform.eM22 << "EMF+\t dx: " << aWorldTransform.eDx << ", dy: " << aWorldTransform.eDy); break; } case EmfPlusRecordTypeScaleWorldTransform: { XForm transform = XForm(); rMF.ReadFloat( transform.eM11 ).ReadFloat( transform.eM22 ); SAL_INFO("cppcanvas.emf", "EMF+ ScaleWorldTransform Sx: " << transform.eM11 << " Sy: " << transform.eM22); SAL_INFO("cppcanvas.emf", "EMF+\t m11: " << aWorldTransform.eM11 << ", m12: " << aWorldTransform.eM12 << "EMF+\t m21: " << aWorldTransform.eM21 << ", m22: " << aWorldTransform.eM22 << "EMF+\t dx: " << aWorldTransform.eDx << ", dy: " << aWorldTransform.eDy); if (flags & 0x2000) // post multiply aWorldTransform.Multiply (transform); else { // pre multiply transform.Multiply (aWorldTransform); aWorldTransform.Set (transform); } SAL_INFO("cppcanvas.emf", "EMF+\t m11: " << aWorldTransform.eM11 << ", m12: " << aWorldTransform.eM12 << "EMF+\t m21: " << aWorldTransform.eM21 << ", m22: " << aWorldTransform.eM22 << "EMF+\t dx: " << aWorldTransform.eDx << ", dy: " << aWorldTransform.eDy); break; } case EmfPlusRecordTypeSetClipRect: { int combineMode = (flags >> 8) & 0xf; SAL_INFO("cppcanvas.emf", "EMF+ SetClipRect combine mode: " << combineMode); #if OSL_DEBUG_LEVEL > 1 if (combineMode > 1) { SAL_INFO ("cppcanvas.emf", "EMF+ TODO combine mode > 1"); } #endif float dx, dy, dw, dh; ReadRectangle (rMF, dx, dy, dw, dh); SAL_INFO("cppcanvas.emf", "EMF+ RectData: " << dx << "," << dy << " " << dw << "x" << dh); B2DPoint mappedPoint (Map (dx, dy)); B2DSize mappedSize( MapSize (dw, dh)); ::basegfx::B2DPolyPolygon polyPolygon( ::basegfx::B2DPolygon( ::basegfx::tools::createPolygonFromRect( ::basegfx::B2DRectangle( mappedPoint.getX(), mappedPoint.getY(), mappedPoint.getX() + mappedSize.getX(), mappedPoint.getY() + mappedSize.getY() ) ) ) ); polyPolygon.transform(rState.mapModeTransform); updateClipping (polyPolygon, rFactoryParms, combineMode == 1); break; } case EmfPlusRecordTypeSetClipPath: { int combineMode = (flags >> 8) & 0xf; SAL_INFO("cppcanvas.emf", "EMF+ SetClipPath combine mode: " << combineMode); SAL_INFO("cppcanvas.emf", "EMF+\tpath in slot: " << (flags & 0xff)); EMFPPath& path = *static_cast( aObjects [flags & 0xff] ); ::basegfx::B2DPolyPolygon& clipPoly (path.GetPolygon (*this)); clipPoly.transform (rState.mapModeTransform); switch (combineMode) { case EmfPlusCombineModeReplace: case EmfPlusCombineModeIntersect: case EmfPlusCombineModeUnion: // Is this, EmfPlusCombineModeXOR and EmfPlusCombineModeComplement correct? case EmfPlusCombineModeXOR: case EmfPlusCombineModeComplement: updateClipping (clipPoly, rFactoryParms, combineMode == 1); break; case EmfPlusCombineModeExclude: // Not doing anything is better then including exactly what we wanted to exclude. break; } break; } case EmfPlusRecordTypeSetClipRegion: { int combineMode = (flags >> 8) & 0xf; SAL_INFO("cppcanvas.emf", "EMF+ SetClipRegion"); SAL_INFO("cppcanvas.emf", "EMF+\tregion in slot: " << (flags & 0xff) << " combine mode: " << combineMode); EMFPRegion *region = static_cast(aObjects [flags & 0xff]); // reset clip if (region && region->parts == 0 && region->initialState == EmfPlusRegionInitialStateInfinite) { updateClipping (::basegfx::B2DPolyPolygon (), rFactoryParms, combineMode == 1); } else { SAL_INFO("cppcanvas.emf", "EMF+\tTODO"); } break; } case EmfPlusRecordTypeDrawDriverString: { SAL_INFO("cppcanvas.emf", "EMF+ DrawDriverString, flags: 0x" << std::hex << flags << std::dec); sal_uInt32 brushIndexOrColor; sal_uInt32 optionFlags; sal_uInt32 hasMatrix; sal_uInt32 glyphsCount; rMF.ReadUInt32( brushIndexOrColor ).ReadUInt32( optionFlags ).ReadUInt32( hasMatrix ).ReadUInt32( glyphsCount ); SAL_INFO("cppcanvas.emf", "EMF+\t: " << ((flags & 0x8000) ? "color" : "brush index") << " 0x" << std::hex << brushIndexOrColor << std::dec); SAL_INFO("cppcanvas.emf", "EMF+\toption flags: 0x" << std::hex << optionFlags << std::dec); SAL_INFO("cppcanvas.emf", "EMF+\thas matrix: " << hasMatrix); SAL_INFO("cppcanvas.emf", "EMF+\tglyphs: " << glyphsCount); if( ( optionFlags & 1 ) && glyphsCount > 0 ) { std::unique_ptr charsPosX( new float[glyphsCount] ); std::unique_ptr charsPosY( new float[glyphsCount] ); OUString text = read_uInt16s_ToOUString(rMF, glyphsCount); for( sal_uInt32 i=0; igetActionCount()-1; } } else { SAL_INFO("cppcanvas.emf", "EMF+\tTODO: fonts (non-unicode glyphs chars)"); } break; } default: SAL_INFO("cppcanvas.emf", "EMF+ unhandled record type: 0x" << std::hex << type << std::dec); SAL_INFO("cppcanvas.emf", "EMF+\tTODO"); } } rMF.Seek (next); if (size <= length) { length -= size; } else { SAL_WARN("cppcanvas.emf", "ImplRenderer::processEMFPlus: " "size " << size << " > length " << length); length = 0; } } } } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */