Use o3tl::convert in Math
[LibreOffice.git] / writerfilter / source / rtftok / rtfsdrimport.cxx
blob3426c9213f4f4b3334ee2d47946b7032d1098766
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include "rtfsdrimport.hxx"
11 #include <cmath>
12 #include <optional>
13 #include <com/sun/star/container/XNamed.hpp>
14 #include <com/sun/star/drawing/FillStyle.hpp>
15 #include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
16 #include <com/sun/star/drawing/XEnhancedCustomShapeDefaulter.hpp>
17 #include <com/sun/star/drawing/XDrawPageSupplier.hpp>
18 #include <com/sun/star/drawing/LineStyle.hpp>
19 #include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp>
20 #include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp>
21 #include <com/sun/star/drawing/ColorMode.hpp>
22 #include <com/sun/star/lang/XServiceInfo.hpp>
23 #include <com/sun/star/table/BorderLine2.hpp>
24 #include <com/sun/star/text/HoriOrientation.hpp>
25 #include <com/sun/star/text/RelOrientation.hpp>
26 #include <com/sun/star/text/SizeType.hpp>
27 #include <com/sun/star/text/VertOrientation.hpp>
28 #include <com/sun/star/text/WrapTextMode.hpp>
29 #include <com/sun/star/text/WritingMode.hpp>
30 #include <com/sun/star/text/WritingMode2.hpp>
31 #include <com/sun/star/text/TextContentAnchorType.hpp>
32 #include <com/sun/star/text/XTextRange.hpp>
33 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
34 #include <ooxml/resourceids.hxx>
35 #include <filter/msfilter/escherex.hxx>
36 #include <filter/msfilter/util.hxx>
37 #include <filter/msfilter/rtfutil.hxx>
38 #include <sal/log.hxx>
39 #include <svx/svdtrans.hxx>
40 #include <comphelper/sequence.hxx>
41 #include <comphelper/propertysequence.hxx>
42 #include "rtfreferenceproperties.hxx"
43 #include <oox/vml/vmlformatting.hxx>
44 #include <oox/helper/modelobjecthelper.hxx>
45 #include <oox/drawingml/shapepropertymap.hxx>
46 #include <oox/helper/propertyset.hxx>
47 #include <basegfx/matrix/b2dhommatrix.hxx>
48 #include <svx/svdobj.hxx>
49 #include <tools/UnitConversion.hxx>
51 #include <dmapper/GraphicZOrderHelper.hxx>
52 #include "rtfdocumentimpl.hxx"
54 using namespace com::sun::star;
56 namespace writerfilter::rtftok
58 RTFSdrImport::RTFSdrImport(RTFDocumentImpl& rDocument,
59 uno::Reference<lang::XComponent> const& xDstDoc)
60 : m_rImport(rDocument)
61 , m_bTextFrame(false)
62 , m_bTextGraphicObject(false)
63 , m_bFakePict(false)
65 uno::Reference<drawing::XDrawPageSupplier> xDrawings(xDstDoc, uno::UNO_QUERY);
66 if (xDrawings.is())
67 m_aParents.push(xDrawings->getDrawPage());
68 m_aGraphicZOrderHelpers.push(writerfilter::dmapper::GraphicZOrderHelper());
71 RTFSdrImport::~RTFSdrImport()
73 if (!m_aGraphicZOrderHelpers.empty())
74 m_aGraphicZOrderHelpers.pop();
75 if (!m_aParents.empty())
76 m_aParents.pop();
79 void RTFSdrImport::createShape(const OUString& rService, uno::Reference<drawing::XShape>& xShape,
80 uno::Reference<beans::XPropertySet>& xPropertySet)
82 if (m_rImport.getModelFactory().is())
83 xShape.set(m_rImport.getModelFactory()->createInstance(rService), uno::UNO_QUERY);
84 xPropertySet.set(xShape, uno::UNO_QUERY);
87 std::vector<beans::PropertyValue> RTFSdrImport::getTextFrameDefaults(bool bNew)
89 std::vector<beans::PropertyValue> aRet;
90 beans::PropertyValue aPropertyValue;
92 aPropertyValue.Name = "HoriOrient";
93 aPropertyValue.Value <<= text::HoriOrientation::NONE;
94 aRet.push_back(aPropertyValue);
95 aPropertyValue.Name = "HoriOrientRelation";
96 aPropertyValue.Value <<= text::RelOrientation::FRAME;
97 aRet.push_back(aPropertyValue);
98 aPropertyValue.Name = "VertOrient";
99 aPropertyValue.Value <<= text::VertOrientation::NONE;
100 aRet.push_back(aPropertyValue);
101 aPropertyValue.Name = "VertOrientRelation";
102 aPropertyValue.Value <<= text::RelOrientation::FRAME;
103 aRet.push_back(aPropertyValue);
104 if (!bNew)
106 aPropertyValue.Name = "BackColorTransparency";
107 aPropertyValue.Value <<= sal_Int32(100);
108 aRet.push_back(aPropertyValue);
110 // See the spec, new-style frame default margins are specified in EMUs.
111 aPropertyValue.Name = "LeftBorderDistance";
112 aPropertyValue.Value <<= sal_Int32(bNew ? (91440 / 360) : 0);
113 aRet.push_back(aPropertyValue);
114 aPropertyValue.Name = "RightBorderDistance";
115 aPropertyValue.Value <<= sal_Int32(bNew ? (91440 / 360) : 0);
116 aRet.push_back(aPropertyValue);
117 aPropertyValue.Name = "TopBorderDistance";
118 aPropertyValue.Value <<= sal_Int32(bNew ? (45720 / 360) : 0);
119 aRet.push_back(aPropertyValue);
120 aPropertyValue.Name = "BottomBorderDistance";
121 aPropertyValue.Value <<= sal_Int32(bNew ? (45720 / 360) : 0);
122 aRet.push_back(aPropertyValue);
123 aPropertyValue.Name = "SizeType";
124 aPropertyValue.Value <<= text::SizeType::FIX;
125 aRet.push_back(aPropertyValue);
126 return aRet;
129 void RTFSdrImport::pushParent(uno::Reference<drawing::XShapes> const& xParent)
131 m_aParents.push(xParent);
132 m_aGraphicZOrderHelpers.push(writerfilter::dmapper::GraphicZOrderHelper());
135 void RTFSdrImport::popParent()
137 if (!m_aGraphicZOrderHelpers.empty())
138 m_aGraphicZOrderHelpers.pop();
139 if (!m_aParents.empty())
140 m_aParents.pop();
143 void RTFSdrImport::resolveDhgt(uno::Reference<beans::XPropertySet> const& xPropertySet,
144 sal_Int32 const nZOrder, bool const bOldStyle)
146 if (!m_aGraphicZOrderHelpers.empty())
148 writerfilter::dmapper::GraphicZOrderHelper& rHelper = m_aGraphicZOrderHelpers.top();
149 xPropertySet->setPropertyValue("ZOrder",
150 uno::makeAny(rHelper.findZOrder(nZOrder, bOldStyle)));
151 rHelper.addItem(xPropertySet, nZOrder);
155 void RTFSdrImport::resolveLineColorAndWidth(bool bTextFrame,
156 const uno::Reference<beans::XPropertySet>& xPropertySet,
157 uno::Any const& rLineColor, uno::Any const& rLineWidth)
159 if (!bTextFrame)
161 xPropertySet->setPropertyValue("LineColor", rLineColor);
162 xPropertySet->setPropertyValue("LineWidth", rLineWidth);
164 else
166 static const char* aBorders[]
167 = { "TopBorder", "LeftBorder", "BottomBorder", "RightBorder" };
168 for (const char* pBorder : aBorders)
170 auto aBorderLine = xPropertySet->getPropertyValue(OUString::createFromAscii(pBorder))
171 .get<table::BorderLine2>();
172 if (rLineColor.hasValue())
173 aBorderLine.Color = rLineColor.get<sal_Int32>();
174 if (rLineWidth.hasValue())
175 aBorderLine.LineWidth = rLineWidth.get<sal_Int32>();
176 xPropertySet->setPropertyValue(OUString::createFromAscii(pBorder),
177 uno::makeAny(aBorderLine));
182 void RTFSdrImport::resolveFLine(uno::Reference<beans::XPropertySet> const& xPropertySet,
183 sal_Int32 const nFLine)
185 if (nFLine == 0)
186 xPropertySet->setPropertyValue("LineStyle", uno::makeAny(drawing::LineStyle_NONE));
187 else
188 xPropertySet->setPropertyValue("LineStyle", uno::makeAny(drawing::LineStyle_SOLID));
191 void RTFSdrImport::applyProperty(uno::Reference<drawing::XShape> const& xShape,
192 std::u16string_view aKey, const OUString& aValue) const
194 uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
195 sal_Int16 nHoriOrient = 0;
196 sal_Int16 nVertOrient = 0;
197 std::optional<bool> obFitShapeToText;
198 bool bFilled = true;
200 if (aKey == u"posh")
202 switch (aValue.toInt32())
204 case 1:
205 nHoriOrient = text::HoriOrientation::LEFT;
206 break;
207 case 2:
208 nHoriOrient = text::HoriOrientation::CENTER;
209 break;
210 case 3:
211 nHoriOrient = text::HoriOrientation::RIGHT;
212 break;
213 case 4:
214 nHoriOrient = text::HoriOrientation::INSIDE;
215 break;
216 case 5:
217 nHoriOrient = text::HoriOrientation::OUTSIDE;
218 break;
219 default:
220 break;
223 else if (aKey == u"posv")
225 switch (aValue.toInt32())
227 case 1:
228 nVertOrient = text::VertOrientation::TOP;
229 break;
230 case 2:
231 nVertOrient = text::VertOrientation::CENTER;
232 break;
233 case 3:
234 nVertOrient = text::VertOrientation::BOTTOM;
235 break;
236 default:
237 break;
240 else if (aKey == u"fFitShapeToText")
241 obFitShapeToText = aValue.toInt32() == 1;
242 else if (aKey == u"fFilled")
243 bFilled = aValue.toInt32() == 1;
244 else if (aKey == u"rotation")
246 // See DffPropertyReader::Fix16ToAngle(): in RTF, positive rotation angles are clockwise, we have them as counter-clockwise.
247 // Additionally, RTF type is 0..360*2^16, our is 0..360*100.
248 sal_Int32 nRotation = aValue.toInt32() * 100 / RTF_MULTIPLIER;
249 uno::Reference<lang::XServiceInfo> xServiceInfo(xShape, uno::UNO_QUERY);
250 if (!xServiceInfo->supportsService("com.sun.star.text.TextFrame"))
251 xPropertySet->setPropertyValue(
252 "RotateAngle", uno::makeAny(NormAngle36000(Degree100(nRotation * -1)).get()));
255 if (nHoriOrient != 0 && xPropertySet.is())
256 xPropertySet->setPropertyValue("HoriOrient", uno::makeAny(nHoriOrient));
257 if (nVertOrient != 0 && xPropertySet.is())
258 xPropertySet->setPropertyValue("VertOrient", uno::makeAny(nVertOrient));
259 if (obFitShapeToText.has_value() && xPropertySet.is())
261 xPropertySet->setPropertyValue(
262 "SizeType",
263 uno::makeAny(*obFitShapeToText ? text::SizeType::MIN : text::SizeType::FIX));
264 xPropertySet->setPropertyValue("FrameIsAutomaticHeight", uno::makeAny(*obFitShapeToText));
266 if (!bFilled && xPropertySet.is())
268 if (m_bTextFrame)
269 xPropertySet->setPropertyValue("BackColorTransparency", uno::makeAny(sal_Int32(100)));
270 else
271 xPropertySet->setPropertyValue("FillStyle", uno::makeAny(drawing::FillStyle_NONE));
275 int RTFSdrImport::initShape(uno::Reference<drawing::XShape>& o_xShape,
276 uno::Reference<beans::XPropertySet>& o_xPropSet, bool& o_rIsCustomShape,
277 RTFShape const& rShape, bool const bClose,
278 ShapeOrPict const shapeOrPict)
280 assert(!o_xShape.is());
281 assert(!o_xPropSet.is());
282 o_rIsCustomShape = false;
283 m_bFakePict = false;
285 // first, find the shape type
286 int nType = -1;
287 auto iter = std::find_if(rShape.getProperties().begin(), rShape.getProperties().end(),
288 [](const std::pair<OUString, OUString>& rProperty) {
289 return rProperty.first == "shapeType";
292 if (iter == rShape.getProperties().end())
294 if (SHAPE == shapeOrPict)
296 // The spec doesn't state what is the default for shapeType,
297 // Word seems to implement it as a rectangle.
298 nType = ESCHER_ShpInst_Rectangle;
300 else
302 // pict is picture by default but can be a rectangle too fdo#79319
303 nType = ESCHER_ShpInst_PictureFrame;
306 else
308 nType = iter->second.toInt32();
309 if (PICT == shapeOrPict && ESCHER_ShpInst_PictureFrame != nType)
311 m_bFakePict = true;
315 switch (nType)
317 case ESCHER_ShpInst_PictureFrame:
318 createShape("com.sun.star.drawing.GraphicObjectShape", o_xShape, o_xPropSet);
319 m_bTextGraphicObject = true;
320 break;
321 case ESCHER_ShpInst_Line:
322 createShape("com.sun.star.drawing.LineShape", o_xShape, o_xPropSet);
323 break;
324 case ESCHER_ShpInst_Rectangle:
325 case ESCHER_ShpInst_TextBox:
326 // If we're inside a groupshape, can't use text frames.
327 if (!bClose && m_aParents.size() == 1)
329 createShape("com.sun.star.text.TextFrame", o_xShape, o_xPropSet);
330 m_bTextFrame = true;
331 std::vector<beans::PropertyValue> aDefaults = getTextFrameDefaults(true);
332 for (const beans::PropertyValue& i : aDefaults)
333 o_xPropSet->setPropertyValue(i.Name, i.Value);
334 break;
336 [[fallthrough]];
337 default:
338 createShape("com.sun.star.drawing.CustomShape", o_xShape, o_xPropSet);
339 o_rIsCustomShape = true;
340 break;
343 // Defaults
344 if (o_xPropSet.is() && !m_bTextFrame)
346 o_xPropSet->setPropertyValue(
347 "FillColor",
348 uno::makeAny(sal_uInt32(0xffffff))); // White in Word, kind of blue in Writer.
349 o_xPropSet->setPropertyValue("VertOrient", uno::makeAny(text::VertOrientation::NONE));
352 return nType;
355 void RTFSdrImport::resolve(RTFShape& rShape, bool bClose, ShapeOrPict const shapeOrPict)
357 bool bPib = false;
358 m_bTextFrame = false;
359 m_bTextGraphicObject = false;
361 uno::Reference<drawing::XShape> xShape;
362 uno::Reference<beans::XPropertySet> xPropertySet;
363 uno::Any aAny;
364 beans::PropertyValue aPropertyValue;
365 awt::Rectangle aViewBox;
366 std::vector<beans::PropertyValue> aPath;
367 // Default line color is black in Word, blue in Writer.
368 uno::Any aLineColor = uno::makeAny(COL_BLACK);
369 // Default line width is 0.75 pt (26 mm100) in Word, 0 in Writer.
370 uno::Any aLineWidth = uno::makeAny(sal_Int32(26));
371 sal_Int16 eWritingMode = text::WritingMode2::LR_TB;
372 // Groupshape support
373 std::optional<sal_Int32> oGroupLeft;
374 std::optional<sal_Int32> oGroupTop;
375 std::optional<sal_Int32> oGroupRight;
376 std::optional<sal_Int32> oGroupBottom;
377 std::optional<sal_Int32> oRelLeft;
378 std::optional<sal_Int32> oRelTop;
379 std::optional<sal_Int32> oRelRight;
380 std::optional<sal_Int32> oRelBottom;
382 // Importing these are not trivial, let the VML import do the hard work.
383 oox::vml::FillModel aFillModel; // Gradient.
384 oox::vml::ShadowModel aShadowModel; // Shadow.
386 bool bOpaque = true;
388 std::optional<sal_Int16> oRelativeWidth;
389 std::optional<sal_Int16> oRelativeHeight;
390 sal_Int16 nRelativeWidthRelation = text::RelOrientation::PAGE_FRAME;
391 sal_Int16 nRelativeHeightRelation = text::RelOrientation::PAGE_FRAME;
392 std::optional<bool> obRelFlipV;
393 bool obFlipH(false);
394 bool obFlipV(false);
396 OUString aShapeText = "";
397 OUString aFontFamily = "";
398 float nFontSize = 1.0;
400 sal_Int32 nContrast = 0x10000;
401 sal_Int16 nBrightness = 0;
403 bool bCustom(false);
404 int const nType = initShape(xShape, xPropertySet, bCustom, rShape, bClose, shapeOrPict);
406 for (auto& rProperty : rShape.getProperties())
408 if (rProperty.first == "shapeType")
410 continue; // ignore: already handled by initShape
412 if (rProperty.first == "wzName")
414 if (m_bTextFrame)
416 uno::Reference<container::XNamed> xNamed(xShape, uno::UNO_QUERY);
417 xNamed->setName(rProperty.second);
419 else
420 xPropertySet->setPropertyValue("Name", uno::makeAny(rProperty.second));
422 else if (rProperty.first == "wzDescription")
423 xPropertySet->setPropertyValue("Description", uno::makeAny(rProperty.second));
424 else if (rProperty.first == "gtextUNICODE")
425 aShapeText = rProperty.second;
426 else if (rProperty.first == "gtextFont")
427 aFontFamily = rProperty.second;
428 else if (rProperty.first == "gtextSize")
430 // RTF size is multiplied by 2^16
431 nFontSize = static_cast<float>(rProperty.second.toUInt32()) / RTF_MULTIPLIER;
433 else if (rProperty.first == "pib")
435 m_rImport.setDestinationText(rProperty.second);
436 bPib = true;
438 else if (rProperty.first == "fillColor" && xPropertySet.is())
440 aAny <<= msfilter::util::BGRToRGB(rProperty.second.toUInt32());
441 if (m_bTextFrame)
442 xPropertySet->setPropertyValue("BackColor", aAny);
443 else
444 xPropertySet->setPropertyValue("FillColor", aAny);
446 // fillType will decide, possible it'll be the start color of a gradient.
447 aFillModel.moColor.set(
449 + msfilter::util::ConvertColorOU(Color(ColorTransparency, aAny.get<sal_Int32>())));
451 else if (rProperty.first == "fillBackColor")
452 // fillType will decide, possible it'll be the end color of a gradient.
453 aFillModel.moColor2.set("#"
454 + msfilter::util::ConvertColorOU(
455 msfilter::util::BGRToRGB(rProperty.second.toInt32())));
456 else if (rProperty.first == "lineColor")
457 aLineColor <<= msfilter::util::BGRToRGB(rProperty.second.toInt32());
458 else if (rProperty.first == "lineBackColor")
459 ; // Ignore: complementer of lineColor
460 else if (rProperty.first == "txflTextFlow" && xPropertySet.is())
462 switch (rProperty.second.toInt32())
464 case 1: // Top to bottom ASCII font
465 case 3: // Top to bottom non-ASCII font
466 eWritingMode = text::WritingMode2::TB_RL;
467 break;
468 case 2: // Bottom to top non-ASCII font
469 eWritingMode = text::WritingMode2::BT_LR;
470 break;
473 else if (rProperty.first == "fLine" && xPropertySet.is())
474 resolveFLine(xPropertySet, rProperty.second.toInt32());
475 else if (rProperty.first == "fillOpacity" && xPropertySet.is())
477 int opacity = 100 - (rProperty.second.toInt32()) * 100 / RTF_MULTIPLIER;
478 xPropertySet->setPropertyValue("FillTransparence", uno::Any(sal_uInt32(opacity)));
480 else if (rProperty.first == "lineWidth")
481 aLineWidth <<= rProperty.second.toInt32() / 360;
482 else if (rProperty.first == "pVerticies")
484 std::vector<drawing::EnhancedCustomShapeParameterPair> aCoordinates;
485 sal_Int32 nSize = 0; // Size of a token (its value is hardwired in the exporter)
486 sal_Int32 nCount = 0; // Number of tokens
487 sal_Int32 nCharIndex = 0; // Character index
490 OUString aToken = rProperty.second.getToken(0, ';', nCharIndex);
491 if (!nSize)
492 nSize = aToken.toInt32();
493 else if (!nCount)
494 nCount = aToken.toInt32();
495 else if (aToken.getLength())
497 // The coordinates are in an (x,y) form.
498 aToken = aToken.copy(1, aToken.getLength() - 2);
499 sal_Int32 nI = 0;
500 sal_Int32 nX = aToken.getToken(0, ',', nI).toInt32();
501 sal_Int32 nY = (nI >= 0) ? aToken.getToken(0, ',', nI).toInt32() : 0;
502 drawing::EnhancedCustomShapeParameterPair aPair;
503 aPair.First.Value <<= nX;
504 aPair.Second.Value <<= nY;
505 aCoordinates.push_back(aPair);
507 } while (nCharIndex >= 0);
508 aPropertyValue.Name = "Coordinates";
509 aPropertyValue.Value <<= comphelper::containerToSequence(aCoordinates);
510 aPath.push_back(aPropertyValue);
512 else if (rProperty.first == "pSegmentInfo")
514 std::vector<drawing::EnhancedCustomShapeSegment> aSegments;
515 sal_Int32 nSize = 0;
516 sal_Int32 nCount = 0;
517 sal_Int32 nCharIndex = 0;
520 sal_Int32 nSeg = rProperty.second.getToken(0, ';', nCharIndex).toInt32();
521 if (!nSize)
522 nSize = nSeg;
523 else if (!nCount)
524 nCount = nSeg;
525 else
527 sal_Int32 nPoints = 1;
528 if (nSeg >= 0x2000 && nSeg < 0x20FF)
530 nPoints = nSeg & 0x0FFF;
531 nSeg &= 0xFF00;
534 drawing::EnhancedCustomShapeSegment aSegment;
535 switch (nSeg)
537 case 0x0001: // lineto
538 aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::LINETO;
539 aSegment.Count = sal_Int32(1);
540 aSegments.push_back(aSegment);
541 break;
542 case 0x4000: // moveto
543 aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::MOVETO;
544 aSegment.Count = sal_Int32(1);
545 aSegments.push_back(aSegment);
546 break;
547 case 0x2000: // curveto
548 aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::CURVETO;
549 aSegment.Count = nPoints;
550 aSegments.push_back(aSegment);
551 break;
552 case 0xb300: // arcto
553 aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::ARCTO;
554 aSegment.Count = sal_Int32(0);
555 aSegments.push_back(aSegment);
556 break;
557 case 0xac00:
558 case 0xaa00: // nofill
559 case 0xab00: // nostroke
560 case 0x6001: // close
561 break;
562 case 0x8000: // end
563 aSegment.Command
564 = drawing::EnhancedCustomShapeSegmentCommand::ENDSUBPATH;
565 aSegment.Count = sal_Int32(0);
566 aSegments.push_back(aSegment);
567 break;
568 default: // given number of lineto elements
569 aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::LINETO;
570 aSegment.Count = nSeg;
571 aSegments.push_back(aSegment);
572 break;
575 } while (nCharIndex >= 0);
576 aPropertyValue.Name = "Segments";
577 aPropertyValue.Value <<= comphelper::containerToSequence(aSegments);
578 aPath.push_back(aPropertyValue);
580 else if (rProperty.first == "geoLeft")
581 aViewBox.X = rProperty.second.toInt32();
582 else if (rProperty.first == "geoTop")
583 aViewBox.Y = rProperty.second.toInt32();
584 else if (rProperty.first == "geoRight")
585 aViewBox.Width = rProperty.second.toInt32();
586 else if (rProperty.first == "geoBottom")
587 aViewBox.Height = rProperty.second.toInt32();
588 else if (rProperty.first == "dhgt")
590 // dhgt is Word 2007, \shpz is Word 97-2003, the later has priority.
591 if (!rShape.hasZ())
592 resolveDhgt(xPropertySet, rProperty.second.toInt32(), /*bOldStyle=*/false);
594 // These are in EMU, convert to mm100.
595 else if (rProperty.first == "dxTextLeft")
597 if (xPropertySet.is())
598 xPropertySet->setPropertyValue("LeftBorderDistance",
599 uno::makeAny(rProperty.second.toInt32() / 360));
601 else if (rProperty.first == "dyTextTop")
603 if (xPropertySet.is())
604 xPropertySet->setPropertyValue("TopBorderDistance",
605 uno::makeAny(rProperty.second.toInt32() / 360));
607 else if (rProperty.first == "dxTextRight")
609 if (xPropertySet.is())
610 xPropertySet->setPropertyValue("RightBorderDistance",
611 uno::makeAny(rProperty.second.toInt32() / 360));
613 else if (rProperty.first == "dyTextBottom")
615 if (xPropertySet.is())
616 xPropertySet->setPropertyValue("BottomBorderDistance",
617 uno::makeAny(rProperty.second.toInt32() / 360));
619 else if (rProperty.first == "dxWrapDistLeft")
621 if (m_bTextGraphicObject)
622 rShape.getAnchorAttributes().set(NS_ooxml::LN_CT_Anchor_distL,
623 new RTFValue(rProperty.second.toInt32()));
624 else if (xPropertySet.is())
625 xPropertySet->setPropertyValue("LeftMargin",
626 uno::makeAny(rProperty.second.toInt32() / 360));
628 else if (rProperty.first == "dyWrapDistTop")
630 if (m_bTextGraphicObject)
631 rShape.getAnchorAttributes().set(NS_ooxml::LN_CT_Anchor_distT,
632 new RTFValue(rProperty.second.toInt32()));
633 else if (xPropertySet.is())
634 xPropertySet->setPropertyValue("TopMargin",
635 uno::makeAny(rProperty.second.toInt32() / 360));
637 else if (rProperty.first == "dxWrapDistRight")
639 if (m_bTextGraphicObject)
640 rShape.getAnchorAttributes().set(NS_ooxml::LN_CT_Anchor_distR,
641 new RTFValue(rProperty.second.toInt32()));
642 else if (xPropertySet.is())
643 xPropertySet->setPropertyValue("RightMargin",
644 uno::makeAny(rProperty.second.toInt32() / 360));
646 else if (rProperty.first == "dyWrapDistBottom")
648 if (m_bTextGraphicObject)
649 rShape.getAnchorAttributes().set(NS_ooxml::LN_CT_Anchor_distB,
650 new RTFValue(rProperty.second.toInt32()));
651 else if (xPropertySet.is())
652 xPropertySet->setPropertyValue("BottomMargin",
653 uno::makeAny(rProperty.second.toInt32() / 360));
655 else if (rProperty.first == "fillType")
657 switch (rProperty.second.toInt32())
659 case 7: // Shade using the fillAngle
660 aFillModel.moType.set(oox::XML_gradient);
661 break;
662 default:
663 SAL_INFO("writerfilter",
664 "TODO handle fillType value '" << rProperty.second << "'");
665 break;
668 else if (rProperty.first == "fillFocus")
669 aFillModel.moFocus.set(rProperty.second.toDouble() / 100); // percent
670 else if (rProperty.first == "fShadow" && xPropertySet.is())
672 if (rProperty.second.toInt32() == 1)
673 aShadowModel.mbHasShadow = true;
675 else if (rProperty.first == "shadowColor")
676 aShadowModel.moColor.set("#"
677 + msfilter::util::ConvertColorOU(
678 msfilter::util::BGRToRGB(rProperty.second.toInt32())));
679 else if (rProperty.first == "shadowOffsetX")
680 // EMUs to points
681 aShadowModel.moOffset.set(OUString::number(rProperty.second.toDouble() / 12700) + "pt");
682 else if (rProperty.first == "posh" || rProperty.first == "posv"
683 || rProperty.first == "fFitShapeToText" || rProperty.first == "fFilled"
684 || rProperty.first == "rotation")
685 applyProperty(xShape, rProperty.first, rProperty.second);
686 else if (rProperty.first == "posrelh")
688 switch (rProperty.second.toInt32())
690 case 1:
691 rShape.setHoriOrientRelation(text::RelOrientation::PAGE_FRAME);
692 break;
693 default:
694 break;
697 else if (rProperty.first == "posrelv")
699 switch (rProperty.second.toInt32())
701 case 1:
702 rShape.setVertOrientRelation(text::RelOrientation::PAGE_FRAME);
703 break;
704 default:
705 break;
708 else if (rProperty.first == "groupLeft")
709 oGroupLeft = convertTwipToMm100(rProperty.second.toInt32());
710 else if (rProperty.first == "groupTop")
711 oGroupTop = convertTwipToMm100(rProperty.second.toInt32());
712 else if (rProperty.first == "groupRight")
713 oGroupRight = convertTwipToMm100(rProperty.second.toInt32());
714 else if (rProperty.first == "groupBottom")
715 oGroupBottom = convertTwipToMm100(rProperty.second.toInt32());
716 else if (rProperty.first == "relLeft")
717 oRelLeft = convertTwipToMm100(rProperty.second.toInt32());
718 else if (rProperty.first == "relTop")
719 oRelTop = convertTwipToMm100(rProperty.second.toInt32());
720 else if (rProperty.first == "relRight")
721 oRelRight = convertTwipToMm100(rProperty.second.toInt32());
722 else if (rProperty.first == "relBottom")
723 oRelBottom = convertTwipToMm100(rProperty.second.toInt32());
724 else if (rProperty.first == "fBehindDocument")
725 bOpaque = !rProperty.second.toInt32();
726 else if (rProperty.first == "pctHoriz" || rProperty.first == "pctVert")
728 sal_Int16 nPercentage = rtl::math::round(rProperty.second.toDouble() / 10);
729 if (nPercentage)
731 std::optional<sal_Int16>& rPercentage
732 = rProperty.first == "pctHoriz" ? oRelativeWidth : oRelativeHeight;
733 rPercentage = nPercentage;
736 else if (rProperty.first == "sizerelh")
738 if (xPropertySet.is())
740 switch (rProperty.second.toInt32())
742 case 0: // margin
743 nRelativeWidthRelation = text::RelOrientation::FRAME;
744 break;
745 case 1: // page
746 nRelativeWidthRelation = text::RelOrientation::PAGE_FRAME;
747 break;
748 default:
749 SAL_WARN("writerfilter", "RTFSdrImport::resolve: unhandled sizerelh value: "
750 << rProperty.second);
751 break;
755 else if (rProperty.first == "sizerelv")
757 if (xPropertySet.is())
759 switch (rProperty.second.toInt32())
761 case 0: // margin
762 nRelativeHeightRelation = text::RelOrientation::FRAME;
763 break;
764 case 1: // page
765 nRelativeHeightRelation = text::RelOrientation::PAGE_FRAME;
766 break;
767 default:
768 SAL_WARN("writerfilter", "RTFSdrImport::resolve: unhandled sizerelv value: "
769 << rProperty.second);
770 break;
774 else if (rProperty.first == "fHorizRule") // TODO: what does "fStandardHR" do?
776 // horizontal rule: relative width defaults to 100% of paragraph
777 // TODO: does it have a default height?
778 if (!oRelativeWidth)
780 oRelativeWidth = 100;
782 nRelativeWidthRelation = text::RelOrientation::FRAME;
783 if (xPropertySet.is())
785 sal_Int16 const nVertOrient = text::VertOrientation::CENTER;
786 xPropertySet->setPropertyValue("VertOrient", uno::makeAny(nVertOrient));
789 else if (rProperty.first == "pctHR")
791 // horizontal rule relative width in permille
792 oRelativeWidth = rProperty.second.toInt32() / 10;
794 else if (rProperty.first == "dxHeightHR")
796 // horizontal rule height
797 sal_uInt32 const nHeight(convertTwipToMm100(rProperty.second.toInt32()));
798 rShape.setBottom(rShape.getTop() + nHeight);
800 else if (rProperty.first == "dxWidthHR")
802 // horizontal rule width
803 sal_uInt32 const nWidth(convertTwipToMm100(rProperty.second.toInt32()));
804 rShape.setRight(rShape.getLeft() + nWidth);
806 else if (rProperty.first == "alignHR")
808 // horizontal orientation *for horizontal rule*
809 sal_Int16 nHoriOrient = text::HoriOrientation::NONE;
810 switch (rProperty.second.toInt32())
812 case 0:
813 nHoriOrient = text::HoriOrientation::LEFT;
814 break;
815 case 1:
816 nHoriOrient = text::HoriOrientation::CENTER;
817 break;
818 case 2:
819 nHoriOrient = text::HoriOrientation::RIGHT;
820 break;
822 if (xPropertySet.is() && text::HoriOrientation::NONE != nHoriOrient)
824 xPropertySet->setPropertyValue("HoriOrient", uno::makeAny(nHoriOrient));
827 else if (rProperty.first == "pWrapPolygonVertices")
829 RTFSprms aPolygonSprms;
830 sal_Int32 nSize = 0; // Size of a token
831 sal_Int32 nCount = 0; // Number of tokens
832 sal_Int32 nCharIndex = 0; // Character index
835 OUString aToken = rProperty.second.getToken(0, ';', nCharIndex);
836 if (!nSize)
837 nSize = aToken.toInt32();
838 else if (!nCount)
839 nCount = aToken.toInt32();
840 else if (aToken.getLength())
842 // The coordinates are in an (x,y) form.
843 aToken = aToken.copy(1, aToken.getLength() - 2);
844 sal_Int32 nI = 0;
845 sal_Int32 nX = aToken.getToken(0, ',', nI).toInt32();
846 sal_Int32 nY = (nI >= 0) ? aToken.getToken(0, ',', nI).toInt32() : 0;
847 RTFSprms aPathAttributes;
848 aPathAttributes.set(NS_ooxml::LN_CT_Point2D_x, new RTFValue(nX));
849 aPathAttributes.set(NS_ooxml::LN_CT_Point2D_y, new RTFValue(nY));
850 aPolygonSprms.set(NS_ooxml::LN_CT_WrapPath_lineTo,
851 new RTFValue(aPathAttributes), RTFOverwrite::NO_APPEND);
853 } while (nCharIndex >= 0);
854 rShape.getWrapPolygonSprms() = aPolygonSprms;
856 else if (rProperty.first == "fRelFlipV")
857 obRelFlipV = rProperty.second.toInt32() == 1;
858 else if (rProperty.first == "fFlipH")
859 obFlipH = rProperty.second.toInt32() == 1;
860 else if (rProperty.first == "fFlipV")
861 obFlipV = rProperty.second.toInt32() == 1;
862 else if (rProperty.first == "pictureContrast")
864 // Gain / contrast.
865 nContrast = rProperty.second.toInt32();
866 if (nContrast < 0x10000)
868 nContrast *= 101; // 100 + 1 to round
869 nContrast /= 0x10000;
870 nContrast -= 100;
873 else if (rProperty.first == "pictureBrightness")
875 // Blacklevel / brightness.
876 nBrightness = rProperty.second.toInt32();
877 if (nBrightness != 0)
879 nBrightness /= 327;
882 else
883 SAL_INFO("writerfilter", "TODO handle shape property '" << rProperty.first << "':'"
884 << rProperty.second << "'");
887 if (xPropertySet.is())
889 resolveLineColorAndWidth(m_bTextFrame, xPropertySet, aLineColor, aLineWidth);
890 if (rShape.hasZ())
892 bool bOldStyle = m_aParents.size() > 1;
893 resolveDhgt(xPropertySet, rShape.getZ(), bOldStyle);
895 if (m_bTextFrame)
896 xPropertySet->setPropertyValue("WritingMode", uno::makeAny(eWritingMode));
897 else
898 // Only Writer textframes implement text::WritingMode2.
899 xPropertySet->setPropertyValue("TextWritingMode",
900 uno::makeAny(text::WritingMode(eWritingMode)));
903 if (!m_aParents.empty() && m_aParents.top().is() && !m_bTextFrame)
904 m_aParents.top()->add(xShape);
906 if (nContrast == -70 && nBrightness == 70 && xPropertySet.is())
908 // Map MSO 'washout' to our watermark colormode.
909 xPropertySet->setPropertyValue("GraphicColorMode",
910 uno::makeAny(drawing::ColorMode_WATERMARK));
913 if (bPib)
915 m_rImport.resolvePict(false, xShape);
918 if (nType == ESCHER_ShpInst_PictureFrame) // picture frame
920 assert(!m_bTextFrame);
921 if (!bPib) // ??? not sure if the early return should be removed on else?
923 m_xShape = xShape; // store it for later resolvePict call
926 // Handle horizontal flip.
927 if (obFlipH && xPropertySet.is())
928 xPropertySet->setPropertyValue("IsMirrored", uno::makeAny(true));
929 return;
932 if (bCustom && xShape.is() && !bPib)
934 uno::Reference<drawing::XEnhancedCustomShapeDefaulter> xDefaulter(xShape, uno::UNO_QUERY);
935 xDefaulter->createCustomShapeDefaults(OUString::number(nType));
938 // Set shape text
939 if (bCustom && !aShapeText.isEmpty())
941 uno::Reference<text::XTextRange> xTextRange(xShape, uno::UNO_QUERY);
942 if (xTextRange.is())
943 xTextRange->setString(aShapeText);
945 xPropertySet->setPropertyValue("CharFontName", uno::makeAny(aFontFamily));
946 xPropertySet->setPropertyValue("CharHeight", uno::makeAny(nFontSize));
949 // Creating CustomShapeGeometry property
950 if (bCustom && xPropertySet.is())
952 bool bChanged = false;
953 comphelper::SequenceAsHashMap aCustomShapeGeometry(
954 xPropertySet->getPropertyValue("CustomShapeGeometry"));
956 if (aViewBox.X || aViewBox.Y || aViewBox.Width || aViewBox.Height)
958 aViewBox.Width -= aViewBox.X;
959 aViewBox.Height -= aViewBox.Y;
960 aCustomShapeGeometry["ViewBox"] <<= aViewBox;
961 bChanged = true;
964 if (!aPath.empty())
966 aCustomShapeGeometry["Path"] <<= comphelper::containerToSequence(aPath);
967 bChanged = true;
970 if (!aShapeText.isEmpty())
972 uno::Sequence<beans::PropertyValue> aSequence(comphelper::InitPropertySequence({
973 { "TextPath", uno::makeAny(true) },
974 }));
975 aCustomShapeGeometry["TextPath"] <<= aSequence;
976 xPropertySet->setPropertyValue("TextAutoGrowHeight", uno::makeAny(false));
977 xPropertySet->setPropertyValue("TextAutoGrowWidth", uno::makeAny(false));
978 bChanged = true;
981 if (bChanged)
983 xPropertySet->setPropertyValue(
984 "CustomShapeGeometry",
985 uno::makeAny(aCustomShapeGeometry.getAsConstPropertyValueList()));
989 if (obRelFlipV.has_value() && xPropertySet.is())
991 if (nType == ESCHER_ShpInst_Line)
993 // Line shape inside group shape: get the polygon sequence and transform it.
994 uno::Sequence<uno::Sequence<awt::Point>> aPolyPolySequence;
995 if ((xPropertySet->getPropertyValue("PolyPolygon") >>= aPolyPolySequence)
996 && aPolyPolySequence.hasElements())
998 uno::Sequence<awt::Point>& rPolygon = aPolyPolySequence.getArray()[0];
999 basegfx::B2DPolygon aPoly;
1000 for (const awt::Point& rPoint : std::as_const(rPolygon))
1002 aPoly.append(basegfx::B2DPoint(rPoint.X, rPoint.Y));
1004 basegfx::B2DHomMatrix aTransformation;
1005 aTransformation.scale(1.0, *obRelFlipV ? -1.0 : 1.0);
1006 aPoly.transform(aTransformation);
1007 auto pPolygon = rPolygon.getArray();
1008 for (sal_Int32 i = 0; i < rPolygon.getLength(); ++i)
1010 basegfx::B2DPoint aPoint(aPoly.getB2DPoint(i));
1011 pPolygon[i] = awt::Point(static_cast<sal_Int32>(aPoint.getX()),
1012 static_cast<sal_Int32>(aPoint.getY()));
1014 xPropertySet->setPropertyValue("PolyPolygon", uno::makeAny(aPolyPolySequence));
1019 // Set position and size
1020 if (xShape.is())
1022 sal_Int32 nLeft = rShape.getLeft();
1023 sal_Int32 nTop = rShape.getTop();
1025 bool bInShapeGroup = oGroupLeft && oGroupTop && oGroupRight && oGroupBottom && oRelLeft
1026 && oRelTop && oRelRight && oRelBottom;
1027 awt::Size aSize;
1028 if (bInShapeGroup)
1030 // See lclGetAbsPoint() in the VML import: rShape is the group shape, oGroup is its coordinate system, oRel is the relative child shape.
1031 sal_Int32 nShapeWidth = rShape.getRight() - rShape.getLeft();
1032 sal_Int32 nShapeHeight = rShape.getBottom() - rShape.getTop();
1033 sal_Int32 nCoordSysWidth = *oGroupRight - *oGroupLeft;
1034 sal_Int32 nCoordSysHeight = *oGroupBottom - *oGroupTop;
1035 double fWidthRatio = static_cast<double>(nShapeWidth) / nCoordSysWidth;
1036 double fHeightRatio = static_cast<double>(nShapeHeight) / nCoordSysHeight;
1037 nLeft = static_cast<sal_Int32>(rShape.getLeft()
1038 + fWidthRatio * (*oRelLeft - *oGroupLeft));
1039 nTop = static_cast<sal_Int32>(rShape.getTop() + fHeightRatio * (*oRelTop - *oGroupTop));
1041 // See lclGetAbsRect() in the VML import.
1042 aSize.Width = std::lround(fWidthRatio * (*oRelRight - *oRelLeft));
1043 aSize.Height = std::lround(fHeightRatio * (*oRelBottom - *oRelTop));
1046 if (m_bTextFrame)
1048 xPropertySet->setPropertyValue("HoriOrientPosition", uno::makeAny(nLeft));
1049 xPropertySet->setPropertyValue("VertOrientPosition", uno::makeAny(nTop));
1051 else
1052 xShape->setPosition(awt::Point(nLeft, nTop));
1054 if (bInShapeGroup)
1055 xShape->setSize(aSize);
1056 else
1057 xShape->setSize(awt::Size(rShape.getRight() - rShape.getLeft(),
1058 rShape.getBottom() - rShape.getTop()));
1060 if (obFlipH || obFlipV)
1062 if (bCustom)
1064 // This has to be set after position and size is set, otherwise flip will affect the position.
1065 comphelper::SequenceAsHashMap aCustomShapeGeometry(
1066 xPropertySet->getPropertyValue("CustomShapeGeometry"));
1067 if (obFlipH)
1068 aCustomShapeGeometry["MirroredX"] <<= true;
1069 if (obFlipV)
1070 aCustomShapeGeometry["MirroredY"] <<= true;
1071 xPropertySet->setPropertyValue(
1072 "CustomShapeGeometry",
1073 uno::makeAny(aCustomShapeGeometry.getAsConstPropertyValueList()));
1075 else if (SdrObject* pObject = SdrObject::getSdrObjectFromXShape(xShape))
1077 Point aRef1 = pObject->GetSnapRect().Center();
1078 Point aRef2(aRef1);
1079 if (obFlipH)
1081 // Horizontal mirror means a vertical reference line.
1082 aRef2.AdjustY(1);
1084 if (obFlipV)
1086 // Vertical mirror means a horizontal reference line.
1087 aRef2.AdjustX(1);
1089 pObject->Mirror(aRef1, aRef2);
1093 if (rShape.getHoriOrientRelation() != 0)
1094 xPropertySet->setPropertyValue("HoriOrientRelation",
1095 uno::makeAny(rShape.getHoriOrientRelation()));
1096 if (rShape.getVertOrientRelation() != 0)
1097 xPropertySet->setPropertyValue("VertOrientRelation",
1098 uno::makeAny(rShape.getVertOrientRelation()));
1099 if (rShape.getWrap() != text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE)
1100 xPropertySet->setPropertyValue("Surround", uno::makeAny(rShape.getWrap()));
1101 oox::ModelObjectHelper aModelObjectHelper(m_rImport.getModelFactory());
1102 if (aFillModel.moType.has())
1104 oox::drawingml::ShapePropertyMap aPropMap(aModelObjectHelper);
1105 aFillModel.pushToPropMap(aPropMap, m_rImport.getGraphicHelper());
1106 // Sets the FillStyle and FillGradient UNO properties.
1107 oox::PropertySet(xShape).setProperties(aPropMap);
1110 if (aShadowModel.mbHasShadow)
1112 oox::drawingml::ShapePropertyMap aPropMap(aModelObjectHelper);
1113 aShadowModel.pushToPropMap(aPropMap, m_rImport.getGraphicHelper());
1114 // Sets the ShadowFormat UNO property.
1115 oox::PropertySet(xShape).setProperties(aPropMap);
1117 xPropertySet->setPropertyValue("AnchorType",
1118 uno::makeAny(text::TextContentAnchorType_AT_CHARACTER));
1119 xPropertySet->setPropertyValue("Opaque", uno::makeAny(bOpaque));
1120 if (oRelativeWidth)
1122 xPropertySet->setPropertyValue("RelativeWidth", uno::makeAny(*oRelativeWidth));
1123 xPropertySet->setPropertyValue("RelativeWidthRelation",
1124 uno::makeAny(nRelativeWidthRelation));
1126 if (oRelativeHeight)
1128 xPropertySet->setPropertyValue("RelativeHeight", uno::makeAny(*oRelativeHeight));
1129 xPropertySet->setPropertyValue("RelativeHeightRelation",
1130 uno::makeAny(nRelativeHeightRelation));
1134 if (m_rImport.isInBackground())
1136 RTFSprms aAttributes;
1137 aAttributes.set(NS_ooxml::LN_CT_Background_color,
1138 new RTFValue(xPropertySet->getPropertyValue("FillColor").get<sal_Int32>()));
1139 m_rImport.Mapper().props(new RTFReferenceProperties(aAttributes));
1141 uno::Reference<lang::XComponent> xComponent(xShape, uno::UNO_QUERY);
1142 xComponent->dispose();
1143 return;
1146 // Send it to dmapper
1147 if (xShape.is())
1149 m_rImport.Mapper().startShape(xShape);
1150 if (bClose)
1152 m_rImport.Mapper().endShape();
1156 // If the shape has an inner shape, the inner object's properties should not be influenced by
1157 // the outer one.
1158 rShape.getProperties().clear();
1160 m_xShape = xShape;
1163 void RTFSdrImport::close() { m_rImport.Mapper().endShape(); }
1165 void RTFSdrImport::append(std::u16string_view aKey, const OUString& aValue)
1167 applyProperty(m_xShape, aKey, aValue);
1170 void RTFSdrImport::appendGroupProperty(std::u16string_view aKey, const OUString& aValue)
1172 if (m_aParents.empty())
1173 return;
1174 uno::Reference<drawing::XShape> xShape(m_aParents.top(), uno::UNO_QUERY);
1175 if (xShape.is())
1176 applyProperty(xShape, aKey, aValue);
1179 } // namespace writerfilter
1181 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */