/* -*- 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 <optional>
#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/drawing/ColorMode.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/WritingMode2.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/propertysequence.hxx>
#include "rtfreferenceproperties.hxx"
#include <oox/vml/vmlformatting.hxx>
#include <oox/helper/modelobjecthelper.hxx>
#include <oox/drawingml/drawingmltypes.hxx>
#include <oox/drawingml/shapepropertymap.hxx>
#include <oox/helper/propertyset.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <svx/svdobj.hxx>
#include <tools/UnitConversion.hxx>
#include <o3tl/string_view.hxx>
#include <dmapper/GraphicZOrderHelper.hxx>
#include "rtfdocumentimpl.hxx"
#include <unotxdoc.hxx>
using namespace com::sun::star;
namespace writerfilter::rtftok
{
RTFSdrImport::RTFSdrImport(RTFDocumentImpl& rDocument,
rtl::Reference<SwXTextDocument> const& xDstDoc)
: m_rImport(rDocument)
, m_bTextFrame(false)
, m_bTextGraphicObject(false)
, m_bFakePict(false)
{
if (xDstDoc)
m_aParents.push(xDstDoc->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.getTextDocument().is())
xShape.set(m_rImport.getTextDocument()->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(u"ZOrder"_ustr,
uno::Any(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(u"LineColor"_ustr, rLineColor);
xPropertySet->setPropertyValue(u"LineWidth"_ustr, rLineWidth);
}
else
{
static constexpr OUString aBorders[]
= { u"TopBorder"_ustr, u"LeftBorder"_ustr, u"BottomBorder"_ustr, u"RightBorder"_ustr };
for (const OUString& pBorder : aBorders)
{
auto aBorderLine = xPropertySet->getPropertyValue(pBorder).get<table::BorderLine2>();
if (rLineColor.hasValue())
aBorderLine.Color = rLineColor.get<sal_Int32>();
if (rLineWidth.hasValue())
aBorderLine.LineWidth = rLineWidth.get<sal_Int32>();
xPropertySet->setPropertyValue(pBorder, uno::Any(aBorderLine));
}
}
}
void RTFSdrImport::resolveFLine(uno::Reference<beans::XPropertySet> const& xPropertySet,
sal_Int32 const nFLine)
{
if (nFLine == 0)
xPropertySet->setPropertyValue(u"LineStyle"_ustr, uno::Any(drawing::LineStyle_NONE));
else
xPropertySet->setPropertyValue(u"LineStyle"_ustr, uno::Any(drawing::LineStyle_SOLID));
}
void RTFSdrImport::applyProperty(uno::Reference<drawing::XShape> const& xShape,
std::u16string_view aKey, std::u16string_view aValue) const
{
uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
sal_Int16 nHoriOrient = 0;
sal_Int16 nVertOrient = 0;
std::optional<bool> obFitShapeToText;
bool bFilled = true;
if (aKey == u"posh")
{
switch (o3tl::toInt32(aValue))
{
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 == u"posv")
{
switch (o3tl::toInt32(aValue))
{
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 == u"fFitShapeToText")
obFitShapeToText = o3tl::toInt32(aValue) == 1;
else if (aKey == u"fFilled")
bFilled = o3tl::toInt32(aValue) == 1;
else if (aKey == u"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 = o3tl::toInt32(aValue) * 100 / RTF_MULTIPLIER;
uno::Reference<lang::XServiceInfo> xServiceInfo(xShape, uno::UNO_QUERY);
if (!xServiceInfo->supportsService(u"com.sun.star.text.TextFrame"_ustr))
xPropertySet->setPropertyValue(
u"RotateAngle"_ustr, uno::Any(NormAngle36000(Degree100(nRotation * -1)).get()));
}
if (nHoriOrient != 0 && xPropertySet.is())
xPropertySet->setPropertyValue(u"HoriOrient"_ustr, uno::Any(nHoriOrient));
if (nVertOrient != 0 && xPropertySet.is())
xPropertySet->setPropertyValue(u"VertOrient"_ustr, uno::Any(nVertOrient));
if (obFitShapeToText.has_value() && xPropertySet.is())
{
xPropertySet->setPropertyValue(
u"SizeType"_ustr,
uno::Any(*obFitShapeToText ? text::SizeType::MIN : text::SizeType::FIX));
xPropertySet->setPropertyValue(u"FrameIsAutomaticHeight"_ustr, uno::Any(*obFitShapeToText));
}
if (!bFilled && xPropertySet.is())
{
if (m_bTextFrame)
xPropertySet->setPropertyValue(u"BackColorTransparency"_ustr, uno::Any(sal_Int32(100)));
else
xPropertySet->setPropertyValue(u"FillStyle"_ustr, uno::Any(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.getProperties().begin(), rShape.getProperties().end(),
[](const std::pair<OUString, OUString>& rProperty) {
return rProperty.first == "shapeType";
});
if (iter == rShape.getProperties().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(u"com.sun.star.drawing.GraphicObjectShape"_ustr, o_xShape, o_xPropSet);
m_bTextGraphicObject = true;
break;
case ESCHER_ShpInst_Line:
createShape(u"com.sun.star.drawing.LineShape"_ustr, 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(u"com.sun.star.text.TextFrame"_ustr, o_xShape, o_xPropSet);
m_bTextFrame = true;
std::vector<beans::PropertyValue> aDefaults = getTextFrameDefaults(true);
for (const beans::PropertyValue& i : aDefaults)
o_xPropSet->setPropertyValue(i.Name, i.Value);
break;
}
[[fallthrough]];
default:
createShape(u"com.sun.star.drawing.CustomShape"_ustr, o_xShape, o_xPropSet);
o_rIsCustomShape = true;
break;
}
// Defaults
if (o_xPropSet.is() && !m_bTextFrame)
{
o_xPropSet->setPropertyValue(
u"FillColor"_ustr,
uno::Any(sal_uInt32(0xffffff))); // White in Word, kind of blue in Writer.
o_xPropSet->setPropertyValue(u"VertOrient"_ustr, uno::Any(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(COL_BLACK);
// Default line width is 0.75 pt (26 mm100) in Word, 0 in Writer.
uno::Any aLineWidth(sal_Int32(26));
sal_Int16 eWritingMode = text::WritingMode2::LR_TB;
// Groupshape support
std::optional<sal_Int32> oGroupLeft;
std::optional<sal_Int32> oGroupTop;
std::optional<sal_Int32> oGroupRight;
std::optional<sal_Int32> oGroupBottom;
std::optional<sal_Int32> oRelLeft;
std::optional<sal_Int32> oRelTop;
std::optional<sal_Int32> oRelRight;
std::optional<sal_Int32> 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;
std::optional<sal_Int16> oRelativeWidth;
std::optional<sal_Int16> oRelativeHeight;
sal_Int16 nRelativeWidthRelation = text::RelOrientation::PAGE_FRAME;
sal_Int16 nRelativeHeightRelation = text::RelOrientation::PAGE_FRAME;
std::optional<bool> obRelFlipV;
bool obFlipH(false);
bool obFlipV(false);
OUString aShapeText = u""_ustr;
OUString aFontFamily = u""_ustr;
float nFontSize = 1.0;
sal_Int32 nContrast = 0x10000;
sal_Int16 nBrightness = 0;
bool bCustom(false);
int const nType = initShape(xShape, xPropertySet, bCustom, rShape, bClose, shapeOrPict);
if (nType == ESCHER_ShpInst_PictureFrame && xPropertySet.is())
{
xPropertySet->setPropertyValue(u"IsEmptyPresentationObject"_ustr, uno::Any(true));
}
for (auto& rProperty : rShape.getProperties())
{
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(u"Name"_ustr, uno::Any(rProperty.second));
}
else if (rProperty.first == "wzDescription")
xPropertySet->setPropertyValue(u"Description"_ustr, uno::Any(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(u"BackColor"_ustr, aAny);
else
xPropertySet->setPropertyValue(u"FillColor"_ustr, aAny);
// fillType will decide, possible it'll be the start color of a gradient.
aFillModel.moColor
= "#"
+ msfilter::util::ConvertColorOU(Color(ColorTransparency, aAny.get<sal_Int32>()));
}
else if (rProperty.first == "fillBackColor")
// fillType will decide, possible it'll be the end color of a gradient.
aFillModel.moColor2 = "#"
+ msfilter::util::ConvertColorOU(
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())
{
switch (rProperty.second.toInt32())
{
case 1: // Top to bottom ASCII font
case 3: // Top to bottom non-ASCII font
eWritingMode = text::WritingMode2::TB_RL;
break;
case 2: // Bottom to top non-ASCII font
eWritingMode = text::WritingMode2::BT_LR;
break;
}
}
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(u"FillTransparence"_ustr, 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
{
std::u16string_view aToken = o3tl::getToken(rProperty.second, 0, ';', nCharIndex);
if (!nSize)
nSize = o3tl::toInt32(aToken);
else if (!nCount)
nCount = o3tl::toInt32(aToken);
else if (!aToken.empty())
{
// The coordinates are in an (x,y) form.
aToken = aToken.substr(1, aToken.size() - 2);
sal_Int32 nI = 0;
sal_Int32 nX = o3tl::toInt32(o3tl::getToken(aToken, 0, ',', nI));
sal_Int32 nY
= (nI >= 0) ? o3tl::toInt32(o3tl::getToken(aToken, 0, ',', nI)) : 0;
drawing::EnhancedCustomShapeParameterPair aPair;
aPair.First.Value <<= nX;
aPair.Second.Value <<= nY;
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
= o3tl::toInt32(o3tl::getToken(rProperty.second, 0, ';', nCharIndex));
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.hasZ())
resolveDhgt(xPropertySet, rProperty.second.toInt32(), /*bOldStyle=*/false);
}
// These are in EMU, convert to mm100.
else if (rProperty.first == "dxTextLeft")
{
if (xPropertySet.is())
xPropertySet->setPropertyValue(u"LeftBorderDistance"_ustr,
uno::Any(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "dyTextTop")
{
if (xPropertySet.is())
xPropertySet->setPropertyValue(u"TopBorderDistance"_ustr,
uno::Any(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "dxTextRight")
{
if (xPropertySet.is())
xPropertySet->setPropertyValue(u"RightBorderDistance"_ustr,
uno::Any(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "dyTextBottom")
{
if (xPropertySet.is())
xPropertySet->setPropertyValue(u"BottomBorderDistance"_ustr,
uno::Any(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "dxWrapDistLeft")
{
if (m_bTextGraphicObject)
rShape.getAnchorAttributes().set(NS_ooxml::LN_CT_Anchor_distL,
new RTFValue(rProperty.second.toInt32()));
else if (xPropertySet.is())
xPropertySet->setPropertyValue(u"LeftMargin"_ustr,
uno::Any(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "dyWrapDistTop")
{
if (m_bTextGraphicObject)
rShape.getAnchorAttributes().set(NS_ooxml::LN_CT_Anchor_distT,
new RTFValue(rProperty.second.toInt32()));
else if (xPropertySet.is())
xPropertySet->setPropertyValue(u"TopMargin"_ustr,
uno::Any(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "dxWrapDistRight")
{
if (m_bTextGraphicObject)
rShape.getAnchorAttributes().set(NS_ooxml::LN_CT_Anchor_distR,
new RTFValue(rProperty.second.toInt32()));
else if (xPropertySet.is())
xPropertySet->setPropertyValue(u"RightMargin"_ustr,
uno::Any(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "dyWrapDistBottom")
{
if (m_bTextGraphicObject)
rShape.getAnchorAttributes().set(NS_ooxml::LN_CT_Anchor_distB,
new RTFValue(rProperty.second.toInt32()));
else if (xPropertySet.is())
xPropertySet->setPropertyValue(u"BottomMargin"_ustr,
uno::Any(rProperty.second.toInt32() / 360));
}
else if (rProperty.first == "fillType")
{
switch (rProperty.second.toInt32())
{
case 7: // Shade using the fillAngle
aFillModel.moType = oox::XML_gradient;
break;
default:
SAL_INFO("writerfilter",
"TODO handle fillType value '" << rProperty.second << "'");
break;
}
}
else if (rProperty.first == "fillAngle")
{
aFillModel.moAngle = rProperty.second.toInt32() / oox::drawingml::PER_DEGREE;
}
else if (rProperty.first == "fillFocus")
aFillModel.moFocus = 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 = "#"
+ msfilter::util::ConvertColorOU(
msfilter::util::BGRToRGB(rProperty.second.toInt32()));
else if (rProperty.first == "shadowOffsetX")
// EMUs to points
aShadowModel.moOffset = 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.setHoriOrientRelation(text::RelOrientation::PAGE_FRAME);
break;
default:
break;
}
}
else if (rProperty.first == "posrelv")
{
switch (rProperty.second.toInt32())
{
case 1:
rShape.setVertOrientRelation(text::RelOrientation::PAGE_FRAME);
break;
default:
break;
}
}
else if (rProperty.first == "groupLeft")
oGroupLeft = convertTwipToMm100(rProperty.second.toInt32());
else if (rProperty.first == "groupTop")
oGroupTop = convertTwipToMm100(rProperty.second.toInt32());
else if (rProperty.first == "groupRight")
oGroupRight = convertTwipToMm100(rProperty.second.toInt32());
else if (rProperty.first == "groupBottom")
oGroupBottom = convertTwipToMm100(rProperty.second.toInt32());
else if (rProperty.first == "relLeft")
oRelLeft = convertTwipToMm100(rProperty.second.toInt32());
else if (rProperty.first == "relTop")
oRelTop = convertTwipToMm100(rProperty.second.toInt32());
else if (rProperty.first == "relRight")
oRelRight = convertTwipToMm100(rProperty.second.toInt32());
else if (rProperty.first == "relBottom")
oRelBottom = 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)
{
std::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;
if (xPropertySet.is())
{
sal_Int16 const nVertOrient = text::VertOrientation::CENTER;
xPropertySet->setPropertyValue(u"VertOrient"_ustr, uno::Any(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.setBottom(rShape.getTop() + nHeight);
}
else if (rProperty.first == "dxWidthHR")
{
// horizontal rule width
sal_uInt32 const nWidth(convertTwipToMm100(rProperty.second.toInt32()));
rShape.setRight(rShape.getLeft() + 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(u"HoriOrient"_ustr, uno::Any(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
{
std::u16string_view aToken = o3tl::getToken(rProperty.second, 0, ';', nCharIndex);
if (!nSize)
nSize = o3tl::toInt32(aToken);
else if (!nCount)
nCount = o3tl::toInt32(aToken);
else if (!aToken.empty())
{
// The coordinates are in an (x,y) form.
aToken = aToken.substr(1, aToken.size() - 2);
sal_Int32 nI = 0;
sal_Int32 nX = o3tl::toInt32(o3tl::getToken(aToken, 0, ',', nI));
sal_Int32 nY
= (nI >= 0) ? o3tl::toInt32(o3tl::getToken(aToken, 0, ',', nI)) : 0;
RTFSprms aPathAttributes;
aPathAttributes.set(NS_ooxml::LN_CT_Point2D_x, new RTFValue(nX));
aPathAttributes.set(NS_ooxml::LN_CT_Point2D_y, new RTFValue(nY));
aPolygonSprms.set(NS_ooxml::LN_CT_WrapPath_lineTo,
new RTFValue(aPathAttributes), RTFOverwrite::NO_APPEND);
}
} while (nCharIndex >= 0);
rShape.getWrapPolygonSprms() = std::move(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 if (rProperty.first == "pictureContrast")
{
// Gain / contrast.
nContrast = rProperty.second.toInt32();
if (nContrast < 0x10000)
{
nContrast *= 101; // 100 + 1 to round
nContrast /= 0x10000;
nContrast -= 100;
}
}
else if (rProperty.first == "pictureBrightness")
{
// Blacklevel / brightness.
nBrightness = rProperty.second.toInt32();
if (nBrightness != 0)
{
nBrightness /= 327;
}
}
else
SAL_INFO("writerfilter", "TODO handle shape property '" << rProperty.first << "':'"
<< rProperty.second << "'");
}
if (xPropertySet.is())
{
resolveLineColorAndWidth(m_bTextFrame, xPropertySet, aLineColor, aLineWidth);
if (rShape.hasZ())
{
bool bOldStyle = m_aParents.size() > 1;
resolveDhgt(xPropertySet, rShape.getZ(), bOldStyle);
}
if (m_bTextFrame)
xPropertySet->setPropertyValue(u"WritingMode"_ustr, uno::Any(eWritingMode));
else
// Only Writer textframes implement text::WritingMode2.
xPropertySet->setPropertyValue(u"TextWritingMode"_ustr,
uno::Any(text::WritingMode(eWritingMode)));
}
if (!m_aParents.empty() && m_aParents.top().is() && !m_bTextFrame)
m_aParents.top()->add(xShape);
if (nContrast == -70 && nBrightness == 70 && xPropertySet.is())
{
// Map MSO 'washout' to our watermark colormode.
xPropertySet->setPropertyValue(u"GraphicColorMode"_ustr,
uno::Any(drawing::ColorMode_WATERMARK));
}
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(u"CharFontName"_ustr, uno::Any(aFontFamily));
xPropertySet->setPropertyValue(u"CharHeight"_ustr, uno::Any(nFontSize));
}
// Creating CustomShapeGeometry property
if (bCustom && xPropertySet.is())
{
bool bChanged = false;
comphelper::SequenceAsHashMap aCustomShapeGeometry(
xPropertySet->getPropertyValue(u"CustomShapeGeometry"_ustr));
if (aViewBox.X || aViewBox.Y || aViewBox.Width || aViewBox.Height)
{
aViewBox.Width -= aViewBox.X;
aViewBox.Height -= aViewBox.Y;
aCustomShapeGeometry[u"ViewBox"_ustr] <<= aViewBox;
bChanged = true;
}
if (!aPath.empty())
{
aCustomShapeGeometry[u"Path"_ustr] <<= comphelper::containerToSequence(aPath);
bChanged = true;
}
if (!aShapeText.isEmpty())
{
uno::Sequence<beans::PropertyValue> aSequence(comphelper::InitPropertySequence({
{ "TextPath", uno::Any(true) },
}));
aCustomShapeGeometry[u"TextPath"_ustr] <<= aSequence;
xPropertySet->setPropertyValue(u"TextAutoGrowHeight"_ustr, uno::Any(false));
xPropertySet->setPropertyValue(u"TextAutoGrowWidth"_ustr, uno::Any(false));
bChanged = true;
}
if (bChanged)
{
xPropertySet->setPropertyValue(
u"CustomShapeGeometry"_ustr,
uno::Any(aCustomShapeGeometry.getAsConstPropertyValueList()));
}
}
if (obRelFlipV.has_value() && 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(u"PolyPolygon"_ustr) >>= aPolyPolySequence)
&& aPolyPolySequence.hasElements())
{
uno::Sequence<awt::Point>& rPolygon = aPolyPolySequence.getArray()[0];
basegfx::B2DPolygon aPoly;
for (const awt::Point& rPoint : rPolygon)
{
aPoly.append(basegfx::B2DPoint(rPoint.X, rPoint.Y));
}
basegfx::B2DHomMatrix aTransformation;
aTransformation.scale(1.0, *obRelFlipV ? -1.0 : 1.0);
aPoly.transform(aTransformation);
auto pPolygon = rPolygon.getArray();
for (sal_Int32 i = 0; i < rPolygon.getLength(); ++i)
{
basegfx::B2DPoint aPoint(aPoly.getB2DPoint(i));
pPolygon[i] = awt::Point(static_cast<sal_Int32>(aPoint.getX()),
static_cast<sal_Int32>(aPoint.getY()));
}
xPropertySet->setPropertyValue(u"PolyPolygon"_ustr, uno::Any(aPolyPolySequence));
}
}
}
// Set position and size
if (xShape.is())
{
sal_Int32 nLeft = rShape.getLeft();
sal_Int32 nTop = rShape.getTop();
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.getRight() - rShape.getLeft();
sal_Int32 nShapeHeight = rShape.getBottom() - rShape.getTop();
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.getLeft()
+ fWidthRatio * (*oRelLeft - *oGroupLeft));
nTop = static_cast<sal_Int32>(rShape.getTop() + 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(u"HoriOrientPosition"_ustr, uno::Any(nLeft));
xPropertySet->setPropertyValue(u"VertOrientPosition"_ustr, uno::Any(nTop));
}
else
xShape->setPosition(awt::Point(nLeft, nTop));
if (bInShapeGroup)
xShape->setSize(aSize);
else
xShape->setSize(awt::Size(rShape.getRight() - rShape.getLeft(),
rShape.getBottom() - rShape.getTop()));
if (obFlipH || obFlipV)
{
if (bCustom)
{
// This has to be set after position and size is set, otherwise flip will affect the position.
comphelper::SequenceAsHashMap aCustomShapeGeometry(
xPropertySet->getPropertyValue(u"CustomShapeGeometry"_ustr));
if (obFlipH)
aCustomShapeGeometry[u"MirroredX"_ustr] <<= true;
if (obFlipV)
aCustomShapeGeometry[u"MirroredY"_ustr] <<= true;
xPropertySet->setPropertyValue(
u"CustomShapeGeometry"_ustr,
uno::Any(aCustomShapeGeometry.getAsConstPropertyValueList()));
}
else if (SdrObject* pObject = SdrObject::getSdrObjectFromXShape(xShape))
{
Point aRef1 = pObject->GetSnapRect().Center();
Point aRef2(aRef1);
if (obFlipH)
{
// Horizontal mirror means a vertical reference line.
aRef2.AdjustY(1);
}
if (obFlipV)
{
// Vertical mirror means a horizontal reference line.
aRef2.AdjustX(1);
}
pObject->Mirror(aRef1, aRef2);
}
}
if (rShape.getHoriOrientRelation() != 0)
xPropertySet->setPropertyValue(u"HoriOrientRelation"_ustr,
uno::Any(rShape.getHoriOrientRelation()));
if (rShape.getVertOrientRelation() != 0)
xPropertySet->setPropertyValue(u"VertOrientRelation"_ustr,
uno::Any(rShape.getVertOrientRelation()));
if (rShape.getWrap() != text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE)
xPropertySet->setPropertyValue(u"Surround"_ustr, uno::Any(rShape.getWrap()));
oox::ModelObjectHelper aModelObjectHelper(m_rImport.getTextDocument());
if (aFillModel.moType.has_value())
{
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(u"AnchorType"_ustr,
uno::Any(text::TextContentAnchorType_AT_CHARACTER));
xPropertySet->setPropertyValue(u"Opaque"_ustr, uno::Any(bOpaque));
if (oRelativeWidth)
{
xPropertySet->setPropertyValue(u"RelativeWidth"_ustr, uno::Any(*oRelativeWidth));
xPropertySet->setPropertyValue(u"RelativeWidthRelation"_ustr,
uno::Any(nRelativeWidthRelation));
}
if (oRelativeHeight)
{
xPropertySet->setPropertyValue(u"RelativeHeight"_ustr, uno::Any(*oRelativeHeight));
xPropertySet->setPropertyValue(u"RelativeHeightRelation"_ustr,
uno::Any(nRelativeHeightRelation));
}
}
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 && xPropertySet.is())
xPropertySet->setPropertyValue(u"IsMirrored"_ustr, uno::Any(true));
return;
}
// Send it to dmapper
if (xShape.is())
{
m_rImport.Mapper().startShape(xShape);
if (bClose)
{
m_rImport.Mapper().endShape();
}
}
if (m_rImport.isInBackground())
{
RTFSprms aSprms;
aSprms.set(NS_ooxml::LN_background_background, new RTFValue()); // action="end"
m_rImport.Mapper().props(new RTFReferenceProperties(RTFSprms(), std::move(aSprms)));
}
// If the shape has an inner shape, the inner object's properties should not be influenced by
// the outer one.
rShape.getProperties().clear();
m_xShape = std::move(xShape);
}
void RTFSdrImport::close() { m_rImport.Mapper().endShape(); }
void RTFSdrImport::append(std::u16string_view aKey, std::u16string_view aValue)
{
applyProperty(m_xShape, aKey, aValue);
}
void RTFSdrImport::appendGroupProperty(std::u16string_view aKey, std::u16string_view 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 writerfilter
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V601 The 'rProperty.second.toInt32() / 360' value is implicitly cast to the bool type. Inspect the first argument.
↑ V601 The 'rProperty.second.toInt32() / 360' value is implicitly cast to the bool type. Inspect the first argument.
↑ V601 The 'rProperty.second.toInt32() / 360' value is implicitly cast to the bool type. Inspect the first argument.
↑ V601 The 'rProperty.second.toInt32() / 360' value is implicitly cast to the bool type. Inspect the first argument.
↑ V601 The 'rProperty.second.toInt32() / 360' value is implicitly cast to the bool type. Inspect the first argument.
↑ V601 The 'rProperty.second.toInt32() / 360' value is implicitly cast to the bool type. Inspect the first argument.
↑ V601 The 'rProperty.second.toInt32() / 360' value is implicitly cast to the bool type. Inspect the first argument.
↑ V601 The 'rProperty.second.toInt32() / 360' value is implicitly cast to the bool type. Inspect the first argument.