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