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