Avoid potential negative array index access to cached text.
[LibreOffice.git] / writerfilter / source / rtftok / rtfsdrimport.cxx
blobf263f2a43f77ccb4b0cc371078181b94c91b83e7
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>
50 #include <o3tl/string_view.hxx>
52 #include <dmapper/GraphicZOrderHelper.hxx>
53 #include "rtfdocumentimpl.hxx"
55 using namespace com::sun::star;
57 namespace writerfilter::rtftok
59 RTFSdrImport::RTFSdrImport(RTFDocumentImpl& rDocument,
60 uno::Reference<lang::XComponent> const& xDstDoc)
61 : m_rImport(rDocument)
62 , m_bTextFrame(false)
63 , m_bTextGraphicObject(false)
64 , m_bFakePict(false)
66 uno::Reference<drawing::XDrawPageSupplier> xDrawings(xDstDoc, uno::UNO_QUERY);
67 if (xDrawings.is())
68 m_aParents.push(xDrawings->getDrawPage());
69 m_aGraphicZOrderHelpers.push(writerfilter::dmapper::GraphicZOrderHelper());
72 RTFSdrImport::~RTFSdrImport()
74 if (!m_aGraphicZOrderHelpers.empty())
75 m_aGraphicZOrderHelpers.pop();
76 if (!m_aParents.empty())
77 m_aParents.pop();
80 void RTFSdrImport::createShape(const OUString& rService, uno::Reference<drawing::XShape>& xShape,
81 uno::Reference<beans::XPropertySet>& xPropertySet)
83 if (m_rImport.getModelFactory().is())
84 xShape.set(m_rImport.getModelFactory()->createInstance(rService), uno::UNO_QUERY);
85 xPropertySet.set(xShape, uno::UNO_QUERY);
88 std::vector<beans::PropertyValue> RTFSdrImport::getTextFrameDefaults(bool bNew)
90 std::vector<beans::PropertyValue> aRet;
91 beans::PropertyValue aPropertyValue;
93 aPropertyValue.Name = "HoriOrient";
94 aPropertyValue.Value <<= text::HoriOrientation::NONE;
95 aRet.push_back(aPropertyValue);
96 aPropertyValue.Name = "HoriOrientRelation";
97 aPropertyValue.Value <<= text::RelOrientation::FRAME;
98 aRet.push_back(aPropertyValue);
99 aPropertyValue.Name = "VertOrient";
100 aPropertyValue.Value <<= text::VertOrientation::NONE;
101 aRet.push_back(aPropertyValue);
102 aPropertyValue.Name = "VertOrientRelation";
103 aPropertyValue.Value <<= text::RelOrientation::FRAME;
104 aRet.push_back(aPropertyValue);
105 if (!bNew)
107 aPropertyValue.Name = "BackColorTransparency";
108 aPropertyValue.Value <<= sal_Int32(100);
109 aRet.push_back(aPropertyValue);
111 // See the spec, new-style frame default margins are specified in EMUs.
112 aPropertyValue.Name = "LeftBorderDistance";
113 aPropertyValue.Value <<= sal_Int32(bNew ? (91440 / 360) : 0);
114 aRet.push_back(aPropertyValue);
115 aPropertyValue.Name = "RightBorderDistance";
116 aPropertyValue.Value <<= sal_Int32(bNew ? (91440 / 360) : 0);
117 aRet.push_back(aPropertyValue);
118 aPropertyValue.Name = "TopBorderDistance";
119 aPropertyValue.Value <<= sal_Int32(bNew ? (45720 / 360) : 0);
120 aRet.push_back(aPropertyValue);
121 aPropertyValue.Name = "BottomBorderDistance";
122 aPropertyValue.Value <<= sal_Int32(bNew ? (45720 / 360) : 0);
123 aRet.push_back(aPropertyValue);
124 aPropertyValue.Name = "SizeType";
125 aPropertyValue.Value <<= text::SizeType::FIX;
126 aRet.push_back(aPropertyValue);
127 return aRet;
130 void RTFSdrImport::pushParent(uno::Reference<drawing::XShapes> const& xParent)
132 m_aParents.push(xParent);
133 m_aGraphicZOrderHelpers.push(writerfilter::dmapper::GraphicZOrderHelper());
136 void RTFSdrImport::popParent()
138 if (!m_aGraphicZOrderHelpers.empty())
139 m_aGraphicZOrderHelpers.pop();
140 if (!m_aParents.empty())
141 m_aParents.pop();
144 void RTFSdrImport::resolveDhgt(uno::Reference<beans::XPropertySet> const& xPropertySet,
145 sal_Int32 const nZOrder, bool const bOldStyle)
147 if (!m_aGraphicZOrderHelpers.empty())
149 writerfilter::dmapper::GraphicZOrderHelper& rHelper = m_aGraphicZOrderHelpers.top();
150 xPropertySet->setPropertyValue("ZOrder", uno::Any(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::Any(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::Any(drawing::LineStyle_NONE));
187 else
188 xPropertySet->setPropertyValue("LineStyle", uno::Any(drawing::LineStyle_SOLID));
191 void RTFSdrImport::applyProperty(uno::Reference<drawing::XShape> const& xShape,
192 std::u16string_view aKey, std::u16string_view 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 (o3tl::toInt32(aValue))
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 (o3tl::toInt32(aValue))
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 = o3tl::toInt32(aValue) == 1;
242 else if (aKey == u"fFilled")
243 bFilled = o3tl::toInt32(aValue) == 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 = o3tl::toInt32(aValue) * 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::Any(NormAngle36000(Degree100(nRotation * -1)).get()));
255 if (nHoriOrient != 0 && xPropertySet.is())
256 xPropertySet->setPropertyValue("HoriOrient", uno::Any(nHoriOrient));
257 if (nVertOrient != 0 && xPropertySet.is())
258 xPropertySet->setPropertyValue("VertOrient", uno::Any(nVertOrient));
259 if (obFitShapeToText.has_value() && xPropertySet.is())
261 xPropertySet->setPropertyValue(
262 "SizeType", uno::Any(*obFitShapeToText ? text::SizeType::MIN : text::SizeType::FIX));
263 xPropertySet->setPropertyValue("FrameIsAutomaticHeight", uno::Any(*obFitShapeToText));
265 if (!bFilled && xPropertySet.is())
267 if (m_bTextFrame)
268 xPropertySet->setPropertyValue("BackColorTransparency", uno::Any(sal_Int32(100)));
269 else
270 xPropertySet->setPropertyValue("FillStyle", uno::Any(drawing::FillStyle_NONE));
274 int RTFSdrImport::initShape(uno::Reference<drawing::XShape>& o_xShape,
275 uno::Reference<beans::XPropertySet>& o_xPropSet, bool& o_rIsCustomShape,
276 RTFShape const& rShape, bool const bClose,
277 ShapeOrPict const shapeOrPict)
279 assert(!o_xShape.is());
280 assert(!o_xPropSet.is());
281 o_rIsCustomShape = false;
282 m_bFakePict = false;
284 // first, find the shape type
285 int nType = -1;
286 auto iter = std::find_if(rShape.getProperties().begin(), rShape.getProperties().end(),
287 [](const std::pair<OUString, OUString>& rProperty) {
288 return rProperty.first == "shapeType";
291 if (iter == rShape.getProperties().end())
293 if (SHAPE == shapeOrPict)
295 // The spec doesn't state what is the default for shapeType,
296 // Word seems to implement it as a rectangle.
297 nType = ESCHER_ShpInst_Rectangle;
299 else
301 // pict is picture by default but can be a rectangle too fdo#79319
302 nType = ESCHER_ShpInst_PictureFrame;
305 else
307 nType = iter->second.toInt32();
308 if (PICT == shapeOrPict && ESCHER_ShpInst_PictureFrame != nType)
310 m_bFakePict = true;
314 switch (nType)
316 case ESCHER_ShpInst_PictureFrame:
317 createShape("com.sun.star.drawing.GraphicObjectShape", o_xShape, o_xPropSet);
318 m_bTextGraphicObject = true;
319 break;
320 case ESCHER_ShpInst_Line:
321 createShape("com.sun.star.drawing.LineShape", o_xShape, o_xPropSet);
322 break;
323 case ESCHER_ShpInst_Rectangle:
324 case ESCHER_ShpInst_TextBox:
325 // If we're inside a groupshape, can't use text frames.
326 if (!bClose && m_aParents.size() == 1)
328 createShape("com.sun.star.text.TextFrame", o_xShape, o_xPropSet);
329 m_bTextFrame = true;
330 std::vector<beans::PropertyValue> aDefaults = getTextFrameDefaults(true);
331 for (const beans::PropertyValue& i : aDefaults)
332 o_xPropSet->setPropertyValue(i.Name, i.Value);
333 break;
335 [[fallthrough]];
336 default:
337 createShape("com.sun.star.drawing.CustomShape", o_xShape, o_xPropSet);
338 o_rIsCustomShape = true;
339 break;
342 // Defaults
343 if (o_xPropSet.is() && !m_bTextFrame)
345 o_xPropSet->setPropertyValue(
346 "FillColor",
347 uno::Any(sal_uInt32(0xffffff))); // White in Word, kind of blue in Writer.
348 o_xPropSet->setPropertyValue("VertOrient", uno::Any(text::VertOrientation::NONE));
351 return nType;
354 void RTFSdrImport::resolve(RTFShape& rShape, bool bClose, ShapeOrPict const shapeOrPict)
356 bool bPib = false;
357 m_bTextFrame = false;
358 m_bTextGraphicObject = false;
360 uno::Reference<drawing::XShape> xShape;
361 uno::Reference<beans::XPropertySet> xPropertySet;
362 uno::Any aAny;
363 beans::PropertyValue aPropertyValue;
364 awt::Rectangle aViewBox;
365 std::vector<beans::PropertyValue> aPath;
366 // Default line color is black in Word, blue in Writer.
367 uno::Any aLineColor(COL_BLACK);
368 // Default line width is 0.75 pt (26 mm100) in Word, 0 in Writer.
369 uno::Any aLineWidth(sal_Int32(26));
370 sal_Int16 eWritingMode = text::WritingMode2::LR_TB;
371 // Groupshape support
372 std::optional<sal_Int32> oGroupLeft;
373 std::optional<sal_Int32> oGroupTop;
374 std::optional<sal_Int32> oGroupRight;
375 std::optional<sal_Int32> oGroupBottom;
376 std::optional<sal_Int32> oRelLeft;
377 std::optional<sal_Int32> oRelTop;
378 std::optional<sal_Int32> oRelRight;
379 std::optional<sal_Int32> oRelBottom;
381 // Importing these are not trivial, let the VML import do the hard work.
382 oox::vml::FillModel aFillModel; // Gradient.
383 oox::vml::ShadowModel aShadowModel; // Shadow.
385 bool bOpaque = true;
387 std::optional<sal_Int16> oRelativeWidth;
388 std::optional<sal_Int16> oRelativeHeight;
389 sal_Int16 nRelativeWidthRelation = text::RelOrientation::PAGE_FRAME;
390 sal_Int16 nRelativeHeightRelation = text::RelOrientation::PAGE_FRAME;
391 std::optional<bool> obRelFlipV;
392 bool obFlipH(false);
393 bool obFlipV(false);
395 OUString aShapeText = "";
396 OUString aFontFamily = "";
397 float nFontSize = 1.0;
399 sal_Int32 nContrast = 0x10000;
400 sal_Int16 nBrightness = 0;
402 bool bCustom(false);
403 int const nType = initShape(xShape, xPropertySet, bCustom, rShape, bClose, shapeOrPict);
405 for (auto& rProperty : rShape.getProperties())
407 if (rProperty.first == "shapeType")
409 continue; // ignore: already handled by initShape
411 if (rProperty.first == "wzName")
413 if (m_bTextFrame)
415 uno::Reference<container::XNamed> xNamed(xShape, uno::UNO_QUERY);
416 xNamed->setName(rProperty.second);
418 else
419 xPropertySet->setPropertyValue("Name", uno::Any(rProperty.second));
421 else if (rProperty.first == "wzDescription")
422 xPropertySet->setPropertyValue("Description", uno::Any(rProperty.second));
423 else if (rProperty.first == "gtextUNICODE")
424 aShapeText = rProperty.second;
425 else if (rProperty.first == "gtextFont")
426 aFontFamily = rProperty.second;
427 else if (rProperty.first == "gtextSize")
429 // RTF size is multiplied by 2^16
430 nFontSize = static_cast<float>(rProperty.second.toUInt32()) / RTF_MULTIPLIER;
432 else if (rProperty.first == "pib")
434 m_rImport.setDestinationText(rProperty.second);
435 bPib = true;
437 else if (rProperty.first == "fillColor" && xPropertySet.is())
439 aAny <<= msfilter::util::BGRToRGB(rProperty.second.toUInt32());
440 if (m_bTextFrame)
441 xPropertySet->setPropertyValue("BackColor", aAny);
442 else
443 xPropertySet->setPropertyValue("FillColor", aAny);
445 // fillType will decide, possible it'll be the start color of a gradient.
446 aFillModel.moColor
447 = "#"
448 + msfilter::util::ConvertColorOU(Color(ColorTransparency, aAny.get<sal_Int32>()));
450 else if (rProperty.first == "fillBackColor")
451 // fillType will decide, possible it'll be the end color of a gradient.
452 aFillModel.moColor2 = "#"
453 + msfilter::util::ConvertColorOU(
454 msfilter::util::BGRToRGB(rProperty.second.toInt32()));
455 else if (rProperty.first == "lineColor")
456 aLineColor <<= msfilter::util::BGRToRGB(rProperty.second.toInt32());
457 else if (rProperty.first == "lineBackColor")
458 ; // Ignore: complementer of lineColor
459 else if (rProperty.first == "txflTextFlow" && xPropertySet.is())
461 switch (rProperty.second.toInt32())
463 case 1: // Top to bottom ASCII font
464 case 3: // Top to bottom non-ASCII font
465 eWritingMode = text::WritingMode2::TB_RL;
466 break;
467 case 2: // Bottom to top non-ASCII font
468 eWritingMode = text::WritingMode2::BT_LR;
469 break;
472 else if (rProperty.first == "fLine" && xPropertySet.is())
473 resolveFLine(xPropertySet, rProperty.second.toInt32());
474 else if (rProperty.first == "fillOpacity" && xPropertySet.is())
476 int opacity = 100 - (rProperty.second.toInt32()) * 100 / RTF_MULTIPLIER;
477 xPropertySet->setPropertyValue("FillTransparence", uno::Any(sal_uInt32(opacity)));
479 else if (rProperty.first == "lineWidth")
480 aLineWidth <<= rProperty.second.toInt32() / 360;
481 else if (rProperty.first == "pVerticies")
483 std::vector<drawing::EnhancedCustomShapeParameterPair> aCoordinates;
484 sal_Int32 nSize = 0; // Size of a token (its value is hardwired in the exporter)
485 sal_Int32 nCount = 0; // Number of tokens
486 sal_Int32 nCharIndex = 0; // Character index
489 std::u16string_view aToken = o3tl::getToken(rProperty.second, 0, ';', nCharIndex);
490 if (!nSize)
491 nSize = o3tl::toInt32(aToken);
492 else if (!nCount)
493 nCount = o3tl::toInt32(aToken);
494 else if (!aToken.empty())
496 // The coordinates are in an (x,y) form.
497 aToken = aToken.substr(1, aToken.size() - 2);
498 sal_Int32 nI = 0;
499 sal_Int32 nX = o3tl::toInt32(o3tl::getToken(aToken, 0, ',', nI));
500 sal_Int32 nY
501 = (nI >= 0) ? o3tl::toInt32(o3tl::getToken(aToken, 0, ',', nI)) : 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
521 = o3tl::toInt32(o3tl::getToken(rProperty.second, 0, ';', nCharIndex));
522 if (!nSize)
523 nSize = nSeg;
524 else if (!nCount)
525 nCount = nSeg;
526 else
528 sal_Int32 nPoints = 1;
529 if (nSeg >= 0x2000 && nSeg < 0x20FF)
531 nPoints = nSeg & 0x0FFF;
532 nSeg &= 0xFF00;
535 drawing::EnhancedCustomShapeSegment aSegment;
536 switch (nSeg)
538 case 0x0001: // lineto
539 aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::LINETO;
540 aSegment.Count = sal_Int32(1);
541 aSegments.push_back(aSegment);
542 break;
543 case 0x4000: // moveto
544 aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::MOVETO;
545 aSegment.Count = sal_Int32(1);
546 aSegments.push_back(aSegment);
547 break;
548 case 0x2000: // curveto
549 aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::CURVETO;
550 aSegment.Count = nPoints;
551 aSegments.push_back(aSegment);
552 break;
553 case 0xb300: // arcto
554 aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::ARCTO;
555 aSegment.Count = sal_Int32(0);
556 aSegments.push_back(aSegment);
557 break;
558 case 0xac00:
559 case 0xaa00: // nofill
560 case 0xab00: // nostroke
561 case 0x6001: // close
562 break;
563 case 0x8000: // end
564 aSegment.Command
565 = drawing::EnhancedCustomShapeSegmentCommand::ENDSUBPATH;
566 aSegment.Count = sal_Int32(0);
567 aSegments.push_back(aSegment);
568 break;
569 default: // given number of lineto elements
570 aSegment.Command = drawing::EnhancedCustomShapeSegmentCommand::LINETO;
571 aSegment.Count = nSeg;
572 aSegments.push_back(aSegment);
573 break;
576 } while (nCharIndex >= 0);
577 aPropertyValue.Name = "Segments";
578 aPropertyValue.Value <<= comphelper::containerToSequence(aSegments);
579 aPath.push_back(aPropertyValue);
581 else if (rProperty.first == "geoLeft")
582 aViewBox.X = rProperty.second.toInt32();
583 else if (rProperty.first == "geoTop")
584 aViewBox.Y = rProperty.second.toInt32();
585 else if (rProperty.first == "geoRight")
586 aViewBox.Width = rProperty.second.toInt32();
587 else if (rProperty.first == "geoBottom")
588 aViewBox.Height = rProperty.second.toInt32();
589 else if (rProperty.first == "dhgt")
591 // dhgt is Word 2007, \shpz is Word 97-2003, the later has priority.
592 if (!rShape.hasZ())
593 resolveDhgt(xPropertySet, rProperty.second.toInt32(), /*bOldStyle=*/false);
595 // These are in EMU, convert to mm100.
596 else if (rProperty.first == "dxTextLeft")
598 if (xPropertySet.is())
599 xPropertySet->setPropertyValue("LeftBorderDistance",
600 uno::Any(rProperty.second.toInt32() / 360));
602 else if (rProperty.first == "dyTextTop")
604 if (xPropertySet.is())
605 xPropertySet->setPropertyValue("TopBorderDistance",
606 uno::Any(rProperty.second.toInt32() / 360));
608 else if (rProperty.first == "dxTextRight")
610 if (xPropertySet.is())
611 xPropertySet->setPropertyValue("RightBorderDistance",
612 uno::Any(rProperty.second.toInt32() / 360));
614 else if (rProperty.first == "dyTextBottom")
616 if (xPropertySet.is())
617 xPropertySet->setPropertyValue("BottomBorderDistance",
618 uno::Any(rProperty.second.toInt32() / 360));
620 else if (rProperty.first == "dxWrapDistLeft")
622 if (m_bTextGraphicObject)
623 rShape.getAnchorAttributes().set(NS_ooxml::LN_CT_Anchor_distL,
624 new RTFValue(rProperty.second.toInt32()));
625 else if (xPropertySet.is())
626 xPropertySet->setPropertyValue("LeftMargin",
627 uno::Any(rProperty.second.toInt32() / 360));
629 else if (rProperty.first == "dyWrapDistTop")
631 if (m_bTextGraphicObject)
632 rShape.getAnchorAttributes().set(NS_ooxml::LN_CT_Anchor_distT,
633 new RTFValue(rProperty.second.toInt32()));
634 else if (xPropertySet.is())
635 xPropertySet->setPropertyValue("TopMargin",
636 uno::Any(rProperty.second.toInt32() / 360));
638 else if (rProperty.first == "dxWrapDistRight")
640 if (m_bTextGraphicObject)
641 rShape.getAnchorAttributes().set(NS_ooxml::LN_CT_Anchor_distR,
642 new RTFValue(rProperty.second.toInt32()));
643 else if (xPropertySet.is())
644 xPropertySet->setPropertyValue("RightMargin",
645 uno::Any(rProperty.second.toInt32() / 360));
647 else if (rProperty.first == "dyWrapDistBottom")
649 if (m_bTextGraphicObject)
650 rShape.getAnchorAttributes().set(NS_ooxml::LN_CT_Anchor_distB,
651 new RTFValue(rProperty.second.toInt32()));
652 else if (xPropertySet.is())
653 xPropertySet->setPropertyValue("BottomMargin",
654 uno::Any(rProperty.second.toInt32() / 360));
656 else if (rProperty.first == "fillType")
658 switch (rProperty.second.toInt32())
660 case 7: // Shade using the fillAngle
661 aFillModel.moType = oox::XML_gradient;
662 break;
663 default:
664 SAL_INFO("writerfilter",
665 "TODO handle fillType value '" << rProperty.second << "'");
666 break;
669 else if (rProperty.first == "fillAngle")
671 aFillModel.moAngle = rProperty.second.toInt32() / 60000;
673 else if (rProperty.first == "fillFocus")
674 aFillModel.moFocus = rProperty.second.toDouble() / 100; // percent
675 else if (rProperty.first == "fShadow" && xPropertySet.is())
677 if (rProperty.second.toInt32() == 1)
678 aShadowModel.mbHasShadow = true;
680 else if (rProperty.first == "shadowColor")
681 aShadowModel.moColor = "#"
682 + msfilter::util::ConvertColorOU(
683 msfilter::util::BGRToRGB(rProperty.second.toInt32()));
684 else if (rProperty.first == "shadowOffsetX")
685 // EMUs to points
686 aShadowModel.moOffset = OUString::number(rProperty.second.toDouble() / 12700) + "pt";
687 else if (rProperty.first == "posh" || rProperty.first == "posv"
688 || rProperty.first == "fFitShapeToText" || rProperty.first == "fFilled"
689 || rProperty.first == "rotation")
690 applyProperty(xShape, rProperty.first, rProperty.second);
691 else if (rProperty.first == "posrelh")
693 switch (rProperty.second.toInt32())
695 case 1:
696 rShape.setHoriOrientRelation(text::RelOrientation::PAGE_FRAME);
697 break;
698 default:
699 break;
702 else if (rProperty.first == "posrelv")
704 switch (rProperty.second.toInt32())
706 case 1:
707 rShape.setVertOrientRelation(text::RelOrientation::PAGE_FRAME);
708 break;
709 default:
710 break;
713 else if (rProperty.first == "groupLeft")
714 oGroupLeft = convertTwipToMm100(rProperty.second.toInt32());
715 else if (rProperty.first == "groupTop")
716 oGroupTop = convertTwipToMm100(rProperty.second.toInt32());
717 else if (rProperty.first == "groupRight")
718 oGroupRight = convertTwipToMm100(rProperty.second.toInt32());
719 else if (rProperty.first == "groupBottom")
720 oGroupBottom = convertTwipToMm100(rProperty.second.toInt32());
721 else if (rProperty.first == "relLeft")
722 oRelLeft = convertTwipToMm100(rProperty.second.toInt32());
723 else if (rProperty.first == "relTop")
724 oRelTop = convertTwipToMm100(rProperty.second.toInt32());
725 else if (rProperty.first == "relRight")
726 oRelRight = convertTwipToMm100(rProperty.second.toInt32());
727 else if (rProperty.first == "relBottom")
728 oRelBottom = convertTwipToMm100(rProperty.second.toInt32());
729 else if (rProperty.first == "fBehindDocument")
730 bOpaque = !rProperty.second.toInt32();
731 else if (rProperty.first == "pctHoriz" || rProperty.first == "pctVert")
733 sal_Int16 nPercentage = rtl::math::round(rProperty.second.toDouble() / 10);
734 if (nPercentage)
736 std::optional<sal_Int16>& rPercentage
737 = rProperty.first == "pctHoriz" ? oRelativeWidth : oRelativeHeight;
738 rPercentage = nPercentage;
741 else if (rProperty.first == "sizerelh")
743 if (xPropertySet.is())
745 switch (rProperty.second.toInt32())
747 case 0: // margin
748 nRelativeWidthRelation = text::RelOrientation::FRAME;
749 break;
750 case 1: // page
751 nRelativeWidthRelation = text::RelOrientation::PAGE_FRAME;
752 break;
753 default:
754 SAL_WARN("writerfilter", "RTFSdrImport::resolve: unhandled sizerelh value: "
755 << rProperty.second);
756 break;
760 else if (rProperty.first == "sizerelv")
762 if (xPropertySet.is())
764 switch (rProperty.second.toInt32())
766 case 0: // margin
767 nRelativeHeightRelation = text::RelOrientation::FRAME;
768 break;
769 case 1: // page
770 nRelativeHeightRelation = text::RelOrientation::PAGE_FRAME;
771 break;
772 default:
773 SAL_WARN("writerfilter", "RTFSdrImport::resolve: unhandled sizerelv value: "
774 << rProperty.second);
775 break;
779 else if (rProperty.first == "fHorizRule") // TODO: what does "fStandardHR" do?
781 // horizontal rule: relative width defaults to 100% of paragraph
782 // TODO: does it have a default height?
783 if (!oRelativeWidth)
785 oRelativeWidth = 100;
787 nRelativeWidthRelation = text::RelOrientation::FRAME;
788 if (xPropertySet.is())
790 sal_Int16 const nVertOrient = text::VertOrientation::CENTER;
791 xPropertySet->setPropertyValue("VertOrient", uno::Any(nVertOrient));
794 else if (rProperty.first == "pctHR")
796 // horizontal rule relative width in permille
797 oRelativeWidth = rProperty.second.toInt32() / 10;
799 else if (rProperty.first == "dxHeightHR")
801 // horizontal rule height
802 sal_uInt32 const nHeight(convertTwipToMm100(rProperty.second.toInt32()));
803 rShape.setBottom(rShape.getTop() + nHeight);
805 else if (rProperty.first == "dxWidthHR")
807 // horizontal rule width
808 sal_uInt32 const nWidth(convertTwipToMm100(rProperty.second.toInt32()));
809 rShape.setRight(rShape.getLeft() + nWidth);
811 else if (rProperty.first == "alignHR")
813 // horizontal orientation *for horizontal rule*
814 sal_Int16 nHoriOrient = text::HoriOrientation::NONE;
815 switch (rProperty.second.toInt32())
817 case 0:
818 nHoriOrient = text::HoriOrientation::LEFT;
819 break;
820 case 1:
821 nHoriOrient = text::HoriOrientation::CENTER;
822 break;
823 case 2:
824 nHoriOrient = text::HoriOrientation::RIGHT;
825 break;
827 if (xPropertySet.is() && text::HoriOrientation::NONE != nHoriOrient)
829 xPropertySet->setPropertyValue("HoriOrient", uno::Any(nHoriOrient));
832 else if (rProperty.first == "pWrapPolygonVertices")
834 RTFSprms aPolygonSprms;
835 sal_Int32 nSize = 0; // Size of a token
836 sal_Int32 nCount = 0; // Number of tokens
837 sal_Int32 nCharIndex = 0; // Character index
840 std::u16string_view aToken = o3tl::getToken(rProperty.second, 0, ';', nCharIndex);
841 if (!nSize)
842 nSize = o3tl::toInt32(aToken);
843 else if (!nCount)
844 nCount = o3tl::toInt32(aToken);
845 else if (!aToken.empty())
847 // The coordinates are in an (x,y) form.
848 aToken = aToken.substr(1, aToken.size() - 2);
849 sal_Int32 nI = 0;
850 sal_Int32 nX = o3tl::toInt32(o3tl::getToken(aToken, 0, ',', nI));
851 sal_Int32 nY
852 = (nI >= 0) ? o3tl::toInt32(o3tl::getToken(aToken, 0, ',', nI)) : 0;
853 RTFSprms aPathAttributes;
854 aPathAttributes.set(NS_ooxml::LN_CT_Point2D_x, new RTFValue(nX));
855 aPathAttributes.set(NS_ooxml::LN_CT_Point2D_y, new RTFValue(nY));
856 aPolygonSprms.set(NS_ooxml::LN_CT_WrapPath_lineTo,
857 new RTFValue(aPathAttributes), RTFOverwrite::NO_APPEND);
859 } while (nCharIndex >= 0);
860 rShape.getWrapPolygonSprms() = aPolygonSprms;
862 else if (rProperty.first == "fRelFlipV")
863 obRelFlipV = rProperty.second.toInt32() == 1;
864 else if (rProperty.first == "fFlipH")
865 obFlipH = rProperty.second.toInt32() == 1;
866 else if (rProperty.first == "fFlipV")
867 obFlipV = rProperty.second.toInt32() == 1;
868 else if (rProperty.first == "pictureContrast")
870 // Gain / contrast.
871 nContrast = rProperty.second.toInt32();
872 if (nContrast < 0x10000)
874 nContrast *= 101; // 100 + 1 to round
875 nContrast /= 0x10000;
876 nContrast -= 100;
879 else if (rProperty.first == "pictureBrightness")
881 // Blacklevel / brightness.
882 nBrightness = rProperty.second.toInt32();
883 if (nBrightness != 0)
885 nBrightness /= 327;
888 else
889 SAL_INFO("writerfilter", "TODO handle shape property '" << rProperty.first << "':'"
890 << rProperty.second << "'");
893 if (xPropertySet.is())
895 resolveLineColorAndWidth(m_bTextFrame, xPropertySet, aLineColor, aLineWidth);
896 if (rShape.hasZ())
898 bool bOldStyle = m_aParents.size() > 1;
899 resolveDhgt(xPropertySet, rShape.getZ(), bOldStyle);
901 if (m_bTextFrame)
902 xPropertySet->setPropertyValue("WritingMode", uno::Any(eWritingMode));
903 else
904 // Only Writer textframes implement text::WritingMode2.
905 xPropertySet->setPropertyValue("TextWritingMode",
906 uno::Any(text::WritingMode(eWritingMode)));
909 if (!m_aParents.empty() && m_aParents.top().is() && !m_bTextFrame)
910 m_aParents.top()->add(xShape);
912 if (nContrast == -70 && nBrightness == 70 && xPropertySet.is())
914 // Map MSO 'washout' to our watermark colormode.
915 xPropertySet->setPropertyValue("GraphicColorMode", uno::Any(drawing::ColorMode_WATERMARK));
918 if (bCustom && xShape.is() && !bPib)
920 uno::Reference<drawing::XEnhancedCustomShapeDefaulter> xDefaulter(xShape, uno::UNO_QUERY);
921 xDefaulter->createCustomShapeDefaults(OUString::number(nType));
924 // Set shape text
925 if (bCustom && !aShapeText.isEmpty())
927 uno::Reference<text::XTextRange> xTextRange(xShape, uno::UNO_QUERY);
928 if (xTextRange.is())
929 xTextRange->setString(aShapeText);
931 xPropertySet->setPropertyValue("CharFontName", uno::Any(aFontFamily));
932 xPropertySet->setPropertyValue("CharHeight", uno::Any(nFontSize));
935 // Creating CustomShapeGeometry property
936 if (bCustom && xPropertySet.is())
938 bool bChanged = false;
939 comphelper::SequenceAsHashMap aCustomShapeGeometry(
940 xPropertySet->getPropertyValue("CustomShapeGeometry"));
942 if (aViewBox.X || aViewBox.Y || aViewBox.Width || aViewBox.Height)
944 aViewBox.Width -= aViewBox.X;
945 aViewBox.Height -= aViewBox.Y;
946 aCustomShapeGeometry["ViewBox"] <<= aViewBox;
947 bChanged = true;
950 if (!aPath.empty())
952 aCustomShapeGeometry["Path"] <<= comphelper::containerToSequence(aPath);
953 bChanged = true;
956 if (!aShapeText.isEmpty())
958 uno::Sequence<beans::PropertyValue> aSequence(comphelper::InitPropertySequence({
959 { "TextPath", uno::Any(true) },
960 }));
961 aCustomShapeGeometry["TextPath"] <<= aSequence;
962 xPropertySet->setPropertyValue("TextAutoGrowHeight", uno::Any(false));
963 xPropertySet->setPropertyValue("TextAutoGrowWidth", uno::Any(false));
964 bChanged = true;
967 if (bChanged)
969 xPropertySet->setPropertyValue(
970 "CustomShapeGeometry",
971 uno::Any(aCustomShapeGeometry.getAsConstPropertyValueList()));
975 if (obRelFlipV.has_value() && xPropertySet.is())
977 if (nType == ESCHER_ShpInst_Line)
979 // Line shape inside group shape: get the polygon sequence and transform it.
980 uno::Sequence<uno::Sequence<awt::Point>> aPolyPolySequence;
981 if ((xPropertySet->getPropertyValue("PolyPolygon") >>= aPolyPolySequence)
982 && aPolyPolySequence.hasElements())
984 uno::Sequence<awt::Point>& rPolygon = aPolyPolySequence.getArray()[0];
985 basegfx::B2DPolygon aPoly;
986 for (const awt::Point& rPoint : std::as_const(rPolygon))
988 aPoly.append(basegfx::B2DPoint(rPoint.X, rPoint.Y));
990 basegfx::B2DHomMatrix aTransformation;
991 aTransformation.scale(1.0, *obRelFlipV ? -1.0 : 1.0);
992 aPoly.transform(aTransformation);
993 auto pPolygon = rPolygon.getArray();
994 for (sal_Int32 i = 0; i < rPolygon.getLength(); ++i)
996 basegfx::B2DPoint aPoint(aPoly.getB2DPoint(i));
997 pPolygon[i] = awt::Point(static_cast<sal_Int32>(aPoint.getX()),
998 static_cast<sal_Int32>(aPoint.getY()));
1000 xPropertySet->setPropertyValue("PolyPolygon", uno::Any(aPolyPolySequence));
1005 // Set position and size
1006 if (xShape.is())
1008 sal_Int32 nLeft = rShape.getLeft();
1009 sal_Int32 nTop = rShape.getTop();
1011 bool bInShapeGroup = oGroupLeft && oGroupTop && oGroupRight && oGroupBottom && oRelLeft
1012 && oRelTop && oRelRight && oRelBottom;
1013 awt::Size aSize;
1014 if (bInShapeGroup)
1016 // See lclGetAbsPoint() in the VML import: rShape is the group shape, oGroup is its coordinate system, oRel is the relative child shape.
1017 sal_Int32 nShapeWidth = rShape.getRight() - rShape.getLeft();
1018 sal_Int32 nShapeHeight = rShape.getBottom() - rShape.getTop();
1019 sal_Int32 nCoordSysWidth = *oGroupRight - *oGroupLeft;
1020 sal_Int32 nCoordSysHeight = *oGroupBottom - *oGroupTop;
1021 double fWidthRatio = static_cast<double>(nShapeWidth) / nCoordSysWidth;
1022 double fHeightRatio = static_cast<double>(nShapeHeight) / nCoordSysHeight;
1023 nLeft = static_cast<sal_Int32>(rShape.getLeft()
1024 + fWidthRatio * (*oRelLeft - *oGroupLeft));
1025 nTop = static_cast<sal_Int32>(rShape.getTop() + fHeightRatio * (*oRelTop - *oGroupTop));
1027 // See lclGetAbsRect() in the VML import.
1028 aSize.Width = std::lround(fWidthRatio * (*oRelRight - *oRelLeft));
1029 aSize.Height = std::lround(fHeightRatio * (*oRelBottom - *oRelTop));
1032 if (m_bTextFrame)
1034 xPropertySet->setPropertyValue("HoriOrientPosition", uno::Any(nLeft));
1035 xPropertySet->setPropertyValue("VertOrientPosition", uno::Any(nTop));
1037 else
1038 xShape->setPosition(awt::Point(nLeft, nTop));
1040 if (bInShapeGroup)
1041 xShape->setSize(aSize);
1042 else
1043 xShape->setSize(awt::Size(rShape.getRight() - rShape.getLeft(),
1044 rShape.getBottom() - rShape.getTop()));
1046 if (obFlipH || obFlipV)
1048 if (bCustom)
1050 // This has to be set after position and size is set, otherwise flip will affect the position.
1051 comphelper::SequenceAsHashMap aCustomShapeGeometry(
1052 xPropertySet->getPropertyValue("CustomShapeGeometry"));
1053 if (obFlipH)
1054 aCustomShapeGeometry["MirroredX"] <<= true;
1055 if (obFlipV)
1056 aCustomShapeGeometry["MirroredY"] <<= true;
1057 xPropertySet->setPropertyValue(
1058 "CustomShapeGeometry",
1059 uno::Any(aCustomShapeGeometry.getAsConstPropertyValueList()));
1061 else if (SdrObject* pObject = SdrObject::getSdrObjectFromXShape(xShape))
1063 Point aRef1 = pObject->GetSnapRect().Center();
1064 Point aRef2(aRef1);
1065 if (obFlipH)
1067 // Horizontal mirror means a vertical reference line.
1068 aRef2.AdjustY(1);
1070 if (obFlipV)
1072 // Vertical mirror means a horizontal reference line.
1073 aRef2.AdjustX(1);
1075 pObject->Mirror(aRef1, aRef2);
1079 if (rShape.getHoriOrientRelation() != 0)
1080 xPropertySet->setPropertyValue("HoriOrientRelation",
1081 uno::Any(rShape.getHoriOrientRelation()));
1082 if (rShape.getVertOrientRelation() != 0)
1083 xPropertySet->setPropertyValue("VertOrientRelation",
1084 uno::Any(rShape.getVertOrientRelation()));
1085 if (rShape.getWrap() != text::WrapTextMode::WrapTextMode_MAKE_FIXED_SIZE)
1086 xPropertySet->setPropertyValue("Surround", uno::Any(rShape.getWrap()));
1087 oox::ModelObjectHelper aModelObjectHelper(m_rImport.getModelFactory());
1088 if (aFillModel.moType.has_value())
1090 oox::drawingml::ShapePropertyMap aPropMap(aModelObjectHelper);
1091 aFillModel.pushToPropMap(aPropMap, m_rImport.getGraphicHelper());
1092 // Sets the FillStyle and FillGradient UNO properties.
1093 oox::PropertySet(xShape).setProperties(aPropMap);
1096 if (aShadowModel.mbHasShadow)
1098 oox::drawingml::ShapePropertyMap aPropMap(aModelObjectHelper);
1099 aShadowModel.pushToPropMap(aPropMap, m_rImport.getGraphicHelper());
1100 // Sets the ShadowFormat UNO property.
1101 oox::PropertySet(xShape).setProperties(aPropMap);
1103 xPropertySet->setPropertyValue("AnchorType",
1104 uno::Any(text::TextContentAnchorType_AT_CHARACTER));
1105 xPropertySet->setPropertyValue("Opaque", uno::Any(bOpaque));
1106 if (oRelativeWidth)
1108 xPropertySet->setPropertyValue("RelativeWidth", uno::Any(*oRelativeWidth));
1109 xPropertySet->setPropertyValue("RelativeWidthRelation",
1110 uno::Any(nRelativeWidthRelation));
1112 if (oRelativeHeight)
1114 xPropertySet->setPropertyValue("RelativeHeight", uno::Any(*oRelativeHeight));
1115 xPropertySet->setPropertyValue("RelativeHeightRelation",
1116 uno::Any(nRelativeHeightRelation));
1120 if (bPib)
1122 m_rImport.resolvePict(false, xShape);
1125 if (nType == ESCHER_ShpInst_PictureFrame) // picture frame
1127 assert(!m_bTextFrame);
1128 if (!bPib) // ??? not sure if the early return should be removed on else?
1130 m_xShape = xShape; // store it for later resolvePict call
1133 // Handle horizontal flip.
1134 if (obFlipH && xPropertySet.is())
1135 xPropertySet->setPropertyValue("IsMirrored", uno::Any(true));
1136 return;
1139 if (m_rImport.isInBackground())
1141 RTFSprms aAttributes;
1142 aAttributes.set(NS_ooxml::LN_CT_Background_color,
1143 new RTFValue(xPropertySet->getPropertyValue("FillColor").get<sal_Int32>()));
1144 m_rImport.Mapper().props(new RTFReferenceProperties(std::move(aAttributes)));
1146 uno::Reference<lang::XComponent> xComponent(xShape, uno::UNO_QUERY);
1147 xComponent->dispose();
1148 return;
1151 // Send it to dmapper
1152 if (xShape.is())
1154 m_rImport.Mapper().startShape(xShape);
1155 if (bClose)
1157 m_rImport.Mapper().endShape();
1161 // If the shape has an inner shape, the inner object's properties should not be influenced by
1162 // the outer one.
1163 rShape.getProperties().clear();
1165 m_xShape = xShape;
1168 void RTFSdrImport::close() { m_rImport.Mapper().endShape(); }
1170 void RTFSdrImport::append(std::u16string_view aKey, std::u16string_view aValue)
1172 applyProperty(m_xShape, aKey, aValue);
1175 void RTFSdrImport::appendGroupProperty(std::u16string_view aKey, std::u16string_view aValue)
1177 if (m_aParents.empty())
1178 return;
1179 uno::Reference<drawing::XShape> xShape(m_aParents.top(), uno::UNO_QUERY);
1180 if (xShape.is())
1181 applyProperty(xShape, aKey, aValue);
1184 } // namespace writerfilter
1186 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */