diff options
Diffstat (limited to 'writerfilter/source/rtftok/rtfsdrimport.cxx')
-rw-r--r-- | writerfilter/source/rtftok/rtfsdrimport.cxx | 1182 |
1 files changed, 1182 insertions, 0 deletions
diff --git a/writerfilter/source/rtftok/rtfsdrimport.cxx b/writerfilter/source/rtftok/rtfsdrimport.cxx new file mode 100644 index 000000000..e928377f9 --- /dev/null +++ b/writerfilter/source/rtftok/rtfsdrimport.cxx @@ -0,0 +1,1182 @@ +/* -*- 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/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" + +using namespace com::sun::star; + +namespace writerfilter::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::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("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::Any(aBorderLine)); + } + } +} + +void RTFSdrImport::resolveFLine(uno::Reference<beans::XPropertySet> const& xPropertySet, + sal_Int32 const nFLine) +{ + if (nFLine == 0) + xPropertySet->setPropertyValue("LineStyle", uno::Any(drawing::LineStyle_NONE)); + else + xPropertySet->setPropertyValue("LineStyle", 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("com.sun.star.text.TextFrame")) + xPropertySet->setPropertyValue( + "RotateAngle", uno::Any(NormAngle36000(Degree100(nRotation * -1)).get())); + } + + if (nHoriOrient != 0 && xPropertySet.is()) + xPropertySet->setPropertyValue("HoriOrient", uno::Any(nHoriOrient)); + if (nVertOrient != 0 && xPropertySet.is()) + xPropertySet->setPropertyValue("VertOrient", uno::Any(nVertOrient)); + if (obFitShapeToText.has_value() && xPropertySet.is()) + { + xPropertySet->setPropertyValue( + "SizeType", uno::Any(*obFitShapeToText ? text::SizeType::MIN : text::SizeType::FIX)); + xPropertySet->setPropertyValue("FrameIsAutomaticHeight", uno::Any(*obFitShapeToText)); + } + if (!bFilled && xPropertySet.is()) + { + if (m_bTextFrame) + xPropertySet->setPropertyValue("BackColorTransparency", uno::Any(sal_Int32(100))); + else + xPropertySet->setPropertyValue("FillStyle", 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("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 (const beans::PropertyValue& i : aDefaults) + o_xPropSet->setPropertyValue(i.Name, i.Value); + break; + } + [[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::Any(sal_uInt32(0xffffff))); // White in Word, kind of blue in Writer. + o_xPropSet->setPropertyValue("VertOrient", 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 = ""; + OUString aFontFamily = ""; + 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); + + 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("Name", uno::Any(rProperty.second)); + } + else if (rProperty.first == "wzDescription") + xPropertySet->setPropertyValue("Description", 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("BackColor", aAny); + else + xPropertySet->setPropertyValue("FillColor", aAny); + + // fillType will decide, possible it'll be the start color of a gradient. + aFillModel.moColor.set( + "#" + + 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.set("#" + + 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("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 + { + 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("LeftBorderDistance", + uno::Any(rProperty.second.toInt32() / 360)); + } + else if (rProperty.first == "dyTextTop") + { + if (xPropertySet.is()) + xPropertySet->setPropertyValue("TopBorderDistance", + uno::Any(rProperty.second.toInt32() / 360)); + } + else if (rProperty.first == "dxTextRight") + { + if (xPropertySet.is()) + xPropertySet->setPropertyValue("RightBorderDistance", + uno::Any(rProperty.second.toInt32() / 360)); + } + else if (rProperty.first == "dyTextBottom") + { + if (xPropertySet.is()) + xPropertySet->setPropertyValue("BottomBorderDistance", + 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("LeftMargin", + 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("TopMargin", + 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("RightMargin", + 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("BottomMargin", + uno::Any(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("#" + + msfilter::util::ConvertColorOU( + 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.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("VertOrient", 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("HoriOrient", 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() = 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("WritingMode", uno::Any(eWritingMode)); + else + // Only Writer textframes implement text::WritingMode2. + xPropertySet->setPropertyValue("TextWritingMode", + 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("GraphicColorMode", 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("CharFontName", uno::Any(aFontFamily)); + xPropertySet->setPropertyValue("CharHeight", uno::Any(nFontSize)); + } + + // Creating CustomShapeGeometry property + if (bCustom && xPropertySet.is()) + { + bool bChanged = false; + comphelper::SequenceAsHashMap aCustomShapeGeometry( + xPropertySet->getPropertyValue("CustomShapeGeometry")); + + if (aViewBox.X || aViewBox.Y || aViewBox.Width || aViewBox.Height) + { + aViewBox.Width -= aViewBox.X; + aViewBox.Height -= aViewBox.Y; + aCustomShapeGeometry["ViewBox"] <<= aViewBox; + bChanged = true; + } + + if (!aPath.empty()) + { + aCustomShapeGeometry["Path"] <<= comphelper::containerToSequence(aPath); + bChanged = true; + } + + if (!aShapeText.isEmpty()) + { + uno::Sequence<beans::PropertyValue> aSequence(comphelper::InitPropertySequence({ + { "TextPath", uno::Any(true) }, + })); + aCustomShapeGeometry["TextPath"] <<= aSequence; + xPropertySet->setPropertyValue("TextAutoGrowHeight", uno::Any(false)); + xPropertySet->setPropertyValue("TextAutoGrowWidth", uno::Any(false)); + bChanged = true; + } + + if (bChanged) + { + xPropertySet->setPropertyValue( + "CustomShapeGeometry", + 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("PolyPolygon") >>= aPolyPolySequence) + && aPolyPolySequence.hasElements()) + { + uno::Sequence<awt::Point>& rPolygon = aPolyPolySequence.getArray()[0]; + basegfx::B2DPolygon aPoly; + for (const awt::Point& rPoint : std::as_const(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("PolyPolygon", 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("HoriOrientPosition", uno::Any(nLeft)); + xPropertySet->setPropertyValue("VertOrientPosition", 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("CustomShapeGeometry")); + if (obFlipH) + aCustomShapeGeometry["MirroredX"] <<= true; + if (obFlipV) + aCustomShapeGeometry["MirroredY"] <<= true; + xPropertySet->setPropertyValue( + "CustomShapeGeometry", + 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("HoriOrientRelation", + uno::Any(rShape.getHoriOrientRelation())); + if (rShape.getVertOrientRelation() != 0) + xPropertySet->setPropertyValue("VertOrientRelation", + uno::Any(rShape.getVertOrientRelation())); + if (rShape.getWrap() != text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE) + xPropertySet->setPropertyValue("Surround", uno::Any(rShape.getWrap())); + 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::Any(text::TextContentAnchorType_AT_CHARACTER)); + xPropertySet->setPropertyValue("Opaque", uno::Any(bOpaque)); + if (oRelativeWidth) + { + xPropertySet->setPropertyValue("RelativeWidth", uno::Any(*oRelativeWidth)); + xPropertySet->setPropertyValue("RelativeWidthRelation", + uno::Any(nRelativeWidthRelation)); + } + if (oRelativeHeight) + { + xPropertySet->setPropertyValue("RelativeHeight", uno::Any(*oRelativeHeight)); + xPropertySet->setPropertyValue("RelativeHeightRelation", + 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("IsMirrored", uno::Any(true)); + return; + } + + 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(std::move(aAttributes))); + + uno::Reference<lang::XComponent> xComponent(xShape, uno::UNO_QUERY); + xComponent->dispose(); + return; + } + + // Send it to dmapper + if (xShape.is()) + { + m_rImport.Mapper().startShape(xShape); + if (bClose) + { + m_rImport.Mapper().endShape(); + } + } + + // 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 = 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: */ |