LibreOffice Module oox (master)  1
drawingml.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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  *
9  * This file incorporates work covered by the following license notice:
10  *
11  * Licensed to the Apache Software Foundation (ASF) under one or more
12  * contributor license agreements. See the NOTICE file distributed
13  * with this work for additional information regarding copyright
14  * ownership. The ASF licenses this file to you under the Apache
15  * License, Version 2.0 (the "License"); you may not use this file
16  * except in compliance with the License. You may obtain a copy of
17  * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <config_features.h>
21 
22 #include <config_folders.h>
23 #include <rtl/bootstrap.hxx>
24 #include <sal/log.hxx>
26 #include <oox/export/drawingml.hxx>
27 #include <oox/export/utils.hxx>
29 #include <oox/drawingml/color.hxx>
32 #include <oox/token/namespaces.hxx>
33 #include <oox/token/properties.hxx>
35 #include <oox/token/tokens.hxx>
37 #include <svtools/unitconv.hxx>
38 #include <sax/fastattribs.hxx>
39 #include <tools/diagnose_ex.h>
44 
45 #include <numeric>
46 #include <string_view>
47 
48 #include <com/sun/star/awt/CharSet.hpp>
49 #include <com/sun/star/awt/FontDescriptor.hpp>
50 #include <com/sun/star/awt/FontSlant.hpp>
51 #include <com/sun/star/awt/FontStrikeout.hpp>
52 #include <com/sun/star/awt/FontWeight.hpp>
53 #include <com/sun/star/awt/FontUnderline.hpp>
54 #include <com/sun/star/awt/Gradient.hpp>
55 #include <com/sun/star/beans/XPropertySet.hpp>
56 #include <com/sun/star/beans/XPropertyState.hpp>
57 #include <com/sun/star/beans/XPropertySetInfo.hpp>
58 #include <com/sun/star/container/XEnumerationAccess.hpp>
59 #include <com/sun/star/container/XIndexAccess.hpp>
60 #include <com/sun/star/container/XNameAccess.hpp>
61 #include <com/sun/star/drawing/BitmapMode.hpp>
62 #include <com/sun/star/drawing/ColorMode.hpp>
63 #include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp>
64 #include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
65 #include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp>
66 #include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp>
67 #include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp>
68 #include <com/sun/star/drawing/Hatch.hpp>
69 #include <com/sun/star/drawing/LineDash.hpp>
70 #include <com/sun/star/drawing/LineJoint.hpp>
71 #include <com/sun/star/drawing/LineStyle.hpp>
72 #include <com/sun/star/drawing/TextFitToSizeType.hpp>
73 #include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
74 #include <com/sun/star/drawing/TextVerticalAdjust.hpp>
75 #include <com/sun/star/drawing/XShape.hpp>
76 #include <com/sun/star/drawing/XShapes.hpp>
77 #include <com/sun/star/drawing/FillStyle.hpp>
78 #include <com/sun/star/frame/XModel.hpp>
79 #include <com/sun/star/graphic/XGraphic.hpp>
80 #include <com/sun/star/i18n/ScriptType.hpp>
81 #include <com/sun/star/i18n/BreakIterator.hpp>
82 #include <com/sun/star/i18n/XBreakIterator.hpp>
83 #include <com/sun/star/io/XOutputStream.hpp>
84 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
85 #include <com/sun/star/style/LineSpacing.hpp>
86 #include <com/sun/star/style/LineSpacingMode.hpp>
87 #include <com/sun/star/text/WritingMode.hpp>
88 #include <com/sun/star/text/WritingMode2.hpp>
89 #include <com/sun/star/text/GraphicCrop.hpp>
90 #include <com/sun/star/text/XText.hpp>
91 #include <com/sun/star/text/XTextColumns.hpp>
92 #include <com/sun/star/text/XTextContent.hpp>
93 #include <com/sun/star/text/XTextField.hpp>
94 #include <com/sun/star/text/XTextRange.hpp>
95 #include <com/sun/star/text/XTextFrame.hpp>
96 #include <com/sun/star/style/CaseMap.hpp>
97 #include <com/sun/star/xml/dom/XNodeList.hpp>
98 #include <com/sun/star/xml/sax/Writer.hpp>
99 #include <com/sun/star/xml/sax/XSAXSerializable.hpp>
100 #include <com/sun/star/container/XNamed.hpp>
101 #include <com/sun/star/drawing/XDrawPages.hpp>
102 #include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
103 
105 #include <comphelper/random.hxx>
106 #include <comphelper/seqstream.hxx>
108 #include <comphelper/xmltools.hxx>
109 #include <o3tl/any.hxx>
110 #include <o3tl/safeint.hxx>
111 #include <o3tl/string_view.hxx>
112 #include <tools/stream.hxx>
113 #include <unotools/fontdefs.hxx>
114 #include <vcl/cvtgrf.hxx>
115 #include <vcl/svapp.hxx>
116 #include <rtl/strbuf.hxx>
118 #include <filter/msfilter/util.hxx>
119 #include <editeng/outlobj.hxx>
120 #include <editeng/svxenum.hxx>
121 #include <editeng/unonames.hxx>
122 #include <editeng/unoprnms.hxx>
123 #include <editeng/flditem.hxx>
125 #include <svx/svdoashp.hxx>
126 #include <svx/svdomedia.hxx>
127 #include <svx/unoshape.hxx>
130 
131 using namespace ::css;
132 using namespace ::css::beans;
133 using namespace ::css::drawing;
134 using namespace ::css::i18n;
135 using namespace ::css::style;
136 using namespace ::css::text;
137 using namespace ::css::uno;
138 using namespace ::css::container;
139 using namespace ::com::sun::star::drawing::EnhancedCustomShapeSegmentCommand;
140 
141 using ::css::io::XOutputStream;
142 using ::sax_fastparser::FSHelperPtr;
143 using ::sax_fastparser::FastSerializerHelper;
144 
145 namespace
146 {
148 sal_Int32 GetAlphaFromTransparenceGradient(const awt::Gradient& rGradient, bool bStart)
149 {
150  // Our alpha is a gray color value.
151  sal_uInt8 nRed = ::Color(ColorTransparency, bStart ? rGradient.StartColor : rGradient.EndColor).GetRed();
152  // drawingML alpha is a percentage on a 0..100000 scale.
153  return (255 - nRed) * oox::drawingml::MAX_PERCENT / 255;
154 }
155 
156 const char* g_aPredefinedClrNames[] = {
157  "dk1",
158  "lt1",
159  "dk2",
160  "lt2",
161  "accent1",
162  "accent2",
163  "accent3",
164  "accent4",
165  "accent5",
166  "accent6",
167  "hlink",
168  "folHlink",
169 };
170 }
171 
172 namespace oox::drawingml {
173 
175 {
176 }
177 
178 OUString URLTransformer::getTransformedString(const OUString& rString) const
179 {
180  return rString;
181 }
182 
183 bool URLTransformer::isExternalURL(const OUString& rURL) const
184 {
185  bool bExternal = true;
186  if (rURL.startsWith("#"))
187  bExternal = false;
188  return bExternal;
189 }
190 
191 static css::uno::Any getLineDash( const css::uno::Reference<css::frame::XModel>& xModel, const OUString& rDashName )
192  {
193  css::uno::Reference<css::lang::XMultiServiceFactory> xFact(xModel, css::uno::UNO_QUERY);
194  css::uno::Reference<css::container::XNameAccess> xNameAccess(
195  xFact->createInstance("com.sun.star.drawing.DashTable"),
196  css::uno::UNO_QUERY );
197  if(xNameAccess.is())
198  {
199  if (!xNameAccess->hasByName(rDashName))
200  return css::uno::Any();
201 
202  return xNameAccess->getByName(rDashName);
203  }
204 
205  return css::uno::Any();
206  }
207 
208 namespace
209 {
210 void WriteGradientPath(const awt::Gradient& rGradient, const FSHelperPtr& pFS, const bool bCircle)
211 {
212  pFS->startElementNS(XML_a, XML_path, XML_path, bCircle ? "circle" : "rect");
213 
214  // Write the focus rectangle. Work with the focus point, and assume
215  // that it extends 50% in all directions. The below
216  // left/top/right/bottom values are percentages, where 0 means the
217  // edge of the tile rectangle and 100% means the center of it.
220  sal_Int32 nLeftPercent = rGradient.XOffset;
221  pAttributeList->add(XML_l, OString::number(nLeftPercent * PER_PERCENT));
222  sal_Int32 nTopPercent = rGradient.YOffset;
223  pAttributeList->add(XML_t, OString::number(nTopPercent * PER_PERCENT));
224  sal_Int32 nRightPercent = 100 - rGradient.XOffset;
225  pAttributeList->add(XML_r, OString::number(nRightPercent * PER_PERCENT));
226  sal_Int32 nBottomPercent = 100 - rGradient.YOffset;
227  pAttributeList->add(XML_b, OString::number(nBottomPercent * PER_PERCENT));
228  pFS->singleElementNS(XML_a, XML_fillToRect, pAttributeList);
229 
230  pFS->endElementNS(XML_a, XML_path);
231 }
232 }
233 
234 // not thread safe
237 std::map<OUString, OUString> DrawingML::maWdpCache;
238 sal_Int32 DrawingML::mnDrawingMLCount = 0;
239 sal_Int32 DrawingML::mnVmlCount = 0;
240 std::stack<std::unordered_map<BitmapChecksum, OUString>> DrawingML::maExportGraphics;
241 
242 sal_Int16 DrawingML::GetScriptType(const OUString& rStr)
243 {
244  if (rStr.getLength() > 0)
245  {
246  static Reference<css::i18n::XBreakIterator> xBreakIterator =
247  css::i18n::BreakIterator::create(comphelper::getProcessComponentContext());
248 
249  sal_Int16 nScriptType = xBreakIterator->getScriptType(rStr, 0);
250 
251  if (nScriptType == css::i18n::ScriptType::WEAK)
252  {
253  sal_Int32 nPos = xBreakIterator->nextScript(rStr, 0, nScriptType);
254  if (nPos < rStr.getLength())
255  nScriptType = xBreakIterator->getScriptType(rStr, nPos);
256 
257  }
258 
259  if (nScriptType != css::i18n::ScriptType::WEAK)
260  return nScriptType;
261  }
262 
263  return css::i18n::ScriptType::LATIN;
264 }
265 
267 {
268  mnImageCounter = 1;
269  mnWdpImageCounter = 1;
270  maWdpCache.clear();
271 }
272 
274 {
275  mnDrawingMLCount = 0;
276  mnVmlCount = 0;
277 }
278 
280 {
281  maExportGraphics.emplace();
282 }
283 
285 {
286  maExportGraphics.pop();
287 }
288 
289 bool DrawingML::GetProperty( const Reference< XPropertySet >& rXPropertySet, const OUString& aName )
290 {
291  try
292  {
293  mAny = rXPropertySet->getPropertyValue(aName);
294  if (mAny.hasValue())
295  return true;
296  }
297  catch( const Exception& )
298  {
299  /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
300  }
301  return false;
302 }
303 
304 bool DrawingML::GetPropertyAndState( const Reference< XPropertySet >& rXPropertySet, const Reference< XPropertyState >& rXPropertyState, const OUString& aName, PropertyState& eState )
305 {
306  try
307  {
308  mAny = rXPropertySet->getPropertyValue(aName);
309  if (mAny.hasValue())
310  {
311  eState = rXPropertyState->getPropertyState(aName);
312  return true;
313  }
314  }
315  catch( const Exception& )
316  {
317  /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
318  }
319  return false;
320 }
321 
322 namespace
323 {
325 OString getColorStr(const ::Color nColor)
326 {
327  // Transparency is a separate element.
328  OString sColor = OString::number(sal_uInt32(nColor) & 0x00FFFFFF, 16);
329  if (sColor.getLength() < 6)
330  {
331  OStringBuffer sBuf("0");
332  int remains = 5 - sColor.getLength();
333 
334  while (remains > 0)
335  {
336  sBuf.append("0");
337  remains--;
338  }
339 
340  sBuf.append(sColor);
341 
342  sColor = sBuf.getStr();
343  }
344  return sColor;
345 }
346 }
347 
348 void DrawingML::WriteColor( ::Color nColor, sal_Int32 nAlpha )
349 {
350  const auto sColor = getColorStr(nColor);
351  if( nAlpha < MAX_PERCENT )
352  {
353  mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
354  mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
355  mpFS->endElementNS( XML_a, XML_srgbClr );
356 
357  }
358  else
359  {
360  mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor);
361  }
362 }
363 
364 void DrawingML::WriteColor( const OUString& sColorSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
365 {
366  // prevent writing a tag with empty val attribute
367  if( sColorSchemeName.isEmpty() )
368  return;
369 
370  if( aTransformations.hasElements() )
371  {
372  mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
373  WriteColorTransformations( aTransformations, nAlpha );
374  mpFS->endElementNS( XML_a, XML_schemeClr );
375  }
376  else if(nAlpha < MAX_PERCENT)
377  {
378  mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
379  mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
380  mpFS->endElementNS( XML_a, XML_schemeClr );
381  }
382  else
383  {
384  mpFS->singleElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
385  }
386 }
387 
388 void DrawingML::WriteColor( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
389 {
390  const auto sColor = getColorStr(nColor);
391  if( aTransformations.hasElements() )
392  {
393  mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
394  WriteColorTransformations(aTransformations, nAlpha);
395  mpFS->endElementNS(XML_a, XML_srgbClr);
396  }
397  else if(nAlpha < MAX_PERCENT)
398  {
399  mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
400  mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
401  mpFS->endElementNS(XML_a, XML_srgbClr);
402  }
403  else
404  {
405  mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor);
406  }
407 }
408 
409 void DrawingML::WriteColorTransformations( const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
410 {
411  for( const auto& rTransformation : aTransformations )
412  {
413  sal_Int32 nToken = Color::getColorTransformationToken( rTransformation.Name );
414  if( nToken != XML_TOKEN_INVALID && rTransformation.Value.hasValue() )
415  {
416  if(nToken == XML_alpha && nAlpha < MAX_PERCENT)
417  {
418  mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nAlpha));
419  }
420  else
421  {
422  sal_Int32 nValue = rTransformation.Value.get<sal_Int32>();
423  mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nValue));
424  }
425  }
426  }
427 }
428 
429 bool DrawingML::WriteCharColor(const css::uno::Reference<css::beans::XPropertySet>& xPropertySet)
430 {
431  if (!xPropertySet->getPropertySetInfo()->hasPropertyByName("CharColorTheme"))
432  {
433  return false;
434  }
435 
436  sal_Int32 nCharColorTheme = -1;
437  xPropertySet->getPropertyValue("CharColorTheme") >>= nCharColorTheme;
438  if (nCharColorTheme < 0 || nCharColorTheme > 11)
439  {
440  return false;
441  }
442 
443  const char* pColorName = g_aPredefinedClrNames[nCharColorTheme];
444 
445  sal_Int32 nCharColorTintOrShade{};
446  xPropertySet->getPropertyValue("CharColorTintOrShade") >>= nCharColorTintOrShade;
447  if (nCharColorTintOrShade != 0)
448  {
449  return false;
450  }
451 
452  mpFS->startElementNS(XML_a, XML_solidFill);
453  mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, pColorName);
454 
455  sal_Int32 nCharColorLumMod{};
456  xPropertySet->getPropertyValue("CharColorLumMod") >>= nCharColorLumMod;
457  if (nCharColorLumMod != 10000)
458  {
459  mpFS->singleElementNS(XML_a, XML_lumMod, XML_val, OString::number(nCharColorLumMod * 10));
460  }
461 
462  sal_Int32 nCharColorLumOff{};
463  xPropertySet->getPropertyValue("CharColorLumOff") >>= nCharColorLumOff;
464  if (nCharColorLumOff != 0)
465  {
466  mpFS->singleElementNS(XML_a, XML_lumOff, XML_val, OString::number(nCharColorLumOff * 10));
467  }
468 
469  mpFS->endElementNS(XML_a, XML_schemeClr);
470  mpFS->endElementNS(XML_a, XML_solidFill);
471 
472  return true;
473 }
474 
475 void DrawingML::WriteSolidFill( ::Color nColor, sal_Int32 nAlpha )
476 {
477  mpFS->startElementNS(XML_a, XML_solidFill);
478  WriteColor( nColor, nAlpha );
479  mpFS->endElementNS( XML_a, XML_solidFill );
480 }
481 
482 void DrawingML::WriteSolidFill( const OUString& sSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
483 {
484  mpFS->startElementNS(XML_a, XML_solidFill);
485  WriteColor( sSchemeName, aTransformations, nAlpha );
486  mpFS->endElementNS( XML_a, XML_solidFill );
487 }
488 
489 void DrawingML::WriteSolidFill( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
490 {
491  mpFS->startElementNS(XML_a, XML_solidFill);
492  WriteColor(nColor, aTransformations, nAlpha);
493  mpFS->endElementNS(XML_a, XML_solidFill);
494 }
495 
497 {
498  // get fill color
499  if ( !GetProperty( rXPropSet, "FillColor" ) )
500  return;
501  sal_uInt32 nFillColor = mAny.get<sal_uInt32>();
502 
503  // get InteropGrabBag and search the relevant attributes
504  OUString sColorFillScheme;
505  sal_uInt32 nOriginalColor = 0;
506  Sequence< PropertyValue > aStyleProperties, aTransformations;
507  if ( GetProperty( rXPropSet, "InteropGrabBag" ) )
508  {
509  Sequence< PropertyValue > aGrabBag;
510  mAny >>= aGrabBag;
511  for( const auto& rProp : std::as_const(aGrabBag) )
512  {
513  if( rProp.Name == "SpPrSolidFillSchemeClr" )
514  rProp.Value >>= sColorFillScheme;
515  else if( rProp.Name == "OriginalSolidFillClr" )
516  rProp.Value >>= nOriginalColor;
517  else if( rProp.Name == "StyleFillRef" )
518  rProp.Value >>= aStyleProperties;
519  else if( rProp.Name == "SpPrSolidFillSchemeClrTransformations" )
520  rProp.Value >>= aTransformations;
521  }
522  }
523 
524  sal_Int32 nAlpha = MAX_PERCENT;
525  if( GetProperty( rXPropSet, "FillTransparence" ) )
526  {
527  sal_Int32 nTransparency = 0;
528  mAny >>= nTransparency;
529  // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
530  nAlpha = (MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
531  }
532 
533  // OOXML has no separate transparence gradient but uses transparency in the gradient stops.
534  // So we merge transparency and color and use gradient fill in such case.
535  awt::Gradient aTransparenceGradient;
536  bool bNeedGradientFill(false);
537  if (GetProperty(rXPropSet, "FillTransparenceGradient"))
538  {
539  mAny >>= aTransparenceGradient;
540  if (aTransparenceGradient.StartColor != aTransparenceGradient.EndColor)
541  bNeedGradientFill = true;
542  else if (aTransparenceGradient.StartColor != 0)
543  nAlpha = GetAlphaFromTransparenceGradient(aTransparenceGradient, true);
544  }
545 
546  // write XML
547  if (bNeedGradientFill)
548  {
549  awt::Gradient aPseudoColorGradient;
550  aPseudoColorGradient.XOffset = aTransparenceGradient.XOffset;
551  aPseudoColorGradient.YOffset = aTransparenceGradient.YOffset;
552  aPseudoColorGradient.StartIntensity = 100;
553  aPseudoColorGradient.EndIntensity = 100;
554  aPseudoColorGradient.Angle = aTransparenceGradient.Angle;
555  aPseudoColorGradient.Border = aTransparenceGradient.Border;
556  aPseudoColorGradient.Style = aTransparenceGradient.Style;
557  aPseudoColorGradient.StartColor = nFillColor;
558  aPseudoColorGradient.EndColor = nFillColor;
559  aPseudoColorGradient.StepCount = aTransparenceGradient.StepCount;
560  mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
561  WriteGradientFill(aPseudoColorGradient, aTransparenceGradient);
562  mpFS->endElementNS( XML_a, XML_gradFill );
563  }
564  else if ( nFillColor != nOriginalColor )
565  {
566  // the user has set a different color for the shape
567  if (!WriteFillColor(rXPropSet))
568  {
569  WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha);
570  }
571  }
572  else if ( !sColorFillScheme.isEmpty() )
573  {
574  // the shape had a scheme color and the user didn't change it
575  WriteSolidFill( sColorFillScheme, aTransformations, nAlpha );
576  }
577  else
578  {
579  // the shape had a custom color and the user didn't change it
580  // tdf#124013
581  WriteSolidFill( ::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha );
582  }
583 }
584 
585 bool DrawingML::WriteFillColor(const uno::Reference<beans::XPropertySet>& xPropertySet)
586 {
587  if (!xPropertySet->getPropertySetInfo()->hasPropertyByName("FillColorTheme"))
588  {
589  return false;
590  }
591 
592  sal_Int32 nFillColorTheme = -1;
593  xPropertySet->getPropertyValue("FillColorTheme") >>= nFillColorTheme;
594  if (nFillColorTheme < 0 || nFillColorTheme > 11)
595  {
596  return false;
597  }
598 
599  const char* pColorName = g_aPredefinedClrNames[nFillColorTheme];
600 
601  mpFS->startElementNS(XML_a, XML_solidFill);
602  mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, pColorName);
603 
604  sal_Int32 nFillColorLumMod{};
605  xPropertySet->getPropertyValue("FillColorLumMod") >>= nFillColorLumMod;
606  if (nFillColorLumMod != 10000)
607  {
608  mpFS->singleElementNS(XML_a, XML_lumMod, XML_val, OString::number(nFillColorLumMod * 10));
609  }
610 
611  sal_Int32 nFillColorLumOff{};
612  xPropertySet->getPropertyValue("FillColorLumOff") >>= nFillColorLumOff;
613  if (nFillColorLumOff != 0)
614  {
615  mpFS->singleElementNS(XML_a, XML_lumOff, XML_val, OString::number(nFillColorLumOff * 10));
616  }
617 
618  mpFS->endElementNS(XML_a, XML_schemeClr);
619  mpFS->endElementNS(XML_a, XML_solidFill);
620 
621  return true;
622 }
623 
624 void DrawingML::WriteGradientStop(sal_uInt16 nStop, ::Color nColor, sal_Int32 nAlpha)
625 {
626  mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nStop * 1000));
627  WriteColor(nColor, nAlpha);
628  mpFS->endElementNS( XML_a, XML_gs );
629 }
630 
631 ::Color DrawingML::ColorWithIntensity( sal_uInt32 nColor, sal_uInt32 nIntensity )
632 {
633  return ::Color(ColorTransparency, ( ( ( nColor & 0xff ) * nIntensity ) / 100 )
634  | ( ( ( ( ( nColor & 0xff00 ) >> 8 ) * nIntensity ) / 100 ) << 8 )
635  | ( ( ( ( ( nColor & 0xff0000 ) >> 8 ) * nIntensity ) / 100 ) << 8 ));
636 }
637 
638 bool DrawingML::EqualGradients( awt::Gradient aGradient1, awt::Gradient aGradient2 )
639 {
640  return aGradient1.Style == aGradient2.Style &&
641  aGradient1.StartColor == aGradient2.StartColor &&
642  aGradient1.EndColor == aGradient2.EndColor &&
643  aGradient1.Angle == aGradient2.Angle &&
644  aGradient1.Border == aGradient2.Border &&
645  aGradient1.XOffset == aGradient2.XOffset &&
646  aGradient1.YOffset == aGradient2.YOffset &&
647  aGradient1.StartIntensity == aGradient2.StartIntensity &&
648  aGradient1.EndIntensity == aGradient2.EndIntensity &&
649  aGradient1.StepCount == aGradient2.StepCount;
650 }
651 
653 {
654  awt::Gradient aGradient;
655  if (!GetProperty(rXPropSet, "FillGradient"))
656  return;
657 
658  aGradient = *o3tl::doAccess<awt::Gradient>(mAny);
659 
660  // get InteropGrabBag and search the relevant attributes
661  awt::Gradient aOriginalGradient;
662  Sequence< PropertyValue > aGradientStops;
663  if ( GetProperty( rXPropSet, "InteropGrabBag" ) )
664  {
665  Sequence< PropertyValue > aGrabBag;
666  mAny >>= aGrabBag;
667  for( const auto& rProp : std::as_const(aGrabBag) )
668  if( rProp.Name == "GradFillDefinition" )
669  rProp.Value >>= aGradientStops;
670  else if( rProp.Name == "OriginalGradFill" )
671  rProp.Value >>= aOriginalGradient;
672  }
673 
674  // check if an ooxml gradient had been imported and if the user has modified it
675  // Gradient grab-bag depends on theme grab-bag, which is implemented
676  // only for DOCX.
677  if( EqualGradients( aOriginalGradient, aGradient ) && GetDocumentType() == DOCUMENT_DOCX)
678  {
679  // If we have no gradient stops that means original gradient were defined by a theme.
680  if( aGradientStops.hasElements() )
681  {
682  mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
683  WriteGrabBagGradientFill(aGradientStops, aGradient);
684  mpFS->endElementNS( XML_a, XML_gradFill );
685  }
686  }
687  else
688  {
689  awt::Gradient aTransparenceGradient;
690  mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
691  OUString sFillTransparenceGradientName;
692  if (GetProperty(rXPropSet, "FillTransparenceGradientName")
693  && (mAny >>= sFillTransparenceGradientName)
694  && !sFillTransparenceGradientName.isEmpty())
695  {
696  if (GetProperty(rXPropSet, "FillTransparenceGradient"))
697  aTransparenceGradient = *o3tl::doAccess<awt::Gradient>(mAny);
698  }
699  else if (GetProperty(rXPropSet, "FillTransparence"))
700  {
701  // currently only StartColor and EndColor are evaluated in WriteGradientFill()
702  sal_Int32 nTransparency = 0;
703  mAny >>= nTransparency;
704  // convert percent to gray color
705  nTransparency = nTransparency * 255/100;
706  const sal_Int32 aGrayColor = static_cast<sal_Int32>( nTransparency | nTransparency << 8 | nTransparency << 16 );
707  aTransparenceGradient.StartColor = aGrayColor;
708  aTransparenceGradient.EndColor = aGrayColor;
709  }
710  WriteGradientFill(aGradient, aTransparenceGradient);
711  mpFS->endElementNS(XML_a, XML_gradFill);
712  }
713 }
714 
715 void DrawingML::WriteGrabBagGradientFill( const Sequence< PropertyValue >& aGradientStops, awt::Gradient rGradient )
716 {
717  // write back the original gradient
718  mpFS->startElementNS(XML_a, XML_gsLst);
719 
720  // get original stops and write them
721  for( const auto& rGradientStop : aGradientStops )
722  {
723  Sequence< PropertyValue > aGradientStop;
724  rGradientStop.Value >>= aGradientStop;
725 
726  // get values
727  OUString sSchemeClr;
728  double nPos = 0;
729  sal_Int16 nTransparency = 0;
730  ::Color nRgbClr;
731  Sequence< PropertyValue > aTransformations;
732  for( const auto& rProp : std::as_const(aGradientStop) )
733  {
734  if( rProp.Name == "SchemeClr" )
735  rProp.Value >>= sSchemeClr;
736  else if( rProp.Name == "RgbClr" )
737  rProp.Value >>= nRgbClr;
738  else if( rProp.Name == "Pos" )
739  rProp.Value >>= nPos;
740  else if( rProp.Name == "Transparency" )
741  rProp.Value >>= nTransparency;
742  else if( rProp.Name == "Transformations" )
743  rProp.Value >>= aTransformations;
744  }
745  // write stop
746  mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nPos * 100000.0).getStr());
747  if( sSchemeClr.isEmpty() )
748  {
749  // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
750  sal_Int32 nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency );
751  WriteColor( nRgbClr, nAlpha );
752  }
753  else
754  {
755  WriteColor( sSchemeClr, aTransformations );
756  }
757  mpFS->endElementNS( XML_a, XML_gs );
758  }
759  mpFS->endElementNS( XML_a, XML_gsLst );
760 
761  switch (rGradient.Style)
762  {
763  default:
764  mpFS->singleElementNS(
765  XML_a, XML_lin, XML_ang,
766  OString::number(((3600 - rGradient.Angle + 900) * 6000) % 21600000));
767  break;
768  case awt::GradientStyle_RADIAL:
769  WriteGradientPath(rGradient, mpFS, true);
770  break;
771  }
772 }
773 
774 void DrawingML::WriteGradientFill(awt::Gradient rGradient, awt::Gradient rTransparenceGradient,
775  const uno::Reference<beans::XPropertySet>& rXPropSet)
776 {
777  sal_Int32 nStartAlpha;
778  sal_Int32 nEndAlpha;
779  if( rXPropSet.is() && GetProperty(rXPropSet, "FillTransparence") )
780  {
781  sal_Int32 nTransparency = 0;
782  mAny >>= nTransparency;
783  nStartAlpha = nEndAlpha = (MAX_PERCENT - (PER_PERCENT * nTransparency));
784  }
785  else
786  {
787  nStartAlpha = GetAlphaFromTransparenceGradient(rTransparenceGradient, true);
788  nEndAlpha = GetAlphaFromTransparenceGradient(rTransparenceGradient, false);
789  }
790  switch( rGradient.Style )
791  {
792  default:
793  case awt::GradientStyle_LINEAR:
794  {
795  mpFS->startElementNS(XML_a, XML_gsLst);
796  WriteGradientStop(rGradient.Border, ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
797  nStartAlpha);
798  WriteGradientStop(100, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
799  nEndAlpha);
800  mpFS->endElementNS( XML_a, XML_gsLst );
801  mpFS->singleElementNS(
802  XML_a, XML_lin, XML_ang,
803  OString::number(((3600 - rGradient.Angle + 900) * 6000) % 21600000));
804  break;
805  }
806 
807  case awt::GradientStyle_AXIAL:
808  {
809  mpFS->startElementNS(XML_a, XML_gsLst);
810  WriteGradientStop(0, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
811  nEndAlpha);
812  if (rGradient.Border > 0 && rGradient.Border < 100)
813  {
814  WriteGradientStop(rGradient.Border/2,
815  ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
816  nEndAlpha);
817  }
818  WriteGradientStop(50, ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
819  nStartAlpha);
820  if (rGradient.Border > 0 && rGradient.Border < 100)
821  {
822  WriteGradientStop(100 - rGradient.Border/2,
823  ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
824  nEndAlpha);
825  }
826  WriteGradientStop(100, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
827  nEndAlpha);
828  mpFS->endElementNS(XML_a, XML_gsLst);
829  mpFS->singleElementNS(
830  XML_a, XML_lin, XML_ang,
831  OString::number(((3600 - rGradient.Angle + 900) * 6000) % 21600000));
832  break;
833  }
834 
835  case awt::GradientStyle_RADIAL:
836  case awt::GradientStyle_ELLIPTICAL:
837  case awt::GradientStyle_RECT:
838  case awt::GradientStyle_SQUARE:
839  {
840  mpFS->startElementNS(XML_a, XML_gsLst);
841  WriteGradientStop(0, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
842  nEndAlpha);
843  if (rGradient.Border > 0 && rGradient.Border < 100)
844  {
845  // Map border to an additional gradient stop, which has the
846  // same color as the final stop.
847  WriteGradientStop(100 - rGradient.Border,
848  ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
849  nStartAlpha);
850  }
851  WriteGradientStop(100,
852  ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
853  nStartAlpha);
854  mpFS->endElementNS(XML_a, XML_gsLst);
855 
856  WriteGradientPath(rGradient, mpFS, rGradient.Style == awt::GradientStyle_RADIAL || rGradient.Style == awt::GradientStyle_ELLIPTICAL);
857  break;
858  }
859  }
860 }
861 
862 void DrawingML::WriteLineArrow( const Reference< XPropertySet >& rXPropSet, bool bLineStart )
863 {
864  ESCHER_LineEnd eLineEnd;
865  sal_Int32 nArrowLength;
866  sal_Int32 nArrowWidth;
867 
868  if ( !EscherPropertyContainer::GetLineArrow( bLineStart, rXPropSet, eLineEnd, nArrowLength, nArrowWidth ) )
869  return;
870 
871  const char* len;
872  const char* type;
873  const char* width;
874 
875  switch( nArrowLength )
876  {
878  len = "sm";
879  break;
880  default:
882  len = "med";
883  break;
885  len = "lg";
886  break;
887  }
888 
889  switch( eLineEnd )
890  {
891  default:
892  case ESCHER_LineNoEnd:
893  type = "none";
894  break;
895  case ESCHER_LineArrowEnd:
896  type = "triangle";
897  break;
899  type = "stealth";
900  break;
902  type = "diamond";
903  break;
905  type = "oval";
906  break;
908  type = "arrow";
909  break;
910  }
911 
912  switch( nArrowWidth )
913  {
915  width = "sm";
916  break;
917  default:
919  width = "med";
920  break;
922  width = "lg";
923  break;
924  }
925 
926  mpFS->singleElementNS( XML_a, bLineStart ? XML_headEnd : XML_tailEnd,
927  XML_len, len,
928  XML_type, type,
929  XML_w, width );
930 }
931 
933 {
934  drawing::LineStyle aLineStyle( drawing::LineStyle_NONE );
935  if (GetProperty(rXPropSet, "LineStyle"))
936  mAny >>= aLineStyle;
937 
938  const LineCap aLineCap = GetProperty(rXPropSet, "LineCap") ? mAny.get<drawing::LineCap>() : LineCap_BUTT;
939 
940  sal_uInt32 nLineWidth = 0;
941  sal_uInt32 nEmuLineWidth = 0;
942  ::Color nColor;
943  sal_Int32 nColorAlpha = MAX_PERCENT;
944  bool bColorSet = false;
945  const char* cap = nullptr;
946  drawing::LineDash aLineDash;
947  bool bDashSet = false;
948  bool bNoFill = false;
949 
950 
951  // get InteropGrabBag and search the relevant attributes
952  OUString sColorFillScheme;
953  ::Color aResolvedColorFillScheme;
954 
955  ::Color nOriginalColor;
956  ::Color nStyleColor;
957  sal_uInt32 nStyleLineWidth = 0;
958 
959  Sequence<PropertyValue> aStyleProperties;
960  Sequence<PropertyValue> aTransformations;
961 
962  drawing::LineStyle aStyleLineStyle(drawing::LineStyle_NONE);
963  drawing::LineJoint aStyleLineJoint(drawing::LineJoint_NONE);
964 
965  if (GetProperty(rXPropSet, "InteropGrabBag"))
966  {
967  Sequence<PropertyValue> aGrabBag;
968  mAny >>= aGrabBag;
969 
970  for (const auto& rProp : std::as_const(aGrabBag))
971  {
972  if( rProp.Name == "SpPrLnSolidFillSchemeClr" )
973  rProp.Value >>= sColorFillScheme;
974  if( rProp.Name == "SpPrLnSolidFillResolvedSchemeClr" )
975  rProp.Value >>= aResolvedColorFillScheme;
976  else if( rProp.Name == "OriginalLnSolidFillClr" )
977  rProp.Value >>= nOriginalColor;
978  else if( rProp.Name == "StyleLnRef" )
979  rProp.Value >>= aStyleProperties;
980  else if( rProp.Name == "SpPrLnSolidFillSchemeClrTransformations" )
981  rProp.Value >>= aTransformations;
982  else if( rProp.Name == "EmuLineWidth" )
983  rProp.Value >>= nEmuLineWidth;
984  }
985  for (const auto& rStyleProp : std::as_const(aStyleProperties))
986  {
987  if( rStyleProp.Name == "Color" )
988  rStyleProp.Value >>= nStyleColor;
989  else if( rStyleProp.Name == "LineStyle" )
990  rStyleProp.Value >>= aStyleLineStyle;
991  else if( rStyleProp.Name == "LineJoint" )
992  rStyleProp.Value >>= aStyleLineJoint;
993  else if( rStyleProp.Name == "LineWidth" )
994  rStyleProp.Value >>= nStyleLineWidth;
995  }
996  }
997 
998  if (GetProperty(rXPropSet, "LineWidth"))
999  mAny >>= nLineWidth;
1000 
1001  switch (aLineStyle)
1002  {
1003  case drawing::LineStyle_NONE:
1004  bNoFill = true;
1005  break;
1006  case drawing::LineStyle_DASH:
1007  if (GetProperty(rXPropSet, "LineDash"))
1008  {
1009  aLineDash = mAny.get<drawing::LineDash>();
1010  //this query is good for shapes, but in the case of charts it returns 0 values
1011  if (aLineDash.Dots == 0 && aLineDash.DotLen == 0 && aLineDash.Dashes == 0 && aLineDash.DashLen == 0 && aLineDash.Distance == 0) {
1012  OUString aLineDashName;
1013  if (GetProperty(rXPropSet, "LineDashName"))
1014  mAny >>= aLineDashName;
1015  if (!aLineDashName.isEmpty() && xModel) {
1016  css::uno::Any aAny = getLineDash(xModel, aLineDashName);
1017  aAny >>= aLineDash;
1018  }
1019  }
1020  }
1021  else
1022  {
1023  //export the linestyle of chart wall (plot area) and chart page
1024  OUString aLineDashName;
1025  if (GetProperty(rXPropSet, "LineDashName"))
1026  mAny >>= aLineDashName;
1027  if (!aLineDashName.isEmpty() && xModel) {
1028  css::uno::Any aAny = getLineDash(xModel, aLineDashName);
1029  aAny >>= aLineDash;
1030  }
1031  }
1032  bDashSet = true;
1033  if (aLineDash.Style == DashStyle_ROUND || aLineDash.Style == DashStyle_ROUNDRELATIVE)
1034  {
1035  cap = "rnd";
1036  }
1037 
1038  SAL_INFO("oox.shape", "dash dots: " << aLineDash.Dots << " dashes: " << aLineDash.Dashes
1039  << " dotlen: " << aLineDash.DotLen << " dashlen: " << aLineDash.DashLen << " distance: " << aLineDash.Distance);
1040 
1041  [[fallthrough]];
1042  case drawing::LineStyle_SOLID:
1043  default:
1044  if (GetProperty(rXPropSet, "LineColor"))
1045  {
1046  nColor = ::Color(ColorTransparency, mAny.get<sal_uInt32>() & 0xffffff);
1047  bColorSet = true;
1048  }
1049  if (GetProperty(rXPropSet, "LineTransparence"))
1050  {
1051  nColorAlpha = MAX_PERCENT - (mAny.get<sal_Int16>() * PER_PERCENT);
1052  }
1053  if (aLineCap == LineCap_ROUND)
1054  cap = "rnd";
1055  else if (aLineCap == LineCap_SQUARE)
1056  cap = "sq";
1057  break;
1058  }
1059 
1060  // if the line-width was not modified after importing then the original EMU value will be exported to avoid unexpected conversion (rounding) error
1061  if (nEmuLineWidth == 0 || static_cast<sal_uInt32>(oox::drawingml::convertEmuToHmm(nEmuLineWidth)) != nLineWidth)
1062  nEmuLineWidth = oox::drawingml::convertHmmToEmu(nLineWidth);
1063  mpFS->startElementNS( XML_a, XML_ln,
1064  XML_cap, cap,
1065  XML_w, sax_fastparser::UseIf(OString::number(nEmuLineWidth),
1066  nLineWidth == 0 || (nLineWidth > 1 && nStyleLineWidth != nLineWidth)) );
1067 
1068  if( bColorSet )
1069  {
1070  if( nColor != nOriginalColor )
1071  {
1072  // the user has set a different color for the line
1073  WriteSolidFill( nColor, nColorAlpha );
1074  }
1075  else if( !sColorFillScheme.isEmpty() )
1076  {
1077  // the line had a scheme color and the user didn't change it
1078  WriteSolidFill( aResolvedColorFillScheme, aTransformations );
1079  }
1080  else
1081  {
1082  WriteSolidFill( nColor, nColorAlpha );
1083  }
1084  }
1085 
1086  if( bDashSet && aStyleLineStyle != drawing::LineStyle_DASH )
1087  {
1088  // Try to detect if it might come from ms preset line style import.
1089  // MS Office styles are always relative, both binary and OOXML.
1090  // "dot" is always the first dash and "dash" the second one. All OOXML presets linestyles
1091  // start with the longer one. Definitions are in OOXML part 1, 20.1.10.49
1092  // The tests are strict, for to not catch styles from standard.sod (as of Aug 2019).
1093  bool bIsConverted = false;
1094 
1095  bool bIsRelative(aLineDash.Style == DashStyle_RECTRELATIVE || aLineDash.Style == DashStyle_ROUNDRELATIVE);
1096  if ( bIsRelative && aLineDash.Dots == 1)
1097  { // The length were tweaked on import in case of prstDash. Revert it here.
1098  sal_uInt32 nDotLen = aLineDash.DotLen;
1099  sal_uInt32 nDashLen = aLineDash.DashLen;
1100  sal_uInt32 nDistance = aLineDash.Distance;
1101  if (aLineCap != LineCap_BUTT && nDistance >= 99)
1102  {
1103  nDistance -= 99;
1104  nDotLen += 99;
1105  if (nDashLen > 0)
1106  nDashLen += 99;
1107  }
1108  // LO uses length 0 for 100%, if the attribute is missing in ODF.
1109  // Other applications might write 100%. Make is unique for the conditions.
1110  if (nDotLen == 0)
1111  nDotLen = 100;
1112  if (nDashLen == 0 && aLineDash.Dashes > 0)
1113  nDashLen = 100;
1114  bIsConverted = true;
1115  if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
1116  {
1117  mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dot");
1118  }
1119  else if (nDotLen == 400 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
1120  {
1121  mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dash");
1122  }
1123  else if (nDotLen == 400 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
1124  {
1125  mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dashDot");
1126  }
1127  else if (nDotLen == 800 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
1128  {
1129  mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDash");
1130  }
1131  else if (nDotLen == 800 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
1132  {
1133  mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDot");
1134  }
1135  else if (nDotLen == 800 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 300)
1136  {
1137  mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDotDot");
1138  }
1139  else if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
1140  {
1141  mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDot");
1142  }
1143  else if (nDotLen == 300 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
1144  {
1145  mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDash");
1146  }
1147  else if (nDotLen == 300 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 100)
1148  {
1149  mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDot");
1150  }
1151  else if (nDotLen == 300 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 100)
1152  {
1153  mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDotDot");
1154  }
1155  else
1156  bIsConverted = false;
1157  }
1158  // Do not map our own line styles to OOXML prstDash values, because custDash gives better results.
1159  if (!bIsConverted)
1160  {
1161  mpFS->startElementNS(XML_a, XML_custDash);
1162  // In case of hairline we would need the current pixel size. Instead use a reasonable
1163  // ersatz for it. The value is the same as SMALLEST_DASH_WIDTH in xattr.cxx.
1164  // (And it makes sure fLineWidth is not zero in below division.)
1165  double fLineWidth = nLineWidth > 0 ? nLineWidth : 26.95;
1166  int i;
1167  double fSp = bIsRelative ? aLineDash.Distance : aLineDash.Distance * 100.0 / fLineWidth;
1168  // LO uses line width, in case Distance is zero. MS Office would use a space of zero length.
1169  // So set 100% explicitly.
1170  if (aLineDash.Distance <= 0)
1171  fSp = 100.0;
1172  // In case of custDash, round caps are included in dash length in MS Office. Square caps are added
1173  // to dash length, same as in ODF. Change the length values accordingly.
1174  if (aLineCap == LineCap_ROUND && fSp > 99.0)
1175  fSp -= 99.0;
1176 
1177  if (aLineDash.Dots > 0)
1178  {
1179  double fD = bIsRelative ? aLineDash.DotLen : aLineDash.DotLen * 100.0 / fLineWidth;
1180  // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended.
1181  if (aLineDash.DotLen == 0)
1182  fD = 100.0;
1183  // Tweak dash length, see above.
1184  if (aLineCap == LineCap_ROUND && fSp > 99.0)
1185  fD += 99.0;
1186 
1187  for( i = 0; i < aLineDash.Dots; i ++ )
1188  {
1189  mpFS->singleElementNS( XML_a, XML_ds,
1190  XML_d , write1000thOfAPercent(fD),
1191  XML_sp, write1000thOfAPercent(fSp) );
1192  }
1193  }
1194  if ( aLineDash.Dashes > 0 )
1195  {
1196  double fD = bIsRelative ? aLineDash.DashLen : aLineDash.DashLen * 100.0 / fLineWidth;
1197  // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended.
1198  if (aLineDash.DashLen == 0)
1199  fD = 100.0;
1200  // Tweak dash length, see above.
1201  if (aLineCap == LineCap_ROUND && fSp > 99.0)
1202  fD += 99.0;
1203 
1204  for( i = 0; i < aLineDash.Dashes; i ++ )
1205  {
1206  mpFS->singleElementNS( XML_a , XML_ds,
1207  XML_d , write1000thOfAPercent(fD),
1208  XML_sp, write1000thOfAPercent(fSp) );
1209  }
1210  }
1211 
1212  SAL_WARN_IF(nLineWidth <= 0,
1213  "oox.shape", "while writing outline - custom dash - line width was < 0 : " << nLineWidth);
1214  SAL_WARN_IF(aLineDash.Dashes < 0,
1215  "oox.shape", "while writing outline - custom dash - number of dashes was < 0 : " << aLineDash.Dashes);
1216  SAL_WARN_IF(aLineDash.Dashes > 0 && aLineDash.DashLen <= 0,
1217  "oox.shape", "while writing outline - custom dash - dash length was < 0 : " << aLineDash.DashLen);
1218  SAL_WARN_IF(aLineDash.Dots < 0,
1219  "oox.shape", "while writing outline - custom dash - number of dots was < 0 : " << aLineDash.Dots);
1220  SAL_WARN_IF(aLineDash.Dots > 0 && aLineDash.DotLen <= 0,
1221  "oox.shape", "while writing outline - custom dash - dot length was < 0 : " << aLineDash.DotLen);
1222  SAL_WARN_IF(aLineDash.Distance <= 0,
1223  "oox.shape", "while writing outline - custom dash - distance was < 0 : " << aLineDash.Distance);
1224 
1225  mpFS->endElementNS( XML_a, XML_custDash );
1226  }
1227  }
1228 
1229  if (!bNoFill && nLineWidth > 1 && GetProperty(rXPropSet, "LineJoint"))
1230  {
1231  LineJoint eLineJoint = mAny.get<LineJoint>();
1232 
1233  if( aStyleLineJoint == LineJoint_NONE || aStyleLineJoint != eLineJoint )
1234  {
1235  // style-defined line joint does not exist, or is different from the shape's joint
1236  switch( eLineJoint )
1237  {
1238  case LineJoint_NONE:
1239  case LineJoint_BEVEL:
1240  mpFS->singleElementNS(XML_a, XML_bevel);
1241  break;
1242  default:
1243  case LineJoint_MIDDLE:
1244  case LineJoint_MITER:
1245  mpFS->singleElementNS(XML_a, XML_miter);
1246  break;
1247  case LineJoint_ROUND:
1248  mpFS->singleElementNS(XML_a, XML_round);
1249  break;
1250  }
1251  }
1252  }
1253 
1254  if( !bNoFill )
1255  {
1256  WriteLineArrow( rXPropSet, true );
1257  WriteLineArrow( rXPropSet, false );
1258  }
1259  else
1260  {
1261  mpFS->singleElementNS(XML_a, XML_noFill);
1262  }
1263 
1264  mpFS->endElementNS( XML_a, XML_ln );
1265 }
1266 
1267 const char* DrawingML::GetComponentDir() const
1268 {
1269  switch ( meDocumentType )
1270  {
1271  case DOCUMENT_DOCX: return "word";
1272  case DOCUMENT_PPTX: return "ppt";
1273  case DOCUMENT_XLSX: return "xl";
1274  }
1275 
1276  return "unknown";
1277 }
1278 
1280 {
1281  switch ( meDocumentType )
1282  {
1283  case DOCUMENT_DOCX: return "";
1284  case DOCUMENT_PPTX:
1285  case DOCUMENT_XLSX: return "../";
1286  }
1287 
1288  return "unknown";
1289 }
1290 
1291 OUString DrawingML::WriteImage( const Graphic& rGraphic , bool bRelPathToMedia )
1292 {
1293  GfxLink aLink = rGraphic.GetGfxLink ();
1294  BitmapChecksum aChecksum = rGraphic.GetChecksum();
1295  OUString sMediaType;
1296  const char* pExtension = "";
1297  OUString sRelId;
1298  OUString sPath;
1299 
1300  // tdf#74670 tdf#91286 Save image only once
1301  if (!maExportGraphics.empty())
1302  {
1303  auto aIterator = maExportGraphics.top().find(aChecksum);
1304  if (aIterator != maExportGraphics.top().end())
1305  sPath = aIterator->second;
1306  }
1307 
1308  if (sPath.isEmpty())
1309  {
1310  SvMemoryStream aStream;
1311  const void* aData = aLink.GetData();
1312  std::size_t nDataSize = aLink.GetDataSize();
1313 
1314  switch (aLink.GetType())
1315  {
1316  case GfxLinkType::NativeGif:
1317  sMediaType = "image/gif";
1318  pExtension = ".gif";
1319  break;
1320 
1321  // #i15508# added BMP type for better exports
1322  // export not yet active, so adding for reference (not checked)
1323  case GfxLinkType::NativeBmp:
1324  sMediaType = "image/bmp";
1325  pExtension = ".bmp";
1326  break;
1327 
1328  case GfxLinkType::NativeJpg:
1329  sMediaType = "image/jpeg";
1330  pExtension = ".jpeg";
1331  break;
1332  case GfxLinkType::NativePng:
1333  sMediaType = "image/png";
1334  pExtension = ".png";
1335  break;
1336  case GfxLinkType::NativeTif:
1337  sMediaType = "image/tiff";
1338  pExtension = ".tif";
1339  break;
1340  case GfxLinkType::NativeWmf:
1341  sMediaType = "image/x-wmf";
1342  pExtension = ".wmf";
1343  break;
1344  case GfxLinkType::NativeMet:
1345  sMediaType = "image/x-met";
1346  pExtension = ".met";
1347  break;
1348  case GfxLinkType::NativePct:
1349  sMediaType = "image/x-pict";
1350  pExtension = ".pct";
1351  break;
1352  case GfxLinkType::NativeMov:
1353  sMediaType = "application/movie";
1354  pExtension = ".MOV";
1355  break;
1356  default:
1357  {
1358  GraphicType aType = rGraphic.GetType();
1359  if (aType == GraphicType::Bitmap || aType == GraphicType::GdiMetafile)
1360  {
1361  if (aType == GraphicType::Bitmap)
1362  {
1363  (void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::PNG);
1364  sMediaType = "image/png";
1365  pExtension = ".png";
1366  }
1367  else
1368  {
1369  (void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::EMF);
1370  sMediaType = "image/x-emf";
1371  pExtension = ".emf";
1372  }
1373  }
1374  else
1375  {
1376  SAL_WARN("oox.shape", "unhandled graphic type " << static_cast<int>(aType));
1377  /*Earlier, even in case of unhandled graphic types we were
1378  proceeding to write the image, which would eventually
1379  write an empty image with a zero size, and return a valid
1380  relationID, which is incorrect.
1381  */
1382  return sRelId;
1383  }
1384 
1385  aData = aStream.GetData();
1386  nDataSize = aStream.GetEndOfData();
1387  break;
1388  }
1389  }
1390 
1392  OUStringBuffer()
1393  .appendAscii(GetComponentDir())
1394  .append("/media/image" + OUString::number(mnImageCounter))
1395  .appendAscii(pExtension)
1396  .makeStringAndClear(),
1397  sMediaType);
1398  xOutStream->writeBytes(Sequence<sal_Int8>(static_cast<const sal_Int8*>(aData), nDataSize));
1399  xOutStream->closeOutput();
1400 
1401  const OString sRelPathToMedia = "media/image";
1402  OString sRelationCompPrefix;
1403  if (bRelPathToMedia)
1404  sRelationCompPrefix = "../";
1405  else
1406  sRelationCompPrefix = GetRelationCompPrefix();
1407  sPath = OUStringBuffer()
1408  .appendAscii(sRelationCompPrefix.getStr())
1409  .appendAscii(sRelPathToMedia.getStr())
1410  .append(static_cast<sal_Int32>(mnImageCounter++))
1411  .appendAscii(pExtension)
1412  .makeStringAndClear();
1413 
1414  if (!maExportGraphics.empty())
1415  maExportGraphics.top()[aChecksum] = sPath;
1416  }
1417 
1418  sRelId = mpFB->addRelation( mpFS->getOutputStream(),
1420  sPath );
1421 
1422  return sRelId;
1423 }
1424 
1425 void DrawingML::WriteMediaNonVisualProperties(const css::uno::Reference<css::drawing::XShape>& xShape)
1426 {
1427  SdrMediaObj* pMediaObj = dynamic_cast<SdrMediaObj*>(SdrObject::getSdrObjectFromXShape(xShape));
1428  if (!pMediaObj)
1429  return;
1430 
1431  // extension
1432  OUString aExtension;
1433  const OUString& rURL(pMediaObj->getURL());
1434  int nLastDot = rURL.lastIndexOf('.');
1435  if (nLastDot >= 0)
1436  aExtension = rURL.copy(nLastDot);
1437 
1438  bool bEmbed = rURL.startsWith("vnd.sun.star.Package:");
1439  Relationship eMediaType = Relationship::VIDEO;
1440 
1441  // mime type
1442 #if HAVE_FEATURE_AVMEDIA
1443  OUString aMimeType(pMediaObj->getMediaProperties().getMimeType());
1444 #else
1445  OUString aMimeType("none");
1446 #endif
1447  if (aMimeType == "application/vnd.sun.star.media")
1448  {
1449  // try to set something better
1450  // TODO fix the importer to actually set the mimetype on import
1451  if (aExtension.equalsIgnoreAsciiCase(".avi"))
1452  aMimeType = "video/x-msvideo";
1453  else if (aExtension.equalsIgnoreAsciiCase(".flv"))
1454  aMimeType = "video/x-flv";
1455  else if (aExtension.equalsIgnoreAsciiCase(".mp4"))
1456  aMimeType = "video/mp4";
1457  else if (aExtension.equalsIgnoreAsciiCase(".mov"))
1458  aMimeType = "video/quicktime";
1459  else if (aExtension.equalsIgnoreAsciiCase(".ogv"))
1460  aMimeType = "video/ogg";
1461  else if (aExtension.equalsIgnoreAsciiCase(".wmv"))
1462  aMimeType = "video/x-ms-wmv";
1463  else if (aExtension.equalsIgnoreAsciiCase(".wav"))
1464  {
1465  aMimeType = "audio/x-wav";
1466  eMediaType = Relationship::AUDIO;
1467  }
1468  else if (aExtension.equalsIgnoreAsciiCase(".m4a"))
1469  {
1470  aMimeType = "audio/mp4";
1471  eMediaType = Relationship::AUDIO;
1472  }
1473  else if (aExtension.equalsIgnoreAsciiCase(".mp3"))
1474  {
1475  aMimeType = "audio/mp3";
1476  eMediaType = Relationship::AUDIO;
1477  }
1478  }
1479 
1480  OUString aVideoFileRelId;
1481  OUString aMediaRelId;
1482 
1483  if (bEmbed)
1484  {
1485  // copy the video stream
1486  Reference<XOutputStream> xOutStream = mpFB->openFragmentStream(OUStringBuffer()
1487  .appendAscii(GetComponentDir())
1488  .append("/media/media" +
1489  OUString::number(mnImageCounter) +
1490  aExtension)
1491  .makeStringAndClear(),
1492  aMimeType);
1493 
1494  uno::Reference<io::XInputStream> xInputStream(pMediaObj->GetInputStream());
1495  comphelper::OStorageHelper::CopyInputToOutput(xInputStream, xOutStream);
1496 
1497  xOutStream->closeOutput();
1498 
1499  // create the relation
1500  OUString aPath = OUStringBuffer().appendAscii(GetRelationCompPrefix())
1501  .append("media/media" + OUString::number(mnImageCounter++) + aExtension)
1502  .makeStringAndClear();
1503  aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), aPath);
1504  aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), aPath);
1505  }
1506  else
1507  {
1508  aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), rURL, true);
1509  aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), rURL, true);
1510  }
1511 
1512  GetFS()->startElementNS(XML_p, XML_nvPr);
1513 
1514  GetFS()->singleElementNS(XML_a, eMediaType == Relationship::VIDEO ? XML_videoFile : XML_audioFile,
1515  FSNS(XML_r, XML_link), aVideoFileRelId);
1516 
1517  GetFS()->startElementNS(XML_p, XML_extLst);
1518  // media extensions; google this ID for details
1519  GetFS()->startElementNS(XML_p, XML_ext, XML_uri, "{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}");
1520 
1521  GetFS()->singleElementNS(XML_p14, XML_media,
1522  bEmbed? FSNS(XML_r, XML_embed): FSNS(XML_r, XML_link), aMediaRelId);
1523 
1524  GetFS()->endElementNS(XML_p, XML_ext);
1525  GetFS()->endElementNS(XML_p, XML_extLst);
1526 
1527  GetFS()->endElementNS(XML_p, XML_nvPr);
1528 }
1529 
1530 void DrawingML::WriteImageBrightnessContrastTransparence(uno::Reference<beans::XPropertySet> const & rXPropSet)
1531 {
1532  sal_Int16 nBright = 0;
1533  sal_Int32 nContrast = 0;
1534  sal_Int32 nTransparence = 0;
1535 
1536  if (GetProperty(rXPropSet, "AdjustLuminance"))
1537  nBright = mAny.get<sal_Int16>();
1538  if (GetProperty(rXPropSet, "AdjustContrast"))
1539  nContrast = mAny.get<sal_Int32>();
1540  // Used for shapes with picture fill
1541  if (GetProperty(rXPropSet, "FillTransparence"))
1542  nTransparence = mAny.get<sal_Int32>();
1543  // Used for pictures
1544  if (nTransparence == 0 && GetProperty(rXPropSet, "Transparency"))
1545  nTransparence = static_cast<sal_Int32>(mAny.get<sal_Int16>());
1546 
1547  if (GetProperty(rXPropSet, "GraphicColorMode"))
1548  {
1549  drawing::ColorMode aColorMode;
1550  mAny >>= aColorMode;
1551  if (aColorMode == drawing::ColorMode_GREYS)
1552  mpFS->singleElementNS(XML_a, XML_grayscl);
1553  else if (aColorMode == drawing::ColorMode_MONO)
1554  //black/white has a 0,5 threshold in LibreOffice
1555  mpFS->singleElementNS(XML_a, XML_biLevel, XML_thresh, OString::number(50000));
1556  else if (aColorMode == drawing::ColorMode_WATERMARK)
1557  {
1558  //map watermark with mso washout
1559  nBright = 70;
1560  nContrast = -70;
1561  }
1562  }
1563 
1564 
1565  if (nBright || nContrast)
1566  {
1567  mpFS->singleElementNS(XML_a, XML_lum,
1568  XML_bright, sax_fastparser::UseIf(OString::number(nBright * 1000), nBright != 0),
1569  XML_contrast, sax_fastparser::UseIf(OString::number(nContrast * 1000), nContrast != 0));
1570  }
1571 
1572  if (nTransparence)
1573  {
1574  sal_Int32 nAlphaMod = (100 - nTransparence ) * PER_PERCENT;
1575  mpFS->singleElementNS(XML_a, XML_alphaModFix, XML_amt, OString::number(nAlphaMod));
1576  }
1577 }
1578 
1579 OUString DrawingML::WriteXGraphicBlip(uno::Reference<beans::XPropertySet> const & rXPropSet,
1580  uno::Reference<graphic::XGraphic> const & rxGraphic,
1581  bool bRelPathToMedia)
1582 {
1583  OUString sRelId;
1584 
1585  if (!rxGraphic.is())
1586  return sRelId;
1587 
1588  Graphic aGraphic(rxGraphic);
1589  sRelId = WriteImage(aGraphic, bRelPathToMedia);
1590 
1591  mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId);
1592 
1594 
1595  WriteArtisticEffect(rXPropSet);
1596 
1597  mpFS->endElementNS(XML_a, XML_blip);
1598 
1599  return sRelId;
1600 }
1601 
1602 void DrawingML::WriteXGraphicBlipMode(uno::Reference<beans::XPropertySet> const & rXPropSet,
1603  uno::Reference<graphic::XGraphic> const & rxGraphic)
1604 {
1605  BitmapMode eBitmapMode(BitmapMode_NO_REPEAT);
1606  if (GetProperty(rXPropSet, "FillBitmapMode"))
1607  mAny >>= eBitmapMode;
1608 
1609  SAL_INFO("oox.shape", "fill bitmap mode: " << int(eBitmapMode));
1610 
1611  switch (eBitmapMode)
1612  {
1613  case BitmapMode_REPEAT:
1614  mpFS->singleElementNS(XML_a, XML_tile);
1615  break;
1616  case BitmapMode_STRETCH:
1617  WriteXGraphicStretch(rXPropSet, rxGraphic);
1618  break;
1619  default:
1620  break;
1621  }
1622 }
1623 
1624 void DrawingML::WriteBlipOrNormalFill( const Reference< XPropertySet >& xPropSet, const OUString& rURLPropName )
1625 {
1626  // check for blip and otherwise fall back to normal fill
1627  // we always store normal fill properties but OOXML
1628  // uses a choice between our fill props and BlipFill
1629  if (GetProperty ( xPropSet, rURLPropName ))
1630  WriteBlipFill( xPropSet, rURLPropName );
1631  else
1632  WriteFill(xPropSet);
1633 }
1634 
1635 void DrawingML::WriteBlipFill( const Reference< XPropertySet >& rXPropSet, const OUString& sURLPropName )
1636 {
1637  WriteBlipFill( rXPropSet, sURLPropName, XML_a );
1638 }
1639 
1640 void DrawingML::WriteBlipFill( const Reference< XPropertySet >& rXPropSet, const OUString& sURLPropName, sal_Int32 nXmlNamespace )
1641 {
1642  if ( !GetProperty( rXPropSet, sURLPropName ) )
1643  return;
1644 
1645  uno::Reference<graphic::XGraphic> xGraphic;
1646  if (mAny.has<uno::Reference<awt::XBitmap>>())
1647  {
1648  uno::Reference<awt::XBitmap> xBitmap = mAny.get<uno::Reference<awt::XBitmap>>();
1649  if (xBitmap.is())
1650  xGraphic.set(xBitmap, uno::UNO_QUERY);
1651  }
1652  else if (mAny.has<uno::Reference<graphic::XGraphic>>())
1653  {
1654  xGraphic = mAny.get<uno::Reference<graphic::XGraphic>>();
1655  }
1656 
1657  if (xGraphic.is())
1658  {
1659  bool bWriteMode = false;
1660  if (sURLPropName == "FillBitmap" || sURLPropName == "BackGraphic")
1661  bWriteMode = true;
1662  WriteXGraphicBlipFill(rXPropSet, xGraphic, nXmlNamespace, bWriteMode);
1663  }
1664 }
1665 
1666 void DrawingML::WriteXGraphicBlipFill(uno::Reference<beans::XPropertySet> const & rXPropSet,
1667  uno::Reference<graphic::XGraphic> const & rxGraphic,
1668  sal_Int32 nXmlNamespace, bool bWriteMode, bool bRelPathToMedia)
1669 {
1670  if (!rxGraphic.is() )
1671  return;
1672 
1673  mpFS->startElementNS(nXmlNamespace , XML_blipFill, XML_rotWithShape, "0");
1674 
1675  WriteXGraphicBlip(rXPropSet, rxGraphic, bRelPathToMedia);
1676 
1677  if (GetDocumentType() != DOCUMENT_DOCX)
1678  {
1679  // Write the crop rectangle of Impress as a source rectangle.
1680  WriteSrcRectXGraphic(rXPropSet, rxGraphic);
1681  }
1682 
1683  if (bWriteMode)
1684  {
1685  WriteXGraphicBlipMode(rXPropSet, rxGraphic);
1686  }
1687  else if(GetProperty(rXPropSet, "FillBitmapStretch"))
1688  {
1689  bool bStretch = mAny.get<bool>();
1690 
1691  if (bStretch)
1692  {
1693  WriteXGraphicStretch(rXPropSet, rxGraphic);
1694  }
1695  }
1696  mpFS->endElementNS(nXmlNamespace, XML_blipFill);
1697 }
1698 
1699 void DrawingML::WritePattFill( const Reference< XPropertySet >& rXPropSet )
1700 {
1701  if ( GetProperty( rXPropSet, "FillHatch" ) )
1702  {
1703  drawing::Hatch aHatch;
1704  mAny >>= aHatch;
1705  WritePattFill(rXPropSet, aHatch);
1706  }
1707 }
1708 
1709 void DrawingML::WritePattFill(const Reference<XPropertySet>& rXPropSet, const css::drawing::Hatch& rHatch)
1710 {
1711  mpFS->startElementNS(XML_a, XML_pattFill, XML_prst, GetHatchPattern(rHatch));
1712 
1713  sal_Int32 nAlpha = MAX_PERCENT;
1714  if (GetProperty(rXPropSet, "FillTransparence"))
1715  {
1716  sal_Int32 nTransparency = 0;
1717  mAny >>= nTransparency;
1718  nAlpha = (MAX_PERCENT - (PER_PERCENT * nTransparency));
1719  }
1720 
1721  mpFS->startElementNS(XML_a, XML_fgClr);
1722  WriteColor(::Color(ColorTransparency, rHatch.Color), nAlpha);
1723  mpFS->endElementNS( XML_a , XML_fgClr );
1724 
1725  ::Color nColor = COL_WHITE;
1726 
1727  if ( GetProperty( rXPropSet, "FillBackground" ) )
1728  {
1729  bool isBackgroundFilled = false;
1730  mAny >>= isBackgroundFilled;
1731  if( isBackgroundFilled )
1732  {
1733  if( GetProperty( rXPropSet, "FillColor" ) )
1734  {
1735  mAny >>= nColor;
1736  }
1737  }
1738  else
1739  nAlpha = 0;
1740  }
1741 
1742  mpFS->startElementNS(XML_a, XML_bgClr);
1743  WriteColor(nColor, nAlpha);
1744  mpFS->endElementNS( XML_a , XML_bgClr );
1745 
1746  mpFS->endElementNS( XML_a , XML_pattFill );
1747 }
1748 
1749 void DrawingML::WriteGraphicCropProperties(uno::Reference<beans::XPropertySet> const & rXPropSet,
1750  Size const & rOriginalSize,
1751  MapMode const & rMapMode)
1752 {
1753  if (!GetProperty(rXPropSet, "GraphicCrop"))
1754  return;
1755 
1756  css::text::GraphicCrop aGraphicCropStruct;
1757  mAny >>= aGraphicCropStruct;
1758 
1759  if(GetProperty(rXPropSet, "CustomShapeGeometry"))
1760  {
1761  // tdf#134210 GraphicCrop property is handled in import filter because of LibreOffice has not core
1762  // feature. We cropped the bitmap physically and MSO shouldn't crop bitmap one more time. When we
1763  // have core feature for graphic cropping in custom shapes, we should uncomment the code anymore.
1764 
1765  mpFS->singleElementNS( XML_a, XML_srcRect);
1766  }
1767  else
1768  {
1769  Size aOriginalSize(rOriginalSize);
1770 
1771  // GraphicCrop is in mm100, so in case the original size is in pixels, convert it over.
1772  if (rMapMode.GetMapUnit() == MapUnit::MapPixel)
1773  aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, MapMode(MapUnit::Map100thMM));
1774 
1775  if ( (0 != aGraphicCropStruct.Left) || (0 != aGraphicCropStruct.Top) || (0 != aGraphicCropStruct.Right) || (0 != aGraphicCropStruct.Bottom) )
1776  {
1777  mpFS->singleElementNS( XML_a, XML_srcRect,
1778  XML_l, OString::number(rtl::math::round(aGraphicCropStruct.Left * 100000.0 / aOriginalSize.Width())),
1779  XML_t, OString::number(rtl::math::round(aGraphicCropStruct.Top * 100000.0 / aOriginalSize.Height())),
1780  XML_r, OString::number(rtl::math::round(aGraphicCropStruct.Right * 100000.0 / aOriginalSize.Width())),
1781  XML_b, OString::number(rtl::math::round(aGraphicCropStruct.Bottom * 100000.0 / aOriginalSize.Height())) );
1782  }
1783  }
1784 }
1785 
1786 void DrawingML::WriteSrcRectXGraphic(uno::Reference<beans::XPropertySet> const & rxPropertySet,
1787  uno::Reference<graphic::XGraphic> const & rxGraphic)
1788 {
1789  Graphic aGraphic(rxGraphic);
1790  Size aOriginalSize = aGraphic.GetPrefSize();
1791  const MapMode& rMapMode = aGraphic.GetPrefMapMode();
1792  WriteGraphicCropProperties(rxPropertySet, aOriginalSize, rMapMode);
1793 }
1794 
1795 void DrawingML::WriteXGraphicStretch(uno::Reference<beans::XPropertySet> const & rXPropSet,
1796  uno::Reference<graphic::XGraphic> const & rxGraphic)
1797 {
1798  if (GetDocumentType() != DOCUMENT_DOCX)
1799  {
1800  // Limiting the area used for stretching is not supported in Impress.
1801  mpFS->singleElementNS(XML_a, XML_stretch);
1802  return;
1803  }
1804 
1805  mpFS->startElementNS(XML_a, XML_stretch);
1806 
1807  bool bCrop = false;
1808  if (GetProperty(rXPropSet, "GraphicCrop"))
1809  {
1810  css::text::GraphicCrop aGraphicCropStruct;
1811  mAny >>= aGraphicCropStruct;
1812 
1813  if ((0 != aGraphicCropStruct.Left)
1814  || (0 != aGraphicCropStruct.Top)
1815  || (0 != aGraphicCropStruct.Right)
1816  || (0 != aGraphicCropStruct.Bottom))
1817  {
1818  Graphic aGraphic(rxGraphic);
1819  Size aOriginalSize(aGraphic.GetPrefSize());
1820  mpFS->singleElementNS(XML_a, XML_fillRect,
1821  XML_l, OString::number(((aGraphicCropStruct.Left) * 100000) / aOriginalSize.Width()),
1822  XML_t, OString::number(((aGraphicCropStruct.Top) * 100000) / aOriginalSize.Height()),
1823  XML_r, OString::number(((aGraphicCropStruct.Right) * 100000) / aOriginalSize.Width()),
1824  XML_b, OString::number(((aGraphicCropStruct.Bottom) * 100000) / aOriginalSize.Height()));
1825  bCrop = true;
1826  }
1827  }
1828 
1829  if (!bCrop)
1830  {
1831  mpFS->singleElementNS(XML_a, XML_fillRect);
1832  }
1833 
1834  mpFS->endElementNS(XML_a, XML_stretch);
1835 }
1836 
1837 namespace
1838 {
1839 bool IsTopGroupObj(const uno::Reference<drawing::XShape>& xShape)
1840 {
1842  if (!pObject)
1843  return false;
1844 
1845  if (pObject->getParentSdrObjectFromSdrObject())
1846  return false;
1847 
1848  return pObject->IsGroupObject();
1849 }
1850 }
1851 
1853  sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, sal_Int32 nRotation, bool bIsGroupShape)
1854 {
1855 
1856  mpFS->startElementNS( nXmlNamespace, XML_xfrm,
1857  XML_flipH, sax_fastparser::UseIf("1", bFlipH),
1858  XML_flipV, sax_fastparser::UseIf("1", bFlipV),
1859  XML_rot, sax_fastparser::UseIf(OString::number(nRotation), nRotation % 21600000 != 0));
1860 
1861  sal_Int32 nLeft = rRect.Left();
1862  sal_Int32 nTop = rRect.Top();
1863  if (GetDocumentType() == DOCUMENT_DOCX && !m_xParent.is())
1864  {
1865  nLeft = 0;
1866  nTop = 0;
1867  }
1868  sal_Int32 nChildLeft = nLeft;
1869  sal_Int32 nChildTop = nTop;
1870 
1871  mpFS->singleElementNS(XML_a, XML_off,
1872  XML_x, OString::number(oox::drawingml::convertHmmToEmu(nLeft)),
1873  XML_y, OString::number(oox::drawingml::convertHmmToEmu(nTop)));
1874  mpFS->singleElementNS(XML_a, XML_ext,
1875  XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())),
1876  XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight())));
1877 
1878  if (bIsGroupShape && (GetDocumentType() != DOCUMENT_DOCX || IsTopGroupObj(xShape)))
1879  {
1880  mpFS->singleElementNS(XML_a, XML_chOff,
1881  XML_x, OString::number(oox::drawingml::convertHmmToEmu(nChildLeft)),
1882  XML_y, OString::number(oox::drawingml::convertHmmToEmu(nChildTop)));
1883  mpFS->singleElementNS(XML_a, XML_chExt,
1884  XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())),
1885  XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight())));
1886  }
1887 
1888  mpFS->endElementNS( nXmlNamespace, XML_xfrm );
1889 }
1890 
1891 void DrawingML::WriteShapeTransformation( const Reference< XShape >& rXShape, sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, bool bSuppressRotation, bool bSuppressFlipping, bool bFlippedBeforeRotation )
1892 {
1893  SAL_INFO("oox.shape", "write shape transformation");
1894 
1895  Degree100 nRotation;
1896  Degree100 nCameraRotation;
1897  awt::Point aPos = rXShape->getPosition();
1898  awt::Size aSize = rXShape->getSize();
1899 
1900  bool bFlipHWrite = bFlipH && !bSuppressFlipping;
1901  bool bFlipVWrite = bFlipV && !bSuppressFlipping;
1902  bFlipH = bFlipH && !bFlippedBeforeRotation;
1903  bFlipV = bFlipV && !bFlippedBeforeRotation;
1904 
1905  if (GetDocumentType() == DOCUMENT_DOCX && m_xParent.is())
1906  {
1907  awt::Point aParentPos = m_xParent->getPosition();
1908  aPos.X -= aParentPos.X;
1909  aPos.Y -= aParentPos.Y;
1910  }
1911 
1912  if ( aSize.Width < 0 )
1913  aSize.Width = 1000;
1914  if ( aSize.Height < 0 )
1915  aSize.Height = 1000;
1916  if (!bSuppressRotation)
1917  {
1918  SdrObject* pShape = SdrObject::getSdrObjectFromXShape(rXShape);
1919  nRotation = pShape ? pShape->GetRotateAngle() : 0_deg100;
1920  if ( GetDocumentType() != DOCUMENT_DOCX )
1921  {
1922  int faccos=bFlipV ? -1 : 1;
1923  int facsin=bFlipH ? -1 : 1;
1924  aPos.X-=(1-faccos*cos(toRadians(nRotation)))*aSize.Width/2-facsin*sin(toRadians(nRotation))*aSize.Height/2;
1925  aPos.Y-=(1-faccos*cos(toRadians(nRotation)))*aSize.Height/2+facsin*sin(toRadians(nRotation))*aSize.Width/2;
1926  }
1927  else if (m_xParent.is() && nRotation != 0_deg100)
1928  {
1929  // Position for rotated shapes inside group is not set by DocxSdrExport.
1930  basegfx::B2DRange aRect(-aSize.Width / 2.0, -aSize.Height / 2.0, aSize.Width / 2.0,
1931  aSize.Height / 2.0);
1932  basegfx::B2DHomMatrix aRotateMatrix =
1934  aRect.transform(aRotateMatrix);
1935  aPos.X += -aSize.Width / 2.0 - aRect.getMinX();
1936  aPos.Y += -aSize.Height / 2.0 - aRect.getMinY();
1937  }
1938 
1939  // The RotateAngle property's value is independent from any flipping, and that's exactly what we need here.
1940  uno::Reference<beans::XPropertySet> xPropertySet(rXShape, uno::UNO_QUERY);
1941  uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
1942  if (xPropertySetInfo->hasPropertyByName("RotateAngle"))
1943  {
1944  sal_Int32 nTmp;
1945  if (xPropertySet->getPropertyValue("RotateAngle") >>= nTmp)
1946  nRotation = Degree100(nTmp);
1947  }
1948  // tdf#133037: restore original rotate angle before output
1949  if (nRotation && xPropertySetInfo->hasPropertyByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG))
1950  {
1951  uno::Sequence<beans::PropertyValue> aGrabBagProps;
1952  xPropertySet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= aGrabBagProps;
1953  auto p3DEffectProps = std::find_if(std::cbegin(aGrabBagProps), std::cend(aGrabBagProps),
1954  [](const PropertyValue& rProp) { return rProp.Name == "3DEffectProperties"; });
1955  if (p3DEffectProps != std::cend(aGrabBagProps))
1956  {
1957  uno::Sequence<beans::PropertyValue> a3DEffectProps;
1958  p3DEffectProps->Value >>= a3DEffectProps;
1959  auto pCameraProps = std::find_if(std::cbegin(a3DEffectProps), std::cend(a3DEffectProps),
1960  [](const PropertyValue& rProp) { return rProp.Name == "Camera"; });
1961  if (pCameraProps != std::cend(a3DEffectProps))
1962  {
1963  uno::Sequence<beans::PropertyValue> aCameraProps;
1964  pCameraProps->Value >>= aCameraProps;
1965  auto pZRotationProp = std::find_if(std::cbegin(aCameraProps), std::cend(aCameraProps),
1966  [](const PropertyValue& rProp) { return rProp.Name == "rotRev"; });
1967  if (pZRotationProp != std::cend(aCameraProps))
1968  {
1969  sal_Int32 nTmp = 0;
1970  pZRotationProp->Value >>= nTmp;
1971  nCameraRotation = NormAngle36000(Degree100(nTmp / -600));
1972  }
1973  }
1974  }
1975  }
1976  }
1977 
1978  // OOXML flips shapes before rotating them.
1979  if(bFlipH != bFlipV)
1980  nRotation = 36000_deg100 - nRotation;
1981 
1982  WriteTransformation(rXShape, tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), nXmlNamespace,
1983  bFlipHWrite, bFlipVWrite, ExportRotateClockwisify(nRotation + nCameraRotation), IsGroupShape( rXShape ));
1984 }
1985 
1986 static OUString lcl_GetTarget(const css::uno::Reference<css::frame::XModel>& xModel, std::u16string_view rURL)
1987 {
1988  Reference<drawing::XDrawPagesSupplier> xDPS(xModel, uno::UNO_QUERY_THROW);
1989  Reference<drawing::XDrawPages> xDrawPages(xDPS->getDrawPages(), uno::UNO_SET_THROW);
1990  sal_uInt32 nPageCount = xDrawPages->getCount();
1991  OUString sTarget;
1992 
1993  for (sal_uInt32 i = 0; i < nPageCount; ++i)
1994  {
1995  Reference<XDrawPage> xDrawPage;
1996  xDrawPages->getByIndex(i) >>= xDrawPage;
1997  Reference<container::XNamed> xNamed(xDrawPage, UNO_QUERY);
1998  if (!xNamed)
1999  continue;
2000  OUString sSlideName = "#" + xNamed->getName();
2001  if (rURL == sSlideName)
2002  {
2003  sTarget = "slide" + OUString::number(i + 1) + ".xml";
2004  break;
2005  }
2006  }
2007 
2008  return sTarget;
2009 }
2010 
2011 void DrawingML::WriteRunProperties( const Reference< XPropertySet >& rRun, bool bIsField, sal_Int32 nElement,
2012  bool bCheckDirect,bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
2013  sal_Int16 nScriptType, const Reference< XPropertySet >& rXShapePropSet)
2014 {
2015  Reference< XPropertySet > rXPropSet = rRun;
2016  Reference< XPropertyState > rXPropState( rRun, UNO_QUERY );
2017  OUString usLanguage;
2018  PropertyState eState;
2019  bool bComplex = ( nScriptType == css::i18n::ScriptType::COMPLEX );
2020  const char* bold = "0";
2021  const char* italic = nullptr;
2022  const char* underline = nullptr;
2023  const char* strikeout = nullptr;
2024  const char* cap = nullptr;
2025  sal_Int32 nSize = 1800;
2026  sal_Int32 nCharEscapement = 0;
2027  sal_Int32 nCharKerning = 0;
2028  sal_Int32 nCharEscapementHeight = 0;
2029 
2030  if ( nElement == XML_endParaRPr && rbOverridingCharHeight )
2031  {
2032  nSize = rnCharHeight;
2033  }
2034  else if (GetProperty(rXPropSet, "CharHeight"))
2035  {
2036  nSize = static_cast<sal_Int32>(100*(*o3tl::doAccess<float>(mAny)));
2037  if ( nElement == XML_rPr || nElement == XML_defRPr )
2038  {
2039  rbOverridingCharHeight = true;
2040  rnCharHeight = nSize;
2041  }
2042  }
2043 
2044  if (GetProperty(rXPropSet, "CharKerning"))
2045  nCharKerning = static_cast<sal_Int32>(*o3tl::doAccess<sal_Int16>(mAny));
2052  nCharKerning = ((nCharKerning * 720)-360) / 254;
2053 
2054  if ((bComplex && GetProperty(rXPropSet, "CharWeightComplex"))
2055  || GetProperty(rXPropSet, "CharWeight"))
2056  {
2057  if ( *o3tl::doAccess<float>(mAny) >= awt::FontWeight::SEMIBOLD )
2058  bold = "1";
2059  }
2060 
2061  if ((bComplex && GetProperty(rXPropSet, "CharPostureComplex"))
2062  || GetProperty(rXPropSet, "CharPosture"))
2063  switch ( *o3tl::doAccess<awt::FontSlant>(mAny) )
2064  {
2065  case awt::FontSlant_OBLIQUE :
2066  case awt::FontSlant_ITALIC :
2067  italic = "1";
2068  break;
2069  default:
2070  break;
2071  }
2072 
2073  if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharUnderline", eState)
2074  && eState == beans::PropertyState_DIRECT_VALUE)
2075  || GetProperty(rXPropSet, "CharUnderline"))
2076  {
2077  switch ( *o3tl::doAccess<sal_Int16>(mAny) )
2078  {
2079  case awt::FontUnderline::SINGLE :
2080  underline = "sng";
2081  break;
2082  case awt::FontUnderline::DOUBLE :
2083  underline = "dbl";
2084  break;
2085  case awt::FontUnderline::DOTTED :
2086  underline = "dotted";
2087  break;
2088  case awt::FontUnderline::DASH :
2089  underline = "dash";
2090  break;
2091  case awt::FontUnderline::LONGDASH :
2092  underline = "dashLong";
2093  break;
2094  case awt::FontUnderline::DASHDOT :
2095  underline = "dotDash";
2096  break;
2097  case awt::FontUnderline::DASHDOTDOT :
2098  underline = "dotDotDash";
2099  break;
2100  case awt::FontUnderline::WAVE :
2101  underline = "wavy";
2102  break;
2103  case awt::FontUnderline::DOUBLEWAVE :
2104  underline = "wavyDbl";
2105  break;
2106  case awt::FontUnderline::BOLD :
2107  underline = "heavy";
2108  break;
2109  case awt::FontUnderline::BOLDDOTTED :
2110  underline = "dottedHeavy";
2111  break;
2112  case awt::FontUnderline::BOLDDASH :
2113  underline = "dashHeavy";
2114  break;
2115  case awt::FontUnderline::BOLDLONGDASH :
2116  underline = "dashLongHeavy";
2117  break;
2118  case awt::FontUnderline::BOLDDASHDOT :
2119  underline = "dotDashHeavy";
2120  break;
2121  case awt::FontUnderline::BOLDDASHDOTDOT :
2122  underline = "dotDotDashHeavy";
2123  break;
2124  case awt::FontUnderline::BOLDWAVE :
2125  underline = "wavyHeavy";
2126  break;
2127  }
2128  }
2129 
2130  if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharStrikeout", eState)
2131  && eState == beans::PropertyState_DIRECT_VALUE)
2132  || GetProperty(rXPropSet, "CharStrikeout"))
2133  {
2134  switch ( *o3tl::doAccess<sal_Int16>(mAny) )
2135  {
2136  case awt::FontStrikeout::NONE :
2137  strikeout = "noStrike";
2138  break;
2139  case awt::FontStrikeout::SINGLE :
2140  // LibO supports further values of character
2141  // strikeout, OOXML standard (20.1.10.78,
2142  // ST_TextStrikeType) however specifies only
2143  // 3 - single, double and none. Approximate
2144  // the remaining ones by single strike (better
2145  // some strike than none at all).
2146  // TODO: figure out how to do this better
2147  case awt::FontStrikeout::BOLD :
2148  case awt::FontStrikeout::SLASH :
2149  case awt::FontStrikeout::X :
2150  strikeout = "sngStrike";
2151  break;
2152  case awt::FontStrikeout::DOUBLE :
2153  strikeout = "dblStrike";
2154  break;
2155  }
2156  }
2157 
2158  bool bLang = false;
2159  switch(nScriptType)
2160  {
2161  case css::i18n::ScriptType::ASIAN:
2162  bLang = GetProperty(rXPropSet, "CharLocaleAsian"); break;
2163  case css::i18n::ScriptType::COMPLEX:
2164  bLang = GetProperty(rXPropSet, "CharLocaleComplex"); break;
2165  default:
2166  bLang = GetProperty(rXPropSet, "CharLocale"); break;
2167  }
2168 
2169  if (bLang)
2170  {
2171  css::lang::Locale aLocale;
2172  mAny >>= aLocale;
2173  LanguageTag aLanguageTag( aLocale);
2174  if (!aLanguageTag.isSystemLocale())
2175  usLanguage = aLanguageTag.getBcp47MS();
2176  }
2177 
2178  if (GetPropertyAndState(rXPropSet, rXPropState, "CharEscapement", eState)
2179  && eState == beans::PropertyState_DIRECT_VALUE)
2180  mAny >>= nCharEscapement;
2181 
2182  if (GetPropertyAndState(rXPropSet, rXPropState, "CharEscapementHeight", eState)
2183  && eState == beans::PropertyState_DIRECT_VALUE)
2184  mAny >>= nCharEscapementHeight;
2185 
2186  if (DFLT_ESC_AUTO_SUPER == nCharEscapement)
2187  {
2188  // Raised by the differences between the ascenders (ascent = baseline to top of highest letter).
2189  // The ascent is generally about 80% of the total font height.
2190  // That is why DFLT_ESC_PROP (58) leads to 33% (DFLT_ESC_SUPER)
2191  nCharEscapement = .8 * (100 - nCharEscapementHeight);
2192  }
2193  else if (DFLT_ESC_AUTO_SUB == nCharEscapement)
2194  {
2195  // Lowered by the differences between the descenders (descent = baseline to bottom of lowest letter).
2196  // The descent is generally about 20% of the total font height.
2197  // That is why DFLT_ESC_PROP (58) leads to 8% (DFLT_ESC_SUB)
2198  nCharEscapement = .2 * -(100 - nCharEscapementHeight);
2199  }
2200 
2201  if (nCharEscapement && nCharEscapementHeight)
2202  {
2203  nSize = (nSize * nCharEscapementHeight) / 100;
2204  // MSO uses default ~58% size
2205  nSize = (nSize / 0.58);
2206  }
2207 
2208  if (GetProperty(rXPropSet, "CharCaseMap"))
2209  {
2210  switch ( *o3tl::doAccess<sal_Int16>(mAny) )
2211  {
2212  case CaseMap::UPPERCASE :
2213  cap = "all";
2214  break;
2215  case CaseMap::SMALLCAPS :
2216  cap = "small";
2217  break;
2218  }
2219  }
2220 
2221  mpFS->startElementNS( XML_a, nElement,
2222  XML_b, bold,
2223  XML_i, italic,
2224  XML_lang, sax_fastparser::UseIf(usLanguage, !usLanguage.isEmpty()),
2225  XML_sz, OString::number(nSize),
2226  // For Condensed character spacing spc value is negative.
2227  XML_spc, sax_fastparser::UseIf(OString::number(nCharKerning), nCharKerning != 0),
2228  XML_strike, strikeout,
2229  XML_u, underline,
2230  XML_baseline, sax_fastparser::UseIf(OString::number(nCharEscapement*1000), nCharEscapement != 0),
2231  XML_cap, cap );
2232 
2233  // Fontwork-shapes in LO have text outline and fill from shape stroke and shape fill
2234  // PowerPoint has this as run properties
2235  if (IsFontworkShape(rXShapePropSet))
2236  {
2237  WriteOutline(rXShapePropSet);
2238  WriteBlipOrNormalFill(rXShapePropSet, "Graphic");
2239  WriteShapeEffects(rXShapePropSet);
2240  }
2241  else
2242  {
2243  // mso doesn't like text color to be placed after typeface
2244  if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharColor", eState)
2245  && eState == beans::PropertyState_DIRECT_VALUE)
2246  || GetProperty(rXPropSet, "CharColor"))
2247  {
2248  ::Color color( ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny) );
2249  SAL_INFO("oox.shape", "run color: " << sal_uInt32(color) << " auto: " << sal_uInt32(COL_AUTO));
2250 
2251  // WriteSolidFill() handles MAX_PERCENT as "no transparency".
2252  sal_Int32 nTransparency = MAX_PERCENT;
2253  if (rXPropSet->getPropertySetInfo()->hasPropertyByName("CharTransparence"))
2254  {
2255  rXPropSet->getPropertyValue("CharTransparence") >>= nTransparency;
2256  // UNO scale is 0..100, OOXML scale is 0..100000; also UNO tracks transparency, OOXML
2257  // tracks opacity.
2258  nTransparency = MAX_PERCENT - (nTransparency * PER_PERCENT);
2259  }
2260 
2261  bool bContoured = false;
2262  if (GetProperty(rXPropSet, "CharContoured"))
2263  bContoured = *o3tl::doAccess<bool>(mAny);
2264 
2265  // tdf#127696 If the CharContoured is true, then the text color is white and the outline color is the CharColor.
2266  if (bContoured)
2267  {
2268  mpFS->startElementNS(XML_a, XML_ln);
2269  if (color == COL_AUTO)
2270  {
2272  }
2273  else
2274  {
2275  color.SetAlpha(255);
2276  if (!WriteCharColor(rXPropSet))
2277  WriteSolidFill(color, nTransparency);
2278  }
2279  mpFS->endElementNS(XML_a, XML_ln);
2280 
2282  }
2283  // tdf#104219 In LibreOffice and MS Office, there are two types of colors:
2284  // Automatic and Fixed. OOXML is setting automatic color, by not providing color.
2285  else if( color != COL_AUTO )
2286  {
2287  color.SetAlpha(255);
2288  // TODO: special handle embossed/engraved
2289  if (!WriteCharColor(rXPropSet))
2290  {
2291  WriteSolidFill(color, nTransparency);
2292  }
2293  }
2294  }
2295  }
2296 
2297  // tdf#128096, exporting XML_highlight to docx already works fine,
2298  // so make sure this code is only run when exporting to pptx, just in case
2299  if (GetDocumentType() == DOCUMENT_PPTX)
2300  {
2301  if (GetProperty(rXPropSet, "CharBackColor"))
2302  {
2303  ::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny));
2304  if( color != COL_AUTO )
2305  {
2306  mpFS->startElementNS(XML_a, XML_highlight);
2307  WriteColor( color );
2308  mpFS->endElementNS( XML_a, XML_highlight );
2309  }
2310  }
2311  }
2312 
2313  if (underline
2314  && ((bCheckDirect
2315  && GetPropertyAndState(rXPropSet, rXPropState, "CharUnderlineColor", eState)
2316  && eState == beans::PropertyState_DIRECT_VALUE)
2317  || GetProperty(rXPropSet, "CharUnderlineColor")))
2318  {
2319  ::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny));
2320  // if color is automatic, then we shouldn't write information about color but to take color from character
2321  if( color != COL_AUTO )
2322  {
2323  mpFS->startElementNS(XML_a, XML_uFill);
2324  WriteSolidFill( color );
2325  mpFS->endElementNS( XML_a, XML_uFill );
2326  }
2327  else
2328  {
2329  mpFS->singleElementNS(XML_a, XML_uFillTx);
2330  }
2331  }
2332 
2333  if (GetProperty(rXPropSet, "CharFontName"))
2334  {
2335  const char* const pitch = nullptr;
2336  const char* const charset = nullptr;
2337  OUString usTypeface;
2338 
2339  mAny >>= usTypeface;
2340  OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) );
2341  if (!aSubstName.isEmpty())
2342  usTypeface = aSubstName;
2343 
2344  mpFS->singleElementNS( XML_a, XML_latin,
2345  XML_typeface, usTypeface,
2346  XML_pitchFamily, pitch,
2347  XML_charset, charset );
2348  }
2349 
2350  if ((bComplex
2351  && (GetPropertyAndState(rXPropSet, rXPropState, "CharFontNameComplex", eState)
2352  && eState == beans::PropertyState_DIRECT_VALUE))
2353  || (!bComplex
2354  && (GetPropertyAndState(rXPropSet, rXPropState, "CharFontNameAsian", eState)
2355  && eState == beans::PropertyState_DIRECT_VALUE)))
2356  {
2357  const char* const pitch = nullptr;
2358  const char* const charset = nullptr;
2359  OUString usTypeface;
2360 
2361  mAny >>= usTypeface;
2362  OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) );
2363  if (!aSubstName.isEmpty())
2364  usTypeface = aSubstName;
2365 
2366  mpFS->singleElementNS( XML_a, bComplex ? XML_cs : XML_ea,
2367  XML_typeface, usTypeface,
2368  XML_pitchFamily, pitch,
2369  XML_charset, charset );
2370  }
2371 
2372  if( bIsField )
2373  {
2374  Reference< XTextField > rXTextField;
2375  if (GetProperty(rXPropSet, "TextField"))
2376  mAny >>= rXTextField;
2377  if( rXTextField.is() )
2378  rXPropSet.set( rXTextField, UNO_QUERY );
2379  }
2380 
2381  // field properties starts here
2382  if (GetProperty(rXPropSet, "URL"))
2383  {
2384  OUString sURL;
2385 
2386  mAny >>= sURL;
2387  if (!sURL.isEmpty())
2388  {
2389  if (!sURL.match("#action?jump="))
2390  {
2391  bool bExtURL = URLTransformer().isExternalURL(sURL);
2392  sURL = bExtURL ? sURL : lcl_GetTarget(GetFB()->getModel(), sURL);
2393 
2394  OUString sRelId
2395  = mpFB->addRelation(mpFS->getOutputStream(),
2398  sURL, bExtURL);
2399 
2400  if (bExtURL)
2401  mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
2402  else
2403  mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId,
2404  XML_action, "ppaction://hlinksldjump");
2405  }
2406  else
2407  {
2408  sal_Int32 nIndex = sURL.indexOf('=');
2409  std::u16string_view aDestination(sURL.subView(nIndex + 1));
2410  mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action,
2411  OUString::Concat("ppaction://hlinkshowjump?jump=") + aDestination);
2412  }
2413  }
2414  }
2415  mpFS->endElementNS( XML_a, nElement );
2416 }
2417 
2418 OUString DrawingML::GetFieldValue( const css::uno::Reference< css::text::XTextRange >& rRun, bool& bIsURLField )
2419 {
2420  Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY );
2421  OUString aFieldType, aFieldValue;
2422 
2423  if (GetProperty(rXPropSet, "TextPortionType"))
2424  {
2425  aFieldType = *o3tl::doAccess<OUString>(mAny);
2426  SAL_INFO("oox.shape", "field type: " << aFieldType);
2427  }
2428 
2429  if( aFieldType == "TextField" )
2430  {
2431  Reference< XTextField > rXTextField;
2432  if (GetProperty(rXPropSet, "TextField"))
2433  mAny >>= rXTextField;
2434  if( rXTextField.is() )
2435  {
2436  rXPropSet.set( rXTextField, UNO_QUERY );
2437  if( rXPropSet.is() )
2438  {
2439  OUString aFieldKind( rXTextField->getPresentation( true ) );
2440  SAL_INFO("oox.shape", "field kind: " << aFieldKind);
2441  if( aFieldKind == "Page" )
2442  {
2443  aFieldValue = "slidenum";
2444  }
2445  else if( aFieldKind == "Pages" )
2446  {
2447  aFieldValue = "slidecount";
2448  }
2449  else if( aFieldKind == "PageName" )
2450  {
2451  aFieldValue = "slidename";
2452  }
2453  else if( aFieldKind == "URL" )
2454  {
2455  bIsURLField = true;
2456  if (GetProperty(rXPropSet, "Representation"))
2457  mAny >>= aFieldValue;
2458 
2459  }
2460  else if(aFieldKind == "Date")
2461  {
2462  sal_Int32 nNumFmt = -1;
2463  rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt;
2464  aFieldValue = GetDatetimeTypeFromDate(static_cast<SvxDateFormat>(nNumFmt));
2465  }
2466  else if(aFieldKind == "ExtTime")
2467  {
2468  sal_Int32 nNumFmt = -1;
2469  rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt;
2470  aFieldValue = GetDatetimeTypeFromTime(static_cast<SvxTimeFormat>(nNumFmt));
2471  }
2472  else if(aFieldKind == "ExtFile")
2473  {
2474  sal_Int32 nNumFmt = -1;
2475  rXPropSet->getPropertyValue(UNO_TC_PROP_FILE_FORMAT) >>= nNumFmt;
2476  switch(nNumFmt)
2477  {
2478  case 0: aFieldValue = "file"; // Path/File name
2479  break;
2480  case 1: aFieldValue = "file1"; // Path
2481  break;
2482  case 2: aFieldValue = "file2"; // File name without extension
2483  break;
2484  case 3: aFieldValue = "file3"; // File name with extension
2485  }
2486  }
2487  else if(aFieldKind == "Author")
2488  {
2489  aFieldValue = "author";
2490  }
2491  }
2492  }
2493  }
2494  return aFieldValue;
2495 }
2496 
2498 {
2499  return GetDatetimeTypeFromDateTime(eDate, SvxTimeFormat::AppDefault);
2500 }
2501 
2503 {
2504  return GetDatetimeTypeFromDateTime(SvxDateFormat::AppDefault, eTime);
2505 }
2506 
2508 {
2509  OUString aDateField;
2510  switch (eDate)
2511  {
2512  case SvxDateFormat::StdSmall:
2513  case SvxDateFormat::A:
2514  aDateField = "datetime";
2515  break;
2516  case SvxDateFormat::B:
2517  aDateField = "datetime1"; // 13/02/1996
2518  break;
2519  case SvxDateFormat::C:
2520  aDateField = "datetime5";
2521  break;
2522  case SvxDateFormat::D:
2523  aDateField = "datetime3"; // 13 February 1996
2524  break;
2525  case SvxDateFormat::StdBig:
2526  case SvxDateFormat::E:
2527  case SvxDateFormat::F:
2528  aDateField = "datetime2";
2529  break;
2530  default:
2531  break;
2532  }
2533 
2534  OUString aTimeField;
2535  switch (eTime)
2536  {
2537  case SvxTimeFormat::Standard:
2538  case SvxTimeFormat::HH24_MM_SS:
2539  case SvxTimeFormat::HH24_MM_SS_00:
2540  aTimeField = "datetime11"; // 13:49:38
2541  break;
2542  case SvxTimeFormat::HH24_MM:
2543  aTimeField = "datetime10"; // 13:49
2544  break;
2545  case SvxTimeFormat::HH12_MM:
2546  case SvxTimeFormat::HH12_MM_AMPM:
2547  aTimeField = "datetime12"; // 01:49 PM
2548  break;
2549  case SvxTimeFormat::HH12_MM_SS:
2550  case SvxTimeFormat::HH12_MM_SS_AMPM:
2551  case SvxTimeFormat::HH12_MM_SS_00:
2552  case SvxTimeFormat::HH12_MM_SS_00_AMPM:
2553  aTimeField = "datetime13"; // 01:49:38 PM
2554  break;
2555  default:
2556  break;
2557  }
2558 
2559  if (!aDateField.isEmpty() && aTimeField.isEmpty())
2560  return aDateField;
2561  else if (!aTimeField.isEmpty() && aDateField.isEmpty())
2562  return aTimeField;
2563  else if (!aDateField.isEmpty() && !aTimeField.isEmpty())
2564  {
2565  if (aTimeField == "datetime11" || aTimeField == "datetime13")
2566  // only datetime format that has Date and HH:MM:SS
2567  return "datetime9"; // dd/mm/yyyy H:MM:SS
2568  else
2569  // only datetime format that has Date and HH:MM
2570  return "datetime8"; // dd/mm/yyyy H:MM
2571  }
2572  else
2573  return "";
2574 }
2575 
2577  bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
2578  const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet)
2579 {
2580  Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY );
2581  sal_Int16 nLevel = -1;
2582  if (GetProperty(rXPropSet, "NumberingLevel"))
2583  mAny >>= nLevel;
2584 
2585  bool bNumberingIsNumber = true;
2586  if (GetProperty(rXPropSet, "NumberingIsNumber"))
2587  mAny >>= bNumberingIsNumber;
2588 
2589  float nFontSize = -1;
2590  if (GetProperty(rXPropSet, "CharHeight"))
2591  mAny >>= nFontSize;
2592 
2593  bool bIsURLField = false;
2594  OUString sFieldValue = GetFieldValue( rRun, bIsURLField );
2595  bool bWriteField = !( sFieldValue.isEmpty() || bIsURLField );
2596 
2597  OUString sText = rRun->getString();
2598 
2599  //if there is no text following the bullet, add a space after the bullet
2600  if (nLevel !=-1 && bNumberingIsNumber && sText.isEmpty() )
2601  sText=" ";
2602 
2603  if ( bIsURLField )
2604  sText = sFieldValue;
2605 
2606  if( sText.isEmpty())
2607  {
2608  Reference< XPropertySet > xPropSet( rRun, UNO_QUERY );
2609 
2610  try
2611  {
2612  if( !xPropSet.is() || !( xPropSet->getPropertyValue( "PlaceholderText" ) >>= sText ) )
2613  return;
2614  if( sText.isEmpty() )
2615  return;
2616  }
2617  catch (const Exception &)
2618  {
2619  return;
2620  }
2621  }
2622 
2623  if (sText == "\n")
2624  {
2625  // Empty run? Do not forget to write the font size in case of pptx:
2626  if ((GetDocumentType() == DOCUMENT_PPTX) && (nFontSize != -1))
2627  {
2628  mpFS->startElementNS(XML_a, XML_br);
2629  mpFS->singleElementNS(XML_a, XML_rPr, XML_sz,
2630  OString::number(nFontSize * 100).getStr());
2631  mpFS->endElementNS(XML_a, XML_br);
2632  }
2633  else
2634  mpFS->singleElementNS(XML_a, XML_br);
2635  }
2636  else
2637  {
2638  if( bWriteField )
2639  {
2640  OString sUUID(comphelper::xml::generateGUIDString());
2641  mpFS->startElementNS( XML_a, XML_fld,
2642  XML_id, sUUID.getStr(),
2643  XML_type, sFieldValue );
2644  }
2645  else
2646  {
2647  mpFS->startElementNS(XML_a, XML_r);
2648  }
2649 
2650  Reference< XPropertySet > xPropSet( rRun, uno::UNO_QUERY );
2651 
2652  WriteRunProperties( xPropSet, bIsURLField, XML_rPr, true, rbOverridingCharHeight, rnCharHeight, GetScriptType(sText), rXShapePropSet);
2653  mpFS->startElementNS(XML_a, XML_t);
2654  mpFS->writeEscaped( sText );
2655  mpFS->endElementNS( XML_a, XML_t );
2656 
2657  if( bWriteField )
2658  mpFS->endElementNS( XML_a, XML_fld );
2659  else
2660  mpFS->endElementNS( XML_a, XML_r );
2661  }
2662 }
2663 
2664 static OUString GetAutoNumType(SvxNumType nNumberingType, bool bSDot, bool bPBehind, bool bPBoth)
2665 {
2666  OUString sPrefixSuffix;
2667 
2668  if (bPBoth)
2669  sPrefixSuffix = "ParenBoth";
2670  else if (bPBehind)
2671  sPrefixSuffix = "ParenR";
2672  else if (bSDot)
2673  sPrefixSuffix = "Period";
2674 
2675  switch( nNumberingType )
2676  {
2679  return "alphaUc" + sPrefixSuffix;
2680 
2683  return "alphaLc" + sPrefixSuffix;
2684 
2685  case SVX_NUM_ROMAN_UPPER :
2686  return "romanUc" + sPrefixSuffix;
2687 
2688  case SVX_NUM_ROMAN_LOWER :
2689  return "romanLc" + sPrefixSuffix;
2690 
2691  case SVX_NUM_ARABIC :
2692  {
2693  if (sPrefixSuffix.isEmpty())
2694  return "arabicPlain";
2695  else
2696  return "arabic" + sPrefixSuffix;
2697  }
2698  default:
2699  break;
2700  }
2701 
2702  return OUString();
2703 }
2704 
2705 void DrawingML::WriteParagraphNumbering(const Reference< XPropertySet >& rXPropSet, float fFirstCharHeight, sal_Int16 nLevel )
2706 {
2707  if (nLevel < 0 || !GetProperty(rXPropSet, "NumberingRules"))
2708  {
2709  if (GetDocumentType() == DOCUMENT_PPTX)
2710  {
2711  mpFS->singleElementNS(XML_a, XML_buNone);
2712  }
2713  return;
2714  }
2715 
2716  Reference< XIndexAccess > rXIndexAccess;
2717 
2718  if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount())
2719  return;
2720 
2721  SAL_INFO("oox.shape", "numbering rules");
2722 
2723  Sequence<PropertyValue> aPropertySequence;
2724  rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence;
2725 
2726  if (!aPropertySequence.hasElements())
2727  return;
2728 
2729  SvxNumType nNumberingType = SVX_NUM_NUMBER_NONE;
2730  bool bSDot = false;
2731  bool bPBehind = false;
2732  bool bPBoth = false;
2733  sal_Unicode aBulletChar = 0x2022; // a bullet
2734  awt::FontDescriptor aFontDesc;
2735  bool bHasFontDesc = false;
2736  uno::Reference<graphic::XGraphic> xGraphic;
2737  sal_Int16 nBulletRelSize = 0;
2738  sal_Int16 nStartWith = 1;
2739  ::Color nBulletColor;
2740  bool bHasBulletColor = false;
2741  awt::Size aGraphicSize;
2742 
2743  for ( const PropertyValue& rPropValue : std::as_const(aPropertySequence) )
2744  {
2745  OUString aPropName( rPropValue.Name );
2746  SAL_INFO("oox.shape", "pro name: " << aPropName);
2747  if ( aPropName == "NumberingType" )
2748  {
2749  nNumberingType = static_cast<SvxNumType>(*o3tl::doAccess<sal_Int16>(rPropValue.Value));
2750  }
2751  else if ( aPropName == "Prefix" )
2752  {
2753  if( *o3tl::doAccess<OUString>(rPropValue.Value) == ")")
2754  bPBoth = true;
2755  }
2756  else if ( aPropName == "Suffix" )
2757  {
2758  auto s = o3tl::doAccess<OUString>(rPropValue.Value);
2759  if( *s == ".")
2760  bSDot = true;
2761  else if( *s == ")")
2762  bPBehind = true;
2763  }
2764  else if(aPropName == "BulletColor")
2765  {
2766  nBulletColor = ::Color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(rPropValue.Value));
2767  bHasBulletColor = true;
2768  }
2769  else if ( aPropName == "BulletChar" )
2770  {
2771  aBulletChar = (*o3tl::doAccess<OUString>(rPropValue.Value))[ 0 ];
2772  }
2773  else if ( aPropName == "BulletFont" )
2774  {
2775  aFontDesc = *o3tl::doAccess<awt::FontDescriptor>(rPropValue.Value);
2776  bHasFontDesc = true;
2777 
2778  // Our numbullet dialog has set the wrong textencoding for our "StarSymbol" font,
2779  // instead of a Unicode encoding the encoding RTL_TEXTENCODING_SYMBOL was used.
2780  // Because there might exist a lot of damaged documents I added this two lines
2781  // which fixes the bullet problem for the export.
2782  if ( aFontDesc.Name.equalsIgnoreAsciiCase("StarSymbol") )
2783  aFontDesc.CharSet = RTL_TEXTENCODING_MS_1252;
2784 
2785  }
2786  else if ( aPropName == "BulletRelSize" )
2787  {
2788  nBulletRelSize = *o3tl::doAccess<sal_Int16>(rPropValue.Value);
2789  }
2790  else if ( aPropName == "StartWith" )
2791  {
2792  nStartWith = *o3tl::doAccess<sal_Int16>(rPropValue.Value);
2793  }
2794  else if (aPropName == "GraphicBitmap")
2795  {
2796  auto xBitmap = rPropValue.Value.get<uno::Reference<awt::XBitmap>>();
2797  xGraphic.set(xBitmap, uno::UNO_QUERY);
2798  }
2799  else if ( aPropName == "GraphicSize" )
2800  {
2801  aGraphicSize = *o3tl::doAccess<awt::Size>(rPropValue.Value);
2802  SAL_INFO("oox.shape", "graphic size: " << aGraphicSize.Width << "x" << aGraphicSize.Height);
2803  }
2804  }
2805 
2806  if (nNumberingType == SVX_NUM_NUMBER_NONE)
2807  return;
2808 
2809  Graphic aGraphic(xGraphic);
2810  if (xGraphic.is() && aGraphic.GetType() != GraphicType::NONE)
2811  {
2812  tools::Long nFirstCharHeightMm = TransformMetric(fFirstCharHeight * 100.f, FieldUnit::POINT, FieldUnit::MM);
2813  float fBulletSizeRel = aGraphicSize.Height / static_cast<float>(nFirstCharHeightMm) / OOX_BULLET_LIST_SCALE_FACTOR;
2814 
2815  OUString sRelationId;
2816 
2817  if (fBulletSizeRel < 1.0f)
2818  {
2819  // Add padding to get the bullet point centered in PPT
2820  Size aDestSize(64, 64);
2821  float fBulletSizeRelX = fBulletSizeRel / aGraphicSize.Height * aGraphicSize.Width;
2822  tools::Long nPaddingX = std::max<tools::Long>(0, std::lround((aDestSize.Width() - fBulletSizeRelX * aDestSize.Width()) / 2.f));
2823  tools::Long nPaddingY = std::lround((aDestSize.Height() - fBulletSizeRel * aDestSize.Height()) / 2.f);
2824  tools::Rectangle aDestRect(nPaddingX, nPaddingY, aDestSize.Width() - nPaddingX, aDestSize.Height() - nPaddingY);
2825 
2826  AlphaMask aMask(aDestSize);
2827  aMask.Erase(255);
2828  BitmapEx aSourceBitmap(aGraphic.GetBitmapEx());
2829  aSourceBitmap.Scale(aDestRect.GetSize());
2830  tools::Rectangle aSourceRect(Point(0, 0), aDestRect.GetSize());
2831  BitmapEx aDestBitmap(Bitmap(aDestSize, vcl::PixelFormat::N24_BPP), aMask);
2832  aDestBitmap.CopyPixel(aDestRect, aSourceRect, &aSourceBitmap);
2833  Graphic aDestGraphic(aDestBitmap);
2834  sRelationId = WriteImage(aDestGraphic);
2835  fBulletSizeRel = 1.0f;
2836  }
2837  else
2838  {
2839  sRelationId = WriteImage(aGraphic);
2840  }
2841 
2842  mpFS->singleElementNS( XML_a, XML_buSzPct,
2843  XML_val, OString::number(std::min<sal_Int32>(std::lround(100000.f * fBulletSizeRel), 400000)));
2844  mpFS->startElementNS(XML_a, XML_buBlip);
2845  mpFS->singleElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelationId);
2846  mpFS->endElementNS( XML_a, XML_buBlip );
2847  }
2848  else
2849  {
2850  if(bHasBulletColor)
2851  {
2852  if (nBulletColor == COL_AUTO )
2853  {
2854  nBulletColor = ::Color(ColorTransparency, mbIsBackgroundDark ? 0xffffff : 0x000000);
2855  }
2856  mpFS->startElementNS(XML_a, XML_buClr);
2857  WriteColor( nBulletColor );
2858  mpFS->endElementNS( XML_a, XML_buClr );
2859  }
2860 
2861  if( nBulletRelSize && nBulletRelSize != 100 )
2862  mpFS->singleElementNS( XML_a, XML_buSzPct,
2863  XML_val, OString::number(std::clamp<sal_Int32>(1000*nBulletRelSize, 25000, 400000)));
2864  if( bHasFontDesc )
2865  {
2866  if ( SVX_NUM_CHAR_SPECIAL == nNumberingType )
2867  aBulletChar = SubstituteBullet( aBulletChar, aFontDesc );
2868  mpFS->singleElementNS( XML_a, XML_buFont,
2869  XML_typeface, aFontDesc.Name,
2870  XML_charset, sax_fastparser::UseIf("2", aFontDesc.CharSet == awt::CharSet::SYMBOL));
2871  }
2872 
2873  OUString aAutoNumType = GetAutoNumType( nNumberingType, bSDot, bPBehind, bPBoth );
2874 
2875  if (!aAutoNumType.isEmpty())
2876  {
2877  mpFS->singleElementNS(XML_a, XML_buAutoNum,
2878  XML_type, aAutoNumType,
2879  XML_startAt, sax_fastparser::UseIf(OString::number(nStartWith), nStartWith > 1));
2880  }
2881  else
2882  {
2883  mpFS->singleElementNS(XML_a, XML_buChar, XML_char, OUString(aBulletChar));
2884  }
2885  }
2886 }
2887 
2889 {
2890  css::uno::Sequence<css::style::TabStop> aTabStops;
2891  if (GetProperty(rXPropSet, "ParaTabStops"))
2892  aTabStops = *o3tl::doAccess<css::uno::Sequence<css::style::TabStop>>(mAny);
2893 
2894  if (aTabStops.getLength() > 0)
2895  mpFS->startElementNS(XML_a, XML_tabLst);
2896 
2897  for (const css::style::TabStop& rTabStop : std::as_const(aTabStops))
2898  {
2899  OString sPosition = OString::number(GetPointFromCoordinate(rTabStop.Position));
2900  OString sAlignment;
2901  switch (rTabStop.Alignment)
2902  {
2903  case css::style::TabAlign_DECIMAL:
2904  sAlignment = "dec";
2905  break;
2906  case css::style::TabAlign_RIGHT:
2907  sAlignment = "r";
2908  break;
2909  case css::style::TabAlign_CENTER:
2910  sAlignment = "ctr";
2911  break;
2912  case css::style::TabAlign_LEFT:
2913  default:
2914  sAlignment = "l";
2915  }
2916  mpFS->singleElementNS(XML_a, XML_tab, XML_algn, sAlignment, XML_pos, sPosition);
2917  }
2918  if (aTabStops.getLength() > 0)
2919  mpFS->endElementNS(XML_a, XML_tabLst);
2920 }
2921 
2923 {
2924  bool bRet = false;
2925  if ( rXShape.is() )
2926  {
2927  uno::Reference<lang::XServiceInfo> xServiceInfo(rXShape, uno::UNO_QUERY_THROW);
2928  bRet = xServiceInfo->supportsService("com.sun.star.drawing.GroupShape");
2929  }
2930  return bRet;
2931 }
2932 
2933 sal_Int32 DrawingML::getBulletMarginIndentation (const Reference< XPropertySet >& rXPropSet,sal_Int16 nLevel, std::u16string_view propName)
2934 {
2935  if (nLevel < 0 || !GetProperty(rXPropSet, "NumberingRules"))
2936  return 0;
2937 
2938  Reference< XIndexAccess > rXIndexAccess;
2939 
2940  if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount())
2941  return 0;
2942 
2943  SAL_INFO("oox.shape", "numbering rules");
2944 
2945  Sequence<PropertyValue> aPropertySequence;
2946  rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence;
2947 
2948  if (!aPropertySequence.hasElements())
2949  return 0;
2950 
2951  for ( const PropertyValue& rPropValue : std::as_const(aPropertySequence) )
2952  {
2953  OUString aPropName( rPropValue.Name );
2954  SAL_INFO("oox.shape", "pro name: " << aPropName);
2955  if ( aPropName == propName )
2956  return *o3tl::doAccess<sal_Int32>(rPropValue.Value);
2957  }
2958 
2959  return 0;
2960 }
2961 
2962 const char* DrawingML::GetAlignment( style::ParagraphAdjust nAlignment )
2963 {
2964  const char* sAlignment = nullptr;
2965 
2966  switch( nAlignment )
2967  {
2968  case style::ParagraphAdjust_CENTER:
2969  sAlignment = "ctr";
2970  break;
2971  case style::ParagraphAdjust_RIGHT:
2972  sAlignment = "r";
2973  break;
2974  case style::ParagraphAdjust_BLOCK:
2975  sAlignment = "just";
2976  break;
2977  default:
2978  ;
2979  }
2980 
2981  return sAlignment;
2982 }
2983 
2984 void DrawingML::WriteLinespacing(const LineSpacing& rSpacing, float fFirstCharHeight)
2985 {
2986  if( rSpacing.Mode == LineSpacingMode::PROP )
2987  {
2988  mpFS->singleElementNS( XML_a, XML_spcPct,
2989  XML_val, OString::number(static_cast<sal_Int32>(rSpacing.Height)*1000));
2990  }
2991  else if (rSpacing.Mode == LineSpacingMode::MINIMUM
2992  && fFirstCharHeight > static_cast<float>(rSpacing.Height) * 0.001 * 72.0 / 2.54)
2993  {
2994  // 100% proportional line spacing = single line spacing
2995  mpFS->singleElementNS(XML_a, XML_spcPct, XML_val,
2996  OString::number(static_cast<sal_Int32>(100000)));
2997  }
2998  else
2999  {
3000  mpFS->singleElementNS( XML_a, XML_spcPts,
3001  XML_val, OString::number(std::lround(rSpacing.Height / 25.4 * 72)));
3002  }
3003 }
3004 
3005 bool DrawingML::WriteParagraphProperties( const Reference< XTextContent >& rParagraph, float fFirstCharHeight, sal_Int32 nElement)
3006 {
3007  Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY );
3008  Reference< XPropertyState > rXPropState( rParagraph, UNO_QUERY );
3009  PropertyState eState;
3010 
3011  if( !rXPropSet.is() || !rXPropState.is() )
3012  return false;
3013 
3014  sal_Int16 nLevel = -1;
3015  if (GetProperty(rXPropSet, "NumberingLevel"))
3016  mAny >>= nLevel;
3017 
3018  sal_Int16 nTmp = sal_Int16(style::ParagraphAdjust_LEFT);
3019  if (GetProperty(rXPropSet, "ParaAdjust"))
3020  mAny >>= nTmp;
3021  style::ParagraphAdjust nAlignment = static_cast<style::ParagraphAdjust>(nTmp);
3022 
3023  bool bHasLinespacing = false;
3024  LineSpacing aLineSpacing;
3025  if (GetPropertyAndState(rXPropSet, rXPropState, "ParaLineSpacing", eState)
3026  && (mAny >>= aLineSpacing)
3027  && (eState == beans::PropertyState_DIRECT_VALUE ||
3028  // only export if it differs from the default 100% line spacing
3029  aLineSpacing.Mode != LineSpacingMode::PROP || aLineSpacing.Height != 100))
3030  bHasLinespacing = true;
3031 
3032  bool bRtl = false;
3033  if (GetProperty(rXPropSet, "WritingMode"))
3034  {
3035  sal_Int16 nWritingMode;
3036  if( ( mAny >>= nWritingMode ) && nWritingMode == text::WritingMode2::RL_TB )
3037  {
3038  bRtl = true;
3039  }
3040  }
3041 
3042  sal_Int32 nParaLeftMargin = 0;
3043  sal_Int32 nParaFirstLineIndent = 0;
3044 
3045  if (GetProperty(rXPropSet, "ParaLeftMargin"))
3046  mAny >>= nParaLeftMargin;
3047  if (GetProperty(rXPropSet, "ParaFirstLineIndent"))
3048  mAny >>= nParaFirstLineIndent;
3049 
3050  sal_Int32 nParaTopMargin = 0;
3051  sal_Int32 nParaBottomMargin = 0;
3052 
3053  if (GetProperty(rXPropSet, "ParaTopMargin"))
3054  mAny >>= nParaTopMargin;
3055  if (GetProperty(rXPropSet, "ParaBottomMargin"))
3056  mAny >>= nParaBottomMargin;
3057 
3058  sal_Int32 nLeftMargin = getBulletMarginIndentation ( rXPropSet, nLevel,u"LeftMargin");
3059  sal_Int32 nLineIndentation = getBulletMarginIndentation ( rXPropSet, nLevel,u"FirstLineOffset");
3060 
3061  if( !(nLevel != -1
3062  || nAlignment != style::ParagraphAdjust_LEFT
3063  || bHasLinespacing) )
3064  return false;
3065 
3066  if (nParaLeftMargin) // For Paragraph
3067  mpFS->startElementNS( XML_a, nElement,
3068  XML_lvl, sax_fastparser::UseIf(OString::number(nLevel), nLevel > 0),
3069  XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaLeftMargin)), nParaLeftMargin > 0),
3070  XML_indent, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaFirstLineIndent)), nParaFirstLineIndent != 0),
3071  XML_algn, GetAlignment( nAlignment ),
3072  XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl));
3073  else
3074  mpFS->startElementNS( XML_a, nElement,
3075  XML_lvl, sax_fastparser::UseIf(OString::number(nLevel), nLevel > 0),
3076  XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeftMargin)), nLeftMargin > 0),
3077  XML_indent, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLineIndentation)), nLineIndentation != 0),
3078  XML_algn, GetAlignment( nAlignment ),
3079  XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl));
3080 
3081 
3082  if( bHasLinespacing )
3083  {
3084  mpFS->startElementNS(XML_a, XML_lnSpc);
3085  WriteLinespacing(aLineSpacing, fFirstCharHeight);
3086  mpFS->endElementNS( XML_a, XML_lnSpc );
3087  }
3088 
3089  if( nParaTopMargin != 0 )
3090  {
3091  mpFS->startElementNS(XML_a, XML_spcBef);
3092  {
3093  mpFS->singleElementNS( XML_a, XML_spcPts,
3094  XML_val, OString::number(std::lround(nParaTopMargin / 25.4 * 72)));
3095  }
3096  mpFS->endElementNS( XML_a, XML_spcBef );
3097  }
3098 
3099  if( nParaBottomMargin != 0 )
3100  {
3101  mpFS->startElementNS(XML_a, XML_spcAft);
3102  {
3103  mpFS->singleElementNS( XML_a, XML_spcPts,
3104  XML_val, OString::number(std::lround(nParaBottomMargin / 25.4 * 72)));
3105  }
3106  mpFS->endElementNS( XML_a, XML_spcAft );
3107  }
3108 
3109  WriteParagraphNumbering( rXPropSet, fFirstCharHeight, nLevel );
3110 
3111  WriteParagraphTabStops( rXPropSet );
3112 
3113  // do not end element for lstStyles since, defRPr should be stacked inside it
3114  if( nElement != XML_lvl1pPr )
3115  mpFS->endElementNS( XML_a, nElement );
3116 
3117  return true;
3118 }
3119 
3120 void DrawingML::WriteLstStyles(const css::uno::Reference<css::text::XTextContent>& rParagraph,
3121  bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
3122  const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet)
3123 {
3124  Reference<XEnumerationAccess> xAccess(rParagraph, UNO_QUERY);
3125  if (!xAccess.is())
3126  return;
3127 
3128  Reference<XEnumeration> xEnumeration(xAccess->createEnumeration());
3129  if (!xEnumeration.is())
3130  return;
3131 
3132 
3133  Reference<XTextRange> rRun;
3134 
3135  if (!xEnumeration->hasMoreElements())
3136  return;
3137 
3138  Any aAny(xEnumeration->nextElement());
3139  if (aAny >>= rRun)
3140  {
3141  float fFirstCharHeight = rnCharHeight / 1000.;
3142  Reference<XPropertySet> xFirstRunPropSet(rRun, UNO_QUERY);
3143  Reference<XPropertySetInfo> xFirstRunPropSetInfo
3144  = xFirstRunPropSet->getPropertySetInfo();
3145 
3146  if (xFirstRunPropSetInfo->hasPropertyByName("CharHeight"))
3147  fFirstCharHeight = xFirstRunPropSet->getPropertyValue("CharHeight").get<float>();
3148 
3149  mpFS->startElementNS(XML_a, XML_lstStyle);
3150  if( !WriteParagraphProperties(rParagraph, fFirstCharHeight, XML_lvl1pPr) )
3151  mpFS->startElementNS(XML_a, XML_lvl1pPr);
3152  WriteRunProperties(xFirstRunPropSet, false, XML_defRPr, true, rbOverridingCharHeight,
3153  rnCharHeight, GetScriptType(rRun->getString()), rXShapePropSet);
3154  mpFS->endElementNS(XML_a, XML_lvl1pPr);
3155  mpFS->endElementNS(XML_a, XML_lstStyle);
3156  }
3157 }
3158 
3160  bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
3161  const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet)
3162 {
3163  Reference< XEnumerationAccess > access( rParagraph, UNO_QUERY );
3164  if( !access.is() )
3165  return;
3166 
3167  Reference< XEnumeration > enumeration( access->createEnumeration() );
3168  if( !enumeration.is() )
3169  return;
3170 
3171  mpFS->startElementNS(XML_a, XML_p);
3172 
3173  bool bPropertiesWritten = false;
3174  while( enumeration->hasMoreElements() )
3175  {
3177  Any any ( enumeration->nextElement() );
3178 
3179  if (any >>= run)
3180  {
3181  if( !bPropertiesWritten )
3182  {
3183  float fFirstCharHeight = rnCharHeight / 1000.;
3184  Reference< XPropertySet > xFirstRunPropSet (run, UNO_QUERY);
3185  Reference< XPropertySetInfo > xFirstRunPropSetInfo = xFirstRunPropSet->getPropertySetInfo();
3186  if( xFirstRunPropSetInfo->hasPropertyByName("CharHeight") )
3187  {
3188  fFirstCharHeight = xFirstRunPropSet->getPropertyValue("CharHeight").get<float>();
3189  rnCharHeight = 100 * fFirstCharHeight;
3190  rbOverridingCharHeight = true;
3191  }
3192  WriteParagraphProperties(rParagraph, fFirstCharHeight, XML_pPr);
3193  bPropertiesWritten = true;
3194  }
3195  WriteRun( run, rbOverridingCharHeight, rnCharHeight, rXShapePropSet);
3196  }
3197  }
3198  Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY );
3199  sal_Int16 nDummy = -1;
3200  WriteRunProperties(rXPropSet, false, XML_endParaRPr, false, rbOverridingCharHeight,
3201  rnCharHeight, nDummy, rXShapePropSet);
3202 
3203  mpFS->endElementNS( XML_a, XML_p );
3204 }
3205 
3206 bool DrawingML::IsFontworkShape(const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet)
3207 {
3208  bool bResult(false);
3209  if (rXShapePropSet.is())
3210  {
3211  Sequence<PropertyValue> aCustomShapeGeometryProps;
3212  if (GetProperty(rXShapePropSet, "CustomShapeGeometry"))
3213  {
3214  mAny >>= aCustomShapeGeometryProps;
3215  uno::Sequence<beans::PropertyValue> aTextPathSeq;
3216  for (const auto& rProp : std::as_const(aCustomShapeGeometryProps))
3217  {
3218  if (rProp.Name == "TextPath")
3219  {
3220  rProp.Value >>= aTextPathSeq;
3221  for (const auto& rTextPathItem : std::as_const(aTextPathSeq))
3222  {
3223  if (rTextPathItem.Name == "TextPath")
3224  {
3225  rTextPathItem.Value >>= bResult;
3226  break;
3227  }
3228  }
3229  break;
3230  }
3231  }
3232  }
3233  }
3234  return bResult;
3235 }
3236 
3237 void DrawingML::WriteText(const Reference<XInterface>& rXIface, bool bBodyPr, bool bText,
3238  sal_Int32 nXmlNamespace, bool bWritePropertiesAsLstStyles)
3239 {
3240  // ToDo: Fontwork in DOCX
3241  Reference< XText > xXText( rXIface, UNO_QUERY );
3242  if( !xXText.is() )
3243  return;
3244 
3245  Reference< XPropertySet > rXPropSet( rXIface, UNO_QUERY );
3246 
3247  sal_Int32 nTextPreRotateAngle = 0;
3248  double nTextRotateAngle = 0;
3249 
3250 #define DEFLRINS 254
3251 #define DEFTBINS 127
3252  sal_Int32 nLeft, nRight, nTop, nBottom;
3253  nLeft = nRight = DEFLRINS;
3254  nTop = nBottom = DEFTBINS;
3255 
3256  // top inset looks a bit different compared to ppt export
3257  // check if something related doesn't work as expected
3258  if (GetProperty(rXPropSet, "TextLeftDistance"))
3259  mAny >>= nLeft;
3260  if (GetProperty(rXPropSet, "TextRightDistance"))
3261  mAny >>= nRight;
3262  if (GetProperty(rXPropSet, "TextUpperDistance"))
3263  mAny >>= nTop;
3264  if (GetProperty(rXPropSet, "TextLowerDistance"))
3265  mAny >>= nBottom;
3266 
3267  TextVerticalAdjust eVerticalAlignment( TextVerticalAdjust_TOP );
3268  const char* sVerticalAlignment = nullptr;
3269  if (GetProperty(rXPropSet, "TextVerticalAdjust"))
3270  mAny >>= eVerticalAlignment;
3271  sVerticalAlignment = GetTextVerticalAdjust(eVerticalAlignment);
3272 
3273  const char* sWritingMode = nullptr;
3274  bool bVertical = false;
3275  if (GetProperty(rXPropSet, "TextWritingMode"))
3276  {
3278 
3279  if( ( mAny >>= eMode ) && eMode == WritingMode_TB_RL )
3280  {
3281  sWritingMode = "eaVert";
3282  bVertical = true;
3283  }
3284  }
3285 
3286  bool bIsFontworkShape(IsFontworkShape(rXPropSet));
3288  uno::Sequence<beans::PropertyValue> aTextPathSeq;
3289  bool bScaleX(false);
3290  OUString sShapeType("non-primitive");
3291  // ToDo move to InteropGrabBag
3292  OUString sMSWordPresetTextWarp;
3293 
3294  if (GetProperty(rXPropSet, "CustomShapeGeometry"))
3295  {
3297  if ( mAny >>= aProps )
3298  {
3299  for ( const auto& rProp : std::as_const(aProps) )
3300  {
3301  if ( rProp.Name == "TextPreRotateAngle" && ( rProp.Value >>= nTextPreRotateAngle ) )
3302  {
3303  if ( nTextPreRotateAngle == -90 )
3304  {
3305  sWritingMode = "vert";
3306  bVertical = true;
3307  }
3308  else if ( nTextPreRotateAngle == -270 )
3309  {
3310  sWritingMode = "vert270";
3311  bVertical = true;
3312  }
3313  }
3314  else if (rProp.Name == "AdjustmentValues")
3315  rProp.Value >>= aAdjustmentSeq;
3316  else if( rProp.Name == "TextRotateAngle" )
3317  rProp.Value >>= nTextRotateAngle;
3318  else if (rProp.Name == "Type")
3319  rProp.Value >>= sShapeType;
3320  else if (rProp.Name == "TextPath")
3321  {
3322  rProp.Value >>= aTextPathSeq;
3323  for (const auto& rTextPathItem : std::as_const(aTextPathSeq))
3324  {
3325  if (rTextPathItem.Name == "ScaleX")
3326  rTextPathItem.Value >>= bScaleX;
3327  }
3328  }
3329  else if (rProp.Name == "PresetTextWarp")
3330  rProp.Value >>= sMSWordPresetTextWarp;
3331  }
3332  }
3333  }
3334  else
3335  {
3336  if (mpTextExport)
3337  {
3338  uno::Reference<drawing::XShape> xShape(rXIface, uno::UNO_QUERY);
3339  if (xShape)
3340  {
3341  auto xTextFrame = mpTextExport->GetUnoTextFrame(xShape);
3342  if (xTextFrame)
3343  {
3344  uno::Reference<beans::XPropertySet> xPropSet(xTextFrame, uno::UNO_QUERY);
3345  auto aAny = xPropSet->getPropertyValue("WritingMode");
3346  sal_Int16 nWritingMode;
3347  if (aAny >>= nWritingMode)
3348  {
3349  switch (nWritingMode)
3350  {
3351  case WritingMode2::TB_RL:
3352  sWritingMode = "vert";
3353  bVertical = true;
3354  break;
3355  case WritingMode2::BT_LR:
3356  sWritingMode = "vert270";
3357  bVertical = true;
3358  break;
3359  default:
3360  break;
3361  }
3362  }
3363  }
3364  }
3365  }
3366  }
3367  OUString sPresetWarp(PresetGeometryTypeNames::GetMsoName(sShapeType));
3368  // ODF may have user defined TextPath, use "textPlain" as ersatz.
3369  if (sPresetWarp.isEmpty())
3370  sPresetWarp = bIsFontworkShape ? std::u16string_view(u"textPlain") : std::u16string_view(u"textNoShape");
3371 
3372  bool bFromWordArt = !bScaleX
3373  && ( sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp"
3374  || sPresetWarp == "textButton" || sPresetWarp == "textCircle");
3375 
3376  TextHorizontalAdjust eHorizontalAlignment( TextHorizontalAdjust_CENTER );
3377  bool bHorizontalCenter = false;
3378  if (GetProperty(rXPropSet, "TextHorizontalAdjust"))
3379  mAny >>= eHorizontalAlignment;
3380  if( eHorizontalAlignment == TextHorizontalAdjust_CENTER )
3381  bHorizontalCenter = true;
3382  else if( bVertical && eHorizontalAlignment == TextHorizontalAdjust_LEFT )
3383  sVerticalAlignment = "b";
3384 
3385  bool bHasWrap = false;
3386  bool bWrap = false;
3387  // Only custom shapes obey the TextWordWrap option, normal text always wraps.
3388  if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, "TextWordWrap"))
3389  {
3390  mAny >>= bWrap;
3391  bHasWrap = true;
3392  }
3393 
3394  if (bBodyPr)
3395  {
3396  const char* pWrap = (bHasWrap && !bWrap) || bIsFontworkShape ? "none" : nullptr;
3397  if (GetDocumentType() == DOCUMENT_DOCX)
3398  {
3399  // In case of DOCX, if we want to have the same effect as
3400  // TextShape's automatic word wrapping, then we need to set
3401  // wrapping to square.
3402  uno::Reference<lang::XServiceInfo> xServiceInfo(rXIface, uno::UNO_QUERY);
3403  if (xServiceInfo.is() && xServiceInfo->supportsService("com.sun.star.drawing.TextShape"))
3404  pWrap = "square";
3405  }
3406 
3407  std::optional<OUString> sHorzOverflow;
3408  std::optional<OUString> sVertOverflow;
3409  sal_Int32 nShapeRotateAngle = rXPropSet->getPropertyValue("RotateAngle").get<sal_Int32>() / 300;
3410  sal_Int16 nCols = 0;
3411  sal_Int32 nColSpacing = -1;
3412  if (GetProperty(rXPropSet, "TextColumns"))
3413  {
3414  if (css::uno::Reference<css::text::XTextColumns> xCols{ mAny, css::uno::UNO_QUERY })
3415  {
3416  nCols = xCols->getColumnCount();
3417  if (css::uno::Reference<css::beans::XPropertySet> xProps{ mAny,
3418  css::uno::UNO_QUERY })
3419  {
3420  if (GetProperty(xProps, "AutomaticDistance"))
3421  mAny >>= nColSpacing;
3422  }
3423  }
3424  }
3425 
3426  std::optional<OString> isUpright;
3427  if (GetProperty(rXPropSet, "InteropGrabBag"))
3428  {
3429  if (rXPropSet->getPropertySetInfo()->hasPropertyByName("InteropGrabBag"))
3430  {
3431  bool bUpright = false;
3432  sal_Int32 nOldShapeRotation = 0;
3433  sal_Int32 nOldTextRotation = 0;
3434  uno::Sequence<beans::PropertyValue> aGrabBag;
3435  rXPropSet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
3436  for (const auto& aProp : std::as_const(aGrabBag))
3437  {
3438  if (aProp.Name == "Upright")
3439  {
3440  aProp.Value >>= bUpright;
3441  isUpright = OString(bUpright ? "1" : "0");
3442  }
3443  else if (aProp.Name == "horzOverflow")
3444  {
3445  OUString sValue;
3446  aProp.Value >>= sValue;
3447  sHorzOverflow = sValue;
3448  }
3449  else if (aProp.Name == "vertOverflow")
3450  {
3451  OUString sValue;
3452  aProp.Value >>= sValue;
3453  sVertOverflow = sValue;
3454  }
3455  }
3456  if (bUpright)
3457  {
3458  for (const auto& aProp : std::as_const(aGrabBag))
3459  {
3460  if (aProp.Name == "nShapeRotationAtImport")
3461  aProp.Value >>= nOldShapeRotation;
3462  else if (aProp.Name == "nTextRotationAtImport")
3463  aProp.Value >>= nOldTextRotation;
3464  }
3465  // So our shape with the textbox in it was not rotated.
3466  // Keep upright and make the preRotateAngle 0, it is an attribute
3467  // of textBodyPr and must be 0 when upright is true, otherwise
3468  // bad rotation happens in MSO.
3469  if (nShapeRotateAngle == nOldShapeRotation && nShapeRotateAngle == nOldTextRotation)
3470  nTextPreRotateAngle = 0;
3471  // So we rotated the shape, in this case lose upright and do
3472  // as LO normally does.
3473  else
3474  isUpright.reset();
3475  }
3476  }
3477  }
3478 
3479  mpFS->startElementNS( (nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr,
3480  XML_numCol, sax_fastparser::UseIf(OString::number(nCols), nCols > 0),
3481  XML_spcCol, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nColSpacing)), nCols > 0 && nColSpacing >= 0),
3482  XML_wrap, pWrap,
3483  XML_horzOverflow, sHorzOverflow,
3484  XML_vertOverflow, sVertOverflow,
3485  XML_fromWordArt, sax_fastparser::UseIf("1", bFromWordArt),
3486  XML_lIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeft)), nLeft != DEFLRINS),
3487  XML_rIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nRight)), nRight != DEFLRINS),
3488  XML_tIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nTop)), nTop != DEFTBINS),
3489  XML_bIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nBottom)), nBottom != DEFTBINS),
3490  XML_anchor, sVerticalAlignment,
3491  XML_anchorCtr, sax_fastparser::UseIf("1", bHorizontalCenter),
3492  XML_vert, sWritingMode,
3493  XML_upright, isUpright,
3494  XML_rot, sax_fastparser::UseIf(oox::drawingml::calcRotationValue((nTextPreRotateAngle + nTextRotateAngle) * 100), (nTextPreRotateAngle + nTextRotateAngle) != 0));
3495  if (bIsFontworkShape)
3496  {
3497  if (aAdjustmentSeq.hasElements())
3498  {
3499  mpFS->startElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp);
3500  mpFS->startElementNS(XML_a, XML_avLst);
3501  bool bHasTwoHandles(
3502  sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour"
3503  || sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour"
3504  || sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1"
3505  || sPresetWarp == "textWave2" || sPresetWarp == "textWave4");
3506  for (sal_Int32 i = 0, nElems = aAdjustmentSeq.getLength(); i < nElems; ++i )
3507  {
3508  OString sName = "adj" + (bHasTwoHandles ? OString::number(i + 1) : OString());
3509  double fValue(0.0);
3510  if (aAdjustmentSeq[i].Value.getValueTypeClass() == TypeClass_DOUBLE)
3511  aAdjustmentSeq[i].Value >>= fValue;
3512  else
3513  {
3514  sal_Int32 nNumber(0);
3515  aAdjustmentSeq[i].Value >>= nNumber;
3516  fValue = static_cast<double>(nNumber);
3517  }
3518  // Convert from binary coordinate system with viewBox "0 0 21600 21600" and simple degree
3519  // to DrawingML with coordinate range 0..100000 and angle in 1/60000 degree.
3520  // Reverse to conversion in lcl_createPresetShape in drawingml/shape.cxx on import.
3521  if (sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp"
3522  || sPresetWarp == "textButton" || sPresetWarp == "textCircle"
3523  || ((i == 0)
3524  && (sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour"
3525  || sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour")))
3526  {
3527  fValue *= 60000.0;
3528  if (fValue < 0)
3529  fValue += 21600000;
3530  }
3531  else if ((i == 1)
3532  && (sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1"
3533  || sPresetWarp == "textWave2" || sPresetWarp == "textWave4"))
3534  {
3535  fValue = fValue / 0.216 - 50000.0;
3536  }
3537  else if ((i == 1)
3538  && (sPresetWarp == "textArchDownPour"
3539  || sPresetWarp == "textArchUpPour"
3540  || sPresetWarp == "textButtonPour"
3541  || sPresetWarp == "textCirclePour"))
3542  {
3543  fValue /= 0.108;
3544  }
3545  else
3546  {
3547  fValue /= 0.216;
3548  }
3549  OString sFmla = "val " + OString::number(std::lround(fValue));
3550  mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla);
3551  // There exists faulty Favorite shapes with one handle but two adjustment values.
3552  if (!bHasTwoHandles)
3553  break;
3554  }
3555  mpFS->endElementNS(XML_a, XML_avLst);
3556  mpFS->endElementNS(XML_a, XML_prstTxWarp);
3557  }
3558  else
3559  {
3560  mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp);
3561  }
3562  }
3563  else if (GetDocumentType() == DOCUMENT_DOCX)
3564  {
3565  // interim solution for fdo#80897, roundtrip DOCX > LO > DOCX
3566  if (!sMSWordPresetTextWarp.isEmpty())
3567  mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sMSWordPresetTextWarp);
3568  }
3569 
3571  {
3572  // tdf#112312: only custom shapes obey the TextAutoGrowHeight option
3573  bool bTextAutoGrowHeight = false;
3574  uno::Reference<drawing::XShape> xShape(rXIface, uno::UNO_QUERY);
3575  auto pSdrObjCustomShape = xShape.is() ? dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape)) : nullptr;
3576  if (pSdrObjCustomShape && GetProperty(rXPropSet, "TextAutoGrowHeight"))
3577  {
3578  mAny >>= bTextAutoGrowHeight;
3579  }
3580  mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit));
3581  }
3582  if (GetDocumentType() == DOCUMENT_PPTX)
3583  {
3584  TextFitToSizeType eFit = TextFitToSizeType_NONE;
3585  if (GetProperty(rXPropSet, "TextFitToSize"))
3586  mAny >>= eFit;
3587 
3588  if (eFit == TextFitToSizeType_AUTOFIT)
3589  {
3590  const sal_Int32 MAX_SCALE_VAL = 100000;
3591  sal_Int32 nFontScale = MAX_SCALE_VAL;
3592  SvxShapeText* pTextShape = dynamic_cast<SvxShapeText*>(rXIface.get());
3593  if (pTextShape)
3594  {
3595  SdrTextObj* pTextObject = dynamic_cast<SdrTextObj*>(pTextShape->GetSdrObject());
3596  if (pTextObject)
3597  nFontScale = pTextObject->GetFontScaleY() * 1000;
3598  }
3599 
3600  mpFS->singleElementNS(XML_a, XML_normAutofit, XML_fontScale,
3601  sax_fastparser::UseIf(OString::number(nFontScale), nFontScale < MAX_SCALE_VAL && nFontScale > 0));
3602  }
3603  else
3604  {
3605  // tdf#127030: Only custom shapes obey the TextAutoGrowHeight option.
3606  bool bTextAutoGrowHeight = false;
3607  if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, "TextAutoGrowHeight"))
3608  mAny >>= bTextAutoGrowHeight;
3609  mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit));
3610  }
3611  }
3612 
3613  Write3DEffects( rXPropSet, /*bIsText=*/true );
3614 
3615  mpFS->endElementNS((nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr);
3616  }
3617 
3618  Reference< XEnumerationAccess > access( xXText, UNO_QUERY );
3619  if( !access.is() || !bText )
3620  return;
3621 
3622  Reference< XEnumeration > enumeration( access->createEnumeration() );
3623  if( !enumeration.is() )
3624  return;
3625 
3626  uno::Reference<drawing::XShape> xShape(rXIface, uno::UNO_QUERY);
3627  SdrObject* pSdrObject = xShape.is() ? SdrObject::getSdrObjectFromXShape(xShape) : nullptr;
3628  const SdrTextObj* pTxtObj = dynamic_cast<SdrTextObj*>( pSdrObject );
3629  if (pTxtObj && mpTextExport)
3630  {
3632 
3633  /*
3634  #i13885#
3635  When the object is actively being edited, that text is not set into
3636  the objects normal text object, but lives in a separate object.
3637  */
3638  if (pTxtObj->IsTextEditActive())
3639  {
3640  pParaObj = pTxtObj->CreateEditOutlinerParaObject();
3641  }
3642  else if (pTxtObj->GetOutlinerParaObject())
3643  pParaObj = *pTxtObj->GetOutlinerParaObject();
3644 
3645  if (pParaObj)
3646  {
3647  // this is reached only in case some text is attached to the shape
3648  mpTextExport->WriteOutliner(*pParaObj);
3649  }
3650  return;
3651  }
3652 
3653  bool bOverridingCharHeight = false;
3654  sal_Int32 nCharHeight = -1;
3655  bool bFirstParagraph = true;
3656 
3657  while( enumeration->hasMoreElements() )
3658  {
3660  Any any ( enumeration->nextElement() );
3661 
3662  if( any >>= paragraph)
3663  {
3664  if (bFirstParagraph && bWritePropertiesAsLstStyles)
3665  WriteLstStyles(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet);
3666 
3667  WriteParagraph(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet);
3668  bFirstParagraph = false;
3669  }
3670  }
3671 }
3672 
3673 void DrawingML::WritePresetShape( const OString& pShape , std::vector< std::pair<sal_Int32,sal_Int32>> & rAvList )
3674 {
3675  mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
3676  if ( !rAvList.empty() )
3677  {
3678 
3679  mpFS->startElementNS(XML_a, XML_avLst);
3680  for (auto const& elem : rAvList)
3681  {
3682  OString sName = "adj" + ( ( elem.first > 0 ) ? OString::number(elem.first) : OString() );
3683  OString sFmla = "val " + OString::number( elem.second );
3684 
3685  mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla);
3686  }
3687  mpFS->endElementNS( XML_a, XML_avLst );
3688  }
3689  else
3690  mpFS->singleElementNS(XML_a, XML_avLst);
3691 
3692  mpFS->endElementNS( XML_a, XML_prstGeom );
3693 }
3694 
3695 void DrawingML::WritePresetShape( const OString& pShape )
3696 {
3697  mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
3698  mpFS->singleElementNS(XML_a, XML_avLst);
3699  mpFS->endElementNS( XML_a, XML_prstGeom );
3700 }
3701 
3702 static std::map< OString, std::vector<OString> > lcl_getAdjNames()
3703 {
3704  std::map< OString, std::vector<OString> > aRet;
3705 
3706  OUString aPath("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/filter/oox-drawingml-adj-names");
3707  rtl::Bootstrap::expandMacros(aPath);
3708  SvFileStream aStream(aPath, StreamMode::READ);
3709  if (aStream.GetError() != ERRCODE_NONE)
3710  SAL_WARN("oox.shape", "failed to open oox-drawingml-adj-names");
3711  OStringBuffer aLine;
3712  bool bNotDone = aStream.ReadLine(aLine);
3713  while (bNotDone)
3714  {
3715  sal_Int32 nIndex = 0;
3716  // Each line is in a "key\tvalue" format: read the key, the rest is the value.
3717  OString aKey( o3tl::getToken(aLine, 0, '\t', nIndex) );
3718  OString aValue( std::string_view(aLine).substr(nIndex) );
3719  aRet[aKey].push_back(aValue);
3720  bNotDone = aStream.ReadLine(aLine);
3721  }
3722  return aRet;
3723 }
3724 
3725 void DrawingML::WritePresetShape( const OString& pShape, MSO_SPT eShapeType, bool bPredefinedHandlesUsed, const PropertyValue& rProp )
3726 {
3727  static std::map< OString, std::vector<OString> > aAdjMap = lcl_getAdjNames();
3728  // If there are predefined adj names for this shape type, look them up now.
3729  std::vector<OString> aAdjustments;
3730  if (aAdjMap.find(pShape) != aAdjMap.end())
3731  aAdjustments = aAdjMap[pShape];
3732 
3733  mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
3734  mpFS->startElementNS(XML_a, XML_avLst);
3735 
3736  Sequence< drawing::EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq;
3737  if ( ( rProp.Value >>= aAdjustmentSeq )
3738  && eShapeType != mso_sptActionButtonForwardNext // we have adjustments values for these type of shape, but MSO doesn't like them
3739  && eShapeType != mso_sptActionButtonBackPrevious // so they are now disabled
3740  && pShape != "rect" //some shape types are commented out in pCustomShapeTypeTranslationTable[] & are being defaulted to rect & rect does not have adjustment values/name.
3741  )
3742  {
3743  SAL_INFO("oox.shape", "adj seq len: " << aAdjustmentSeq.getLength());
3744  sal_Int32 nAdjustmentsWhichNeedsToBeConverted = 0;
3745  if ( bPredefinedHandlesUsed )
3746  EscherPropertyContainer::LookForPolarHandles( eShapeType, nAdjustmentsWhichNeedsToBeConverted );
3747 
3748  sal_Int32 nValue, nLength = aAdjustmentSeq.getLength();
3749  // aAdjustments will give info about the number of adj values for a particular geometry. For example for hexagon aAdjustments.size() will be 2 and for circular arrow it will be 5 as per lcl_getAdjNames.
3750  // Sometimes there are more values than needed, so we ignore the excessive ones.
3751  if (aAdjustments.size() <= o3tl::make_unsigned(nLength))
3752  {
3753  for (sal_Int32 i = 0; i < static_cast<sal_Int32>(aAdjustments.size()); i++)
3754  {
3755  if( EscherPropertyContainer::GetAdjustmentValue( aAdjustmentSeq[ i ], i, nAdjustmentsWhichNeedsToBeConverted, nValue ) )
3756  {
3757  // If the document model doesn't have an adjustment name (e.g. shape was created from VML), then take it from the predefined list.
3758  OString aAdjName = aAdjustmentSeq[i].Name.isEmpty()
3759  ? aAdjustments[i]
3760  : aAdjustmentSeq[i].Name.toUtf8();
3761 
3762  mpFS->singleElementNS( XML_a, XML_gd,
3763  XML_name, aAdjName,
3764  XML_fmla, "val " + OString::number(nValue));
3765  }
3766  }
3767  }
3768  }
3769 
3770  mpFS->endElementNS( XML_a, XML_avLst );
3771  mpFS->endElementNS( XML_a, XML_prstGeom );
3772 }
3773 
3774 namespace // helpers for DrawingML::WriteCustomGeometry
3775 {
3776 sal_Int32
3777 FindNextCommandEndSubpath(const sal_Int32 nStart,
3778  const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments)
3779 {
3780  sal_Int32 i = nStart < 0 ? 0 : nStart;
3781  while (i < rSegments.getLength() && rSegments[i].Command != ENDSUBPATH)
3782  i++;
3783  return i;
3784 }
3785 
3786 bool HasCommandInSubPath(const sal_Int16 nCommand, const sal_Int32 nFirst, const sal_Int32 nLast,
3787  const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments)
3788 {
3789  for (sal_Int32 i = nFirst < 0 ? 0 : nFirst; i <= nLast && i < rSegments.getLength(); i++)
3790  {
3791  if (rSegments[i].Command == nCommand)
3792  return true;
3793  }
3794  return false;
3795 }
3796 
3797 // Ellipse is given by radii fwR and fhR and center (fCx|fCy). The ray from center through point RayP
3798 // intersects the ellipse in point S and this point S has angle fAngleDeg in degrees.
3799 void getEllipsePointAndAngleFromRayPoint(double& rfAngleDeg, double& rfSx, double& rfSy,
3800  const double fWR, const double fHR, const double fCx,
3801  const double fCy, const double fRayPx, const double fRayPy)
3802 {
3804  {
3805  rfSx = fCx; // needed for getting new 'current point'
3806  rfSy = fCy;
3807  }
3808  else
3809  {
3810  // center ellipse at origin, stretch in y-direction to circle, flip to Math orientation
3811  // and get angle
3812  double fCircleMathAngle = atan2(-fWR / fHR * (fRayPy - fCy), fRayPx - fCx);
3813  // use angle for intersection point on circle and stretch back to ellipse
3814  double fPointMathEllipse_x = fWR * cos(fCircleMathAngle);
3815  double fPointMathEllipse_y = fHR * sin(fCircleMathAngle);
3816  // get angle of intersection point on ellipse
3817  double fEllipseMathAngle = atan2(fPointMathEllipse_y, fPointMathEllipse_x);
3818  // convert from Math to View orientation and shift ellipse back from origin
3819  rfAngleDeg = -basegfx::rad2deg(fEllipseMathAngle);
3820  rfSx = fPointMathEllipse_x + fCx;
3821  rfSy = -fPointMathEllipse_y + fCy;
3822  }
3823 }
3824 
3825 void getEllipsePointFromViewAngle(double& rfSx, double& rfSy, const double fWR, const double fHR,
3826  const double fCx, const double fCy, const double fViewAngleDeg)
3827 {
3829  {
3830  rfSx = fCx; // needed for getting new 'current point'
3831  rfSy = fCy;
3832  }
3833  else
3834  {
3835  double fX = cos(basegfx::deg2rad(fViewAngleDeg)) / fWR;
3836  double fY = sin(basegfx::deg2rad(fViewAngleDeg)) / fHR;
3837  double fRadius = 1.0 / std::hypot(fX, fY);
3838  rfSx = fCx + fRadius * cos(basegfx::deg2rad(fViewAngleDeg));
3839  rfSy = fCy + fRadius * sin(basegfx::deg2rad(fViewAngleDeg));
3840  }
3841 }
3842 
3843 sal_Int32 GetCustomGeometryPointValue(const css::drawing::EnhancedCustomShapeParameter& rParam,
3844  const EnhancedCustomShape2d& rCustomShape2d,
3845  const bool bReplaceGeoWidth, const bool bReplaceGeoHeight)
3846 {
3847  double fValue = 0.0;
3848  rCustomShape2d.GetParameter(fValue, rParam, bReplaceGeoWidth, bReplaceGeoHeight);
3849  sal_Int32 nValue(std::lround(fValue));
3850 
3851  return nValue;
3852 }
3853 
3854 struct TextAreaRect
3855 {
3856  OString left;
3857  OString top;
3858  OString right;
3859  OString bottom;
3860 };
3861 
3862 struct Guide
3863 {
3864  OString sName;
3865  OString sFormula;
3866 };
3867 
3868 void prepareTextArea(const EnhancedCustomShape2d& rEnhancedCustomShape2d,
3869  std::vector<Guide>& rGuideList, TextAreaRect& rTextAreaRect)
3870 {
3871  tools::Rectangle aTextAreaLO(rEnhancedCustomShape2d.GetTextRect());
3872  tools::Rectangle aLogicRectLO(rEnhancedCustomShape2d.GetLogicRect());
3873  if (aTextAreaLO == aLogicRectLO)
3874  {
3875  rTextAreaRect.left = "l";
3876  rTextAreaRect.top = "t";
3877  rTextAreaRect.right = "r";
3878  rTextAreaRect.bottom = "b";
3879  return;
3880  }
3881  // Flip aTextAreaLO if shape is flipped
3882  if (rEnhancedCustomShape2d.IsFlipHorz())
3883  aTextAreaLO.Move((aLogicRectLO.Center().X() - aTextAreaLO.Center().X()) * 2, 0);
3884  if (rEnhancedCustomShape2d.IsFlipVert())
3885  aTextAreaLO.Move(0, (aLogicRectLO.Center().Y() - aTextAreaLO.Center().Y()) * 2);
3886 
3887  Guide aGuide;
3888  // horizontal
3889  const sal_Int32 nWidth = aLogicRectLO.Right() - aLogicRectLO.Left();
3890  const OString sWidth = OString::number(oox::drawingml::convertHmmToEmu(nWidth));
3891 
3892  // left
3893  aGuide.sName = "textAreaLeft";
3894  sal_Int32 nHelp = aTextAreaLO.Left() - aLogicRectLO.Left();
3895  const OString sLeft = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
3896  aGuide.sFormula = "*/ " + sLeft + " w " + sWidth;
3897  rTextAreaRect.left = aGuide.sName;
3898  rGuideList.push_back(aGuide);
3899 
3900  // right
3901  aGuide.sName = "textAreaRight";
3902  nHelp = aTextAreaLO.Right() - aLogicRectLO.Left();
3903  const OString sRight = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
3904  aGuide.sFormula = "*/ " + sRight + " w " + sWidth;
3905  rTextAreaRect.right = aGuide.sName;
3906  rGuideList.push_back(aGuide);
3907 
3908  // vertical
3909  const sal_Int32 nHeight = aLogicRectLO.Bottom() - aLogicRectLO.Top();
3910  const OString sHeight = OString::number(oox::drawingml::convertHmmToEmu(nHeight));
3911 
3912  // top
3913  aGuide.sName = "textAreaTop";
3914  nHelp = aTextAreaLO.Top() - aLogicRectLO.Top();
3915  const OString sTop = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
3916  aGuide.sFormula = "*/ " + sTop + " h " + sHeight;
3917  rTextAreaRect.top = aGuide.sName;
3918  rGuideList.push_back(aGuide);
3919 
3920  // bottom
3921  aGuide.sName = "textAreaBottom";
3922  nHelp = aTextAreaLO.Bottom() - aLogicRectLO.Top();
3923  const OString sBottom = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
3924  aGuide.sFormula = "*/ " + sBottom + " h " + sHeight;
3925  rTextAreaRect.bottom = aGuide.sName;
3926  rGuideList.push_back(aGuide);
3927 
3928  return;
3929 }
3930 }
3931 
3933  const Reference< XShape >& rXShape,
3934  const SdrObjCustomShape& rSdrObjCustomShape)
3935 {
3936  uno::Reference< beans::XPropertySet > aXPropSet;
3937  uno::Any aAny( rXShape->queryInterface(cppu::UnoType<beans::XPropertySet>::get()));
3938 
3939  if ( ! (aAny >>= aXPropSet) )
3940  return false;
3941 
3942  try
3943  {
3944  aAny = aXPropSet->getPropertyValue( "CustomShapeGeometry" );
3945  if ( !aAny.hasValue() )
3946  return false;
3947  }
3948  catch( const ::uno::Exception& )
3949  {
3950  return false;
3951  }
3952 
3953  auto pGeometrySeq = o3tl::tryAccess<uno::Sequence<beans::PropertyValue>>(aAny);
3954  if (!pGeometrySeq)
3955  return false;
3956 
3957  auto pPathProp = std::find_if(std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq),
3958  [](const PropertyValue& rProp) { return rProp.Name == "Path"; });
3959  if (pPathProp == std::cend(*pGeometrySeq))
3960  return false;
3961 
3962  uno::Sequence<beans::PropertyValue> aPathProp;
3963  pPathProp->Value >>= aPathProp;
3964 
3965  uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs;
3966  uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments;
3967  uno::Sequence<awt::Size> aPathSize;
3968  bool bReplaceGeoWidth = false;
3969  bool bReplaceGeoHeight = false;
3970  for (const beans::PropertyValue& rPathProp : std::as_const(aPathProp))
3971  {
3972  if (rPathProp.Name == "Coordinates")
3973  rPathProp.Value >>= aPairs;
3974  else if (rPathProp.Name == "Segments")
3975  rPathProp.Value >>= aSegments;
3976  else if (rPathProp.Name == "SubViewSize")
3977  rPathProp.Value >>= aPathSize;
3978  else if (rPathProp.Name == "StretchX")
3979  bReplaceGeoWidth = true;
3980  else if (rPathProp.Name == "StretchY")
3981  bReplaceGeoHeight = true;
3982  }
3983 
3984  if ( !aPairs.hasElements() )
3985  return false;
3986 
3987  if ( !aSegments.hasElements() )
3988  {
3989  aSegments = uno::Sequence<drawing::EnhancedCustomShapeSegment>
3990  {
3991  { MOVETO, 1 },
3992  { LINETO,
3993  static_cast<sal_Int16>(std::min( aPairs.getLength() - 1, sal_Int32(32767) )) },
3994  { CLOSESUBPATH, 0 },
3995  { ENDSUBPATH, 0 }
3996  };
3997  };
3998 
3999  int nExpectedPairCount = std::accumulate(std::cbegin(aSegments), std::cend(aSegments), 0,
4000  [](const int nSum, const drawing::EnhancedCustomShapeSegment& rSegment) { return nSum + rSegment.Count; });
4001 
4002  if ( nExpectedPairCount > aPairs.getLength() )
4003  {
4004  SAL_WARN("oox.shape", "Segments need " << nExpectedPairCount << " coordinates, but Coordinates have only " << aPairs.getLength() << " pairs.");
4005  return false;
4006  }
4007 
4008  // A EnhancedCustomShape2d caches the equation results. Therefore we use only one of it for the
4009  // entire method.
4010  const EnhancedCustomShape2d aCustomShape2d(const_cast<SdrObjCustomShape&>(rSdrObjCustomShape));
4011 
4012  TextAreaRect aTextAreaRect;
4013  std::vector<Guide> aGuideList; // for now only for <a:rect>
4014  prepareTextArea(aCustomShape2d, aGuideList, aTextAreaRect);
4015  mpFS->startElementNS(XML_a, XML_custGeom);
4016  mpFS->singleElementNS(XML_a, XML_avLst);
4017  if (aGuideList.empty())
4018  {
4019  mpFS->singleElementNS(XML_a, XML_gdLst);
4020  }
4021  else
4022  {
4023  mpFS->startElementNS(XML_a, XML_gdLst);
4024  for (auto const& elem : aGuideList)
4025  {
4026  mpFS->singleElementNS(XML_a, XML_gd, XML_name, elem.sName, XML_fmla, elem.sFormula);
4027  }
4028  mpFS->endElementNS(XML_a, XML_gdLst);
4029  }
4030  mpFS->singleElementNS(XML_a, XML_ahLst);
4031  mpFS->singleElementNS(XML_a, XML_rect, XML_l, aTextAreaRect.left, XML_t, aTextAreaRect.top,
4032  XML_r, aTextAreaRect.right, XML_b, aTextAreaRect.bottom);
4033  mpFS->startElementNS(XML_a, XML_pathLst);
4034 
4035  // Prepare width and height for <a:path>
4036  bool bUseGlobalViewBox(false);
4037 
4038  // nViewBoxWidth must be integer otherwise ReplaceGeoWidth in aCustomShape2d.GetParameter() is not
4039  // triggered; same for height.
4040  sal_Int32 nViewBoxWidth(0);
4041  sal_Int32 nViewBoxHeight(0);
4042  if (!aPathSize.hasElements())
4043  {
4044  bUseGlobalViewBox = true;
4045  // If draw:viewBox is missing in draw:enhancedGeometry, then import sets
4046  // viewBox="0 0 21600 21600". Missing ViewBox can only occur, if user has manipulated
4047  // current file via macro. Author of macro has to fix it.
4048  auto pProp = std::find_if(
4049  std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq),
4050  [](const beans::PropertyValue& rGeomProp) { return rGeomProp.Name == "ViewBox"; });
4051  if (pProp != std::cend(*pGeometrySeq))
4052  {
4053  css::awt::Rectangle aViewBox;
4054  if (pProp->Value >>= aViewBox)
4055  {
4056  nViewBoxWidth = aViewBox.Width;
4057  nViewBoxHeight = aViewBox.Height;
4058  css::drawing::EnhancedCustomShapeParameter aECSP;
4059  aECSP.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL;
4060  aECSP.Value <<= nViewBoxWidth;
4061  double fRetValue;
4062  aCustomShape2d.GetParameter(fRetValue, aECSP, true, false);
4063  nViewBoxWidth = basegfx::fround(fRetValue);
4064  aECSP.Value <<= nViewBoxHeight;
4065  aCustomShape2d.GetParameter(fRetValue, aECSP, false, true);
4066  nViewBoxHeight = basegfx::fround(fRetValue);
4067  }
4068  }
4069  // Import from oox or documents, which are imported from oox and saved to strict ODF, might
4070  // have no subViewSize but viewBox="0 0 0 0". We need to generate width and height in those
4071  // cases. Even if that is fixed, we need the substitute for old documents.
4072  if ((nViewBoxWidth == 0 && nViewBoxHeight == 0) || pProp == std::cend(*pGeometrySeq))
4073  {
4074  // Generate a substitute based on point coordinates
4075  sal_Int32 nXMin(0);
4076  aPairs[0].First.Value >>= nXMin;
4077  sal_Int32 nXMax = nXMin;
4078  sal_Int32 nYMin(0);
4079  aPairs[0].Second.Value >>= nYMin;
4080  sal_Int32 nYMax = nYMin;
4081 
4082  for (const auto& rPair : std::as_const(aPairs))
4083  {
4084  sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, aCustomShape2d,
4085  bReplaceGeoWidth, false);
4086  sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, aCustomShape2d, false,
4087  bReplaceGeoHeight);
4088  if (nX < nXMin)
4089  nXMin = nX;
4090  if (nY < nYMin)
4091  nYMin = nY;
4092  if (nX > nXMax)
4093  nXMax = nX;
4094  if (nY > nYMax)
4095  nYMax = nY;
4096  }
4097  nViewBoxWidth = std::max(nXMax, nXMax - nXMin);
4098  nViewBoxHeight = std::max(nYMax, nYMax - nYMin);
4099  }
4100  // ToDo: Other values of left,top than 0,0 are not considered yet. Such would require a
4101  // shift of the resulting path coordinates.
4102  }
4103 
4104  // Iterate over subpaths
4105  sal_Int32 nPairIndex = 0; // index over "Coordinates"
4106  sal_Int32 nPathSizeIndex = 0; // index over "SubViewSize"
4107  sal_Int32 nSubpathStartIndex(0); // index over "Segments"
4108  sal_Int32 nSubPathIndex(0); // serial number of current subpath
4109  do
4110  {
4111  bool bOK(true); // catch faulty paths were commands do not correspond to points
4112  // get index of next command ENDSUBPATH; if such doesn't exist use index behind last segment
4113  sal_Int32 nNextNcommandIndex = FindNextCommandEndSubpath(nSubpathStartIndex, aSegments);
4114 
4115  // Prepare attributes for a:path start element
4116  // NOFILL or one of the LIGHTEN commands
4117  std::optional<OString> sFill;
4118  if (HasCommandInSubPath(NOFILL, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
4119  sFill = "none";
4120  else if (HasCommandInSubPath(DARKEN, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
4121  sFill = "darken";
4122  else if (HasCommandInSubPath(DARKENLESS, nSubpathStartIndex, nNextNcommandIndex - 1,
4123  aSegments))
4124  sFill = "darkenLess";
4125  else if (HasCommandInSubPath(LIGHTEN, nSubpathStartIndex, nNextNcommandIndex - 1,
4126  aSegments))
4127  sFill = "lighten";
4128  else if (HasCommandInSubPath(LIGHTENLESS, nSubpathStartIndex, nNextNcommandIndex - 1,
4129  aSegments))
4130  sFill = "lightenLess";
4131  else
4132  {
4133  // shading info might be in object type, e.g. "Octagon Bevel".
4134  sal_Int32 nLuminanceChange(aCustomShape2d.GetLuminanceChange(nSubPathIndex));
4135  if (nLuminanceChange <= -40)
4136  sFill = "darken";
4137  else if (nLuminanceChange <= -10)
4138  sFill = "darkenLess";
4139  else if (nLuminanceChange >= 40)
4140  sFill = "lighten";
4141  else if (nLuminanceChange >= 10)
4142  sFill = "lightenLess";
4143  }
4144  // NOSTROKE
4145  std::optional<OString> sStroke;
4146  if (HasCommandInSubPath(NOSTROKE, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
4147  sStroke = "0";
4148 
4149  // Write a:path start element
4150  mpFS->startElementNS(
4151  XML_a, XML_path, XML_fill, sFill, XML_stroke, sStroke, XML_w,
4152  OString::number(bUseGlobalViewBox ? nViewBoxWidth : aPathSize[nPathSizeIndex].Width),
4153  XML_h,
4154  OString::number(bUseGlobalViewBox ? nViewBoxHeight : aPathSize[nPathSizeIndex].Height));
4155 
4156  // Arcs drawn by commands ELLIPTICALQUADRANTX and ELLIPTICALQUADRANTY depend on the position
4157  // of the target point in regard to the current point. Therefore we need to track the
4158  // current point. A current point is not defined in the beginning.
4159  double fCurrentX(0.0);
4160  double fCurrentY(0.0);
4161  bool bCurrentValid(false);
4162  // Actually write the subpath
4163  for (sal_Int32 nSegmentIndex = nSubpathStartIndex; nSegmentIndex < nNextNcommandIndex;
4164  ++nSegmentIndex)
4165  {
4166  const auto& rSegment(aSegments[nSegmentIndex]);
4167  if (rSegment.Command == CLOSESUBPATH)
4168  {
4169  mpFS->singleElementNS(XML_a, XML_close); // command Z has no parameter
4170  // ODF 1.4 specifies, that the start of the subpath becomes the current point.
4171  // But that is not implemented yet. Currently LO keeps the last current point.
4172  }
4173  for (sal_Int32 k = 0; k < rSegment.Count && bOK; ++k)
4174  {
4175  bOK = WriteCustomGeometrySegment(rSegment.Command, k, aPairs, nPairIndex, fCurrentX,
4176  fCurrentY, bCurrentValid, aCustomShape2d,
4177  bReplaceGeoWidth, bReplaceGeoHeight);
4178  }
4179  } // end loop over all commands of subpath
4180  // finish this subpath in any case
4181  mpFS->endElementNS(XML_a, XML_path);
4182 
4183  if (!bOK)
4184  break; // exit loop if not enough values in aPairs
4185 
4186  // step forward to next subpath
4187  nSubpathStartIndex = nNextNcommandIndex + 1;
4188  nPathSizeIndex++;
4189  nSubPathIndex++;
4190  } while (nSubpathStartIndex < aSegments.getLength());
4191 
4192  mpFS->endElementNS(XML_a, XML_pathLst);
4193  mpFS->endElementNS(XML_a, XML_custGeom);
4194  return true; // We have written custGeom even if path is poorly structured.
4195 }
4196 
4198  const sal_Int16 eCommand, const sal_Int32 nCount,
4199  const uno::Sequence<css::drawing::EnhancedCustomShapeParameterPair>& rPairs,
4200  sal_Int32& rnPairIndex, double& rfCurrentX, double& rfCurrentY, bool& rbCurrentValid,
4201  const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth,
4202  const bool bReplaceGeoHeight)
4203 {
4204  switch (eCommand)
4205  {
4206  case MOVETO:
4207  {
4208  if (rnPairIndex >= rPairs.getLength())
4209  return false;
4210 
4211  mpFS->startElementNS(XML_a, XML_moveTo);
4212  WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
4213  bReplaceGeoHeight);
4214  mpFS->endElementNS(XML_a, XML_moveTo);
4215  rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth,
4216  false);
4217  rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false,
4218  bReplaceGeoHeight);
4219  rbCurrentValid = true;
4220  rnPairIndex++;
4221  break;
4222  }
4223  case LINETO:
4224  {
4225  if (rnPairIndex >= rPairs.getLength())
4226  return false;
4227  // LINETO without valid current point is a faulty path. LO is tolerant and makes a
4228  // moveTo instead. Do the same on export. MS OFFICE requires a current point for lnTo,
4229  // otherwise it shows nothing of the shape.
4230  if (rbCurrentValid)
4231  {
4232  mpFS->startElementNS(XML_a, XML_lnTo);
4233  WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
4234  bReplaceGeoHeight);
4235  mpFS->endElementNS(XML_a, XML_lnTo);
4236  }
4237  else
4238  {
4239  mpFS->startElementNS(XML_a, XML_moveTo);
4240  WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
4241  bReplaceGeoHeight);
4242  mpFS->endElementNS(XML_a, XML_moveTo);
4243  }
4244  rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth,
4245  false);
4246  rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false,
4247  bReplaceGeoHeight);
4248  rbCurrentValid = true;
4249  rnPairIndex++;
4250  break;
4251  }
4252  case CURVETO:
4253  {
4254  if (rnPairIndex + 2 >= rPairs.getLength())
4255  return false;
4256 
4257  mpFS->startElementNS(XML_a, XML_cubicBezTo);
4258  for (sal_uInt8 i = 0; i <= 2; ++i)
4259  {
4260  WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth,
4261  bReplaceGeoHeight);
4262  }
4263  mpFS->endElementNS(XML_a, XML_cubicBezTo);
4264  rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth,
4265  false);
4266  rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 2].Second, false,
4267  bReplaceGeoHeight);
4268  rbCurrentValid = true;
4269  rnPairIndex += 3;
4270  break;
4271  }
4272  case ANGLEELLIPSETO:
4273  case ANGLEELLIPSE:
4274  {
4275  if (rnPairIndex + 2 >= rPairs.getLength())
4276  return false;
4277 
4278  // Read parameters
4279  double fCx = 0.0;
4280  rCustomShape2d.GetParameter(fCx, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
4281  double fCy = 0.0;
4282  rCustomShape2d.GetParameter(fCy, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
4283  double fWR = 0.0;
4284  rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex + 1].First, false, false);
4285  double fHR = 0.0;
4286  rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex + 1].Second, false, false);
4287  double fStartAngle = 0.0;
4288  rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 2].First, false, false);
4289  double fEndAngle = 0.0;
4290  rCustomShape2d.GetParameter(fEndAngle, rPairs[rnPairIndex + 2].Second, false, false);
4291 
4292  // Prepare start and swing angle
4293  sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
4294  sal_Int32 nSwingAng = 0;
4295  if (basegfx::fTools::equalZero(fStartAngle)
4296  && basegfx::fTools::equalZero(fEndAngle - 360.0))
4297  nSwingAng = 360 * 60000; // special case full circle
4298  else
4299  {
4300  nSwingAng = std::lround((fEndAngle - fStartAngle) * 60000);
4301  if (nSwingAng < 0)
4302  nSwingAng += 360 * 60000;
4303  }
4304 
4305  // calculate start point on ellipse
4306  double fSx = 0.0;
4307  double fSy = 0.0;
4308  getEllipsePointFromViewAngle(fSx, fSy, fWR, fHR, fCx, fCy, fStartAngle);
4309 
4310  // write markup for going to start point
4311  // lnTo requires a valid current point
4312  if (eCommand == ANGLEELLIPSETO && rbCurrentValid)
4313  {
4314  mpFS->startElementNS(XML_a, XML_lnTo);
4315  mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)),
4316  XML_y, OString::number(std::lround(fSy)));
4317  mpFS->endElementNS(XML_a, XML_lnTo);
4318  }
4319  else
4320  {
4321  mpFS->startElementNS(XML_a, XML_moveTo);
4322  mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)),
4323  XML_y, OString::number(std::lround(fSy)));
4324  mpFS->endElementNS(XML_a, XML_moveTo);
4325  }
4326  // write markup for arcTo
4328  mpFS->singleElement(
4329  FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR,
4330  OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng),
4331  XML_swAng, OString::number(nSwingAng));
4332 
4333  getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy, fEndAngle);
4334  rbCurrentValid = true;
4335  rnPairIndex += 3;
4336  break;
4337  }
4338  case ARCTO:
4339  case ARC:
4340  case CLOCKWISEARCTO:
4341  case CLOCKWISEARC:
4342  {
4343  if (rnPairIndex + 3 >= rPairs.getLength())
4344  return false;
4345 
4346  // read parameters
4347  double fX1 = 0.0;
4348  rCustomShape2d.GetParameter(fX1, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
4349  double fY1 = 0.0;
4350  rCustomShape2d.GetParameter(fY1, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
4351  double fX2 = 0.0;
4352  rCustomShape2d.GetParameter(fX2, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth,
4353  false);
4354  double fY2 = 0.0;
4355  rCustomShape2d.GetParameter(fY2, rPairs[rnPairIndex + 1].Second, false,
4356  bReplaceGeoHeight);
4357  double fX3 = 0.0;
4358  rCustomShape2d.GetParameter(fX3, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth,
4359  false);
4360  double fY3 = 0.0;
4361  rCustomShape2d.GetParameter(fY3, rPairs[rnPairIndex + 2].Second, false,
4362  bReplaceGeoHeight);
4363  double fX4 = 0.0;
4364  rCustomShape2d.GetParameter(fX4, rPairs[rnPairIndex + 3].First, bReplaceGeoWidth,
4365  false);
4366  double fY4 = 0.0;
4367  rCustomShape2d.GetParameter(fY4, rPairs[rnPairIndex + 3].Second, false,
4368  bReplaceGeoHeight);
4369  // calculate ellipse parameter
4370  const double fWR = (fX2 - fX1) / 2.0;
4371  const double fHR = (fY2 - fY1) / 2.0;
4372  const double fCx = (fX1 + fX2) / 2.0;
4373  const double fCy = (fY1 + fY2) / 2.0;
4374  // calculate start angle
4375  double fStartAngle = 0.0;
4376  double fPx = 0.0;
4377  double fPy = 0.0;
4378  getEllipsePointAndAngleFromRayPoint(fStartAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX3,
4379  fY3);
4380  // markup for going to start point
4381  // lnTo requires a valid current point.
4382  if ((eCommand == ARCTO || eCommand == CLOCKWISEARCTO) && rbCurrentValid)
4383  {
4384  mpFS->startElementNS(XML_a, XML_lnTo);
4385  mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)),
4386  XML_y, OString::number(std::lround(fPy)));
4387  mpFS->endElementNS(XML_a, XML_lnTo);
4388  }
4389  else
4390  {
4391  mpFS->startElementNS(XML_a, XML_moveTo);
4392  mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)),
4393  XML_y, OString::number(std::lround(fPy)));
4394  mpFS->endElementNS(XML_a, XML_moveTo);
4395  }
4396  // calculate swing angle
4397  double fEndAngle = 0.0;
4398  getEllipsePointAndAngleFromRayPoint(fEndAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX4, fY4);
4399  double fSwingAngle(fEndAngle - fStartAngle);
4400  const bool bIsClockwise(eCommand == CLOCKWISEARCTO || eCommand == CLOCKWISEARC);
4401  if (bIsClockwise && fSwingAngle < 0)
4402  fSwingAngle += 360.0;
4403  else if (!bIsClockwise && fSwingAngle > 0)
4404  fSwingAngle -= 360.0;
4405  // markup for arcTo
4406  // ToDo: write markup for case zero width or height of ellipse
4407  const sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
4408  const sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000));
4409  mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)),
4410  XML_hR, OString::number(std::lround(fHR)), XML_stAng,
4411  OString::number(nStartAng), XML_swAng, OString::number(nSwingAng));
4412  rfCurrentX = fPx;
4413  rfCurrentY = fPy;
4414  rbCurrentValid = true;
4415  rnPairIndex += 4;
4416  break;
4417  }
4418  case ELLIPTICALQUADRANTX:
4419  case ELLIPTICALQUADRANTY:
4420  {
4421  if (rnPairIndex >= rPairs.getLength())
4422  return false;
4423 
4424  // read parameters
4425  double fX = 0.0;
4426  rCustomShape2d.GetParameter(fX, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
4427  double fY = 0.0;
4428  rCustomShape2d.GetParameter(fY, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
4429 
4430  // Prepare parameters for arcTo
4431  if (rbCurrentValid)
4432  {
4433  double fWR = std::abs(rfCurrentX - fX);
4434  double fHR = std::abs(rfCurrentY - fY);
4435  double fStartAngle(0.0);
4436  double fSwingAngle(0.0);
4437  // The starting direction of the arc toggles between X and Y
4438  if ((eCommand == ELLIPTICALQUADRANTX && !(nCount % 2))
4439  || (eCommand == ELLIPTICALQUADRANTY && (nCount % 2)))
4440  {
4441  // arc starts horizontal
4442  fStartAngle = fY < rfCurrentY ? 90.0 : 270.0;
4443  const bool bClockwise = (fX < rfCurrentX && fY < rfCurrentY)
4444  || (fX > rfCurrentX && fY > rfCurrentY);
4445  fSwingAngle = bClockwise ? 90.0 : -90.0;
4446  }
4447  else
4448  {
4449  // arc starts vertical
4450  fStartAngle = fX < rfCurrentX ? 0.0 : 180.0;
4451  const bool bClockwise = (fX < rfCurrentX && fY > rfCurrentY)
4452  || (fX > rfCurrentX && fY < rfCurrentY);
4453  fSwingAngle = bClockwise ? 90.0 : -90.0;
4454  }
4455  sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
4456  sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000));
4457  mpFS->singleElement(
4458  FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR,
4459  OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng),
4460  XML_swAng, OString::number(nSwingAng));
4461  }
4462  else
4463  {
4464  // faulty path, but we continue with the target point
4465  mpFS->startElementNS(XML_a, XML_moveTo);
4466  WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
4467  bReplaceGeoHeight);
4468  mpFS->endElementNS(XML_a, XML_moveTo);
4469  }
4470  rfCurrentX = fX;
4471  rfCurrentY = fY;
4472  rbCurrentValid = true;
4473  rnPairIndex++;
4474  break;
4475  }
4476  case QUADRATICCURVETO:
4477  {
4478  if (rnPairIndex + 1 >= rPairs.getLength())
4479  return false;
4480 
4481  mpFS->startElementNS(XML_a, XML_quadBezTo);
4482  for (sal_uInt8 i = 0; i < 2; ++i)
4483  {
4484  WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth,
4485  bReplaceGeoHeight);
4486  }
4487  mpFS->endElementNS(XML_a, XML_quadBezTo);
4488  rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth,
4489  false);
4490  rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 1].Second, false,
4491  bReplaceGeoHeight);
4492  rbCurrentValid = true;
4493  rnPairIndex += 2;
4494  break;
4495  }
4496  case ARCANGLETO:
4497  {
4498  if (rnPairIndex + 1 >= rPairs.getLength())
4499  return false;
4500 
4501  double fWR = 0.0;
4502  rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex].First, false, false);
4503  double fHR = 0.0;
4504  rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex].Second, false, false);
4505  double fStartAngle = 0.0;
4506  rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 1].First, false, false);
4507  sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
4508  double fSwingAng = 0.0;
4509  rCustomShape2d.GetParameter(fSwingAng, rPairs[rnPairIndex + 1].Second, false, false);
4510  sal_Int32 nSwingAng(std::lround(fSwingAng * 60000));
4511  mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(fWR), XML_hR,
4512  OString::number(fHR), XML_stAng, OString::number(nStartAng),
4513  XML_swAng, OString::number(nSwingAng));
4514  double fPx = 0.0;
4515  double fPy = 0.0;
4516  getEllipsePointFromViewAngle(fPx, fPy, fWR, fHR, 0.0, 0.0, fStartAngle);
4517  double fCx = rfCurrentX - fPx;
4518  double fCy = rfCurrentY - fPy;
4519  getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy,
4520  fStartAngle + fSwingAng);
4521  rbCurrentValid = true;
4522  rnPairIndex += 2;
4523  break;
4524  }
4525  default:
4526  // do nothing
4527  break;
4528  }
4529  return true;
4530 }
4531 
4533  const drawing::EnhancedCustomShapeParameterPair& rParamPair,
4534  const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth,
4535  const bool bReplaceGeoHeight)
4536 {
4537  sal_Int32 nX
4538  = GetCustomGeometryPointValue(rParamPair.First, rCustomShape2d, bReplaceGeoWidth, false);
4539  sal_Int32 nY
4540  = GetCustomGeometryPointValue(rParamPair.Second, rCustomShape2d, false, bReplaceGeoHeight);
4541 
4542  mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(nX), XML_y, OString::number(nY));
4543 }
4544 
4546 {
4547  // This method is used for export to docx in case WriteCustomGeometry fails.
4548  mpFS->startElementNS(XML_a, XML_custGeom);
4549  mpFS->singleElementNS(XML_a, XML_avLst);
4550  mpFS->singleElementNS(XML_a, XML_gdLst);
4551  mpFS->singleElementNS(XML_a, XML_ahLst);
4552  mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b");
4553  mpFS->singleElementNS(XML_a, XML_pathLst);
4554  mpFS->endElementNS(XML_a, XML_custGeom);
4555 }
4556 
4557 // version for SdrPathObj
4558 void DrawingML::WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape,
4559  const bool bClosed)
4560 {
4562  // In case of Writer, the parent element is <wps:spPr>, and there the
4563  // <a:custGeom> element is not optional.
4564  if (aPolyPolygon.Count() < 1 && GetDocumentType() != DOCUMENT_DOCX)
4565  return;
4566 
4567  mpFS->startElementNS(XML_a, XML_custGeom);
4568  mpFS->singleElementNS(XML_a, XML_avLst);
4569  mpFS->singleElementNS(XML_a, XML_gdLst);
4570  mpFS->singleElementNS(XML_a, XML_ahLst);
4571  mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b");
4572 
4573  mpFS->startElementNS(XML_a, XML_pathLst);
4574 
4575  awt::Size aSize = rXShape->getSize();
4576  awt::Point aPos = rXShape->getPosition();
4577  Reference<XPropertySet> xPropertySet(rXShape, UNO_QUERY);
4578  uno::Reference<XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
4579  if (xPropertySetInfo->hasPropertyByName("AnchorPosition"))
4580  {
4581  awt::Point aAnchorPosition;
4582  xPropertySet->getPropertyValue("AnchorPosition") >>= aAnchorPosition;
4583  aPos.X += aAnchorPosition.X;
4584  aPos.Y += aAnchorPosition.Y;
4585  }
4586 
4587  // Only closed SdrPathObj can be filled
4588  std::optional<OString> sFill;
4589  if (!bClosed)
4590  sFill = "none"; // for possible values see ST_PathFillMode in OOXML standard
4591 
4592  // Put all polygons of rPolyPolygon in the same path element
4593  // to subtract the overlapped areas.
4594  mpFS->startElementNS(XML_a, XML_path, XML_fill, sFill, XML_w, OString::number(aSize.Width),
4595  XML_h, OString::number(aSize.Height));
4596 
4597  for (sal_uInt16 i = 0; i < aPolyPolygon.Count(); i++)
4598  {
4599  const tools::Polygon& aPoly = aPolyPolygon[i];
4600 
4601  if (aPoly.GetSize() > 0)
4602  {
4603  mpFS->startElementNS(XML_a, XML_moveTo);
4604 
4605  mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[0].X() - aPos.X),
4606  XML_y, OString::number(aPoly[0].Y() - aPos.Y));
4607 
4608  mpFS->endElementNS(XML_a, XML_moveTo);
4609  }
4610 
4611  for (sal_uInt16 j = 1; j < aPoly.GetSize(); j++)
4612  {
4613  PolyFlags flags = aPoly.GetFlags(j);
4614  if (flags == PolyFlags::Control)
4615  {
4616  // a:cubicBezTo can only contain 3 a:pt elements, so we need to make sure of this
4617  if (j + 2 < aPoly.GetSize() && aPoly.GetFlags(j + 1) == PolyFlags::Control
4618  && aPoly.GetFlags(j + 2) != PolyFlags::Control)
4619  {
4620  mpFS->startElementNS(XML_a, XML_cubicBezTo);
4621  for (sal_uInt8 k = 0; k <= 2; ++k)
4622  {
4623  mpFS->singleElementNS(XML_a, XML_pt, XML_x,
4624  OString::number(aPoly[j + k].X() - aPos.X), XML_y,
4625  OString::number(aPoly[j + k].Y() - aPos.Y));
4626  }
4627  mpFS->endElementNS(XML_a, XML_cubicBezTo);
4628  j += 2;
4629  }
4630  }
4631  else if (flags == PolyFlags::Normal)
4632  {
4633  mpFS->startElementNS(XML_a, XML_lnTo);
4634  mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[j].X() - aPos.X),
4635  XML_y, OString::number(aPoly[j].Y() - aPos.Y));
4636  mpFS->endElementNS(XML_a, XML_lnTo);
4637  }
4638  }
4639  }
4640  if (bClosed)
4641  mpFS->singleElementNS(XML_a, XML_close);
4642  mpFS->endElementNS(XML_a, XML_path);
4643 
4644  mpFS->endElementNS(XML_a, XML_pathLst);
4645 
4646  mpFS->endElementNS(XML_a, XML_custGeom);
4647 }
4648 
4649 void DrawingML::WriteConnectorConnections( EscherConnectorListEntry& rConnectorEntry, sal_Int32 nStartID, sal_Int32 nEndID )
4650 {
4651  if( nStartID != -1 )
4652  {
4653  mpFS->singleElementNS( XML_a, XML_stCxn,
4654  XML_id, OString::number(nStartID),
4655  XML_idx, OString::number(rConnectorEntry.GetConnectorRule(true)) );
4656  }
4657  if( nEndID != -1 )
4658  {
4659  mpFS->singleElementNS( XML_a, XML_endCxn,
4660  XML_id, OString::number(nEndID),
4661  XML_idx, OString::number(rConnectorEntry.GetConnectorRule(false)) );
4662  }
4663 }
4664 
4665 sal_Unicode DrawingML::SubstituteBullet( sal_Unicode cBulletId, css::awt::FontDescriptor& rFontDesc )
4666 {
4667  if ( IsStarSymbol(rFontDesc.Name) )
4668  {
4669  rtl_TextEncoding eCharSet = rFontDesc.CharSet;
4670  cBulletId = msfilter::util::bestFitOpenSymbolToMSFont(cBulletId, eCharSet, rFontDesc.Name);
4671  rFontDesc.CharSet = eCharSet;
4672  }
4673 
4674  return cBulletId;
4675 }
4676 
4678  const OUString& sFullStream,
4679  std::u16string_view sRelativeStream,
4680  const Reference< XOutputStream >& xParentRelation,
4681  const char* sContentType,
4682  const char* sRelationshipType,
4683  OUString* pRelationshipId )
4684 {
4685  OUString sRelationshipId;
4686  if (xParentRelation.is())
4687  sRelationshipId = GetFB()->addRelation( xParentRelation, OUString::createFromAscii( sRelationshipType), sRelativeStream );
4688  else
4689  sRelationshipId = GetFB()->addRelation( OUString::createFromAscii( sRelationshipType ), sRelativeStream );
4690 
4691  if( pRelationshipId )
4692  *pRelationshipId = sRelationshipId;
4693 
4694  sax_fastparser::FSHelperPtr p = GetFB()->openFragmentStreamWithSerializer( sFullStream, OUString::createFromAscii( sContentType ) );
4695 
4696  return p;
4697 }
4698 
4700 {
4701  if ( !GetProperty( xPropSet, "FillStyle" ) )
4702  return;
4703  FillStyle aFillStyle( FillStyle_NONE );
4704  xPropSet->getPropertyValue( "FillStyle" ) >>= aFillStyle;
4705 
4706  // map full transparent background to no fill
4707  if ( aFillStyle == FillStyle_SOLID && GetProperty( xPropSet, "FillTransparence" ) )
4708  {
4709  sal_Int16 nVal = 0;
4710  xPropSet->getPropertyValue( "FillTransparence" ) >>= nVal;
4711  if ( nVal == 100 )
4712  aFillStyle = FillStyle_NONE;
4713  }
4714  if (aFillStyle == FillStyle_SOLID && GetProperty( xPropSet, "FillTransparenceGradient"))
4715  {
4716  awt::Gradient aTransparenceGradient;
4717  mAny >>= aTransparenceGradient;
4718  if (aTransparenceGradient.StartColor == 0xffffff && aTransparenceGradient.EndColor == 0xffffff)
4719  aFillStyle = FillStyle_NONE;
4720  }
4721 
4722  switch( aFillStyle )
4723  {
4724  case FillStyle_SOLID :
4725  WriteSolidFill( xPropSet );
4726  break;
4727  case FillStyle_GRADIENT :
4728  WriteGradientFill( xPropSet );
4729  break;
4730  case FillStyle_BITMAP :
4731  WriteBlipFill( xPropSet, "FillBitmap" );
4732  break;
4733  case FillStyle_HATCH :
4734  WritePattFill( xPropSet );
4735  break;
4736  case FillStyle_NONE:
4737  mpFS->singleElementNS(XML_a, XML_noFill);
4738  break;
4739  default:
4740  ;
4741  }
4742 }
4743 
4744 void DrawingML::WriteStyleProperties( sal_Int32 nTokenId, const Sequence< PropertyValue >& aProperties )
4745 {
4746  if( aProperties.hasElements() )
4747  {
4748  OUString sSchemeClr;
4749  sal_uInt32 nIdx = 0;
4750  Sequence< PropertyValue > aTransformations;
4751  for( const auto& rProp : aProperties)
4752  {
4753  if( rProp.Name == "SchemeClr" )
4754  rProp.Value >>= sSchemeClr;
4755  else if( rProp.Name == "Idx" )
4756  rProp.Value >>= nIdx;
4757  else if( rProp.Name == "Transformations" )
4758  rProp.Value >>= aTransformations;
4759  }
4760  mpFS->startElementNS(XML_a, nTokenId, XML_idx, OString::number(nIdx));
4761  WriteColor(sSchemeClr, aTransformations);
4762  mpFS->endElementNS( XML_a, nTokenId );
4763  }
4764  else
4765  {
4766  // write mock <a:*Ref> tag
4767  mpFS->singleElementNS(XML_a, nTokenId, XML_idx, OString::number(0));
4768  }
4769 }
4770 
4772 {
4773  // check existence of the grab bag
4774  if ( !GetProperty( xPropSet, "InteropGrabBag" ) )
4775  return;
4776 
4777  // extract the relevant properties from the grab bag
4778  Sequence< PropertyValue > aGrabBag;
4779  Sequence< PropertyValue > aFillRefProperties, aLnRefProperties, aEffectRefProperties;
4780  mAny >>= aGrabBag;
4781  for( const auto& rProp : std::as_const(aGrabBag))
4782  {
4783  if( rProp.Name == "StyleFillRef" )
4784  rProp.Value >>= aFillRefProperties;
4785  else if( rProp.Name == "StyleLnRef" )
4786  rProp.Value >>= aLnRefProperties;
4787  else if( rProp.Name == "StyleEffectRef" )
4788  rProp.Value >>= aEffectRefProperties;
4789  }
4790 
4791  WriteStyleProperties( XML_lnRef, aLnRefProperties );
4792  WriteStyleProperties( XML_fillRef, aFillRefProperties );
4793  WriteStyleProperties( XML_effectRef, aEffectRefProperties );
4794 
4795  // write mock <a:fontRef>
4796  mpFS->singleElementNS(XML_a, XML_fontRef, XML_idx, "minor");
4797 }
4798 
4799 void DrawingML::WriteShapeEffect( std::u16string_view sName, const Sequence< PropertyValue >& aEffectProps )
4800 {
4801  if( !aEffectProps.hasElements() )
4802  return;
4803 
4804  // assign the proper tag and enable bContainsColor if necessary
4805  sal_Int32 nEffectToken = 0;
4806  bool bContainsColor = false;
4807  if( sName == u"outerShdw" )
4808  {
4809  nEffectToken = FSNS( XML_a, XML_outerShdw );
4810  bContainsColor = true;
4811  }
4812  else if( sName == u"innerShdw" )
4813  {
4814  nEffectToken = FSNS( XML_a, XML_innerShdw );
4815  bContainsColor = true;
4816  }
4817  else if( sName == u"glow" )
4818  {
4819  nEffectToken = FSNS( XML_a, XML_glow );
4820  bContainsColor = true;
4821  }
4822  else if( sName == u"softEdge" )
4823  nEffectToken = FSNS( XML_a, XML_softEdge );
4824  else if( sName == u"reflection" )
4825  nEffectToken = FSNS( XML_a, XML_reflection );
4826  else if( sName == u"blur" )
4827  nEffectToken = FSNS( XML_a, XML_blur );
4828 
4829  OUString sSchemeClr;
4830  ::Color nRgbClr;
4831  sal_Int32 nAlpha = MAX_PERCENT;
4832  Sequence< PropertyValue > aTransformations;
4833  rtl::Reference<sax_fastparser::FastAttributeList> aOuterShdwAttrList = FastSerializerHelper::createAttrList();
4834  for( const auto& rEffectProp : aEffectProps )
4835  {
4836  if( rEffectProp.Name == "Attribs" )
4837  {
4838  // read tag attributes
4839  uno::Sequence< beans::PropertyValue > aOuterShdwProps;
4840  rEffectProp.Value >>= aOuterShdwProps;
4841  for( const auto& rOuterShdwProp : std::as_const(aOuterShdwProps) )
4842  {
4843  if( rOuterShdwProp.Name == "algn" )
4844  {
4845  OUString sVal;
4846  rOuterShdwProp.Value >>= sVal;
4847  aOuterShdwAttrList->add( XML_algn, sVal );
4848  }
4849  else if( rOuterShdwProp.Name == "blurRad" )
4850  {
4851  sal_Int64 nVal = 0;
4852  rOuterShdwProp.Value >>= nVal;
4853  aOuterShdwAttrList->add( XML_blurRad, OString::number( nVal ).getStr() );
4854  }
4855  else if( rOuterShdwProp.Name == "dir" )
4856  {
4857  sal_Int32 nVal = 0;
4858  rOuterShdwProp.Value >>= nVal;
4859  aOuterShdwAttrList->add( XML_dir, OString::number( nVal ).getStr() );
4860  }
4861  else if( rOuterShdwProp.Name == "dist" )
4862  {
4863  sal_Int32 nVal = 0;
4864  rOuterShdwProp.Value >>= nVal;
4865  aOuterShdwAttrList->add( XML_dist, OString::number( nVal ).getStr() );
4866  }
4867  else if( rOuterShdwProp.Name == "kx" )
4868  {
4869  sal_Int32 nVal = 0;
4870  rOuterShdwProp.Value >>= nVal;
4871  aOuterShdwAttrList->add( XML_kx, OString::number( nVal ).getStr() );
4872  }
4873  else if( rOuterShdwProp.Name == "ky" )
4874  {
4875  sal_Int32 nVal = 0;
4876  rOuterShdwProp.Value >>= nVal;
4877  aOuterShdwAttrList->add( XML_ky, OString::number( nVal ).getStr() );
4878  }
4879  else if( rOuterShdwProp.Name == "rotWithShape" )
4880  {
4881  sal_Int32 nVal = 0;
4882  rOuterShdwProp.Value >>= nVal;
4883  aOuterShdwAttrList->add( XML_rotWithShape, OString::number( nVal ).getStr() );
4884  }
4885  else if( rOuterShdwProp.Name == "sx" )
4886  {
4887  sal_Int32 nVal = 0;
4888  rOuterShdwProp.Value >>= nVal;
4889  aOuterShdwAttrList->add( XML_sx, OString::number( nVal ).getStr() );
4890  }
4891  else if( rOuterShdwProp.Name == "sy" )
4892  {
4893  sal_Int32 nVal = 0;
4894  rOuterShdwProp.Value >>= nVal;
4895  aOuterShdwAttrList->add( XML_sy, OString::number( nVal ).getStr() );
4896  }
4897  else if( rOuterShdwProp.Name == "rad" )
4898  {
4899  sal_Int64 nVal = 0;
4900  rOuterShdwProp.Value >>= nVal;
4901  aOuterShdwAttrList->add( XML_rad, OString::number( nVal ).getStr() );
4902  }
4903  else if( rOuterShdwProp.Name == "endA" )
4904  {
4905  sal_Int32 nVal = 0;
4906  rOuterShdwProp.Value >>= nVal;
4907  aOuterShdwAttrList->add( XML_endA, OString::number( nVal ).getStr() );
4908  }
4909  else if( rOuterShdwProp.Name == "endPos" )
4910  {
4911  sal_Int32 nVal = 0;
4912  rOuterShdwProp.Value >>= nVal;
4913  aOuterShdwAttrList->add( XML_endPos, OString::number( nVal ).getStr() );
4914  }
4915  else if( rOuterShdwProp.Name == "fadeDir" )
4916  {
4917  sal_Int32 nVal = 0;
4918  rOuterShdwProp.Value >>= nVal;
4919  aOuterShdwAttrList->add( XML_fadeDir, OString::number( nVal ).getStr() );
4920  }
4921  else if( rOuterShdwProp.Name == "stA" )
4922  {
4923  sal_Int32 nVal = 0;
4924  rOuterShdwProp.Value >>= nVal;
4925  aOuterShdwAttrList->add( XML_stA, OString::number( nVal ).getStr() );
4926  }
4927  else if( rOuterShdwProp.Name == "stPos" )
4928  {
4929  sal_Int32 nVal = 0;
4930  rOuterShdwProp.Value >>= nVal;
4931  aOuterShdwAttrList->add( XML_stPos, OString::number( nVal ).getStr() );
4932  }
4933  else if( rOuterShdwProp.Name == "grow" )
4934  {
4935  sal_Int32 nVal = 0;
4936  rOuterShdwProp.Value >>= nVal;
4937  aOuterShdwAttrList->add( XML_grow, OString::number( nVal ).getStr() );
4938  }
4939  }
4940  }
4941  else if(rEffectProp.Name == "RgbClr")
4942  {
4943  rEffectProp.Value >>= nRgbClr;
4944  }
4945  else if(rEffectProp.Name == "RgbClrTransparency")
4946  {
4947  sal_Int32 nTransparency;
4948  if (rEffectProp.Value >>= nTransparency)
4949  // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
4950  nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency );
4951  }
4952  else if(rEffectProp.Name == "SchemeClr")
4953  {
4954  rEffectProp.Value >>= sSchemeClr;
4955  }
4956  else if(rEffectProp.Name == "SchemeClrTransformations")
4957  {
4958  rEffectProp.Value >>= aTransformations;
4959  }
4960  }
4961 
4962  if( nEffectToken <= 0 )
4963  return;
4964 
4965  mpFS->startElement( nEffectToken, aOuterShdwAttrList );
4966 
4967  if( bContainsColor )
4968  {
4969  if( sSchemeClr.isEmpty() )
4970  WriteColor( nRgbClr, nAlpha );
4971  else
4972  WriteColor( sSchemeClr, aTransformations );
4973  }
4974 
4975  mpFS->endElement( nEffectToken );
4976 }
4977 
4978 static sal_Int32 lcl_CalculateDist(const double dX, const double dY)
4979 {
4980  return static_cast< sal_Int32 >(sqrt(dX*dX + dY*dY) * 360);
4981 }
4982 
4983 static sal_Int32 lcl_CalculateDir(const double dX, const double dY)
4984 {
4985  return (static_cast< sal_Int32 >(basegfx::rad2deg<60000>(atan2(dY,dX))) + 21600000) % 21600000;
4986 }
4987 
4989 {
4990  Sequence< PropertyValue > aGrabBag, aEffects, aOuterShdwProps;
4991  bool bHasInteropGrabBag = rXPropSet->getPropertySetInfo()->hasPropertyByName("InteropGrabBag");
4992  if (bHasInteropGrabBag && GetProperty(rXPropSet, "InteropGrabBag"))
4993  {
4994  mAny >>= aGrabBag;
4995  auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
4996  [](const PropertyValue& rProp) { return rProp.Name == "EffectProperties"; });
4997  if (pProp != std::cend(aGrabBag))
4998  {
4999  pProp->Value >>= aEffects;
5000  auto pEffect = std::find_if(std::cbegin(aEffects), std::cend(aEffects),
5001  [](const PropertyValue& rEffect) { return rEffect.Name == "outerShdw"; });
5002  if (pEffect != std::cend(aEffects))
5003  pEffect->Value >>= aOuterShdwProps;
5004  }
5005  }
5006 
5007  // tdf#132201: the order of effects is important. Effects order (CT_EffectList in ECMA-376):
5008  // blur -> fillOverlay -> glow -> innerShdw -> outerShdw -> prstShdw -> reflection -> softEdge
5009 
5010  if( !aEffects.hasElements() )
5011  {
5012  bool bHasShadow = false;
5013  if( GetProperty( rXPropSet, "Shadow" ) )
5014  mAny >>= bHasShadow;
5015  bool bHasEffects = bHasShadow;
5016  if (!bHasEffects && GetProperty(rXPropSet, "GlowEffectRadius"))
5017  {
5018  sal_Int32 rad = 0;
5019  mAny >>= rad;
5020  bHasEffects = rad > 0;
5021  }
5022  if (!bHasEffects && GetProperty(rXPropSet, "SoftEdgeRadius"))
5023  {
5024  sal_Int32 rad = 0;
5025  mAny >>= rad;
5026  bHasEffects = rad > 0;
5027  }
5028 
5029  if (bHasEffects)
5030  {
5031  mpFS->startElementNS(XML_a, XML_effectLst);
5032  WriteGlowEffect(rXPropSet);
5033  if( bHasShadow )
5034  {
5035  double dX = +0.0, dY = +0.0;
5036  sal_Int32 nBlur =0;
5037  rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX;
5038  rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY;
5039  rXPropSet->getPropertyValue( "ShadowBlur" ) >>= nBlur;
5040 
5041  Sequence< PropertyValue > aShadowAttribsGrabBag{
5045  comphelper::makePropertyValue("rotWithShape", false) //ooxml default is 'true', so must write it
5046  };
5047 
5048  Sequence< PropertyValue > aShadowGrabBag{
5049  comphelper::makePropertyValue("Attribs", aShadowAttribsGrabBag),
5050  comphelper::makePropertyValue("RgbClr", rXPropSet->getPropertyValue( "ShadowColor" )),
5051  comphelper::makePropertyValue("RgbClrTransparency", rXPropSet->getPropertyValue( "ShadowTransparence" ))
5052  };
5053 
5054  WriteShapeEffect( u"outerShdw", aShadowGrabBag );
5055  }
5056  WriteSoftEdgeEffect(rXPropSet);
5057  mpFS->endElementNS(XML_a, XML_effectLst);
5058  }
5059  }
5060  else
5061  {
5062  for( auto& rOuterShdwProp : asNonConstRange(aOuterShdwProps) )
5063  {
5064  if( rOuterShdwProp.Name == "Attribs" )
5065  {
5066  Sequence< PropertyValue > aAttribsProps;
5067  rOuterShdwProp.Value >>= aAttribsProps;
5068 
5069  double dX = +0.0, dY = +0.0;
5070  sal_Int32 nBlur =0;
5071  rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX;
5072  rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY;
5073  rXPropSet->getPropertyValue( "ShadowBlur" ) >>= nBlur;
5074 
5075 
5076  for( auto& rAttribsProp : asNonConstRange(aAttribsProps) )
5077  {
5078  if( rAttribsProp.Name == "dist" )
5079  {
5080  rAttribsProp.Value <<= lcl_CalculateDist(dX, dY);
5081  }
5082  else if( rAttribsProp.Name == "dir" )
5083  {
5084  rAttribsProp.Value <<= lcl_CalculateDir(dX, dY);
5085  }
5086  else if( rAttribsProp.Name == "blurRad" )
5087  {
5088  rAttribsProp.Value <<= oox::drawingml::convertHmmToEmu(nBlur);
5089  }
5090  }
5091 
5092  rOuterShdwProp.Value <<= aAttribsProps;
5093  }
5094  else if( rOuterShdwProp.Name == "RgbClr" )
5095  {
5096  rOuterShdwProp.Value = rXPropSet->getPropertyValue( "ShadowColor" );
5097  }
5098  else if( rOuterShdwProp.Name == "RgbClrTransparency" )
5099  {
5100  rOuterShdwProp.Value = rXPropSet->getPropertyValue( "ShadowTransparence" );
5101  }
5102  }
5103 
5104  mpFS->startElementNS(XML_a, XML_effectLst);
5105  bool bGlowWritten = false;
5106  for( const auto& rEffect : std::as_const(aEffects) )
5107  {
5108  if (!bGlowWritten
5109  && (rEffect.Name == "innerShdw" || rEffect.Name == "outerShdw"
5110  || rEffect.Name == "prstShdw" || rEffect.Name == "reflection"
5111  || rEffect.Name == "softEdge"))
5112  {
5113  WriteGlowEffect(rXPropSet);
5114  bGlowWritten = true;
5115  }
5116 
5117  if( rEffect.Name == "outerShdw" )
5118  {
5119  WriteShapeEffect( rEffect.Name, aOuterShdwProps );
5120  }
5121  else
5122  {
5123  Sequence< PropertyValue > aEffectProps;
5124  rEffect.Value >>= aEffectProps;
5125  WriteShapeEffect( rEffect.Name, aEffectProps );
5126  }
5127  }
5128  if (!bGlowWritten)
5129  WriteGlowEffect(rXPropSet);
5130  WriteSoftEdgeEffect(rXPropSet); // the last
5131 
5132  mpFS->endElementNS(XML_a, XML_effectLst);
5133  }
5134 }
5135 
5137 {
5138  if (!rXPropSet->getPropertySetInfo()->hasPropertyByName("GlowEffectRadius"))
5139  {
5140  return;
5141  }
5142 
5143  sal_Int32 nRad = 0;
5144  rXPropSet->getPropertyValue("GlowEffectRadius") >>= nRad;
5145  if (!nRad)
5146  return;
5147 
5149  "rad", oox::drawingml::convertHmmToEmu(nRad)) };
5150  Sequence< PropertyValue > aGlowProps{
5151  comphelper::makePropertyValue("Attribs", aGlowAttribs),
5152  comphelper::makePropertyValue("RgbClr", rXPropSet->getPropertyValue("GlowEffectColor")),
5153  comphelper::makePropertyValue("RgbClrTransparency", rXPropSet->getPropertyValue("GlowEffectTransparency"))
5154  };
5155  // TODO other stuff like saturation or luminance
5156 
5157  WriteShapeEffect(u"glow", aGlowProps);
5158 }
5159 
5160 void DrawingML::WriteSoftEdgeEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet)
5161 {
5162  if (!rXPropSet->getPropertySetInfo()->hasPropertyByName("SoftEdgeRadius"))
5163  {
5164  return;
5165  }
5166 
5167  sal_Int32 nRad = 0;
5168  rXPropSet->getPropertyValue("SoftEdgeRadius") >>= nRad;
5169  if (!nRad)
5170  return;
5171 
5172  css::uno::Sequence<css::beans::PropertyValue> aAttribs{ comphelper::makePropertyValue(
5173  "rad", oox::drawingml::convertHmmToEmu(nRad)) };
5174  css::uno::Sequence<css::beans::PropertyValue> aProps{ comphelper::makePropertyValue("Attribs",
5175  aAttribs) };
5176 
5177  WriteShapeEffect(u"softEdge", aProps);
5178 }
5179 
5180 void DrawingML::Write3DEffects( const Reference< XPropertySet >& xPropSet, bool bIsText )
5181 {
5182  // check existence of the grab bag
5183  if( !GetProperty( xPropSet, "InteropGrabBag" ) )
5184  return;
5185 
5186  // extract the relevant properties from the grab bag
5187  Sequence< PropertyValue > aGrabBag, aEffectProps, aLightRigProps, aShape3DProps;
5188  mAny >>= aGrabBag;
5189 
5190  auto pShapeProp = std::find_if( std::cbegin(aGrabBag), std::cend(aGrabBag),
5191  [bIsText](const PropertyValue& rProp)
5192  { return rProp.Name == (bIsText ? u"Text3DEffectProperties" : u"3DEffectProperties"); });
5193  if (pShapeProp != std::cend(aGrabBag))
5194  {
5195  Sequence< PropertyValue > a3DEffectProps;
5196  pShapeProp->Value >>= a3DEffectProps;
5197  for( const auto& r3DEffectProp : std::as_const(a3DEffectProps) )
5198  {
5199  if( r3DEffectProp.Name == "Camera" )
5200  r3DEffectProp.Value >>= aEffectProps;
5201  else if( r3DEffectProp.Name == "LightRig" )
5202  r3DEffectProp.Value >>= aLightRigProps;
5203  else if( r3DEffectProp.Name == "Shape3D" )
5204  r3DEffectProp.Value >>= aShape3DProps;
5205  }
5206  }
5207 
5208  if( !aEffectProps.hasElements() && !aLightRigProps.hasElements() && !aShape3DProps.hasElements() )
5209  return;
5210 
5211  bool bCameraRotationPresent = false;
5212  rtl::Reference<sax_fastparser::FastAttributeList> aCameraAttrList = FastSerializerHelper::createAttrList();
5213  rtl::Reference<sax_fastparser::FastAttributeList> aCameraRotationAttrList = FastSerializerHelper::createAttrList();
5214  for( const auto& rEffectProp : std::as_const(aEffectProps) )
5215  {
5216  if( rEffectProp.Name == "prst" )
5217  {
5218  OUString sVal;
5219  rEffectProp.Value >>= sVal;
5220  aCameraAttrList->add(XML_prst, sVal);
5221  }
5222  else if( rEffectProp.Name == "fov" )
5223  {
5224  float fVal = 0;
5225  rEffectProp.Value >>= fVal;
5226  aCameraAttrList->add( XML_fov, OString::number( fVal * 60000 ).getStr() );
5227  }
5228  else if( rEffectProp.Name == "zoom" )
5229  {
5230  float fVal = 1;
5231  rEffectProp.Value >>= fVal;
5232  aCameraAttrList->add( XML_zoom, OString::number( fVal * 100000 ).getStr() );
5233  }
5234  else if( rEffectProp.Name == "rotLat" ||
5235  rEffectProp.Name == "rotLon" ||
5236  rEffectProp.Name == "rotRev" )
5237  {
5238  sal_Int32 nVal = 0, nToken = XML_none;
5239  rEffectProp.Value >>= nVal;
5240  if( rEffectProp.Name == "rotLat" )
5241  nToken = XML_lat;
5242  else if( rEffectProp.Name == "rotLon" )
5243  nToken = XML_lon;
5244  else if( rEffectProp.Name == "rotRev" )
5245  nToken = XML_rev;
5246  aCameraRotationAttrList->add( nToken, OString::number( nVal ).getStr() );
5247  bCameraRotationPresent = true;
5248  }
5249  }
5250 
5251  bool bLightRigRotationPresent = false;
5252  rtl::Reference<sax_fastparser::FastAttributeList> aLightRigAttrList = FastSerializerHelper::createAttrList();
5253  rtl::Reference<sax_fastparser::FastAttributeList> aLightRigRotationAttrList = FastSerializerHelper::createAttrList();
5254  for( const auto& rLightRigProp : std::as_const(aLightRigProps) )
5255  {
5256  if( rLightRigProp.Name == "rig" || rLightRigProp.Name == "dir" )
5257  {
5258  OUString sVal;
5259  sal_Int32 nToken = XML_none;
5260  rLightRigProp.Value >>= sVal;
5261  if( rLightRigProp.Name == "rig" )
5262  nToken = XML_rig;
5263  else if( rLightRigProp.Name == "dir" )
5264  nToken = XML_dir;
5265  aLightRigAttrList->add(nToken, sVal);
5266  }
5267  else if( rLightRigProp.Name == "rotLat" ||
5268  rLightRigProp.Name == "rotLon" ||
5269  rLightRigProp.Name == "rotRev" )
5270  {
5271  sal_Int32 nVal = 0, nToken = XML_none;
5272  rLightRigProp.Value >>= nVal;
5273  if( rLightRigProp.Name == "rotLat" )
5274  nToken = XML_lat;
5275  else if( rLightRigProp.Name == "rotLon" )
5276  nToken = XML_lon;
5277  else if( rLightRigProp.Name == "rotRev" )
5278  nToken = XML_rev;
5279  aLightRigRotationAttrList->add( nToken, OString::number( nVal ).getStr() );
5280  bLightRigRotationPresent = true;
5281  }
5282  }
5283 
5284  mpFS->startElementNS(XML_a, XML_scene3d);
5285 
5286  if( aEffectProps.hasElements() )
5287  {
5288  mpFS->startElementNS( XML_a, XML_camera, aCameraAttrList );
5289  if( bCameraRotationPresent )
5290  {
5291  mpFS->singleElementNS( XML_a, XML_rot, aCameraRotationAttrList );
5292  }
5293  mpFS->endElementNS( XML_a, XML_camera );
5294  }
5295  else
5296  {
5297  // a:camera with Word default values - Word won't open the document if this is not present
5298  mpFS->singleElementNS(XML_a, XML_camera, XML_prst, "orthographicFront");
5299  }
5300 
5301  if( aEffectProps.hasElements() )
5302  {
5303  mpFS->startElementNS( XML_a, XML_lightRig, aLightRigAttrList );
5304  if( bLightRigRotationPresent )
5305  {
5306  mpFS->singleElementNS( XML_a, XML_rot, aLightRigRotationAttrList );
5307  }
5308  mpFS->endElementNS( XML_a, XML_lightRig );
5309  }
5310  else
5311  {
5312  // a:lightRig with Word default values - Word won't open the document if this is not present
5313  mpFS->singleElementNS(XML_a, XML_lightRig, XML_rig, "threePt", XML_dir, "t");
5314  }
5315 
5316  mpFS->endElementNS( XML_a, XML_scene3d );
5317 
5318  if( !aShape3DProps.hasElements() )
5319  return;
5320 
5321  bool bBevelTPresent = false, bBevelBPresent = false;
5322  Sequence< PropertyValue > aExtrusionColorProps, aContourColorProps;
5323  rtl::Reference<sax_fastparser::FastAttributeList> aBevelTAttrList = FastSerializerHelper::createAttrList();
5324  rtl::Reference<sax_fastparser::FastAttributeList> aBevelBAttrList = FastSerializerHelper::createAttrList();
5325  rtl::Reference<sax_fastparser::FastAttributeList> aShape3DAttrList = FastSerializerHelper::createAttrList();
5326  for( const auto& rShape3DProp : std::as_const(aShape3DProps) )
5327  {
5328  if( rShape3DProp.Name == "extrusionH" || rShape3DProp.Name == "contourW" || rShape3DProp.Name == "z" )
5329  {
5330  sal_Int32 nVal = 0, nToken = XML_none;
5331  rShape3DProp.Value >>= nVal;
5332  if( rShape3DProp.Name == "extrusionH" )
5333  nToken = XML_extrusionH;
5334  else if( rShape3DProp.Name == "contourW" )
5335  nToken = XML_contourW;
5336  else if( rShape3DProp.Name == "z" )
5337  nToken = XML_z;
5338  aShape3DAttrList->add( nToken, OString::number( nVal ).getStr() );
5339  }
5340  else if( rShape3DProp.Name == "prstMaterial" )
5341  {
5342  OUString sVal;
5343  rShape3DProp.Value >>= sVal;
5344  aShape3DAttrList->add(XML_prstMaterial, sVal);
5345  }
5346  else if( rShape3DProp.Name == "extrusionClr" )
5347  {
5348  rShape3DProp.Value >>= aExtrusionColorProps;
5349  }
5350  else if( rShape3DProp.Name == "contourClr" )
5351  {
5352  rShape3DProp.Value >>= aContourColorProps;
5353  }
5354  else if( rShape3DProp.Name == "bevelT" || rShape3DProp.Name == "bevelB" )
5355  {
5356  Sequence< PropertyValue > aBevelProps;
5357  rShape3DProp.Value >>= aBevelProps;
5358  if ( !aBevelProps.hasElements() )
5359  continue;
5360 
5362  if( rShape3DProp.Name == "bevelT" )
5363  {
5364  bBevelTPresent = true;
5365  aBevelAttrList = aBevelTAttrList;
5366  }
5367  else
5368  {
5369  bBevelBPresent = true;
5370  aBevelAttrList = aBevelBAttrList;
5371  }
5372  for( const auto& rBevelProp : std::as_const(aBevelProps) )
5373  {
5374  if( rBevelProp.Name == "w" || rBevelProp.Name == "h" )
5375  {
5376  sal_Int32 nVal = 0, nToken = XML_none;
5377  rBevelProp.Value >>= nVal;
5378  if( rBevelProp.Name == "w" )
5379  nToken = XML_w;
5380  else if( rBevelProp.Name == "h" )
5381  nToken = XML_h;
5382  aBevelAttrList->add( nToken, OString::number( nVal ).getStr() );
5383  }
5384  else if( rBevelProp.Name == "prst" )
5385  {
5386  OUString sVal;
5387  rBevelProp.Value >>= sVal;
5388  aBevelAttrList->add(XML_prst, sVal);
5389  }
5390  }
5391 
5392  }
5393  }
5394 
5395  mpFS->startElementNS( XML_a, XML_sp3d, aShape3DAttrList );
5396  if( bBevelTPresent )
5397  {
5398  mpFS->singleElementNS( XML_a, XML_bevelT, aBevelTAttrList );
5399  }
5400  if( bBevelBPresent )
5401  {
5402  mpFS->singleElementNS( XML_a, XML_bevelB, aBevelBAttrList );
5403  }
5404  if( aExtrusionColorProps.hasElements() )
5405  {
5406  OUString sSchemeClr;
5407  ::Color nColor;
5408  sal_Int32 nTransparency(0);
5409  Sequence< PropertyValue > aColorTransformations;
5410  for( const auto& rExtrusionColorProp : std::as_const(aExtrusionColorProps) )
5411  {
5412  if( rExtrusionColorProp.Name == "schemeClr" )
5413  rExtrusionColorProp.Value >>= sSchemeClr;
5414  else if( rExtrusionColorProp.Name == "schemeClrTransformations" )
5415  rExtrusionColorProp.Value >>= aColorTransformations;
5416  else if( rExtrusionColorProp.Name == "rgbClr" )
5417  rExtrusionColorProp.Value >>= nColor;
5418  else if( rExtrusionColorProp.Name == "rgbClrTransparency" )
5419  rExtrusionColorProp.Value >>= nTransparency;
5420  }
5421  mpFS->startElementNS(XML_a, XML_extrusionClr);
5422 
5423  if( sSchemeClr.isEmpty() )
5424  WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
5425  else
5426  WriteColor( sSchemeClr, aColorTransformations );
5427 
5428  mpFS->endElementNS( XML_a, XML_extrusionClr );
5429  }
5430  if( aContourColorProps.hasElements() )
5431  {
5432  OUString sSchemeClr;
5433  ::Color nColor;
5434  sal_Int32 nTransparency(0);
5435  Sequence< PropertyValue > aColorTransformations;
5436  for( const auto& rContourColorProp : std::as_const(aContourColorProps) )
5437  {
5438  if( rContourColorProp.Name == "schemeClr" )
5439  rContourColorProp.Value >>= sSchemeClr;
5440  else if( rContourColorProp.Name == "schemeClrTransformations" )
5441  rContourColorProp.Value >>= aColorTransformations;
5442  else if( rContourColorProp.Name == "rgbClr" )
5443  rContourColorProp.Value >>= nColor;
5444  else if( rContourColorProp.Name == "rgbClrTransparency" )
5445  rContourColorProp.Value >>= nTransparency;
5446  }
5447  mpFS->startElementNS(XML_a, XML_contourClr);
5448 
5449  if( sSchemeClr.isEmpty() )
5450  WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
5451  else
5452  WriteColor( sSchemeClr, aContourColorProps );
5453 
5454  mpFS->endElementNS( XML_a, XML_contourClr );
5455  }
5456  mpFS->endElementNS( XML_a, XML_sp3d );
5457 }
5458 
5460 {
5461  if( !GetProperty( rXPropSet, "InteropGrabBag" ) )
5462  return;
5463 
5464  PropertyValue aEffect;
5465  Sequence< PropertyValue > aGrabBag;
5466  mAny >>= aGrabBag;
5467  auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
5468  [](const PropertyValue& rProp) { return rProp.Name == "ArtisticEffectProperties"; });
5469  if (pProp != std::cend(aGrabBag))
5470  pProp->Value >>= aEffect;
5471  sal_Int32 nEffectToken = ArtisticEffectProperties::getEffectToken( aEffect.Name );
5472  if( nEffectToken == XML_none )
5473  return;
5474 
5476  aEffect.Value >>= aAttrs;
5477  rtl::Reference<sax_fastparser::FastAttributeList> aAttrList = FastSerializerHelper::createAttrList();
5478  OString sRelId;
5479  for( const auto& rAttr : std::as_const(aAttrs) )
5480  {
5481  sal_Int32 nToken = ArtisticEffectProperties::getEffectToken( rAttr.Name );
5482  if( nToken != XML_none )
5483  {
5484  sal_Int32 nVal = 0;
5485  rAttr.Value >>= nVal;
5486  aAttrList->add( nToken, OString::number( nVal ).getStr() );
5487  }
5488  else if( rAttr.Name == "OriginalGraphic" )
5489  {
5490  Sequence< PropertyValue > aGraphic;
5491  rAttr.Value >>= aGraphic;
5492  Sequence< sal_Int8 > aGraphicData;
5493  OUString sGraphicId;
5494  for( const auto& rProp : std::as_const(aGraphic) )
5495  {
5496  if( rProp.Name == "Id" )
5497  rProp.Value >>= sGraphicId;
5498  else if( rProp.Name == "Data" )
5499  rProp.Value >>= aGraphicData;
5500  }
5501  sRelId = WriteWdpPicture( sGraphicId, aGraphicData );
5502  }
5503  }
5504 
5505  mpFS->startElementNS(XML_a, XML_extLst);
5506  mpFS->startElementNS(XML_a, XML_ext, XML_uri, "{BEBA8EAE-BF5A-486C-A8C5-ECC9F3942E4B}");
5507  mpFS->startElementNS( XML_a14, XML_imgProps,
5508  FSNS(XML_xmlns, XML_a14), mpFB->getNamespaceURL(OOX_NS(a14)) );
5509  mpFS->startElementNS(XML_a14, XML_imgLayer, FSNS(XML_r, XML_embed), sRelId);
5510  mpFS->startElementNS(XML_a14, XML_imgEffect);
5511 
5512  mpFS->singleElementNS( XML_a14, nEffectToken, aAttrList );
5513 
5514  mpFS->endElementNS( XML_a14, XML_imgEffect );
5515  mpFS->endElementNS( XML_a14, XML_imgLayer );
5516  mpFS->endElementNS( XML_a14, XML_imgProps );
5517  mpFS->endElementNS( XML_a, XML_ext );
5518  mpFS->endElementNS( XML_a, XML_extLst );
5519 }
5520 
5521 OString DrawingML::WriteWdpPicture( const OUString& rFileId, const Sequence< sal_Int8 >& rPictureData )
5522 {
5523  std::map<OUString, OUString>::iterator aCachedItem = maWdpCache.find( rFileId );
5524  if( aCachedItem != maWdpCache.end() )
5525  return OUStringToOString( aCachedItem->second, RTL_TEXTENCODING_UTF8 );
5526 
5527  OUString sFileName = "media/hdphoto" + OUString::number( mnWdpImageCounter++ ) + ".wdp";
5528  Reference< XOutputStream > xOutStream = mpFB->openFragmentStream( OUStringBuffer()
5529  .appendAscii( GetComponentDir() )
5530  .append( "/" + sFileName )
5531  .makeStringAndClear(),
5532  "image/vnd.ms-photo" );
5533  OUString sId;
5534  xOutStream->writeBytes( rPictureData );
5535  xOutStream->closeOutput();
5536 
5537  sId = mpFB->addRelation( mpFS->getOutputStream(),
5539  OUStringBuffer()
5540  .appendAscii( GetRelationCompPrefix() )
5541  .append( sFileName )
5542  .makeStringAndClear() );
5543 
5544  maWdpCache[rFileId] = sId;
5545  return OUStringToOString( sId, RTL_TEXTENCODING_UTF8 );
5546 }
5547 
5548 void DrawingML::WriteDiagram(const css::uno::Reference<css::drawing::XShape>& rXShape, int nDiagramId)
5549 {
5550  uno::Reference<beans::XPropertySet> xPropSet(rXShape, uno::UNO_QUERY);
5551 
5552  uno::Reference<xml::dom::XDocument> dataDom;
5553  uno::Reference<xml::dom::XDocument> layoutDom;
5554  uno::Reference<xml::dom::XDocument> styleDom;
5555  uno::Reference<xml::dom::XDocument> colorDom;
5556  uno::Reference<xml::dom::XDocument> drawingDom;
5557  uno::Sequence<uno::Sequence<uno::Any>> xDataRelSeq;
5558  uno::Sequence<uno::Any> diagramDrawing;
5559 
5560  // retrieve the doms from the GrabBag
5561  uno::Sequence<beans::PropertyValue> propList;
5562  xPropSet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= propList;
5563  for (const auto& rProp : std::as_const(propList))
5564  {
5565  OUString propName = rProp.Name;
5566  if (propName == "OOXData")
5567  rProp.Value >>= dataDom;
5568  else if (propName == "OOXLayout")
5569  rProp.Value >>= layoutDom;
5570  else if (propName == "OOXStyle")
5571  rProp.Value >>= styleDom;
5572  else if (propName == "OOXColor")
5573  rProp.Value >>= colorDom;
5574  else if (propName == "OOXDrawing")
5575  {
5576  rProp.Value >>= diagramDrawing;
5577  diagramDrawing[0]
5578  >>= drawingDom; // if there is OOXDrawing property then set drawingDom here only.
5579  }
5580  else if (propName == "OOXDiagramDataRels")
5581  rProp.Value >>= xDataRelSeq;
5582  }
5583 
5584  // check that we have the 4 mandatory XDocuments
5585  // if not, there was an error importing and we won't output anything
5586  if (!dataDom.is() || !layoutDom.is() || !styleDom.is() || !colorDom.is())
5587  return;
5588 
5589  // generate a unique id
5591  =