office-gobmx/cppcanvas/source/mtfrenderer/emfplus.cxx
Mike Kaganski 8c48d69f06 Use more basegfx deg<->rad functions, instead of direct formulas
Also make the functions constexpr.

Due to slight changes in floating-point arithmetics (90.0 instead of
180.0, M_PI2 instead of M_PI resp.), results might differ in last
digits (usually 17th decimal digit). This has lead to need to tweak
char2dump's PieChartTest unit test.

Change-Id: I20323dd7dab27e4deb408ea4181e390cc05e7cd3
Reviewed-on: https://gerrit.libreoffice.org/58583
Tested-by: Jenkins
Reviewed-by: Tamás Zolnai <tamas.zolnai@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
2018-08-04 20:51:27 +02:00

1666 lines
82 KiB
C++

/* -*- 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 <memory>
#include <tools/stream.hxx>
#include <vcl/metaact.hxx>
#include <vcl/graphicfilter.hxx>
#include <basegfx/curve/b2dcubicbezier.hxx>
#include <basegfx/utils/canvastools.hxx>
#include <basegfx/utils/gradienttools.hxx>
#include <basegfx/utils/tools.hxx>
#include <basegfx/numeric/ftools.hxx>
#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/vector/b2dsize.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/range/b2drectangle.hxx>
#include <basegfx/polygon/b2dlinegeometry.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <vcl/canvastools.hxx>
#include <rtl/ustring.hxx>
#include <sal/log.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <com/sun/star/rendering/PanoseWeight.hpp>
#include <com/sun/star/rendering/TexturingMode.hpp>
#include <com/sun/star/rendering/XCanvas.hpp>
#include <comphelper/propertysequence.hxx>
#include "bitmapaction.hxx"
#include <implrenderer.hxx>
#include <outdevstate.hxx>
#include "polypolyaction.hxx"
#include "textaction.hxx"
#include "emfpbrush.hxx"
#include "emfpcustomlinecap.hxx"
#include "emfpfont.hxx"
#include "emfpimage.hxx"
#include "emfppath.hxx"
#include "emfppen.hxx"
#include "emfpregion.hxx"
#include "emfpstringformat.hxx"
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 0x4029
#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 & 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("cppcanvas.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 ImplRenderer::MapToDevice (double& x, double& y) const
{
// TODO: other units
x = 100*nMmX*x/nPixX;
y = 100*nMmY*y/nPixY;
}
::basegfx::B2DPoint ImplRenderer::Map (double ix, double iy) const
{
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) const
{
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 const & 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 );
std::shared_ptr<Action> 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 = internal::PolyPolyActionFactory::createPolyPolyAction( localPolygon, rParms.mrCanvas, rState );
} else {
rState.isFillColorSet = true;
// extract UseBrush
EMFPBrush* brush = static_cast<EMFPBrush*>( aObjects [brushIndexOrColor & 0xff].get() );
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 == 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;
}
rState.isFillColorSet = true;
rState.isLineColorSet = false;
::Color fillColor;
if (isHatchBlend)
{
fillColor = brush->solidColor;
fillColor.Merge(brush->secondColor, static_cast<sal_uInt8>(255 * blendFactor));
}
else
{
fillColor = brush->secondColor;
}
rState.fillColor = vcl::unotools::colorToDoubleSequence(fillColor, rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace());
pPolyAction = internal::PolyPolyActionFactory::createPolyPolyAction( localPolygon, rParms.mrCanvas, rState );
}
else if (brush->type == BrushTypeTextureFill)
{
SAL_WARN("cppcanvas.emf", "EMF+\tTODO: implement BrushTypeTextureFill brush");
}
else if (brush->type == BrushTypePathGradient || brush->type == BrushTypeLinearGradient)
{
if (brush->type == BrushTypePathGradient && !(brush->additionalFlags & 0x1))
{
SAL_WARN("cppcanvas.emf", "EMF+\t TODO Verify proper displaying of BrushTypePathGradient with flags: " << std::hex << brush->additionalFlags << std::dec);
}
::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);
// TODO Verify on example image, why there is shift (-0.5, -0.5)
if (brush->type == BrushTypePathGradient && (brush->additionalFlags & 0x1))
{
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);
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 == BrushTypeLinearGradient) {
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 == BrushTypeLinearGradient) ? 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 == BrushTypeLinearGradient) {
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 == BrushTypeLinearGradient) {
aGradientService = "LinearGradient";
aGradInfo = basegfx::utils::createLinearODFGradientInfo(
aBoundsRectangle,
aStops.getLength(),
0,
0);
} else {
aGradientService = "EllipticalGradient";
aGradInfo = basegfx::utils::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<uno::Any> args(comphelper::InitAnyPropertySequence(
{
{"Colors", uno::Any(aColors)},
{"Stops", uno::Any(aStops)},
{"AspectRatio", uno::Any(static_cast<sal_Int32>(1))},
}));
aTexture.Gradient.set(
xFactory->createInstanceWithArguments( aGradientService,
args ),
uno::UNO_QUERY);
}
::basegfx::unotools::affineMatrixFromHomMatrix( aTexture.AffineTransform,
aTextureTransformation );
if( aTexture.Gradient.is() )
pPolyAction = internal::PolyPolyActionFactory::createPolyPolyAction( localPolygon,
rParms.mrCanvas,
rState,
aTexture );
}
}
if( pPolyAction )
{
SAL_INFO("cppcanvas.emf", "EMF+\t\tadd poly action");
maActions.emplace_back(
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::utils::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;
std::shared_ptr<Action> pAction2(internal::PolyPolyActionFactory::createPolyPolyAction(aArrow, rParms.mrCanvas, rState));
if (pAction2)
{
maActions.emplace_back(pAction2, rParms.mrCurrActionIndex);
rParms.mrCurrActionIndex += pAction2->getActionCount()-1;
}
rState.isFillColorSet = bWasFillColorSet;
}
else
{
std::shared_ptr<Action> pAction(internal::PolyPolyActionFactory::createPolyPolyAction(aArrow, rParms.mrCanvas, rState, rAttributes));
if (pAction)
{
maActions.emplace_back(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<EMFPPen*>( aObjects [penIndex & 0xff].get() );
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::utils::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::utils::getSnippetAbsolute(aPolygon, fStart, fPolyLength - fEnd, fPolyLength);
}
aFinalPolyPolygon.append(aPolygon);
}
}
// finally render the polygon
std::shared_ptr<Action> pPolyAction(internal::PolyPolyActionFactory::createPolyPolyAction(aFinalPolyPolygon, rParms.mrCanvas, rState, aPolygonAttributes));
if( pPolyAction )
{
maActions.emplace_back(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) {
aObjects [index].reset();
}
switch (flags & 0x7f00) {
case EmfPlusObjectTypeBrush:
{
std::unique_ptr<EMFPBrush> brush(new EMFPBrush());
brush->Read (rObjectStream, *this);
aObjects [index] = std::move(brush);
break;
}
case EmfPlusObjectTypePen:
{
std::unique_ptr<EMFPPen> pen(new EMFPPen ());
pen->Read (rObjectStream, *this);
aObjects [index] = std::move(pen);
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);
std::unique_ptr<EMFPPath> path(new EMFPPath (points));
path->Read (rObjectStream, pathFlags, *this);
aObjects [index] = std::move(path);
break;
}
case EmfPlusObjectTypeRegion: {
std::unique_ptr<EMFPRegion> region(new EMFPRegion ());
region->Read (rObjectStream);
aObjects [index] = std::move(region);
break;
}
case EmfPlusObjectTypeImage:
{
std::unique_ptr<EMFPImage> image(new EMFPImage);
image->type = 0;
image->width = 0;
image->height = 0;
image->stride = 0;
image->pixelFormat = 0;
image->Read (rObjectStream, dataSize, bUseWholeStream);
aObjects [index] = std::move(image);
break;
}
case EmfPlusObjectTypeFont:
{
std::unique_ptr<EMFPFont> font(new EMFPFont);
font->emSize = 0;
font->sizeUnit = 0;
font->fontFlags = 0;
font->Read (rObjectStream);
aObjects [index] = std::move(font);
break;
}
case EmfPlusObjectTypeStringFormat:
{
std::unique_ptr<EMFPStringFormat> stringFormat(new EMFPStringFormat());
stringFormat->Read (rObjectStream);
aObjects [index] = std::move(stringFormat);
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 ].get() );
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 const & 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 const * pAct, const ActionFactoryParameters& rFactoryParms,
OutDevState& rState, const CanvasSharedPtr& rCanvas )
{
sal_uInt32 length = pAct->GetDataSize ();
SvMemoryStream rMF (const_cast<sal_uInt8 *>(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<const char *>(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 = basegfx::deg2rad(startAngle);
sweepAngle = basegfx::deg2rad(sweepAngle);
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<float>(M_PI*2));
if (startAngle < 0)
startAngle += static_cast<float>(M_PI*2);
endAngle = fmodf(endAngle, static_cast<float>(M_PI*2));
if (endAngle < 0)
endAngle += static_cast<float>(M_PI*2);
if (sweepAngle < 0)
std::swap (endAngle, startAngle);
SAL_INFO("cppcanvas.emf", "EMF+\t adjusted angles: start " <<
basegfx::rad2deg(startAngle) << ", end: " << basegfx::rad2deg(endAngle) <<
" startAngle: " << startAngle << " sweepAngle: " << sweepAngle);
B2DPolygon polygon = basegfx::utils::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<EMFPPath*>( aObjects [index].get())->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::utils::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<EMFPPath*>( aObjects [flags & 0xff].get() );
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");
// For DrawImage and DrawImagePoints, source unit of measurement type must be 1 pixel
if (sourceUnit == UnitTypePixel && aObjects [flags & 0xff]) {
EMFPImage& image = *static_cast<EMFPImage *>( aObjects [flags & 0xff].get());
float sx, sy, sw, sh;
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;
if (type == EmfPlusRecordTypeDrawImagePoints) {
sal_Int32 aCount;
rMF.ReadInt32( aCount );
// Number of points used by DrawImagePoints. Exactly 3 points must be specified.
if( aCount == 3 ) {
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);
} else {
SAL_WARN("cppcanvas.emf", "EMF+ DrawImagePoints Wrong EMF+ file. Expected 3 points, received: "<< aCount);
break;
}
} 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);
}
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 ) {
std::shared_ptr<Action> pBmpAction (
internal::BitmapActionFactory::createBitmapAction (
aBmp,
rState.mapModeTransform * aDstPoint,
rState.mapModeTransform * aDstSize,
rCanvas,
rState));
if( pBmpAction ) {
maActions.emplace_back( pBmpAction,
rFactoryParms.mrCurrActionIndex );
rFactoryParms.mrCurrActionIndex += pBmpAction->getActionCount()-1;
}
} else {
SAL_WARN("cppcanvas.emf", "EMF+ warning: empty bitmap");
}
} else {
SAL_WARN("cppcanvas.emf", "EMF+ DrawImage(Points) Wrong EMF+ file. Only Unit Type Pixel is support by EMF+ standard in DrawImage(Points)");
}
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 ].get() );
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 ) );
std::shared_ptr<Action> 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(),
::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.emplace_back(
pTextAction,
rFactoryParms.mrCurrActionIndex );
rFactoryParms.mrCurrActionIndex += pTextAction->getActionCount()-1;
}
} else {
SAL_WARN("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::utils::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<EMFPPath*>( aObjects [flags & 0xff].get() );
::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<EMFPRegion*>(aObjects [flags & 0xff].get());
// 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<float[]> charsPosX( new float[glyphsCount] );
std::unique_ptr<float[]> charsPosY( new float[glyphsCount] );
OUString text = read_uInt16s_ToOUString(rMF, glyphsCount);
for( sal_uInt32 i=0; i<glyphsCount; i++) {
rMF.ReadFloat( charsPosX[i] ).ReadFloat( charsPosY[i] );
SAL_INFO("cppcanvas.emf", "EMF+\tglyphPosition[" << i << "]: " << charsPosX[i] << "," << charsPosY[i]);
}
XForm transform;
if( hasMatrix ) {
ReadXForm( rMF, transform );
SAL_INFO("cppcanvas.emf", "EMF+\tmatrix: " << transform.eM11 << ", " << transform.eM12 << ", " << transform.eM21 << ", " << transform.eM22 << ", " << transform.eDx << ", " << transform.eDy);
}
rendering::FontRequest aFontRequest;
// add the text action
setFont (aFontRequest, flags & 0xff, rFactoryParms, rState);
if( flags & 0x8000 )
rState.textColor = COLOR( brushIndexOrColor );
::basegfx::B2DPoint point( Map( charsPosX[0], charsPosY[0] ) );
std::shared_ptr<Action> pTextAction(
TextActionFactory::createTextAction(
vcl::unotools::pointFromB2DPoint ( point ),
::Size(),
::Color(),
::Size(),
::Color(),
::Color(),
text,
0,
glyphsCount,
nullptr,
rFactoryParms.mrVDev,
rFactoryParms.mrCanvas,
rState,
rFactoryParms.mrParms,
false ) );
if( pTextAction )
{
SAL_INFO("cppcanvas.emf", "EMF+\t\tadd text action");
maActions.emplace_back(
pTextAction,
rFactoryParms.mrCurrActionIndex );
rFactoryParms.mrCurrActionIndex += pTextAction->getActionCount()-1;
}
} else {
SAL_WARN("cppcanvas.emf", "EMF+\tTODO: fonts (non-unicode glyphs chars)");
}
break;
}
default:
SAL_WARN("cppcanvas.emf", "EMF+ TODO unhandled record type: 0x" << std::hex << type << std::dec);
}
}
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: */