3005 lines
131 KiB
C++
3005 lines
131 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 <wmfemfhelper.hxx>
|
|
#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
|
|
#include <vcl/lineinfo.hxx>
|
|
#include <vcl/metaact.hxx>
|
|
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
|
|
#include <basegfx/matrix/b2dhommatrixtools.hxx>
|
|
#include <basegfx/utils/gradienttools.hxx>
|
|
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
|
|
#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
|
|
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
|
|
#include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx>
|
|
#include <basegfx/polygon/b2dpolygontools.hxx>
|
|
#include <drawinglayer/primitive2d/discretebitmapprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
|
|
#include <vcl/BitmapPalette.hxx>
|
|
#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
|
|
#include <basegfx/polygon/b2dpolygonclipper.hxx>
|
|
#include <drawinglayer/primitive2d/invertprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
|
|
#include <primitive2d/wallpaperprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
|
|
#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
|
|
#include <primitive2d/textlineprimitive2d.hxx>
|
|
#include <primitive2d/textstrikeoutprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/epsprimitive2d.hxx>
|
|
#include <sal/log.hxx>
|
|
#include <tools/fract.hxx>
|
|
#include <tools/stream.hxx>
|
|
#include <tools/UnitConversion.hxx>
|
|
#include <vcl/canvastools.hxx>
|
|
#include <vcl/gradient.hxx>
|
|
#include <vcl/hatch.hxx>
|
|
#include <vcl/outdev.hxx>
|
|
#include <i18nlangtag/languagetag.hxx>
|
|
#include <emfplushelper.hxx>
|
|
#include <numeric>
|
|
#include <toolkit/helper/vclunohelper.hxx>
|
|
|
|
namespace drawinglayer::primitive2d
|
|
{
|
|
namespace {
|
|
|
|
/** NonOverlappingFillGradientPrimitive2D class
|
|
|
|
This is a special version of the FillGradientPrimitive2D which decomposes
|
|
to a non-overlapping geometry version of the gradient. This needs to be
|
|
used to support the old XOR paint-'trick'.
|
|
|
|
It does not need an own identifier since a renderer who wants to interpret
|
|
it itself may do so. It just overrides the decomposition of the C++
|
|
implementation class to do an alternative decomposition.
|
|
*/
|
|
class NonOverlappingFillGradientPrimitive2D : public FillGradientPrimitive2D
|
|
{
|
|
protected:
|
|
/// local decomposition.
|
|
virtual Primitive2DReference create2DDecomposition(
|
|
const geometry::ViewInformation2D& rViewInformation) const override;
|
|
|
|
public:
|
|
/// constructor
|
|
NonOverlappingFillGradientPrimitive2D(
|
|
const basegfx::B2DRange& rObjectRange,
|
|
const attribute::FillGradientAttribute& rFillGradient)
|
|
: FillGradientPrimitive2D(rObjectRange, rFillGradient)
|
|
{
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
Primitive2DReference NonOverlappingFillGradientPrimitive2D::create2DDecomposition(
|
|
const geometry::ViewInformation2D& /*rViewInformation*/) const
|
|
{
|
|
if (!getFillGradient().isDefault())
|
|
{
|
|
return createFill(false);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
} // end of namespace
|
|
|
|
namespace wmfemfhelper
|
|
{
|
|
/** helper class for graphic context
|
|
|
|
This class allows to hold a complete representation of classic
|
|
VCL OutputDevice state. This data is needed for correct
|
|
interpretation of the MetaFile action flow.
|
|
*/
|
|
PropertyHolder::PropertyHolder()
|
|
: maMapUnit(MapUnit::Map100thMM),
|
|
maTextColor(sal_uInt32(COL_BLACK)),
|
|
maRasterOp(RasterOp::OverPaint),
|
|
mnLayoutMode(vcl::text::ComplexTextLayoutFlags::Default),
|
|
maLanguageType(0),
|
|
mnPushFlags(vcl::PushFlags::NONE),
|
|
mbLineColor(false),
|
|
mbFillColor(false),
|
|
mbTextColor(true),
|
|
mbTextFillColor(false),
|
|
mbTextLineColor(false),
|
|
mbOverlineColor(false),
|
|
mbClipPolyPolygonActive(false)
|
|
{
|
|
}
|
|
}
|
|
|
|
namespace wmfemfhelper
|
|
{
|
|
/** stack for properties
|
|
|
|
This class builds a stack based on the PropertyHolder
|
|
class. It encapsulates the pointer/new/delete usage to
|
|
make it safe and implements the push/pop as needed by a
|
|
VCL Metafile interpreter. The critical part here are the
|
|
flag values VCL OutputDevice uses here; not all stuff is
|
|
pushed and thus needs to be copied at pop.
|
|
*/
|
|
PropertyHolders::PropertyHolders()
|
|
{
|
|
maPropertyHolders.push_back(new PropertyHolder());
|
|
}
|
|
|
|
void PropertyHolders::PushDefault()
|
|
{
|
|
PropertyHolder* pNew = new PropertyHolder();
|
|
maPropertyHolders.push_back(pNew);
|
|
}
|
|
|
|
void PropertyHolders::Push(vcl::PushFlags nPushFlags)
|
|
{
|
|
if (bool(nPushFlags))
|
|
{
|
|
OSL_ENSURE(maPropertyHolders.size(), "PropertyHolders: PUSH with no property holders (!)");
|
|
if (!maPropertyHolders.empty())
|
|
{
|
|
PropertyHolder* pNew = new PropertyHolder(*maPropertyHolders.back());
|
|
pNew->setPushFlags(nPushFlags);
|
|
maPropertyHolders.push_back(pNew);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PropertyHolders::Pop()
|
|
{
|
|
OSL_ENSURE(maPropertyHolders.size(), "PropertyHolders: POP with no property holders (!)");
|
|
const sal_uInt32 nSize(maPropertyHolders.size());
|
|
|
|
if (!nSize)
|
|
return;
|
|
|
|
const PropertyHolder* pTip = maPropertyHolders.back();
|
|
const vcl::PushFlags nPushFlags(pTip->getPushFlags());
|
|
|
|
if (nPushFlags != vcl::PushFlags::NONE)
|
|
{
|
|
if (nSize > 1)
|
|
{
|
|
// copy back content for all non-set flags
|
|
PropertyHolder* pLast = maPropertyHolders[nSize - 2];
|
|
|
|
if (vcl::PushFlags::ALL != nPushFlags)
|
|
{
|
|
if (!(nPushFlags & vcl::PushFlags::LINECOLOR))
|
|
{
|
|
pLast->setLineColor(pTip->getLineColor());
|
|
pLast->setLineColorActive(pTip->getLineColorActive());
|
|
}
|
|
if (!(nPushFlags & vcl::PushFlags::FILLCOLOR))
|
|
{
|
|
pLast->setFillColor(pTip->getFillColor());
|
|
pLast->setFillColorActive(pTip->getFillColorActive());
|
|
}
|
|
if (!(nPushFlags & vcl::PushFlags::FONT))
|
|
{
|
|
pLast->setFont(pTip->getFont());
|
|
}
|
|
if (!(nPushFlags & vcl::PushFlags::TEXTCOLOR))
|
|
{
|
|
pLast->setTextColor(pTip->getTextColor());
|
|
pLast->setTextColorActive(pTip->getTextColorActive());
|
|
}
|
|
if (!(nPushFlags & vcl::PushFlags::MAPMODE))
|
|
{
|
|
pLast->setTransformation(pTip->getTransformation());
|
|
pLast->setMapUnit(pTip->getMapUnit());
|
|
}
|
|
if (!(nPushFlags & vcl::PushFlags::CLIPREGION))
|
|
{
|
|
pLast->setClipPolyPolygon(pTip->getClipPolyPolygon());
|
|
pLast->setClipPolyPolygonActive(pTip->getClipPolyPolygonActive());
|
|
}
|
|
if (!(nPushFlags & vcl::PushFlags::RASTEROP))
|
|
{
|
|
pLast->setRasterOp(pTip->getRasterOp());
|
|
}
|
|
if (!(nPushFlags & vcl::PushFlags::TEXTFILLCOLOR))
|
|
{
|
|
pLast->setTextFillColor(pTip->getTextFillColor());
|
|
pLast->setTextFillColorActive(pTip->getTextFillColorActive());
|
|
}
|
|
if (!(nPushFlags & vcl::PushFlags::TEXTALIGN))
|
|
{
|
|
if (pLast->getFont().GetAlignment() != pTip->getFont().GetAlignment())
|
|
{
|
|
vcl::Font aFont(pLast->getFont());
|
|
aFont.SetAlignment(pTip->getFont().GetAlignment());
|
|
pLast->setFont(aFont);
|
|
}
|
|
}
|
|
if (!(nPushFlags & vcl::PushFlags::REFPOINT))
|
|
{
|
|
// not supported
|
|
}
|
|
if (!(nPushFlags & vcl::PushFlags::TEXTLINECOLOR))
|
|
{
|
|
pLast->setTextLineColor(pTip->getTextLineColor());
|
|
pLast->setTextLineColorActive(pTip->getTextLineColorActive());
|
|
}
|
|
if (!(nPushFlags & vcl::PushFlags::TEXTLAYOUTMODE))
|
|
{
|
|
pLast->setLayoutMode(pTip->getLayoutMode());
|
|
}
|
|
if (!(nPushFlags & vcl::PushFlags::TEXTLANGUAGE))
|
|
{
|
|
pLast->setLanguageType(pTip->getLanguageType());
|
|
}
|
|
if (!(nPushFlags & vcl::PushFlags::OVERLINECOLOR))
|
|
{
|
|
pLast->setOverlineColor(pTip->getOverlineColor());
|
|
pLast->setOverlineColorActive(pTip->getOverlineColorActive());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// execute the pop
|
|
delete maPropertyHolders.back();
|
|
maPropertyHolders.pop_back();
|
|
}
|
|
|
|
PropertyHolder& PropertyHolders::Current()
|
|
{
|
|
static PropertyHolder aDummy;
|
|
OSL_ENSURE(maPropertyHolders.size(), "PropertyHolders: CURRENT with no property holders (!)");
|
|
return maPropertyHolders.empty() ? aDummy : *maPropertyHolders.back();
|
|
}
|
|
|
|
PropertyHolders::~PropertyHolders()
|
|
{
|
|
while (!maPropertyHolders.empty())
|
|
{
|
|
delete maPropertyHolders.back();
|
|
maPropertyHolders.pop_back();
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
/** helper to convert a vcl::Region to a B2DPolyPolygon
|
|
when it does not yet contain one. In the future
|
|
this may be expanded to merge the polygons created
|
|
from rectangles or use a special algo to directly turn
|
|
the spans of regions to a single, already merged
|
|
PolyPolygon.
|
|
*/
|
|
basegfx::B2DPolyPolygon getB2DPolyPolygonFromRegion(const vcl::Region& rRegion)
|
|
{
|
|
basegfx::B2DPolyPolygon aRetval;
|
|
|
|
if (!rRegion.IsEmpty())
|
|
{
|
|
aRetval = rRegion.GetAsB2DPolyPolygon();
|
|
}
|
|
|
|
return aRetval;
|
|
}
|
|
}
|
|
|
|
namespace wmfemfhelper
|
|
{
|
|
/** Helper class to buffer and hold a Primitive target vector. It
|
|
encapsulates the new/delete functionality and allows to work
|
|
on pointers of the implementation classes. All data will
|
|
be converted to uno sequences of uno references when accessing the
|
|
data.
|
|
*/
|
|
TargetHolder::TargetHolder()
|
|
{
|
|
}
|
|
|
|
TargetHolder::~TargetHolder()
|
|
{
|
|
}
|
|
|
|
sal_uInt32 TargetHolder::size() const
|
|
{
|
|
return aTargets.size();
|
|
}
|
|
|
|
void TargetHolder::append(drawinglayer::primitive2d::BasePrimitive2D* pCandidate)
|
|
{
|
|
if (pCandidate)
|
|
{
|
|
aTargets.push_back(pCandidate);
|
|
}
|
|
}
|
|
|
|
drawinglayer::primitive2d::Primitive2DContainer TargetHolder::getPrimitive2DSequence(const PropertyHolder& rPropertyHolder)
|
|
{
|
|
drawinglayer::primitive2d::Primitive2DContainer xRetval = std::move(aTargets);
|
|
|
|
if (!xRetval.empty() && rPropertyHolder.getClipPolyPolygonActive())
|
|
{
|
|
const basegfx::B2DPolyPolygon& rClipPolyPolygon = rPropertyHolder.getClipPolyPolygon();
|
|
|
|
if (rClipPolyPolygon.count())
|
|
{
|
|
xRetval = drawinglayer::primitive2d::Primitive2DContainer{
|
|
new drawinglayer::primitive2d::MaskPrimitive2D(
|
|
rClipPolyPolygon,
|
|
std::move(xRetval))
|
|
};
|
|
}
|
|
}
|
|
|
|
return xRetval;
|
|
}
|
|
}
|
|
|
|
namespace wmfemfhelper
|
|
{
|
|
/** Helper class which builds a stack on the TargetHolder class */
|
|
TargetHolders::TargetHolders()
|
|
{
|
|
maTargetHolders.push_back(new TargetHolder());
|
|
}
|
|
|
|
sal_uInt32 TargetHolders::size() const
|
|
{
|
|
return maTargetHolders.size();
|
|
}
|
|
|
|
void TargetHolders::Push()
|
|
{
|
|
maTargetHolders.push_back(new TargetHolder());
|
|
}
|
|
|
|
void TargetHolders::Pop()
|
|
{
|
|
OSL_ENSURE(maTargetHolders.size(), "TargetHolders: POP with no property holders (!)");
|
|
if (!maTargetHolders.empty())
|
|
{
|
|
delete maTargetHolders.back();
|
|
maTargetHolders.pop_back();
|
|
}
|
|
}
|
|
|
|
TargetHolder& TargetHolders::Current()
|
|
{
|
|
static TargetHolder aDummy;
|
|
OSL_ENSURE(maTargetHolders.size(), "TargetHolders: CURRENT with no property holders (!)");
|
|
return maTargetHolders.empty() ? aDummy : *maTargetHolders.back();
|
|
}
|
|
|
|
TargetHolders::~TargetHolders()
|
|
{
|
|
while (!maTargetHolders.empty())
|
|
{
|
|
delete maTargetHolders.back();
|
|
maTargetHolders.pop_back();
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
/** helper to convert a MapMode to a transformation */
|
|
basegfx::B2DHomMatrix getTransformFromMapMode(const MapMode& rMapMode)
|
|
{
|
|
basegfx::B2DHomMatrix aMapping;
|
|
const Fraction aNoScale(1, 1);
|
|
const Point& rOrigin(rMapMode.GetOrigin());
|
|
|
|
if(0 != rOrigin.X() || 0 != rOrigin.Y())
|
|
{
|
|
aMapping.translate(rOrigin.X(), rOrigin.Y());
|
|
}
|
|
|
|
if(rMapMode.GetScaleX() != aNoScale || rMapMode.GetScaleY() != aNoScale)
|
|
{
|
|
aMapping.scale(
|
|
double(rMapMode.GetScaleX()),
|
|
double(rMapMode.GetScaleY()));
|
|
}
|
|
|
|
return aMapping;
|
|
}
|
|
}
|
|
|
|
namespace wmfemfhelper
|
|
{
|
|
/** helper to create a PointArrayPrimitive2D based on current context */
|
|
static void createPointArrayPrimitive(
|
|
std::vector< basegfx::B2DPoint >&& rPositions,
|
|
TargetHolder& rTarget,
|
|
PropertyHolder const & rProperties,
|
|
const basegfx::BColor& rBColor)
|
|
{
|
|
if(rPositions.empty())
|
|
return;
|
|
|
|
if(rProperties.getTransformation().isIdentity())
|
|
{
|
|
rTarget.append(
|
|
new drawinglayer::primitive2d::PointArrayPrimitive2D(
|
|
std::move(rPositions),
|
|
rBColor));
|
|
}
|
|
else
|
|
{
|
|
for(basegfx::B2DPoint & aPosition : rPositions)
|
|
{
|
|
aPosition = rProperties.getTransformation() * aPosition;
|
|
}
|
|
|
|
rTarget.append(
|
|
new drawinglayer::primitive2d::PointArrayPrimitive2D(
|
|
std::move(rPositions),
|
|
rBColor));
|
|
}
|
|
}
|
|
|
|
/** helper to create a PolygonHairlinePrimitive2D based on current context */
|
|
static void createHairlinePrimitive(
|
|
const basegfx::B2DPolygon& rLinePolygon,
|
|
TargetHolder& rTarget,
|
|
PropertyHolder const & rProperties)
|
|
{
|
|
if(rLinePolygon.count())
|
|
{
|
|
basegfx::B2DPolygon aLinePolygon(rLinePolygon);
|
|
aLinePolygon.transform(rProperties.getTransformation());
|
|
rTarget.append(
|
|
new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(
|
|
std::move(aLinePolygon),
|
|
rProperties.getLineColor()));
|
|
}
|
|
}
|
|
|
|
/** helper to create a PolyPolygonColorPrimitive2D based on current context */
|
|
static void createFillPrimitive(
|
|
const basegfx::B2DPolyPolygon& rFillPolyPolygon,
|
|
TargetHolder& rTarget,
|
|
PropertyHolder const & rProperties)
|
|
{
|
|
if(rFillPolyPolygon.count())
|
|
{
|
|
basegfx::B2DPolyPolygon aFillPolyPolygon(rFillPolyPolygon);
|
|
aFillPolyPolygon.transform(rProperties.getTransformation());
|
|
rTarget.append(
|
|
new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
|
|
std::move(aFillPolyPolygon),
|
|
rProperties.getFillColor()));
|
|
}
|
|
}
|
|
|
|
/** helper to create a PolygonStrokePrimitive2D based on current context */
|
|
static void createLinePrimitive(
|
|
const basegfx::B2DPolygon& rLinePolygon,
|
|
const LineInfo& rLineInfo,
|
|
TargetHolder& rTarget,
|
|
PropertyHolder const & rProperties)
|
|
{
|
|
if(!rLinePolygon.count())
|
|
return;
|
|
|
|
const bool bDashDotUsed(LineStyle::Dash == rLineInfo.GetStyle());
|
|
const bool bWidthUsed(rLineInfo.GetWidth() > 1);
|
|
|
|
if(bDashDotUsed || bWidthUsed)
|
|
{
|
|
basegfx::B2DPolygon aLinePolygon(rLinePolygon);
|
|
aLinePolygon.transform(rProperties.getTransformation());
|
|
drawinglayer::attribute::LineAttribute aLineAttribute(
|
|
rProperties.getLineColor(),
|
|
bWidthUsed ? rLineInfo.GetWidth() : 0.0,
|
|
rLineInfo.GetLineJoin(),
|
|
rLineInfo.GetLineCap());
|
|
|
|
if(bDashDotUsed)
|
|
{
|
|
std::vector< double > fDotDashArray = rLineInfo.GetDotDashArray();
|
|
const double fAccumulated(std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0));
|
|
drawinglayer::attribute::StrokeAttribute aStrokeAttribute(
|
|
std::move(fDotDashArray),
|
|
fAccumulated);
|
|
|
|
rTarget.append(
|
|
new drawinglayer::primitive2d::PolygonStrokePrimitive2D(
|
|
std::move(aLinePolygon),
|
|
std::move(aLineAttribute),
|
|
std::move(aStrokeAttribute)));
|
|
}
|
|
else
|
|
{
|
|
rTarget.append(
|
|
new drawinglayer::primitive2d::PolygonStrokePrimitive2D(
|
|
std::move(aLinePolygon),
|
|
aLineAttribute));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
createHairlinePrimitive(rLinePolygon, rTarget, rProperties);
|
|
}
|
|
}
|
|
|
|
/** helper to create needed line and fill primitives based on current context */
|
|
static void createHairlineAndFillPrimitive(
|
|
const basegfx::B2DPolygon& rPolygon,
|
|
TargetHolder& rTarget,
|
|
PropertyHolder const & rProperties)
|
|
{
|
|
if(rProperties.getFillColorActive())
|
|
{
|
|
createFillPrimitive(basegfx::B2DPolyPolygon(rPolygon), rTarget, rProperties);
|
|
}
|
|
|
|
if(rProperties.getLineColorActive())
|
|
{
|
|
createHairlinePrimitive(rPolygon, rTarget, rProperties);
|
|
}
|
|
}
|
|
|
|
/** helper to create needed line and fill primitives based on current context */
|
|
static void createHairlineAndFillPrimitive(
|
|
const basegfx::B2DPolyPolygon& rPolyPolygon,
|
|
TargetHolder& rTarget,
|
|
PropertyHolder const & rProperties)
|
|
{
|
|
if(rProperties.getFillColorActive())
|
|
{
|
|
createFillPrimitive(rPolyPolygon, rTarget, rProperties);
|
|
}
|
|
|
|
if(rProperties.getLineColorActive())
|
|
{
|
|
for(sal_uInt32 a(0); a < rPolyPolygon.count(); a++)
|
|
{
|
|
createHairlinePrimitive(rPolyPolygon.getB2DPolygon(a), rTarget, rProperties);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** helper to create DiscreteBitmapPrimitive2D based on current context.
|
|
The DiscreteBitmapPrimitive2D is especially created for this usage
|
|
since no other usage defines a bitmap visualisation based on top-left
|
|
position and size in pixels. At the end it will create a view-dependent
|
|
transformed embedding of a BitmapPrimitive2D.
|
|
*/
|
|
static void createBitmapExPrimitive(
|
|
const BitmapEx& rBitmapEx,
|
|
const Point& rPoint,
|
|
TargetHolder& rTarget,
|
|
PropertyHolder const & rProperties)
|
|
{
|
|
if(!rBitmapEx.IsEmpty())
|
|
{
|
|
basegfx::B2DPoint aPoint(rPoint.X(), rPoint.Y());
|
|
aPoint = rProperties.getTransformation() * aPoint;
|
|
|
|
rTarget.append(
|
|
new drawinglayer::primitive2d::DiscreteBitmapPrimitive2D(
|
|
rBitmapEx,
|
|
aPoint));
|
|
}
|
|
}
|
|
|
|
/** helper to create BitmapPrimitive2D based on current context */
|
|
static void createBitmapExPrimitive(
|
|
const BitmapEx& rBitmapEx,
|
|
const Point& rPoint,
|
|
const Size& rSize,
|
|
TargetHolder& rTarget,
|
|
PropertyHolder const & rProperties)
|
|
{
|
|
if(rBitmapEx.IsEmpty())
|
|
return;
|
|
|
|
basegfx::B2DHomMatrix aObjectTransform;
|
|
|
|
aObjectTransform.set(0, 0, rSize.Width());
|
|
aObjectTransform.set(1, 1, rSize.Height());
|
|
aObjectTransform.set(0, 2, rPoint.X());
|
|
aObjectTransform.set(1, 2, rPoint.Y());
|
|
|
|
aObjectTransform = rProperties.getTransformation() * aObjectTransform;
|
|
|
|
rTarget.append(
|
|
new drawinglayer::primitive2d::BitmapPrimitive2D(
|
|
rBitmapEx,
|
|
aObjectTransform));
|
|
}
|
|
|
|
/** helper to create a regular BotmapEx from a MaskAction (definitions
|
|
which use a bitmap without transparence but define one of the colors as
|
|
transparent)
|
|
*/
|
|
static BitmapEx createMaskBmpEx(const Bitmap& rBitmap, const Color& rMaskColor)
|
|
{
|
|
const Color aWhite(COL_WHITE);
|
|
BitmapPalette aBiLevelPalette {
|
|
aWhite, rMaskColor
|
|
};
|
|
|
|
AlphaMask aMask(rBitmap.CreateAlphaMask(aWhite));
|
|
Bitmap aSolid(rBitmap.GetSizePixel(), vcl::PixelFormat::N8_BPP, &aBiLevelPalette);
|
|
|
|
aSolid.Erase(rMaskColor);
|
|
|
|
return BitmapEx(aSolid, aMask);
|
|
}
|
|
|
|
/** helper to convert from a VCL Gradient definition to the corresponding
|
|
data for primitive representation
|
|
*/
|
|
static drawinglayer::attribute::FillGradientAttribute createFillGradientAttribute(const Gradient& rGradient)
|
|
{
|
|
const Color aStartColor(rGradient.GetStartColor());
|
|
const sal_uInt16 nStartIntens(rGradient.GetStartIntensity());
|
|
basegfx::BColor aStart(aStartColor.getBColor());
|
|
|
|
if(nStartIntens != 100)
|
|
{
|
|
const basegfx::BColor aBlack;
|
|
aStart = interpolate(aBlack, aStart, static_cast<double>(nStartIntens) * 0.01);
|
|
}
|
|
|
|
const Color aEndColor(rGradient.GetEndColor());
|
|
const sal_uInt16 nEndIntens(rGradient.GetEndIntensity());
|
|
basegfx::BColor aEnd(aEndColor.getBColor());
|
|
|
|
if(nEndIntens != 100)
|
|
{
|
|
const basegfx::BColor aBlack;
|
|
aEnd = interpolate(aBlack, aEnd, static_cast<double>(nEndIntens) * 0.01);
|
|
}
|
|
|
|
return drawinglayer::attribute::FillGradientAttribute(
|
|
rGradient.GetStyle(),
|
|
static_cast<double>(rGradient.GetBorder()) * 0.01,
|
|
static_cast<double>(rGradient.GetOfsX()) * 0.01,
|
|
static_cast<double>(rGradient.GetOfsY()) * 0.01,
|
|
toRadians(rGradient.GetAngle()),
|
|
basegfx::BColorStops(aStart, aEnd),
|
|
rGradient.GetSteps());
|
|
}
|
|
|
|
/** helper to convert from a VCL Hatch definition to the corresponding
|
|
data for primitive representation
|
|
*/
|
|
static drawinglayer::attribute::FillHatchAttribute createFillHatchAttribute(const Hatch& rHatch)
|
|
{
|
|
drawinglayer::attribute::HatchStyle aHatchStyle(drawinglayer::attribute::HatchStyle::Single);
|
|
|
|
switch(rHatch.GetStyle())
|
|
{
|
|
default : // case HatchStyle::Single :
|
|
{
|
|
aHatchStyle = drawinglayer::attribute::HatchStyle::Single;
|
|
break;
|
|
}
|
|
case HatchStyle::Double :
|
|
{
|
|
aHatchStyle = drawinglayer::attribute::HatchStyle::Double;
|
|
break;
|
|
}
|
|
case HatchStyle::Triple :
|
|
{
|
|
aHatchStyle = drawinglayer::attribute::HatchStyle::Triple;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return drawinglayer::attribute::FillHatchAttribute(
|
|
aHatchStyle,
|
|
static_cast<double>(rHatch.GetDistance()),
|
|
toRadians(rHatch.GetAngle()),
|
|
rHatch.GetColor().getBColor(),
|
|
3, // same default as VCL, a minimum of three discrete units (pixels) offset
|
|
false);
|
|
}
|
|
|
|
/** helper to take needed action on ClipRegion change. This method needs to be called
|
|
on any vcl::Region change, e.g. at the obvious actions doing this, but also at pop-calls
|
|
which change the vcl::Region of the current context. It takes care of creating the
|
|
current embedded context, set the new vcl::Region at the context and possibly prepare
|
|
a new target for including new geometry into the current region
|
|
*/
|
|
void HandleNewClipRegion(
|
|
const basegfx::B2DPolyPolygon& rClipPolyPolygon,
|
|
TargetHolders& rTargetHolders,
|
|
PropertyHolders& rPropertyHolders)
|
|
{
|
|
const bool bNewActive(rClipPolyPolygon.count());
|
|
|
|
// #i108636# The handling of new ClipPolyPolygons was not done as good as possible
|
|
// in the first version of this interpreter; e.g. when a ClipPolyPolygon was set
|
|
// initially and then using a lot of push/pop actions, the pop always leads
|
|
// to setting a 'new' ClipPolyPolygon which indeed is the return to the ClipPolyPolygon
|
|
// of the properties next on the stack.
|
|
|
|
// This ClipPolyPolygon is identical to the current one, so there is no need to
|
|
// create a MaskPrimitive2D containing the up-to-now created primitives, but
|
|
// this was done before. While this does not lead to wrong primitive
|
|
// representations of the metafile data, it creates unnecessarily expensive
|
|
// representations. Just detecting when no really 'new' ClipPolyPolygon gets set
|
|
// solves the problem.
|
|
|
|
if(!rPropertyHolders.Current().getClipPolyPolygonActive() && !bNewActive)
|
|
{
|
|
// no active ClipPolyPolygon exchanged by no new one, done
|
|
return;
|
|
}
|
|
|
|
if(rPropertyHolders.Current().getClipPolyPolygonActive() && bNewActive)
|
|
{
|
|
// active ClipPolyPolygon and new active ClipPolyPolygon
|
|
if(rPropertyHolders.Current().getClipPolyPolygon() == rClipPolyPolygon)
|
|
{
|
|
// new is the same as old, done
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Here the old and the new are definitively different, maybe
|
|
// old one and/or new one is not active.
|
|
|
|
// Handle deletion of old ClipPolyPolygon. The process evtl. created primitives which
|
|
// belong to this active ClipPolyPolygon. These need to be embedded to a
|
|
// MaskPrimitive2D accordingly.
|
|
if(rPropertyHolders.Current().getClipPolyPolygonActive() && rTargetHolders.size() > 1)
|
|
{
|
|
drawinglayer::primitive2d::Primitive2DContainer aSubContent;
|
|
|
|
if(rPropertyHolders.Current().getClipPolyPolygon().count()
|
|
&& rTargetHolders.Current().size())
|
|
{
|
|
aSubContent = rTargetHolders.Current().getPrimitive2DSequence(
|
|
rPropertyHolders.Current());
|
|
}
|
|
|
|
rTargetHolders.Pop();
|
|
|
|
if(!aSubContent.empty())
|
|
{
|
|
rTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::GroupPrimitive2D(
|
|
std::move(aSubContent)));
|
|
}
|
|
}
|
|
|
|
// apply new settings to current properties by setting
|
|
// the new region now
|
|
rPropertyHolders.Current().setClipPolyPolygonActive(bNewActive);
|
|
|
|
if(bNewActive)
|
|
{
|
|
rPropertyHolders.Current().setClipPolyPolygon(rClipPolyPolygon);
|
|
|
|
// prepare new content holder for new active region
|
|
rTargetHolders.Push();
|
|
}
|
|
}
|
|
|
|
/** helper to handle the change of RasterOp. It takes care of encapsulating all current
|
|
geometry to the current RasterOp (if changed) and needs to be called on any RasterOp
|
|
change. It will also start a new geometry target to embrace to the new RasterOp if
|
|
a changing RasterOp is used. Currently, RasterOp::Xor and RasterOp::Invert are supported using
|
|
InvertPrimitive2D, and RasterOp::N0 by using a ModifiedColorPrimitive2D to force to black paint
|
|
*/
|
|
static void HandleNewRasterOp(
|
|
RasterOp aRasterOp,
|
|
TargetHolders& rTargetHolders,
|
|
PropertyHolders& rPropertyHolders)
|
|
{
|
|
// check if currently active
|
|
if(rPropertyHolders.Current().isRasterOpActive() && rTargetHolders.size() > 1)
|
|
{
|
|
drawinglayer::primitive2d::Primitive2DContainer aSubContent;
|
|
|
|
if(rTargetHolders.Current().size())
|
|
{
|
|
aSubContent = rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current());
|
|
}
|
|
|
|
rTargetHolders.Pop();
|
|
|
|
if(!aSubContent.empty())
|
|
{
|
|
if(rPropertyHolders.Current().isRasterOpForceBlack())
|
|
{
|
|
// force content to black
|
|
rTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
|
|
std::move(aSubContent),
|
|
std::make_shared<basegfx::BColorModifier_replace>(
|
|
basegfx::BColor(0.0, 0.0, 0.0))));
|
|
}
|
|
else // if(rPropertyHolders.Current().isRasterOpInvert())
|
|
{
|
|
// invert content
|
|
rTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::InvertPrimitive2D(
|
|
std::move(aSubContent)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// apply new settings
|
|
rPropertyHolders.Current().setRasterOp(aRasterOp);
|
|
|
|
// check if now active
|
|
if(rPropertyHolders.Current().isRasterOpActive())
|
|
{
|
|
// prepare new content holder for new invert
|
|
rTargetHolders.Push();
|
|
}
|
|
}
|
|
|
|
/** helper to create needed data to emulate the VCL Wallpaper Metafile action.
|
|
It is a quite mighty action. This helper is for simple color filled background.
|
|
*/
|
|
static rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> CreateColorWallpaper(
|
|
const basegfx::B2DRange& rRange,
|
|
const basegfx::BColor& rColor,
|
|
PropertyHolder const & rPropertyHolder)
|
|
{
|
|
basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(rRange));
|
|
aOutline.transform(rPropertyHolder.getTransformation());
|
|
|
|
return new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
|
|
basegfx::B2DPolyPolygon(aOutline),
|
|
rColor);
|
|
}
|
|
|
|
/** helper to create needed data to emulate the VCL Wallpaper Metafile action.
|
|
It is a quite mighty action. This helper is for gradient filled background.
|
|
*/
|
|
static rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> CreateGradientWallpaper(
|
|
const basegfx::B2DRange& rRange,
|
|
const Gradient& rGradient,
|
|
PropertyHolder const & rPropertyHolder)
|
|
{
|
|
drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient));
|
|
basegfx::BColor aSingleColor;
|
|
|
|
if (aAttribute.getColorStops().isSingleColor(aSingleColor))
|
|
{
|
|
// not really a gradient. Create filled rectangle
|
|
return CreateColorWallpaper(rRange, aSingleColor, rPropertyHolder);
|
|
}
|
|
else
|
|
{
|
|
// really a gradient
|
|
rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pRetval(
|
|
new drawinglayer::primitive2d::FillGradientPrimitive2D(
|
|
rRange,
|
|
std::move(aAttribute)));
|
|
|
|
if(!rPropertyHolder.getTransformation().isIdentity())
|
|
{
|
|
const drawinglayer::primitive2d::Primitive2DReference xPrim(pRetval);
|
|
drawinglayer::primitive2d::Primitive2DContainer xSeq { xPrim };
|
|
|
|
pRetval = new drawinglayer::primitive2d::TransformPrimitive2D(
|
|
rPropertyHolder.getTransformation(),
|
|
std::move(xSeq));
|
|
}
|
|
|
|
return pRetval;
|
|
}
|
|
}
|
|
|
|
/** helper to create needed data to emulate the VCL Wallpaper Metafile action.
|
|
It is a quite mighty action. This helper decides if color and/or gradient
|
|
background is needed for the wanted bitmap fill and then creates the needed
|
|
WallpaperBitmapPrimitive2D. This primitive was created for this purpose and
|
|
takes over all needed logic of orientations and tiling.
|
|
*/
|
|
static void CreateAndAppendBitmapWallpaper(
|
|
basegfx::B2DRange aWallpaperRange,
|
|
const Wallpaper& rWallpaper,
|
|
TargetHolder& rTarget,
|
|
PropertyHolder const & rProperty)
|
|
{
|
|
const BitmapEx& aBitmapEx(rWallpaper.GetBitmap());
|
|
const WallpaperStyle eWallpaperStyle(rWallpaper.GetStyle());
|
|
|
|
// if bitmap visualisation is transparent, maybe background
|
|
// needs to be filled. Create background
|
|
if(aBitmapEx.IsAlpha()
|
|
|| (WallpaperStyle::Tile != eWallpaperStyle && WallpaperStyle::Scale != eWallpaperStyle))
|
|
{
|
|
if(rWallpaper.IsGradient())
|
|
{
|
|
rTarget.append(
|
|
CreateGradientWallpaper(
|
|
aWallpaperRange,
|
|
rWallpaper.GetGradient(),
|
|
rProperty));
|
|
}
|
|
else if(!rWallpaper.GetColor().IsTransparent())
|
|
{
|
|
rTarget.append(
|
|
CreateColorWallpaper(
|
|
aWallpaperRange,
|
|
rWallpaper.GetColor().getBColor(),
|
|
rProperty));
|
|
}
|
|
}
|
|
|
|
// use wallpaper rect if set
|
|
if(rWallpaper.IsRect() && !rWallpaper.GetRect().IsEmpty())
|
|
{
|
|
aWallpaperRange = vcl::unotools::b2DRectangleFromRectangle(rWallpaper.GetRect());
|
|
}
|
|
|
|
rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pBitmapWallpaperFill =
|
|
new drawinglayer::primitive2d::WallpaperBitmapPrimitive2D(
|
|
aWallpaperRange,
|
|
aBitmapEx,
|
|
eWallpaperStyle);
|
|
|
|
if(rProperty.getTransformation().isIdentity())
|
|
{
|
|
// add directly
|
|
rTarget.append(pBitmapWallpaperFill);
|
|
}
|
|
else
|
|
{
|
|
// when a transformation is set, embed to it
|
|
rTarget.append(
|
|
new drawinglayer::primitive2d::TransformPrimitive2D(
|
|
rProperty.getTransformation(),
|
|
drawinglayer::primitive2d::Primitive2DContainer { pBitmapWallpaperFill }));
|
|
}
|
|
}
|
|
|
|
/** helper to decide UnderlineAbove for text primitives */
|
|
static bool isUnderlineAbove(const vcl::Font& rFont)
|
|
{
|
|
if(!rFont.IsVertical())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// the underline is right for Japanese only
|
|
return (LANGUAGE_JAPANESE == rFont.GetLanguage()) || (LANGUAGE_JAPANESE == rFont.GetCJKContextLanguage());
|
|
}
|
|
|
|
static void createFontAttributeTransformAndAlignment(
|
|
drawinglayer::attribute::FontAttribute& rFontAttribute,
|
|
basegfx::B2DHomMatrix& rTextTransform,
|
|
basegfx::B2DVector& rAlignmentOffset,
|
|
PropertyHolder const & rProperty)
|
|
{
|
|
const vcl::Font& rFont = rProperty.getFont();
|
|
basegfx::B2DVector aFontScaling;
|
|
|
|
rFontAttribute = drawinglayer::primitive2d::getFontAttributeFromVclFont(
|
|
aFontScaling,
|
|
rFont,
|
|
bool(rProperty.getLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiRtl),
|
|
bool(rProperty.getLayoutMode() & vcl::text::ComplexTextLayoutFlags::BiDiStrong));
|
|
|
|
// add FontScaling
|
|
rTextTransform.scale(aFontScaling.getX(), aFontScaling.getY());
|
|
|
|
// take text align into account
|
|
if(ALIGN_BASELINE != rFont.GetAlignment())
|
|
{
|
|
drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
|
|
aTextLayouterDevice.setFont(rFont);
|
|
|
|
if(ALIGN_TOP == rFont.GetAlignment())
|
|
{
|
|
rAlignmentOffset.setY(aTextLayouterDevice.getFontAscent());
|
|
}
|
|
else // ALIGN_BOTTOM
|
|
{
|
|
rAlignmentOffset.setY(-aTextLayouterDevice.getFontDescent());
|
|
}
|
|
|
|
rTextTransform.translate(rAlignmentOffset.getX(), rAlignmentOffset.getY());
|
|
}
|
|
|
|
// add FontRotation (if used)
|
|
if(rFont.GetOrientation())
|
|
{
|
|
rTextTransform.rotate(-toRadians(rFont.GetOrientation()));
|
|
}
|
|
}
|
|
|
|
/** helper which takes complete care for creating the needed text primitives. It
|
|
takes care of decorated stuff and all the geometry adaptations needed
|
|
*/
|
|
static void processMetaTextAction(
|
|
const Point& rTextStartPosition,
|
|
const OUString& rText,
|
|
sal_uInt16 nTextStart,
|
|
sal_uInt16 nTextLength,
|
|
std::vector< double >&& rDXArray,
|
|
std::vector< sal_Bool >&& rKashidaArray,
|
|
TargetHolder& rTarget,
|
|
PropertyHolder const & rProperty)
|
|
{
|
|
rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pResult;
|
|
const vcl::Font& rFont = rProperty.getFont();
|
|
basegfx::B2DVector aAlignmentOffset(0.0, 0.0);
|
|
|
|
if(nTextLength)
|
|
{
|
|
drawinglayer::attribute::FontAttribute aFontAttribute;
|
|
basegfx::B2DHomMatrix aTextTransform;
|
|
|
|
// fill parameters derived from current font
|
|
createFontAttributeTransformAndAlignment(
|
|
aFontAttribute,
|
|
aTextTransform,
|
|
aAlignmentOffset,
|
|
rProperty);
|
|
|
|
// add TextStartPosition
|
|
aTextTransform.translate(rTextStartPosition.X(), rTextStartPosition.Y());
|
|
|
|
// prepare FontColor and Locale
|
|
const basegfx::BColor aFontColor(rProperty.getTextColor());
|
|
const Color aFillColor(rFont.GetFillColor());
|
|
css::lang::Locale aLocale(LanguageTag(rProperty.getLanguageType()).getLocale());
|
|
const bool bWordLineMode(rFont.IsWordLineMode());
|
|
|
|
const bool bDecoratedIsNeeded(
|
|
LINESTYLE_NONE != rFont.GetOverline()
|
|
|| LINESTYLE_NONE != rFont.GetUnderline()
|
|
|| STRIKEOUT_NONE != rFont.GetStrikeout()
|
|
|| FontEmphasisMark::NONE != (rFont.GetEmphasisMark() & FontEmphasisMark::Style)
|
|
|| FontRelief::NONE != rFont.GetRelief()
|
|
|| rFont.IsShadow()
|
|
|| bWordLineMode);
|
|
|
|
if(bDecoratedIsNeeded)
|
|
{
|
|
// prepare overline, underline and strikeout data
|
|
const drawinglayer::primitive2d::TextLine eFontOverline(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rFont.GetOverline()));
|
|
const drawinglayer::primitive2d::TextLine eFontLineStyle(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rFont.GetUnderline()));
|
|
const drawinglayer::primitive2d::TextStrikeout eTextStrikeout(drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rFont.GetStrikeout()));
|
|
|
|
// check UndelineAbove
|
|
const bool bUnderlineAbove(drawinglayer::primitive2d::TEXT_LINE_NONE != eFontLineStyle && isUnderlineAbove(rFont));
|
|
|
|
// prepare emphasis mark data
|
|
drawinglayer::primitive2d::TextEmphasisMark eTextEmphasisMark(drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE);
|
|
|
|
switch(rFont.GetEmphasisMark() & FontEmphasisMark::Style)
|
|
{
|
|
case FontEmphasisMark::Dot : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DOT; break;
|
|
case FontEmphasisMark::Circle : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_CIRCLE; break;
|
|
case FontEmphasisMark::Disc : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_DISC; break;
|
|
case FontEmphasisMark::Accent : eTextEmphasisMark = drawinglayer::primitive2d::TEXT_FONT_EMPHASIS_MARK_ACCENT; break;
|
|
default: break;
|
|
}
|
|
|
|
const bool bEmphasisMarkAbove(rFont.GetEmphasisMark() & FontEmphasisMark::PosAbove);
|
|
const bool bEmphasisMarkBelow(rFont.GetEmphasisMark() & FontEmphasisMark::PosBelow);
|
|
|
|
// prepare font relief data
|
|
drawinglayer::primitive2d::TextRelief eTextRelief(drawinglayer::primitive2d::TEXT_RELIEF_NONE);
|
|
|
|
switch(rFont.GetRelief())
|
|
{
|
|
case FontRelief::Embossed : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_EMBOSSED; break;
|
|
case FontRelief::Engraved : eTextRelief = drawinglayer::primitive2d::TEXT_RELIEF_ENGRAVED; break;
|
|
default : break; // RELIEF_NONE, FontRelief_FORCE_EQUAL_SIZE
|
|
}
|
|
|
|
// prepare shadow/outline data
|
|
const bool bShadow(rFont.IsShadow());
|
|
|
|
// TextDecoratedPortionPrimitive2D is needed, create one
|
|
pResult = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
|
|
|
|
// attributes for TextSimplePortionPrimitive2D
|
|
aTextTransform,
|
|
rText,
|
|
nTextStart,
|
|
nTextLength,
|
|
std::vector(rDXArray),
|
|
std::move(rKashidaArray),
|
|
aFontAttribute,
|
|
aLocale,
|
|
aFontColor,
|
|
aFillColor,
|
|
|
|
// attributes for TextDecoratedPortionPrimitive2D
|
|
rProperty.getOverlineColorActive() ? rProperty.getOverlineColor() : aFontColor,
|
|
rProperty.getTextLineColorActive() ? rProperty.getTextLineColor() : aFontColor,
|
|
eFontOverline,
|
|
eFontLineStyle,
|
|
bUnderlineAbove,
|
|
eTextStrikeout,
|
|
bWordLineMode,
|
|
eTextEmphasisMark,
|
|
bEmphasisMarkAbove,
|
|
bEmphasisMarkBelow,
|
|
eTextRelief,
|
|
bShadow);
|
|
}
|
|
else
|
|
{
|
|
// TextSimplePortionPrimitive2D is enough
|
|
pResult = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
|
|
aTextTransform,
|
|
rText,
|
|
nTextStart,
|
|
nTextLength,
|
|
std::vector(rDXArray),
|
|
std::move(rKashidaArray),
|
|
std::move(aFontAttribute),
|
|
std::move(aLocale),
|
|
aFontColor);
|
|
}
|
|
}
|
|
|
|
if(pResult && rProperty.getTextFillColorActive())
|
|
{
|
|
// text background is requested, add and encapsulate both to new primitive
|
|
drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
|
|
aTextLayouterDevice.setFont(rFont);
|
|
|
|
// get text width
|
|
double fTextWidth(0.0);
|
|
|
|
if (rDXArray.empty())
|
|
{
|
|
fTextWidth = aTextLayouterDevice.getTextWidth(rText, nTextStart, nTextLength);
|
|
}
|
|
else
|
|
{
|
|
fTextWidth = rDXArray.back();
|
|
}
|
|
|
|
if (fTextWidth > 0.0 && !basegfx::fTools::equalZero(fTextWidth))
|
|
{
|
|
// build text range
|
|
const basegfx::B2DRange aTextRange(
|
|
0.0, -aTextLayouterDevice.getFontAscent(),
|
|
fTextWidth, aTextLayouterDevice.getFontDescent());
|
|
|
|
// create Transform
|
|
basegfx::B2DHomMatrix aTextTransform;
|
|
|
|
aTextTransform.translate(aAlignmentOffset.getX(), aAlignmentOffset.getY());
|
|
|
|
if(rFont.GetOrientation())
|
|
{
|
|
aTextTransform.rotate(-toRadians(rFont.GetOrientation()));
|
|
}
|
|
|
|
aTextTransform.translate(rTextStartPosition.X(), rTextStartPosition.Y());
|
|
|
|
// prepare Primitive2DSequence, put text in foreground
|
|
drawinglayer::primitive2d::Primitive2DContainer aSequence(2);
|
|
aSequence[1] = pResult;
|
|
|
|
// prepare filled polygon
|
|
basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aTextRange));
|
|
aOutline.transform(aTextTransform);
|
|
|
|
aSequence[0] = drawinglayer::primitive2d::Primitive2DReference(
|
|
new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
|
|
basegfx::B2DPolyPolygon(aOutline),
|
|
rProperty.getTextFillColor()));
|
|
|
|
// set as group at pResult
|
|
pResult = new drawinglayer::primitive2d::GroupPrimitive2D(std::move(aSequence));
|
|
}
|
|
}
|
|
|
|
if(!pResult)
|
|
return;
|
|
|
|
// add created text primitive to target
|
|
if(rProperty.getTransformation().isIdentity())
|
|
{
|
|
rTarget.append(pResult);
|
|
}
|
|
else
|
|
{
|
|
// when a transformation is set, embed to it
|
|
rTarget.append(
|
|
new drawinglayer::primitive2d::TransformPrimitive2D(
|
|
rProperty.getTransformation(),
|
|
drawinglayer::primitive2d::Primitive2DContainer { pResult }));
|
|
}
|
|
}
|
|
|
|
/** helper which takes complete care for creating the needed textLine primitives */
|
|
static void processMetaTextLineAction(
|
|
const MetaTextLineAction& rAction,
|
|
TargetHolder& rTarget,
|
|
PropertyHolder const & rProperty)
|
|
{
|
|
const double fLineWidth(fabs(static_cast<double>(rAction.GetWidth())));
|
|
|
|
if(fLineWidth <= 0.0)
|
|
return;
|
|
|
|
const drawinglayer::primitive2d::TextLine aOverlineMode(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rAction.GetOverline()));
|
|
const drawinglayer::primitive2d::TextLine aUnderlineMode(drawinglayer::primitive2d::mapFontLineStyleToTextLine(rAction.GetUnderline()));
|
|
const drawinglayer::primitive2d::TextStrikeout aTextStrikeout(drawinglayer::primitive2d::mapFontStrikeoutToTextStrikeout(rAction.GetStrikeout()));
|
|
|
|
const bool bOverlineUsed(drawinglayer::primitive2d::TEXT_LINE_NONE != aOverlineMode);
|
|
const bool bUnderlineUsed(drawinglayer::primitive2d::TEXT_LINE_NONE != aUnderlineMode);
|
|
const bool bStrikeoutUsed(drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE != aTextStrikeout);
|
|
|
|
if(!(bUnderlineUsed || bStrikeoutUsed || bOverlineUsed))
|
|
return;
|
|
|
|
drawinglayer::primitive2d::Primitive2DContainer aTargets;
|
|
basegfx::B2DVector aAlignmentOffset(0.0, 0.0);
|
|
drawinglayer::attribute::FontAttribute aFontAttribute;
|
|
basegfx::B2DHomMatrix aTextTransform;
|
|
|
|
// fill parameters derived from current font
|
|
createFontAttributeTransformAndAlignment(
|
|
aFontAttribute,
|
|
aTextTransform,
|
|
aAlignmentOffset,
|
|
rProperty);
|
|
|
|
// add TextStartPosition
|
|
aTextTransform.translate(rAction.GetStartPoint().X(), rAction.GetStartPoint().Y());
|
|
|
|
// prepare TextLayouter (used in most cases)
|
|
drawinglayer::primitive2d::TextLayouterDevice aTextLayouter;
|
|
aTextLayouter.setFont(rProperty.getFont());
|
|
|
|
if(bOverlineUsed)
|
|
{
|
|
// create primitive geometry for overline
|
|
aTargets.push_back(
|
|
new drawinglayer::primitive2d::TextLinePrimitive2D(
|
|
aTextTransform,
|
|
fLineWidth,
|
|
aTextLayouter.getOverlineOffset(),
|
|
aTextLayouter.getOverlineHeight(),
|
|
aOverlineMode,
|
|
rProperty.getOverlineColor()));
|
|
}
|
|
|
|
if(bUnderlineUsed)
|
|
{
|
|
// create primitive geometry for underline
|
|
aTargets.push_back(
|
|
new drawinglayer::primitive2d::TextLinePrimitive2D(
|
|
aTextTransform,
|
|
fLineWidth,
|
|
aTextLayouter.getUnderlineOffset(),
|
|
aTextLayouter.getUnderlineHeight(),
|
|
aUnderlineMode,
|
|
rProperty.getTextLineColor()));
|
|
}
|
|
|
|
if(bStrikeoutUsed)
|
|
{
|
|
// create primitive geometry for strikeout
|
|
if(drawinglayer::primitive2d::TEXT_STRIKEOUT_SLASH == aTextStrikeout
|
|
|| drawinglayer::primitive2d::TEXT_STRIKEOUT_X == aTextStrikeout)
|
|
{
|
|
// strikeout with character
|
|
const sal_Unicode aStrikeoutChar(
|
|
drawinglayer::primitive2d::TEXT_STRIKEOUT_SLASH == aTextStrikeout ? '/' : 'X');
|
|
css::lang::Locale aLocale(LanguageTag(
|
|
rProperty.getLanguageType()).getLocale());
|
|
|
|
aTargets.push_back(
|
|
new drawinglayer::primitive2d::TextCharacterStrikeoutPrimitive2D(
|
|
aTextTransform,
|
|
fLineWidth,
|
|
rProperty.getTextColor(),
|
|
aStrikeoutChar,
|
|
std::move(aFontAttribute),
|
|
std::move(aLocale)));
|
|
}
|
|
else
|
|
{
|
|
// strikeout with geometry
|
|
aTargets.push_back(
|
|
new drawinglayer::primitive2d::TextGeometryStrikeoutPrimitive2D(
|
|
aTextTransform,
|
|
fLineWidth,
|
|
rProperty.getTextColor(),
|
|
aTextLayouter.getUnderlineHeight(),
|
|
aTextLayouter.getStrikeoutOffset(),
|
|
aTextStrikeout));
|
|
}
|
|
}
|
|
|
|
if(aTargets.empty())
|
|
return;
|
|
|
|
// add created text primitive to target
|
|
if(rProperty.getTransformation().isIdentity())
|
|
{
|
|
rTarget.append(std::move(aTargets));
|
|
}
|
|
else
|
|
{
|
|
// when a transformation is set, embed to it
|
|
rTarget.append(
|
|
new drawinglayer::primitive2d::TransformPrimitive2D(
|
|
rProperty.getTransformation(),
|
|
std::move(aTargets)));
|
|
}
|
|
}
|
|
|
|
/** This is the main interpreter method. It is designed to handle the given Metafile
|
|
completely inside the given context and target. It may use and modify the context and
|
|
target. This design allows to call itself recursively which adapted contexts and
|
|
targets as e.g. needed for the MetaActionType::FLOATTRANSPARENT where the content is expressed
|
|
as a metafile as sub-content.
|
|
|
|
This interpreter is as free of VCL functionality as possible. It uses VCL data classes
|
|
(else reading the data would not be possible), but e.g. does NOT use a local OutputDevice
|
|
as most other MetaFile interpreters/exporters do to hold and work with the current context.
|
|
This is necessary to be able to get away from the strong internal VCL-binding.
|
|
|
|
It tries to combine e.g. pixel and/or point actions and to stitch together single line primitives
|
|
where possible (which is not trivial with the possible line geometry definitions).
|
|
|
|
It tries to handle clipping no longer as Regions and spans of Rectangles, but as PolyPolygon
|
|
ClipRegions with (where possible) high precision by using the best possible data quality
|
|
from the Region. The vcl::Region is unavoidable as data container, but nowadays allows the transport
|
|
of Polygon-based clip regions. Where this is not used, a Polygon is constructed from the
|
|
vcl::Region ranges. All primitive clipping uses the MaskPrimitive2D with Polygon-based clipping.
|
|
|
|
I have marked the single MetaActions with:
|
|
|
|
SIMPLE, DONE:
|
|
Simple, e.g nothing to do or value setting in the context
|
|
|
|
CHECKED, WORKS WELL:
|
|
Thoroughly tested with extra written test code which created a replacement
|
|
Metafile just to test this action in various combinations
|
|
|
|
NEEDS IMPLEMENTATION:
|
|
Not implemented and asserted, but also no usage found, neither in own Metafile
|
|
creations, nor in EMF/WMF imports (checked with a whole bunch of critical EMF/WMF
|
|
bugdocs)
|
|
|
|
For more comments, see the single action implementations.
|
|
*/
|
|
static void implInterpretMetafile(
|
|
const GDIMetaFile& rMetaFile,
|
|
TargetHolders& rTargetHolders,
|
|
PropertyHolders& rPropertyHolders,
|
|
const drawinglayer::geometry::ViewInformation2D& rViewInformation)
|
|
{
|
|
const size_t nCount(rMetaFile.GetActionSize());
|
|
std::unique_ptr<emfplushelper::EmfPlusHelper> aEMFPlus;
|
|
|
|
for(size_t nAction(0); nAction < nCount; nAction++)
|
|
{
|
|
MetaAction* pAction = rMetaFile.GetAction(nAction);
|
|
|
|
switch(pAction->GetType())
|
|
{
|
|
case MetaActionType::NONE :
|
|
{
|
|
/** SIMPLE, DONE */
|
|
break;
|
|
}
|
|
case MetaActionType::PIXEL :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
std::vector< basegfx::B2DPoint > aPositions;
|
|
Color aLastColor(COL_BLACK);
|
|
|
|
while(MetaActionType::PIXEL == pAction->GetType() && nAction < nCount)
|
|
{
|
|
const MetaPixelAction* pA = static_cast<const MetaPixelAction*>(pAction);
|
|
|
|
if(pA->GetColor() != aLastColor)
|
|
{
|
|
if(!aPositions.empty())
|
|
{
|
|
createPointArrayPrimitive(std::move(aPositions), rTargetHolders.Current(), rPropertyHolders.Current(), aLastColor.getBColor());
|
|
aPositions.clear();
|
|
}
|
|
|
|
aLastColor = pA->GetColor();
|
|
}
|
|
|
|
const Point& rPoint = pA->GetPoint();
|
|
aPositions.emplace_back(rPoint.X(), rPoint.Y());
|
|
nAction++; if(nAction < nCount) pAction = rMetaFile.GetAction(nAction);
|
|
}
|
|
|
|
nAction--;
|
|
|
|
if(!aPositions.empty())
|
|
{
|
|
createPointArrayPrimitive(std::move(aPositions), rTargetHolders.Current(), rPropertyHolders.Current(), aLastColor.getBColor());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::POINT :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
if(rPropertyHolders.Current().getLineColorActive())
|
|
{
|
|
std::vector< basegfx::B2DPoint > aPositions;
|
|
|
|
while(MetaActionType::POINT == pAction->GetType() && nAction < nCount)
|
|
{
|
|
const MetaPointAction* pA = static_cast<const MetaPointAction*>(pAction);
|
|
const Point& rPoint = pA->GetPoint();
|
|
aPositions.emplace_back(rPoint.X(), rPoint.Y());
|
|
nAction++; if(nAction < nCount) pAction = rMetaFile.GetAction(nAction);
|
|
}
|
|
|
|
nAction--;
|
|
|
|
if(!aPositions.empty())
|
|
{
|
|
createPointArrayPrimitive(std::move(aPositions), rTargetHolders.Current(), rPropertyHolders.Current(), rPropertyHolders.Current().getLineColor());
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::LINE :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
if(rPropertyHolders.Current().getLineColorActive())
|
|
{
|
|
basegfx::B2DPolygon aLinePolygon;
|
|
LineInfo aLineInfo;
|
|
|
|
while(MetaActionType::LINE == pAction->GetType() && nAction < nCount)
|
|
{
|
|
const MetaLineAction* pA = static_cast<const MetaLineAction*>(pAction);
|
|
const Point& rStartPoint = pA->GetStartPoint();
|
|
const Point& rEndPoint = pA->GetEndPoint();
|
|
const basegfx::B2DPoint aStart(rStartPoint.X(), rStartPoint.Y());
|
|
const basegfx::B2DPoint aEnd(rEndPoint.X(), rEndPoint.Y());
|
|
|
|
if(aLinePolygon.count())
|
|
{
|
|
if(pA->GetLineInfo() == aLineInfo
|
|
&& aStart == aLinePolygon.getB2DPoint(aLinePolygon.count() - 1))
|
|
{
|
|
aLinePolygon.append(aEnd);
|
|
}
|
|
else
|
|
{
|
|
createLinePrimitive(aLinePolygon, aLineInfo, rTargetHolders.Current(), rPropertyHolders.Current());
|
|
aLinePolygon.clear();
|
|
aLineInfo = pA->GetLineInfo();
|
|
aLinePolygon.append(aStart);
|
|
aLinePolygon.append(aEnd);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
aLineInfo = pA->GetLineInfo();
|
|
aLinePolygon.append(aStart);
|
|
aLinePolygon.append(aEnd);
|
|
}
|
|
|
|
nAction++;
|
|
if (nAction < nCount)
|
|
pAction = rMetaFile.GetAction(nAction);
|
|
}
|
|
|
|
nAction--;
|
|
if (aLinePolygon.count())
|
|
createLinePrimitive(aLinePolygon, aLineInfo, rTargetHolders.Current(), rPropertyHolders.Current());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::RECT :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
if(rPropertyHolders.Current().getLineOrFillActive())
|
|
{
|
|
const MetaRectAction* pA = static_cast<const MetaRectAction*>(pAction);
|
|
const tools::Rectangle& rRectangle = pA->GetRect();
|
|
|
|
if(!rRectangle.IsEmpty())
|
|
{
|
|
const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle);
|
|
|
|
if(!aRange.isEmpty())
|
|
{
|
|
const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange));
|
|
createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::ROUNDRECT :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
/** The original OutputDevice::DrawRect paints nothing when nHor or nVer is zero; but just
|
|
because the tools::Polygon operator creating the rounding does produce nonsense. I assume
|
|
this an error and create an unrounded rectangle in that case (implicit in
|
|
createPolygonFromRect)
|
|
*/
|
|
if(rPropertyHolders.Current().getLineOrFillActive())
|
|
{
|
|
const MetaRoundRectAction* pA = static_cast<const MetaRoundRectAction*>(pAction);
|
|
const tools::Rectangle& rRectangle = pA->GetRect();
|
|
|
|
if(!rRectangle.IsEmpty())
|
|
{
|
|
const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle);
|
|
|
|
if(!aRange.isEmpty())
|
|
{
|
|
const sal_uInt32 nHor(pA->GetHorzRound());
|
|
const sal_uInt32 nVer(pA->GetVertRound());
|
|
basegfx::B2DPolygon aOutline;
|
|
|
|
if(nHor || nVer)
|
|
{
|
|
double fRadiusX((nHor * 2.0) / (aRange.getWidth() > 0.0 ? aRange.getWidth() : 1.0));
|
|
double fRadiusY((nVer * 2.0) / (aRange.getHeight() > 0.0 ? aRange.getHeight() : 1.0));
|
|
fRadiusX = std::clamp(fRadiusX, 0.0, 1.0);
|
|
fRadiusY = std::clamp(fRadiusY, 0.0, 1.0);
|
|
|
|
aOutline = basegfx::utils::createPolygonFromRect(aRange, fRadiusX, fRadiusY);
|
|
}
|
|
else
|
|
{
|
|
aOutline = basegfx::utils::createPolygonFromRect(aRange);
|
|
}
|
|
|
|
createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::ELLIPSE :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
if(rPropertyHolders.Current().getLineOrFillActive())
|
|
{
|
|
const MetaEllipseAction* pA = static_cast<const MetaEllipseAction*>(pAction);
|
|
const tools::Rectangle& rRectangle = pA->GetRect();
|
|
|
|
if(!rRectangle.IsEmpty())
|
|
{
|
|
const basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle);
|
|
|
|
if(!aRange.isEmpty())
|
|
{
|
|
const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromEllipse(
|
|
aRange.getCenter(), aRange.getWidth() * 0.5, aRange.getHeight() * 0.5));
|
|
|
|
createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::ARC :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
if(rPropertyHolders.Current().getLineColorActive())
|
|
{
|
|
const MetaArcAction* pA = static_cast<const MetaArcAction*>(pAction);
|
|
const tools::Polygon aToolsPoly(pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Arc);
|
|
const basegfx::B2DPolygon aOutline(aToolsPoly.getB2DPolygon());
|
|
|
|
createHairlinePrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::PIE :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
if(rPropertyHolders.Current().getLineOrFillActive())
|
|
{
|
|
const MetaPieAction* pA = static_cast<const MetaPieAction*>(pAction);
|
|
const tools::Polygon aToolsPoly(pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Pie);
|
|
const basegfx::B2DPolygon aOutline(aToolsPoly.getB2DPolygon());
|
|
|
|
createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::CHORD :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
if(rPropertyHolders.Current().getLineOrFillActive())
|
|
{
|
|
const MetaChordAction* pA = static_cast<const MetaChordAction*>(pAction);
|
|
const tools::Polygon aToolsPoly(pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Chord);
|
|
const basegfx::B2DPolygon aOutline(aToolsPoly.getB2DPolygon());
|
|
|
|
createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::POLYLINE :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
if(rPropertyHolders.Current().getLineColorActive())
|
|
{
|
|
const MetaPolyLineAction* pA = static_cast<const MetaPolyLineAction*>(pAction);
|
|
createLinePrimitive(pA->GetPolygon().getB2DPolygon(), pA->GetLineInfo(), rTargetHolders.Current(), rPropertyHolders.Current());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::POLYGON :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
if(rPropertyHolders.Current().getLineOrFillActive())
|
|
{
|
|
const MetaPolygonAction* pA = static_cast<const MetaPolygonAction*>(pAction);
|
|
basegfx::B2DPolygon aOutline(pA->GetPolygon().getB2DPolygon());
|
|
|
|
// the metafile play interprets the polygons from MetaPolygonAction
|
|
// always as closed and always paints an edge from last to first point,
|
|
// so force to closed here to emulate that
|
|
if(aOutline.count() > 1 && !aOutline.isClosed())
|
|
{
|
|
aOutline.setClosed(true);
|
|
}
|
|
|
|
createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::POLYPOLYGON :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
if(rPropertyHolders.Current().getLineOrFillActive())
|
|
{
|
|
const MetaPolyPolygonAction* pA = static_cast<const MetaPolyPolygonAction*>(pAction);
|
|
basegfx::B2DPolyPolygon aPolyPolygonOutline(pA->GetPolyPolygon().getB2DPolyPolygon());
|
|
|
|
// the metafile play interprets the single polygons from MetaPolyPolygonAction
|
|
// always as closed and always paints an edge from last to first point,
|
|
// so force to closed here to emulate that
|
|
for(sal_uInt32 b(0); b < aPolyPolygonOutline.count(); b++)
|
|
{
|
|
basegfx::B2DPolygon aPolygonOutline(aPolyPolygonOutline.getB2DPolygon(b));
|
|
|
|
if(aPolygonOutline.count() > 1 && !aPolygonOutline.isClosed())
|
|
{
|
|
aPolygonOutline.setClosed(true);
|
|
aPolyPolygonOutline.setB2DPolygon(b, aPolygonOutline);
|
|
}
|
|
}
|
|
|
|
createHairlineAndFillPrimitive(aPolyPolygonOutline, rTargetHolders.Current(), rPropertyHolders.Current());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::TEXT :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction);
|
|
sal_uInt32 nTextLength(pA->GetLen());
|
|
const sal_uInt32 nTextIndex(pA->GetIndex());
|
|
const sal_uInt32 nStringLength(pA->GetText().getLength());
|
|
|
|
if(nTextLength + nTextIndex > nStringLength)
|
|
{
|
|
nTextLength = nStringLength - nTextIndex;
|
|
}
|
|
|
|
if(nTextLength && rPropertyHolders.Current().getTextColorActive())
|
|
{
|
|
std::vector< double > aDXArray{};
|
|
processMetaTextAction(
|
|
pA->GetPoint(),
|
|
pA->GetText(),
|
|
nTextIndex,
|
|
nTextLength,
|
|
std::move(aDXArray),
|
|
{},
|
|
rTargetHolders.Current(),
|
|
rPropertyHolders.Current());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::TEXTARRAY :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction);
|
|
sal_uInt32 nTextLength(pA->GetLen());
|
|
const sal_uInt32 nTextIndex(pA->GetIndex());
|
|
const sal_uInt32 nStringLength(pA->GetText().getLength());
|
|
|
|
if(nTextLength + nTextIndex > nStringLength)
|
|
{
|
|
nTextLength = nTextIndex > nStringLength ? 0 : nStringLength - nTextIndex;
|
|
}
|
|
|
|
if(nTextLength && rPropertyHolders.Current().getTextColorActive())
|
|
{
|
|
// prepare DXArray (if used)
|
|
std::vector< double > aDXArray;
|
|
const KernArray& rDXArray = pA->GetDXArray();
|
|
std::vector< sal_Bool > aKashidaArray = pA->GetKashidaArray();
|
|
|
|
if(!rDXArray.empty())
|
|
{
|
|
aDXArray.reserve(nTextLength);
|
|
|
|
for(sal_uInt32 a(0); a < nTextLength; a++)
|
|
{
|
|
aDXArray.push_back(static_cast<double>(rDXArray[a]));
|
|
}
|
|
}
|
|
|
|
processMetaTextAction(
|
|
pA->GetPoint(),
|
|
pA->GetText(),
|
|
nTextIndex,
|
|
nTextLength,
|
|
std::move(aDXArray),
|
|
std::move(aKashidaArray),
|
|
rTargetHolders.Current(),
|
|
rPropertyHolders.Current());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::STRETCHTEXT :
|
|
{
|
|
// #i108440# StarMath uses MetaStretchTextAction, thus support is needed.
|
|
// It looks as if it pretty never really uses a width different from
|
|
// the default text-layout width, but it's not possible to be sure.
|
|
// Implemented getting the DXArray and checking for scale at all. If
|
|
// scale is more than 3.5% different, scale the DXArray before usage.
|
|
// New status:
|
|
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction);
|
|
sal_uInt32 nTextLength(pA->GetLen());
|
|
const sal_uInt32 nTextIndex(pA->GetIndex());
|
|
const sal_uInt32 nStringLength(pA->GetText().getLength());
|
|
|
|
if(nTextLength + nTextIndex > nStringLength)
|
|
{
|
|
nTextLength = nStringLength - nTextIndex;
|
|
}
|
|
|
|
if(nTextLength && rPropertyHolders.Current().getTextColorActive())
|
|
{
|
|
drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
|
|
aTextLayouterDevice.setFont(rPropertyHolders.Current().getFont());
|
|
|
|
std::vector< double > aTextArray(
|
|
aTextLayouterDevice.getTextArray(
|
|
pA->GetText(),
|
|
nTextIndex,
|
|
nTextLength));
|
|
|
|
if(!aTextArray.empty())
|
|
{
|
|
const double fTextLength(aTextArray.back());
|
|
|
|
if(0.0 != fTextLength && pA->GetWidth())
|
|
{
|
|
const double fRelative(pA->GetWidth() / fTextLength);
|
|
|
|
if(fabs(fRelative - 1.0) >= 0.035)
|
|
{
|
|
// when derivation is more than 3,5% from default text size,
|
|
// scale the DXArray
|
|
for(double & a : aTextArray)
|
|
{
|
|
a *= fRelative;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
processMetaTextAction(
|
|
pA->GetPoint(),
|
|
pA->GetText(),
|
|
nTextIndex,
|
|
nTextLength,
|
|
std::move(aTextArray),
|
|
{},
|
|
rTargetHolders.Current(),
|
|
rPropertyHolders.Current());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::TEXTRECT :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
// OSL_FAIL("MetaActionType::TEXTRECT requested (!)");
|
|
const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction);
|
|
const tools::Rectangle& rRectangle = pA->GetRect();
|
|
const sal_uInt32 nStringLength(pA->GetText().getLength());
|
|
|
|
if(!rRectangle.IsEmpty() && 0 != nStringLength)
|
|
{
|
|
// The problem with this action is that it describes unlayouted text
|
|
// and the layout capabilities are in EditEngine/Outliner in SVX. The
|
|
// same problem is true for VCL which internally has implementations
|
|
// to layout text in this case. There exists even a call
|
|
// OutputDevice::AddTextRectActions(...) to create the needed actions
|
|
// as 'sub-content' of a Metafile. Unfortunately i do not have an
|
|
// OutputDevice here since this interpreter tries to work without
|
|
// VCL AFAP.
|
|
// Since AddTextRectActions is the only way as long as we do not have
|
|
// a simple text layouter available, i will try to add it to the
|
|
// TextLayouterDevice isolation.
|
|
drawinglayer::primitive2d::TextLayouterDevice aTextLayouterDevice;
|
|
aTextLayouterDevice.setFont(rPropertyHolders.Current().getFont());
|
|
GDIMetaFile aGDIMetaFile;
|
|
|
|
aTextLayouterDevice.addTextRectActions(
|
|
rRectangle, pA->GetText(), pA->GetStyle(), aGDIMetaFile);
|
|
|
|
if(aGDIMetaFile.GetActionSize())
|
|
{
|
|
// create sub-content
|
|
drawinglayer::primitive2d::Primitive2DContainer xSubContent;
|
|
{
|
|
rTargetHolders.Push();
|
|
|
|
// for sub-Mteafile contents, do start with new, default render state
|
|
// #i124686# ...but copy font, this is already set accordingly
|
|
vcl::Font aTargetFont = rPropertyHolders.Current().getFont();
|
|
rPropertyHolders.PushDefault();
|
|
rPropertyHolders.Current().setFont(aTargetFont);
|
|
|
|
implInterpretMetafile(aGDIMetaFile, rTargetHolders, rPropertyHolders, rViewInformation);
|
|
xSubContent = rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current());
|
|
rPropertyHolders.Pop();
|
|
rTargetHolders.Pop();
|
|
}
|
|
|
|
if(!xSubContent.empty())
|
|
{
|
|
// add with transformation
|
|
rTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::TransformPrimitive2D(
|
|
rPropertyHolders.Current().getTransformation(),
|
|
std::move(xSubContent)));
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::BMP :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaBmpAction* pA = static_cast<const MetaBmpAction*>(pAction);
|
|
const BitmapEx aBitmapEx(pA->GetBitmap());
|
|
|
|
createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), rTargetHolders.Current(), rPropertyHolders.Current());
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::BMPSCALE :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
|
|
const BitmapEx aBitmapEx(pA->GetBitmap());
|
|
|
|
createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), pA->GetSize(), rTargetHolders.Current(), rPropertyHolders.Current());
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::BMPSCALEPART :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pAction);
|
|
const Bitmap& rBitmap = pA->GetBitmap();
|
|
|
|
if(!rBitmap.IsEmpty())
|
|
{
|
|
Bitmap aCroppedBitmap(rBitmap);
|
|
const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize());
|
|
|
|
if(!aCropRectangle.IsEmpty())
|
|
{
|
|
aCroppedBitmap.Crop(aCropRectangle);
|
|
}
|
|
|
|
const BitmapEx aCroppedBitmapEx(aCroppedBitmap);
|
|
createBitmapExPrimitive(aCroppedBitmapEx, pA->GetDestPoint(), pA->GetDestSize(), rTargetHolders.Current(), rPropertyHolders.Current());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::BMPEX :
|
|
{
|
|
/** CHECKED, WORKS WELL: Simply same as MetaActionType::BMP */
|
|
const MetaBmpExAction* pA = static_cast<const MetaBmpExAction*>(pAction);
|
|
const BitmapEx& rBitmapEx = pA->GetBitmapEx();
|
|
|
|
createBitmapExPrimitive(rBitmapEx, pA->GetPoint(), rTargetHolders.Current(), rPropertyHolders.Current());
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::BMPEXSCALE :
|
|
{
|
|
/** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALE */
|
|
const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
|
|
const BitmapEx& rBitmapEx = pA->GetBitmapEx();
|
|
|
|
createBitmapExPrimitive(rBitmapEx, pA->GetPoint(), pA->GetSize(), rTargetHolders.Current(), rPropertyHolders.Current());
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::BMPEXSCALEPART :
|
|
{
|
|
/** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALEPART */
|
|
const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction);
|
|
const BitmapEx& rBitmapEx = pA->GetBitmapEx();
|
|
|
|
if(!rBitmapEx.IsEmpty())
|
|
{
|
|
BitmapEx aCroppedBitmapEx(rBitmapEx);
|
|
const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize());
|
|
|
|
if(!aCropRectangle.IsEmpty())
|
|
{
|
|
aCroppedBitmapEx.Crop(aCropRectangle);
|
|
}
|
|
|
|
createBitmapExPrimitive(aCroppedBitmapEx, pA->GetDestPoint(), pA->GetDestSize(), rTargetHolders.Current(), rPropertyHolders.Current());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::MASK :
|
|
{
|
|
/** CHECKED, WORKS WELL: Simply same as MetaActionType::BMP */
|
|
/** Huh, no it isn't!? */
|
|
const MetaMaskAction* pA = static_cast<const MetaMaskAction*>(pAction);
|
|
const BitmapEx aBitmapEx(createMaskBmpEx(pA->GetBitmap(), pA->GetColor()));
|
|
|
|
createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), rTargetHolders.Current(), rPropertyHolders.Current());
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::MASKSCALE :
|
|
{
|
|
/** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALE */
|
|
const MetaMaskScaleAction* pA = static_cast<const MetaMaskScaleAction*>(pAction);
|
|
const BitmapEx aBitmapEx(createMaskBmpEx(pA->GetBitmap(), pA->GetColor()));
|
|
|
|
createBitmapExPrimitive(aBitmapEx, pA->GetPoint(), pA->GetSize(), rTargetHolders.Current(), rPropertyHolders.Current());
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::MASKSCALEPART :
|
|
{
|
|
/** CHECKED, WORKS WELL: Simply same as MetaActionType::BMPSCALEPART */
|
|
const MetaMaskScalePartAction* pA = static_cast<const MetaMaskScalePartAction*>(pAction);
|
|
const Bitmap& rBitmap = pA->GetBitmap();
|
|
|
|
if(!rBitmap.IsEmpty())
|
|
{
|
|
Bitmap aCroppedBitmap(rBitmap);
|
|
const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize());
|
|
|
|
if(!aCropRectangle.IsEmpty())
|
|
{
|
|
aCroppedBitmap.Crop(aCropRectangle);
|
|
}
|
|
|
|
const BitmapEx aCroppedBitmapEx(createMaskBmpEx(aCroppedBitmap, pA->GetColor()));
|
|
createBitmapExPrimitive(aCroppedBitmapEx, pA->GetDestPoint(), pA->GetDestSize(), rTargetHolders.Current(), rPropertyHolders.Current());
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::GRADIENT :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pAction);
|
|
const tools::Rectangle& rRectangle = pA->GetRect();
|
|
|
|
if(!rRectangle.IsEmpty())
|
|
{
|
|
basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle);
|
|
|
|
if(!aRange.isEmpty())
|
|
{
|
|
const Gradient& rGradient = pA->GetGradient();
|
|
drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient));
|
|
basegfx::B2DPolyPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange));
|
|
basegfx::BColor aSingleColor;
|
|
|
|
if (aAttribute.getColorStops().isSingleColor(aSingleColor))
|
|
{
|
|
// not really a gradient. Create filled rectangle
|
|
createFillPrimitive(
|
|
aOutline,
|
|
rTargetHolders.Current(),
|
|
rPropertyHolders.Current());
|
|
}
|
|
else
|
|
{
|
|
// really a gradient
|
|
aRange.transform(rPropertyHolders.Current().getTransformation());
|
|
drawinglayer::primitive2d::Primitive2DContainer xGradient(1);
|
|
|
|
if(rPropertyHolders.Current().isRasterOpInvert())
|
|
{
|
|
// use a special version of FillGradientPrimitive2D which creates
|
|
// non-overlapping geometry on decomposition to make the old XOR
|
|
// paint 'trick' work.
|
|
xGradient[0] = drawinglayer::primitive2d::Primitive2DReference(
|
|
new drawinglayer::primitive2d::NonOverlappingFillGradientPrimitive2D(
|
|
aRange,
|
|
aAttribute));
|
|
}
|
|
else
|
|
{
|
|
xGradient[0] = drawinglayer::primitive2d::Primitive2DReference(
|
|
new drawinglayer::primitive2d::FillGradientPrimitive2D(
|
|
aRange,
|
|
std::move(aAttribute)));
|
|
}
|
|
|
|
// #i112300# clip against polygon representing the rectangle from
|
|
// the action. This is implicitly done using a temp Clipping in VCL
|
|
// when a MetaGradientAction is executed
|
|
aOutline.transform(rPropertyHolders.Current().getTransformation());
|
|
rTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::MaskPrimitive2D(
|
|
std::move(aOutline),
|
|
std::move(xGradient)));
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::HATCH :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaHatchAction* pA = static_cast<const MetaHatchAction*>(pAction);
|
|
basegfx::B2DPolyPolygon aOutline(pA->GetPolyPolygon().getB2DPolyPolygon());
|
|
|
|
if(aOutline.count())
|
|
{
|
|
const Hatch& rHatch = pA->GetHatch();
|
|
drawinglayer::attribute::FillHatchAttribute aAttribute(createFillHatchAttribute(rHatch));
|
|
|
|
aOutline.transform(rPropertyHolders.Current().getTransformation());
|
|
|
|
const basegfx::B2DRange aObjectRange(aOutline.getB2DRange());
|
|
const drawinglayer::primitive2d::Primitive2DReference aFillHatch(
|
|
new drawinglayer::primitive2d::FillHatchPrimitive2D(
|
|
aObjectRange,
|
|
basegfx::BColor(),
|
|
std::move(aAttribute)));
|
|
|
|
rTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::MaskPrimitive2D(
|
|
std::move(aOutline),
|
|
drawinglayer::primitive2d::Primitive2DContainer { aFillHatch }));
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::WALLPAPER :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaWallpaperAction* pA = static_cast<const MetaWallpaperAction*>(pAction);
|
|
tools::Rectangle aWallpaperRectangle(pA->GetRect());
|
|
|
|
if(!aWallpaperRectangle.IsEmpty())
|
|
{
|
|
const Wallpaper& rWallpaper = pA->GetWallpaper();
|
|
const WallpaperStyle eWallpaperStyle(rWallpaper.GetStyle());
|
|
basegfx::B2DRange aWallpaperRange = vcl::unotools::b2DRectangleFromRectangle(aWallpaperRectangle);
|
|
|
|
if(WallpaperStyle::NONE != eWallpaperStyle)
|
|
{
|
|
if(rWallpaper.IsBitmap())
|
|
{
|
|
// create bitmap background. Caution: This
|
|
// also will create gradient/color background(s)
|
|
// when the bitmap is transparent or not tiled
|
|
CreateAndAppendBitmapWallpaper(
|
|
aWallpaperRange,
|
|
rWallpaper,
|
|
rTargetHolders.Current(),
|
|
rPropertyHolders.Current());
|
|
}
|
|
else if(rWallpaper.IsGradient())
|
|
{
|
|
// create gradient background
|
|
rTargetHolders.Current().append(
|
|
CreateGradientWallpaper(
|
|
aWallpaperRange,
|
|
rWallpaper.GetGradient(),
|
|
rPropertyHolders.Current()));
|
|
}
|
|
else if(!rWallpaper.GetColor().IsTransparent())
|
|
{
|
|
// create color background
|
|
rTargetHolders.Current().append(
|
|
CreateColorWallpaper(
|
|
aWallpaperRange,
|
|
rWallpaper.GetColor().getBColor(),
|
|
rPropertyHolders.Current()));
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::CLIPREGION :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaClipRegionAction* pA = static_cast<const MetaClipRegionAction*>(pAction);
|
|
|
|
if(pA->IsClipping())
|
|
{
|
|
// new clipping. Get tools::PolyPolygon and transform with current transformation
|
|
basegfx::B2DPolyPolygon aNewClipPolyPolygon(getB2DPolyPolygonFromRegion(pA->GetRegion()));
|
|
|
|
aNewClipPolyPolygon.transform(rPropertyHolders.Current().getTransformation());
|
|
HandleNewClipRegion(aNewClipPolyPolygon, rTargetHolders, rPropertyHolders);
|
|
}
|
|
else
|
|
{
|
|
// end clipping
|
|
const basegfx::B2DPolyPolygon aEmptyPolyPolygon;
|
|
|
|
HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::ISECTRECTCLIPREGION :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaISectRectClipRegionAction* pA = static_cast<const MetaISectRectClipRegionAction*>(pAction);
|
|
const tools::Rectangle& rRectangle = pA->GetRect();
|
|
|
|
if(rRectangle.IsEmpty())
|
|
{
|
|
// intersect with empty rectangle will always give empty
|
|
// ClipPolyPolygon; start new clipping with empty PolyPolygon
|
|
const basegfx::B2DPolyPolygon aEmptyPolyPolygon;
|
|
|
|
HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders);
|
|
}
|
|
else
|
|
{
|
|
// create transformed ClipRange
|
|
basegfx::B2DRange aClipRange = vcl::unotools::b2DRectangleFromRectangle(rRectangle);
|
|
|
|
aClipRange.transform(rPropertyHolders.Current().getTransformation());
|
|
|
|
if(rPropertyHolders.Current().getClipPolyPolygonActive())
|
|
{
|
|
if(0 == rPropertyHolders.Current().getClipPolyPolygon().count())
|
|
{
|
|
// nothing to do, empty active clipPolyPolygon will stay
|
|
// empty when intersecting
|
|
}
|
|
else
|
|
{
|
|
// AND existing region and new ClipRange
|
|
const basegfx::B2DPolyPolygon aOriginalPolyPolygon(
|
|
rPropertyHolders.Current().getClipPolyPolygon());
|
|
basegfx::B2DPolyPolygon aClippedPolyPolygon;
|
|
|
|
if(aOriginalPolyPolygon.count())
|
|
{
|
|
aClippedPolyPolygon = basegfx::utils::clipPolyPolygonOnRange(
|
|
aOriginalPolyPolygon,
|
|
aClipRange,
|
|
true,
|
|
false);
|
|
}
|
|
|
|
if(aClippedPolyPolygon != aOriginalPolyPolygon)
|
|
{
|
|
// start new clipping with intersected region
|
|
HandleNewClipRegion(
|
|
aClippedPolyPolygon,
|
|
rTargetHolders,
|
|
rPropertyHolders);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// start new clipping with ClipRange
|
|
const basegfx::B2DPolyPolygon aNewClipPolyPolygon(
|
|
basegfx::utils::createPolygonFromRect(aClipRange));
|
|
|
|
HandleNewClipRegion(aNewClipPolyPolygon, rTargetHolders, rPropertyHolders);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::ISECTREGIONCLIPREGION :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaISectRegionClipRegionAction* pA = static_cast<const MetaISectRegionClipRegionAction*>(pAction);
|
|
const vcl::Region& rNewRegion = pA->GetRegion();
|
|
|
|
if(rNewRegion.IsEmpty())
|
|
{
|
|
// intersect with empty region will always give empty
|
|
// region; start new clipping with empty PolyPolygon
|
|
const basegfx::B2DPolyPolygon aEmptyPolyPolygon;
|
|
|
|
HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders);
|
|
}
|
|
else
|
|
{
|
|
// get new ClipPolyPolygon, transform it with current transformation
|
|
basegfx::B2DPolyPolygon aNewClipPolyPolygon(getB2DPolyPolygonFromRegion(rNewRegion));
|
|
aNewClipPolyPolygon.transform(rPropertyHolders.Current().getTransformation());
|
|
|
|
if(rPropertyHolders.Current().getClipPolyPolygonActive())
|
|
{
|
|
if(0 == rPropertyHolders.Current().getClipPolyPolygon().count())
|
|
{
|
|
// nothing to do, empty active clipPolyPolygon will stay empty
|
|
// when intersecting with any region
|
|
}
|
|
else
|
|
{
|
|
// AND existing and new region
|
|
const basegfx::B2DPolyPolygon aOriginalPolyPolygon(
|
|
rPropertyHolders.Current().getClipPolyPolygon());
|
|
basegfx::B2DPolyPolygon aClippedPolyPolygon;
|
|
|
|
if(aOriginalPolyPolygon.count())
|
|
{
|
|
aClippedPolyPolygon = basegfx::utils::clipPolyPolygonOnPolyPolygon(
|
|
aOriginalPolyPolygon, aNewClipPolyPolygon, true, false);
|
|
}
|
|
|
|
if(aClippedPolyPolygon != aOriginalPolyPolygon)
|
|
{
|
|
// start new clipping with intersected ClipPolyPolygon
|
|
HandleNewClipRegion(aClippedPolyPolygon, rTargetHolders, rPropertyHolders);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// start new clipping with new ClipPolyPolygon
|
|
HandleNewClipRegion(aNewClipPolyPolygon, rTargetHolders, rPropertyHolders);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::MOVECLIPREGION :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaMoveClipRegionAction* pA = static_cast<const MetaMoveClipRegionAction*>(pAction);
|
|
|
|
if(rPropertyHolders.Current().getClipPolyPolygonActive())
|
|
{
|
|
if(0 == rPropertyHolders.Current().getClipPolyPolygon().count())
|
|
{
|
|
// nothing to do
|
|
}
|
|
else
|
|
{
|
|
const sal_Int32 nHor(pA->GetHorzMove());
|
|
const sal_Int32 nVer(pA->GetVertMove());
|
|
|
|
if(0 != nHor || 0 != nVer)
|
|
{
|
|
// prepare translation, add current transformation
|
|
basegfx::B2DVector aVector(pA->GetHorzMove(), pA->GetVertMove());
|
|
aVector *= rPropertyHolders.Current().getTransformation();
|
|
basegfx::B2DHomMatrix aTransform(
|
|
basegfx::utils::createTranslateB2DHomMatrix(aVector));
|
|
|
|
// transform existing region
|
|
basegfx::B2DPolyPolygon aClipPolyPolygon(
|
|
rPropertyHolders.Current().getClipPolyPolygon());
|
|
|
|
aClipPolyPolygon.transform(aTransform);
|
|
HandleNewClipRegion(aClipPolyPolygon, rTargetHolders, rPropertyHolders);
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::LINECOLOR :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaLineColorAction* pA = static_cast<const MetaLineColorAction*>(pAction);
|
|
// tdf#89901 do as OutDev does: COL_TRANSPARENT deactivates line draw
|
|
const bool bActive(pA->IsSetting() && COL_TRANSPARENT != pA->GetColor());
|
|
|
|
rPropertyHolders.Current().setLineColorActive(bActive);
|
|
if(bActive)
|
|
rPropertyHolders.Current().setLineColor(pA->GetColor().getBColor());
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::FILLCOLOR :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaFillColorAction* pA = static_cast<const MetaFillColorAction*>(pAction);
|
|
// tdf#89901 do as OutDev does: COL_TRANSPARENT deactivates polygon fill
|
|
const bool bActive(pA->IsSetting() && COL_TRANSPARENT != pA->GetColor());
|
|
|
|
rPropertyHolders.Current().setFillColorActive(bActive);
|
|
if(bActive)
|
|
rPropertyHolders.Current().setFillColor(pA->GetColor().getBColor());
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::TEXTCOLOR :
|
|
{
|
|
/** SIMPLE, DONE */
|
|
const MetaTextColorAction* pA = static_cast<const MetaTextColorAction*>(pAction);
|
|
const bool bActivate(COL_TRANSPARENT != pA->GetColor());
|
|
|
|
rPropertyHolders.Current().setTextColorActive(bActivate);
|
|
rPropertyHolders.Current().setTextColor(pA->GetColor().getBColor());
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::TEXTFILLCOLOR :
|
|
{
|
|
/** SIMPLE, DONE */
|
|
const MetaTextFillColorAction* pA = static_cast<const MetaTextFillColorAction*>(pAction);
|
|
const bool bWithColorArgument(pA->IsSetting());
|
|
|
|
if(bWithColorArgument)
|
|
{
|
|
// emulate OutputDevice::SetTextFillColor(...) WITH argument
|
|
const Color& rFontFillColor = pA->GetColor();
|
|
rPropertyHolders.Current().setTextFillColor(rFontFillColor.getBColor());
|
|
rPropertyHolders.Current().setTextFillColorActive(COL_TRANSPARENT != rFontFillColor);
|
|
}
|
|
else
|
|
{
|
|
// emulate SetFillColor() <- NO argument (!)
|
|
rPropertyHolders.Current().setTextFillColorActive(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::TEXTALIGN :
|
|
{
|
|
/** SIMPLE, DONE */
|
|
const MetaTextAlignAction* pA = static_cast<const MetaTextAlignAction*>(pAction);
|
|
const TextAlign aNewTextAlign = pA->GetTextAlign();
|
|
|
|
// TextAlign is applied to the current font (as in
|
|
// OutputDevice::SetTextAlign which would be used when
|
|
// playing the Metafile)
|
|
if(rPropertyHolders.Current().getFont().GetAlignment() != aNewTextAlign)
|
|
{
|
|
vcl::Font aNewFont(rPropertyHolders.Current().getFont());
|
|
aNewFont.SetAlignment(aNewTextAlign);
|
|
rPropertyHolders.Current().setFont(aNewFont);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::MAPMODE :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
// the most necessary MapMode to be interpreted is MapUnit::MapRelative,
|
|
// but also the others may occur. Even not yet supported ones
|
|
// may need to be added here later
|
|
const MetaMapModeAction* pA = static_cast<const MetaMapModeAction*>(pAction);
|
|
const MapMode& rMapMode = pA->GetMapMode();
|
|
basegfx::B2DHomMatrix aMapping;
|
|
|
|
if(MapUnit::MapRelative == rMapMode.GetMapUnit())
|
|
{
|
|
aMapping = getTransformFromMapMode(rMapMode);
|
|
}
|
|
else
|
|
{
|
|
const auto eFrom = MapToO3tlLength(rPropertyHolders.Current().getMapUnit()),
|
|
eTo = MapToO3tlLength(rMapMode.GetMapUnit());
|
|
if (eFrom != o3tl::Length::invalid && eTo != o3tl::Length::invalid)
|
|
{
|
|
const double fConvert(o3tl::convert(1.0, eFrom, eTo));
|
|
aMapping.scale(fConvert, fConvert);
|
|
}
|
|
else
|
|
OSL_FAIL("implInterpretMetafile: MetaActionType::MAPMODE with unsupported MapUnit (!)");
|
|
|
|
aMapping = getTransformFromMapMode(rMapMode) * aMapping;
|
|
rPropertyHolders.Current().setMapUnit(rMapMode.GetMapUnit());
|
|
}
|
|
|
|
if(!aMapping.isIdentity())
|
|
{
|
|
aMapping = aMapping * rPropertyHolders.Current().getTransformation();
|
|
rPropertyHolders.Current().setTransformation(aMapping);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::FONT :
|
|
{
|
|
/** SIMPLE, DONE */
|
|
const MetaFontAction* pA = static_cast<const MetaFontAction*>(pAction);
|
|
rPropertyHolders.Current().setFont(pA->GetFont());
|
|
Size aFontSize(pA->GetFont().GetFontSize());
|
|
|
|
if(0 == aFontSize.Height())
|
|
{
|
|
// this should not happen but i got Metafiles where this was the
|
|
// case. A height needs to be guessed (similar to OutputDevice::ImplNewFont())
|
|
vcl::Font aCorrectedFont(pA->GetFont());
|
|
|
|
// guess 16 pixel (as in VCL)
|
|
aFontSize = Size(0, 16);
|
|
|
|
// convert to target MapUnit if not pixels
|
|
aFontSize = OutputDevice::LogicToLogic(
|
|
aFontSize, MapMode(MapUnit::MapPixel), MapMode(rPropertyHolders.Current().getMapUnit()));
|
|
|
|
aCorrectedFont.SetFontSize(aFontSize);
|
|
rPropertyHolders.Current().setFont(aCorrectedFont);
|
|
}
|
|
|
|
// older Metafiles have no MetaActionType::TEXTCOLOR which defines
|
|
// the FontColor now, so use the Font's color when not transparent
|
|
const Color& rFontColor = pA->GetFont().GetColor();
|
|
const bool bActivate(COL_TRANSPARENT != rFontColor);
|
|
|
|
if(bActivate)
|
|
{
|
|
rPropertyHolders.Current().setTextColor(rFontColor.getBColor());
|
|
}
|
|
|
|
// caution: do NOT deactivate here on transparent, see
|
|
// OutputDevice::SetFont(..) for more info
|
|
// rPropertyHolders.Current().setTextColorActive(bActivate);
|
|
|
|
// for fill color emulate a MetaTextFillColorAction with !transparent as bool,
|
|
// see OutputDevice::SetFont(..) the if(mpMetaFile) case
|
|
if(bActivate)
|
|
{
|
|
const Color& rFontFillColor = pA->GetFont().GetFillColor();
|
|
rPropertyHolders.Current().setTextFillColor(rFontFillColor.getBColor());
|
|
rPropertyHolders.Current().setTextFillColorActive(COL_TRANSPARENT != rFontFillColor);
|
|
}
|
|
else
|
|
{
|
|
rPropertyHolders.Current().setTextFillColorActive(false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::PUSH :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaPushAction* pA = static_cast<const MetaPushAction*>(pAction);
|
|
rPropertyHolders.Push(pA->GetFlags());
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::POP :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const bool bRegionMayChange(rPropertyHolders.Current().getPushFlags() & vcl::PushFlags::CLIPREGION);
|
|
const bool bRasterOpMayChange(rPropertyHolders.Current().getPushFlags() & vcl::PushFlags::RASTEROP);
|
|
|
|
if(bRegionMayChange && rPropertyHolders.Current().getClipPolyPolygonActive())
|
|
{
|
|
// end evtl. clipping
|
|
const basegfx::B2DPolyPolygon aEmptyPolyPolygon;
|
|
|
|
HandleNewClipRegion(aEmptyPolyPolygon, rTargetHolders, rPropertyHolders);
|
|
}
|
|
|
|
if(bRasterOpMayChange && rPropertyHolders.Current().isRasterOpActive())
|
|
{
|
|
// end evtl. RasterOp
|
|
HandleNewRasterOp(RasterOp::OverPaint, rTargetHolders, rPropertyHolders);
|
|
}
|
|
|
|
rPropertyHolders.Pop();
|
|
|
|
if(bRasterOpMayChange && rPropertyHolders.Current().isRasterOpActive())
|
|
{
|
|
// start evtl. RasterOp
|
|
HandleNewRasterOp(rPropertyHolders.Current().getRasterOp(), rTargetHolders, rPropertyHolders);
|
|
}
|
|
|
|
if(bRegionMayChange && rPropertyHolders.Current().getClipPolyPolygonActive())
|
|
{
|
|
// start evtl. clipping
|
|
HandleNewClipRegion(
|
|
rPropertyHolders.Current().getClipPolyPolygon(), rTargetHolders, rPropertyHolders);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::RASTEROP :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaRasterOpAction* pA = static_cast<const MetaRasterOpAction*>(pAction);
|
|
const RasterOp aRasterOp = pA->GetRasterOp();
|
|
|
|
HandleNewRasterOp(aRasterOp, rTargetHolders, rPropertyHolders);
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::Transparent :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaTransparentAction* pA = static_cast<const MetaTransparentAction*>(pAction);
|
|
const basegfx::B2DPolyPolygon aOutline(pA->GetPolyPolygon().getB2DPolyPolygon());
|
|
|
|
if(aOutline.count())
|
|
{
|
|
const sal_uInt16 nTransparence(pA->GetTransparence());
|
|
|
|
if(0 == nTransparence)
|
|
{
|
|
// not transparent
|
|
createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
|
|
}
|
|
else if(nTransparence >= 100)
|
|
{
|
|
// fully or more than transparent
|
|
}
|
|
else
|
|
{
|
|
// transparent. Create new target
|
|
rTargetHolders.Push();
|
|
|
|
// create primitives there and get them
|
|
createHairlineAndFillPrimitive(aOutline, rTargetHolders.Current(), rPropertyHolders.Current());
|
|
drawinglayer::primitive2d::Primitive2DContainer aSubContent(
|
|
rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current()));
|
|
|
|
// back to old target
|
|
rTargetHolders.Pop();
|
|
|
|
if(!aSubContent.empty())
|
|
{
|
|
rTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
|
|
std::move(aSubContent),
|
|
nTransparence * 0.01));
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::EPS :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
// To support this action, I have added a EpsPrimitive2D which will
|
|
// by default decompose to the Metafile replacement data. To support
|
|
// this EPS on screen, the renderer visualizing this has to support
|
|
// that primitive and visualize the Eps file (e.g. printing)
|
|
const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pAction);
|
|
const tools::Rectangle aRectangle(pA->GetPoint(), pA->GetSize());
|
|
|
|
if(!aRectangle.IsEmpty())
|
|
{
|
|
// create object transform
|
|
basegfx::B2DHomMatrix aObjectTransform;
|
|
|
|
aObjectTransform.set(0, 0, aRectangle.GetWidth());
|
|
aObjectTransform.set(1, 1, aRectangle.GetHeight());
|
|
aObjectTransform.set(0, 2, aRectangle.Left());
|
|
aObjectTransform.set(1, 2, aRectangle.Top());
|
|
|
|
// add current transformation
|
|
aObjectTransform = rPropertyHolders.Current().getTransformation() * aObjectTransform;
|
|
|
|
// embed using EpsPrimitive
|
|
rTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::EpsPrimitive2D(
|
|
aObjectTransform,
|
|
pA->GetLink(),
|
|
pA->GetSubstitute()));
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::REFPOINT :
|
|
{
|
|
/** SIMPLE, DONE */
|
|
// only used for hatch and line pattern offsets, pretty much no longer
|
|
// supported today
|
|
// const MetaRefPointAction* pA = (const MetaRefPointAction*)pAction;
|
|
break;
|
|
}
|
|
case MetaActionType::TEXTLINECOLOR :
|
|
{
|
|
/** SIMPLE, DONE */
|
|
const MetaTextLineColorAction* pA = static_cast<const MetaTextLineColorAction*>(pAction);
|
|
const bool bActive(pA->IsSetting());
|
|
|
|
rPropertyHolders.Current().setTextLineColorActive(bActive);
|
|
if(bActive)
|
|
rPropertyHolders.Current().setTextLineColor(pA->GetColor().getBColor());
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::TEXTLINE :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
// actually creates overline, underline and strikeouts, so
|
|
// these should be isolated from TextDecoratedPortionPrimitive2D
|
|
// to own primitives. Done, available now.
|
|
//
|
|
// This Metaaction seems not to be used (was not used in any
|
|
// checked files). It's used in combination with the current
|
|
// Font.
|
|
const MetaTextLineAction* pA = static_cast<const MetaTextLineAction*>(pAction);
|
|
|
|
processMetaTextLineAction(
|
|
*pA,
|
|
rTargetHolders.Current(),
|
|
rPropertyHolders.Current());
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::FLOATTRANSPARENT :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pAction);
|
|
const basegfx::B2DRange aTargetRange(
|
|
pA->GetPoint().X(),
|
|
pA->GetPoint().Y(),
|
|
pA->GetPoint().X() + pA->GetSize().Width(),
|
|
pA->GetPoint().Y() + pA->GetSize().Height());
|
|
|
|
if(!aTargetRange.isEmpty())
|
|
{
|
|
const GDIMetaFile& rContent = pA->GetGDIMetaFile();
|
|
|
|
if(rContent.GetActionSize())
|
|
{
|
|
// create the sub-content with no embedding specific to the
|
|
// sub-metafile, this seems not to be used.
|
|
drawinglayer::primitive2d::Primitive2DContainer xSubContent;
|
|
{
|
|
rTargetHolders.Push();
|
|
// #i# for sub-Mteafile contents, do start with new, default render state
|
|
rPropertyHolders.PushDefault();
|
|
implInterpretMetafile(rContent, rTargetHolders, rPropertyHolders, rViewInformation);
|
|
xSubContent = rTargetHolders.Current().getPrimitive2DSequence(rPropertyHolders.Current());
|
|
rPropertyHolders.Pop();
|
|
rTargetHolders.Pop();
|
|
}
|
|
|
|
if(!xSubContent.empty())
|
|
{
|
|
// prepare sub-content transform
|
|
basegfx::B2DHomMatrix aSubTransform;
|
|
|
|
// create SourceRange
|
|
const basegfx::B2DRange aSourceRange(
|
|
rContent.GetPrefMapMode().GetOrigin().X(),
|
|
rContent.GetPrefMapMode().GetOrigin().Y(),
|
|
rContent.GetPrefMapMode().GetOrigin().X() + rContent.GetPrefSize().Width(),
|
|
rContent.GetPrefMapMode().GetOrigin().Y() + rContent.GetPrefSize().Height());
|
|
|
|
// apply mapping if aTargetRange and aSourceRange are not equal
|
|
if(!aSourceRange.equal(aTargetRange))
|
|
{
|
|
aSubTransform.translate(-aSourceRange.getMinX(), -aSourceRange.getMinY());
|
|
aSubTransform.scale(
|
|
aTargetRange.getWidth() / (basegfx::fTools::equalZero(aSourceRange.getWidth()) ? 1.0 : aSourceRange.getWidth()),
|
|
aTargetRange.getHeight() / (basegfx::fTools::equalZero(aSourceRange.getHeight()) ? 1.0 : aSourceRange.getHeight()));
|
|
aSubTransform.translate(aTargetRange.getMinX(), aTargetRange.getMinY());
|
|
}
|
|
|
|
// apply general current transformation
|
|
aSubTransform = rPropertyHolders.Current().getTransformation() * aSubTransform;
|
|
|
|
// evtl. embed sub-content to its transformation
|
|
if(!aSubTransform.isIdentity())
|
|
{
|
|
const drawinglayer::primitive2d::Primitive2DReference aEmbeddedTransform(
|
|
new drawinglayer::primitive2d::TransformPrimitive2D(
|
|
aSubTransform,
|
|
std::move(xSubContent)));
|
|
|
|
xSubContent = drawinglayer::primitive2d::Primitive2DContainer { aEmbeddedTransform };
|
|
}
|
|
|
|
// check if gradient is a real gradient
|
|
const Gradient& rGradient = pA->GetGradient();
|
|
drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient));
|
|
basegfx::BColor aSingleColor;
|
|
|
|
if (aAttribute.getColorStops().isSingleColor(aSingleColor))
|
|
{
|
|
// not really a gradient; create UnifiedTransparencePrimitive2D
|
|
rTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
|
|
std::move(xSubContent),
|
|
aSingleColor.luminance()));
|
|
}
|
|
else
|
|
{
|
|
// really a gradient. Create gradient sub-content (with correct scaling)
|
|
basegfx::B2DRange aRange(aTargetRange);
|
|
aRange.transform(rPropertyHolders.Current().getTransformation());
|
|
|
|
// prepare gradient for transparent content
|
|
const drawinglayer::primitive2d::Primitive2DReference xTransparence(
|
|
new drawinglayer::primitive2d::FillGradientPrimitive2D(
|
|
aRange,
|
|
std::move(aAttribute)));
|
|
|
|
// create transparence primitive
|
|
rTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::TransparencePrimitive2D(
|
|
std::move(xSubContent),
|
|
drawinglayer::primitive2d::Primitive2DContainer { xTransparence }));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::GRADIENTEX :
|
|
{
|
|
/** SIMPLE, DONE */
|
|
// This is only a data holder which is interpreted inside comment actions,
|
|
// see MetaActionType::COMMENT for more info
|
|
// const MetaGradientExAction* pA = (const MetaGradientExAction*)pAction;
|
|
break;
|
|
}
|
|
case MetaActionType::LAYOUTMODE :
|
|
{
|
|
/** SIMPLE, DONE */
|
|
const MetaLayoutModeAction* pA = static_cast<const MetaLayoutModeAction*>(pAction);
|
|
rPropertyHolders.Current().setLayoutMode(pA->GetLayoutMode());
|
|
break;
|
|
}
|
|
case MetaActionType::TEXTLANGUAGE :
|
|
{
|
|
/** SIMPLE, DONE */
|
|
const MetaTextLanguageAction* pA = static_cast<const MetaTextLanguageAction*>(pAction);
|
|
rPropertyHolders.Current().setLanguageType(pA->GetTextLanguage());
|
|
break;
|
|
}
|
|
case MetaActionType::OVERLINECOLOR :
|
|
{
|
|
/** SIMPLE, DONE */
|
|
const MetaOverlineColorAction* pA = static_cast<const MetaOverlineColorAction*>(pAction);
|
|
const bool bActive(pA->IsSetting());
|
|
|
|
rPropertyHolders.Current().setOverlineColorActive(bActive);
|
|
if(bActive)
|
|
rPropertyHolders.Current().setOverlineColor(pA->GetColor().getBColor());
|
|
|
|
break;
|
|
}
|
|
case MetaActionType::COMMENT :
|
|
{
|
|
/** CHECKED, WORKS WELL */
|
|
// I already implemented
|
|
// XPATHFILL_SEQ_BEGIN, XPATHFILL_SEQ_END
|
|
// XPATHSTROKE_SEQ_BEGIN, XPATHSTROKE_SEQ_END,
|
|
// but opted to remove these again; it works well without them
|
|
// and makes the code less dependent from those Metafile Add-Ons
|
|
const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
|
|
|
|
if (pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN"))
|
|
{
|
|
// XGRAD_SEQ_BEGIN, XGRAD_SEQ_END should be supported since the
|
|
// pure recorded paint of the gradients uses the XOR paint functionality
|
|
// ('trick'). This is (and will be) problematic with AntiAliasing, so it's
|
|
// better to use this info
|
|
const MetaGradientExAction* pMetaGradientExAction = nullptr;
|
|
bool bDone(false);
|
|
size_t b(nAction + 1);
|
|
|
|
for(; !bDone && b < nCount; b++)
|
|
{
|
|
pAction = rMetaFile.GetAction(b);
|
|
|
|
if(MetaActionType::GRADIENTEX == pAction->GetType())
|
|
{
|
|
pMetaGradientExAction = static_cast<const MetaGradientExAction*>(pAction);
|
|
}
|
|
else if(MetaActionType::COMMENT == pAction->GetType())
|
|
{
|
|
if (static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END"))
|
|
{
|
|
bDone = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(bDone && pMetaGradientExAction)
|
|
{
|
|
// consume actions and skip forward
|
|
nAction = b - 1;
|
|
|
|
// get geometry data
|
|
basegfx::B2DPolyPolygon aPolyPolygon(pMetaGradientExAction->GetPolyPolygon().getB2DPolyPolygon());
|
|
|
|
if(aPolyPolygon.count())
|
|
{
|
|
// transform geometry
|
|
aPolyPolygon.transform(rPropertyHolders.Current().getTransformation());
|
|
|
|
// get and check if gradient is a real gradient
|
|
const Gradient& rGradient = pMetaGradientExAction->GetGradient();
|
|
drawinglayer::attribute::FillGradientAttribute aAttribute(createFillGradientAttribute(rGradient));
|
|
basegfx::BColor aSingleColor;
|
|
|
|
if (aAttribute.getColorStops().isSingleColor(aSingleColor))
|
|
{
|
|
// not really a gradient
|
|
rTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
|
|
std::move(aPolyPolygon),
|
|
aSingleColor));
|
|
}
|
|
else
|
|
{
|
|
// really a gradient
|
|
rTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::PolyPolygonGradientPrimitive2D(
|
|
aPolyPolygon,
|
|
std::move(aAttribute)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (pA->GetComment().equalsIgnoreAsciiCase("EMF_PLUS_HEADER_INFO"))
|
|
{
|
|
if (aEMFPlus)
|
|
{
|
|
// error: should not yet exist
|
|
SAL_INFO("drawinglayer.emf", "Error: multiple EMF_PLUS_HEADER_INFO");
|
|
}
|
|
else
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+ passed to canvas mtf renderer - header info, size: " << pA->GetDataSize());
|
|
SvMemoryStream aMemoryStream(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(), StreamMode::READ);
|
|
|
|
aEMFPlus.reset(
|
|
new emfplushelper::EmfPlusHelper(
|
|
aMemoryStream,
|
|
rTargetHolders,
|
|
rPropertyHolders));
|
|
}
|
|
}
|
|
else if (pA->GetComment().equalsIgnoreAsciiCase("EMF_PLUS"))
|
|
{
|
|
if (!aEMFPlus)
|
|
{
|
|
// error: should exist
|
|
SAL_INFO("drawinglayer.emf", "Error: EMF_PLUS before EMF_PLUS_HEADER_INFO");
|
|
}
|
|
else
|
|
{
|
|
static int count = -1, limit = 0x7fffffff;
|
|
|
|
if (count == -1)
|
|
{
|
|
count = 0;
|
|
|
|
if (char *env = getenv("EMF_PLUS_LIMIT"))
|
|
{
|
|
limit = atoi(env);
|
|
SAL_INFO("drawinglayer.emf", "EMF+ records limit: " << limit);
|
|
}
|
|
}
|
|
|
|
SAL_INFO("drawinglayer.emf", "EMF+ passed to canvas mtf renderer, size: " << pA->GetDataSize());
|
|
|
|
if (count < limit)
|
|
{
|
|
SvMemoryStream aMemoryStream(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(), StreamMode::READ);
|
|
|
|
aEMFPlus->processEmfPlusData(
|
|
aMemoryStream,
|
|
rViewInformation);
|
|
}
|
|
|
|
count++;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
OSL_FAIL("Unknown MetaFile Action (!)");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace wmfemfhelper
|
|
{
|
|
drawinglayer::primitive2d::Primitive2DContainer interpretMetafile(
|
|
const GDIMetaFile& rMetaFile,
|
|
const drawinglayer::geometry::ViewInformation2D& rViewInformation)
|
|
{
|
|
// prepare target and properties; each will have one default entry
|
|
drawinglayer::primitive2d::Primitive2DContainer xRetval;
|
|
TargetHolders aTargetHolders;
|
|
PropertyHolders aPropertyHolders;
|
|
|
|
// set target MapUnit at Properties
|
|
aPropertyHolders.Current().setMapUnit(rMetaFile.GetPrefMapMode().GetMapUnit());
|
|
|
|
// interpret the Metafile
|
|
implInterpretMetafile(rMetaFile, aTargetHolders, aPropertyHolders, rViewInformation);
|
|
|
|
// get the content. There should be only one target, as in the start condition,
|
|
// but iterating will be the right thing to do when some push/pop is not closed
|
|
while (aTargetHolders.size() > 1)
|
|
{
|
|
xRetval.append(
|
|
aTargetHolders.Current().getPrimitive2DSequence(aPropertyHolders.Current()));
|
|
aTargetHolders.Pop();
|
|
}
|
|
|
|
xRetval.append(
|
|
aTargetHolders.Current().getPrimitive2DSequence(aPropertyHolders.Current()));
|
|
|
|
return xRetval;
|
|
}
|
|
|
|
} // end of namespace wmfemfhelper
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|