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