Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / oox / source / export / drawingml.cxx
blobe021354a4f0ad307b6b9ab116402ec0591a458e4
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <config_features.h>
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>
47 #include <numeric>
48 #include <string_view>
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>
108 #include <comphelper/propertyvalue.hxx>
109 #include <comphelper/random.hxx>
110 #include <comphelper/seqstream.hxx>
111 #include <comphelper/storagehelper.hxx>
112 #include <comphelper/xmltools.hxx>
113 #include <o3tl/any.hxx>
114 #include <o3tl/safeint.hxx>
115 #include <o3tl/string_view.hxx>
116 #include <tools/stream.hxx>
117 #include <tools/UnitConversion.hxx>
118 #include <unotools/fontdefs.hxx>
119 #include <vcl/cvtgrf.hxx>
120 #include <vcl/svapp.hxx>
121 #include <rtl/strbuf.hxx>
122 #include <filter/msfilter/escherex.hxx>
123 #include <filter/msfilter/util.hxx>
124 #include <editeng/outlobj.hxx>
125 #include <editeng/svxenum.hxx>
126 #include <editeng/unonames.hxx>
127 #include <editeng/unoprnms.hxx>
128 #include <editeng/flditem.hxx>
129 #include <editeng/escapementitem.hxx>
130 #include <editeng/unonrule.hxx>
131 #include <docmodel/uno/UnoComplexColor.hxx>
132 #include <svx/svdoashp.hxx>
133 #include <svx/svdomedia.hxx>
134 #include <svx/svdtrans.hxx>
135 #include <svx/unoshape.hxx>
136 #include <svx/EnhancedCustomShape2d.hxx>
137 #include <drawingml/presetgeometrynames.hxx>
138 #include <docmodel/uno/UnoGradientTools.hxx>
140 using namespace ::css;
141 using namespace ::css::beans;
142 using namespace ::css::drawing;
143 using namespace ::css::i18n;
144 using namespace ::css::style;
145 using namespace ::css::text;
146 using namespace ::css::uno;
147 using namespace ::css::container;
148 using namespace ::com::sun::star::drawing::EnhancedCustomShapeSegmentCommand;
150 using ::css::io::XOutputStream;
151 using ::sax_fastparser::FSHelperPtr;
152 using ::sax_fastparser::FastSerializerHelper;
154 namespace
156 const char* g_aPredefinedClrNames[] = {
157 "dk1",
158 "lt1",
159 "dk2",
160 "lt2",
161 "accent1",
162 "accent2",
163 "accent3",
164 "accent4",
165 "accent5",
166 "accent6",
167 "hlink",
168 "folHlink",
172 namespace oox::drawingml {
174 URLTransformer::~URLTransformer()
178 OUString URLTransformer::getTransformedString(const OUString& rString) const
180 return rString;
183 bool URLTransformer::isExternalURL(const OUString& rURL) const
185 bool bExternal = true;
186 if (rURL.startsWith("#"))
187 bExternal = false;
188 return bExternal;
191 GraphicExportCache& GraphicExportCache::get()
193 static GraphicExportCache staticGraphicExportCache;
194 return staticGraphicExportCache;
197 static css::uno::Any getLineDash( const css::uno::Reference<css::frame::XModel>& xModel, const OUString& rDashName )
199 css::uno::Reference<css::lang::XMultiServiceFactory> xFact(xModel, css::uno::UNO_QUERY);
200 css::uno::Reference<css::container::XNameAccess> xNameAccess(
201 xFact->createInstance("com.sun.star.drawing.DashTable"),
202 css::uno::UNO_QUERY );
203 if(xNameAccess.is())
205 if (!xNameAccess->hasByName(rDashName))
206 return css::uno::Any();
208 return xNameAccess->getByName(rDashName);
211 return css::uno::Any();
214 namespace
216 void WriteGradientPath(const basegfx::BGradient& rBGradient, const FSHelperPtr& pFS, const bool bCircle)
218 pFS->startElementNS(XML_a, XML_path, XML_path, bCircle ? "circle" : "rect");
220 // Write the focus rectangle. Work with the focus point, and assume
221 // that it extends 50% in all directions. The below
222 // left/top/right/bottom values are percentages, where 0 means the
223 // edge of the tile rectangle and 100% means the center of it.
224 rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList(
225 sax_fastparser::FastSerializerHelper::createAttrList());
226 sal_Int32 nLeftPercent = rBGradient.GetXOffset();
227 pAttributeList->add(XML_l, OString::number(nLeftPercent * PER_PERCENT));
228 sal_Int32 nTopPercent = rBGradient.GetYOffset();
229 pAttributeList->add(XML_t, OString::number(nTopPercent * PER_PERCENT));
230 sal_Int32 nRightPercent = 100 - rBGradient.GetXOffset();
231 pAttributeList->add(XML_r, OString::number(nRightPercent * PER_PERCENT));
232 sal_Int32 nBottomPercent = 100 - rBGradient.GetYOffset();
233 pAttributeList->add(XML_b, OString::number(nBottomPercent * PER_PERCENT));
234 pFS->singleElementNS(XML_a, XML_fillToRect, pAttributeList);
236 pFS->endElementNS(XML_a, XML_path);
240 // not thread safe
241 sal_Int32 DrawingML::mnDrawingMLCount = 0;
242 sal_Int32 DrawingML::mnVmlCount = 0;
243 sal_Int32 DrawingML::mnChartCount = 0;
245 sal_Int16 DrawingML::GetScriptType(const OUString& rStr)
247 if (rStr.getLength() > 0)
249 static Reference<css::i18n::XBreakIterator> xBreakIterator =
250 css::i18n::BreakIterator::create(comphelper::getProcessComponentContext());
252 sal_Int16 nScriptType = xBreakIterator->getScriptType(rStr, 0);
254 if (nScriptType == css::i18n::ScriptType::WEAK)
256 sal_Int32 nPos = xBreakIterator->nextScript(rStr, 0, nScriptType);
257 if (nPos < rStr.getLength())
258 nScriptType = xBreakIterator->getScriptType(rStr, nPos);
262 if (nScriptType != css::i18n::ScriptType::WEAK)
263 return nScriptType;
266 return css::i18n::ScriptType::LATIN;
269 void DrawingML::ResetMlCounters()
271 mnDrawingMLCount = 0;
272 mnVmlCount = 0;
273 mnChartCount = 0;
276 bool DrawingML::GetProperty( const Reference< XPropertySet >& rXPropertySet, const OUString& aName )
280 mAny = rXPropertySet->getPropertyValue(aName);
281 if (mAny.hasValue())
282 return true;
284 catch( const Exception& )
286 /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
288 return false;
291 bool DrawingML::GetPropertyAndState( const Reference< XPropertySet >& rXPropertySet, const Reference< XPropertyState >& rXPropertyState, const OUString& aName, PropertyState& eState )
295 mAny = rXPropertySet->getPropertyValue(aName);
296 if (mAny.hasValue())
298 eState = rXPropertyState->getPropertyState(aName);
299 return true;
302 catch( const Exception& )
304 /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
306 return false;
309 namespace
311 /// Gets hexa value of color on string format.
312 OString getColorStr(const ::Color nColor)
314 // Transparency is a separate element.
315 OString sColor = OString::number(sal_uInt32(nColor) & 0x00FFFFFF, 16);
316 if (sColor.getLength() < 6)
318 OStringBuffer sBuf("0");
319 int remains = 5 - sColor.getLength();
321 while (remains > 0)
323 sBuf.append("0");
324 remains--;
327 sBuf.append(sColor);
329 sColor = sBuf.toString();
331 return sColor;
335 void DrawingML::WriteColor( ::Color nColor, sal_Int32 nAlpha )
337 const auto sColor = getColorStr(nColor);
338 if( nAlpha < MAX_PERCENT )
340 mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
341 mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
342 mpFS->endElementNS( XML_a, XML_srgbClr );
345 else
347 mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor);
351 void DrawingML::WriteColor( const OUString& sColorSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
353 // prevent writing a tag with empty val attribute
354 if( sColorSchemeName.isEmpty() )
355 return;
357 if( aTransformations.hasElements() )
359 mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
360 WriteColorTransformations( aTransformations, nAlpha );
361 mpFS->endElementNS( XML_a, XML_schemeClr );
363 else if(nAlpha < MAX_PERCENT)
365 mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
366 mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
367 mpFS->endElementNS( XML_a, XML_schemeClr );
369 else
371 mpFS->singleElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
375 void DrawingML::WriteColor( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
377 const auto sColor = getColorStr(nColor);
378 if( aTransformations.hasElements() )
380 mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
381 WriteColorTransformations(aTransformations, nAlpha);
382 mpFS->endElementNS(XML_a, XML_srgbClr);
384 else if(nAlpha < MAX_PERCENT)
386 mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
387 mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
388 mpFS->endElementNS(XML_a, XML_srgbClr);
390 else
392 mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor);
396 void DrawingML::WriteColorTransformations( const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
398 for( const auto& rTransformation : aTransformations )
400 sal_Int32 nToken = Color::getColorTransformationToken( rTransformation.Name );
401 if( nToken != XML_TOKEN_INVALID && rTransformation.Value.hasValue() )
403 if(nToken == XML_alpha && nAlpha < MAX_PERCENT)
405 mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nAlpha));
407 else
409 sal_Int32 nValue = rTransformation.Value.get<sal_Int32>();
410 mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nValue));
416 void DrawingML::WriteSolidFill( ::Color nColor, sal_Int32 nAlpha )
418 mpFS->startElementNS(XML_a, XML_solidFill);
419 WriteColor( nColor, nAlpha );
420 mpFS->endElementNS( XML_a, XML_solidFill );
423 void DrawingML::WriteSolidFill( const OUString& sSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
425 mpFS->startElementNS(XML_a, XML_solidFill);
426 WriteColor( sSchemeName, aTransformations, nAlpha );
427 mpFS->endElementNS( XML_a, XML_solidFill );
430 void DrawingML::WriteSolidFill( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
432 mpFS->startElementNS(XML_a, XML_solidFill);
433 WriteColor(nColor, aTransformations, nAlpha);
434 mpFS->endElementNS(XML_a, XML_solidFill);
437 void DrawingML::WriteSolidFill( const Reference< XPropertySet >& rXPropSet )
439 // get fill color
440 if ( !GetProperty( rXPropSet, "FillColor" ) )
441 return;
442 sal_uInt32 nFillColor = mAny.get<sal_uInt32>();
444 // get InteropGrabBag and search the relevant attributes
445 OUString sColorFillScheme;
446 sal_uInt32 nOriginalColor = 0;
447 Sequence< PropertyValue > aStyleProperties, aTransformations;
448 if ( GetProperty( rXPropSet, "InteropGrabBag" ) )
450 Sequence< PropertyValue > aGrabBag;
451 mAny >>= aGrabBag;
452 for( const auto& rProp : std::as_const(aGrabBag) )
454 if( rProp.Name == "SpPrSolidFillSchemeClr" )
455 rProp.Value >>= sColorFillScheme;
456 else if( rProp.Name == "OriginalSolidFillClr" )
457 rProp.Value >>= nOriginalColor;
458 else if( rProp.Name == "StyleFillRef" )
459 rProp.Value >>= aStyleProperties;
460 else if( rProp.Name == "SpPrSolidFillSchemeClrTransformations" )
461 rProp.Value >>= aTransformations;
465 sal_Int32 nAlpha = MAX_PERCENT;
466 if( GetProperty( rXPropSet, "FillTransparence" ) )
468 sal_Int32 nTransparency = 0;
469 mAny >>= nTransparency;
470 // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
471 nAlpha = (MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
474 // OOXML has no separate transparence gradient but uses transparency in the gradient stops.
475 // So we merge transparency and color and use gradient fill in such case.
476 basegfx::BGradient aTransparenceGradient;
477 OUString sFillTransparenceGradientName;
478 bool bNeedGradientFill(false);
480 if (GetProperty(rXPropSet, "FillTransparenceGradientName")
481 && (mAny >>= sFillTransparenceGradientName)
482 && !sFillTransparenceGradientName.isEmpty()
483 && GetProperty(rXPropSet, "FillTransparenceGradient"))
485 aTransparenceGradient = model::gradient::getFromAny(mAny);
486 basegfx::BColor aSingleColor;
487 bNeedGradientFill = !aTransparenceGradient.GetColorStops().isSingleColor(aSingleColor);
489 // we no longer need to 'guess' if FillTransparenceGradient is used by
490 // comparing it's 1st color to COL_BLACK after having tested that the
491 // FillTransparenceGradientName is set
492 if (!bNeedGradientFill)
494 // Our alpha is a gray color value.
495 const sal_uInt8 nRed(aSingleColor.getRed() * 255.0);
497 // drawingML alpha is a percentage on a 0..100000 scale.
498 nAlpha = (255 - nRed) * oox::drawingml::MAX_PERCENT / 255;
502 // write XML
503 if (bNeedGradientFill)
505 // no longer create copy/PseudoColorGradient, use new API of
506 // WriteGradientFill to express fix fill color
507 mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
508 WriteGradientFill(nullptr, nFillColor, &aTransparenceGradient);
509 mpFS->endElementNS( XML_a, XML_gradFill );
511 else if ( nFillColor != nOriginalColor )
513 // the user has set a different color for the shape
514 if (!WriteSchemeColor(u"FillComplexColor", rXPropSet))
516 WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha);
519 // tdf#91332 LO doesn't export the actual theme.xml in XLSX.
520 else if ( !sColorFillScheme.isEmpty() && GetDocumentType() != DOCUMENT_XLSX )
522 // the shape had a scheme color and the user didn't change it
523 WriteSolidFill( sColorFillScheme, aTransformations, nAlpha );
525 else
527 // the shape had a custom color and the user didn't change it
528 // tdf#124013
529 WriteSolidFill( ::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha );
533 bool DrawingML::WriteSchemeColor(OUString const& rPropertyName, const uno::Reference<beans::XPropertySet>& xPropertySet)
535 if (!xPropertySet->getPropertySetInfo()->hasPropertyByName(rPropertyName))
536 return false;
538 uno::Reference<util::XComplexColor> xComplexColor;
539 xPropertySet->getPropertyValue(rPropertyName) >>= xComplexColor;
540 if (!xComplexColor.is())
541 return false;
543 auto aComplexColor = model::color::getFromXComplexColor(xComplexColor);
544 if (aComplexColor.getSchemeType() == model::ThemeColorType::Unknown)
545 return false;
546 const char* pColorName = g_aPredefinedClrNames[sal_Int16(aComplexColor.getSchemeType())];
547 mpFS->startElementNS(XML_a, XML_solidFill);
548 mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, pColorName);
549 for (auto const& rTransform : aComplexColor.getTransformations())
551 switch (rTransform.meType)
553 case model::TransformationType::LumMod:
554 mpFS->singleElementNS(XML_a, XML_lumMod, XML_val, OString::number(rTransform.mnValue * 10));
555 break;
556 case model::TransformationType::LumOff:
557 mpFS->singleElementNS(XML_a, XML_lumOff, XML_val, OString::number(rTransform.mnValue * 10));
558 break;
559 case model::TransformationType::Tint:
560 mpFS->singleElementNS(XML_a, XML_tint, XML_val, OString::number(rTransform.mnValue * 10));
561 break;
562 case model::TransformationType::Shade:
563 mpFS->singleElementNS(XML_a, XML_shade, XML_val, OString::number(rTransform.mnValue * 10));
564 break;
565 default:
566 break;
569 // Alpha is actually not contained in maTransformations although possible (as of Mar 2023).
570 sal_Int16 nAPITransparency(0);
571 if ((rPropertyName == u"FillComplexColor" && GetProperty(xPropertySet, "FillTransparence"))
572 || (rPropertyName == u"LineComplexColor" && GetProperty(xPropertySet, "LineTransparence"))
573 || (rPropertyName == u"CharComplexColor" && GetProperty(xPropertySet, "CharTransparence")))
575 mAny >>= nAPITransparency;
577 if (nAPITransparency != 0)
578 mpFS->singleElementNS(XML_a, XML_alpha, XML_val,
579 OString::number(MAX_PERCENT - (PER_PERCENT * nAPITransparency)));
581 mpFS->endElementNS(XML_a, XML_schemeClr);
582 mpFS->endElementNS(XML_a, XML_solidFill);
584 return true;
587 void DrawingML::WriteGradientStop(double fOffset, const basegfx::BColor& rColor, const basegfx::BColor& rAlpha)
589 mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(basegfx::fround(fOffset * 100000)));
590 WriteColor(
591 ::Color(rColor),
592 basegfx::fround((1.0 - rAlpha.luminance()) * oox::drawingml::MAX_PERCENT));
593 mpFS->endElementNS( XML_a, XML_gs );
596 ::Color DrawingML::ColorWithIntensity( sal_uInt32 nColor, sal_uInt32 nIntensity )
598 return ::Color(ColorTransparency, ( ( ( nColor & 0xff ) * nIntensity ) / 100 )
599 | ( ( ( ( ( nColor & 0xff00 ) >> 8 ) * nIntensity ) / 100 ) << 8 )
600 | ( ( ( ( ( nColor & 0xff0000 ) >> 8 ) * nIntensity ) / 100 ) << 8 ));
603 void DrawingML::WriteGradientFill( const Reference< XPropertySet >& rXPropSet )
605 if (!GetProperty(rXPropSet, "FillGradient"))
606 return;
608 // use BGradient constructor directly, it will take care of Gradient/Gradient2
609 basegfx::BGradient aGradient = model::gradient::getFromAny(mAny);
611 // get InteropGrabBag and search the relevant attributes
612 basegfx::BGradient aOriginalGradient;
613 Sequence< PropertyValue > aGradientStops;
614 if ( GetProperty( rXPropSet, "InteropGrabBag" ) )
616 Sequence< PropertyValue > aGrabBag;
617 mAny >>= aGrabBag;
618 for( const auto& rProp : std::as_const(aGrabBag) )
619 if( rProp.Name == "GradFillDefinition" )
620 rProp.Value >>= aGradientStops;
621 else if( rProp.Name == "OriginalGradFill" )
622 aOriginalGradient = model::gradient::getFromAny(rProp.Value);
625 // check if an ooxml gradient had been imported and if the user has modified it
626 // Gradient grab-bag depends on theme grab-bag, which is implemented
627 // only for DOCX.
628 if (aOriginalGradient == aGradient && GetDocumentType() == DOCUMENT_DOCX)
630 // If we have no gradient stops that means original gradient were defined by a theme.
631 if( aGradientStops.hasElements() )
633 mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
634 WriteGrabBagGradientFill(aGradientStops, aGradient);
635 mpFS->endElementNS( XML_a, XML_gradFill );
638 else
640 mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
642 basegfx::BGradient aTransparenceGradient;
643 basegfx::BGradient* pTransparenceGradient(nullptr);
644 double fTransparency(0.0);
645 OUString sFillTransparenceGradientName;
647 if (GetProperty(rXPropSet, "FillTransparenceGradientName")
648 && (mAny >>= sFillTransparenceGradientName)
649 && !sFillTransparenceGradientName.isEmpty()
650 && GetProperty(rXPropSet, "FillTransparenceGradient"))
652 // TransparenceGradient is only used when name is not empty
653 aTransparenceGradient = model::gradient::getFromAny(mAny);
654 pTransparenceGradient = &aTransparenceGradient;
656 else if (GetProperty(rXPropSet, "FillTransparence"))
658 // no longer create PseudoTransparencyGradient, use new API of
659 // WriteGradientFill to express fix transparency
660 sal_Int32 nTransparency(0);
661 mAny >>= nTransparency;
662 // nTransparency is [0..100]%
663 fTransparency = nTransparency * 0.01;
666 // tdf#155852 The gradient might wrongly have StepCount==0, as the draw:gradient-step-count
667 // attribute in ODF does not belong to the gradient definition but is an attribute in
668 // the graphic style of the shape.
669 if (GetProperty(rXPropSet, "FillGradientStepCount"))
671 sal_Int16 nStepCount = 0;
672 mAny >>= nStepCount;
673 aGradient.SetSteps(nStepCount);
676 WriteGradientFill(&aGradient, 0, pTransparenceGradient, fTransparency);
678 mpFS->endElementNS(XML_a, XML_gradFill);
682 void DrawingML::WriteGrabBagGradientFill( const Sequence< PropertyValue >& aGradientStops, const basegfx::BGradient& rBGradient )
684 // write back the original gradient
685 mpFS->startElementNS(XML_a, XML_gsLst);
687 // get original stops and write them
688 for( const auto& rGradientStop : aGradientStops )
690 Sequence< PropertyValue > aGradientStop;
691 rGradientStop.Value >>= aGradientStop;
693 // get values
694 OUString sSchemeClr;
695 double nPos = 0;
696 sal_Int16 nTransparency = 0;
697 ::Color nRgbClr;
698 Sequence< PropertyValue > aTransformations;
699 for( const auto& rProp : std::as_const(aGradientStop) )
701 if( rProp.Name == "SchemeClr" )
702 rProp.Value >>= sSchemeClr;
703 else if( rProp.Name == "RgbClr" )
704 rProp.Value >>= nRgbClr;
705 else if( rProp.Name == "Pos" )
706 rProp.Value >>= nPos;
707 else if( rProp.Name == "Transparency" )
708 rProp.Value >>= nTransparency;
709 else if( rProp.Name == "Transformations" )
710 rProp.Value >>= aTransformations;
712 // write stop
713 mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nPos * 100000.0));
714 if( sSchemeClr.isEmpty() )
716 // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
717 sal_Int32 nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency );
718 WriteColor( nRgbClr, nAlpha );
720 else
722 WriteColor( sSchemeClr, aTransformations );
724 mpFS->endElementNS( XML_a, XML_gs );
726 mpFS->endElementNS( XML_a, XML_gsLst );
728 switch (rBGradient.GetGradientStyle())
730 default:
732 const sal_Int16 nAngle(rBGradient.GetAngle());
733 mpFS->singleElementNS(
734 XML_a, XML_lin, XML_ang,
735 OString::number(((3600 - static_cast<sal_Int32>(nAngle) + 900) * 6000) % 21600000));
736 break;
738 case awt::GradientStyle_RADIAL:
740 WriteGradientPath(rBGradient, mpFS, true);
741 break;
746 void DrawingML::WriteGradientFill(
747 const basegfx::BGradient* pColorGradient, sal_Int32 nFixColor,
748 const basegfx::BGradient* pTransparenceGradient, double fFixTransparence)
750 basegfx::BColorStops aColorStops;
751 basegfx::BColorStops aAlphaStops;
752 basegfx::BColor aSingleColor(::Color(ColorTransparency, nFixColor).getBColor());
753 basegfx::BColor aSingleAlpha(fFixTransparence);
754 const basegfx::BGradient* pGradient(pColorGradient);
756 if (nullptr != pColorGradient)
758 // extract and correct/process ColorStops
759 basegfx::utils::prepareColorStops(*pColorGradient, aColorStops, aSingleColor);
761 // tdf#155827 Convert 'axial' to 'linear' before synchronize and for each gradient separate.
762 if (aColorStops.size() > 0 && awt::GradientStyle_AXIAL == pColorGradient->GetGradientStyle())
763 aColorStops.doApplyAxial();
765 if (nullptr != pTransparenceGradient)
767 // remember basic Gradient definition to use
768 // So we can get the gradient geometry in any case from pGradient.
769 if (nullptr == pGradient)
771 pGradient = pTransparenceGradient;
774 // extract and correct/process AlphaStops
775 basegfx::utils::prepareColorStops(*pTransparenceGradient, aAlphaStops, aSingleAlpha);
776 if (aAlphaStops.size() > 0
777 && awt::GradientStyle_AXIAL == pTransparenceGradient->GetGradientStyle())
779 aAlphaStops.doApplyAxial();
783 if (nullptr == pGradient)
785 // an error - see comment in header - is to give neither pColorGradient
786 // nor pTransparenceGradient
787 assert(false && "pColorGradient or pTransparenceGradient should be set");
788 return;
791 // Apply steps if used. That increases the number of stops and thus needs to be done before
792 // synchronize.
793 if (pGradient->GetSteps())
795 aColorStops.doApplySteps(pGradient->GetSteps());
796 // transparency gradients are always automatic, so do not have steps.
799 // synchronize ColorStops and AlphaStops as preparation to export
800 // so also gradients 'coupled' indirectly using the 'FillTransparenceGradient'
801 // method (at import time) will be exported again.
802 basegfx::utils::synchronizeColorStops(aColorStops, aAlphaStops, aSingleColor, aSingleAlpha);
804 if (aColorStops.size() != aAlphaStops.size())
806 // this is an error - synchronizeColorStops above *has* to create that
807 // state, see description there (!)
808 assert(false && "oox::WriteGradientFill: non-synchronized gradients (!)");
809 return;
812 bool bLinearOrAxial(awt::GradientStyle_LINEAR == pGradient->GetGradientStyle()
813 || awt::GradientStyle_AXIAL == pGradient->GetGradientStyle());
814 if (!bLinearOrAxial)
816 // case awt::GradientStyle_RADIAL:
817 // case awt::GradientStyle_ELLIPTICAL:
818 // case awt::GradientStyle_RECT:
819 // case awt::GradientStyle_SQUARE:
820 // all these types need the gradients to be mirrored
821 aColorStops.reverseColorStops();
822 aAlphaStops.reverseColorStops();
825 // If there were one stop, prepareColorStops() method would have cleared aColorStops, same for
826 // aAlphaStops. In case of empty stops vectors synchronizeColorStops() method creates two stops
827 // for each. So at this point we have at least two stops and can fulfill the requirement of
828 // <gsLst> element to have at least two child elements.
830 // export GradientStops (with alpha)
831 mpFS->startElementNS(XML_a, XML_gsLst);
833 basegfx::BColorStops::const_iterator aCurrColor(aColorStops.begin());
834 basegfx::BColorStops::const_iterator aCurrAlpha(aAlphaStops.begin());
836 while (aCurrColor != aColorStops.end() && aCurrAlpha != aAlphaStops.end())
838 WriteGradientStop(
839 aCurrColor->getStopOffset(),
840 aCurrColor->getStopColor(),
841 aCurrAlpha->getStopColor());
842 aCurrColor++;
843 aCurrAlpha++;
846 mpFS->endElementNS( XML_a, XML_gsLst );
848 if (bLinearOrAxial)
850 // CT_LinearShadeProperties, cases where gradient rotation has to be exported
851 // 'scaled' does not exist in LO, so only 'ang'.
852 const sal_Int16 nAngle(pGradient->GetAngle());
853 mpFS->singleElementNS(
854 XML_a, XML_lin, XML_ang,
855 OString::number(((3600 - static_cast<sal_Int32>(nAngle) + 900) * 6000) % 21600000));
857 else
859 // CT_PathShadeProperties, cases where gradient path has to be exported
860 // Concentric fill is not yet implemented, therefore no type 'shape', only 'circle' or 'rect'
861 const bool bCircle(pGradient->GetGradientStyle() == awt::GradientStyle_RADIAL ||
862 pGradient->GetGradientStyle() == awt::GradientStyle_ELLIPTICAL);
863 WriteGradientPath(*pGradient, mpFS, bCircle);
867 void DrawingML::WriteLineArrow( const Reference< XPropertySet >& rXPropSet, bool bLineStart )
869 ESCHER_LineEnd eLineEnd;
870 sal_Int32 nArrowLength;
871 sal_Int32 nArrowWidth;
873 if ( !EscherPropertyContainer::GetLineArrow( bLineStart, rXPropSet, eLineEnd, nArrowLength, nArrowWidth ) )
874 return;
876 const char* len;
877 const char* type;
878 const char* width;
880 switch( nArrowLength )
882 case ESCHER_LineShortArrow:
883 len = "sm";
884 break;
885 default:
886 case ESCHER_LineMediumLenArrow:
887 len = "med";
888 break;
889 case ESCHER_LineLongArrow:
890 len = "lg";
891 break;
894 switch( eLineEnd )
896 default:
897 case ESCHER_LineNoEnd:
898 type = "none";
899 break;
900 case ESCHER_LineArrowEnd:
901 type = "triangle";
902 break;
903 case ESCHER_LineArrowStealthEnd:
904 type = "stealth";
905 break;
906 case ESCHER_LineArrowDiamondEnd:
907 type = "diamond";
908 break;
909 case ESCHER_LineArrowOvalEnd:
910 type = "oval";
911 break;
912 case ESCHER_LineArrowOpenEnd:
913 type = "arrow";
914 break;
917 switch( nArrowWidth )
919 case ESCHER_LineNarrowArrow:
920 width = "sm";
921 break;
922 default:
923 case ESCHER_LineMediumWidthArrow:
924 width = "med";
925 break;
926 case ESCHER_LineWideArrow:
927 width = "lg";
928 break;
931 mpFS->singleElementNS( XML_a, bLineStart ? XML_headEnd : XML_tailEnd,
932 XML_len, len,
933 XML_type, type,
934 XML_w, width );
937 void DrawingML::WriteOutline( const Reference<XPropertySet>& rXPropSet, Reference< frame::XModel > const & xModel )
939 drawing::LineStyle aLineStyle( drawing::LineStyle_NONE );
940 if (GetProperty(rXPropSet, "LineStyle"))
941 mAny >>= aLineStyle;
943 const LineCap aLineCap = GetProperty(rXPropSet, "LineCap") ? mAny.get<drawing::LineCap>() : LineCap_BUTT;
945 sal_uInt32 nLineWidth = 0;
946 sal_uInt32 nEmuLineWidth = 0;
947 ::Color nColor;
948 sal_Int32 nColorAlpha = MAX_PERCENT;
949 bool bColorSet = false;
950 const char* cap = nullptr;
951 drawing::LineDash aLineDash;
952 bool bDashSet = false;
953 bool bNoFill = false;
956 // get InteropGrabBag and search the relevant attributes
957 OUString sColorFillScheme;
958 ::Color aResolvedColorFillScheme;
960 ::Color nOriginalColor;
961 ::Color nStyleColor;
962 sal_uInt32 nStyleLineWidth = 0;
964 Sequence<PropertyValue> aStyleProperties;
965 Sequence<PropertyValue> aTransformations;
967 drawing::LineStyle aStyleLineStyle(drawing::LineStyle_NONE);
968 drawing::LineJoint aStyleLineJoint(drawing::LineJoint_NONE);
970 if (GetProperty(rXPropSet, "InteropGrabBag"))
972 Sequence<PropertyValue> aGrabBag;
973 mAny >>= aGrabBag;
975 for (const auto& rProp : std::as_const(aGrabBag))
977 if( rProp.Name == "SpPrLnSolidFillSchemeClr" )
978 rProp.Value >>= sColorFillScheme;
979 if( rProp.Name == "SpPrLnSolidFillResolvedSchemeClr" )
980 rProp.Value >>= aResolvedColorFillScheme;
981 else if( rProp.Name == "OriginalLnSolidFillClr" )
982 rProp.Value >>= nOriginalColor;
983 else if( rProp.Name == "StyleLnRef" )
984 rProp.Value >>= aStyleProperties;
985 else if( rProp.Name == "SpPrLnSolidFillSchemeClrTransformations" )
986 rProp.Value >>= aTransformations;
987 else if( rProp.Name == "EmuLineWidth" )
988 rProp.Value >>= nEmuLineWidth;
990 for (const auto& rStyleProp : std::as_const(aStyleProperties))
992 if( rStyleProp.Name == "Color" )
993 rStyleProp.Value >>= nStyleColor;
994 else if( rStyleProp.Name == "LineStyle" )
995 rStyleProp.Value >>= aStyleLineStyle;
996 else if( rStyleProp.Name == "LineJoint" )
997 rStyleProp.Value >>= aStyleLineJoint;
998 else if( rStyleProp.Name == "LineWidth" )
999 rStyleProp.Value >>= nStyleLineWidth;
1003 if (GetProperty(rXPropSet, "LineWidth"))
1004 mAny >>= nLineWidth;
1006 switch (aLineStyle)
1008 case drawing::LineStyle_NONE:
1009 bNoFill = true;
1010 break;
1011 case drawing::LineStyle_DASH:
1012 if (GetProperty(rXPropSet, "LineDash"))
1014 aLineDash = mAny.get<drawing::LineDash>();
1015 //this query is good for shapes, but in the case of charts it returns 0 values
1016 if (aLineDash.Dots == 0 && aLineDash.DotLen == 0 && aLineDash.Dashes == 0 && aLineDash.DashLen == 0 && aLineDash.Distance == 0) {
1017 OUString aLineDashName;
1018 if (GetProperty(rXPropSet, "LineDashName"))
1019 mAny >>= aLineDashName;
1020 if (!aLineDashName.isEmpty() && xModel) {
1021 css::uno::Any aAny = getLineDash(xModel, aLineDashName);
1022 aAny >>= aLineDash;
1026 else
1028 //export the linestyle of chart wall (plot area) and chart page
1029 OUString aLineDashName;
1030 if (GetProperty(rXPropSet, "LineDashName"))
1031 mAny >>= aLineDashName;
1032 if (!aLineDashName.isEmpty() && xModel) {
1033 css::uno::Any aAny = getLineDash(xModel, aLineDashName);
1034 aAny >>= aLineDash;
1037 bDashSet = true;
1038 if (aLineDash.Style == DashStyle_ROUND || aLineDash.Style == DashStyle_ROUNDRELATIVE)
1040 cap = "rnd";
1043 SAL_INFO("oox.shape", "dash dots: " << aLineDash.Dots << " dashes: " << aLineDash.Dashes
1044 << " dotlen: " << aLineDash.DotLen << " dashlen: " << aLineDash.DashLen << " distance: " << aLineDash.Distance);
1046 [[fallthrough]];
1047 case drawing::LineStyle_SOLID:
1048 default:
1049 if (GetProperty(rXPropSet, "LineColor"))
1051 nColor = ::Color(ColorTransparency, mAny.get<sal_uInt32>() & 0xffffff);
1052 bColorSet = true;
1054 if (GetProperty(rXPropSet, "LineTransparence"))
1056 nColorAlpha = MAX_PERCENT - (mAny.get<sal_Int16>() * PER_PERCENT);
1058 if (aLineCap == LineCap_ROUND)
1059 cap = "rnd";
1060 else if (aLineCap == LineCap_SQUARE)
1061 cap = "sq";
1062 break;
1065 // if the line-width was not modified after importing then the original EMU value will be exported to avoid unexpected conversion (rounding) error
1066 if (nEmuLineWidth == 0 || static_cast<sal_uInt32>(oox::drawingml::convertEmuToHmm(nEmuLineWidth)) != nLineWidth)
1067 nEmuLineWidth = oox::drawingml::convertHmmToEmu(nLineWidth);
1068 mpFS->startElementNS( XML_a, XML_ln,
1069 XML_cap, cap,
1070 XML_w, sax_fastparser::UseIf(OString::number(nEmuLineWidth),
1071 nLineWidth == 0 || GetDocumentType() == DOCUMENT_XLSX // tdf#119565 LO doesn't export the actual theme.xml in XLSX.
1072 || (nLineWidth > 1 && nStyleLineWidth != nLineWidth)));
1074 if( bColorSet )
1076 if( nColor != nOriginalColor )
1078 // the user has set a different color for the line
1079 if (!WriteSchemeColor(u"LineComplexColor", rXPropSet))
1080 WriteSolidFill(nColor, nColorAlpha);
1082 else if( !sColorFillScheme.isEmpty() )
1084 // the line had a scheme color and the user didn't change it
1085 WriteSolidFill( aResolvedColorFillScheme, aTransformations );
1087 else
1089 WriteSolidFill( nColor, nColorAlpha );
1093 if( bDashSet && aStyleLineStyle != drawing::LineStyle_DASH )
1095 // Try to detect if it might come from ms preset line style import.
1096 // MS Office styles are always relative, both binary and OOXML.
1097 // "dot" is always the first dash and "dash" the second one. All OOXML presets linestyles
1098 // start with the longer one. Definitions are in OOXML part 1, 20.1.10.49
1099 // The tests are strict, for to not catch styles from standard.sod (as of Aug 2019).
1100 bool bIsConverted = false;
1102 bool bIsRelative(aLineDash.Style == DashStyle_RECTRELATIVE || aLineDash.Style == DashStyle_ROUNDRELATIVE);
1103 if ( bIsRelative && aLineDash.Dots == 1)
1104 { // The length were tweaked on import in case of prstDash. Revert it here.
1105 sal_uInt32 nDotLen = aLineDash.DotLen;
1106 sal_uInt32 nDashLen = aLineDash.DashLen;
1107 sal_uInt32 nDistance = aLineDash.Distance;
1108 if (aLineCap != LineCap_BUTT && nDistance >= 99)
1110 nDistance -= 99;
1111 nDotLen += 99;
1112 if (nDashLen > 0)
1113 nDashLen += 99;
1115 // LO uses length 0 for 100%, if the attribute is missing in ODF.
1116 // Other applications might write 100%. Make is unique for the conditions.
1117 if (nDotLen == 0)
1118 nDotLen = 100;
1119 if (nDashLen == 0 && aLineDash.Dashes > 0)
1120 nDashLen = 100;
1121 bIsConverted = true;
1122 if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
1124 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dot");
1126 else if (nDotLen == 400 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
1128 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dash");
1130 else if (nDotLen == 400 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
1132 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dashDot");
1134 else if (nDotLen == 800 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
1136 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDash");
1138 else if (nDotLen == 800 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
1140 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDot");
1142 else if (nDotLen == 800 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 300)
1144 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDotDot");
1146 else if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
1148 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDot");
1150 else if (nDotLen == 300 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
1152 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDash");
1154 else if (nDotLen == 300 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 100)
1156 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDot");
1158 else if (nDotLen == 300 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 100)
1160 mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDotDot");
1162 else
1163 bIsConverted = false;
1165 // Do not map our own line styles to OOXML prstDash values, because custDash gives better results.
1166 if (!bIsConverted)
1168 mpFS->startElementNS(XML_a, XML_custDash);
1169 // In case of hairline we would need the current pixel size. Instead use a reasonable
1170 // ersatz for it. The value is the same as SMALLEST_DASH_WIDTH in xattr.cxx.
1171 // (And it makes sure fLineWidth is not zero in below division.)
1172 double fLineWidth = nLineWidth > 0 ? nLineWidth : 26.95;
1173 int i;
1174 double fSp = bIsRelative ? aLineDash.Distance : aLineDash.Distance * 100.0 / fLineWidth;
1175 // LO uses line width, in case Distance is zero. MS Office would use a space of zero length.
1176 // So set 100% explicitly.
1177 if (aLineDash.Distance <= 0)
1178 fSp = 100.0;
1179 // In case of custDash, round caps are included in dash length in MS Office. Square caps are added
1180 // to dash length, same as in ODF. Change the length values accordingly.
1181 if (aLineCap == LineCap_ROUND && fSp > 99.0)
1182 fSp -= 99.0;
1184 if (aLineDash.Dots > 0)
1186 double fD = bIsRelative ? aLineDash.DotLen : aLineDash.DotLen * 100.0 / fLineWidth;
1187 // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended.
1188 if (aLineDash.DotLen == 0)
1189 fD = 100.0;
1190 // Tweak dash length, see above.
1191 if (aLineCap == LineCap_ROUND && fSp > 99.0)
1192 fD += 99.0;
1194 for( i = 0; i < aLineDash.Dots; i ++ )
1196 mpFS->singleElementNS( XML_a, XML_ds,
1197 XML_d , write1000thOfAPercent(fD),
1198 XML_sp, write1000thOfAPercent(fSp) );
1201 if ( aLineDash.Dashes > 0 )
1203 double fD = bIsRelative ? aLineDash.DashLen : aLineDash.DashLen * 100.0 / fLineWidth;
1204 // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended.
1205 if (aLineDash.DashLen == 0)
1206 fD = 100.0;
1207 // Tweak dash length, see above.
1208 if (aLineCap == LineCap_ROUND && fSp > 99.0)
1209 fD += 99.0;
1211 for( i = 0; i < aLineDash.Dashes; i ++ )
1213 mpFS->singleElementNS( XML_a , XML_ds,
1214 XML_d , write1000thOfAPercent(fD),
1215 XML_sp, write1000thOfAPercent(fSp) );
1219 SAL_WARN_IF(nLineWidth <= 0,
1220 "oox.shape", "while writing outline - custom dash - line width was < 0 : " << nLineWidth);
1221 SAL_WARN_IF(aLineDash.Dashes < 0,
1222 "oox.shape", "while writing outline - custom dash - number of dashes was < 0 : " << aLineDash.Dashes);
1223 SAL_WARN_IF(aLineDash.Dashes > 0 && aLineDash.DashLen <= 0,
1224 "oox.shape", "while writing outline - custom dash - dash length was < 0 : " << aLineDash.DashLen);
1225 SAL_WARN_IF(aLineDash.Dots < 0,
1226 "oox.shape", "while writing outline - custom dash - number of dots was < 0 : " << aLineDash.Dots);
1227 SAL_WARN_IF(aLineDash.Dots > 0 && aLineDash.DotLen <= 0,
1228 "oox.shape", "while writing outline - custom dash - dot length was < 0 : " << aLineDash.DotLen);
1229 SAL_WARN_IF(aLineDash.Distance <= 0,
1230 "oox.shape", "while writing outline - custom dash - distance was < 0 : " << aLineDash.Distance);
1232 mpFS->endElementNS( XML_a, XML_custDash );
1236 if (!bNoFill && nLineWidth > 1 && GetProperty(rXPropSet, "LineJoint"))
1238 LineJoint eLineJoint = mAny.get<LineJoint>();
1240 // tdf#119565 LO doesn't export the actual theme.xml in XLSX.
1241 if (aStyleLineJoint == LineJoint_NONE || GetDocumentType() == DOCUMENT_XLSX
1242 || aStyleLineJoint != eLineJoint)
1244 // style-defined line joint does not exist, or is different from the shape's joint
1245 switch( eLineJoint )
1247 case LineJoint_NONE:
1248 case LineJoint_BEVEL:
1249 mpFS->singleElementNS(XML_a, XML_bevel);
1250 break;
1251 default:
1252 case LineJoint_MIDDLE:
1253 case LineJoint_MITER:
1254 mpFS->singleElementNS(XML_a, XML_miter);
1255 break;
1256 case LineJoint_ROUND:
1257 mpFS->singleElementNS(XML_a, XML_round);
1258 break;
1263 if( !bNoFill )
1265 WriteLineArrow( rXPropSet, true );
1266 WriteLineArrow( rXPropSet, false );
1268 else
1270 mpFS->singleElementNS(XML_a, XML_noFill);
1273 mpFS->endElementNS( XML_a, XML_ln );
1276 const char* DrawingML::GetComponentDir() const
1278 return getComponentDir(meDocumentType);
1281 const char* DrawingML::GetRelationCompPrefix() const
1283 return getRelationCompPrefix(meDocumentType);
1286 OUString GraphicExport::writeBlip(Graphic const& rGraphic, std::vector<model::BlipEffect> const& rEffects, bool bRelPathToMedia)
1288 OUString sRelId;
1290 sRelId = writeToStorage(rGraphic, bRelPathToMedia);
1292 mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId);
1294 for (auto const& rEffect : rEffects)
1296 switch (rEffect.meType)
1298 case model::BlipEffectType::AlphaBiLevel:
1300 mpFS->singleElementNS(XML_a, XML_alphaBiLevel, XML_thresh, OString::number(rEffect.mnThreshold));
1302 break;
1303 case model::BlipEffectType::AlphaCeiling:
1305 mpFS->singleElementNS(XML_a, XML_alphaCeiling);
1307 break;
1308 case model::BlipEffectType::AlphaFloor:
1310 mpFS->singleElementNS(XML_a, XML_alphaFloor);
1312 break;
1313 case model::BlipEffectType::AlphaInverse:
1315 mpFS->singleElementNS(XML_a, XML_alphaInv);
1316 // TODO: export rEffect.maColor1
1318 break;
1319 case model::BlipEffectType::AlphaModulate:
1321 mpFS->singleElementNS(XML_a, XML_alphaMod);
1322 // TODO
1324 break;
1325 case model::BlipEffectType::AlphaModulateFixed:
1327 mpFS->singleElementNS(XML_a, XML_alphaModFix, XML_amt, OString::number(rEffect.mnAmount));
1329 break;
1330 case model::BlipEffectType::AlphaReplace:
1332 mpFS->singleElementNS(XML_a, XML_alphaRepl, XML_a, OString::number(rEffect.mnAlpha));
1334 break;
1335 case model::BlipEffectType::BiLevel:
1337 mpFS->singleElementNS(XML_a, XML_biLevel, XML_thresh, OString::number(rEffect.mnThreshold));
1339 break;
1340 case model::BlipEffectType::Blur:
1342 mpFS->singleElementNS(XML_a, XML_blur,
1343 XML_rad, OString::number(rEffect.mnRadius),
1344 XML_grow, rEffect.mbGrow ? "1" : "0");
1346 break;
1347 case model::BlipEffectType::ColorChange:
1349 mpFS->startElementNS(XML_a, XML_clrChange, XML_useA, rEffect.mbUseAlpha ? "1" : "0");
1350 mpFS->endElementNS(XML_a, XML_clrChange);
1352 break;
1353 case model::BlipEffectType::ColorReplace:
1355 mpFS->startElementNS(XML_a, XML_clrRepl);
1356 mpFS->endElementNS(XML_a, XML_clrRepl);
1358 break;
1359 case model::BlipEffectType::DuoTone:
1361 mpFS->startElementNS(XML_a, XML_duotone);
1362 mpFS->endElementNS(XML_a, XML_duotone);
1364 break;
1365 case model::BlipEffectType::FillOverlay:
1367 mpFS->singleElementNS(XML_a, XML_fillOverlay);
1369 break;
1370 case model::BlipEffectType::Grayscale:
1372 mpFS->singleElementNS(XML_a, XML_grayscl);
1374 break;
1375 case model::BlipEffectType::HSL:
1377 mpFS->singleElementNS(XML_a, XML_hsl,
1378 XML_hue, OString::number(rEffect.mnHue),
1379 XML_sat, OString::number(rEffect.mnSaturation),
1380 XML_lum, OString::number(rEffect.mnLuminance));
1382 break;
1383 case model::BlipEffectType::Luminance:
1385 mpFS->singleElementNS(XML_a, XML_lum,
1386 XML_bright, OString::number(rEffect.mnBrightness),
1387 XML_contrast, OString::number(rEffect.mnContrast));
1389 break;
1390 case model::BlipEffectType::Tint:
1392 mpFS->singleElementNS(XML_a, XML_tint,
1393 XML_hue, OString::number(rEffect.mnHue),
1394 XML_amt, OString::number(rEffect.mnAmount));
1396 break;
1398 default:
1399 break;
1403 mpFS->endElementNS(XML_a, XML_blip);
1405 return sRelId;
1408 OUString GraphicExport::writeToStorage(const Graphic& rGraphic , bool bRelPathToMedia)
1410 GfxLink aLink = rGraphic.GetGfxLink ();
1411 BitmapChecksum aChecksum = rGraphic.GetChecksum();
1412 OUString sMediaType;
1413 const char* pExtension = "";
1414 OUString sRelId;
1415 OUString sPath;
1417 // tdf#74670 tdf#91286 Save image only once
1418 GraphicExportCache& rGraphicExportCache = GraphicExportCache::get();
1419 sPath = rGraphicExportCache.findExportGraphics(aChecksum);
1421 if (sPath.isEmpty())
1423 SvMemoryStream aStream;
1424 const void* aData = aLink.GetData();
1425 std::size_t nDataSize = aLink.GetDataSize();
1427 switch (aLink.GetType())
1429 case GfxLinkType::NativeGif:
1430 sMediaType = "image/gif";
1431 pExtension = ".gif";
1432 break;
1434 // #i15508# added BMP type for better exports
1435 // export not yet active, so adding for reference (not checked)
1436 case GfxLinkType::NativeBmp:
1437 sMediaType = "image/bmp";
1438 pExtension = ".bmp";
1439 break;
1441 case GfxLinkType::NativeJpg:
1442 sMediaType = "image/jpeg";
1443 pExtension = ".jpeg";
1444 break;
1445 case GfxLinkType::NativePng:
1446 sMediaType = "image/png";
1447 pExtension = ".png";
1448 break;
1449 case GfxLinkType::NativeTif:
1450 sMediaType = "image/tiff";
1451 pExtension = ".tif";
1452 break;
1453 case GfxLinkType::NativeWmf:
1454 sMediaType = "image/x-wmf";
1455 pExtension = ".wmf";
1456 break;
1457 case GfxLinkType::NativeMet:
1458 sMediaType = "image/x-met";
1459 pExtension = ".met";
1460 break;
1461 case GfxLinkType::NativePct:
1462 sMediaType = "image/x-pict";
1463 pExtension = ".pct";
1464 break;
1465 case GfxLinkType::NativeMov:
1466 sMediaType = "application/movie";
1467 pExtension = ".MOV";
1468 break;
1469 default:
1471 GraphicType aType = rGraphic.GetType();
1472 if (aType == GraphicType::Bitmap || aType == GraphicType::GdiMetafile)
1474 if (aType == GraphicType::Bitmap)
1476 (void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::PNG);
1477 sMediaType = "image/png";
1478 pExtension = ".png";
1480 else
1482 (void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::EMF);
1483 sMediaType = "image/x-emf";
1484 pExtension = ".emf";
1487 else
1489 SAL_WARN("oox.shape", "unhandled graphic type " << static_cast<int>(aType));
1490 /*Earlier, even in case of unhandled graphic types we were
1491 proceeding to write the image, which would eventually
1492 write an empty image with a zero size, and return a valid
1493 relationID, which is incorrect.
1495 return sRelId;
1498 aData = aStream.GetData();
1499 nDataSize = aStream.GetEndOfData();
1500 break;
1504 sal_Int32 nImageCount = rGraphicExportCache.nextImageCount();
1505 Reference<XOutputStream> xOutStream = mpFilterBase->openFragmentStream(
1506 OUStringBuffer()
1507 .appendAscii(getComponentDir(meDocumentType))
1508 .append("/media/image" + OUString::number(nImageCount))
1509 .appendAscii(pExtension)
1510 .makeStringAndClear(),
1511 sMediaType);
1512 xOutStream->writeBytes(Sequence<sal_Int8>(static_cast<const sal_Int8*>(aData), nDataSize));
1513 xOutStream->closeOutput();
1515 const OString sRelPathToMedia = "media/image";
1516 OString sRelationCompPrefix;
1517 if (bRelPathToMedia)
1518 sRelationCompPrefix = "../";
1519 else
1520 sRelationCompPrefix = getRelationCompPrefix(meDocumentType);
1521 sPath = OUStringBuffer()
1522 .appendAscii(sRelationCompPrefix.getStr())
1523 .appendAscii(sRelPathToMedia.getStr())
1524 .append(nImageCount)
1525 .appendAscii(pExtension)
1526 .makeStringAndClear();
1528 rGraphicExportCache.addExportGraphics(aChecksum, sPath);
1531 sRelId = mpFilterBase->addRelation( mpFS->getOutputStream(),
1532 oox::getRelationship(Relationship::IMAGE),
1533 sPath );
1535 return sRelId;
1538 OUString DrawingML::WriteImage( const Graphic& rGraphic , bool bRelPathToMedia )
1540 GraphicExport exporter(mpFS, mpFB, meDocumentType);
1541 return exporter.writeToStorage(rGraphic, bRelPathToMedia);
1544 void DrawingML::WriteMediaNonVisualProperties(const css::uno::Reference<css::drawing::XShape>& xShape)
1546 SdrMediaObj* pMediaObj = dynamic_cast<SdrMediaObj*>(SdrObject::getSdrObjectFromXShape(xShape));
1547 if (!pMediaObj)
1548 return;
1550 // extension
1551 OUString aExtension;
1552 const OUString& rURL(pMediaObj->getURL());
1553 int nLastDot = rURL.lastIndexOf('.');
1554 if (nLastDot >= 0)
1555 aExtension = rURL.copy(nLastDot);
1557 bool bEmbed = rURL.startsWith("vnd.sun.star.Package:");
1558 Relationship eMediaType = Relationship::VIDEO;
1560 // mime type
1561 #if HAVE_FEATURE_AVMEDIA
1562 OUString aMimeType(pMediaObj->getMediaProperties().getMimeType());
1563 #else
1564 OUString aMimeType("none");
1565 #endif
1566 if (aMimeType.startsWith("audio/"))
1568 eMediaType = Relationship::AUDIO;
1570 else
1571 if (aMimeType == "application/vnd.sun.star.media")
1573 // try to set something better
1574 // TODO fix the importer to actually set the mimetype on import
1575 if (aExtension.equalsIgnoreAsciiCase(".avi"))
1576 aMimeType = "video/x-msvideo";
1577 else if (aExtension.equalsIgnoreAsciiCase(".flv"))
1578 aMimeType = "video/x-flv";
1579 else if (aExtension.equalsIgnoreAsciiCase(".mp4"))
1580 aMimeType = "video/mp4";
1581 else if (aExtension.equalsIgnoreAsciiCase(".mov"))
1582 aMimeType = "video/quicktime";
1583 else if (aExtension.equalsIgnoreAsciiCase(".ogv"))
1584 aMimeType = "video/ogg";
1585 else if (aExtension.equalsIgnoreAsciiCase(".wmv"))
1586 aMimeType = "video/x-ms-wmv";
1587 else if (aExtension.equalsIgnoreAsciiCase(".wav"))
1589 aMimeType = "audio/x-wav";
1590 eMediaType = Relationship::AUDIO;
1592 else if (aExtension.equalsIgnoreAsciiCase(".m4a"))
1594 aMimeType = "audio/mp4";
1595 eMediaType = Relationship::AUDIO;
1597 else if (aExtension.equalsIgnoreAsciiCase(".mp3"))
1599 aMimeType = "audio/mp3";
1600 eMediaType = Relationship::AUDIO;
1604 OUString aVideoFileRelId;
1605 OUString aMediaRelId;
1607 if (bEmbed)
1609 sal_Int32 nImageCount = GraphicExportCache::get().nextImageCount();
1611 OUString sFileName = OUStringBuffer()
1612 .appendAscii(GetComponentDir())
1613 .append("/media/media" + OUString::number(nImageCount) + aExtension)
1614 .makeStringAndClear();
1616 // copy the video stream
1617 Reference<XOutputStream> xOutStream = mpFB->openFragmentStream(sFileName, aMimeType);
1619 uno::Reference<io::XInputStream> xInputStream(pMediaObj->GetInputStream());
1620 comphelper::OStorageHelper::CopyInputToOutput(xInputStream, xOutStream);
1622 xOutStream->closeOutput();
1624 // create the relation
1625 OUString aPath = OUStringBuffer().appendAscii(GetRelationCompPrefix())
1626 .append("media/media" + OUString::number(nImageCount) + aExtension)
1627 .makeStringAndClear();
1628 aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), aPath);
1629 aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), aPath);
1631 else
1633 aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), rURL, true);
1634 aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), rURL, true);
1637 GetFS()->startElementNS(XML_p, XML_nvPr);
1639 GetFS()->singleElementNS(XML_a, eMediaType == Relationship::VIDEO ? XML_videoFile : XML_audioFile,
1640 FSNS(XML_r, XML_link), aVideoFileRelId);
1642 GetFS()->startElementNS(XML_p, XML_extLst);
1643 // media extensions; google this ID for details
1644 GetFS()->startElementNS(XML_p, XML_ext, XML_uri, "{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}");
1646 GetFS()->singleElementNS(XML_p14, XML_media,
1647 bEmbed? FSNS(XML_r, XML_embed): FSNS(XML_r, XML_link), aMediaRelId);
1649 GetFS()->endElementNS(XML_p, XML_ext);
1650 GetFS()->endElementNS(XML_p, XML_extLst);
1652 GetFS()->endElementNS(XML_p, XML_nvPr);
1655 void DrawingML::WriteImageBrightnessContrastTransparence(uno::Reference<beans::XPropertySet> const & rXPropSet)
1657 sal_Int16 nBright = 0;
1658 sal_Int32 nContrast = 0;
1659 sal_Int32 nTransparence = 0;
1661 if (GetProperty(rXPropSet, "AdjustLuminance"))
1662 nBright = mAny.get<sal_Int16>();
1663 if (GetProperty(rXPropSet, "AdjustContrast"))
1664 nContrast = mAny.get<sal_Int32>();
1665 // Used for shapes with picture fill
1666 if (GetProperty(rXPropSet, "FillTransparence"))
1667 nTransparence = mAny.get<sal_Int32>();
1668 // Used for pictures
1669 if (nTransparence == 0 && GetProperty(rXPropSet, "Transparency"))
1670 nTransparence = static_cast<sal_Int32>(mAny.get<sal_Int16>());
1672 if (GetProperty(rXPropSet, "GraphicColorMode"))
1674 drawing::ColorMode aColorMode;
1675 mAny >>= aColorMode;
1676 if (aColorMode == drawing::ColorMode_GREYS)
1677 mpFS->singleElementNS(XML_a, XML_grayscl);
1678 else if (aColorMode == drawing::ColorMode_MONO)
1679 //black/white has a 0,5 threshold in LibreOffice
1680 mpFS->singleElementNS(XML_a, XML_biLevel, XML_thresh, OString::number(50000));
1681 else if (aColorMode == drawing::ColorMode_WATERMARK)
1683 //map watermark with mso washout
1684 nBright = 70;
1685 nContrast = -70;
1690 if (nBright || nContrast)
1692 mpFS->singleElementNS(XML_a, XML_lum,
1693 XML_bright, sax_fastparser::UseIf(OString::number(nBright * 1000), nBright != 0),
1694 XML_contrast, sax_fastparser::UseIf(OString::number(nContrast * 1000), nContrast != 0));
1697 if (nTransparence)
1699 sal_Int32 nAlphaMod = (100 - nTransparence ) * PER_PERCENT;
1700 mpFS->singleElementNS(XML_a, XML_alphaModFix, XML_amt, OString::number(nAlphaMod));
1704 OUString DrawingML::WriteXGraphicBlip(uno::Reference<beans::XPropertySet> const & rXPropSet,
1705 uno::Reference<graphic::XGraphic> const & rxGraphic,
1706 bool bRelPathToMedia)
1708 OUString sRelId;
1710 if (!rxGraphic.is())
1711 return sRelId;
1713 Graphic aGraphic(rxGraphic);
1714 sRelId = WriteImage(aGraphic, bRelPathToMedia);
1716 mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId);
1718 WriteImageBrightnessContrastTransparence(rXPropSet);
1720 WriteArtisticEffect(rXPropSet);
1722 mpFS->endElementNS(XML_a, XML_blip);
1724 return sRelId;
1727 void DrawingML::WriteXGraphicBlipMode(uno::Reference<beans::XPropertySet> const & rXPropSet,
1728 uno::Reference<graphic::XGraphic> const & rxGraphic,
1729 css::awt::Size const& rSize)
1731 BitmapMode eBitmapMode(BitmapMode_NO_REPEAT);
1732 if (GetProperty(rXPropSet, "FillBitmapMode"))
1733 mAny >>= eBitmapMode;
1735 SAL_INFO("oox.shape", "fill bitmap mode: " << int(eBitmapMode));
1737 switch (eBitmapMode)
1739 case BitmapMode_REPEAT:
1740 WriteXGraphicTile(rXPropSet, rxGraphic, rSize);
1741 break;
1742 case BitmapMode_STRETCH:
1743 WriteXGraphicStretch(rXPropSet, rxGraphic);
1744 break;
1745 case BitmapMode_NO_REPEAT:
1746 WriteXGraphicCustomPosition(rXPropSet, rxGraphic, rSize);
1747 break;
1748 default:
1749 break;
1753 void DrawingML::WriteBlipOrNormalFill(const Reference<XPropertySet>& xPropSet,
1754 const OUString& rURLPropName, const awt::Size& rSize)
1756 // check for blip and otherwise fall back to normal fill
1757 // we always store normal fill properties but OOXML
1758 // uses a choice between our fill props and BlipFill
1759 if (GetProperty ( xPropSet, rURLPropName ))
1760 WriteBlipFill( xPropSet, rURLPropName );
1761 else
1762 WriteFill(xPropSet, rSize);
1765 void DrawingML::WriteBlipFill(const Reference<XPropertySet>& rXPropSet,
1766 const OUString& sURLPropName, const awt::Size& rSize)
1768 WriteBlipFill( rXPropSet, rSize, sURLPropName, XML_a );
1771 void DrawingML::WriteBlipFill(const Reference<XPropertySet>& rXPropSet, const awt::Size& rSize,
1772 const OUString& sURLPropName, sal_Int32 nXmlNamespace)
1774 if ( !GetProperty( rXPropSet, sURLPropName ) )
1775 return;
1777 uno::Reference<graphic::XGraphic> xGraphic;
1778 if (mAny.has<uno::Reference<awt::XBitmap>>())
1780 uno::Reference<awt::XBitmap> xBitmap = mAny.get<uno::Reference<awt::XBitmap>>();
1781 if (xBitmap.is())
1782 xGraphic.set(xBitmap, uno::UNO_QUERY);
1784 else if (mAny.has<uno::Reference<graphic::XGraphic>>())
1786 xGraphic = mAny.get<uno::Reference<graphic::XGraphic>>();
1789 if (xGraphic.is())
1791 bool bWriteMode = false;
1792 if (sURLPropName == "FillBitmap" || sURLPropName == "BackGraphic")
1793 bWriteMode = true;
1794 WriteXGraphicBlipFill(rXPropSet, xGraphic, nXmlNamespace, bWriteMode, false, rSize);
1798 void DrawingML::WriteXGraphicBlipFill(uno::Reference<beans::XPropertySet> const & rXPropSet,
1799 uno::Reference<graphic::XGraphic> const & rxGraphic,
1800 sal_Int32 nXmlNamespace, bool bWriteMode,
1801 bool bRelPathToMedia, css::awt::Size const& rSize)
1803 if (!rxGraphic.is() )
1804 return;
1806 mpFS->startElementNS(nXmlNamespace , XML_blipFill, XML_rotWithShape, "0");
1808 WriteXGraphicBlip(rXPropSet, rxGraphic, bRelPathToMedia);
1810 if (GetDocumentType() != DOCUMENT_DOCX)
1812 // Write the crop rectangle of Impress as a source rectangle.
1813 WriteSrcRectXGraphic(rXPropSet, rxGraphic);
1816 if (bWriteMode)
1818 WriteXGraphicBlipMode(rXPropSet, rxGraphic, rSize);
1820 else if(GetProperty(rXPropSet, "FillBitmapStretch"))
1822 bool bStretch = mAny.get<bool>();
1824 if (bStretch)
1826 WriteXGraphicStretch(rXPropSet, rxGraphic);
1829 mpFS->endElementNS(nXmlNamespace, XML_blipFill);
1832 void DrawingML::WritePattFill( const Reference< XPropertySet >& rXPropSet )
1834 if ( GetProperty( rXPropSet, "FillHatch" ) )
1836 drawing::Hatch aHatch;
1837 mAny >>= aHatch;
1838 WritePattFill(rXPropSet, aHatch);
1842 void DrawingML::WritePattFill(const Reference<XPropertySet>& rXPropSet, const css::drawing::Hatch& rHatch)
1844 mpFS->startElementNS(XML_a, XML_pattFill, XML_prst, GetHatchPattern(rHatch));
1846 sal_Int32 nAlpha = MAX_PERCENT;
1847 if (GetProperty(rXPropSet, "FillTransparence"))
1849 sal_Int32 nTransparency = 0;
1850 mAny >>= nTransparency;
1851 nAlpha = (MAX_PERCENT - (PER_PERCENT * nTransparency));
1854 mpFS->startElementNS(XML_a, XML_fgClr);
1855 WriteColor(::Color(ColorTransparency, rHatch.Color), nAlpha);
1856 mpFS->endElementNS( XML_a , XML_fgClr );
1858 ::Color nColor = COL_WHITE;
1860 if ( GetProperty( rXPropSet, "FillBackground" ) )
1862 bool isBackgroundFilled = false;
1863 mAny >>= isBackgroundFilled;
1864 if( isBackgroundFilled )
1866 if( GetProperty( rXPropSet, "FillColor" ) )
1868 mAny >>= nColor;
1871 else
1872 nAlpha = 0;
1875 mpFS->startElementNS(XML_a, XML_bgClr);
1876 WriteColor(nColor, nAlpha);
1877 mpFS->endElementNS( XML_a , XML_bgClr );
1879 mpFS->endElementNS( XML_a , XML_pattFill );
1882 void DrawingML::WriteGraphicCropProperties(uno::Reference<beans::XPropertySet> const & rXPropSet,
1883 Size const & rOriginalSize,
1884 MapMode const & rMapMode)
1886 if (!GetProperty(rXPropSet, "GraphicCrop"))
1887 return;
1889 css::text::GraphicCrop aGraphicCropStruct;
1890 mAny >>= aGraphicCropStruct;
1892 if(GetProperty(rXPropSet, "CustomShapeGeometry"))
1894 // tdf#134210 GraphicCrop property is handled in import filter because of LibreOffice has not core
1895 // feature. We cropped the bitmap physically and MSO shouldn't crop bitmap one more time. When we
1896 // have core feature for graphic cropping in custom shapes, we should uncomment the code anymore.
1898 mpFS->singleElementNS( XML_a, XML_srcRect);
1900 else
1902 Size aOriginalSize(rOriginalSize);
1904 // GraphicCrop is in mm100, so in case the original size is in pixels, convert it over.
1905 if (rMapMode.GetMapUnit() == MapUnit::MapPixel)
1906 aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, MapMode(MapUnit::Map100thMM));
1908 if ( (0 != aGraphicCropStruct.Left) || (0 != aGraphicCropStruct.Top) || (0 != aGraphicCropStruct.Right) || (0 != aGraphicCropStruct.Bottom) )
1910 mpFS->singleElementNS( XML_a, XML_srcRect,
1911 XML_l, OString::number(rtl::math::round(aGraphicCropStruct.Left * 100000.0 / aOriginalSize.Width())),
1912 XML_t, OString::number(rtl::math::round(aGraphicCropStruct.Top * 100000.0 / aOriginalSize.Height())),
1913 XML_r, OString::number(rtl::math::round(aGraphicCropStruct.Right * 100000.0 / aOriginalSize.Width())),
1914 XML_b, OString::number(rtl::math::round(aGraphicCropStruct.Bottom * 100000.0 / aOriginalSize.Height())) );
1919 void DrawingML::WriteSrcRectXGraphic(uno::Reference<beans::XPropertySet> const & rxPropertySet,
1920 uno::Reference<graphic::XGraphic> const & rxGraphic)
1922 Graphic aGraphic(rxGraphic);
1923 Size aOriginalSize = aGraphic.GetPrefSize();
1924 const MapMode& rMapMode = aGraphic.GetPrefMapMode();
1925 WriteGraphicCropProperties(rxPropertySet, aOriginalSize, rMapMode);
1928 void DrawingML::WriteXGraphicStretch(uno::Reference<beans::XPropertySet> const & rXPropSet,
1929 uno::Reference<graphic::XGraphic> const & rxGraphic)
1931 if (GetDocumentType() != DOCUMENT_DOCX)
1933 // Limiting the area used for stretching is not supported in Impress.
1934 mpFS->singleElementNS(XML_a, XML_stretch);
1935 return;
1938 mpFS->startElementNS(XML_a, XML_stretch);
1940 bool bCrop = false;
1941 if (GetProperty(rXPropSet, "GraphicCrop"))
1943 css::text::GraphicCrop aGraphicCropStruct;
1944 mAny >>= aGraphicCropStruct;
1946 if ((0 != aGraphicCropStruct.Left)
1947 || (0 != aGraphicCropStruct.Top)
1948 || (0 != aGraphicCropStruct.Right)
1949 || (0 != aGraphicCropStruct.Bottom))
1951 Graphic aGraphic(rxGraphic);
1952 Size aOriginalSize(aGraphic.GetPrefSize());
1953 mpFS->singleElementNS(XML_a, XML_fillRect,
1954 XML_l, OString::number(((aGraphicCropStruct.Left) * 100000) / aOriginalSize.Width()),
1955 XML_t, OString::number(((aGraphicCropStruct.Top) * 100000) / aOriginalSize.Height()),
1956 XML_r, OString::number(((aGraphicCropStruct.Right) * 100000) / aOriginalSize.Width()),
1957 XML_b, OString::number(((aGraphicCropStruct.Bottom) * 100000) / aOriginalSize.Height()));
1958 bCrop = true;
1962 if (!bCrop)
1964 mpFS->singleElementNS(XML_a, XML_fillRect);
1967 mpFS->endElementNS(XML_a, XML_stretch);
1970 static OUString lclConvertRectanglePointToToken(RectanglePoint eRectanglePoint)
1972 OUString sAlignment;
1973 switch (eRectanglePoint)
1975 case RectanglePoint_LEFT_TOP:
1976 sAlignment = "tl";
1977 break;
1978 case RectanglePoint_MIDDLE_TOP:
1979 sAlignment = "t";
1980 break;
1981 case RectanglePoint_RIGHT_TOP:
1982 sAlignment = "tr";
1983 break;
1984 case RectanglePoint_LEFT_MIDDLE:
1985 sAlignment = "l";
1986 break;
1987 case RectanglePoint_MIDDLE_MIDDLE:
1988 sAlignment = "ctr";
1989 break;
1990 case RectanglePoint_RIGHT_MIDDLE:
1991 sAlignment = "r";
1992 break;
1993 case RectanglePoint_LEFT_BOTTOM:
1994 sAlignment = "bl";
1995 break;
1996 case RectanglePoint_MIDDLE_BOTTOM:
1997 sAlignment = "b";
1998 break;
1999 case RectanglePoint_RIGHT_BOTTOM:
2000 sAlignment = "br";
2001 break;
2002 default:
2003 break;
2005 return sAlignment;
2008 void DrawingML::WriteXGraphicTile(uno::Reference<beans::XPropertySet> const& rXPropSet,
2009 uno::Reference<graphic::XGraphic> const& rxGraphic,
2010 css::awt::Size const& rSize)
2012 Graphic aGraphic(rxGraphic);
2013 Size aOriginalSize(aGraphic.GetPrefSize());
2014 const MapMode& rMapMode = aGraphic.GetPrefMapMode();
2015 // if the original size is in pixel, convert it to mm100
2016 if (rMapMode.GetMapUnit() == MapUnit::MapPixel)
2017 aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize,
2018 MapMode(MapUnit::Map100thMM));
2019 sal_Int32 nSizeX = 0;
2020 sal_Int32 nOffsetX = 0;
2021 if (GetProperty(rXPropSet, "FillBitmapSizeX"))
2023 mAny >>= nSizeX;
2024 if (GetProperty(rXPropSet, "FillBitmapPositionOffsetX"))
2026 sal_Int32 nX = (nSizeX != 0) ? nSizeX : aOriginalSize.Width();
2027 if (nX < 0 && rSize.Width > 0)
2028 nX = rSize.Width * std::abs(nX) / 100;
2029 nOffsetX = (*o3tl::doAccess<sal_Int32>(mAny)) * nX * 3.6;
2032 // convert the X size of bitmap to a percentage
2033 if (nSizeX > 0)
2034 nSizeX = double(nSizeX) / aOriginalSize.Width() * 100000;
2035 else if (nSizeX < 0)
2036 nSizeX *= 1000;
2037 else
2038 nSizeX = 100000;
2041 sal_Int32 nSizeY = 0;
2042 sal_Int32 nOffsetY = 0;
2043 if (GetProperty(rXPropSet, "FillBitmapSizeY"))
2045 mAny >>= nSizeY;
2046 if (GetProperty(rXPropSet, "FillBitmapPositionOffsetY"))
2048 sal_Int32 nY = (nSizeY != 0) ? nSizeY : aOriginalSize.Height();
2049 if (nY < 0 && rSize.Height > 0)
2050 nY = rSize.Height * std::abs(nY) / 100;
2051 nOffsetY = (*o3tl::doAccess<sal_Int32>(mAny)) * nY * 3.6;
2054 // convert the Y size of bitmap to a percentage
2055 if (nSizeY > 0)
2056 nSizeY = double(nSizeY) / aOriginalSize.Height() * 100000;
2057 else if (nSizeY < 0)
2058 nSizeY *= 1000;
2059 else
2060 nSizeY = 100000;
2063 // if the "Scale" setting is checked in the images settings dialog.
2064 if (nSizeX < 0 && nSizeY < 0)
2066 if (rSize.Width != 0 && rSize.Height != 0)
2068 nSizeX = rSize.Width / double(aOriginalSize.Width()) * std::abs(nSizeX);
2069 nSizeY = rSize.Height / double(aOriginalSize.Height()) * std::abs(nSizeY);
2071 else
2073 nSizeX = std::abs(nSizeX);
2074 nSizeY = std::abs(nSizeY);
2078 OUString sRectanglePoint;
2079 if (GetProperty(rXPropSet, "FillBitmapRectanglePoint"))
2080 sRectanglePoint = lclConvertRectanglePointToToken(*o3tl::doAccess<RectanglePoint>(mAny));
2082 mpFS->singleElementNS(XML_a, XML_tile, XML_tx, OUString::number(nOffsetX), XML_ty,
2083 OUString::number(nOffsetY), XML_sx, OUString::number(nSizeX), XML_sy,
2084 OUString::number(nSizeY), XML_algn, sRectanglePoint);
2087 void DrawingML::WriteXGraphicCustomPosition(uno::Reference<beans::XPropertySet> const& rXPropSet,
2088 uno::Reference<graphic::XGraphic> const& rxGraphic,
2089 css::awt::Size const& rSize)
2091 Graphic aGraphic(rxGraphic);
2092 Size aOriginalSize(aGraphic.GetPrefSize());
2093 const MapMode& rMapMode = aGraphic.GetPrefMapMode();
2094 // if the original size is in pixel, convert it to mm100
2095 if (rMapMode.GetMapUnit() == MapUnit::MapPixel)
2096 aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize,
2097 MapMode(MapUnit::Map100thMM));
2098 double nSizeX = 0;
2099 if (GetProperty(rXPropSet, "FillBitmapSizeX"))
2101 mAny >>= nSizeX;
2102 if (nSizeX <= 0)
2104 if (nSizeX == 0)
2105 nSizeX = aOriginalSize.Width();
2106 else
2107 nSizeX /= 100; // percentage
2111 double nSizeY = 0;
2112 if (GetProperty(rXPropSet, "FillBitmapSizeY"))
2114 mAny >>= nSizeY;
2115 if (nSizeY <= 0)
2117 if (nSizeY == 0)
2118 nSizeY = aOriginalSize.Height();
2119 else
2120 nSizeY /= 100; // percentage
2124 if (nSizeX < 0 && nSizeY < 0 && rSize.Width != 0 && rSize.Height != 0)
2126 nSizeX = rSize.Width * std::abs(nSizeX);
2127 nSizeY = rSize.Height * std::abs(nSizeY);
2130 sal_Int32 nL = 0, nT = 0, nR = 0, nB = 0;
2131 if (GetProperty(rXPropSet, "FillBitmapRectanglePoint") && rSize.Width != 0 && rSize.Height != 0)
2133 sal_Int32 nWidth = (1 - (nSizeX / rSize.Width)) * 100000;
2134 sal_Int32 nHeight = (1 - (nSizeY / rSize.Height)) * 100000;
2136 switch (*o3tl::doAccess<RectanglePoint>(mAny))
2138 case RectanglePoint_LEFT_TOP: nR = nWidth; nB = nHeight; break;
2139 case RectanglePoint_RIGHT_TOP: nL = nWidth; nB = nHeight; break;
2140 case RectanglePoint_LEFT_BOTTOM: nR = nWidth; nT = nHeight; break;
2141 case RectanglePoint_RIGHT_BOTTOM: nL = nWidth; nT = nHeight; break;
2142 case RectanglePoint_LEFT_MIDDLE: nR = nWidth; nT = nB = nHeight / 2; break;
2143 case RectanglePoint_RIGHT_MIDDLE: nL = nWidth; nT = nB = nHeight / 2; break;
2144 case RectanglePoint_MIDDLE_TOP: nB = nHeight; nL = nR = nWidth / 2; break;
2145 case RectanglePoint_MIDDLE_BOTTOM: nT = nHeight; nL = nR = nWidth / 2; break;
2146 case RectanglePoint_MIDDLE_MIDDLE: nL = nR = nWidth / 2; nT = nB = nHeight / 2; break;
2147 default: break;
2151 mpFS->startElementNS(XML_a, XML_stretch);
2153 mpFS->singleElementNS(XML_a, XML_fillRect, XML_l,
2154 sax_fastparser::UseIf(OString::number(nL), nL != 0), XML_t,
2155 sax_fastparser::UseIf(OString::number(nT), nT != 0), XML_r,
2156 sax_fastparser::UseIf(OString::number(nR), nR != 0), XML_b,
2157 sax_fastparser::UseIf(OString::number(nB), nB != 0));
2159 mpFS->endElementNS(XML_a, XML_stretch);
2162 namespace
2164 bool IsTopGroupObj(const uno::Reference<drawing::XShape>& xShape)
2166 SdrObject* pObject = SdrObject::getSdrObjectFromXShape(xShape);
2167 if (!pObject)
2168 return false;
2170 if (pObject->getParentSdrObjectFromSdrObject())
2171 return false;
2173 return pObject->IsGroupObject();
2177 void DrawingML::WriteTransformation(const Reference< XShape >& xShape, const tools::Rectangle& rRect,
2178 sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, sal_Int32 nRotation, bool bIsGroupShape)
2181 mpFS->startElementNS( nXmlNamespace, XML_xfrm,
2182 XML_flipH, sax_fastparser::UseIf("1", bFlipH),
2183 XML_flipV, sax_fastparser::UseIf("1", bFlipV),
2184 XML_rot, sax_fastparser::UseIf(OString::number(nRotation), nRotation % 21600000 != 0));
2186 sal_Int32 nLeft = rRect.Left();
2187 sal_Int32 nTop = rRect.Top();
2188 if (GetDocumentType() == DOCUMENT_DOCX && !m_xParent.is())
2190 nLeft = 0;
2191 nTop = 0;
2193 sal_Int32 nChildLeft = nLeft;
2194 sal_Int32 nChildTop = nTop;
2196 mpFS->singleElementNS(XML_a, XML_off,
2197 XML_x, OString::number(oox::drawingml::convertHmmToEmu(nLeft)),
2198 XML_y, OString::number(oox::drawingml::convertHmmToEmu(nTop)));
2199 mpFS->singleElementNS(XML_a, XML_ext,
2200 XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())),
2201 XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight())));
2203 if (bIsGroupShape && (GetDocumentType() != DOCUMENT_DOCX || IsTopGroupObj(xShape)))
2205 mpFS->singleElementNS(XML_a, XML_chOff,
2206 XML_x, OString::number(oox::drawingml::convertHmmToEmu(nChildLeft)),
2207 XML_y, OString::number(oox::drawingml::convertHmmToEmu(nChildTop)));
2208 mpFS->singleElementNS(XML_a, XML_chExt,
2209 XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())),
2210 XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight())));
2213 mpFS->endElementNS( nXmlNamespace, XML_xfrm );
2216 void DrawingML::WriteShapeTransformation( const Reference< XShape >& rXShape, sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, bool bSuppressRotation, bool bSuppressFlipping, bool bFlippedBeforeRotation )
2218 SAL_INFO("oox.shape", "write shape transformation");
2220 Degree100 nRotation;
2221 Degree100 nCameraRotation;
2222 awt::Point aPos = rXShape->getPosition();
2223 awt::Size aSize = rXShape->getSize();
2225 bool bFlipHWrite = bFlipH && !bSuppressFlipping;
2226 bool bFlipVWrite = bFlipV && !bSuppressFlipping;
2227 bFlipH = bFlipH && !bFlippedBeforeRotation;
2228 bFlipV = bFlipV && !bFlippedBeforeRotation;
2230 if (GetDocumentType() == DOCUMENT_DOCX && m_xParent.is())
2232 awt::Point aParentPos = m_xParent->getPosition();
2233 aPos.X -= aParentPos.X;
2234 aPos.Y -= aParentPos.Y;
2237 if ( aSize.Width < 0 )
2238 aSize.Width = 1000;
2239 if ( aSize.Height < 0 )
2240 aSize.Height = 1000;
2241 if (!bSuppressRotation)
2243 SdrObject* pShape = SdrObject::getSdrObjectFromXShape(rXShape);
2244 nRotation = pShape ? pShape->GetRotateAngle() : 0_deg100;
2245 if ( GetDocumentType() != DOCUMENT_DOCX )
2247 int faccos=bFlipV ? -1 : 1;
2248 int facsin=bFlipH ? -1 : 1;
2249 aPos.X-=(1-faccos*cos(toRadians(nRotation)))*aSize.Width/2-facsin*sin(toRadians(nRotation))*aSize.Height/2;
2250 aPos.Y-=(1-faccos*cos(toRadians(nRotation)))*aSize.Height/2+facsin*sin(toRadians(nRotation))*aSize.Width/2;
2252 else if (m_xParent.is() && nRotation != 0_deg100)
2254 // Position for rotated shapes inside group is not set by DocxSdrExport.
2255 basegfx::B2DRange aRect(-aSize.Width / 2.0, -aSize.Height / 2.0, aSize.Width / 2.0,
2256 aSize.Height / 2.0);
2257 basegfx::B2DHomMatrix aRotateMatrix =
2258 basegfx::utils::createRotateB2DHomMatrix(toRadians(nRotation));
2259 aRect.transform(aRotateMatrix);
2260 aPos.X += -aSize.Width / 2.0 - aRect.getMinX();
2261 aPos.Y += -aSize.Height / 2.0 - aRect.getMinY();
2264 // The RotateAngle property's value is independent from any flipping, and that's exactly what we need here.
2265 uno::Reference<beans::XPropertySet> xPropertySet(rXShape, uno::UNO_QUERY);
2266 uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
2267 if (xPropertySetInfo->hasPropertyByName("RotateAngle"))
2269 sal_Int32 nTmp;
2270 if (xPropertySet->getPropertyValue("RotateAngle") >>= nTmp)
2271 nRotation = Degree100(nTmp);
2273 // tdf#133037: restore original rotate angle before output
2274 if (nRotation && xPropertySetInfo->hasPropertyByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG))
2276 uno::Sequence<beans::PropertyValue> aGrabBagProps;
2277 xPropertySet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= aGrabBagProps;
2278 auto p3DEffectProps = std::find_if(std::cbegin(aGrabBagProps), std::cend(aGrabBagProps),
2279 [](const PropertyValue& rProp) { return rProp.Name == "3DEffectProperties"; });
2280 if (p3DEffectProps != std::cend(aGrabBagProps))
2282 uno::Sequence<beans::PropertyValue> a3DEffectProps;
2283 p3DEffectProps->Value >>= a3DEffectProps;
2284 auto pCameraProps = std::find_if(std::cbegin(a3DEffectProps), std::cend(a3DEffectProps),
2285 [](const PropertyValue& rProp) { return rProp.Name == "Camera"; });
2286 if (pCameraProps != std::cend(a3DEffectProps))
2288 uno::Sequence<beans::PropertyValue> aCameraProps;
2289 pCameraProps->Value >>= aCameraProps;
2290 auto pZRotationProp = std::find_if(std::cbegin(aCameraProps), std::cend(aCameraProps),
2291 [](const PropertyValue& rProp) { return rProp.Name == "rotRev"; });
2292 if (pZRotationProp != std::cend(aCameraProps))
2294 sal_Int32 nTmp = 0;
2295 pZRotationProp->Value >>= nTmp;
2296 nCameraRotation = NormAngle36000(Degree100(nTmp / -600));
2303 // OOXML flips shapes before rotating them.
2304 if(bFlipH != bFlipV)
2305 nRotation = 36000_deg100 - nRotation;
2307 WriteTransformation(rXShape, tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), nXmlNamespace,
2308 bFlipHWrite, bFlipVWrite, ExportRotateClockwisify(nRotation + nCameraRotation), IsGroupShape( rXShape ));
2311 static OUString lcl_GetTarget(const css::uno::Reference<css::frame::XModel>& xModel, OUString& rURL)
2313 Reference<drawing::XDrawPagesSupplier> xDPS(xModel, uno::UNO_QUERY_THROW);
2314 Reference<drawing::XDrawPages> xDrawPages(xDPS->getDrawPages(), uno::UNO_SET_THROW);
2315 sal_uInt32 nPageCount = xDrawPages->getCount();
2316 OUString sTarget;
2318 for (sal_uInt32 i = 0; i < nPageCount; ++i)
2320 Reference<XDrawPage> xDrawPage;
2321 xDrawPages->getByIndex(i) >>= xDrawPage;
2322 Reference<container::XNamed> xNamed(xDrawPage, UNO_QUERY);
2323 if (!xNamed)
2324 continue;
2325 OUString sSlideName = "#" + xNamed->getName();
2326 if (rURL == sSlideName)
2328 sTarget = "slide" + OUString::number(i + 1) + ".xml";
2329 break;
2332 if (sTarget.isEmpty())
2334 sal_Int32 nSplit = rURL.lastIndexOf(' ');
2335 if (nSplit > -1)
2336 sTarget = OUString::Concat("slide") + rURL.subView(nSplit + 1) + ".xml";
2339 return sTarget;
2342 void DrawingML::WriteRunProperties( const Reference< XPropertySet >& rRun, bool bIsField, sal_Int32 nElement,
2343 bool bCheckDirect,bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
2344 sal_Int16 nScriptType, const Reference< XPropertySet >& rXShapePropSet)
2346 Reference< XPropertySet > rXPropSet = rRun;
2347 Reference< XPropertyState > rXPropState( rRun, UNO_QUERY );
2348 OUString usLanguage;
2349 PropertyState eState;
2350 bool bComplex = ( nScriptType == css::i18n::ScriptType::COMPLEX );
2351 const char* bold = "0";
2352 const char* italic = nullptr;
2353 const char* underline = nullptr;
2354 const char* strikeout = nullptr;
2355 const char* cap = nullptr;
2356 sal_Int32 nSize = 1800;
2357 sal_Int32 nCharEscapement = 0;
2358 sal_Int32 nCharKerning = 0;
2359 sal_Int32 nCharEscapementHeight = 0;
2361 if ( nElement == XML_endParaRPr && rbOverridingCharHeight )
2363 nSize = rnCharHeight;
2365 else if (GetProperty(rXPropSet, "CharHeight"))
2367 nSize = static_cast<sal_Int32>(100*(*o3tl::doAccess<float>(mAny)));
2368 if ( nElement == XML_rPr || nElement == XML_defRPr )
2370 rbOverridingCharHeight = true;
2371 rnCharHeight = nSize;
2375 if (GetProperty(rXPropSet, "CharKerning"))
2376 nCharKerning = static_cast<sal_Int32>(*o3tl::doAccess<sal_Int16>(mAny));
2377 /** While setting values in propertymap,
2378 * CharKerning converted using GetTextSpacingPoint
2379 * i.e set @ https://opengrok.libreoffice.org/xref/core/oox/source/drawingml/textcharacterproperties.cxx#129
2380 * therefore to get original value CharKerning need to be convert.
2381 * https://opengrok.libreoffice.org/xref/core/oox/source/drawingml/drawingmltypes.cxx#95
2383 nCharKerning = ((nCharKerning * 720)-360) / 254;
2385 if ((bComplex && GetProperty(rXPropSet, "CharWeightComplex"))
2386 || GetProperty(rXPropSet, "CharWeight"))
2388 if ( *o3tl::doAccess<float>(mAny) >= awt::FontWeight::SEMIBOLD )
2389 bold = "1";
2392 if ((bComplex && GetProperty(rXPropSet, "CharPostureComplex"))
2393 || GetProperty(rXPropSet, "CharPosture"))
2394 switch ( *o3tl::doAccess<awt::FontSlant>(mAny) )
2396 case awt::FontSlant_OBLIQUE :
2397 case awt::FontSlant_ITALIC :
2398 italic = "1";
2399 break;
2400 default:
2401 break;
2404 if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharUnderline", eState)
2405 && eState == beans::PropertyState_DIRECT_VALUE)
2406 || GetProperty(rXPropSet, "CharUnderline"))
2408 switch ( *o3tl::doAccess<sal_Int16>(mAny) )
2410 case awt::FontUnderline::SINGLE :
2411 underline = "sng";
2412 break;
2413 case awt::FontUnderline::DOUBLE :
2414 underline = "dbl";
2415 break;
2416 case awt::FontUnderline::DOTTED :
2417 underline = "dotted";
2418 break;
2419 case awt::FontUnderline::DASH :
2420 underline = "dash";
2421 break;
2422 case awt::FontUnderline::LONGDASH :
2423 underline = "dashLong";
2424 break;
2425 case awt::FontUnderline::DASHDOT :
2426 underline = "dotDash";
2427 break;
2428 case awt::FontUnderline::DASHDOTDOT :
2429 underline = "dotDotDash";
2430 break;
2431 case awt::FontUnderline::WAVE :
2432 underline = "wavy";
2433 break;
2434 case awt::FontUnderline::DOUBLEWAVE :
2435 underline = "wavyDbl";
2436 break;
2437 case awt::FontUnderline::BOLD :
2438 underline = "heavy";
2439 break;
2440 case awt::FontUnderline::BOLDDOTTED :
2441 underline = "dottedHeavy";
2442 break;
2443 case awt::FontUnderline::BOLDDASH :
2444 underline = "dashHeavy";
2445 break;
2446 case awt::FontUnderline::BOLDLONGDASH :
2447 underline = "dashLongHeavy";
2448 break;
2449 case awt::FontUnderline::BOLDDASHDOT :
2450 underline = "dotDashHeavy";
2451 break;
2452 case awt::FontUnderline::BOLDDASHDOTDOT :
2453 underline = "dotDotDashHeavy";
2454 break;
2455 case awt::FontUnderline::BOLDWAVE :
2456 underline = "wavyHeavy";
2457 break;
2461 if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharStrikeout", eState)
2462 && eState == beans::PropertyState_DIRECT_VALUE)
2463 || GetProperty(rXPropSet, "CharStrikeout"))
2465 switch ( *o3tl::doAccess<sal_Int16>(mAny) )
2467 case awt::FontStrikeout::NONE :
2468 strikeout = "noStrike";
2469 break;
2470 case awt::FontStrikeout::SINGLE :
2471 // LibO supports further values of character
2472 // strikeout, OOXML standard (20.1.10.78,
2473 // ST_TextStrikeType) however specifies only
2474 // 3 - single, double and none. Approximate
2475 // the remaining ones by single strike (better
2476 // some strike than none at all).
2477 // TODO: figure out how to do this better
2478 case awt::FontStrikeout::BOLD :
2479 case awt::FontStrikeout::SLASH :
2480 case awt::FontStrikeout::X :
2481 strikeout = "sngStrike";
2482 break;
2483 case awt::FontStrikeout::DOUBLE :
2484 strikeout = "dblStrike";
2485 break;
2489 bool bLang = false;
2490 switch(nScriptType)
2492 case css::i18n::ScriptType::ASIAN:
2493 bLang = GetProperty(rXPropSet, "CharLocaleAsian"); break;
2494 case css::i18n::ScriptType::COMPLEX:
2495 bLang = GetProperty(rXPropSet, "CharLocaleComplex"); break;
2496 default:
2497 bLang = GetProperty(rXPropSet, "CharLocale"); break;
2500 if (bLang)
2502 css::lang::Locale aLocale;
2503 mAny >>= aLocale;
2504 LanguageTag aLanguageTag( aLocale);
2505 if (!aLanguageTag.isSystemLocale())
2506 usLanguage = aLanguageTag.getBcp47MS();
2509 if (GetPropertyAndState(rXPropSet, rXPropState, "CharEscapement", eState)
2510 && eState == beans::PropertyState_DIRECT_VALUE)
2511 mAny >>= nCharEscapement;
2513 if (GetPropertyAndState(rXPropSet, rXPropState, "CharEscapementHeight", eState)
2514 && eState == beans::PropertyState_DIRECT_VALUE)
2515 mAny >>= nCharEscapementHeight;
2517 if (DFLT_ESC_AUTO_SUPER == nCharEscapement)
2519 // Raised by the differences between the ascenders (ascent = baseline to top of highest letter).
2520 // The ascent is generally about 80% of the total font height.
2521 // That is why DFLT_ESC_PROP (58) leads to 33% (DFLT_ESC_SUPER)
2522 nCharEscapement = .8 * (100 - nCharEscapementHeight);
2524 else if (DFLT_ESC_AUTO_SUB == nCharEscapement)
2526 // Lowered by the differences between the descenders (descent = baseline to bottom of lowest letter).
2527 // The descent is generally about 20% of the total font height.
2528 // That is why DFLT_ESC_PROP (58) leads to 8% (DFLT_ESC_SUB)
2529 nCharEscapement = .2 * -(100 - nCharEscapementHeight);
2532 if (nCharEscapement && nCharEscapementHeight)
2534 nSize = (nSize * nCharEscapementHeight) / 100;
2535 // MSO uses default ~58% size
2536 nSize = (nSize / 0.58);
2539 if (GetProperty(rXPropSet, "CharCaseMap"))
2541 switch ( *o3tl::doAccess<sal_Int16>(mAny) )
2543 case CaseMap::UPPERCASE :
2544 cap = "all";
2545 break;
2546 case CaseMap::SMALLCAPS :
2547 cap = "small";
2548 break;
2552 mpFS->startElementNS( XML_a, nElement,
2553 XML_b, bold,
2554 XML_i, italic,
2555 XML_lang, sax_fastparser::UseIf(usLanguage, !usLanguage.isEmpty()),
2556 XML_sz, OString::number(nSize),
2557 // For Condensed character spacing spc value is negative.
2558 XML_spc, sax_fastparser::UseIf(OString::number(nCharKerning), nCharKerning != 0),
2559 XML_strike, strikeout,
2560 XML_u, underline,
2561 XML_baseline, sax_fastparser::UseIf(OString::number(nCharEscapement*1000), nCharEscapement != 0),
2562 XML_cap, cap );
2564 // Fontwork-shapes in LO have text outline and fill from shape stroke and shape fill
2565 // PowerPoint has this as run properties
2566 if (IsFontworkShape(rXShapePropSet))
2568 WriteOutline(rXShapePropSet);
2569 WriteBlipOrNormalFill(rXShapePropSet, "Graphic");
2570 WriteShapeEffects(rXShapePropSet);
2572 else
2574 // mso doesn't like text color to be placed after typeface
2575 if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharColor", eState)
2576 && eState == beans::PropertyState_DIRECT_VALUE)
2577 || GetProperty(rXPropSet, "CharColor"))
2579 ::Color color( ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny) );
2580 SAL_INFO("oox.shape", "run color: " << sal_uInt32(color) << " auto: " << sal_uInt32(COL_AUTO));
2582 // WriteSolidFill() handles MAX_PERCENT as "no transparency".
2583 sal_Int32 nTransparency = MAX_PERCENT;
2584 if (rXPropSet->getPropertySetInfo()->hasPropertyByName("CharTransparence"))
2586 rXPropSet->getPropertyValue("CharTransparence") >>= nTransparency;
2587 // UNO scale is 0..100, OOXML scale is 0..100000; also UNO tracks transparency, OOXML
2588 // tracks opacity.
2589 nTransparency = MAX_PERCENT - (nTransparency * PER_PERCENT);
2592 bool bContoured = false;
2593 if (GetProperty(rXPropSet, "CharContoured"))
2594 bContoured = *o3tl::doAccess<bool>(mAny);
2596 // tdf#127696 If the CharContoured is true, then the text color is white and the outline color is the CharColor.
2597 if (bContoured)
2599 mpFS->startElementNS(XML_a, XML_ln);
2600 if (color == COL_AUTO)
2602 mbIsBackgroundDark ? WriteSolidFill(COL_WHITE) : WriteSolidFill(COL_BLACK);
2604 else
2606 color.SetAlpha(255);
2607 if (!WriteSchemeColor(u"CharComplexColor", rXPropSet))
2608 WriteSolidFill(color, nTransparency);
2610 mpFS->endElementNS(XML_a, XML_ln);
2612 WriteSolidFill(COL_WHITE);
2614 // tdf#104219 In LibreOffice and MS Office, there are two types of colors:
2615 // Automatic and Fixed. OOXML is setting automatic color, by not providing color.
2616 else if( color != COL_AUTO )
2618 color.SetAlpha(255);
2619 // TODO: special handle embossed/engraved
2620 if (!WriteSchemeColor(u"CharComplexColor", rXPropSet))
2622 WriteSolidFill(color, nTransparency);
2625 else if (GetDocumentType() == DOCUMENT_PPTX)
2627 // Resolve COL_AUTO for PPTX since MS Powerpoint doesn't have automatic colors.
2628 bool bIsTextBackgroundDark = mbIsBackgroundDark;
2629 if (rXShapePropSet.is() && GetProperty(rXShapePropSet, "FillStyle")
2630 && mAny.get<FillStyle>() != FillStyle_NONE
2631 && GetProperty(rXShapePropSet, "FillColor"))
2633 ::Color aShapeFillColor(ColorTransparency, mAny.get<sal_uInt32>());
2634 bIsTextBackgroundDark = aShapeFillColor.IsDark();
2637 if (bIsTextBackgroundDark)
2638 WriteSolidFill(COL_WHITE);
2639 else
2640 WriteSolidFill(COL_BLACK);
2645 // tdf#128096, exporting XML_highlight to docx already works fine,
2646 // so make sure this code is only run when exporting to pptx, just in case
2647 if (GetDocumentType() == DOCUMENT_PPTX)
2649 if (GetProperty(rXPropSet, "CharBackColor"))
2651 ::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny));
2652 if( color != COL_AUTO )
2654 mpFS->startElementNS(XML_a, XML_highlight);
2655 WriteColor( color );
2656 mpFS->endElementNS( XML_a, XML_highlight );
2661 if (underline
2662 && ((bCheckDirect
2663 && GetPropertyAndState(rXPropSet, rXPropState, "CharUnderlineColor", eState)
2664 && eState == beans::PropertyState_DIRECT_VALUE)
2665 || GetProperty(rXPropSet, "CharUnderlineColor")))
2667 ::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny));
2668 // if color is automatic, then we shouldn't write information about color but to take color from character
2669 if( color != COL_AUTO )
2671 mpFS->startElementNS(XML_a, XML_uFill);
2672 WriteSolidFill( color );
2673 mpFS->endElementNS( XML_a, XML_uFill );
2675 else
2677 mpFS->singleElementNS(XML_a, XML_uFillTx);
2681 if (GetProperty(rXPropSet, "CharFontName"))
2683 const char* const pitch = nullptr;
2684 const char* const charset = nullptr;
2685 OUString usTypeface;
2687 mAny >>= usTypeface;
2688 OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) );
2689 if (!aSubstName.isEmpty())
2690 usTypeface = aSubstName;
2692 mpFS->singleElementNS( XML_a, XML_latin,
2693 XML_typeface, usTypeface,
2694 XML_pitchFamily, pitch,
2695 XML_charset, charset );
2698 if ((bComplex
2699 && (GetPropertyAndState(rXPropSet, rXPropState, "CharFontNameComplex", eState)
2700 && eState == beans::PropertyState_DIRECT_VALUE))
2701 || (!bComplex
2702 && (GetPropertyAndState(rXPropSet, rXPropState, "CharFontNameAsian", eState)
2703 && eState == beans::PropertyState_DIRECT_VALUE)))
2705 const char* const pitch = nullptr;
2706 const char* const charset = nullptr;
2707 OUString usTypeface;
2709 mAny >>= usTypeface;
2710 OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) );
2711 if (!aSubstName.isEmpty())
2712 usTypeface = aSubstName;
2714 mpFS->singleElementNS( XML_a, bComplex ? XML_cs : XML_ea,
2715 XML_typeface, usTypeface,
2716 XML_pitchFamily, pitch,
2717 XML_charset, charset );
2720 if( bIsField )
2722 Reference< XTextField > rXTextField;
2723 if (GetProperty(rXPropSet, "TextField"))
2724 mAny >>= rXTextField;
2725 if( rXTextField.is() )
2726 rXPropSet.set( rXTextField, UNO_QUERY );
2729 // field properties starts here
2730 if (GetProperty(rXPropSet, "URL"))
2732 OUString sURL;
2734 mAny >>= sURL;
2735 if (!sURL.isEmpty())
2737 if (!sURL.match("#action?jump="))
2739 bool bExtURL = URLTransformer().isExternalURL(sURL);
2740 sURL = bExtURL ? sURL : lcl_GetTarget(GetFB()->getModel(), sURL);
2742 OUString sRelId
2743 = mpFB->addRelation(mpFS->getOutputStream(),
2744 bExtURL ? oox::getRelationship(Relationship::HYPERLINK)
2745 : oox::getRelationship(Relationship::SLIDE),
2746 sURL, bExtURL);
2748 if (bExtURL)
2749 mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
2750 else
2751 mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId,
2752 XML_action, "ppaction://hlinksldjump");
2754 else
2756 sal_Int32 nIndex = sURL.indexOf('=');
2757 std::u16string_view aDestination(sURL.subView(nIndex + 1));
2758 mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action,
2759 OUString::Concat("ppaction://hlinkshowjump?jump=") + aDestination);
2763 mpFS->endElementNS( XML_a, nElement );
2766 OUString DrawingML::GetFieldValue( const css::uno::Reference< css::text::XTextRange >& rRun, bool& bIsURLField )
2768 Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY );
2769 OUString aFieldType, aFieldValue;
2771 if (GetProperty(rXPropSet, "TextPortionType"))
2773 aFieldType = *o3tl::doAccess<OUString>(mAny);
2774 SAL_INFO("oox.shape", "field type: " << aFieldType);
2777 if( aFieldType == "TextField" )
2779 Reference< XTextField > rXTextField;
2780 if (GetProperty(rXPropSet, "TextField"))
2781 mAny >>= rXTextField;
2782 if( rXTextField.is() )
2784 rXPropSet.set( rXTextField, UNO_QUERY );
2785 if( rXPropSet.is() )
2787 OUString aFieldKind( rXTextField->getPresentation( true ) );
2788 SAL_INFO("oox.shape", "field kind: " << aFieldKind);
2789 if( aFieldKind == "Page" )
2791 aFieldValue = "slidenum";
2793 else if( aFieldKind == "Pages" )
2795 aFieldValue = "slidecount";
2797 else if( aFieldKind == "PageName" )
2799 aFieldValue = "slidename";
2801 else if( aFieldKind == "URL" )
2803 bIsURLField = true;
2804 if (GetProperty(rXPropSet, "Representation"))
2805 mAny >>= aFieldValue;
2808 else if(aFieldKind == "Date")
2810 sal_Int32 nNumFmt = -1;
2811 rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt;
2812 aFieldValue = GetDatetimeTypeFromDate(static_cast<SvxDateFormat>(nNumFmt));
2814 else if(aFieldKind == "ExtTime")
2816 sal_Int32 nNumFmt = -1;
2817 rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt;
2818 aFieldValue = GetDatetimeTypeFromTime(static_cast<SvxTimeFormat>(nNumFmt));
2820 else if(aFieldKind == "ExtFile")
2822 sal_Int32 nNumFmt = -1;
2823 rXPropSet->getPropertyValue(UNO_TC_PROP_FILE_FORMAT) >>= nNumFmt;
2824 switch(nNumFmt)
2826 case 0: aFieldValue = "file"; // Path/File name
2827 break;
2828 case 1: aFieldValue = "file1"; // Path
2829 break;
2830 case 2: aFieldValue = "file2"; // File name without extension
2831 break;
2832 case 3: aFieldValue = "file3"; // File name with extension
2835 else if(aFieldKind == "Author")
2837 aFieldValue = "author";
2842 return aFieldValue;
2845 OUString DrawingML::GetDatetimeTypeFromDate(SvxDateFormat eDate)
2847 return GetDatetimeTypeFromDateTime(eDate, SvxTimeFormat::AppDefault);
2850 OUString DrawingML::GetDatetimeTypeFromTime(SvxTimeFormat eTime)
2852 return GetDatetimeTypeFromDateTime(SvxDateFormat::AppDefault, eTime);
2855 OUString DrawingML::GetDatetimeTypeFromDateTime(SvxDateFormat eDate, SvxTimeFormat eTime)
2857 OUString aDateField;
2858 switch (eDate)
2860 case SvxDateFormat::StdSmall:
2861 case SvxDateFormat::A:
2862 aDateField = "datetime";
2863 break;
2864 case SvxDateFormat::B:
2865 aDateField = "datetime1"; // 13/02/1996
2866 break;
2867 case SvxDateFormat::C:
2868 aDateField = "datetime5";
2869 break;
2870 case SvxDateFormat::D:
2871 aDateField = "datetime3"; // 13 February 1996
2872 break;
2873 case SvxDateFormat::StdBig:
2874 case SvxDateFormat::E:
2875 case SvxDateFormat::F:
2876 aDateField = "datetime2";
2877 break;
2878 default:
2879 break;
2882 OUString aTimeField;
2883 switch (eTime)
2885 case SvxTimeFormat::Standard:
2886 case SvxTimeFormat::HH24_MM_SS:
2887 case SvxTimeFormat::HH24_MM_SS_00:
2888 aTimeField = "datetime11"; // 13:49:38
2889 break;
2890 case SvxTimeFormat::HH24_MM:
2891 aTimeField = "datetime10"; // 13:49
2892 break;
2893 case SvxTimeFormat::HH12_MM:
2894 case SvxTimeFormat::HH12_MM_AMPM:
2895 aTimeField = "datetime12"; // 01:49 PM
2896 break;
2897 case SvxTimeFormat::HH12_MM_SS:
2898 case SvxTimeFormat::HH12_MM_SS_AMPM:
2899 case SvxTimeFormat::HH12_MM_SS_00:
2900 case SvxTimeFormat::HH12_MM_SS_00_AMPM:
2901 aTimeField = "datetime13"; // 01:49:38 PM
2902 break;
2903 default:
2904 break;
2907 if (!aDateField.isEmpty() && aTimeField.isEmpty())
2908 return aDateField;
2909 else if (!aTimeField.isEmpty() && aDateField.isEmpty())
2910 return aTimeField;
2911 else if (!aDateField.isEmpty() && !aTimeField.isEmpty())
2913 if (aTimeField == "datetime11" || aTimeField == "datetime13")
2914 // only datetime format that has Date and HH:MM:SS
2915 return "datetime9"; // dd/mm/yyyy H:MM:SS
2916 else
2917 // only datetime format that has Date and HH:MM
2918 return "datetime8"; // dd/mm/yyyy H:MM
2920 else
2921 return "";
2924 void DrawingML::WriteRun( const Reference< XTextRange >& rRun,
2925 bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
2926 const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet)
2928 Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY );
2929 sal_Int16 nLevel = -1;
2930 if (GetProperty(rXPropSet, "NumberingLevel"))
2931 mAny >>= nLevel;
2933 bool bNumberingIsNumber = true;
2934 if (GetProperty(rXPropSet, "NumberingIsNumber"))
2935 mAny >>= bNumberingIsNumber;
2937 float nFontSize = -1;
2938 if (GetProperty(rXPropSet, "CharHeight"))
2939 mAny >>= nFontSize;
2941 bool bIsURLField = false;
2942 OUString sFieldValue = GetFieldValue( rRun, bIsURLField );
2943 bool bWriteField = !( sFieldValue.isEmpty() || bIsURLField );
2945 OUString sText = rRun->getString();
2947 //if there is no text following the bullet, add a space after the bullet
2948 if (nLevel !=-1 && bNumberingIsNumber && sText.isEmpty() )
2949 sText=" ";
2951 if ( bIsURLField )
2952 sText = sFieldValue;
2954 if( sText.isEmpty())
2956 Reference< XPropertySet > xPropSet( rRun, UNO_QUERY );
2960 if( !xPropSet.is() || !( xPropSet->getPropertyValue( "PlaceholderText" ) >>= sText ) )
2961 return;
2962 if( sText.isEmpty() )
2963 return;
2965 catch (const Exception &)
2967 return;
2971 if (sText == "\n")
2973 // Empty run? Do not forget to write the font size in case of pptx:
2974 if ((GetDocumentType() == DOCUMENT_PPTX) && (nFontSize != -1))
2976 mpFS->startElementNS(XML_a, XML_br);
2977 mpFS->singleElementNS(XML_a, XML_rPr, XML_sz,
2978 OString::number(nFontSize * 100));
2979 mpFS->endElementNS(XML_a, XML_br);
2981 else
2982 mpFS->singleElementNS(XML_a, XML_br);
2984 else
2986 if( bWriteField )
2988 OString sUUID(comphelper::xml::generateGUIDString());
2989 mpFS->startElementNS( XML_a, XML_fld,
2990 XML_id, sUUID.getStr(),
2991 XML_type, sFieldValue );
2993 else
2995 mpFS->startElementNS(XML_a, XML_r);
2998 Reference< XPropertySet > xPropSet( rRun, uno::UNO_QUERY );
3000 WriteRunProperties( xPropSet, bIsURLField, XML_rPr, true, rbOverridingCharHeight, rnCharHeight, GetScriptType(sText), rXShapePropSet);
3001 mpFS->startElementNS(XML_a, XML_t);
3002 mpFS->writeEscaped( sText );
3003 mpFS->endElementNS( XML_a, XML_t );
3005 if( bWriteField )
3006 mpFS->endElementNS( XML_a, XML_fld );
3007 else
3008 mpFS->endElementNS( XML_a, XML_r );
3012 static OUString GetAutoNumType(SvxNumType nNumberingType, bool bSDot, bool bPBehind, bool bPBoth)
3014 OUString sPrefixSuffix;
3016 if (bPBoth)
3017 sPrefixSuffix = "ParenBoth";
3018 else if (bPBehind)
3019 sPrefixSuffix = "ParenR";
3020 else if (bSDot)
3021 sPrefixSuffix = "Period";
3023 switch( nNumberingType )
3025 case SVX_NUM_CHARS_UPPER_LETTER_N :
3026 case SVX_NUM_CHARS_UPPER_LETTER :
3027 return "alphaUc" + sPrefixSuffix;
3029 case SVX_NUM_CHARS_LOWER_LETTER_N :
3030 case SVX_NUM_CHARS_LOWER_LETTER :
3031 return "alphaLc" + sPrefixSuffix;
3033 case SVX_NUM_ROMAN_UPPER :
3034 return "romanUc" + sPrefixSuffix;
3036 case SVX_NUM_ROMAN_LOWER :
3037 return "romanLc" + sPrefixSuffix;
3039 case SVX_NUM_ARABIC :
3041 if (sPrefixSuffix.isEmpty())
3042 return "arabicPlain";
3043 else
3044 return "arabic" + sPrefixSuffix;
3046 default:
3047 break;
3050 return OUString();
3053 void DrawingML::WriteParagraphNumbering(const Reference< XPropertySet >& rXPropSet, float fFirstCharHeight, sal_Int16 nLevel )
3055 if (nLevel < 0 || !GetProperty(rXPropSet, "NumberingRules"))
3056 return;
3058 Reference< XIndexAccess > rXIndexAccess;
3060 if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount())
3061 return;
3063 SAL_INFO("oox.shape", "numbering rules");
3065 Sequence<PropertyValue> aPropertySequence;
3066 rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence;
3068 if (!aPropertySequence.hasElements())
3069 return;
3071 SvxNumType nNumberingType = SVX_NUM_NUMBER_NONE;
3072 bool bSDot = false;
3073 bool bPBehind = false;
3074 bool bPBoth = false;
3075 sal_Unicode aBulletChar = 0x2022; // a bullet
3076 awt::FontDescriptor aFontDesc;
3077 bool bHasFontDesc = false;
3078 uno::Reference<graphic::XGraphic> xGraphic;
3079 sal_Int16 nBulletRelSize = 0;
3080 sal_Int16 nStartWith = 1;
3081 ::Color nBulletColor;
3082 bool bHasBulletColor = false;
3083 awt::Size aGraphicSize;
3085 for ( const PropertyValue& rPropValue : std::as_const(aPropertySequence) )
3087 OUString aPropName( rPropValue.Name );
3088 SAL_INFO("oox.shape", "pro name: " << aPropName);
3089 if ( aPropName == "NumberingType" )
3091 nNumberingType = static_cast<SvxNumType>(*o3tl::doAccess<sal_Int16>(rPropValue.Value));
3093 else if ( aPropName == "Prefix" )
3095 if( *o3tl::doAccess<OUString>(rPropValue.Value) == ")")
3096 bPBoth = true;
3098 else if ( aPropName == "Suffix" )
3100 auto s = o3tl::doAccess<OUString>(rPropValue.Value);
3101 if( *s == ".")
3102 bSDot = true;
3103 else if( *s == ")")
3104 bPBehind = true;
3106 else if(aPropName == "BulletColor")
3108 nBulletColor = ::Color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(rPropValue.Value));
3109 bHasBulletColor = true;
3111 else if ( aPropName == "BulletChar" )
3113 aBulletChar = (*o3tl::doAccess<OUString>(rPropValue.Value))[ 0 ];
3115 else if ( aPropName == "BulletFont" )
3117 aFontDesc = *o3tl::doAccess<awt::FontDescriptor>(rPropValue.Value);
3118 bHasFontDesc = true;
3120 // Our numbullet dialog has set the wrong textencoding for our "StarSymbol" font,
3121 // instead of a Unicode encoding the encoding RTL_TEXTENCODING_SYMBOL was used.
3122 // Because there might exist a lot of damaged documents I added this two lines
3123 // which fixes the bullet problem for the export.
3124 if ( aFontDesc.Name.equalsIgnoreAsciiCase("StarSymbol") )
3125 aFontDesc.CharSet = RTL_TEXTENCODING_MS_1252;
3128 else if ( aPropName == "BulletRelSize" )
3130 nBulletRelSize = *o3tl::doAccess<sal_Int16>(rPropValue.Value);
3132 else if ( aPropName == "StartWith" )
3134 nStartWith = *o3tl::doAccess<sal_Int16>(rPropValue.Value);
3136 else if (aPropName == "GraphicBitmap")
3138 auto xBitmap = rPropValue.Value.get<uno::Reference<awt::XBitmap>>();
3139 xGraphic.set(xBitmap, uno::UNO_QUERY);
3141 else if ( aPropName == "GraphicSize" )
3143 aGraphicSize = *o3tl::doAccess<awt::Size>(rPropValue.Value);
3144 SAL_INFO("oox.shape", "graphic size: " << aGraphicSize.Width << "x" << aGraphicSize.Height);
3148 if (nNumberingType == SVX_NUM_NUMBER_NONE)
3149 return;
3151 Graphic aGraphic(xGraphic);
3152 if (xGraphic.is() && aGraphic.GetType() != GraphicType::NONE)
3154 tools::Long nFirstCharHeightMm = TransformMetric(fFirstCharHeight * 100.f, FieldUnit::POINT, FieldUnit::MM);
3155 float fBulletSizeRel = aGraphicSize.Height / static_cast<float>(nFirstCharHeightMm) / OOX_BULLET_LIST_SCALE_FACTOR;
3157 OUString sRelationId;
3159 if (fBulletSizeRel < 1.0f)
3161 // Add padding to get the bullet point centered in PPT
3162 Size aDestSize(64, 64);
3163 float fBulletSizeRelX = fBulletSizeRel / aGraphicSize.Height * aGraphicSize.Width;
3164 tools::Long nPaddingX = std::max<tools::Long>(0, std::lround((aDestSize.Width() - fBulletSizeRelX * aDestSize.Width()) / 2.f));
3165 tools::Long nPaddingY = std::lround((aDestSize.Height() - fBulletSizeRel * aDestSize.Height()) / 2.f);
3166 tools::Rectangle aDestRect(nPaddingX, nPaddingY, aDestSize.Width() - nPaddingX, aDestSize.Height() - nPaddingY);
3168 AlphaMask aMask(aDestSize);
3169 aMask.Erase(255);
3170 BitmapEx aSourceBitmap(aGraphic.GetBitmapEx());
3171 aSourceBitmap.Scale(aDestRect.GetSize());
3172 tools::Rectangle aSourceRect(Point(0, 0), aDestRect.GetSize());
3173 BitmapEx aDestBitmap(Bitmap(aDestSize, vcl::PixelFormat::N24_BPP), aMask);
3174 aDestBitmap.CopyPixel(aDestRect, aSourceRect, &aSourceBitmap);
3175 Graphic aDestGraphic(aDestBitmap);
3176 sRelationId = WriteImage(aDestGraphic);
3177 fBulletSizeRel = 1.0f;
3179 else
3181 sRelationId = WriteImage(aGraphic);
3184 mpFS->singleElementNS( XML_a, XML_buSzPct,
3185 XML_val, OString::number(std::min<sal_Int32>(std::lround(100000.f * fBulletSizeRel), 400000)));
3186 mpFS->startElementNS(XML_a, XML_buBlip);
3187 mpFS->singleElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelationId);
3188 mpFS->endElementNS( XML_a, XML_buBlip );
3190 else
3192 if(bHasBulletColor)
3194 if (nBulletColor == COL_AUTO )
3196 nBulletColor = ::Color(ColorTransparency, mbIsBackgroundDark ? 0xffffff : 0x000000);
3198 mpFS->startElementNS(XML_a, XML_buClr);
3199 WriteColor( nBulletColor );
3200 mpFS->endElementNS( XML_a, XML_buClr );
3203 if( nBulletRelSize && nBulletRelSize != 100 )
3204 mpFS->singleElementNS( XML_a, XML_buSzPct,
3205 XML_val, OString::number(std::clamp<sal_Int32>(1000*nBulletRelSize, 25000, 400000)));
3206 if( bHasFontDesc )
3208 if ( SVX_NUM_CHAR_SPECIAL == nNumberingType )
3209 aBulletChar = SubstituteBullet( aBulletChar, aFontDesc );
3210 mpFS->singleElementNS( XML_a, XML_buFont,
3211 XML_typeface, aFontDesc.Name,
3212 XML_charset, sax_fastparser::UseIf("2", aFontDesc.CharSet == awt::CharSet::SYMBOL));
3215 OUString aAutoNumType = GetAutoNumType( nNumberingType, bSDot, bPBehind, bPBoth );
3217 if (!aAutoNumType.isEmpty())
3219 mpFS->singleElementNS(XML_a, XML_buAutoNum,
3220 XML_type, aAutoNumType,
3221 XML_startAt, sax_fastparser::UseIf(OString::number(nStartWith), nStartWith > 1));
3223 else
3225 mpFS->singleElementNS(XML_a, XML_buChar, XML_char, OUString(aBulletChar));
3230 void DrawingML::WriteParagraphTabStops(const Reference<XPropertySet>& rXPropSet)
3232 css::uno::Sequence<css::style::TabStop> aTabStops;
3233 if (GetProperty(rXPropSet, "ParaTabStops"))
3234 aTabStops = *o3tl::doAccess<css::uno::Sequence<css::style::TabStop>>(mAny);
3236 if (aTabStops.getLength() > 0)
3237 mpFS->startElementNS(XML_a, XML_tabLst);
3239 for (const css::style::TabStop& rTabStop : std::as_const(aTabStops))
3241 OString sPosition = OString::number(GetPointFromCoordinate(rTabStop.Position));
3242 OString sAlignment;
3243 switch (rTabStop.Alignment)
3245 case css::style::TabAlign_DECIMAL:
3246 sAlignment = "dec";
3247 break;
3248 case css::style::TabAlign_RIGHT:
3249 sAlignment = "r";
3250 break;
3251 case css::style::TabAlign_CENTER:
3252 sAlignment = "ctr";
3253 break;
3254 case css::style::TabAlign_LEFT:
3255 default:
3256 sAlignment = "l";
3258 mpFS->singleElementNS(XML_a, XML_tab, XML_algn, sAlignment, XML_pos, sPosition);
3260 if (aTabStops.getLength() > 0)
3261 mpFS->endElementNS(XML_a, XML_tabLst);
3264 bool DrawingML::IsGroupShape( const Reference< XShape >& rXShape )
3266 bool bRet = false;
3267 if ( rXShape.is() )
3269 uno::Reference<lang::XServiceInfo> xServiceInfo(rXShape, uno::UNO_QUERY_THROW);
3270 bRet = xServiceInfo->supportsService("com.sun.star.drawing.GroupShape");
3272 return bRet;
3275 sal_Int32 DrawingML::getBulletMarginIndentation (const Reference< XPropertySet >& rXPropSet,sal_Int16 nLevel, std::u16string_view propName)
3277 if (nLevel < 0 || !GetProperty(rXPropSet, "NumberingRules"))
3278 return 0;
3280 Reference< XIndexAccess > rXIndexAccess;
3282 if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount())
3283 return 0;
3285 SAL_INFO("oox.shape", "numbering rules");
3287 Sequence<PropertyValue> aPropertySequence;
3288 rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence;
3290 if (!aPropertySequence.hasElements())
3291 return 0;
3293 for ( const PropertyValue& rPropValue : std::as_const(aPropertySequence) )
3295 OUString aPropName( rPropValue.Name );
3296 SAL_INFO("oox.shape", "pro name: " << aPropName);
3297 if ( aPropName == propName )
3298 return *o3tl::doAccess<sal_Int32>(rPropValue.Value);
3301 return 0;
3304 const char* DrawingML::GetAlignment( style::ParagraphAdjust nAlignment )
3306 const char* sAlignment = nullptr;
3308 switch( nAlignment )
3310 case style::ParagraphAdjust_CENTER:
3311 sAlignment = "ctr";
3312 break;
3313 case style::ParagraphAdjust_RIGHT:
3314 sAlignment = "r";
3315 break;
3316 case style::ParagraphAdjust_BLOCK:
3317 sAlignment = "just";
3318 break;
3319 default:
3323 return sAlignment;
3326 void DrawingML::WriteLinespacing(const LineSpacing& rSpacing, float fFirstCharHeight)
3328 if( rSpacing.Mode == LineSpacingMode::PROP )
3330 mpFS->singleElementNS( XML_a, XML_spcPct,
3331 XML_val, OString::number(static_cast<sal_Int32>(rSpacing.Height)*1000));
3333 else if (rSpacing.Mode == LineSpacingMode::MINIMUM
3334 && fFirstCharHeight > static_cast<float>(rSpacing.Height) * 0.001 * 72.0 / 2.54)
3336 // 100% proportional line spacing = single line spacing
3337 mpFS->singleElementNS(XML_a, XML_spcPct, XML_val,
3338 OString::number(static_cast<sal_Int32>(100000)));
3340 else
3342 mpFS->singleElementNS( XML_a, XML_spcPts,
3343 XML_val, OString::number(std::lround(rSpacing.Height / 25.4 * 72)));
3347 bool DrawingML::WriteParagraphProperties(const Reference<XTextContent>& rParagraph, float fFirstCharHeight, sal_Int32 nElement)
3349 Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY );
3350 Reference< XPropertyState > rXPropState( rParagraph, UNO_QUERY );
3351 PropertyState eState;
3353 if( !rXPropSet.is() || !rXPropState.is() )
3354 return false;
3356 sal_Int16 nLevel = -1;
3357 if (GetProperty(rXPropSet, "NumberingLevel"))
3358 mAny >>= nLevel;
3360 bool bWriteNumbering = true;
3361 bool bForceZeroIndent = false;
3362 if (mbPlaceholder)
3364 Reference< text::XTextRange > xParaText(rParagraph, UNO_QUERY);
3365 if (xParaText)
3367 bool bNumberingOnThisLevel = false;
3368 if (nLevel > -1)
3370 Reference< XIndexAccess > xNumberingRules(rXPropSet->getPropertyValue("NumberingRules"), UNO_QUERY);
3371 const PropertyValues& rNumRuleOfLevel = xNumberingRules->getByIndex(nLevel).get<PropertyValues>();
3372 for (const PropertyValue& rRule : rNumRuleOfLevel)
3373 if (rRule.Name == "NumberingType" && rRule.Value.hasValue())
3374 bNumberingOnThisLevel = rRule.Value.get<sal_uInt16>() != style::NumberingType::NUMBER_NONE;
3377 const bool bIsNumberingVisible = rXPropSet->getPropertyValue("NumberingIsNumber").get<bool>();
3378 const bool bIsLineEmpty = !xParaText->getString().getLength();
3380 bWriteNumbering = !bIsLineEmpty && bIsNumberingVisible && (nLevel != -1);
3381 bForceZeroIndent = (!bIsNumberingVisible || bIsLineEmpty || !bNumberingOnThisLevel);
3386 sal_Int16 nTmp = sal_Int16(style::ParagraphAdjust_LEFT);
3387 if (GetProperty(rXPropSet, "ParaAdjust"))
3388 mAny >>= nTmp;
3389 style::ParagraphAdjust nAlignment = static_cast<style::ParagraphAdjust>(nTmp);
3391 bool bHasLinespacing = false;
3392 LineSpacing aLineSpacing;
3393 if (GetPropertyAndState(rXPropSet, rXPropState, "ParaLineSpacing", eState)
3394 && (mAny >>= aLineSpacing)
3395 && (eState == beans::PropertyState_DIRECT_VALUE ||
3396 // only export if it differs from the default 100% line spacing
3397 aLineSpacing.Mode != LineSpacingMode::PROP || aLineSpacing.Height != 100))
3398 bHasLinespacing = true;
3400 bool bRtl = false;
3401 if (GetProperty(rXPropSet, "WritingMode"))
3403 sal_Int16 nWritingMode;
3404 if( ( mAny >>= nWritingMode ) && nWritingMode == text::WritingMode2::RL_TB )
3406 bRtl = true;
3410 sal_Int32 nParaLeftMargin = 0;
3411 sal_Int32 nParaFirstLineIndent = 0;
3413 if (GetProperty(rXPropSet, "ParaLeftMargin"))
3414 mAny >>= nParaLeftMargin;
3415 if (GetProperty(rXPropSet, "ParaFirstLineIndent"))
3416 mAny >>= nParaFirstLineIndent;
3418 sal_Int32 nParaTopMargin = 0;
3419 sal_Int32 nParaBottomMargin = 0;
3421 if (GetProperty(rXPropSet, "ParaTopMargin"))
3422 mAny >>= nParaTopMargin;
3423 if (GetProperty(rXPropSet, "ParaBottomMargin"))
3424 mAny >>= nParaBottomMargin;
3426 sal_Int32 nLeftMargin = getBulletMarginIndentation ( rXPropSet, nLevel,u"LeftMargin");
3427 sal_Int32 nLineIndentation = getBulletMarginIndentation ( rXPropSet, nLevel,u"FirstLineOffset");
3429 if (bWriteNumbering && !bForceZeroIndent)
3431 if (!(nLevel != -1
3432 || nAlignment != style::ParagraphAdjust_LEFT
3433 || bHasLinespacing))
3434 return false;
3437 sal_Int32 nParaDefaultTabSize = 0;
3438 if (GetProperty(rXPropSet, "ParaTabStopDefaultDistance"))
3439 mAny >>= nParaDefaultTabSize;
3441 if (nParaLeftMargin) // For Paragraph
3442 mpFS->startElementNS( XML_a, nElement,
3443 XML_lvl, sax_fastparser::UseIf(OString::number(nLevel), nLevel > 0),
3444 XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaLeftMargin)), nParaLeftMargin > 0),
3445 XML_indent, sax_fastparser::UseIf(OString::number((bForceZeroIndent && nParaFirstLineIndent == 0) ? 0 : oox::drawingml::convertHmmToEmu(nParaFirstLineIndent)), (bForceZeroIndent || nParaFirstLineIndent != 0)),
3446 XML_algn, GetAlignment( nAlignment ),
3447 XML_defTabSz, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaDefaultTabSize)), nParaDefaultTabSize > 0),
3448 XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl));
3449 else
3450 mpFS->startElementNS( XML_a, nElement,
3451 XML_lvl, sax_fastparser::UseIf(OString::number(nLevel), nLevel > 0),
3452 XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeftMargin)), nLeftMargin > 0),
3453 XML_indent, sax_fastparser::UseIf(OString::number(!bForceZeroIndent ? oox::drawingml::convertHmmToEmu(nLineIndentation) : 0), (bForceZeroIndent || ( nLineIndentation != 0))),
3454 XML_algn, GetAlignment( nAlignment ),
3455 XML_defTabSz, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaDefaultTabSize)), nParaDefaultTabSize > 0),
3456 XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl));
3459 if( bHasLinespacing )
3461 mpFS->startElementNS(XML_a, XML_lnSpc);
3462 WriteLinespacing(aLineSpacing, fFirstCharHeight);
3463 mpFS->endElementNS( XML_a, XML_lnSpc );
3466 if( nParaTopMargin != 0 )
3468 mpFS->startElementNS(XML_a, XML_spcBef);
3470 mpFS->singleElementNS( XML_a, XML_spcPts,
3471 XML_val, OString::number(std::lround(nParaTopMargin / 25.4 * 72)));
3473 mpFS->endElementNS( XML_a, XML_spcBef );
3476 if( nParaBottomMargin != 0 )
3478 mpFS->startElementNS(XML_a, XML_spcAft);
3480 mpFS->singleElementNS( XML_a, XML_spcPts,
3481 XML_val, OString::number(std::lround(nParaBottomMargin / 25.4 * 72)));
3483 mpFS->endElementNS( XML_a, XML_spcAft );
3486 if (!bWriteNumbering)
3487 mpFS->singleElementNS(XML_a, XML_buNone);
3488 else
3489 WriteParagraphNumbering( rXPropSet, fFirstCharHeight, nLevel );
3491 WriteParagraphTabStops( rXPropSet );
3493 // do not end element for lstStyles since, defRPr should be stacked inside it
3494 if( nElement != XML_lvl1pPr )
3495 mpFS->endElementNS( XML_a, nElement );
3497 return true;
3500 void DrawingML::WriteLstStyles(const css::uno::Reference<css::text::XTextContent>& rParagraph,
3501 bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
3502 const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet)
3504 Reference<XEnumerationAccess> xAccess(rParagraph, UNO_QUERY);
3505 if (!xAccess.is())
3506 return;
3508 Reference<XEnumeration> xEnumeration(xAccess->createEnumeration());
3509 if (!xEnumeration.is())
3510 return;
3513 Reference<XTextRange> rRun;
3515 if (!xEnumeration->hasMoreElements())
3516 return;
3518 Any aAny(xEnumeration->nextElement());
3519 if (aAny >>= rRun)
3521 float fFirstCharHeight = rnCharHeight / 1000.;
3522 Reference<XPropertySet> xFirstRunPropSet(rRun, UNO_QUERY);
3523 Reference<XPropertySetInfo> xFirstRunPropSetInfo
3524 = xFirstRunPropSet->getPropertySetInfo();
3526 if (xFirstRunPropSetInfo->hasPropertyByName("CharHeight"))
3527 fFirstCharHeight = xFirstRunPropSet->getPropertyValue("CharHeight").get<float>();
3529 mpFS->startElementNS(XML_a, XML_lstStyle);
3530 if( !WriteParagraphProperties(rParagraph, fFirstCharHeight, XML_lvl1pPr) )
3531 mpFS->startElementNS(XML_a, XML_lvl1pPr);
3532 WriteRunProperties(xFirstRunPropSet, false, XML_defRPr, true, rbOverridingCharHeight,
3533 rnCharHeight, GetScriptType(rRun->getString()), rXShapePropSet);
3534 mpFS->endElementNS(XML_a, XML_lvl1pPr);
3535 mpFS->endElementNS(XML_a, XML_lstStyle);
3539 void DrawingML::WriteParagraph( const Reference< XTextContent >& rParagraph,
3540 bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
3541 const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet)
3543 Reference< XEnumerationAccess > access( rParagraph, UNO_QUERY );
3544 if( !access.is() )
3545 return;
3547 Reference< XEnumeration > enumeration( access->createEnumeration() );
3548 if( !enumeration.is() )
3549 return;
3551 mpFS->startElementNS(XML_a, XML_p);
3553 bool bPropertiesWritten = false;
3554 while( enumeration->hasMoreElements() )
3556 Reference< XTextRange > run;
3557 Any any ( enumeration->nextElement() );
3559 if (any >>= run)
3561 if( !bPropertiesWritten )
3563 float fFirstCharHeight = rnCharHeight / 1000.;
3564 Reference< XPropertySet > xFirstRunPropSet (run, UNO_QUERY);
3565 Reference< XPropertySetInfo > xFirstRunPropSetInfo = xFirstRunPropSet->getPropertySetInfo();
3566 if( xFirstRunPropSetInfo->hasPropertyByName("CharHeight") )
3568 fFirstCharHeight = xFirstRunPropSet->getPropertyValue("CharHeight").get<float>();
3569 rnCharHeight = 100 * fFirstCharHeight;
3570 rbOverridingCharHeight = true;
3572 WriteParagraphProperties(rParagraph, fFirstCharHeight, XML_pPr);
3573 bPropertiesWritten = true;
3575 WriteRun( run, rbOverridingCharHeight, rnCharHeight, rXShapePropSet);
3578 Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY );
3579 sal_Int16 nDummy = -1;
3580 WriteRunProperties(rXPropSet, false, XML_endParaRPr, false, rbOverridingCharHeight,
3581 rnCharHeight, nDummy, rXShapePropSet);
3583 mpFS->endElementNS( XML_a, XML_p );
3586 bool DrawingML::IsFontworkShape(const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet)
3588 bool bResult(false);
3589 if (rXShapePropSet.is())
3591 Sequence<PropertyValue> aCustomShapeGeometryProps;
3592 if (GetProperty(rXShapePropSet, "CustomShapeGeometry"))
3594 mAny >>= aCustomShapeGeometryProps;
3595 uno::Sequence<beans::PropertyValue> aTextPathSeq;
3596 for (const auto& rProp : std::as_const(aCustomShapeGeometryProps))
3598 if (rProp.Name == "TextPath")
3600 rProp.Value >>= aTextPathSeq;
3601 for (const auto& rTextPathItem : std::as_const(aTextPathSeq))
3603 if (rTextPathItem.Name == "TextPath")
3605 rTextPathItem.Value >>= bResult;
3606 break;
3609 break;
3614 return bResult;
3617 void DrawingML::WriteText(const Reference<XInterface>& rXIface, bool bBodyPr, bool bText,
3618 sal_Int32 nXmlNamespace, bool bWritePropertiesAsLstStyles)
3620 // ToDo: Fontwork in DOCX
3621 uno::Reference<XText> xXText(rXIface, UNO_QUERY);
3622 if( !xXText.is() )
3623 return;
3625 uno::Reference<drawing::XShape> xShape(rXIface, UNO_QUERY);
3626 uno::Reference<XPropertySet> rXPropSet(rXIface, UNO_QUERY);
3628 constexpr const sal_Int32 constDefaultLeftRightInset = 254;
3629 constexpr const sal_Int32 constDefaultTopBottomInset = 127;
3630 sal_Int32 nLeft = constDefaultLeftRightInset;
3631 sal_Int32 nRight = constDefaultLeftRightInset;
3632 sal_Int32 nTop = constDefaultTopBottomInset;
3633 sal_Int32 nBottom = constDefaultTopBottomInset;
3635 // top inset looks a bit different compared to ppt export
3636 // check if something related doesn't work as expected
3637 if (GetProperty(rXPropSet, "TextLeftDistance"))
3638 mAny >>= nLeft;
3639 if (GetProperty(rXPropSet, "TextRightDistance"))
3640 mAny >>= nRight;
3641 if (GetProperty(rXPropSet, "TextUpperDistance"))
3642 mAny >>= nTop;
3643 if (GetProperty(rXPropSet, "TextLowerDistance"))
3644 mAny >>= nBottom;
3646 // Transform the text distance values so they are compatible with OOXML insets
3647 if (xShape.is())
3649 sal_Int32 nTextHeight = xShape->getSize().Height; // Hmm, default
3651 // CustomShape can have text area different from shape rectangle
3652 auto* pCustomShape
3653 = dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape));
3654 if (pCustomShape)
3656 const EnhancedCustomShape2d aCustomShape2d(*pCustomShape);
3657 nTextHeight = aCustomShape2d.GetTextRect().getOpenHeight();
3658 if (DOCUMENT_DOCX == meDocumentType)
3659 nTextHeight = convertTwipToMm100(nTextHeight);
3662 if (nTop + nBottom >= nTextHeight)
3664 // Effective bottom would be above effective top of text area. LO normalizes the
3665 // effective text area in such case implicitly for rendering. MS needs indents so that
3666 // the result is the normalized effective text area.
3667 std::swap(nTop, nBottom);
3668 nTop = nTextHeight - nTop;
3669 nBottom = nTextHeight - nBottom;
3673 std::optional<OString> sWritingMode;
3674 if (GetProperty(rXPropSet, "TextWritingMode"))
3676 WritingMode eMode;
3677 if( ( mAny >>= eMode ) && eMode == WritingMode_TB_RL )
3678 sWritingMode = "eaVert";
3680 if (GetProperty(rXPropSet, "WritingMode"))
3682 sal_Int16 nWritingMode;
3683 if (mAny >>= nWritingMode)
3685 if (nWritingMode == text::WritingMode2::TB_RL)
3686 sWritingMode = "eaVert";
3687 else if (nWritingMode == text::WritingMode2::BT_LR)
3688 sWritingMode = "vert270";
3689 else if (nWritingMode == text::WritingMode2::TB_RL90)
3690 sWritingMode = "vert";
3691 else if (nWritingMode == text::WritingMode2::TB_LR)
3692 sWritingMode = "mongolianVert";
3696 // read values from CustomShapeGeometry
3697 Sequence<drawing::EnhancedCustomShapeAdjustmentValue> aAdjustmentSeq;
3698 uno::Sequence<beans::PropertyValue> aTextPathSeq;
3699 bool bScaleX(false);
3700 OUString sShapeType("non-primitive");
3701 OUString sMSWordPresetTextWarp;
3702 sal_Int32 nTextPreRotateAngle = 0; // degree
3703 std::optional<Degree100> nTextRotateAngleDeg100; // text area rotation
3705 if (GetProperty(rXPropSet, "CustomShapeGeometry"))
3707 Sequence< PropertyValue > aProps;
3708 if ( mAny >>= aProps )
3710 for ( const auto& rProp : std::as_const(aProps) )
3712 if (rProp.Name == "TextPreRotateAngle")
3713 rProp.Value >>= nTextPreRotateAngle;
3714 else if (rProp.Name == "AdjustmentValues")
3715 rProp.Value >>= aAdjustmentSeq;
3716 else if (rProp.Name == "TextRotateAngle")
3718 double fTextRotateAngle = 0; // degree
3719 rProp.Value >>= fTextRotateAngle;
3720 nTextRotateAngleDeg100 = Degree100(std::lround(fTextRotateAngle * 100.0));
3722 else if (rProp.Name == "Type")
3723 rProp.Value >>= sShapeType;
3724 else if (rProp.Name == "TextPath")
3726 rProp.Value >>= aTextPathSeq;
3727 for (const auto& rTextPathItem : std::as_const(aTextPathSeq))
3729 if (rTextPathItem.Name == "ScaleX")
3730 rTextPathItem.Value >>= bScaleX;
3733 else if (rProp.Name == "PresetTextWarp")
3734 rProp.Value >>= sMSWordPresetTextWarp;
3738 else
3740 if (mpTextExport)
3742 if (xShape)
3744 auto xTextFrame = mpTextExport->GetUnoTextFrame(xShape);
3745 if (xTextFrame)
3747 uno::Reference<beans::XPropertySet> xPropSet(xTextFrame, uno::UNO_QUERY);
3748 auto aAny = xPropSet->getPropertyValue("WritingMode");
3749 sal_Int16 nWritingMode;
3750 if (aAny >>= nWritingMode)
3752 switch (nWritingMode)
3754 case WritingMode2::TB_RL:
3755 sWritingMode = "eaVert";
3756 break;
3757 case WritingMode2::BT_LR:
3758 sWritingMode = "vert270";
3759 break;
3760 case WritingMode2::TB_RL90:
3761 sWritingMode = "vert";
3762 break;
3763 case WritingMode2::TB_LR:
3764 sWritingMode = "mongolianVert";
3765 break;
3766 default:
3767 break;
3775 // read InteropGrabBag if any
3776 std::optional<OUString> sHorzOverflow;
3777 std::optional<OUString> sVertOverflow;
3778 bool bUpright = false;
3779 std::optional<OString> isUpright;
3780 if (rXPropSet->getPropertySetInfo()->hasPropertyByName("InteropGrabBag"))
3782 uno::Sequence<beans::PropertyValue> aGrabBag;
3783 rXPropSet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
3784 for (const auto& aProp : std::as_const(aGrabBag))
3786 if (aProp.Name == "Upright")
3788 aProp.Value >>= bUpright;
3789 isUpright = OString(bUpright ? "1" : "0");
3791 else if (aProp.Name == "horzOverflow")
3793 OUString sValue;
3794 aProp.Value >>= sValue;
3795 sHorzOverflow = sValue;
3797 else if (aProp.Name == "vertOverflow")
3799 OUString sValue;
3800 aProp.Value >>= sValue;
3801 sVertOverflow = sValue;
3806 bool bIsFontworkShape(IsFontworkShape(rXPropSet));
3807 OUString sPresetWarp(PresetGeometryTypeNames::GetMsoName(sShapeType));
3808 // ODF may have user defined TextPath, use "textPlain" as ersatz.
3809 if (sPresetWarp.isEmpty())
3810 sPresetWarp = bIsFontworkShape ? std::u16string_view(u"textPlain") : std::u16string_view(u"textNoShape");
3812 bool bFromWordArt = !bScaleX
3813 && ( sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp"
3814 || sPresetWarp == "textButton" || sPresetWarp == "textCircle");
3816 // Fontwork shapes in LO ignore insets in rendering, Word interprets them.
3817 if (GetDocumentType() == DOCUMENT_DOCX && bIsFontworkShape)
3819 nLeft = 0;
3820 nRight = 0;
3821 nTop = 0;
3822 nBottom = 0;
3825 if (bUpright)
3827 Degree100 nShapeRotateAngleDeg100(0_deg100);
3828 if (GetProperty(rXPropSet, "RotateAngle"))
3829 nShapeRotateAngleDeg100 = Degree100(mAny.get<sal_Int32>());
3830 // Depending on shape rotation, the import has made 90deg changes to properties
3831 // "TextPreRotateAngle" and "TextRotateAngle". Revert it.
3832 bool bWasAngleChanged
3833 = (nShapeRotateAngleDeg100 > 4500_deg100 && nShapeRotateAngleDeg100 <= 13500_deg100)
3834 || (nShapeRotateAngleDeg100 > 22500_deg100
3835 && nShapeRotateAngleDeg100 <= 31500_deg100);
3836 if (bWasAngleChanged)
3838 nTextRotateAngleDeg100 = nTextRotateAngleDeg100.value_or(0_deg100) + 9000_deg100;
3839 nTextPreRotateAngle -= 90;
3841 // If text is no longer upright, user has changed something. Do not write 'upright' then.
3842 // This try to detect the case assumes, that the text area rotation was 0 in the original
3843 // MS Office document. That is likely because MS Office has no UI to set it and the
3844 // predefined SmartArt shapes, which use it, do not use 'upright'.
3845 Degree100 nAngleSum = nShapeRotateAngleDeg100 + nTextRotateAngleDeg100.value_or(0_deg100);
3846 if (abs(NormAngle18000(nAngleSum)) < 100_deg100) // consider inaccuracy from rounding
3848 nTextRotateAngleDeg100.reset(); // 'upright' does not overrule text area rotation.
3850 else
3852 // User changes. Keep current angles.
3853 isUpright.reset();
3854 if (bWasAngleChanged)
3856 nTextPreRotateAngle += 90;
3857 nTextRotateAngleDeg100 = nTextRotateAngleDeg100.value_or(0_deg100) - 9000_deg100;
3862 // ToDo: Unsure about this. Need to investigate shapes from diagram import, especially diagrams
3863 // with vertical text directions.
3864 if (nTextPreRotateAngle != 0 && !sWritingMode)
3866 if (nTextPreRotateAngle == -90 || nTextPreRotateAngle == 270)
3867 sWritingMode = "vert";
3868 else if (nTextPreRotateAngle == -270 || nTextPreRotateAngle == 90)
3869 sWritingMode = "vert270";
3870 else if (nTextPreRotateAngle == -180 || nTextPreRotateAngle == 180)
3872 #if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12
3873 #pragma GCC diagnostic push
3874 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
3875 #endif
3876 nTextRotateAngleDeg100
3877 = NormAngle18000(nTextRotateAngleDeg100.value_or(0_deg100) + 18000_deg100);
3878 #if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12
3879 #pragma GCC diagnostic pop
3880 #endif
3881 // ToDo: Examine insets. They might need rotation too. Check diagrams (SmartArt).
3883 else
3884 SAL_WARN("oox", "unsuitable value for TextPreRotateAngle:" << nTextPreRotateAngle);
3886 else if (nTextPreRotateAngle != 0 && sWritingMode && sWritingMode.value() == "eaVert")
3888 // ToDo: eaVert plus 270deg clockwise rotation has to be written with vert="horz"
3889 // plus attribute 'normalEastAsianFlow="1"' on the <wps:wsp> element.
3891 // else nothing to do
3893 // Our WritingMode introduces text pre rotation which includes padding, MSO vert does not include
3894 // padding. Therefore set padding so, that is looks the same in MSO as in LO.
3895 if (sWritingMode)
3897 if (sWritingMode.value() == "vert" || sWritingMode.value() == "eaVert")
3899 sal_Int32 nHelp = nLeft;
3900 nLeft = nBottom;
3901 nBottom = nRight;
3902 nRight = nTop;
3903 nTop = nHelp;
3905 else if (sWritingMode.value() == "vert270")
3907 sal_Int32 nHelp = nLeft;
3908 nLeft = nTop;
3909 nTop = nRight;
3910 nRight = nBottom;
3911 nBottom = nHelp;
3913 else if (sWritingMode.value() == "mongolianVert")
3915 // ToDo: Examine padding
3920 std::optional<OString> sTextRotateAngleMSUnit;
3921 if (nTextRotateAngleDeg100.has_value())
3922 #if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12
3923 #pragma GCC diagnostic push
3924 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
3925 #endif
3926 sTextRotateAngleMSUnit
3927 = oox::drawingml::calcRotationValue(nTextRotateAngleDeg100.value().get());
3928 #if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12
3929 #pragma GCC diagnostic pop
3930 #endif
3932 // Prepare attributes 'anchor' and 'anchorCtr'
3933 // LibreOffice has 12 value sets, MS Office only 6. We map them so, that it reverses the
3934 // 6 mappings from import, and we assign the others approximately.
3935 TextVerticalAdjust eVerticalAlignment(TextVerticalAdjust_TOP);
3936 if (GetProperty(rXPropSet, "TextVerticalAdjust"))
3937 mAny >>= eVerticalAlignment;
3938 TextHorizontalAdjust eHorizontalAlignment(TextHorizontalAdjust_CENTER);
3939 if (GetProperty(rXPropSet, "TextHorizontalAdjust"))
3940 mAny >>= eHorizontalAlignment;
3942 const char* sAnchor = nullptr;
3943 bool bAnchorCtr = false;
3944 if (sWritingMode.has_value()
3945 && (sWritingMode.value() == "eaVert" || sWritingMode.value() == "mongolianVert"))
3947 bAnchorCtr = eVerticalAlignment == TextVerticalAdjust_CENTER
3948 || eVerticalAlignment == TextVerticalAdjust_BOTTOM
3949 || eVerticalAlignment == TextVerticalAdjust_BLOCK;
3950 switch (eHorizontalAlignment)
3952 case TextHorizontalAdjust_CENTER:
3953 sAnchor = "ctr";
3954 break;
3955 case TextHorizontalAdjust_LEFT:
3956 sAnchor = sWritingMode.value() == "eaVert" ? "b" : "t";
3957 break;
3958 case TextHorizontalAdjust_RIGHT:
3959 default: // TextHorizontalAdjust_BLOCK, should not happen
3960 sAnchor = sWritingMode.value() == "eaVert" ? "t" : "b";
3961 break;
3964 else
3966 bAnchorCtr = eHorizontalAlignment == TextHorizontalAdjust_CENTER
3967 || eHorizontalAlignment == TextHorizontalAdjust_RIGHT;
3968 sAnchor = GetTextVerticalAdjust(eVerticalAlignment);
3971 bool bHasWrap = false;
3972 bool bWrap = false;
3973 // Only custom shapes obey the TextWordWrap option, normal text always wraps.
3974 if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, "TextWordWrap"))
3976 mAny >>= bWrap;
3977 bHasWrap = true;
3980 if (bBodyPr)
3982 const char* pWrap = (bHasWrap && !bWrap) || bIsFontworkShape ? "none" : nullptr;
3983 if (GetDocumentType() == DOCUMENT_DOCX)
3985 // In case of DOCX, if we want to have the same effect as
3986 // TextShape's automatic word wrapping, then we need to set
3987 // wrapping to square.
3988 uno::Reference<lang::XServiceInfo> xServiceInfo(rXIface, uno::UNO_QUERY);
3989 if ((xServiceInfo.is() && xServiceInfo->supportsService("com.sun.star.drawing.TextShape"))
3990 || bIsFontworkShape)
3991 pWrap = "square";
3994 sal_Int16 nCols = 0;
3995 sal_Int32 nColSpacing = -1;
3996 if (GetProperty(rXPropSet, "TextColumns"))
3998 if (css::uno::Reference<css::text::XTextColumns> xCols{ mAny, css::uno::UNO_QUERY })
4000 nCols = xCols->getColumnCount();
4001 if (css::uno::Reference<css::beans::XPropertySet> xProps{ mAny,
4002 css::uno::UNO_QUERY })
4004 if (GetProperty(xProps, "AutomaticDistance"))
4005 mAny >>= nColSpacing;
4010 if (!sVertOverflow && GetProperty(rXPropSet, "TextClipVerticalOverflow") && mAny.get<bool>())
4012 sVertOverflow = "clip";
4015 // tdf#151134 When writing placeholder shapes, inset must be explicitly specified
4016 bool bRequireInset = GetProperty(rXPropSet, "IsPresentationObject") && rXPropSet->getPropertyValue("IsPresentationObject").get<bool>();
4018 mpFS->startElementNS( (nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr,
4019 XML_numCol, sax_fastparser::UseIf(OString::number(nCols), nCols > 0),
4020 XML_spcCol, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nColSpacing)), nCols > 0 && nColSpacing >= 0),
4021 XML_wrap, pWrap,
4022 XML_horzOverflow, sHorzOverflow,
4023 XML_vertOverflow, sVertOverflow,
4024 XML_fromWordArt, sax_fastparser::UseIf("1", bFromWordArt),
4025 XML_lIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeft)),
4026 bRequireInset || nLeft != constDefaultLeftRightInset),
4027 XML_rIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nRight)),
4028 bRequireInset || nRight != constDefaultLeftRightInset),
4029 XML_tIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nTop)),
4030 bRequireInset || nTop != constDefaultTopBottomInset),
4031 XML_bIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nBottom)),
4032 bRequireInset || nBottom != constDefaultTopBottomInset),
4033 XML_anchor, sAnchor,
4034 XML_anchorCtr, sax_fastparser::UseIf("1", bAnchorCtr),
4035 XML_vert, sWritingMode,
4036 XML_upright, isUpright,
4037 XML_rot, sTextRotateAngleMSUnit);
4039 if (bIsFontworkShape)
4041 if (aAdjustmentSeq.hasElements())
4043 mpFS->startElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp);
4044 mpFS->startElementNS(XML_a, XML_avLst);
4045 bool bHasTwoHandles(
4046 sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour"
4047 || sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour"
4048 || sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1"
4049 || sPresetWarp == "textWave2" || sPresetWarp == "textWave4");
4050 for (sal_Int32 i = 0, nElems = aAdjustmentSeq.getLength(); i < nElems; ++i )
4052 OString sName = "adj" + (bHasTwoHandles ? OString::number(i + 1) : OString());
4053 double fValue(0.0);
4054 if (aAdjustmentSeq[i].Value.getValueTypeClass() == TypeClass_DOUBLE)
4055 aAdjustmentSeq[i].Value >>= fValue;
4056 else
4058 sal_Int32 nNumber(0);
4059 aAdjustmentSeq[i].Value >>= nNumber;
4060 fValue = static_cast<double>(nNumber);
4062 // Convert from binary coordinate system with viewBox "0 0 21600 21600" and simple degree
4063 // to DrawingML with coordinate range 0..100000 and angle in 1/60000 degree.
4064 // Reverse to conversion in lcl_createPresetShape in drawingml/shape.cxx on import.
4065 if (sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp"
4066 || sPresetWarp == "textButton" || sPresetWarp == "textCircle"
4067 || ((i == 0)
4068 && (sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour"
4069 || sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour")))
4071 fValue *= 60000.0;
4072 if (fValue < 0)
4073 fValue += 21600000;
4075 else if ((i == 1)
4076 && (sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1"
4077 || sPresetWarp == "textWave2" || sPresetWarp == "textWave4"))
4079 fValue = fValue / 0.216 - 50000.0;
4081 else if ((i == 1)
4082 && (sPresetWarp == "textArchDownPour"
4083 || sPresetWarp == "textArchUpPour"
4084 || sPresetWarp == "textButtonPour"
4085 || sPresetWarp == "textCirclePour"))
4087 fValue /= 0.108;
4089 else
4091 fValue /= 0.216;
4093 OString sFmla = "val " + OString::number(std::lround(fValue));
4094 mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla);
4095 // There exists faulty Favorite shapes with one handle but two adjustment values.
4096 if (!bHasTwoHandles)
4097 break;
4099 mpFS->endElementNS(XML_a, XML_avLst);
4100 mpFS->endElementNS(XML_a, XML_prstTxWarp);
4102 else
4104 mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp);
4107 else if (GetDocumentType() == DOCUMENT_DOCX)
4109 // interim solution for fdo#80897, roundtrip DOCX > LO > DOCX
4110 if (!sMSWordPresetTextWarp.isEmpty())
4111 mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sMSWordPresetTextWarp);
4114 if (GetDocumentType() == DOCUMENT_DOCX || GetDocumentType() == DOCUMENT_XLSX)
4116 // tdf#112312: only custom shapes obey the TextAutoGrowHeight option
4117 bool bTextAutoGrowHeight = false;
4118 auto pSdrObjCustomShape = xShape.is() ? dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape)) : nullptr;
4119 if (pSdrObjCustomShape && GetProperty(rXPropSet, "TextAutoGrowHeight"))
4121 mAny >>= bTextAutoGrowHeight;
4123 mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit));
4125 if (GetDocumentType() == DOCUMENT_PPTX)
4127 TextFitToSizeType eFit = TextFitToSizeType_NONE;
4128 if (GetProperty(rXPropSet, "TextFitToSize"))
4129 mAny >>= eFit;
4131 if (eFit == TextFitToSizeType_AUTOFIT)
4133 const sal_Int32 MAX_SCALE_VAL = 100000;
4134 sal_Int32 nFontScale = MAX_SCALE_VAL;
4135 sal_Int32 nSpacingReduction = 0;
4136 SvxShapeText* pTextShape = dynamic_cast<SvxShapeText*>(rXIface.get());
4137 if (pTextShape)
4139 SdrTextObj* pTextObject = DynCastSdrTextObj(pTextShape->GetSdrObject());
4140 if (pTextObject)
4142 nFontScale = sal_Int32(pTextObject->GetFontScale() * 1000.0);
4143 nSpacingReduction = sal_Int32((100.0 - pTextObject->GetSpacingScale()) * 1000.0);
4147 bool bExportFontScale = false;
4148 if (nFontScale < MAX_SCALE_VAL && nFontScale > 0)
4149 bExportFontScale = true;
4151 bool bExportSpaceReduction = false;
4152 if (nSpacingReduction < MAX_SCALE_VAL && nSpacingReduction > 0)
4153 bExportSpaceReduction = true;
4155 mpFS->singleElementNS(XML_a, XML_normAutofit,
4156 XML_fontScale, sax_fastparser::UseIf(OString::number(nFontScale), bExportFontScale),
4157 XML_lnSpcReduction, sax_fastparser::UseIf(OString::number(nSpacingReduction), bExportSpaceReduction));
4159 else
4161 // tdf#127030: Only custom shapes obey the TextAutoGrowHeight option.
4162 bool bTextAutoGrowHeight = false;
4163 if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, "TextAutoGrowHeight"))
4164 mAny >>= bTextAutoGrowHeight;
4165 mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit));
4169 Write3DEffects( rXPropSet, /*bIsText=*/true );
4171 mpFS->endElementNS((nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr);
4174 Reference< XEnumerationAccess > access( xXText, UNO_QUERY );
4175 if( !access.is() || !bText )
4176 return;
4178 Reference< XEnumeration > enumeration( access->createEnumeration() );
4179 if( !enumeration.is() )
4180 return;
4182 SdrObject* pSdrObject = xShape.is() ? SdrObject::getSdrObjectFromXShape(xShape) : nullptr;
4183 const SdrTextObj* pTxtObj = DynCastSdrTextObj( pSdrObject );
4184 if (pTxtObj && mpTextExport)
4186 std::vector<beans::PropertyValue> aOldCharFillPropVec;
4187 if (bIsFontworkShape)
4189 // Users may have set the character fill properties for more convenient editing.
4190 // Save the properties before changing them for Fontwork export.
4191 FontworkHelpers::collectCharColorProps(xXText, aOldCharFillPropVec);
4192 // Word has properties for abc-transform in the run properties of the text of the shape.
4193 // Writer has the Fontwork properties as shape properties. Create the character fill
4194 // properties needed for export from the shape fill properties
4195 // and apply them to all runs.
4196 std::vector<beans::PropertyValue> aExportCharFillPropVec;
4197 FontworkHelpers::createCharFillPropsFromShape(rXPropSet, aExportCharFillPropVec);
4198 FontworkHelpers::applyPropsToRuns(aExportCharFillPropVec, xXText);
4199 // Import has converted some items from CharInteropGrabBag to fill and line
4200 // properties of the shape. For export we convert them back because users might have
4201 // changed them. And we create them in case we come from an odt document.
4202 std::vector<beans::PropertyValue> aUpdatePropVec;
4203 FontworkHelpers::createCharInteropGrabBagUpdatesFromShapeProps(rXPropSet, aUpdatePropVec);
4204 FontworkHelpers::applyUpdatesToCharInteropGrabBag(aUpdatePropVec, xXText);
4207 std::optional<OutlinerParaObject> pParaObj;
4210 #i13885#
4211 When the object is actively being edited, that text is not set into
4212 the objects normal text object, but lives in a separate object.
4214 if (pTxtObj->IsTextEditActive())
4216 pParaObj = pTxtObj->CreateEditOutlinerParaObject();
4218 else if (pTxtObj->GetOutlinerParaObject())
4219 pParaObj = *pTxtObj->GetOutlinerParaObject();
4221 if (pParaObj)
4223 // this is reached only in case some text is attached to the shape
4224 mpTextExport->WriteOutliner(*pParaObj);
4227 if (bIsFontworkShape)
4228 FontworkHelpers::applyPropsToRuns(aOldCharFillPropVec, xXText);
4229 return;
4232 bool bOverridingCharHeight = false;
4233 sal_Int32 nCharHeight = -1;
4234 bool bFirstParagraph = true;
4236 // tdf#144092 For shapes without text: Export run properties (into
4237 // endParaRPr) from the shape's propset instead of the paragraph's.
4238 if(xXText->getString().isEmpty() && enumeration->hasMoreElements())
4240 Any aAny (enumeration->nextElement());
4241 Reference<XTextContent> xParagraph;
4242 if( aAny >>= xParagraph )
4244 mpFS->startElementNS(XML_a, XML_p);
4245 WriteParagraphProperties(xParagraph, nCharHeight, XML_pPr);
4246 sal_Int16 nDummy = -1;
4247 WriteRunProperties(rXPropSet, false, XML_endParaRPr, false,
4248 bOverridingCharHeight, nCharHeight, nDummy, rXPropSet);
4249 mpFS->endElementNS(XML_a, XML_p);
4251 return;
4254 while( enumeration->hasMoreElements() )
4256 Reference< XTextContent > paragraph;
4257 Any any ( enumeration->nextElement() );
4259 if( any >>= paragraph)
4261 if (bFirstParagraph && bWritePropertiesAsLstStyles)
4262 WriteLstStyles(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet);
4264 WriteParagraph(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet);
4265 bFirstParagraph = false;
4270 void DrawingML::WritePresetShape( const OString& pShape , std::vector< std::pair<sal_Int32,sal_Int32>> & rAvList )
4272 mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
4273 if ( !rAvList.empty() )
4276 mpFS->startElementNS(XML_a, XML_avLst);
4277 for (auto const& elem : rAvList)
4279 OString sName = "adj" + ( ( elem.first > 0 ) ? OString::number(elem.first) : OString() );
4280 OString sFmla = "val " + OString::number( elem.second );
4282 mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla);
4284 mpFS->endElementNS( XML_a, XML_avLst );
4286 else
4287 mpFS->singleElementNS(XML_a, XML_avLst);
4289 mpFS->endElementNS( XML_a, XML_prstGeom );
4292 void DrawingML::WritePresetShape( const OString& pShape )
4294 mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
4295 mpFS->singleElementNS(XML_a, XML_avLst);
4296 mpFS->endElementNS( XML_a, XML_prstGeom );
4299 static std::map< OString, std::vector<OString> > lcl_getAdjNames()
4301 std::map< OString, std::vector<OString> > aRet;
4303 OUString aPath("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/filter/oox-drawingml-adj-names");
4304 rtl::Bootstrap::expandMacros(aPath);
4305 SvFileStream aStream(aPath, StreamMode::READ);
4306 if (aStream.GetError() != ERRCODE_NONE)
4307 SAL_WARN("oox.shape", "failed to open oox-drawingml-adj-names");
4308 OStringBuffer aLine;
4309 bool bNotDone = aStream.ReadLine(aLine);
4310 while (bNotDone)
4312 sal_Int32 nIndex = 0;
4313 // Each line is in a "key\tvalue" format: read the key, the rest is the value.
4314 OString aKey( o3tl::getToken(aLine, 0, '\t', nIndex) );
4315 OString aValue( std::string_view(aLine).substr(nIndex) );
4316 aRet[aKey].push_back(aValue);
4317 bNotDone = aStream.ReadLine(aLine);
4319 return aRet;
4322 void DrawingML::WritePresetShape( const OString& pShape, MSO_SPT eShapeType, bool bPredefinedHandlesUsed, const PropertyValue& rProp )
4324 static std::map< OString, std::vector<OString> > aAdjMap = lcl_getAdjNames();
4325 // If there are predefined adj names for this shape type, look them up now.
4326 std::vector<OString> aAdjustments;
4327 if (aAdjMap.find(pShape) != aAdjMap.end())
4328 aAdjustments = aAdjMap[pShape];
4330 mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
4331 mpFS->startElementNS(XML_a, XML_avLst);
4333 Sequence< drawing::EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq;
4334 if ( ( rProp.Value >>= aAdjustmentSeq )
4335 && eShapeType != mso_sptActionButtonForwardNext // we have adjustments values for these type of shape, but MSO doesn't like them
4336 && eShapeType != mso_sptActionButtonBackPrevious // so they are now disabled
4337 && pShape != "rect" //some shape types are commented out in pCustomShapeTypeTranslationTable[] & are being defaulted to rect & rect does not have adjustment values/name.
4340 SAL_INFO("oox.shape", "adj seq len: " << aAdjustmentSeq.getLength());
4341 sal_Int32 nAdjustmentsWhichNeedsToBeConverted = 0;
4342 if ( bPredefinedHandlesUsed )
4343 EscherPropertyContainer::LookForPolarHandles( eShapeType, nAdjustmentsWhichNeedsToBeConverted );
4345 sal_Int32 nValue, nLength = aAdjustmentSeq.getLength();
4346 // 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.
4347 // Sometimes there are more values than needed, so we ignore the excessive ones.
4348 if (aAdjustments.size() <= o3tl::make_unsigned(nLength))
4350 for (sal_Int32 i = 0; i < static_cast<sal_Int32>(aAdjustments.size()); i++)
4352 if( EscherPropertyContainer::GetAdjustmentValue( aAdjustmentSeq[ i ], i, nAdjustmentsWhichNeedsToBeConverted, nValue ) )
4354 // If the document model doesn't have an adjustment name (e.g. shape was created from VML), then take it from the predefined list.
4355 OString aAdjName = aAdjustmentSeq[i].Name.isEmpty()
4356 ? aAdjustments[i]
4357 : aAdjustmentSeq[i].Name.toUtf8();
4359 mpFS->singleElementNS( XML_a, XML_gd,
4360 XML_name, aAdjName,
4361 XML_fmla, "val " + OString::number(nValue));
4367 mpFS->endElementNS( XML_a, XML_avLst );
4368 mpFS->endElementNS( XML_a, XML_prstGeom );
4371 namespace // helpers for DrawingML::WriteCustomGeometry
4373 sal_Int32
4374 FindNextCommandEndSubpath(const sal_Int32 nStart,
4375 const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments)
4377 sal_Int32 i = nStart < 0 ? 0 : nStart;
4378 while (i < rSegments.getLength() && rSegments[i].Command != ENDSUBPATH)
4379 i++;
4380 return i;
4383 bool HasCommandInSubPath(const sal_Int16 nCommand, const sal_Int32 nFirst, const sal_Int32 nLast,
4384 const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments)
4386 for (sal_Int32 i = nFirst < 0 ? 0 : nFirst; i <= nLast && i < rSegments.getLength(); i++)
4388 if (rSegments[i].Command == nCommand)
4389 return true;
4391 return false;
4394 // Ellipse is given by radii fwR and fhR and center (fCx|fCy). The ray from center through point RayP
4395 // intersects the ellipse in point S and this point S has angle fAngleDeg in degrees.
4396 void getEllipsePointAndAngleFromRayPoint(double& rfAngleDeg, double& rfSx, double& rfSy,
4397 const double fWR, const double fHR, const double fCx,
4398 const double fCy, const double fRayPx, const double fRayPy)
4400 if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR))
4402 rfSx = fCx; // needed for getting new 'current point'
4403 rfSy = fCy;
4405 else
4407 // center ellipse at origin, stretch in y-direction to circle, flip to Math orientation
4408 // and get angle
4409 double fCircleMathAngle = atan2(-fWR / fHR * (fRayPy - fCy), fRayPx - fCx);
4410 // use angle for intersection point on circle and stretch back to ellipse
4411 double fPointMathEllipse_x = fWR * cos(fCircleMathAngle);
4412 double fPointMathEllipse_y = fHR * sin(fCircleMathAngle);
4413 // get angle of intersection point on ellipse
4414 double fEllipseMathAngle = atan2(fPointMathEllipse_y, fPointMathEllipse_x);
4415 // convert from Math to View orientation and shift ellipse back from origin
4416 rfAngleDeg = -basegfx::rad2deg(fEllipseMathAngle);
4417 rfSx = fPointMathEllipse_x + fCx;
4418 rfSy = -fPointMathEllipse_y + fCy;
4422 void getEllipsePointFromViewAngle(double& rfSx, double& rfSy, const double fWR, const double fHR,
4423 const double fCx, const double fCy, const double fViewAngleDeg)
4425 if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR))
4427 rfSx = fCx; // needed for getting new 'current point'
4428 rfSy = fCy;
4430 else
4432 double fX = cos(basegfx::deg2rad(fViewAngleDeg)) / fWR;
4433 double fY = sin(basegfx::deg2rad(fViewAngleDeg)) / fHR;
4434 double fRadius = 1.0 / std::hypot(fX, fY);
4435 rfSx = fCx + fRadius * cos(basegfx::deg2rad(fViewAngleDeg));
4436 rfSy = fCy + fRadius * sin(basegfx::deg2rad(fViewAngleDeg));
4440 sal_Int32 GetCustomGeometryPointValue(const css::drawing::EnhancedCustomShapeParameter& rParam,
4441 const EnhancedCustomShape2d& rCustomShape2d,
4442 const bool bReplaceGeoWidth, const bool bReplaceGeoHeight)
4444 double fValue = 0.0;
4445 rCustomShape2d.GetParameter(fValue, rParam, bReplaceGeoWidth, bReplaceGeoHeight);
4446 sal_Int32 nValue(std::lround(fValue));
4448 return nValue;
4451 struct TextAreaRect
4453 OString left;
4454 OString top;
4455 OString right;
4456 OString bottom;
4459 struct Guide
4461 OString sName;
4462 OString sFormula;
4465 void prepareTextArea(const EnhancedCustomShape2d& rEnhancedCustomShape2d,
4466 std::vector<Guide>& rGuideList, TextAreaRect& rTextAreaRect)
4468 tools::Rectangle aTextAreaLO(rEnhancedCustomShape2d.GetTextRect());
4469 tools::Rectangle aLogicRectLO(rEnhancedCustomShape2d.GetLogicRect());
4470 if (aTextAreaLO == aLogicRectLO)
4472 rTextAreaRect.left = "l";
4473 rTextAreaRect.top = "t";
4474 rTextAreaRect.right = "r";
4475 rTextAreaRect.bottom = "b";
4476 return;
4478 // Flip aTextAreaLO if shape is flipped
4479 if (rEnhancedCustomShape2d.IsFlipHorz())
4480 aTextAreaLO.Move((aLogicRectLO.Center().X() - aTextAreaLO.Center().X()) * 2, 0);
4481 if (rEnhancedCustomShape2d.IsFlipVert())
4482 aTextAreaLO.Move(0, (aLogicRectLO.Center().Y() - aTextAreaLO.Center().Y()) * 2);
4484 Guide aGuide;
4485 // horizontal
4486 const sal_Int32 nWidth = aLogicRectLO.Right() - aLogicRectLO.Left();
4487 const OString sWidth = OString::number(oox::drawingml::convertHmmToEmu(nWidth));
4489 // left
4490 aGuide.sName = "textAreaLeft";
4491 sal_Int32 nHelp = aTextAreaLO.Left() - aLogicRectLO.Left();
4492 const OString sLeft = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
4493 aGuide.sFormula = "*/ " + sLeft + " w " + sWidth;
4494 rTextAreaRect.left = aGuide.sName;
4495 rGuideList.push_back(aGuide);
4497 // right
4498 aGuide.sName = "textAreaRight";
4499 nHelp = aTextAreaLO.Right() - aLogicRectLO.Left();
4500 const OString sRight = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
4501 aGuide.sFormula = "*/ " + sRight + " w " + sWidth;
4502 rTextAreaRect.right = aGuide.sName;
4503 rGuideList.push_back(aGuide);
4505 // vertical
4506 const sal_Int32 nHeight = aLogicRectLO.Bottom() - aLogicRectLO.Top();
4507 const OString sHeight = OString::number(oox::drawingml::convertHmmToEmu(nHeight));
4509 // top
4510 aGuide.sName = "textAreaTop";
4511 nHelp = aTextAreaLO.Top() - aLogicRectLO.Top();
4512 const OString sTop = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
4513 aGuide.sFormula = "*/ " + sTop + " h " + sHeight;
4514 rTextAreaRect.top = aGuide.sName;
4515 rGuideList.push_back(aGuide);
4517 // bottom
4518 aGuide.sName = "textAreaBottom";
4519 nHelp = aTextAreaLO.Bottom() - aLogicRectLO.Top();
4520 const OString sBottom = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
4521 aGuide.sFormula = "*/ " + sBottom + " h " + sHeight;
4522 rTextAreaRect.bottom = aGuide.sName;
4523 rGuideList.push_back(aGuide);
4525 return;
4529 bool DrawingML::WriteCustomGeometry(
4530 const Reference< XShape >& rXShape,
4531 const SdrObjCustomShape& rSdrObjCustomShape)
4533 uno::Reference< beans::XPropertySet > aXPropSet;
4534 uno::Any aAny( rXShape->queryInterface(cppu::UnoType<beans::XPropertySet>::get()));
4536 if ( ! (aAny >>= aXPropSet) )
4537 return false;
4541 aAny = aXPropSet->getPropertyValue( "CustomShapeGeometry" );
4542 if ( !aAny.hasValue() )
4543 return false;
4545 catch( const ::uno::Exception& )
4547 return false;
4550 auto pGeometrySeq = o3tl::tryAccess<uno::Sequence<beans::PropertyValue>>(aAny);
4551 if (!pGeometrySeq)
4552 return false;
4554 auto pPathProp = std::find_if(std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq),
4555 [](const PropertyValue& rProp) { return rProp.Name == "Path"; });
4556 if (pPathProp == std::cend(*pGeometrySeq))
4557 return false;
4559 uno::Sequence<beans::PropertyValue> aPathProp;
4560 pPathProp->Value >>= aPathProp;
4562 uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs;
4563 uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments;
4564 uno::Sequence<awt::Size> aPathSize;
4565 bool bReplaceGeoWidth = false;
4566 bool bReplaceGeoHeight = false;
4567 for (const beans::PropertyValue& rPathProp : std::as_const(aPathProp))
4569 if (rPathProp.Name == "Coordinates")
4570 rPathProp.Value >>= aPairs;
4571 else if (rPathProp.Name == "Segments")
4572 rPathProp.Value >>= aSegments;
4573 else if (rPathProp.Name == "SubViewSize")
4574 rPathProp.Value >>= aPathSize;
4575 else if (rPathProp.Name == "StretchX")
4576 bReplaceGeoWidth = true;
4577 else if (rPathProp.Name == "StretchY")
4578 bReplaceGeoHeight = true;
4581 if ( !aPairs.hasElements() )
4582 return false;
4584 if ( !aSegments.hasElements() )
4586 aSegments = uno::Sequence<drawing::EnhancedCustomShapeSegment>
4588 { MOVETO, 1 },
4589 { LINETO,
4590 static_cast<sal_Int16>(std::min( aPairs.getLength() - 1, sal_Int32(32767) )) },
4591 { CLOSESUBPATH, 0 },
4592 { ENDSUBPATH, 0 }
4596 int nExpectedPairCount = std::accumulate(std::cbegin(aSegments), std::cend(aSegments), 0,
4597 [](const int nSum, const drawing::EnhancedCustomShapeSegment& rSegment) { return nSum + rSegment.Count; });
4599 if ( nExpectedPairCount > aPairs.getLength() )
4601 SAL_WARN("oox.shape", "Segments need " << nExpectedPairCount << " coordinates, but Coordinates have only " << aPairs.getLength() << " pairs.");
4602 return false;
4605 // A EnhancedCustomShape2d caches the equation results. Therefore we use only one of it for the
4606 // entire method.
4607 const EnhancedCustomShape2d aCustomShape2d(const_cast<SdrObjCustomShape&>(rSdrObjCustomShape));
4609 TextAreaRect aTextAreaRect;
4610 std::vector<Guide> aGuideList; // for now only for <a:rect>
4611 prepareTextArea(aCustomShape2d, aGuideList, aTextAreaRect);
4612 mpFS->startElementNS(XML_a, XML_custGeom);
4613 mpFS->singleElementNS(XML_a, XML_avLst);
4614 if (aGuideList.empty())
4616 mpFS->singleElementNS(XML_a, XML_gdLst);
4618 else
4620 mpFS->startElementNS(XML_a, XML_gdLst);
4621 for (auto const& elem : aGuideList)
4623 mpFS->singleElementNS(XML_a, XML_gd, XML_name, elem.sName, XML_fmla, elem.sFormula);
4625 mpFS->endElementNS(XML_a, XML_gdLst);
4627 mpFS->singleElementNS(XML_a, XML_ahLst);
4628 mpFS->singleElementNS(XML_a, XML_rect, XML_l, aTextAreaRect.left, XML_t, aTextAreaRect.top,
4629 XML_r, aTextAreaRect.right, XML_b, aTextAreaRect.bottom);
4630 mpFS->startElementNS(XML_a, XML_pathLst);
4632 // Prepare width and height for <a:path>
4633 bool bUseGlobalViewBox(false);
4635 // nViewBoxWidth must be integer otherwise ReplaceGeoWidth in aCustomShape2d.GetParameter() is not
4636 // triggered; same for height.
4637 sal_Int32 nViewBoxWidth(0);
4638 sal_Int32 nViewBoxHeight(0);
4639 if (!aPathSize.hasElements())
4641 bUseGlobalViewBox = true;
4642 // If draw:viewBox is missing in draw:enhancedGeometry, then import sets
4643 // viewBox="0 0 21600 21600". Missing ViewBox can only occur, if user has manipulated
4644 // current file via macro. Author of macro has to fix it.
4645 auto pProp = std::find_if(
4646 std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq),
4647 [](const beans::PropertyValue& rGeomProp) { return rGeomProp.Name == "ViewBox"; });
4648 if (pProp != std::cend(*pGeometrySeq))
4650 css::awt::Rectangle aViewBox;
4651 if (pProp->Value >>= aViewBox)
4653 nViewBoxWidth = aViewBox.Width;
4654 nViewBoxHeight = aViewBox.Height;
4655 css::drawing::EnhancedCustomShapeParameter aECSP;
4656 aECSP.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL;
4657 aECSP.Value <<= nViewBoxWidth;
4658 double fRetValue;
4659 aCustomShape2d.GetParameter(fRetValue, aECSP, true, false);
4660 nViewBoxWidth = basegfx::fround(fRetValue);
4661 aECSP.Value <<= nViewBoxHeight;
4662 aCustomShape2d.GetParameter(fRetValue, aECSP, false, true);
4663 nViewBoxHeight = basegfx::fround(fRetValue);
4666 // Import from oox or documents, which are imported from oox and saved to strict ODF, might
4667 // have no subViewSize but viewBox="0 0 0 0". We need to generate width and height in those
4668 // cases. Even if that is fixed, we need the substitute for old documents.
4669 if ((nViewBoxWidth == 0 && nViewBoxHeight == 0) || pProp == std::cend(*pGeometrySeq))
4671 // Generate a substitute based on point coordinates
4672 sal_Int32 nXMin(0);
4673 aPairs[0].First.Value >>= nXMin;
4674 sal_Int32 nXMax = nXMin;
4675 sal_Int32 nYMin(0);
4676 aPairs[0].Second.Value >>= nYMin;
4677 sal_Int32 nYMax = nYMin;
4679 for (const auto& rPair : std::as_const(aPairs))
4681 sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, aCustomShape2d,
4682 bReplaceGeoWidth, false);
4683 sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, aCustomShape2d, false,
4684 bReplaceGeoHeight);
4685 if (nX < nXMin)
4686 nXMin = nX;
4687 if (nY < nYMin)
4688 nYMin = nY;
4689 if (nX > nXMax)
4690 nXMax = nX;
4691 if (nY > nYMax)
4692 nYMax = nY;
4694 nViewBoxWidth = std::max(nXMax, nXMax - nXMin);
4695 nViewBoxHeight = std::max(nYMax, nYMax - nYMin);
4697 // ToDo: Other values of left,top than 0,0 are not considered yet. Such would require a
4698 // shift of the resulting path coordinates.
4701 // Iterate over subpaths
4702 sal_Int32 nPairIndex = 0; // index over "Coordinates"
4703 sal_Int32 nPathSizeIndex = 0; // index over "SubViewSize"
4704 sal_Int32 nSubpathStartIndex(0); // index over "Segments"
4705 sal_Int32 nSubPathIndex(0); // serial number of current subpath
4708 bool bOK(true); // catch faulty paths were commands do not correspond to points
4709 // get index of next command ENDSUBPATH; if such doesn't exist use index behind last segment
4710 sal_Int32 nNextNcommandIndex = FindNextCommandEndSubpath(nSubpathStartIndex, aSegments);
4712 // Prepare attributes for a:path start element
4713 // NOFILL or one of the LIGHTEN commands
4714 std::optional<OString> sFill;
4715 if (HasCommandInSubPath(NOFILL, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
4716 sFill = "none";
4717 else if (HasCommandInSubPath(DARKEN, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
4718 sFill = "darken";
4719 else if (HasCommandInSubPath(DARKENLESS, nSubpathStartIndex, nNextNcommandIndex - 1,
4720 aSegments))
4721 sFill = "darkenLess";
4722 else if (HasCommandInSubPath(LIGHTEN, nSubpathStartIndex, nNextNcommandIndex - 1,
4723 aSegments))
4724 sFill = "lighten";
4725 else if (HasCommandInSubPath(LIGHTENLESS, nSubpathStartIndex, nNextNcommandIndex - 1,
4726 aSegments))
4727 sFill = "lightenLess";
4728 else
4730 // shading info might be in object type, e.g. "Octagon Bevel".
4731 sal_Int32 nLuminanceChange(aCustomShape2d.GetLuminanceChange(nSubPathIndex));
4732 if (nLuminanceChange <= -40)
4733 sFill = "darken";
4734 else if (nLuminanceChange <= -10)
4735 sFill = "darkenLess";
4736 else if (nLuminanceChange >= 40)
4737 sFill = "lighten";
4738 else if (nLuminanceChange >= 10)
4739 sFill = "lightenLess";
4741 // NOSTROKE
4742 std::optional<OString> sStroke;
4743 if (HasCommandInSubPath(NOSTROKE, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
4744 sStroke = "0";
4746 // Write a:path start element
4747 mpFS->startElementNS(
4748 XML_a, XML_path, XML_fill, sFill, XML_stroke, sStroke, XML_w,
4749 OString::number(bUseGlobalViewBox ? nViewBoxWidth : aPathSize[nPathSizeIndex].Width),
4750 XML_h,
4751 OString::number(bUseGlobalViewBox ? nViewBoxHeight : aPathSize[nPathSizeIndex].Height));
4753 // Arcs drawn by commands ELLIPTICALQUADRANTX and ELLIPTICALQUADRANTY depend on the position
4754 // of the target point in regard to the current point. Therefore we need to track the
4755 // current point. A current point is not defined in the beginning.
4756 double fCurrentX(0.0);
4757 double fCurrentY(0.0);
4758 bool bCurrentValid(false);
4759 // Actually write the subpath
4760 for (sal_Int32 nSegmentIndex = nSubpathStartIndex; nSegmentIndex < nNextNcommandIndex;
4761 ++nSegmentIndex)
4763 const auto& rSegment(aSegments[nSegmentIndex]);
4764 if (rSegment.Command == CLOSESUBPATH)
4766 mpFS->singleElementNS(XML_a, XML_close); // command Z has no parameter
4767 // ODF 1.4 specifies, that the start of the subpath becomes the current point.
4768 // But that is not implemented yet. Currently LO keeps the last current point.
4770 for (sal_Int32 k = 0; k < rSegment.Count && bOK; ++k)
4772 bOK = WriteCustomGeometrySegment(rSegment.Command, k, aPairs, nPairIndex, fCurrentX,
4773 fCurrentY, bCurrentValid, aCustomShape2d,
4774 bReplaceGeoWidth, bReplaceGeoHeight);
4776 } // end loop over all commands of subpath
4777 // finish this subpath in any case
4778 mpFS->endElementNS(XML_a, XML_path);
4780 if (!bOK)
4781 break; // exit loop if not enough values in aPairs
4783 // step forward to next subpath
4784 nSubpathStartIndex = nNextNcommandIndex + 1;
4785 nPathSizeIndex++;
4786 nSubPathIndex++;
4787 } while (nSubpathStartIndex < aSegments.getLength());
4789 mpFS->endElementNS(XML_a, XML_pathLst);
4790 mpFS->endElementNS(XML_a, XML_custGeom);
4791 return true; // We have written custGeom even if path is poorly structured.
4794 bool DrawingML::WriteCustomGeometrySegment(
4795 const sal_Int16 eCommand, const sal_Int32 nCount,
4796 const uno::Sequence<css::drawing::EnhancedCustomShapeParameterPair>& rPairs,
4797 sal_Int32& rnPairIndex, double& rfCurrentX, double& rfCurrentY, bool& rbCurrentValid,
4798 const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth,
4799 const bool bReplaceGeoHeight)
4801 switch (eCommand)
4803 case MOVETO:
4805 if (rnPairIndex >= rPairs.getLength())
4806 return false;
4808 mpFS->startElementNS(XML_a, XML_moveTo);
4809 WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
4810 bReplaceGeoHeight);
4811 mpFS->endElementNS(XML_a, XML_moveTo);
4812 rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth,
4813 false);
4814 rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false,
4815 bReplaceGeoHeight);
4816 rbCurrentValid = true;
4817 rnPairIndex++;
4818 break;
4820 case LINETO:
4822 if (rnPairIndex >= rPairs.getLength())
4823 return false;
4824 // LINETO without valid current point is a faulty path. LO is tolerant and makes a
4825 // moveTo instead. Do the same on export. MS OFFICE requires a current point for lnTo,
4826 // otherwise it shows nothing of the shape.
4827 if (rbCurrentValid)
4829 mpFS->startElementNS(XML_a, XML_lnTo);
4830 WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
4831 bReplaceGeoHeight);
4832 mpFS->endElementNS(XML_a, XML_lnTo);
4834 else
4836 mpFS->startElementNS(XML_a, XML_moveTo);
4837 WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
4838 bReplaceGeoHeight);
4839 mpFS->endElementNS(XML_a, XML_moveTo);
4841 rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth,
4842 false);
4843 rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false,
4844 bReplaceGeoHeight);
4845 rbCurrentValid = true;
4846 rnPairIndex++;
4847 break;
4849 case CURVETO:
4851 if (rnPairIndex + 2 >= rPairs.getLength())
4852 return false;
4854 mpFS->startElementNS(XML_a, XML_cubicBezTo);
4855 for (sal_uInt8 i = 0; i <= 2; ++i)
4857 WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth,
4858 bReplaceGeoHeight);
4860 mpFS->endElementNS(XML_a, XML_cubicBezTo);
4861 rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth,
4862 false);
4863 rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 2].Second, false,
4864 bReplaceGeoHeight);
4865 rbCurrentValid = true;
4866 rnPairIndex += 3;
4867 break;
4869 case ANGLEELLIPSETO:
4870 case ANGLEELLIPSE:
4872 if (rnPairIndex + 2 >= rPairs.getLength())
4873 return false;
4875 // Read parameters
4876 double fCx = 0.0;
4877 rCustomShape2d.GetParameter(fCx, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
4878 double fCy = 0.0;
4879 rCustomShape2d.GetParameter(fCy, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
4880 double fWR = 0.0;
4881 rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex + 1].First, false, false);
4882 double fHR = 0.0;
4883 rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex + 1].Second, false, false);
4884 double fStartAngle = 0.0;
4885 rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 2].First, false, false);
4886 double fEndAngle = 0.0;
4887 rCustomShape2d.GetParameter(fEndAngle, rPairs[rnPairIndex + 2].Second, false, false);
4889 // Prepare start and swing angle
4890 sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
4891 sal_Int32 nSwingAng = 0;
4892 if (basegfx::fTools::equalZero(fStartAngle)
4893 && basegfx::fTools::equalZero(fEndAngle - 360.0))
4894 nSwingAng = 360 * 60000; // special case full circle
4895 else
4897 nSwingAng = std::lround((fEndAngle - fStartAngle) * 60000);
4898 if (nSwingAng < 0)
4899 nSwingAng += 360 * 60000;
4902 // calculate start point on ellipse
4903 double fSx = 0.0;
4904 double fSy = 0.0;
4905 getEllipsePointFromViewAngle(fSx, fSy, fWR, fHR, fCx, fCy, fStartAngle);
4907 // write markup for going to start point
4908 // lnTo requires a valid current point
4909 if (eCommand == ANGLEELLIPSETO && rbCurrentValid)
4911 mpFS->startElementNS(XML_a, XML_lnTo);
4912 mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)),
4913 XML_y, OString::number(std::lround(fSy)));
4914 mpFS->endElementNS(XML_a, XML_lnTo);
4916 else
4918 mpFS->startElementNS(XML_a, XML_moveTo);
4919 mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)),
4920 XML_y, OString::number(std::lround(fSy)));
4921 mpFS->endElementNS(XML_a, XML_moveTo);
4923 // write markup for arcTo
4924 if (!basegfx::fTools::equalZero(fWR) && !basegfx::fTools::equalZero(fHR))
4925 mpFS->singleElement(
4926 FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR,
4927 OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng),
4928 XML_swAng, OString::number(nSwingAng));
4930 getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy, fEndAngle);
4931 rbCurrentValid = true;
4932 rnPairIndex += 3;
4933 break;
4935 case ARCTO:
4936 case ARC:
4937 case CLOCKWISEARCTO:
4938 case CLOCKWISEARC:
4940 if (rnPairIndex + 3 >= rPairs.getLength())
4941 return false;
4943 // read parameters
4944 double fX1 = 0.0;
4945 rCustomShape2d.GetParameter(fX1, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
4946 double fY1 = 0.0;
4947 rCustomShape2d.GetParameter(fY1, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
4948 double fX2 = 0.0;
4949 rCustomShape2d.GetParameter(fX2, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth,
4950 false);
4951 double fY2 = 0.0;
4952 rCustomShape2d.GetParameter(fY2, rPairs[rnPairIndex + 1].Second, false,
4953 bReplaceGeoHeight);
4954 double fX3 = 0.0;
4955 rCustomShape2d.GetParameter(fX3, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth,
4956 false);
4957 double fY3 = 0.0;
4958 rCustomShape2d.GetParameter(fY3, rPairs[rnPairIndex + 2].Second, false,
4959 bReplaceGeoHeight);
4960 double fX4 = 0.0;
4961 rCustomShape2d.GetParameter(fX4, rPairs[rnPairIndex + 3].First, bReplaceGeoWidth,
4962 false);
4963 double fY4 = 0.0;
4964 rCustomShape2d.GetParameter(fY4, rPairs[rnPairIndex + 3].Second, false,
4965 bReplaceGeoHeight);
4966 // calculate ellipse parameter
4967 const double fWR = (std::max(fX1, fX2) - std::min(fX1, fX2)) / 2.0;
4968 const double fHR = (std::max(fY1, fY2) - std::min(fY1, fY2)) / 2.0;
4969 const double fCx = (fX1 + fX2) / 2.0;
4970 const double fCy = (fY1 + fY2) / 2.0;
4971 // calculate start angle
4972 double fStartAngle = 0.0;
4973 double fPx = 0.0;
4974 double fPy = 0.0;
4975 getEllipsePointAndAngleFromRayPoint(fStartAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX3,
4976 fY3);
4977 // markup for going to start point
4978 // lnTo requires a valid current point.
4979 if ((eCommand == ARCTO || eCommand == CLOCKWISEARCTO) && rbCurrentValid)
4981 mpFS->startElementNS(XML_a, XML_lnTo);
4982 mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)),
4983 XML_y, OString::number(std::lround(fPy)));
4984 mpFS->endElementNS(XML_a, XML_lnTo);
4986 else
4988 mpFS->startElementNS(XML_a, XML_moveTo);
4989 mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)),
4990 XML_y, OString::number(std::lround(fPy)));
4991 mpFS->endElementNS(XML_a, XML_moveTo);
4993 // calculate swing angle
4994 double fEndAngle = 0.0;
4995 getEllipsePointAndAngleFromRayPoint(fEndAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX4, fY4);
4996 double fSwingAngle(fEndAngle - fStartAngle);
4997 const bool bIsClockwise(eCommand == CLOCKWISEARCTO || eCommand == CLOCKWISEARC);
4998 if (bIsClockwise && fSwingAngle < 0)
4999 fSwingAngle += 360.0;
5000 else if (!bIsClockwise && fSwingAngle > 0)
5001 fSwingAngle -= 360.0;
5002 // markup for arcTo
5003 // ToDo: write markup for case zero width or height of ellipse
5004 const sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
5005 const sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000));
5006 mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)),
5007 XML_hR, OString::number(std::lround(fHR)), XML_stAng,
5008 OString::number(nStartAng), XML_swAng, OString::number(nSwingAng));
5009 rfCurrentX = fPx;
5010 rfCurrentY = fPy;
5011 rbCurrentValid = true;
5012 rnPairIndex += 4;
5013 break;
5015 case ELLIPTICALQUADRANTX:
5016 case ELLIPTICALQUADRANTY:
5018 if (rnPairIndex >= rPairs.getLength())
5019 return false;
5021 // read parameters
5022 double fX = 0.0;
5023 rCustomShape2d.GetParameter(fX, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
5024 double fY = 0.0;
5025 rCustomShape2d.GetParameter(fY, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
5027 // Prepare parameters for arcTo
5028 if (rbCurrentValid)
5030 double fWR = std::abs(rfCurrentX - fX);
5031 double fHR = std::abs(rfCurrentY - fY);
5032 double fStartAngle(0.0);
5033 double fSwingAngle(0.0);
5034 // The starting direction of the arc toggles between X and Y
5035 if ((eCommand == ELLIPTICALQUADRANTX && !(nCount % 2))
5036 || (eCommand == ELLIPTICALQUADRANTY && (nCount % 2)))
5038 // arc starts horizontal
5039 fStartAngle = fY < rfCurrentY ? 90.0 : 270.0;
5040 const bool bClockwise = (fX < rfCurrentX && fY < rfCurrentY)
5041 || (fX > rfCurrentX && fY > rfCurrentY);
5042 fSwingAngle = bClockwise ? 90.0 : -90.0;
5044 else
5046 // arc starts vertical
5047 fStartAngle = fX < rfCurrentX ? 0.0 : 180.0;
5048 const bool bClockwise = (fX < rfCurrentX && fY > rfCurrentY)
5049 || (fX > rfCurrentX && fY < rfCurrentY);
5050 fSwingAngle = bClockwise ? 90.0 : -90.0;
5052 sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
5053 sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000));
5054 mpFS->singleElement(
5055 FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR,
5056 OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng),
5057 XML_swAng, OString::number(nSwingAng));
5059 else
5061 // faulty path, but we continue with the target point
5062 mpFS->startElementNS(XML_a, XML_moveTo);
5063 WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
5064 bReplaceGeoHeight);
5065 mpFS->endElementNS(XML_a, XML_moveTo);
5067 rfCurrentX = fX;
5068 rfCurrentY = fY;
5069 rbCurrentValid = true;
5070 rnPairIndex++;
5071 break;
5073 case QUADRATICCURVETO:
5075 if (rnPairIndex + 1 >= rPairs.getLength())
5076 return false;
5078 mpFS->startElementNS(XML_a, XML_quadBezTo);
5079 for (sal_uInt8 i = 0; i < 2; ++i)
5081 WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth,
5082 bReplaceGeoHeight);
5084 mpFS->endElementNS(XML_a, XML_quadBezTo);
5085 rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth,
5086 false);
5087 rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 1].Second, false,
5088 bReplaceGeoHeight);
5089 rbCurrentValid = true;
5090 rnPairIndex += 2;
5091 break;
5093 case ARCANGLETO:
5095 if (rnPairIndex + 1 >= rPairs.getLength())
5096 return false;
5098 double fWR = 0.0;
5099 rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex].First, false, false);
5100 double fHR = 0.0;
5101 rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex].Second, false, false);
5102 double fStartAngle = 0.0;
5103 rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 1].First, false, false);
5104 sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
5105 double fSwingAng = 0.0;
5106 rCustomShape2d.GetParameter(fSwingAng, rPairs[rnPairIndex + 1].Second, false, false);
5107 sal_Int32 nSwingAng(std::lround(fSwingAng * 60000));
5108 mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(fWR), XML_hR,
5109 OString::number(fHR), XML_stAng, OString::number(nStartAng),
5110 XML_swAng, OString::number(nSwingAng));
5111 double fPx = 0.0;
5112 double fPy = 0.0;
5113 getEllipsePointFromViewAngle(fPx, fPy, fWR, fHR, 0.0, 0.0, fStartAngle);
5114 double fCx = rfCurrentX - fPx;
5115 double fCy = rfCurrentY - fPy;
5116 getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy,
5117 fStartAngle + fSwingAng);
5118 rbCurrentValid = true;
5119 rnPairIndex += 2;
5120 break;
5122 default:
5123 // do nothing
5124 break;
5126 return true;
5129 void DrawingML::WriteCustomGeometryPoint(
5130 const drawing::EnhancedCustomShapeParameterPair& rParamPair,
5131 const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth,
5132 const bool bReplaceGeoHeight)
5134 sal_Int32 nX
5135 = GetCustomGeometryPointValue(rParamPair.First, rCustomShape2d, bReplaceGeoWidth, false);
5136 sal_Int32 nY
5137 = GetCustomGeometryPointValue(rParamPair.Second, rCustomShape2d, false, bReplaceGeoHeight);
5139 mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(nX), XML_y, OString::number(nY));
5142 void DrawingML::WriteEmptyCustomGeometry()
5144 // This method is used for export to docx in case WriteCustomGeometry fails.
5145 mpFS->startElementNS(XML_a, XML_custGeom);
5146 mpFS->singleElementNS(XML_a, XML_avLst);
5147 mpFS->singleElementNS(XML_a, XML_gdLst);
5148 mpFS->singleElementNS(XML_a, XML_ahLst);
5149 mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b");
5150 mpFS->singleElementNS(XML_a, XML_pathLst);
5151 mpFS->endElementNS(XML_a, XML_custGeom);
5154 // version for SdrPathObj
5155 void DrawingML::WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape,
5156 const bool bClosed)
5158 tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(rXShape);
5159 // In case of Writer, the parent element is <wps:spPr>, and there the
5160 // <a:custGeom> element is not optional.
5161 if (aPolyPolygon.Count() < 1 && GetDocumentType() != DOCUMENT_DOCX)
5162 return;
5164 mpFS->startElementNS(XML_a, XML_custGeom);
5165 mpFS->singleElementNS(XML_a, XML_avLst);
5166 mpFS->singleElementNS(XML_a, XML_gdLst);
5167 mpFS->singleElementNS(XML_a, XML_ahLst);
5168 mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b");
5170 mpFS->startElementNS(XML_a, XML_pathLst);
5172 awt::Size aSize = rXShape->getSize();
5173 awt::Point aPos = rXShape->getPosition();
5174 Reference<XPropertySet> xPropertySet(rXShape, UNO_QUERY);
5175 uno::Reference<XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
5176 if (xPropertySetInfo->hasPropertyByName("AnchorPosition"))
5178 awt::Point aAnchorPosition;
5179 xPropertySet->getPropertyValue("AnchorPosition") >>= aAnchorPosition;
5180 aPos.X += aAnchorPosition.X;
5181 aPos.Y += aAnchorPosition.Y;
5184 // Only closed SdrPathObj can be filled
5185 std::optional<OString> sFill;
5186 if (!bClosed)
5187 sFill = "none"; // for possible values see ST_PathFillMode in OOXML standard
5189 // Put all polygons of rPolyPolygon in the same path element
5190 // to subtract the overlapped areas.
5191 mpFS->startElementNS(XML_a, XML_path, XML_fill, sFill, XML_w, OString::number(aSize.Width),
5192 XML_h, OString::number(aSize.Height));
5194 for (sal_uInt16 i = 0; i < aPolyPolygon.Count(); i++)
5196 const tools::Polygon& aPoly = aPolyPolygon[i];
5198 if (aPoly.GetSize() > 0)
5200 mpFS->startElementNS(XML_a, XML_moveTo);
5202 mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[0].X() - aPos.X),
5203 XML_y, OString::number(aPoly[0].Y() - aPos.Y));
5205 mpFS->endElementNS(XML_a, XML_moveTo);
5208 for (sal_uInt16 j = 1; j < aPoly.GetSize(); j++)
5210 PolyFlags flags = aPoly.GetFlags(j);
5211 if (flags == PolyFlags::Control)
5213 // a:cubicBezTo can only contain 3 a:pt elements, so we need to make sure of this
5214 if (j + 2 < aPoly.GetSize() && aPoly.GetFlags(j + 1) == PolyFlags::Control
5215 && aPoly.GetFlags(j + 2) != PolyFlags::Control)
5217 mpFS->startElementNS(XML_a, XML_cubicBezTo);
5218 for (sal_uInt8 k = 0; k <= 2; ++k)
5220 mpFS->singleElementNS(XML_a, XML_pt, XML_x,
5221 OString::number(aPoly[j + k].X() - aPos.X), XML_y,
5222 OString::number(aPoly[j + k].Y() - aPos.Y));
5224 mpFS->endElementNS(XML_a, XML_cubicBezTo);
5225 j += 2;
5228 else if (flags == PolyFlags::Normal)
5230 mpFS->startElementNS(XML_a, XML_lnTo);
5231 mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[j].X() - aPos.X),
5232 XML_y, OString::number(aPoly[j].Y() - aPos.Y));
5233 mpFS->endElementNS(XML_a, XML_lnTo);
5237 if (bClosed)
5238 mpFS->singleElementNS(XML_a, XML_close);
5239 mpFS->endElementNS(XML_a, XML_path);
5241 mpFS->endElementNS(XML_a, XML_pathLst);
5243 mpFS->endElementNS(XML_a, XML_custGeom);
5246 void DrawingML::WriteConnectorConnections( sal_Int32 nStartGlueId, sal_Int32 nEndGlueId, sal_Int32 nStartID, sal_Int32 nEndID )
5248 if( nStartID != -1 )
5250 mpFS->singleElementNS( XML_a, XML_stCxn,
5251 XML_id, OString::number(nStartID),
5252 XML_idx, OString::number(nStartGlueId) );
5254 if( nEndID != -1 )
5256 mpFS->singleElementNS( XML_a, XML_endCxn,
5257 XML_id, OString::number(nEndID),
5258 XML_idx, OString::number(nEndGlueId) );
5262 sal_Unicode DrawingML::SubstituteBullet( sal_Unicode cBulletId, css::awt::FontDescriptor& rFontDesc )
5264 if ( IsOpenSymbol(rFontDesc.Name) )
5266 rtl_TextEncoding eCharSet = rFontDesc.CharSet;
5267 cBulletId = msfilter::util::bestFitOpenSymbolToMSFont(cBulletId, eCharSet, rFontDesc.Name);
5268 rFontDesc.CharSet = eCharSet;
5271 return cBulletId;
5274 sax_fastparser::FSHelperPtr DrawingML::CreateOutputStream (
5275 const OUString& sFullStream,
5276 std::u16string_view sRelativeStream,
5277 const Reference< XOutputStream >& xParentRelation,
5278 const OUString& sContentType,
5279 const OUString& sRelationshipType,
5280 OUString* pRelationshipId )
5282 OUString sRelationshipId;
5283 if (xParentRelation.is())
5284 sRelationshipId = GetFB()->addRelation( xParentRelation, sRelationshipType, sRelativeStream );
5285 else
5286 sRelationshipId = GetFB()->addRelation( sRelationshipType, sRelativeStream );
5288 if( pRelationshipId )
5289 *pRelationshipId = sRelationshipId;
5291 sax_fastparser::FSHelperPtr p = GetFB()->openFragmentStreamWithSerializer( sFullStream, sContentType );
5293 return p;
5296 void DrawingML::WriteFill(const Reference<XPropertySet>& xPropSet, const awt::Size& rSize)
5298 if ( !GetProperty( xPropSet, "FillStyle" ) )
5299 return;
5300 FillStyle aFillStyle( FillStyle_NONE );
5301 xPropSet->getPropertyValue( "FillStyle" ) >>= aFillStyle;
5303 // map full transparent background to no fill
5304 if (aFillStyle == FillStyle_SOLID)
5306 OUString sFillTransparenceGradientName;
5308 if (GetProperty(xPropSet, "FillTransparenceGradientName")
5309 && (mAny >>= sFillTransparenceGradientName)
5310 && !sFillTransparenceGradientName.isEmpty()
5311 && GetProperty(xPropSet, "FillTransparenceGradient"))
5313 // check if a fully transparent TransparenceGradient is used
5314 // use BGradient constructor & tooling here now
5315 const basegfx::BGradient aTransparenceGradient = model::gradient::getFromAny(mAny);
5316 basegfx::BColor aSingleColor;
5317 const bool bSingleColor(aTransparenceGradient.GetColorStops().isSingleColor(aSingleColor));
5318 const bool bCompletelyTransparent(bSingleColor && basegfx::fTools::equal(aSingleColor.luminance(), 1.0));
5320 if (bCompletelyTransparent)
5322 aFillStyle = FillStyle_NONE;
5325 else if ( GetProperty( xPropSet, "FillTransparence" ) )
5327 // check if a fully transparent FillTransparence is used
5328 sal_Int16 nVal = 0;
5329 xPropSet->getPropertyValue( "FillTransparence" ) >>= nVal;
5330 if ( nVal == 100 )
5331 aFillStyle = FillStyle_NONE;
5335 bool bUseBackground(false);
5336 if (GetProperty(xPropSet, "FillUseSlideBackground"))
5337 xPropSet->getPropertyValue("FillUseSlideBackground") >>= bUseBackground;
5339 switch( aFillStyle )
5341 case FillStyle_SOLID :
5342 WriteSolidFill( xPropSet );
5343 break;
5344 case FillStyle_GRADIENT :
5345 WriteGradientFill( xPropSet );
5346 break;
5347 case FillStyle_BITMAP :
5348 WriteBlipFill( xPropSet, "FillBitmap", rSize );
5349 break;
5350 case FillStyle_HATCH :
5351 WritePattFill( xPropSet );
5352 break;
5353 case FillStyle_NONE:
5354 if (!bUseBackground) // attribute `useBgFill` will be written at parent p:sp shape
5355 mpFS->singleElementNS(XML_a, XML_noFill);
5356 break;
5357 default:
5362 void DrawingML::WriteStyleProperties( sal_Int32 nTokenId, const Sequence< PropertyValue >& aProperties )
5364 if( aProperties.hasElements() )
5366 OUString sSchemeClr;
5367 sal_uInt32 nIdx = 0;
5368 Sequence< PropertyValue > aTransformations;
5369 for( const auto& rProp : aProperties)
5371 if( rProp.Name == "SchemeClr" )
5372 rProp.Value >>= sSchemeClr;
5373 else if( rProp.Name == "Idx" )
5374 rProp.Value >>= nIdx;
5375 else if( rProp.Name == "Transformations" )
5376 rProp.Value >>= aTransformations;
5378 mpFS->startElementNS(XML_a, nTokenId, XML_idx, OString::number(nIdx));
5379 WriteColor(sSchemeClr, aTransformations);
5380 mpFS->endElementNS( XML_a, nTokenId );
5382 else
5384 // write mock <a:*Ref> tag
5385 mpFS->singleElementNS(XML_a, nTokenId, XML_idx, OString::number(0));
5389 void DrawingML::WriteShapeStyle( const Reference< XPropertySet >& xPropSet )
5391 // check existence of the grab bag
5392 if ( !GetProperty( xPropSet, "InteropGrabBag" ) )
5393 return;
5395 // extract the relevant properties from the grab bag
5396 Sequence< PropertyValue > aGrabBag;
5397 Sequence< PropertyValue > aFillRefProperties, aLnRefProperties, aEffectRefProperties;
5398 mAny >>= aGrabBag;
5399 for( const auto& rProp : std::as_const(aGrabBag))
5401 if( rProp.Name == "StyleFillRef" )
5402 rProp.Value >>= aFillRefProperties;
5403 else if( rProp.Name == "StyleLnRef" )
5404 rProp.Value >>= aLnRefProperties;
5405 else if( rProp.Name == "StyleEffectRef" )
5406 rProp.Value >>= aEffectRefProperties;
5409 WriteStyleProperties( XML_lnRef, aLnRefProperties );
5410 WriteStyleProperties( XML_fillRef, aFillRefProperties );
5411 WriteStyleProperties( XML_effectRef, aEffectRefProperties );
5413 // write mock <a:fontRef>
5414 mpFS->singleElementNS(XML_a, XML_fontRef, XML_idx, "minor");
5417 void DrawingML::WriteShapeEffect( std::u16string_view sName, const Sequence< PropertyValue >& aEffectProps )
5419 if( !aEffectProps.hasElements() )
5420 return;
5422 // assign the proper tag and enable bContainsColor if necessary
5423 sal_Int32 nEffectToken = 0;
5424 bool bContainsColor = false;
5425 if( sName == u"outerShdw" )
5427 nEffectToken = FSNS( XML_a, XML_outerShdw );
5428 bContainsColor = true;
5430 else if( sName == u"innerShdw" )
5432 nEffectToken = FSNS( XML_a, XML_innerShdw );
5433 bContainsColor = true;
5435 else if( sName == u"glow" )
5437 nEffectToken = FSNS( XML_a, XML_glow );
5438 bContainsColor = true;
5440 else if( sName == u"softEdge" )
5441 nEffectToken = FSNS( XML_a, XML_softEdge );
5442 else if( sName == u"reflection" )
5443 nEffectToken = FSNS( XML_a, XML_reflection );
5444 else if( sName == u"blur" )
5445 nEffectToken = FSNS( XML_a, XML_blur );
5447 OUString sSchemeClr;
5448 ::Color nRgbClr;
5449 sal_Int32 nAlpha = MAX_PERCENT;
5450 Sequence< PropertyValue > aTransformations;
5451 rtl::Reference<sax_fastparser::FastAttributeList> aOuterShdwAttrList = FastSerializerHelper::createAttrList();
5452 for( const auto& rEffectProp : aEffectProps )
5454 if( rEffectProp.Name == "Attribs" )
5456 // read tag attributes
5457 uno::Sequence< beans::PropertyValue > aOuterShdwProps;
5458 rEffectProp.Value >>= aOuterShdwProps;
5459 for( const auto& rOuterShdwProp : std::as_const(aOuterShdwProps) )
5461 if( rOuterShdwProp.Name == "algn" )
5463 OUString sVal;
5464 rOuterShdwProp.Value >>= sVal;
5465 aOuterShdwAttrList->add( XML_algn, sVal );
5467 else if( rOuterShdwProp.Name == "blurRad" )
5469 sal_Int64 nVal = 0;
5470 rOuterShdwProp.Value >>= nVal;
5471 aOuterShdwAttrList->add( XML_blurRad, OString::number( nVal ) );
5473 else if( rOuterShdwProp.Name == "dir" )
5475 sal_Int32 nVal = 0;
5476 rOuterShdwProp.Value >>= nVal;
5477 aOuterShdwAttrList->add( XML_dir, OString::number( nVal ) );
5479 else if( rOuterShdwProp.Name == "dist" )
5481 sal_Int32 nVal = 0;
5482 rOuterShdwProp.Value >>= nVal;
5483 aOuterShdwAttrList->add( XML_dist, OString::number( nVal ) );
5485 else if( rOuterShdwProp.Name == "kx" )
5487 sal_Int32 nVal = 0;
5488 rOuterShdwProp.Value >>= nVal;
5489 aOuterShdwAttrList->add( XML_kx, OString::number( nVal ) );
5491 else if( rOuterShdwProp.Name == "ky" )
5493 sal_Int32 nVal = 0;
5494 rOuterShdwProp.Value >>= nVal;
5495 aOuterShdwAttrList->add( XML_ky, OString::number( nVal ) );
5497 else if( rOuterShdwProp.Name == "rotWithShape" )
5499 sal_Int32 nVal = 0;
5500 rOuterShdwProp.Value >>= nVal;
5501 aOuterShdwAttrList->add( XML_rotWithShape, OString::number( nVal ) );
5503 else if( rOuterShdwProp.Name == "sx" )
5505 sal_Int32 nVal = 0;
5506 rOuterShdwProp.Value >>= nVal;
5507 aOuterShdwAttrList->add( XML_sx, OString::number( nVal ) );
5509 else if( rOuterShdwProp.Name == "sy" )
5511 sal_Int32 nVal = 0;
5512 rOuterShdwProp.Value >>= nVal;
5513 aOuterShdwAttrList->add( XML_sy, OString::number( nVal ) );
5515 else if( rOuterShdwProp.Name == "rad" )
5517 sal_Int64 nVal = 0;
5518 rOuterShdwProp.Value >>= nVal;
5519 aOuterShdwAttrList->add( XML_rad, OString::number( nVal ) );
5521 else if( rOuterShdwProp.Name == "endA" )
5523 sal_Int32 nVal = 0;
5524 rOuterShdwProp.Value >>= nVal;
5525 aOuterShdwAttrList->add( XML_endA, OString::number( nVal ) );
5527 else if( rOuterShdwProp.Name == "endPos" )
5529 sal_Int32 nVal = 0;
5530 rOuterShdwProp.Value >>= nVal;
5531 aOuterShdwAttrList->add( XML_endPos, OString::number( nVal ) );
5533 else if( rOuterShdwProp.Name == "fadeDir" )
5535 sal_Int32 nVal = 0;
5536 rOuterShdwProp.Value >>= nVal;
5537 aOuterShdwAttrList->add( XML_fadeDir, OString::number( nVal ) );
5539 else if( rOuterShdwProp.Name == "stA" )
5541 sal_Int32 nVal = 0;
5542 rOuterShdwProp.Value >>= nVal;
5543 aOuterShdwAttrList->add( XML_stA, OString::number( nVal ) );
5545 else if( rOuterShdwProp.Name == "stPos" )
5547 sal_Int32 nVal = 0;
5548 rOuterShdwProp.Value >>= nVal;
5549 aOuterShdwAttrList->add( XML_stPos, OString::number( nVal ) );
5551 else if( rOuterShdwProp.Name == "grow" )
5553 sal_Int32 nVal = 0;
5554 rOuterShdwProp.Value >>= nVal;
5555 aOuterShdwAttrList->add( XML_grow, OString::number( nVal ) );
5559 else if(rEffectProp.Name == "RgbClr")
5561 rEffectProp.Value >>= nRgbClr;
5563 else if(rEffectProp.Name == "RgbClrTransparency")
5565 sal_Int32 nTransparency;
5566 if (rEffectProp.Value >>= nTransparency)
5567 // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
5568 nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency );
5570 else if(rEffectProp.Name == "SchemeClr")
5572 rEffectProp.Value >>= sSchemeClr;
5574 else if(rEffectProp.Name == "SchemeClrTransformations")
5576 rEffectProp.Value >>= aTransformations;
5580 if( nEffectToken <= 0 )
5581 return;
5583 mpFS->startElement( nEffectToken, aOuterShdwAttrList );
5585 if( bContainsColor )
5587 if( sSchemeClr.isEmpty() )
5588 WriteColor( nRgbClr, nAlpha );
5589 else
5590 WriteColor( sSchemeClr, aTransformations );
5593 mpFS->endElement( nEffectToken );
5596 static sal_Int32 lcl_CalculateDist(const double dX, const double dY)
5598 return static_cast< sal_Int32 >(std::hypot(dX, dY) * 360);
5601 static sal_Int32 lcl_CalculateDir(const double dX, const double dY)
5603 return (static_cast< sal_Int32 >(basegfx::rad2deg<60000>(atan2(dY,dX))) + 21600000) % 21600000;
5606 void DrawingML::WriteShapeEffects( const Reference< XPropertySet >& rXPropSet )
5608 Sequence< PropertyValue > aGrabBag, aEffects, aOuterShdwProps;
5609 bool bHasInteropGrabBag = rXPropSet->getPropertySetInfo()->hasPropertyByName("InteropGrabBag");
5610 if (bHasInteropGrabBag && GetProperty(rXPropSet, "InteropGrabBag"))
5612 mAny >>= aGrabBag;
5613 auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
5614 [](const PropertyValue& rProp) { return rProp.Name == "EffectProperties"; });
5615 if (pProp != std::cend(aGrabBag))
5617 pProp->Value >>= aEffects;
5618 auto pEffect = std::find_if(std::cbegin(aEffects), std::cend(aEffects),
5619 [](const PropertyValue& rEffect) { return rEffect.Name == "outerShdw"; });
5620 if (pEffect != std::cend(aEffects))
5621 pEffect->Value >>= aOuterShdwProps;
5625 // tdf#132201: the order of effects is important. Effects order (CT_EffectList in ECMA-376):
5626 // blur -> fillOverlay -> glow -> innerShdw -> outerShdw -> prstShdw -> reflection -> softEdge
5628 if( !aEffects.hasElements() )
5630 bool bHasShadow = false;
5631 if( GetProperty( rXPropSet, "Shadow" ) )
5632 mAny >>= bHasShadow;
5633 bool bHasEffects = bHasShadow;
5634 if (!bHasEffects && GetProperty(rXPropSet, "GlowEffectRadius"))
5636 sal_Int32 rad = 0;
5637 mAny >>= rad;
5638 bHasEffects = rad > 0;
5640 if (!bHasEffects && GetProperty(rXPropSet, "SoftEdgeRadius"))
5642 sal_Int32 rad = 0;
5643 mAny >>= rad;
5644 bHasEffects = rad > 0;
5647 if (bHasEffects)
5649 mpFS->startElementNS(XML_a, XML_effectLst);
5650 WriteGlowEffect(rXPropSet);
5651 if( bHasShadow )
5653 double dX = +0.0, dY = +0.0;
5654 sal_Int32 nBlur =0;
5655 rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX;
5656 rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY;
5657 rXPropSet->getPropertyValue( "ShadowBlur" ) >>= nBlur;
5659 Sequence< PropertyValue > aShadowAttribsGrabBag{
5660 comphelper::makePropertyValue("dist", lcl_CalculateDist(dX, dY)),
5661 comphelper::makePropertyValue("dir", lcl_CalculateDir(dX, dY)),
5662 comphelper::makePropertyValue("blurRad", oox::drawingml::convertHmmToEmu(nBlur)),
5663 comphelper::makePropertyValue("rotWithShape", false) //ooxml default is 'true', so must write it
5666 Sequence< PropertyValue > aShadowGrabBag{
5667 comphelper::makePropertyValue("Attribs", aShadowAttribsGrabBag),
5668 comphelper::makePropertyValue("RgbClr", rXPropSet->getPropertyValue( "ShadowColor" )),
5669 comphelper::makePropertyValue("RgbClrTransparency", rXPropSet->getPropertyValue( "ShadowTransparence" ))
5672 WriteShapeEffect( u"outerShdw", aShadowGrabBag );
5674 WriteSoftEdgeEffect(rXPropSet);
5675 mpFS->endElementNS(XML_a, XML_effectLst);
5678 else
5680 for( auto& rOuterShdwProp : asNonConstRange(aOuterShdwProps) )
5682 if( rOuterShdwProp.Name == "Attribs" )
5684 Sequence< PropertyValue > aAttribsProps;
5685 rOuterShdwProp.Value >>= aAttribsProps;
5687 double dX = +0.0, dY = +0.0;
5688 sal_Int32 nBlur =0;
5689 rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX;
5690 rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY;
5691 rXPropSet->getPropertyValue( "ShadowBlur" ) >>= nBlur;
5694 for( auto& rAttribsProp : asNonConstRange(aAttribsProps) )
5696 if( rAttribsProp.Name == "dist" )
5698 rAttribsProp.Value <<= lcl_CalculateDist(dX, dY);
5700 else if( rAttribsProp.Name == "dir" )
5702 rAttribsProp.Value <<= lcl_CalculateDir(dX, dY);
5704 else if( rAttribsProp.Name == "blurRad" )
5706 rAttribsProp.Value <<= oox::drawingml::convertHmmToEmu(nBlur);
5710 rOuterShdwProp.Value <<= aAttribsProps;
5712 else if( rOuterShdwProp.Name == "RgbClr" )
5714 rOuterShdwProp.Value = rXPropSet->getPropertyValue( "ShadowColor" );
5716 else if( rOuterShdwProp.Name == "RgbClrTransparency" )
5718 rOuterShdwProp.Value = rXPropSet->getPropertyValue( "ShadowTransparence" );
5722 mpFS->startElementNS(XML_a, XML_effectLst);
5723 bool bGlowWritten = false;
5724 for( const auto& rEffect : std::as_const(aEffects) )
5726 if (!bGlowWritten
5727 && (rEffect.Name == "innerShdw" || rEffect.Name == "outerShdw"
5728 || rEffect.Name == "prstShdw" || rEffect.Name == "reflection"
5729 || rEffect.Name == "softEdge"))
5731 WriteGlowEffect(rXPropSet);
5732 bGlowWritten = true;
5735 if( rEffect.Name == "outerShdw" )
5737 WriteShapeEffect( rEffect.Name, aOuterShdwProps );
5739 else
5741 Sequence< PropertyValue > aEffectProps;
5742 rEffect.Value >>= aEffectProps;
5743 WriteShapeEffect( rEffect.Name, aEffectProps );
5746 if (!bGlowWritten)
5747 WriteGlowEffect(rXPropSet);
5748 WriteSoftEdgeEffect(rXPropSet); // the last
5750 mpFS->endElementNS(XML_a, XML_effectLst);
5754 void DrawingML::WriteGlowEffect(const Reference< XPropertySet >& rXPropSet)
5756 if (!rXPropSet->getPropertySetInfo()->hasPropertyByName("GlowEffectRadius"))
5758 return;
5761 sal_Int32 nRad = 0;
5762 rXPropSet->getPropertyValue("GlowEffectRadius") >>= nRad;
5763 if (!nRad)
5764 return;
5766 Sequence< PropertyValue > aGlowAttribs{ comphelper::makePropertyValue(
5767 "rad", oox::drawingml::convertHmmToEmu(nRad)) };
5768 Sequence< PropertyValue > aGlowProps{
5769 comphelper::makePropertyValue("Attribs", aGlowAttribs),
5770 comphelper::makePropertyValue("RgbClr", rXPropSet->getPropertyValue("GlowEffectColor")),
5771 comphelper::makePropertyValue("RgbClrTransparency", rXPropSet->getPropertyValue("GlowEffectTransparency"))
5773 // TODO other stuff like saturation or luminance
5775 WriteShapeEffect(u"glow", aGlowProps);
5778 void DrawingML::WriteSoftEdgeEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet)
5780 if (!rXPropSet->getPropertySetInfo()->hasPropertyByName("SoftEdgeRadius"))
5782 return;
5785 sal_Int32 nRad = 0;
5786 rXPropSet->getPropertyValue("SoftEdgeRadius") >>= nRad;
5787 if (!nRad)
5788 return;
5790 css::uno::Sequence<css::beans::PropertyValue> aAttribs{ comphelper::makePropertyValue(
5791 "rad", oox::drawingml::convertHmmToEmu(nRad)) };
5792 css::uno::Sequence<css::beans::PropertyValue> aProps{ comphelper::makePropertyValue("Attribs",
5793 aAttribs) };
5795 WriteShapeEffect(u"softEdge", aProps);
5798 void DrawingML::Write3DEffects( const Reference< XPropertySet >& xPropSet, bool bIsText )
5800 // check existence of the grab bag
5801 if( !GetProperty( xPropSet, "InteropGrabBag" ) )
5802 return;
5804 // extract the relevant properties from the grab bag
5805 Sequence< PropertyValue > aGrabBag, aEffectProps, aLightRigProps, aShape3DProps;
5806 mAny >>= aGrabBag;
5808 auto pShapeProp = std::find_if( std::cbegin(aGrabBag), std::cend(aGrabBag),
5809 [bIsText](const PropertyValue& rProp)
5810 { return rProp.Name == (bIsText ? u"Text3DEffectProperties" : u"3DEffectProperties"); });
5811 if (pShapeProp != std::cend(aGrabBag))
5813 Sequence< PropertyValue > a3DEffectProps;
5814 pShapeProp->Value >>= a3DEffectProps;
5815 for( const auto& r3DEffectProp : std::as_const(a3DEffectProps) )
5817 if( r3DEffectProp.Name == "Camera" )
5818 r3DEffectProp.Value >>= aEffectProps;
5819 else if( r3DEffectProp.Name == "LightRig" )
5820 r3DEffectProp.Value >>= aLightRigProps;
5821 else if( r3DEffectProp.Name == "Shape3D" )
5822 r3DEffectProp.Value >>= aShape3DProps;
5826 if( !aEffectProps.hasElements() && !aLightRigProps.hasElements() && !aShape3DProps.hasElements() )
5827 return;
5829 bool bCameraRotationPresent = false;
5830 rtl::Reference<sax_fastparser::FastAttributeList> aCameraAttrList = FastSerializerHelper::createAttrList();
5831 rtl::Reference<sax_fastparser::FastAttributeList> aCameraRotationAttrList = FastSerializerHelper::createAttrList();
5832 for( const auto& rEffectProp : std::as_const(aEffectProps) )
5834 if( rEffectProp.Name == "prst" )
5836 OUString sVal;
5837 rEffectProp.Value >>= sVal;
5838 aCameraAttrList->add(XML_prst, sVal);
5840 else if( rEffectProp.Name == "fov" )
5842 float fVal = 0;
5843 rEffectProp.Value >>= fVal;
5844 aCameraAttrList->add( XML_fov, OString::number( fVal * 60000 ) );
5846 else if( rEffectProp.Name == "zoom" )
5848 float fVal = 1;
5849 rEffectProp.Value >>= fVal;
5850 aCameraAttrList->add( XML_zoom, OString::number( fVal * 100000 ) );
5852 else if( rEffectProp.Name == "rotLat" ||
5853 rEffectProp.Name == "rotLon" ||
5854 rEffectProp.Name == "rotRev" )
5856 sal_Int32 nVal = 0, nToken = XML_none;
5857 rEffectProp.Value >>= nVal;
5858 if( rEffectProp.Name == "rotLat" )
5859 nToken = XML_lat;
5860 else if( rEffectProp.Name == "rotLon" )
5861 nToken = XML_lon;
5862 else if( rEffectProp.Name == "rotRev" )
5863 nToken = XML_rev;
5864 aCameraRotationAttrList->add( nToken, OString::number( nVal ) );
5865 bCameraRotationPresent = true;
5869 bool bLightRigRotationPresent = false;
5870 rtl::Reference<sax_fastparser::FastAttributeList> aLightRigAttrList = FastSerializerHelper::createAttrList();
5871 rtl::Reference<sax_fastparser::FastAttributeList> aLightRigRotationAttrList = FastSerializerHelper::createAttrList();
5872 for( const auto& rLightRigProp : std::as_const(aLightRigProps) )
5874 if( rLightRigProp.Name == "rig" || rLightRigProp.Name == "dir" )
5876 OUString sVal;
5877 sal_Int32 nToken = XML_none;
5878 rLightRigProp.Value >>= sVal;
5879 if( rLightRigProp.Name == "rig" )
5880 nToken = XML_rig;
5881 else if( rLightRigProp.Name == "dir" )
5882 nToken = XML_dir;
5883 aLightRigAttrList->add(nToken, sVal);
5885 else if( rLightRigProp.Name == "rotLat" ||
5886 rLightRigProp.Name == "rotLon" ||
5887 rLightRigProp.Name == "rotRev" )
5889 sal_Int32 nVal = 0, nToken = XML_none;
5890 rLightRigProp.Value >>= nVal;
5891 if( rLightRigProp.Name == "rotLat" )
5892 nToken = XML_lat;
5893 else if( rLightRigProp.Name == "rotLon" )
5894 nToken = XML_lon;
5895 else if( rLightRigProp.Name == "rotRev" )
5896 nToken = XML_rev;
5897 aLightRigRotationAttrList->add( nToken, OString::number( nVal ) );
5898 bLightRigRotationPresent = true;
5902 mpFS->startElementNS(XML_a, XML_scene3d);
5904 if( aEffectProps.hasElements() )
5906 mpFS->startElementNS( XML_a, XML_camera, aCameraAttrList );
5907 if( bCameraRotationPresent )
5909 mpFS->singleElementNS( XML_a, XML_rot, aCameraRotationAttrList );
5911 mpFS->endElementNS( XML_a, XML_camera );
5913 else
5915 // a:camera with Word default values - Word won't open the document if this is not present
5916 mpFS->singleElementNS(XML_a, XML_camera, XML_prst, "orthographicFront");
5919 if( aEffectProps.hasElements() )
5921 mpFS->startElementNS( XML_a, XML_lightRig, aLightRigAttrList );
5922 if( bLightRigRotationPresent )
5924 mpFS->singleElementNS( XML_a, XML_rot, aLightRigRotationAttrList );
5926 mpFS->endElementNS( XML_a, XML_lightRig );
5928 else
5930 // a:lightRig with Word default values - Word won't open the document if this is not present
5931 mpFS->singleElementNS(XML_a, XML_lightRig, XML_rig, "threePt", XML_dir, "t");
5934 mpFS->endElementNS( XML_a, XML_scene3d );
5936 if( !aShape3DProps.hasElements() )
5937 return;
5939 bool bBevelTPresent = false, bBevelBPresent = false;
5940 Sequence< PropertyValue > aExtrusionColorProps, aContourColorProps;
5941 rtl::Reference<sax_fastparser::FastAttributeList> aBevelTAttrList = FastSerializerHelper::createAttrList();
5942 rtl::Reference<sax_fastparser::FastAttributeList> aBevelBAttrList = FastSerializerHelper::createAttrList();
5943 rtl::Reference<sax_fastparser::FastAttributeList> aShape3DAttrList = FastSerializerHelper::createAttrList();
5944 for( const auto& rShape3DProp : std::as_const(aShape3DProps) )
5946 if( rShape3DProp.Name == "extrusionH" || rShape3DProp.Name == "contourW" || rShape3DProp.Name == "z" )
5948 sal_Int32 nVal = 0, nToken = XML_none;
5949 rShape3DProp.Value >>= nVal;
5950 if( rShape3DProp.Name == "extrusionH" )
5951 nToken = XML_extrusionH;
5952 else if( rShape3DProp.Name == "contourW" )
5953 nToken = XML_contourW;
5954 else if( rShape3DProp.Name == "z" )
5955 nToken = XML_z;
5956 aShape3DAttrList->add( nToken, OString::number( nVal ) );
5958 else if( rShape3DProp.Name == "prstMaterial" )
5960 OUString sVal;
5961 rShape3DProp.Value >>= sVal;
5962 aShape3DAttrList->add(XML_prstMaterial, sVal);
5964 else if( rShape3DProp.Name == "extrusionClr" )
5966 rShape3DProp.Value >>= aExtrusionColorProps;
5968 else if( rShape3DProp.Name == "contourClr" )
5970 rShape3DProp.Value >>= aContourColorProps;
5972 else if( rShape3DProp.Name == "bevelT" || rShape3DProp.Name == "bevelB" )
5974 Sequence< PropertyValue > aBevelProps;
5975 rShape3DProp.Value >>= aBevelProps;
5976 if ( !aBevelProps.hasElements() )
5977 continue;
5979 rtl::Reference<sax_fastparser::FastAttributeList> aBevelAttrList;
5980 if( rShape3DProp.Name == "bevelT" )
5982 bBevelTPresent = true;
5983 aBevelAttrList = aBevelTAttrList;
5985 else
5987 bBevelBPresent = true;
5988 aBevelAttrList = aBevelBAttrList;
5990 for( const auto& rBevelProp : std::as_const(aBevelProps) )
5992 if( rBevelProp.Name == "w" || rBevelProp.Name == "h" )
5994 sal_Int32 nVal = 0, nToken = XML_none;
5995 rBevelProp.Value >>= nVal;
5996 if( rBevelProp.Name == "w" )
5997 nToken = XML_w;
5998 else if( rBevelProp.Name == "h" )
5999 nToken = XML_h;
6000 aBevelAttrList->add( nToken, OString::number( nVal ) );
6002 else if( rBevelProp.Name == "prst" )
6004 OUString sVal;
6005 rBevelProp.Value >>= sVal;
6006 aBevelAttrList->add(XML_prst, sVal);
6013 mpFS->startElementNS( XML_a, XML_sp3d, aShape3DAttrList );
6014 if( bBevelTPresent )
6016 mpFS->singleElementNS( XML_a, XML_bevelT, aBevelTAttrList );
6018 if( bBevelBPresent )
6020 mpFS->singleElementNS( XML_a, XML_bevelB, aBevelBAttrList );
6022 if( aExtrusionColorProps.hasElements() )
6024 OUString sSchemeClr;
6025 ::Color nColor;
6026 sal_Int32 nTransparency(0);
6027 Sequence< PropertyValue > aColorTransformations;
6028 for( const auto& rExtrusionColorProp : std::as_const(aExtrusionColorProps) )
6030 if( rExtrusionColorProp.Name == "schemeClr" )
6031 rExtrusionColorProp.Value >>= sSchemeClr;
6032 else if( rExtrusionColorProp.Name == "schemeClrTransformations" )
6033 rExtrusionColorProp.Value >>= aColorTransformations;
6034 else if( rExtrusionColorProp.Name == "rgbClr" )
6035 rExtrusionColorProp.Value >>= nColor;
6036 else if( rExtrusionColorProp.Name == "rgbClrTransparency" )
6037 rExtrusionColorProp.Value >>= nTransparency;
6039 mpFS->startElementNS(XML_a, XML_extrusionClr);
6041 if( sSchemeClr.isEmpty() )
6042 WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
6043 else
6044 WriteColor( sSchemeClr, aColorTransformations );
6046 mpFS->endElementNS( XML_a, XML_extrusionClr );
6048 if( aContourColorProps.hasElements() )
6050 OUString sSchemeClr;
6051 ::Color nColor;
6052 sal_Int32 nTransparency(0);
6053 Sequence< PropertyValue > aColorTransformations;
6054 for( const auto& rContourColorProp : std::as_const(aContourColorProps) )
6056 if( rContourColorProp.Name == "schemeClr" )
6057 rContourColorProp.Value >>= sSchemeClr;
6058 else if( rContourColorProp.Name == "schemeClrTransformations" )
6059 rContourColorProp.Value >>= aColorTransformations;
6060 else if( rContourColorProp.Name == "rgbClr" )
6061 rContourColorProp.Value >>= nColor;
6062 else if( rContourColorProp.Name == "rgbClrTransparency" )
6063 rContourColorProp.Value >>= nTransparency;
6065 mpFS->startElementNS(XML_a, XML_contourClr);
6067 if( sSchemeClr.isEmpty() )
6068 WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
6069 else
6070 WriteColor( sSchemeClr, aContourColorProps );
6072 mpFS->endElementNS( XML_a, XML_contourClr );
6074 mpFS->endElementNS( XML_a, XML_sp3d );
6077 void DrawingML::WriteArtisticEffect( const Reference< XPropertySet >& rXPropSet )
6079 if( !GetProperty( rXPropSet, "InteropGrabBag" ) )
6080 return;
6082 PropertyValue aEffect;
6083 Sequence< PropertyValue > aGrabBag;
6084 mAny >>= aGrabBag;
6085 auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
6086 [](const PropertyValue& rProp) { return rProp.Name == "ArtisticEffectProperties"; });
6087 if (pProp != std::cend(aGrabBag))
6088 pProp->Value >>= aEffect;
6089 sal_Int32 nEffectToken = ArtisticEffectProperties::getEffectToken( aEffect.Name );
6090 if( nEffectToken == XML_none )
6091 return;
6093 Sequence< PropertyValue > aAttrs;
6094 aEffect.Value >>= aAttrs;
6095 rtl::Reference<sax_fastparser::FastAttributeList> aAttrList = FastSerializerHelper::createAttrList();
6096 OString sRelId;
6097 for( const auto& rAttr : std::as_const(aAttrs) )
6099 sal_Int32 nToken = ArtisticEffectProperties::getEffectToken( rAttr.Name );
6100 if( nToken != XML_none )
6102 sal_Int32 nVal = 0;
6103 rAttr.Value >>= nVal;
6104 aAttrList->add( nToken, OString::number( nVal ) );
6106 else if( rAttr.Name == "OriginalGraphic" )
6108 Sequence< PropertyValue > aGraphic;
6109 rAttr.Value >>= aGraphic;
6110 Sequence< sal_Int8 > aGraphicData;
6111 OUString sGraphicId;
6112 for( const auto& rProp : std::as_const(aGraphic) )
6114 if( rProp.Name == "Id" )
6115 rProp.Value >>= sGraphicId;
6116 else if( rProp.Name == "Data" )
6117 rProp.Value >>= aGraphicData;
6119 sRelId = WriteWdpPicture( sGraphicId, aGraphicData );
6123 mpFS->startElementNS(XML_a, XML_extLst);
6124 mpFS->startElementNS(XML_a, XML_ext, XML_uri, "{BEBA8EAE-BF5A-486C-A8C5-ECC9F3942E4B}");
6125 mpFS->startElementNS( XML_a14, XML_imgProps,
6126 FSNS(XML_xmlns, XML_a14), mpFB->getNamespaceURL(OOX_NS(a14)) );
6127 mpFS->startElementNS(XML_a14, XML_imgLayer, FSNS(XML_r, XML_embed), sRelId);
6128 mpFS->startElementNS(XML_a14, XML_imgEffect);
6130 mpFS->singleElementNS( XML_a14, nEffectToken, aAttrList );
6132 mpFS->endElementNS( XML_a14, XML_imgEffect );
6133 mpFS->endElementNS( XML_a14, XML_imgLayer );
6134 mpFS->endElementNS( XML_a14, XML_imgProps );
6135 mpFS->endElementNS( XML_a, XML_ext );
6136 mpFS->endElementNS( XML_a, XML_extLst );
6139 OString DrawingML::WriteWdpPicture( const OUString& rFileId, const Sequence< sal_Int8 >& rPictureData )
6141 auto& rGraphicExportCache = GraphicExportCache::get();
6143 OUString aId = rGraphicExportCache.findWdpID(rFileId);
6144 if (!aId.isEmpty())
6145 return OUStringToOString(aId, RTL_TEXTENCODING_UTF8);
6147 sal_Int32 nWdpImageCount = rGraphicExportCache.nextWdpImageCount();
6148 OUString sFileName = "media/hdphoto" + OUString::number(nWdpImageCount) + ".wdp";
6149 OUString sFragment = OUStringBuffer().appendAscii(GetComponentDir()).append( "/" + sFileName).makeStringAndClear();
6150 Reference< XOutputStream > xOutStream = mpFB->openFragmentStream(sFragment, "image/vnd.ms-photo");
6151 xOutStream->writeBytes( rPictureData );
6152 xOutStream->closeOutput();
6154 aId = mpFB->addRelation( mpFS->getOutputStream(),
6155 oox::getRelationship(Relationship::HDPHOTO),
6156 OUStringBuffer()
6157 .appendAscii( GetRelationCompPrefix() )
6158 .append( sFileName ) );
6160 rGraphicExportCache.addToWdpCache(rFileId, aId);
6162 return OUStringToOString(aId, RTL_TEXTENCODING_UTF8);
6165 void DrawingML::WriteDiagram(const css::uno::Reference<css::drawing::XShape>& rXShape, int nDiagramId)
6167 uno::Reference<beans::XPropertySet> xPropSet(rXShape, uno::UNO_QUERY);
6169 uno::Reference<xml::dom::XDocument> dataDom;
6170 uno::Reference<xml::dom::XDocument> layoutDom;
6171 uno::Reference<xml::dom::XDocument> styleDom;
6172 uno::Reference<xml::dom::XDocument> colorDom;
6173 uno::Reference<xml::dom::XDocument> drawingDom;
6174 uno::Sequence<uno::Sequence<uno::Any>> xDataRelSeq;
6175 uno::Sequence<uno::Any> diagramDrawing;
6177 // retrieve the doms from the GrabBag
6178 uno::Sequence<beans::PropertyValue> propList;
6179 xPropSet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= propList;
6180 for (const auto& rProp : std::as_const(propList))
6182 OUString propName = rProp.Name;
6183 if (propName == "OOXData")
6184 rProp.Value >>= dataDom;
6185 else if (propName == "OOXLayout")
6186 rProp.Value >>= layoutDom;
6187 else if (propName == "OOXStyle")
6188 rProp.Value >>= styleDom;
6189 else if (propName == "OOXColor")
6190 rProp.Value >>= colorDom;
6191 else if (propName == "OOXDrawing")
6193 rProp.Value >>= diagramDrawing;
6194 diagramDrawing[0]
6195 >>= drawingDom; // if there is OOXDrawing property then set drawingDom here only.
6197 else if (propName == "OOXDiagramDataRels")
6198 rProp.Value >>= xDataRelSeq;
6201 // check that we have the 4 mandatory XDocuments
6202 // if not, there was an error importing and we won't output anything
6203 if (!dataDom.is() || !layoutDom.is() || !styleDom.is() || !colorDom.is())
6204 return;
6206 // generate a unique id
6207 rtl::Reference<sax_fastparser::FastAttributeList> pDocPrAttrList
6208 = sax_fastparser::FastSerializerHelper::createAttrList();
6209 pDocPrAttrList->add(XML_id, OString::number(nDiagramId));
6210 OString sName = "Diagram" + OString::number(nDiagramId);
6211 pDocPrAttrList->add(XML_name, sName);
6213 if (GetDocumentType() == DOCUMENT_DOCX)
6215 mpFS->singleElementNS(XML_wp, XML_docPr, pDocPrAttrList);
6216 mpFS->singleElementNS(XML_wp, XML_cNvGraphicFramePr);
6218 mpFS->startElementNS(XML_a, XML_graphic, FSNS(XML_xmlns, XML_a),
6219 mpFB->getNamespaceURL(OOX_NS(dml)));
6221 else
6223 mpFS->startElementNS(XML_p, XML_nvGraphicFramePr);
6225 mpFS->singleElementNS(XML_p, XML_cNvPr, pDocPrAttrList);
6226 mpFS->singleElementNS(XML_p, XML_cNvGraphicFramePr);
6228 mpFS->startElementNS(XML_p, XML_nvPr);
6229 mpFS->startElementNS(XML_p, XML_extLst);
6230 // change tracking extension - required in PPTX
6231 mpFS->startElementNS(XML_p, XML_ext, XML_uri, "{D42A27DB-BD31-4B8C-83A1-F6EECF244321}");
6232 mpFS->singleElementNS(XML_p14, XML_modId,
6233 FSNS(XML_xmlns, XML_p14), mpFB->getNamespaceURL(OOX_NS(p14)),
6234 XML_val,
6235 OString::number(comphelper::rng::uniform_uint_distribution(1, SAL_MAX_UINT32)));
6236 mpFS->endElementNS(XML_p, XML_ext);
6237 mpFS->endElementNS(XML_p, XML_extLst);
6238 mpFS->endElementNS(XML_p, XML_nvPr);
6240 mpFS->endElementNS(XML_p, XML_nvGraphicFramePr);
6242 // store size and position of background shape instead of group shape
6243 // as some shapes may be outside
6244 css::uno::Reference<css::drawing::XShapes> xShapes(rXShape, uno::UNO_QUERY);
6245 if (xShapes.is() && xShapes->hasElements())
6247 css::uno::Reference<css::drawing::XShape> xShapeBg(xShapes->getByIndex(0),
6248 uno::UNO_QUERY);
6249 awt::Point aPos = xShapeBg->getPosition();
6250 awt::Size aSize = xShapeBg->getSize();
6251 WriteTransformation(
6252 xShapeBg, tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)),
6253 XML_p, false, false, 0, false);
6256 mpFS->startElementNS(XML_a, XML_graphic);
6259 mpFS->startElementNS(XML_a, XML_graphicData, XML_uri,
6260 "http://schemas.openxmlformats.org/drawingml/2006/diagram");
6262 OUString sRelationCompPrefix = OUString::createFromAscii(GetRelationCompPrefix());
6264 // add data relation
6265 OUString dataFileName = "diagrams/data" + OUString::number(nDiagramId) + ".xml";
6266 OUString dataRelId =
6267 mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDATA),
6268 Concat2View(sRelationCompPrefix + dataFileName));
6270 // add layout relation
6271 OUString layoutFileName = "diagrams/layout" + OUString::number(nDiagramId) + ".xml";
6272 OUString layoutRelId = mpFB->addRelation(mpFS->getOutputStream(),
6273 oox::getRelationship(Relationship::DIAGRAMLAYOUT),
6274 Concat2View(sRelationCompPrefix + layoutFileName));
6276 // add style relation
6277 OUString styleFileName = "diagrams/quickStyle" + OUString::number(nDiagramId) + ".xml";
6278 OUString styleRelId = mpFB->addRelation(mpFS->getOutputStream(),
6279 oox::getRelationship(Relationship::DIAGRAMQUICKSTYLE),
6280 Concat2View(sRelationCompPrefix + styleFileName));
6282 // add color relation
6283 OUString colorFileName = "diagrams/colors" + OUString::number(nDiagramId) + ".xml";
6284 OUString colorRelId = mpFB->addRelation(mpFS->getOutputStream(),
6285 oox::getRelationship(Relationship::DIAGRAMCOLORS),
6286 Concat2View(sRelationCompPrefix + colorFileName));
6288 OUString drawingFileName;
6289 if (drawingDom.is())
6291 // add drawing relation
6292 drawingFileName = "diagrams/drawing" + OUString::number(nDiagramId) + ".xml";
6293 OUString drawingRelId = mpFB->addRelation(
6294 mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDRAWING),
6295 Concat2View(sRelationCompPrefix + drawingFileName));
6297 // the data dom contains a reference to the drawing relation. We need to update it with the new generated
6298 // relation value before writing the dom to a file
6300 // Get the dsp:damaModelExt node from the dom
6301 uno::Reference<xml::dom::XNodeList> nodeList = dataDom->getElementsByTagNameNS(
6302 "http://schemas.microsoft.com/office/drawing/2008/diagram", "dataModelExt");
6304 // There must be one element only so get it
6305 uno::Reference<xml::dom::XNode> node = nodeList->item(0);
6307 // Get the list of attributes of the node
6308 uno::Reference<xml::dom::XNamedNodeMap> nodeMap = node->getAttributes();
6310 // Get the node with the relId attribute and set its new value
6311 uno::Reference<xml::dom::XNode> relIdNode = nodeMap->getNamedItem("relId");
6312 relIdNode->setNodeValue(drawingRelId);
6315 mpFS->singleElementNS(XML_dgm, XML_relIds,
6316 FSNS(XML_xmlns, XML_dgm), mpFB->getNamespaceURL(OOX_NS(dmlDiagram)),
6317 FSNS(XML_xmlns, XML_r), mpFB->getNamespaceURL(OOX_NS(officeRel)),
6318 FSNS(XML_r, XML_dm), dataRelId, FSNS(XML_r, XML_lo), layoutRelId,
6319 FSNS(XML_r, XML_qs), styleRelId, FSNS(XML_r, XML_cs), colorRelId);
6321 mpFS->endElementNS(XML_a, XML_graphicData);
6322 mpFS->endElementNS(XML_a, XML_graphic);
6324 uno::Reference<xml::sax::XSAXSerializable> serializer;
6325 uno::Reference<xml::sax::XWriter> writer
6326 = xml::sax::Writer::create(comphelper::getProcessComponentContext());
6328 OUString sDir = OUString::createFromAscii(GetComponentDir());
6330 // write data file
6331 serializer.set(dataDom, uno::UNO_QUERY);
6332 uno::Reference<io::XOutputStream> xDataOutputStream = mpFB->openFragmentStream(
6333 sDir + "/" + dataFileName,
6334 "application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml");
6335 writer->setOutputStream(xDataOutputStream);
6336 serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
6337 uno::Sequence<beans::StringPair>());
6339 // write the associated Images and rels for data file
6340 writeDiagramRels(xDataRelSeq, xDataOutputStream, u"OOXDiagramDataRels", nDiagramId);
6342 // write layout file
6343 serializer.set(layoutDom, uno::UNO_QUERY);
6344 writer->setOutputStream(mpFB->openFragmentStream(
6345 sDir + "/" + layoutFileName,
6346 "application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml"));
6347 serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
6348 uno::Sequence<beans::StringPair>());
6350 // write style file
6351 serializer.set(styleDom, uno::UNO_QUERY);
6352 writer->setOutputStream(mpFB->openFragmentStream(
6353 sDir + "/" + styleFileName,
6354 "application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml"));
6355 serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
6356 uno::Sequence<beans::StringPair>());
6358 // write color file
6359 serializer.set(colorDom, uno::UNO_QUERY);
6360 writer->setOutputStream(mpFB->openFragmentStream(
6361 sDir + "/" + colorFileName,
6362 "application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml"));
6363 serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
6364 uno::Sequence<beans::StringPair>());
6366 // write drawing file
6367 if (!drawingDom.is())
6368 return;
6370 serializer.set(drawingDom, uno::UNO_QUERY);
6371 uno::Reference<io::XOutputStream> xDrawingOutputStream = mpFB->openFragmentStream(
6372 sDir + "/" + drawingFileName, "application/vnd.ms-office.drawingml.diagramDrawing+xml");
6373 writer->setOutputStream(xDrawingOutputStream);
6374 serializer->serialize(
6375 uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
6376 uno::Sequence<beans::StringPair>());
6378 // write the associated Images and rels for drawing file
6379 uno::Sequence<uno::Sequence<uno::Any>> xDrawingRelSeq;
6380 diagramDrawing[1] >>= xDrawingRelSeq;
6381 writeDiagramRels(xDrawingRelSeq, xDrawingOutputStream, u"OOXDiagramDrawingRels", nDiagramId);
6384 void DrawingML::writeDiagramRels(const uno::Sequence<uno::Sequence<uno::Any>>& xRelSeq,
6385 const uno::Reference<io::XOutputStream>& xOutStream,
6386 std::u16string_view sGrabBagProperyName, int nDiagramId)
6388 // add image relationships of OOXData, OOXDiagram
6389 OUString sType(oox::getRelationship(Relationship::IMAGE));
6390 uno::Reference<xml::sax::XWriter> xWriter
6391 = xml::sax::Writer::create(comphelper::getProcessComponentContext());
6392 xWriter->setOutputStream(xOutStream);
6394 // retrieve the relationships from Sequence
6395 for (sal_Int32 j = 0; j < xRelSeq.getLength(); j++)
6397 // diagramDataRelTuple[0] => RID,
6398 // diagramDataRelTuple[1] => xInputStream
6399 // diagramDataRelTuple[2] => extension
6400 uno::Sequence<uno::Any> diagramDataRelTuple = xRelSeq[j];
6402 OUString sRelId;
6403 OUString sExtension;
6404 diagramDataRelTuple[0] >>= sRelId;
6405 diagramDataRelTuple[2] >>= sExtension;
6406 OUString sContentType;
6407 if (sExtension.equalsIgnoreAsciiCase(".WMF"))
6408 sContentType = "image/x-wmf";
6409 else
6410 sContentType = OUString::Concat("image/") + sExtension.subView(1);
6411 sRelId = sRelId.copy(3);
6413 StreamDataSequence dataSeq;
6414 diagramDataRelTuple[1] >>= dataSeq;
6415 uno::Reference<io::XInputStream> dataImagebin(
6416 new ::comphelper::SequenceInputStream(dataSeq));
6418 //nDiagramId is used to make the name unique irrespective of the number of smart arts.
6419 OUString sFragment = OUString::Concat("media/") + sGrabBagProperyName
6420 + OUString::number(nDiagramId) + "_"
6421 + OUString::number(j) + sExtension;
6423 PropertySet aProps(xOutStream);
6424 aProps.setAnyProperty(PROP_RelId, uno::Any(sRelId.toInt32()));
6426 mpFB->addRelation(xOutStream, sType, Concat2View("../" + sFragment));
6428 OUString sDir = OUString::createFromAscii(GetComponentDir());
6429 uno::Reference<io::XOutputStream> xBinOutStream
6430 = mpFB->openFragmentStream(sDir + "/" + sFragment, sContentType);
6434 comphelper::OStorageHelper::CopyInputToOutput(dataImagebin, xBinOutStream);
6436 catch (const uno::Exception&)
6438 TOOLS_WARN_EXCEPTION("oox.drawingml", "DrawingML::writeDiagramRels Failed to copy grabbaged Image");
6440 dataImagebin->closeInput();
6444 void DrawingML::WriteFromTo(const uno::Reference<css::drawing::XShape>& rXShape, const awt::Size& aPageSize,
6445 const FSHelperPtr& pDrawing)
6447 awt::Point aTopLeft = rXShape->getPosition();
6448 awt::Size aSize = rXShape->getSize();
6450 SdrObject* pObj = SdrObject::getSdrObjectFromXShape(rXShape);
6451 if (pObj)
6453 Degree100 nRotation = pObj->GetRotateAngle();
6454 if (nRotation)
6456 sal_Int16 nHalfWidth = aSize.Width / 2;
6457 sal_Int16 nHalfHeight = aSize.Height / 2;
6458 // aTopLeft needs correction for rotated customshapes
6459 if (pObj->GetObjIdentifier() == SdrObjKind::CustomShape)
6461 // Center of bounding box of the rotated shape
6462 const auto aSnapRectCenter(pObj->GetSnapRect().Center());
6463 aTopLeft.X = aSnapRectCenter.X() - nHalfWidth;
6464 aTopLeft.Y = aSnapRectCenter.Y() - nHalfHeight;
6467 // MSO changes the anchor positions at these angles and that does an extra 90 degrees
6468 // rotation on our shapes, so we output it in such position that MSO
6469 // can draw this shape correctly.
6470 if ((nRotation >= 4500_deg100 && nRotation < 13500_deg100) || (nRotation >= 22500_deg100 && nRotation < 31500_deg100))
6472 aTopLeft.X = aTopLeft.X - nHalfHeight + nHalfWidth;
6473 aTopLeft.Y = aTopLeft.Y - nHalfWidth + nHalfHeight;
6475 std::swap(aSize.Width, aSize.Height);
6480 tools::Rectangle aLocation(aTopLeft.X, aTopLeft.Y, aTopLeft.X + aSize.Width, aTopLeft.Y + aSize.Height);
6481 double nXpos = static_cast<double>(aLocation.TopLeft().getX()) / static_cast<double>(aPageSize.Width);
6482 double nYpos = static_cast<double>(aLocation.TopLeft().getY()) / static_cast<double>(aPageSize.Height);
6484 pDrawing->startElement(FSNS(XML_cdr, XML_from));
6485 pDrawing->startElement(FSNS(XML_cdr, XML_x));
6486 pDrawing->write(nXpos);
6487 pDrawing->endElement(FSNS(XML_cdr, XML_x));
6488 pDrawing->startElement(FSNS(XML_cdr, XML_y));
6489 pDrawing->write(nYpos);
6490 pDrawing->endElement(FSNS(XML_cdr, XML_y));
6491 pDrawing->endElement(FSNS(XML_cdr, XML_from));
6493 nXpos = static_cast<double>(aLocation.BottomRight().getX()) / static_cast<double>(aPageSize.Width);
6494 nYpos = static_cast<double>(aLocation.BottomRight().getY()) / static_cast<double>(aPageSize.Height);
6496 pDrawing->startElement(FSNS(XML_cdr, XML_to));
6497 pDrawing->startElement(FSNS(XML_cdr, XML_x));
6498 pDrawing->write(nXpos);
6499 pDrawing->endElement(FSNS(XML_cdr, XML_x));
6500 pDrawing->startElement(FSNS(XML_cdr, XML_y));
6501 pDrawing->write(nYpos);
6502 pDrawing->endElement(FSNS(XML_cdr, XML_y));
6503 pDrawing->endElement(FSNS(XML_cdr, XML_to));
6508 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */