/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include "rtfsdrimport.hxx"
#include <cmath>
#include <com/sun/star/container/XNamed.hpp>
#include <com/sun/star/drawing/FillStyle.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
#include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp>
#include <com/sun/star/drawing/XDrawPageSupplier.hpp>
#include <com/sun/star/drawing/LineStyle.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp>
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/table/BorderLine2.hpp>
#include <com/sun/star/text/HoriOrientation.hpp>
#include <com/sun/star/text/RelOrientation.hpp>
#include <com/sun/star/text/SizeType.hpp>
#include <com/sun/star/text/VertOrientation.hpp>
#include <com/sun/star/text/WrapTextMode.hpp>
#include <com/sun/star/text/WritingMode.hpp>
#include <com/sun/star/text/TextContentAnchorType.hpp>
#include <com/sun/star/text/XTextRange.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <ooxml/resourceids.hxx>
#include <filter/msfilter/escherex.hxx>
#include <filter/msfilter/util.hxx>
#include <filter/msfilter/rtfutil.hxx>
#include <sal/log.hxx>
#include <svx/svdtrans.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/propertyvalue.hxx>
#include <comphelper/propertysequence.hxx>
#include "rtfreferenceproperties.hxx"
#include <oox/vml/vmlformatting.hxx>
#include <oox/helper/modelobjecthelper.hxx>
#include <oox/drawingml/shapepropertymap.hxx>
#include <oox/helper/propertyset.hxx>
#include <boost/logic/tribool.hpp>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <dmapper/GraphicZOrderHelper.hxx>
#include "rtfdocumentimpl.hxx"
using namespace com::sun::star;
namespace writerfilter
{
namespace rtftok
{
RTFSdrImport::RTFSdrImport(RTFDocumentImpl& rDocument,
uno::Reference<lang::XComponent> const& xDstDoc)
: m_rImport(rDocument)
, m_bTextFrame(false)
, m_bTextGraphicObject(false)
, m_bFakePict(false)
{
uno::Reference<drawing::XDrawPageSupplier> xDrawings(xDstDoc, uno::UNO_QUERY);
if (xDrawings.is())
m_aParents.push(xDrawings->getDrawPage());
m_aGraphicZOrderHelpers.push(writerfilter::dmapper::GraphicZOrderHelper());
}
RTFSdrImport::~RTFSdrImport()
{
if (!m_aGraphicZOrderHelpers.empty())
m_aGraphicZOrderHelpers.pop();
if (!m_aParents.empty())
m_aParents.pop();
}
void RTFSdrImport::createShape(const OUString& rService, uno::Reference<drawing::XShape>& xShape,
uno::Reference<beans::XPropertySet>& xPropertySet)
{
if (m_rImport.getModelFactory().is())
xShape.set(m_rImport.getModelFactory()->createInstance(rService), uno::UNO_QUERY);
xPropertySet.set(xShape, uno::UNO_QUERY);
}
std::vector<beans::PropertyValue> RTFSdrImport::getTextFrameDefaults(bool bNew)
{
std::vector<beans::PropertyValue> aRet;
beans::PropertyValue aPropertyValue;
aPropertyValue.Name = "HoriOrient";
aPropertyValue.Value <<= text::HoriOrientation::NONE;
aRet.push_back(aPropertyValue);
aPropertyValue.Name = "HoriOrientRelation";
aPropertyValue.Value <<= text::RelOrientation::FRAME;
aRet.push_back(aPropertyValue);
aPropertyValue.Name = "VertOrient";
aPropertyValue.Value <<= text::VertOrientation::NONE;
aRet.push_back(aPropertyValue);
aPropertyValue.Name = "VertOrientRelation";
aPropertyValue.Value <<= text::RelOrientation::FRAME;
aRet.push_back(aPropertyValue);
if (!bNew)
{
aPropertyValue.Name = "BackColorTransparency";
aPropertyValue.Value <<= sal_Int32(100);
aRet.push_back(aPropertyValue);
}
// See the spec, new-style frame default margins are specified in EMUs.
aPropertyValue.Name = "LeftBorderDistance";
aPropertyValue.Value <<= sal_Int32(bNew ? (91440 / 360) : 0);
aRet.push_back(aPropertyValue);
aPropertyValue.Name = "RightBorderDistance";
aPropertyValue.Value <<= sal_Int32(bNew ? (91440 / 360) : 0);
aRet.push_back(aPropertyValue);
aPropertyValue.Name = "TopBorderDistance";
aPropertyValue.Value <<= sal_Int32(bNew ? (45720 / 360) : 0);
aRet.push_back(aPropertyValue);
aPropertyValue.Name = "BottomBorderDistance";
aPropertyValue.Value <<= sal_Int32(bNew ? (45720 / 360) : 0);
aRet.push_back(aPropertyValue);
aPropertyValue.Name = "SizeType";
aPropertyValue.Value <<= text::SizeType::FIX;
aRet.push_back(aPropertyValue);
return aRet;
}
void RTFSdrImport::pushParent(uno::Reference<drawing::XShapes> const& xParent)
{
m_aParents.push(xParent);
m_aGraphicZOrderHelpers.push(writerfilter::dmapper::GraphicZOrderHelper());
}
void RTFSdrImport::popParent()
{
if (!m_aGraphicZOrderHelpers.empty())
m_aGraphicZOrderHelpers.pop();
if (!m_aParents.empty())
m_aParents.pop();
}
void RTFSdrImport::resolveDhgt(uno::Reference<beans::XPropertySet> const& xPropertySet,
sal_Int32 const nZOrder, bool const bOldStyle)
{
if (!m_aGraphicZOrderHelpers.empty())
{
writerfilter::dmapper::GraphicZOrderHelper& rHelper = m_aGraphicZOrderHelpers.top();
xPropertySet->setPropertyValue("ZOrder",
uno::makeAny(rHelper.findZOrder(nZOrder, bOldStyle)));
rHelper.addItem(xPropertySet, nZOrder);
}
}
void RTFSdrImport::resolveLineColorAndWidth(bool bTextFrame,
const uno::Reference<beans::XPropertySet>& xPropertySet,
uno::Any const& rLineColor, uno::Any const& rLineWidth)
{
if (!bTextFrame)
{
xPropertySet->setPropertyValue("LineColor", rLineColor);
xPropertySet->setPropertyValue("LineWidth", rLineWidth);
}
else
{
static const char* aBorders[]
= { "TopBorder", "LeftBorder", "BottomBorder", "RightBorder" };
for (const char* pBorder : aBorders)
{
auto aBorderLine = xPropertySet->getPropertyValue(OUString::createFromAscii(pBorder))
.get<table::BorderLine2>();
if (rLineColor.hasValue())
aBorderLine.Color = rLineColor.get<sal_Int32>();
if (rLineWidth.hasValue())
aBorderLine.LineWidth = rLineWidth.get<sal_Int32>();
xPropertySet->setPropertyValue(OUString::createFromAscii(pBorder),
uno::makeAny(aBorderLine));
}
}
}
void RTFSdrImport::resolveFLine(uno::Reference<beans::XPropertySet> const& xPropertySet,
sal_Int32 const nFLine)
{
if (nFLine == 0)
xPropertySet->setPropertyValue("LineStyle", uno::makeAny(drawing::LineStyle_NONE));
else
xPropertySet->setPropertyValue("LineStyle", uno::makeAny(drawing::LineStyle_SOLID));
}
void RTFSdrImport::applyProperty(uno::Reference<drawing::XShape> const& xShape,
const OUString& aKey, const OUString& aValue)
{
uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
sal_Int16 nHoriOrient = 0;
sal_Int16 nVertOrient = 0;
boost::logic::tribool obFitShapeToText(boost::logic::indeterminate);
bool bFilled = true;
if (aKey == "posh")
{
switch (aValue.toInt32())
{
case 1:
nHoriOrient = text::HoriOrientation::LEFT;
break;
case 2:
nHoriOrient = text::HoriOrientation::CENTER;
break;
case 3:
nHoriOrient = text::HoriOrientation::RIGHT;
break;
case 4:
nHoriOrient = text::HoriOrientation::INSIDE;
break;
case 5:
nHoriOrient = text::HoriOrientation::OUTSIDE;
break;
default:
break;
}
}
else if (aKey == "posv")
{
switch (aValue.toInt32())
{
case 1:
nVertOrient = text::VertOrientation::TOP;
break;
case 2:
nVertOrient = text::VertOrientation::CENTER;
break;
case 3:
nVertOrient = text::VertOrientation::BOTTOM;
break;
default:
break;
}
}
else if (aKey == "fFitShapeToText")
obFitShapeToText = aValue.toInt32() == 1;
else if (aKey == "fFilled")
bFilled = aValue.toInt32() == 1;
else if (aKey == "rotation")
{
// See DffPropertyReader::Fix16ToAngle(): in RTF, positive rotation angles are clockwise, we have them as counter-clockwise.
// Additionally, RTF type is 0..360*2^16, our is 0..360*100.
sal_Int32 nRotation = aValue.toInt32() * 100 / RTF_MULTIPLIER;
uno::Reference<lang::XServiceInfo> xServiceInfo(xShape, uno::UNO_QUERY);
if (!xServiceInfo->supportsService("com.sun.star.text.TextFrame"))
xPropertySet->setPropertyValue(
"RotateAngle",
uno::makeAny(sal_Int32(NormAngle36000(static_cast<long>(nRotation) * -1))));
}
if (nHoriOrient != 0 && xPropertySet.is())
xPropertySet->setPropertyValue("HoriOrient", uno::makeAny(nHoriOrient));
if (nVertOrient != 0 && xPropertySet.is())
xPropertySet->setPropertyValue("VertOrient", uno::makeAny(nVertOrient));
if (!boost::logic::indeterminate(obFitShapeToText) && xPropertySet.is())
{
xPropertySet->setPropertyValue(
"SizeType", uno::makeAny(obFitShapeToText ? text::SizeType::MIN : text::SizeType::FIX));
xPropertySet->setPropertyValue("FrameIsAutomaticHeight",
uno::makeAny(static_cast<bool>(obFitShapeToText)));
}
if (!bFilled && xPropertySet.is())
{
if (m_bTextFrame)
xPropertySet->setPropertyValue("BackColorTransparency", uno::makeAny(sal_Int32(100)));
else
xPropertySet->setPropertyValue("FillStyle", uno::makeAny(drawing::FillStyle_NONE));
}
}
int RTFSdrImport::initShape(uno::Reference<drawing::XShape>& o_xShape,
uno::Reference<beans::XPropertySet>& o_xPropSet, bool& o_rIsCustomShape,
RTFShape const& rShape, bool const bClose,
ShapeOrPict const shapeOrPict)
{
assert(!o_xShape.is());
assert(!o_xPropSet.is());
o_rIsCustomShape = false;
m_bFakePict = false;
// first, find the shape type
int nType = -1;
auto iter = std::find_if(
rShape.aProperties.begin(), rShape.aProperties.end(),
[](std::pair<OUString, OUString> aProperty) { return aProperty.first == "shapeType"; });
if (iter == rShape.aProperties.end())
{
if (SHAPE == shapeOrPict)
{
// The spec doesn't state what is the default for shapeType,
// Word seems to implement it as a rectangle.
nType = ESCHER_ShpInst_Rectangle;
}
else
{
// pict is picture by default but can be a rectangle too fdo#79319
nType = ESCHER_ShpInst_PictureFrame;
}
}
else
{
nType = iter->second.toInt32();
if (PICT == shapeOrPict && ESCHER_ShpInst_PictureFrame != nType)
{
m_bFakePict = true;
}
}
switch (nType)
{
case ESCHER_ShpInst_PictureFrame:
createShape("com.sun.star.drawing.GraphicObjectShape", o_xShape, o_xPropSet);
m_bTextGraphicObject = true;
break;
case ESCHER_ShpInst_Line:
createShape("com.sun.star.drawing.LineShape", o_xShape, o_xPropSet);
break;
case ESCHER_ShpInst_Rectangle:
case ESCHER_ShpInst_TextBox:
// If we're inside a groupshape, can't use text frames.
if (!bClose && m_aParents.size() == 1)
{
createShape("com.sun.star.text.TextFrame", o_xShape, o_xPropSet);
m_bTextFrame = true;
std::vector<beans::PropertyValue> aDefaults = getTextFrameDefaults(true);
for (beans::PropertyValue& i : aDefaults)
o_xPropSet->setPropertyValue(i.Name, i.Value);
break;
}
SAL_FALLTHROUGH;
default:
createShape("com.sun.star.drawing.CustomShape", o_xShape, o_xPropSet);
o_rIsCustomShape = true;
break;
}
// Defaults
if (o_xPropSet.is() && !m_bTextFrame)
{
o_xPropSet->setPropertyValue(
"FillColor",
uno::makeAny(sal_uInt32(0xffffff))); // White in Word, kind of blue in Writer.
o_xPropSet->setPropertyValue("VertOrient", uno::makeAny(text::VertOrientation::NONE));
}
return nType;
}
void RTFSdrImport::resolve(RTFShape& rShape, bool bClose, ShapeOrPict const shapeOrPict)
{
bool bPib = false;
m_bTextFrame = false;
m_bTextGraphicObject = false;
uno::Reference<drawing::XShape> xShape;
uno::Reference<beans::XPropertySet> xPropertySet;
uno::Any aAny;
beans::PropertyValue aPropertyValue;
awt::Rectangle aViewBox;
std::vector<beans::PropertyValue> aPath;
// Default line color is black in Word, blue in Writer.
uno::Any aLineColor = uno::makeAny(COL_BLACK);
// Default line width is 0.75 pt (26 mm100) in Word, 0 in Writer.
uno::Any aLineWidth = uno::makeAny(sal_Int32(26));
text::WritingMode eWritingMode = text::WritingMode_LR_TB;
// Groupshape support
boost::optional<sal_Int32> oGroupLeft, oGroupTop, oGroupRight, oGroupBottom;
boost::optional<sal_Int32> oRelLeft, oRelTop, oRelRight, oRelBottom;
// Importing these are not trivial, let the VML import do the hard work.
oox::vml::FillModel aFillModel; // Gradient.
oox::vml::ShadowModel aShadowModel; // Shadow.
bool bOpaque = true;
boost::optional<sal_Int16> oRelativeWidth, oRelativeHeight;
sal_Int16 nRelativeWidthRelation = text::RelOrientation::PAGE_FRAME;
sal_Int16 nRelativeHeightRelation = text::RelOrientation::PAGE_FRAME;
boost::logic::tribool obRelFlipV(boost::logic::indeterminate);
boost::logic::tribool obFlipH(boost::logic::indeterminate);
boost::logic::tribool obFlipV(boost::logic::indeterminate);
OUString aShapeText = "";
OUString aFontFamily = "";
float nFontSize = 1.0;
bool bCustom(false);
int const nType = initShape(xShape, xPropertySet, bCustom, rShape, bClose, shapeOrPict);
for (auto& rProperty : rShape.aProperties)
{
if (rProperty.first == "shapeType")
{
continue; // ignore: already handled by initShape
}
if (rProperty.first == "wzName")
{
if (m_bTextFrame)
{
uno::Reference<container::XNamed> xNamed(xShape, uno::UNO_QUERY);
xNamed->setName(rProperty.second);
}
else
xPropertySet->setPropertyValue("Name", uno::makeAny(rProperty.second));
}
else if (rProperty.first == "wzDescription")
xPropertySet->setPropertyValue("Description", uno::makeAny(rProperty.second));
else if (rProperty.first == "gtextUNICODE")
aShapeText = rProperty.second;
else if (rProperty.first == "gtextFont")
aFontFamily = rProperty.second;
else if (rProperty.first == "gtextSize")
{
// RTF size is multiplied by 2^16
nFontSize = static_cast<float>(rProperty.second.toUInt32()) / RTF_MULTIPLIER;
}
else if (rProperty.first == "pib")
{
m_rImport.setDestinationText(rProperty.second);
bPib = true;
}
else if (rProperty.first == "fillColor" && xPropertySet.is())
{
aAny <<= msfilter::util::BGRToRGB(rProperty.second.toUInt32());
if (m_bTextFrame)
xPropertySet->setPropertyValue("BackColor", aAny);
else
xPropertySet->setPropertyValue("FillColor", aAny);
// fillType will decide, possible it'll be the start color of a gradient.
aFillModel.moColor.set(
"#" + OUString::fromUtf8(msfilter::util::ConvertColor(aAny.get<sal_Int32>())));
}
else if (rProperty.first == "fillBackColor")
// fillType will decide, possible it'll be the end color of a gradient.
aFillModel.moColor2.set("#"
+ OUString::fromUtf8(msfilter::util::ConvertColor(
msfilter::util::BGRToRGB(rProperty.second.toInt32()))));
else if (rProperty.first == "lineColor")
aLineColor <<= msfilter::util::BGRToRGB(rProperty.second.toInt32());
else if (rProperty.first == "lineBackColor")
; // Ignore: complementer of lineColor
else if (rProperty.first == "txflTextFlow" && xPropertySet.is())
{
if (rProperty.second.toInt32() == 1)
eWritingMode = text::WritingMode_TB_RL;
}
else if (rProperty.first == "fLine" && xPropertySet.is())
resolveFLine(xPropertySet, rProperty.second.toInt32());
else if (rProperty.first == "fillOpacity" && xPropertySet.is())
{
int opacity = 100 - (rProperty.second.toInt32()) * 100 / RTF_MULTIPLIER;
xPropertySet->setPropertyValue("FillTransparence", uno::Any(sal_uInt32(opacity)));
}
else if (rProperty.first == "lineWidth")
aLineWidth <<= rProperty.second.toInt32() / 360;
else if (rProperty.first == "pVerticies")
{
std::vector<drawing::EnhancedCustomShapeParameterPair> aCoordinates;
sal_Int32 nSize = 0; // Size of a token (its value is hardwired in the exporter)
sal_Int32 nCount = 0; // Number of tokens
sal_Int32 nCharIndex = 0; // Character index
do
{
OUString aToken = rProperty.second.getToken(0, ';', nCharIndex);
if (!nSize)
nSize = aToken.toInt32();
else if (!nCount)
nCount = aToken.toInt32();
else if (aToken.getLength())
{
// The coordinates are in an (x,y) form.
aToken = aToken.copy(1, aToken.getLength() - 2);
sal_Int32 nI = 0;
boost::optional<sal_Int32> oX;
boost::optional<sal_Int32> oY;
do
{
OUString aPoint = aToken.getToken(0, ',', nI);
if (!oX)
oX.reset(aPoint.toInt32());
else
oY.reset(aPoint.toInt32());
} while (nI >= 0);
drawing::EnhancedCustomShapeParameterPair aPair;
aPair.First.Value <<= *oX;
aPair.Second.Value <<= *oY;
aCoordinates.push_back(aPair);
}
} while (nCharIndex >= 0);
aPropertyValue.Name = "Coordinates";
aPropertyValue.Value <<= comphelper::containerToSequence(aCoordinates);
aPath.push_back(aPropertyValue);
}
else if (rProperty.first == "pSegmentInfo")
{
std::vector<drawing::EnhancedCustomShapeSegment> aSegments;
sal_Int32 nSize = 0;
sal_Int32 nCount = 0;
sal_Int32 nCharIndex = 0;
do
{
sal_Int32 nSeg = rProperty.second.getToken(0, ';', nCharIndex).toInt32();
if (!nSize)
nSize = nSeg;
else if (!nCount)
nCount = nSeg;
else
{
sal_Int32 nPoints = 1;
if (nSeg >= 0x2000 && nSeg < 0x20FF)
{
nPoints = nSeg & 0x0FFF;
nSeg &= 0xFF00;
}
drawing::EnhancedCustomShapeSegment aSegment;
switch (nSeg)
{
case 0x0001: // lineto
aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::LINETO;
aSegment.Count = sal_Int32(1);
aSegments.push_back(aSegment);
break;
case 0x4000: // moveto
aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::MOVETO;
aSegment.Count = sal_Int32(1);
aSegments.push_back(aSegment);
break;
case 0x2000: // curveto
aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::CURVETO;
aSegment.Count = nPoints;
aSegments.push_back(aSegment);
break;
case 0xb300: // arcto
aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::ARCTO;
aSegment.Count = sal_Int32(0);
aSegments.push_back(aSegment);
break;
case 0xac00:
case 0xaa00: // nofill
case 0xab00: // nostroke
case 0x6001: // close
break;
case 0x8000: // end
aSegment.Command
= drawing::EnhancedCustomShapeSegmentCommand::ENDSUBPATH;
aSegment.Count = sal_Int32(0);
aSegments.push_back(aSegment);
break;
default: // given number of lineto elements
aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::LINETO;
aSegment.Count = nSeg;
aSegments.push_back(aSegment);
break;
}
}
} while (nCharIndex >= 0);
aPropertyValue.Name = "Segments";
aPropertyValue.Value <<= comphelper::containerToSequence(aSegments);
aPath.push_back(aPropertyValue);
}
else if (rProperty.first == "geoLeft")
aViewBox.X = rProperty.second.toInt32();
else if (rProperty.first == "geoTop")
aViewBox.Y = rProperty.second.toInt32();
else if (rProperty.first == "geoRight")
aViewBox.Width = rProperty.second.toInt32();
else if (rProperty.first == "geoBottom")
aViewBox.Height = rProperty.second.toInt32();
else if (rProperty.first == "dhgt")
{
// dhgt is Word 2007, \shpz is Word 97-2003, the later has priority.
if (!rShape.oZ)
resolveDhgt(xPropertySet, rProperty.second.toInt32(), /*bOldStyle=*/false);
}
// These are in EMU, convert to mm100.
else if (rProperty.first == "dxTextLeft")
{
if (xPropertySet.is())
xPropertySet->setPropertyValue("LeftBorderDistance",
uno::makeAny(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "dyTextTop")
{
if (xPropertySet.is())
xPropertySet->setPropertyValue("TopBorderDistance",
uno::makeAny(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "dxTextRight")
{
if (xPropertySet.is())
xPropertySet->setPropertyValue("RightBorderDistance",
uno::makeAny(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "dyTextBottom")
{
if (xPropertySet.is())
xPropertySet->setPropertyValue("BottomBorderDistance",
uno::makeAny(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "dxWrapDistLeft")
{
if (m_bTextGraphicObject)
rShape.aAnchorAttributes.set(NS_ooxml::LN_CT_Anchor_distL,
new RTFValue(rProperty.second.toInt32()));
else if (xPropertySet.is())
xPropertySet->setPropertyValue("LeftMargin",
uno::makeAny(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "dyWrapDistTop")
{
if (m_bTextGraphicObject)
rShape.aAnchorAttributes.set(NS_ooxml::LN_CT_Anchor_distT,
new RTFValue(rProperty.second.toInt32()));
else if (xPropertySet.is())
xPropertySet->setPropertyValue("TopMargin",
uno::makeAny(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "dxWrapDistRight")
{
if (m_bTextGraphicObject)
rShape.aAnchorAttributes.set(NS_ooxml::LN_CT_Anchor_distR,
new RTFValue(rProperty.second.toInt32()));
else if (xPropertySet.is())
xPropertySet->setPropertyValue("RightMargin",
uno::makeAny(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "dyWrapDistBottom")
{
if (m_bTextGraphicObject)
rShape.aAnchorAttributes.set(NS_ooxml::LN_CT_Anchor_distB,
new RTFValue(rProperty.second.toInt32()));
else if (xPropertySet.is())
xPropertySet->setPropertyValue("BottomMargin",
uno::makeAny(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "fillType")
{
switch (rProperty.second.toInt32())
{
case 7: // Shade using the fillAngle
aFillModel.moType.set(oox::XML_gradient);
break;
default:
SAL_INFO("writerfilter",
"TODO handle fillType value '" << rProperty.second << "'");
break;
}
}
else if (rProperty.first == "fillFocus")
aFillModel.moFocus.set(rProperty.second.toDouble() / 100); // percent
else if (rProperty.first == "fShadow" && xPropertySet.is())
{
if (rProperty.second.toInt32() == 1)
aShadowModel.mbHasShadow = true;
}
else if (rProperty.first == "shadowColor")
aShadowModel.moColor.set("#"
+ OUString::fromUtf8(msfilter::util::ConvertColor(
msfilter::util::BGRToRGB(rProperty.second.toInt32()))));
else if (rProperty.first == "shadowOffsetX")
// EMUs to points
aShadowModel.moOffset.set(OUString::number(rProperty.second.toDouble() / 12700) + "pt");
else if (rProperty.first == "posh" || rProperty.first == "posv"
|| rProperty.first == "fFitShapeToText" || rProperty.first == "fFilled"
|| rProperty.first == "rotation")
applyProperty(xShape, rProperty.first, rProperty.second);
else if (rProperty.first == "posrelh")
{
switch (rProperty.second.toInt32())
{
case 1:
rShape.nHoriOrientRelation = text::RelOrientation::PAGE_FRAME;
break;
default:
break;
}
}
else if (rProperty.first == "posrelv")
{
switch (rProperty.second.toInt32())
{
case 1:
rShape.nVertOrientRelation = text::RelOrientation::PAGE_FRAME;
break;
default:
break;
}
}
else if (rProperty.first == "groupLeft")
oGroupLeft.reset(convertTwipToMm100(rProperty.second.toInt32()));
else if (rProperty.first == "groupTop")
oGroupTop.reset(convertTwipToMm100(rProperty.second.toInt32()));
else if (rProperty.first == "groupRight")
oGroupRight.reset(convertTwipToMm100(rProperty.second.toInt32()));
else if (rProperty.first == "groupBottom")
oGroupBottom.reset(convertTwipToMm100(rProperty.second.toInt32()));
else if (rProperty.first == "relLeft")
oRelLeft.reset(convertTwipToMm100(rProperty.second.toInt32()));
else if (rProperty.first == "relTop")
oRelTop.reset(convertTwipToMm100(rProperty.second.toInt32()));
else if (rProperty.first == "relRight")
oRelRight.reset(convertTwipToMm100(rProperty.second.toInt32()));
else if (rProperty.first == "relBottom")
oRelBottom.reset(convertTwipToMm100(rProperty.second.toInt32()));
else if (rProperty.first == "fBehindDocument")
bOpaque = !rProperty.second.toInt32();
else if (rProperty.first == "pctHoriz" || rProperty.first == "pctVert")
{
sal_Int16 nPercentage = rtl::math::round(rProperty.second.toDouble() / 10);
if (nPercentage)
{
boost::optional<sal_Int16>& rPercentage
= rProperty.first == "pctHoriz" ? oRelativeWidth : oRelativeHeight;
rPercentage = nPercentage;
}
}
else if (rProperty.first == "sizerelh")
{
if (xPropertySet.is())
{
switch (rProperty.second.toInt32())
{
case 0: // margin
nRelativeWidthRelation = text::RelOrientation::FRAME;
break;
case 1: // page
nRelativeWidthRelation = text::RelOrientation::PAGE_FRAME;
break;
default:
SAL_WARN("writerfilter", "RTFSdrImport::resolve: unhandled sizerelh value: "
<< rProperty.second);
break;
}
}
}
else if (rProperty.first == "sizerelv")
{
if (xPropertySet.is())
{
switch (rProperty.second.toInt32())
{
case 0: // margin
nRelativeHeightRelation = text::RelOrientation::FRAME;
break;
case 1: // page
nRelativeHeightRelation = text::RelOrientation::PAGE_FRAME;
break;
default:
SAL_WARN("writerfilter", "RTFSdrImport::resolve: unhandled sizerelv value: "
<< rProperty.second);
break;
}
}
}
else if (rProperty.first == "fHorizRule") // TODO: what does "fStandardHR" do?
{
// horizontal rule: relative width defaults to 100% of paragraph
// TODO: does it have a default height?
if (!oRelativeWidth)
{
oRelativeWidth = 100;
}
nRelativeWidthRelation = text::RelOrientation::FRAME;
sal_Int16 const nVertOrient = text::VertOrientation::CENTER;
if (xPropertySet.is())
{
xPropertySet->setPropertyValue("VertOrient", uno::makeAny(nVertOrient));
}
}
else if (rProperty.first == "pctHR")
{
// horizontal rule relative width in permille
oRelativeWidth = rProperty.second.toInt32() / 10;
}
else if (rProperty.first == "dxHeightHR")
{
// horizontal rule height
sal_uInt32 const nHeight(convertTwipToMm100(rProperty.second.toInt32()));
rShape.nBottom = rShape.nTop + nHeight;
}
else if (rProperty.first == "dxWidthHR")
{
// horizontal rule width
sal_uInt32 const nWidth(convertTwipToMm100(rProperty.second.toInt32()));
rShape.nRight = rShape.nLeft + nWidth;
}
else if (rProperty.first == "alignHR")
{
// horizontal orientation *for horizontal rule*
sal_Int16 nHoriOrient = text::HoriOrientation::NONE;
switch (rProperty.second.toInt32())
{
case 0:
nHoriOrient = text::HoriOrientation::LEFT;
break;
case 1:
nHoriOrient = text::HoriOrientation::CENTER;
break;
case 2:
nHoriOrient = text::HoriOrientation::RIGHT;
break;
}
if (xPropertySet.is() && text::HoriOrientation::NONE != nHoriOrient)
{
xPropertySet->setPropertyValue("HoriOrient", uno::makeAny(nHoriOrient));
}
}
else if (rProperty.first == "pWrapPolygonVertices")
{
RTFSprms aPolygonSprms;
sal_Int32 nSize = 0; // Size of a token
sal_Int32 nCount = 0; // Number of tokens
sal_Int32 nCharIndex = 0; // Character index
do
{
OUString aToken = rProperty.second.getToken(0, ';', nCharIndex);
if (!nSize)
nSize = aToken.toInt32();
else if (!nCount)
nCount = aToken.toInt32();
else if (aToken.getLength())
{
// The coordinates are in an (x,y) form.
aToken = aToken.copy(1, aToken.getLength() - 2);
sal_Int32 nI = 0;
boost::optional<sal_Int32> oX;
boost::optional<sal_Int32> oY;
do
{
OUString aPoint = aToken.getToken(0, ',', nI);
if (!oX)
oX.reset(aPoint.toInt32());
else
oY.reset(aPoint.toInt32());
} while (nI >= 0);
RTFSprms aPathAttributes;
aPathAttributes.set(NS_ooxml::LN_CT_Point2D_x, new RTFValue(*oX));
aPathAttributes.set(NS_ooxml::LN_CT_Point2D_y, new RTFValue(*oY));
aPolygonSprms.set(NS_ooxml::LN_CT_WrapPath_lineTo,
new RTFValue(aPathAttributes), RTFOverwrite::NO_APPEND);
}
} while (nCharIndex >= 0);
rShape.aWrapPolygonSprms = aPolygonSprms;
}
else if (rProperty.first == "fRelFlipV")
obRelFlipV = rProperty.second.toInt32() == 1;
else if (rProperty.first == "fFlipH")
obFlipH = rProperty.second.toInt32() == 1;
else if (rProperty.first == "fFlipV")
obFlipV = rProperty.second.toInt32() == 1;
else
SAL_INFO("writerfilter", "TODO handle shape property '" << rProperty.first << "':'"
<< rProperty.second << "'");
}
if (xPropertySet.is())
{
resolveLineColorAndWidth(m_bTextFrame, xPropertySet, aLineColor, aLineWidth);
if (rShape.oZ)
{
bool bOldStyle = m_aParents.size() > 1;
resolveDhgt(xPropertySet, *rShape.oZ, bOldStyle);
}
if (m_bTextFrame)
// Writer textframes implement text::WritingMode2, which is a different data type.
xPropertySet->setPropertyValue("WritingMode", uno::makeAny(sal_Int16(eWritingMode)));
else
xPropertySet->setPropertyValue("TextWritingMode", uno::makeAny(eWritingMode));
}
if (!m_aParents.empty() && m_aParents.top().is() && !m_bTextFrame)
m_aParents.top()->add(xShape);
if (bPib)
{
m_rImport.resolvePict(false, xShape);
}
if (nType == ESCHER_ShpInst_PictureFrame) // picture frame
{
assert(!m_bTextFrame);
if (!bPib) // ??? not sure if the early return should be removed on else?
{
m_xShape = xShape; // store it for later resolvePict call
}
// Handle horizontal flip.
if (obFlipH == true)
{
if (xPropertySet.is())
xPropertySet->setPropertyValue("IsMirrored", uno::makeAny(true));
}
return;
}
if (bCustom && xShape.is() && !bPib)
{
uno::Reference<drawing::XEnhancedCustomShapeDefaulter> xDefaulter(xShape, uno::UNO_QUERY);
xDefaulter->createCustomShapeDefaults(OUString::number(nType));
}
// Set shape text
if (bCustom && !aShapeText.isEmpty())
{
uno::Reference<text::XTextRange> xTextRange(xShape, uno::UNO_QUERY);
if (xTextRange.is())
xTextRange->setString(aShapeText);
xPropertySet->setPropertyValue("CharFontName", uno::makeAny(aFontFamily));
xPropertySet->setPropertyValue("CharHeight", uno::makeAny(nFontSize));
}
// Creating CustomShapeGeometry property
std::vector<beans::PropertyValue> aGeometry;
if (aViewBox.X || aViewBox.Y || aViewBox.Width || aViewBox.Height)
{
aViewBox.Width -= aViewBox.X;
aViewBox.Height -= aViewBox.Y;
aPropertyValue.Name = "ViewBox";
aPropertyValue.Value <<= aViewBox;
aGeometry.push_back(aPropertyValue);
}
if (!aPath.empty())
{
aPropertyValue.Name = "Path";
aPropertyValue.Value <<= comphelper::containerToSequence(aPath);
aGeometry.push_back(aPropertyValue);
}
if (!aGeometry.empty() && xPropertySet.is() && !m_bTextFrame)
xPropertySet->setPropertyValue("CustomShapeGeometry",
uno::Any(comphelper::containerToSequence(aGeometry)));
if (!aShapeText.isEmpty())
{
auto aGeomPropSeq = xPropertySet->getPropertyValue("CustomShapeGeometry")
.get<uno::Sequence<beans::PropertyValue>>();
auto aGeomPropVec
= comphelper::sequenceToContainer<std::vector<beans::PropertyValue>>(aGeomPropSeq);
uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence({
{ "TextPath", uno::makeAny(true) },
}));
auto it = std::find_if(
aGeomPropVec.begin(), aGeomPropVec.end(),
[](const beans::PropertyValue& rValue) { return rValue.Name == "TextPath"; });
if (it == aGeomPropVec.end())
aGeomPropVec.push_back(comphelper::makePropertyValue("TextPath", aPropertyValues));
else
it->Value <<= aPropertyValues;
xPropertySet->setPropertyValue("CustomShapeGeometry",
uno::makeAny(comphelper::containerToSequence(aGeomPropVec)));
xPropertySet->setPropertyValue("TextAutoGrowHeight", uno::makeAny(false));
xPropertySet->setPropertyValue("TextAutoGrowWidth", uno::makeAny(false));
}
if (!boost::logic::indeterminate(obRelFlipV) && xPropertySet.is())
{
if (nType == ESCHER_ShpInst_Line)
{
// Line shape inside group shape: get the polygon sequence and transform it.
uno::Sequence<uno::Sequence<awt::Point>> aPolyPolySequence;
if ((xPropertySet->getPropertyValue("PolyPolygon") >>= aPolyPolySequence)
&& aPolyPolySequence.hasElements())
{
uno::Sequence<awt::Point>& rPolygon = aPolyPolySequence[0];
basegfx::B2DPolygon aPoly;
for (sal_Int32 i = 0; i < rPolygon.getLength(); ++i)
{
const awt::Point& rPoint = rPolygon[i];
aPoly.insert(i, basegfx::B2DPoint(rPoint.X, rPoint.Y));
}
basegfx::B2DHomMatrix aTransformation;
aTransformation.scale(1.0, obRelFlipV ? -1.0 : 1.0);
aPoly.transform(aTransformation);
for (sal_Int32 i = 0; i < rPolygon.getLength(); ++i)
{
basegfx::B2DPoint aPoint(aPoly.getB2DPoint(i));
rPolygon[i]
= awt::Point(static_cast<sal_Int32>(convertMm100ToTwip(aPoint.getX())),
static_cast<sal_Int32>(convertMm100ToTwip(aPoint.getY())));
}
xPropertySet->setPropertyValue("PolyPolygon", uno::makeAny(aPolyPolySequence));
}
}
}
// Set position and size
if (xShape.is())
{
sal_Int32 nLeft = rShape.nLeft;
sal_Int32 nTop = rShape.nTop;
bool bInShapeGroup = oGroupLeft && oGroupTop && oGroupRight && oGroupBottom && oRelLeft
&& oRelTop && oRelRight && oRelBottom;
awt::Size aSize;
if (bInShapeGroup)
{
// See lclGetAbsPoint() in the VML import: rShape is the group shape, oGroup is its coordinate system, oRel is the relative child shape.
sal_Int32 nShapeWidth = rShape.nRight - rShape.nLeft;
sal_Int32 nShapeHeight = rShape.nBottom - rShape.nTop;
sal_Int32 nCoordSysWidth = *oGroupRight - *oGroupLeft;
sal_Int32 nCoordSysHeight = *oGroupBottom - *oGroupTop;
double fWidthRatio = static_cast<double>(nShapeWidth) / nCoordSysWidth;
double fHeightRatio = static_cast<double>(nShapeHeight) / nCoordSysHeight;
nLeft = static_cast<sal_Int32>(rShape.nLeft + fWidthRatio * (*oRelLeft - *oGroupLeft));
nTop = static_cast<sal_Int32>(rShape.nTop + fHeightRatio * (*oRelTop - *oGroupTop));
// See lclGetAbsRect() in the VML import.
aSize.Width = std::lround(fWidthRatio * (*oRelRight - *oRelLeft));
aSize.Height = std::lround(fHeightRatio * (*oRelBottom - *oRelTop));
}
if (m_bTextFrame)
{
xPropertySet->setPropertyValue("HoriOrientPosition", uno::makeAny(nLeft));
xPropertySet->setPropertyValue("VertOrientPosition", uno::makeAny(nTop));
}
else
xShape->setPosition(awt::Point(nLeft, nTop));
if (bInShapeGroup)
xShape->setSize(aSize);
else
xShape->setSize(awt::Size(rShape.nRight - rShape.nLeft, rShape.nBottom - rShape.nTop));
if (obFlipH == true || obFlipV == true)
{
// Line shapes have no CustomShapeGeometry.
if (nType != ESCHER_ShpInst_Line)
{
// This has to be set after position and size is set, otherwise flip will affect the position.
comphelper::SequenceAsHashMap aCustomShapeGeometry(
xPropertySet->getPropertyValue("CustomShapeGeometry"));
if (obFlipH == true)
aCustomShapeGeometry["MirroredX"] <<= true;
if (obFlipV == true)
aCustomShapeGeometry["MirroredY"] <<= true;
xPropertySet->setPropertyValue(
"CustomShapeGeometry",
uno::makeAny(aCustomShapeGeometry.getAsConstPropertyValueList()));
}
}
if (rShape.nHoriOrientRelation != 0)
xPropertySet->setPropertyValue("HoriOrientRelation",
uno::makeAny(rShape.nHoriOrientRelation));
if (rShape.nVertOrientRelation != 0)
xPropertySet->setPropertyValue("VertOrientRelation",
uno::makeAny(rShape.nVertOrientRelation));
if (rShape.nWrap != text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE)
xPropertySet->setPropertyValue("Surround",
uno::makeAny(text::WrapTextMode(rShape.nWrap)));
oox::ModelObjectHelper aModelObjectHelper(m_rImport.getModelFactory());
if (aFillModel.moType.has())
{
oox::drawingml::ShapePropertyMap aPropMap(aModelObjectHelper);
aFillModel.pushToPropMap(aPropMap, m_rImport.getGraphicHelper());
// Sets the FillStyle and FillGradient UNO properties.
oox::PropertySet(xShape).setProperties(aPropMap);
}
if (aShadowModel.mbHasShadow)
{
oox::drawingml::ShapePropertyMap aPropMap(aModelObjectHelper);
aShadowModel.pushToPropMap(aPropMap, m_rImport.getGraphicHelper());
// Sets the ShadowFormat UNO property.
oox::PropertySet(xShape).setProperties(aPropMap);
}
xPropertySet->setPropertyValue("AnchorType",
uno::makeAny(text::TextContentAnchorType_AT_CHARACTER));
xPropertySet->setPropertyValue("Opaque", uno::makeAny(bOpaque));
if (oRelativeWidth)
{
xPropertySet->setPropertyValue("RelativeWidth", uno::makeAny(*oRelativeWidth));
xPropertySet->setPropertyValue("RelativeWidthRelation",
uno::makeAny(nRelativeWidthRelation));
}
if (oRelativeHeight)
{
xPropertySet->setPropertyValue("RelativeHeight", uno::makeAny(*oRelativeHeight));
xPropertySet->setPropertyValue("RelativeHeightRelation",
uno::makeAny(nRelativeHeightRelation));
}
}
if (m_rImport.isInBackground())
{
RTFSprms aAttributes;
aAttributes.set(NS_ooxml::LN_CT_Background_color,
new RTFValue(xPropertySet->getPropertyValue("FillColor").get<sal_Int32>()));
m_rImport.Mapper().props(new RTFReferenceProperties(aAttributes));
uno::Reference<lang::XComponent> xComponent(xShape, uno::UNO_QUERY);
xComponent->dispose();
return;
}
// Send it to dmapper
m_rImport.Mapper().startShape(xShape);
if (bClose)
{
m_rImport.Mapper().endShape();
}
m_xShape = xShape;
}
void RTFSdrImport::close() { m_rImport.Mapper().endShape(); }
void RTFSdrImport::append(const OUString& aKey, const OUString& aValue)
{
applyProperty(m_xShape, aKey, aValue);
}
void RTFSdrImport::appendGroupProperty(const OUString& aKey, const OUString& aValue)
{
if (m_aParents.empty())
return;
uno::Reference<drawing::XShape> xShape(m_aParents.top(), uno::UNO_QUERY);
if (xShape.is())
applyProperty(xShape, aKey, aValue);
}
} // namespace rtftok
} // namespace writerfilter
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V1007 The value from the uninitialized optional 'oRelLeft' is used. Probably it is a mistake.
↑ V1007 The value from the uninitialized optional 'oRelTop' is used. Probably it is a mistake.
↑ V1007 The value from the uninitialized optional 'oGroupLeft' is used. Probably it is a mistake.
↑ V1007 The value from the uninitialized optional 'oRelLeft' is used. Probably it is a mistake.
↑ V1007 The value from the uninitialized optional 'oGroupTop' is used. Probably it is a mistake.
↑ V1007 The value from the uninitialized optional 'oGroupBottom' is used. Probably it is a mistake.
↑ V1007 The value from the uninitialized optional 'oGroupLeft' is used. Probably it is a mistake.
↑ V1007 The value from the uninitialized optional 'oGroupRight' is used. Probably it is a mistake.
↑ V1007 The value from the uninitialized optional 'oY' is used. Probably it is a mistake.
↑ V1007 The value from the uninitialized optional 'oX' is used. Probably it is a mistake.
↑ V1007 The value from the uninitialized optional 'oY' is used. Probably it is a mistake.
↑ V1007 The value from the uninitialized optional 'oX' is used. Probably it is a mistake.
↑ V1007 The value from the uninitialized optional 'oRelBottom' is used. Probably it is a mistake.
↑ V1007 The value from the uninitialized optional 'oRelTop' is used. Probably it is a mistake.
↑ V1007 The value from the uninitialized optional 'oGroupTop' is used. Probably it is a mistake.
↑ V1007 The value from the uninitialized optional 'oRelRight' is used. Probably it is a mistake.
↑ V560 A part of conditional expression is always false: oRelLeft.
↑ V560 A part of conditional expression is always false: oGroupBottom.
↑ V560 A part of conditional expression is always false: oGroupRight.
↑ V560 A part of conditional expression is always false: oGroupTop.
↑ V560 A part of conditional expression is always false: oGroupLeft.
↑ V547 Expression '!oX' is always true.
↑ V547 Expression '!oX' is always true.
↑ V560 A part of conditional expression is always false: oRelRight.
↑ V560 A part of conditional expression is always false: oRelBottom.
↑ V547 Expression 'bInShapeGroup' is always false.
↑ V547 Expression 'bInShapeGroup' is always false.
↑ V560 A part of conditional expression is always false: oRelTop.