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