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