1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
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
;
156 const char* g_aPredefinedClrNames
[] = {
172 namespace oox::drawingml
{
174 URLTransformer::~URLTransformer()
178 OUString
URLTransformer::getTransformedString(const OUString
& rString
) const
183 bool URLTransformer::isExternalURL(const OUString
& rURL
) const
185 bool bExternal
= true;
186 if (rURL
.startsWith("#"))
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
);
205 if (!xNameAccess
->hasByName(rDashName
))
206 return css::uno::Any();
208 return xNameAccess
->getByName(rDashName
);
211 return css::uno::Any();
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
);
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
)
266 return css::i18n::ScriptType::LATIN
;
269 void DrawingML::ResetMlCounters()
271 mnDrawingMLCount
= 0;
276 bool DrawingML::GetProperty( const Reference
< XPropertySet
>& rXPropertySet
, const OUString
& aName
)
280 mAny
= rXPropertySet
->getPropertyValue(aName
);
284 catch( const Exception
& )
286 /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
291 bool DrawingML::GetPropertyAndState( const Reference
< XPropertySet
>& rXPropertySet
, const Reference
< XPropertyState
>& rXPropertyState
, const OUString
& aName
, PropertyState
& eState
)
295 mAny
= rXPropertySet
->getPropertyValue(aName
);
298 eState
= rXPropertyState
->getPropertyState(aName
);
302 catch( const Exception
& )
304 /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
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();
329 sColor
= sBuf
.toString();
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
);
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() )
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
);
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
);
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
));
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
)
440 if ( !GetProperty( rXPropSet
, "FillColor" ) )
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
;
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;
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
);
527 // the shape had a custom color and the user didn't change it
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
))
538 uno::Reference
<util::XComplexColor
> xComplexColor
;
539 xPropertySet
->getPropertyValue(rPropertyName
) >>= xComplexColor
;
540 if (!xComplexColor
.is())
543 auto aComplexColor
= model::color::getFromXComplexColor(xComplexColor
);
544 if (aComplexColor
.getSchemeType() == model::ThemeColorType::Unknown
)
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));
556 case model::TransformationType::LumOff
:
557 mpFS
->singleElementNS(XML_a
, XML_lumOff
, XML_val
, OString::number(rTransform
.mnValue
* 10));
559 case model::TransformationType::Tint
:
560 mpFS
->singleElementNS(XML_a
, XML_tint
, XML_val
, OString::number(rTransform
.mnValue
* 10));
562 case model::TransformationType::Shade
:
563 mpFS
->singleElementNS(XML_a
, XML_shade
, XML_val
, OString::number(rTransform
.mnValue
* 10));
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
);
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)));
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"))
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
;
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
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
);
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;
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
;
696 sal_Int16 nTransparency
= 0;
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
;
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
);
722 WriteColor( sSchemeClr
, aTransformations
);
724 mpFS
->endElementNS( XML_a
, XML_gs
);
726 mpFS
->endElementNS( XML_a
, XML_gsLst
);
728 switch (rBGradient
.GetGradientStyle())
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));
738 case awt::GradientStyle_RADIAL
:
740 WriteGradientPath(rBGradient
, mpFS
, true);
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");
791 // Apply steps if used. That increases the number of stops and thus needs to be done before
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 (!)");
812 bool bLinearOrAxial(awt::GradientStyle_LINEAR
== pGradient
->GetGradientStyle()
813 || awt::GradientStyle_AXIAL
== pGradient
->GetGradientStyle());
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())
839 aCurrColor
->getStopOffset(),
840 aCurrColor
->getStopColor(),
841 aCurrAlpha
->getStopColor());
846 mpFS
->endElementNS( XML_a
, XML_gsLst
);
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));
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
) )
880 switch( nArrowLength
)
882 case ESCHER_LineShortArrow
:
886 case ESCHER_LineMediumLenArrow
:
889 case ESCHER_LineLongArrow
:
897 case ESCHER_LineNoEnd
:
900 case ESCHER_LineArrowEnd
:
903 case ESCHER_LineArrowStealthEnd
:
906 case ESCHER_LineArrowDiamondEnd
:
909 case ESCHER_LineArrowOvalEnd
:
912 case ESCHER_LineArrowOpenEnd
:
917 switch( nArrowWidth
)
919 case ESCHER_LineNarrowArrow
:
923 case ESCHER_LineMediumWidthArrow
:
926 case ESCHER_LineWideArrow
:
931 mpFS
->singleElementNS( XML_a
, bLineStart
? XML_headEnd
: XML_tailEnd
,
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"))
943 const LineCap aLineCap
= GetProperty(rXPropSet
, "LineCap") ? mAny
.get
<drawing::LineCap
>() : LineCap_BUTT
;
945 sal_uInt32 nLineWidth
= 0;
946 sal_uInt32 nEmuLineWidth
= 0;
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
;
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
;
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
;
1008 case drawing::LineStyle_NONE
:
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
);
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
);
1038 if (aLineDash
.Style
== DashStyle_ROUND
|| aLineDash
.Style
== DashStyle_ROUNDRELATIVE
)
1043 SAL_INFO("oox.shape", "dash dots: " << aLineDash
.Dots
<< " dashes: " << aLineDash
.Dashes
1044 << " dotlen: " << aLineDash
.DotLen
<< " dashlen: " << aLineDash
.DashLen
<< " distance: " << aLineDash
.Distance
);
1047 case drawing::LineStyle_SOLID
:
1049 if (GetProperty(rXPropSet
, "LineColor"))
1051 nColor
= ::Color(ColorTransparency
, mAny
.get
<sal_uInt32
>() & 0xffffff);
1054 if (GetProperty(rXPropSet
, "LineTransparence"))
1056 nColorAlpha
= MAX_PERCENT
- (mAny
.get
<sal_Int16
>() * PER_PERCENT
);
1058 if (aLineCap
== LineCap_ROUND
)
1060 else if (aLineCap
== LineCap_SQUARE
)
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
,
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
)));
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
);
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)
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.
1119 if (nDashLen
== 0 && aLineDash
.Dashes
> 0)
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");
1163 bIsConverted
= false;
1165 // Do not map our own line styles to OOXML prstDash values, because custDash gives better results.
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;
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)
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)
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)
1190 // Tweak dash length, see above.
1191 if (aLineCap
== LineCap_ROUND
&& fSp
> 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)
1207 // Tweak dash length, see above.
1208 if (aLineCap
== LineCap_ROUND
&& fSp
> 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
);
1252 case LineJoint_MIDDLE
:
1253 case LineJoint_MITER
:
1254 mpFS
->singleElementNS(XML_a
, XML_miter
);
1256 case LineJoint_ROUND
:
1257 mpFS
->singleElementNS(XML_a
, XML_round
);
1265 WriteLineArrow( rXPropSet
, true );
1266 WriteLineArrow( rXPropSet
, false );
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
)
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
));
1303 case model::BlipEffectType::AlphaCeiling
:
1305 mpFS
->singleElementNS(XML_a
, XML_alphaCeiling
);
1308 case model::BlipEffectType::AlphaFloor
:
1310 mpFS
->singleElementNS(XML_a
, XML_alphaFloor
);
1313 case model::BlipEffectType::AlphaInverse
:
1315 mpFS
->singleElementNS(XML_a
, XML_alphaInv
);
1316 // TODO: export rEffect.maColor1
1319 case model::BlipEffectType::AlphaModulate
:
1321 mpFS
->singleElementNS(XML_a
, XML_alphaMod
);
1325 case model::BlipEffectType::AlphaModulateFixed
:
1327 mpFS
->singleElementNS(XML_a
, XML_alphaModFix
, XML_amt
, OString::number(rEffect
.mnAmount
));
1330 case model::BlipEffectType::AlphaReplace
:
1332 mpFS
->singleElementNS(XML_a
, XML_alphaRepl
, XML_a
, OString::number(rEffect
.mnAlpha
));
1335 case model::BlipEffectType::BiLevel
:
1337 mpFS
->singleElementNS(XML_a
, XML_biLevel
, XML_thresh
, OString::number(rEffect
.mnThreshold
));
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");
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
);
1353 case model::BlipEffectType::ColorReplace
:
1355 mpFS
->startElementNS(XML_a
, XML_clrRepl
);
1356 mpFS
->endElementNS(XML_a
, XML_clrRepl
);
1359 case model::BlipEffectType::DuoTone
:
1361 mpFS
->startElementNS(XML_a
, XML_duotone
);
1362 mpFS
->endElementNS(XML_a
, XML_duotone
);
1365 case model::BlipEffectType::FillOverlay
:
1367 mpFS
->singleElementNS(XML_a
, XML_fillOverlay
);
1370 case model::BlipEffectType::Grayscale
:
1372 mpFS
->singleElementNS(XML_a
, XML_grayscl
);
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
));
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
));
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
));
1403 mpFS
->endElementNS(XML_a
, XML_blip
);
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
= "";
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";
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";
1441 case GfxLinkType::NativeJpg
:
1442 sMediaType
= "image/jpeg";
1443 pExtension
= ".jpeg";
1445 case GfxLinkType::NativePng
:
1446 sMediaType
= "image/png";
1447 pExtension
= ".png";
1449 case GfxLinkType::NativeTif
:
1450 sMediaType
= "image/tiff";
1451 pExtension
= ".tif";
1453 case GfxLinkType::NativeWmf
:
1454 sMediaType
= "image/x-wmf";
1455 pExtension
= ".wmf";
1457 case GfxLinkType::NativeMet
:
1458 sMediaType
= "image/x-met";
1459 pExtension
= ".met";
1461 case GfxLinkType::NativePct
:
1462 sMediaType
= "image/x-pict";
1463 pExtension
= ".pct";
1465 case GfxLinkType::NativeMov
:
1466 sMediaType
= "application/movie";
1467 pExtension
= ".MOV";
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";
1482 (void)GraphicConverter::Export(aStream
, rGraphic
, ConvertDataFormat::EMF
);
1483 sMediaType
= "image/x-emf";
1484 pExtension
= ".emf";
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.
1498 aData
= aStream
.GetData();
1499 nDataSize
= aStream
.GetEndOfData();
1504 sal_Int32 nImageCount
= rGraphicExportCache
.nextImageCount();
1505 Reference
<XOutputStream
> xOutStream
= mpFilterBase
->openFragmentStream(
1507 .appendAscii(getComponentDir(meDocumentType
))
1508 .append("/media/image" + OUString::number(nImageCount
))
1509 .appendAscii(pExtension
)
1510 .makeStringAndClear(),
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
= "../";
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
),
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
));
1551 OUString aExtension
;
1552 const OUString
& rURL(pMediaObj
->getURL());
1553 int nLastDot
= rURL
.lastIndexOf('.');
1555 aExtension
= rURL
.copy(nLastDot
);
1557 bool bEmbed
= rURL
.startsWith("vnd.sun.star.Package:");
1558 Relationship eMediaType
= Relationship::VIDEO
;
1561 #if HAVE_FEATURE_AVMEDIA
1562 OUString
aMimeType(pMediaObj
->getMediaProperties().getMimeType());
1564 OUString
aMimeType("none");
1566 if (aMimeType
.startsWith("audio/"))
1568 eMediaType
= Relationship::AUDIO
;
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
;
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
);
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
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));
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
)
1710 if (!rxGraphic
.is())
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
);
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
);
1742 case BitmapMode_STRETCH
:
1743 WriteXGraphicStretch(rXPropSet
, rxGraphic
);
1745 case BitmapMode_NO_REPEAT
:
1746 WriteXGraphicCustomPosition(rXPropSet
, rxGraphic
, rSize
);
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
);
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
) )
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
>>();
1782 xGraphic
.set(xBitmap
, uno::UNO_QUERY
);
1784 else if (mAny
.has
<uno::Reference
<graphic::XGraphic
>>())
1786 xGraphic
= mAny
.get
<uno::Reference
<graphic::XGraphic
>>();
1791 bool bWriteMode
= false;
1792 if (sURLPropName
== "FillBitmap" || sURLPropName
== "BackGraphic")
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() )
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
);
1818 WriteXGraphicBlipMode(rXPropSet
, rxGraphic
, rSize
);
1820 else if(GetProperty(rXPropSet
, "FillBitmapStretch"))
1822 bool bStretch
= mAny
.get
<bool>();
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
;
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" ) )
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"))
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
);
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
);
1938 mpFS
->startElementNS(XML_a
, XML_stretch
);
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()));
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
:
1978 case RectanglePoint_MIDDLE_TOP
:
1981 case RectanglePoint_RIGHT_TOP
:
1984 case RectanglePoint_LEFT_MIDDLE
:
1987 case RectanglePoint_MIDDLE_MIDDLE
:
1990 case RectanglePoint_RIGHT_MIDDLE
:
1993 case RectanglePoint_LEFT_BOTTOM
:
1996 case RectanglePoint_MIDDLE_BOTTOM
:
1999 case RectanglePoint_RIGHT_BOTTOM
:
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"))
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
2034 nSizeX
= double(nSizeX
) / aOriginalSize
.Width() * 100000;
2035 else if (nSizeX
< 0)
2041 sal_Int32 nSizeY
= 0;
2042 sal_Int32 nOffsetY
= 0;
2043 if (GetProperty(rXPropSet
, "FillBitmapSizeY"))
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
2056 nSizeY
= double(nSizeY
) / aOriginalSize
.Height() * 100000;
2057 else if (nSizeY
< 0)
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
);
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
));
2099 if (GetProperty(rXPropSet
, "FillBitmapSizeX"))
2105 nSizeX
= aOriginalSize
.Width();
2107 nSizeX
/= 100; // percentage
2112 if (GetProperty(rXPropSet
, "FillBitmapSizeY"))
2118 nSizeY
= aOriginalSize
.Height();
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;
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
);
2164 bool IsTopGroupObj(const uno::Reference
<drawing::XShape
>& xShape
)
2166 SdrObject
* pObject
= SdrObject::getSdrObjectFromXShape(xShape
);
2170 if (pObject
->getParentSdrObjectFromSdrObject())
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())
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 )
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"))
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
))
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();
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
);
2325 OUString sSlideName
= "#" + xNamed
->getName();
2326 if (rURL
== sSlideName
)
2328 sTarget
= "slide" + OUString::number(i
+ 1) + ".xml";
2332 if (sTarget
.isEmpty())
2334 sal_Int32 nSplit
= rURL
.lastIndexOf(' ');
2336 sTarget
= OUString::Concat("slide") + rURL
.subView(nSplit
+ 1) + ".xml";
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
)
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
:
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
:
2413 case awt::FontUnderline::DOUBLE
:
2416 case awt::FontUnderline::DOTTED
:
2417 underline
= "dotted";
2419 case awt::FontUnderline::DASH
:
2422 case awt::FontUnderline::LONGDASH
:
2423 underline
= "dashLong";
2425 case awt::FontUnderline::DASHDOT
:
2426 underline
= "dotDash";
2428 case awt::FontUnderline::DASHDOTDOT
:
2429 underline
= "dotDotDash";
2431 case awt::FontUnderline::WAVE
:
2434 case awt::FontUnderline::DOUBLEWAVE
:
2435 underline
= "wavyDbl";
2437 case awt::FontUnderline::BOLD
:
2438 underline
= "heavy";
2440 case awt::FontUnderline::BOLDDOTTED
:
2441 underline
= "dottedHeavy";
2443 case awt::FontUnderline::BOLDDASH
:
2444 underline
= "dashHeavy";
2446 case awt::FontUnderline::BOLDLONGDASH
:
2447 underline
= "dashLongHeavy";
2449 case awt::FontUnderline::BOLDDASHDOT
:
2450 underline
= "dotDashHeavy";
2452 case awt::FontUnderline::BOLDDASHDOTDOT
:
2453 underline
= "dotDotDashHeavy";
2455 case awt::FontUnderline::BOLDWAVE
:
2456 underline
= "wavyHeavy";
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";
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";
2483 case awt::FontStrikeout::DOUBLE
:
2484 strikeout
= "dblStrike";
2492 case css::i18n::ScriptType::ASIAN
:
2493 bLang
= GetProperty(rXPropSet
, "CharLocaleAsian"); break;
2494 case css::i18n::ScriptType::COMPLEX
:
2495 bLang
= GetProperty(rXPropSet
, "CharLocaleComplex"); break;
2497 bLang
= GetProperty(rXPropSet
, "CharLocale"); break;
2502 css::lang::Locale 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
:
2546 case CaseMap::SMALLCAPS
:
2552 mpFS
->startElementNS( XML_a
, nElement
,
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
,
2561 XML_baseline
, sax_fastparser::UseIf(OString::number(nCharEscapement
*1000), nCharEscapement
!= 0),
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
);
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
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.
2599 mpFS
->startElementNS(XML_a
, XML_ln
);
2600 if (color
== COL_AUTO
)
2602 mbIsBackgroundDark
? WriteSolidFill(COL_WHITE
) : WriteSolidFill(COL_BLACK
);
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
);
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
);
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
);
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
);
2699 && (GetPropertyAndState(rXPropSet
, rXPropState
, "CharFontNameComplex", eState
)
2700 && eState
== beans::PropertyState_DIRECT_VALUE
))
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
);
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"))
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
);
2743 = mpFB
->addRelation(mpFS
->getOutputStream(),
2744 bExtURL
? oox::getRelationship(Relationship::HYPERLINK
)
2745 : oox::getRelationship(Relationship::SLIDE
),
2749 mpFS
->singleElementNS(XML_a
, XML_hlinkClick
, FSNS(XML_r
, XML_id
), sRelId
);
2751 mpFS
->singleElementNS(XML_a
, XML_hlinkClick
, FSNS(XML_r
, XML_id
), sRelId
,
2752 XML_action
, "ppaction://hlinksldjump");
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" )
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
;
2826 case 0: aFieldValue
= "file"; // Path/File name
2828 case 1: aFieldValue
= "file1"; // Path
2830 case 2: aFieldValue
= "file2"; // File name without extension
2832 case 3: aFieldValue
= "file3"; // File name with extension
2835 else if(aFieldKind
== "Author")
2837 aFieldValue
= "author";
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
;
2860 case SvxDateFormat::StdSmall
:
2861 case SvxDateFormat::A
:
2862 aDateField
= "datetime";
2864 case SvxDateFormat::B
:
2865 aDateField
= "datetime1"; // 13/02/1996
2867 case SvxDateFormat::C
:
2868 aDateField
= "datetime5";
2870 case SvxDateFormat::D
:
2871 aDateField
= "datetime3"; // 13 February 1996
2873 case SvxDateFormat::StdBig
:
2874 case SvxDateFormat::E
:
2875 case SvxDateFormat::F
:
2876 aDateField
= "datetime2";
2882 OUString aTimeField
;
2885 case SvxTimeFormat::Standard
:
2886 case SvxTimeFormat::HH24_MM_SS
:
2887 case SvxTimeFormat::HH24_MM_SS_00
:
2888 aTimeField
= "datetime11"; // 13:49:38
2890 case SvxTimeFormat::HH24_MM
:
2891 aTimeField
= "datetime10"; // 13:49
2893 case SvxTimeFormat::HH12_MM
:
2894 case SvxTimeFormat::HH12_MM_AMPM
:
2895 aTimeField
= "datetime12"; // 01:49 PM
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
2907 if (!aDateField
.isEmpty() && aTimeField
.isEmpty())
2909 else if (!aTimeField
.isEmpty() && aDateField
.isEmpty())
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
2917 // only datetime format that has Date and HH:MM
2918 return "datetime8"; // dd/mm/yyyy H:MM
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"))
2933 bool bNumberingIsNumber
= true;
2934 if (GetProperty(rXPropSet
, "NumberingIsNumber"))
2935 mAny
>>= bNumberingIsNumber
;
2937 float nFontSize
= -1;
2938 if (GetProperty(rXPropSet
, "CharHeight"))
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() )
2952 sText
= sFieldValue
;
2954 if( sText
.isEmpty())
2956 Reference
< XPropertySet
> xPropSet( rRun
, UNO_QUERY
);
2960 if( !xPropSet
.is() || !( xPropSet
->getPropertyValue( "PlaceholderText" ) >>= sText
) )
2962 if( sText
.isEmpty() )
2965 catch (const Exception
&)
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
);
2982 mpFS
->singleElementNS(XML_a
, XML_br
);
2988 OString
sUUID(comphelper::xml::generateGUIDString());
2989 mpFS
->startElementNS( XML_a
, XML_fld
,
2990 XML_id
, sUUID
.getStr(),
2991 XML_type
, sFieldValue
);
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
);
3006 mpFS
->endElementNS( XML_a
, XML_fld
);
3008 mpFS
->endElementNS( XML_a
, XML_r
);
3012 static OUString
GetAutoNumType(SvxNumType nNumberingType
, bool bSDot
, bool bPBehind
, bool bPBoth
)
3014 OUString sPrefixSuffix
;
3017 sPrefixSuffix
= "ParenBoth";
3019 sPrefixSuffix
= "ParenR";
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";
3044 return "arabic" + sPrefixSuffix
;
3053 void DrawingML::WriteParagraphNumbering(const Reference
< XPropertySet
>& rXPropSet
, float fFirstCharHeight
, sal_Int16 nLevel
)
3055 if (nLevel
< 0 || !GetProperty(rXPropSet
, "NumberingRules"))
3058 Reference
< XIndexAccess
> rXIndexAccess
;
3060 if (!(mAny
>>= rXIndexAccess
) || nLevel
>= rXIndexAccess
->getCount())
3063 SAL_INFO("oox.shape", "numbering rules");
3065 Sequence
<PropertyValue
> aPropertySequence
;
3066 rXIndexAccess
->getByIndex(nLevel
) >>= aPropertySequence
;
3068 if (!aPropertySequence
.hasElements())
3071 SvxNumType nNumberingType
= SVX_NUM_NUMBER_NONE
;
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
) == ")")
3098 else if ( aPropName
== "Suffix" )
3100 auto s
= o3tl::doAccess
<OUString
>(rPropValue
.Value
);
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
)
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
);
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
;
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
);
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)));
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));
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
));
3243 switch (rTabStop
.Alignment
)
3245 case css::style::TabAlign_DECIMAL
:
3248 case css::style::TabAlign_RIGHT
:
3251 case css::style::TabAlign_CENTER
:
3254 case css::style::TabAlign_LEFT
:
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
)
3269 uno::Reference
<lang::XServiceInfo
> xServiceInfo(rXShape
, uno::UNO_QUERY_THROW
);
3270 bRet
= xServiceInfo
->supportsService("com.sun.star.drawing.GroupShape");
3275 sal_Int32
DrawingML::getBulletMarginIndentation (const Reference
< XPropertySet
>& rXPropSet
,sal_Int16 nLevel
, std::u16string_view propName
)
3277 if (nLevel
< 0 || !GetProperty(rXPropSet
, "NumberingRules"))
3280 Reference
< XIndexAccess
> rXIndexAccess
;
3282 if (!(mAny
>>= rXIndexAccess
) || nLevel
>= rXIndexAccess
->getCount())
3285 SAL_INFO("oox.shape", "numbering rules");
3287 Sequence
<PropertyValue
> aPropertySequence
;
3288 rXIndexAccess
->getByIndex(nLevel
) >>= aPropertySequence
;
3290 if (!aPropertySequence
.hasElements())
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
);
3304 const char* DrawingML::GetAlignment( style::ParagraphAdjust nAlignment
)
3306 const char* sAlignment
= nullptr;
3308 switch( nAlignment
)
3310 case style::ParagraphAdjust_CENTER
:
3313 case style::ParagraphAdjust_RIGHT
:
3316 case style::ParagraphAdjust_BLOCK
:
3317 sAlignment
= "just";
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)));
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() )
3356 sal_Int16 nLevel
= -1;
3357 if (GetProperty(rXPropSet
, "NumberingLevel"))
3360 bool bWriteNumbering
= true;
3361 bool bForceZeroIndent
= false;
3364 Reference
< text::XTextRange
> xParaText(rParagraph
, UNO_QUERY
);
3367 bool bNumberingOnThisLevel
= false;
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"))
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;
3401 if (GetProperty(rXPropSet
, "WritingMode"))
3403 sal_Int16 nWritingMode
;
3404 if( ( mAny
>>= nWritingMode
) && nWritingMode
== text::WritingMode2::RL_TB
)
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
)
3432 || nAlignment
!= style::ParagraphAdjust_LEFT
3433 || bHasLinespacing
))
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
));
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
);
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
);
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
);
3508 Reference
<XEnumeration
> xEnumeration(xAccess
->createEnumeration());
3509 if (!xEnumeration
.is())
3513 Reference
<XTextRange
> rRun
;
3515 if (!xEnumeration
->hasMoreElements())
3518 Any
aAny(xEnumeration
->nextElement());
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
);
3547 Reference
< XEnumeration
> enumeration( access
->createEnumeration() );
3548 if( !enumeration
.is() )
3551 mpFS
->startElementNS(XML_a
, XML_p
);
3553 bool bPropertiesWritten
= false;
3554 while( enumeration
->hasMoreElements() )
3556 Reference
< XTextRange
> run
;
3557 Any
any ( enumeration
->nextElement() );
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
;
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
);
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"))
3639 if (GetProperty(rXPropSet
, "TextRightDistance"))
3641 if (GetProperty(rXPropSet
, "TextUpperDistance"))
3643 if (GetProperty(rXPropSet
, "TextLowerDistance"))
3646 // Transform the text distance values so they are compatible with OOXML insets
3649 sal_Int32 nTextHeight
= xShape
->getSize().Height
; // Hmm, default
3651 // CustomShape can have text area different from shape rectangle
3653 = dynamic_cast<SdrObjCustomShape
*>(SdrObject::getSdrObjectFromXShape(xShape
));
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"))
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
;
3744 auto xTextFrame
= mpTextExport
->GetUnoTextFrame(xShape
);
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";
3757 case WritingMode2::BT_LR
:
3758 sWritingMode
= "vert270";
3760 case WritingMode2::TB_RL90
:
3761 sWritingMode
= "vert";
3763 case WritingMode2::TB_LR
:
3764 sWritingMode
= "mongolianVert";
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")
3794 aProp
.Value
>>= sValue
;
3795 sHorzOverflow
= sValue
;
3797 else if (aProp
.Name
== "vertOverflow")
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
)
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.
3852 // User changes. Keep current angles.
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"
3876 nTextRotateAngleDeg100
3877 = NormAngle18000(nTextRotateAngleDeg100
.value_or(0_deg100
) + 18000_deg100
);
3878 #if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12
3879 #pragma GCC diagnostic pop
3881 // ToDo: Examine insets. They might need rotation too. Check diagrams (SmartArt).
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.
3897 if (sWritingMode
.value() == "vert" || sWritingMode
.value() == "eaVert")
3899 sal_Int32 nHelp
= nLeft
;
3905 else if (sWritingMode
.value() == "vert270")
3907 sal_Int32 nHelp
= nLeft
;
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"
3926 sTextRotateAngleMSUnit
3927 = oox::drawingml::calcRotationValue(nTextRotateAngleDeg100
.value().get());
3928 #if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12
3929 #pragma GCC diagnostic pop
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
:
3955 case TextHorizontalAdjust_LEFT
:
3956 sAnchor
= sWritingMode
.value() == "eaVert" ? "b" : "t";
3958 case TextHorizontalAdjust_RIGHT
:
3959 default: // TextHorizontalAdjust_BLOCK, should not happen
3960 sAnchor
= sWritingMode
.value() == "eaVert" ? "t" : "b";
3966 bAnchorCtr
= eHorizontalAlignment
== TextHorizontalAdjust_CENTER
3967 || eHorizontalAlignment
== TextHorizontalAdjust_RIGHT
;
3968 sAnchor
= GetTextVerticalAdjust(eVerticalAlignment
);
3971 bool bHasWrap
= false;
3973 // Only custom shapes obey the TextWordWrap option, normal text always wraps.
3974 if (dynamic_cast<SvxCustomShape
*>(rXIface
.get()) && GetProperty(rXPropSet
, "TextWordWrap"))
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
)
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),
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());
4054 if (aAdjustmentSeq
[i
].Value
.getValueTypeClass() == TypeClass_DOUBLE
)
4055 aAdjustmentSeq
[i
].Value
>>= fValue
;
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"
4068 && (sPresetWarp
== "textArchDownPour" || sPresetWarp
== "textArchUpPour"
4069 || sPresetWarp
== "textButtonPour" || sPresetWarp
== "textCirclePour")))
4076 && (sPresetWarp
== "textDoubleWave1" || sPresetWarp
== "textWave1"
4077 || sPresetWarp
== "textWave2" || sPresetWarp
== "textWave4"))
4079 fValue
= fValue
/ 0.216 - 50000.0;
4082 && (sPresetWarp
== "textArchDownPour"
4083 || sPresetWarp
== "textArchUpPour"
4084 || sPresetWarp
== "textButtonPour"
4085 || sPresetWarp
== "textCirclePour"))
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
)
4099 mpFS
->endElementNS(XML_a
, XML_avLst
);
4100 mpFS
->endElementNS(XML_a
, XML_prstTxWarp
);
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"))
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());
4139 SdrTextObj
* pTextObject
= DynCastSdrTextObj(pTextShape
->GetSdrObject());
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
));
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
)
4178 Reference
< XEnumeration
> enumeration( access
->createEnumeration() );
4179 if( !enumeration
.is() )
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
;
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();
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
);
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
);
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
);
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
);
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
);
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()
4357 : aAdjustmentSeq
[i
].Name
.toUtf8();
4359 mpFS
->singleElementNS( XML_a
, XML_gd
,
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
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
)
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
)
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'
4407 // center ellipse at origin, stretch in y-direction to circle, flip to Math orientation
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'
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
));
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";
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);
4486 const sal_Int32 nWidth
= aLogicRectLO
.Right() - aLogicRectLO
.Left();
4487 const OString sWidth
= OString::number(oox::drawingml::convertHmmToEmu(nWidth
));
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
);
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
);
4506 const sal_Int32 nHeight
= aLogicRectLO
.Bottom() - aLogicRectLO
.Top();
4507 const OString sHeight
= OString::number(oox::drawingml::convertHmmToEmu(nHeight
));
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
);
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
);
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
) )
4541 aAny
= aXPropSet
->getPropertyValue( "CustomShapeGeometry" );
4542 if ( !aAny
.hasValue() )
4545 catch( const ::uno::Exception
& )
4550 auto pGeometrySeq
= o3tl::tryAccess
<uno::Sequence
<beans::PropertyValue
>>(aAny
);
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
))
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() )
4584 if ( !aSegments
.hasElements() )
4586 aSegments
= uno::Sequence
<drawing::EnhancedCustomShapeSegment
>
4590 static_cast<sal_Int16
>(std::min( aPairs
.getLength() - 1, sal_Int32(32767) )) },
4591 { CLOSESUBPATH
, 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.");
4605 // A EnhancedCustomShape2d caches the equation results. Therefore we use only one of it for the
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
);
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
;
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
4673 aPairs
[0].First
.Value
>>= nXMin
;
4674 sal_Int32 nXMax
= nXMin
;
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,
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
))
4717 else if (HasCommandInSubPath(DARKEN
, nSubpathStartIndex
, nNextNcommandIndex
- 1, aSegments
))
4719 else if (HasCommandInSubPath(DARKENLESS
, nSubpathStartIndex
, nNextNcommandIndex
- 1,
4721 sFill
= "darkenLess";
4722 else if (HasCommandInSubPath(LIGHTEN
, nSubpathStartIndex
, nNextNcommandIndex
- 1,
4725 else if (HasCommandInSubPath(LIGHTENLESS
, nSubpathStartIndex
, nNextNcommandIndex
- 1,
4727 sFill
= "lightenLess";
4730 // shading info might be in object type, e.g. "Octagon Bevel".
4731 sal_Int32
nLuminanceChange(aCustomShape2d
.GetLuminanceChange(nSubPathIndex
));
4732 if (nLuminanceChange
<= -40)
4734 else if (nLuminanceChange
<= -10)
4735 sFill
= "darkenLess";
4736 else if (nLuminanceChange
>= 40)
4738 else if (nLuminanceChange
>= 10)
4739 sFill
= "lightenLess";
4742 std::optional
<OString
> sStroke
;
4743 if (HasCommandInSubPath(NOSTROKE
, nSubpathStartIndex
, nNextNcommandIndex
- 1, aSegments
))
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
),
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
;
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
);
4781 break; // exit loop if not enough values in aPairs
4783 // step forward to next subpath
4784 nSubpathStartIndex
= nNextNcommandIndex
+ 1;
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
)
4805 if (rnPairIndex
>= rPairs
.getLength())
4808 mpFS
->startElementNS(XML_a
, XML_moveTo
);
4809 WriteCustomGeometryPoint(rPairs
[rnPairIndex
], rCustomShape2d
, bReplaceGeoWidth
,
4811 mpFS
->endElementNS(XML_a
, XML_moveTo
);
4812 rCustomShape2d
.GetParameter(rfCurrentX
, rPairs
[rnPairIndex
].First
, bReplaceGeoWidth
,
4814 rCustomShape2d
.GetParameter(rfCurrentY
, rPairs
[rnPairIndex
].Second
, false,
4816 rbCurrentValid
= true;
4822 if (rnPairIndex
>= rPairs
.getLength())
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.
4829 mpFS
->startElementNS(XML_a
, XML_lnTo
);
4830 WriteCustomGeometryPoint(rPairs
[rnPairIndex
], rCustomShape2d
, bReplaceGeoWidth
,
4832 mpFS
->endElementNS(XML_a
, XML_lnTo
);
4836 mpFS
->startElementNS(XML_a
, XML_moveTo
);
4837 WriteCustomGeometryPoint(rPairs
[rnPairIndex
], rCustomShape2d
, bReplaceGeoWidth
,
4839 mpFS
->endElementNS(XML_a
, XML_moveTo
);
4841 rCustomShape2d
.GetParameter(rfCurrentX
, rPairs
[rnPairIndex
].First
, bReplaceGeoWidth
,
4843 rCustomShape2d
.GetParameter(rfCurrentY
, rPairs
[rnPairIndex
].Second
, false,
4845 rbCurrentValid
= true;
4851 if (rnPairIndex
+ 2 >= rPairs
.getLength())
4854 mpFS
->startElementNS(XML_a
, XML_cubicBezTo
);
4855 for (sal_uInt8 i
= 0; i
<= 2; ++i
)
4857 WriteCustomGeometryPoint(rPairs
[rnPairIndex
+ i
], rCustomShape2d
, bReplaceGeoWidth
,
4860 mpFS
->endElementNS(XML_a
, XML_cubicBezTo
);
4861 rCustomShape2d
.GetParameter(rfCurrentX
, rPairs
[rnPairIndex
+ 2].First
, bReplaceGeoWidth
,
4863 rCustomShape2d
.GetParameter(rfCurrentY
, rPairs
[rnPairIndex
+ 2].Second
, false,
4865 rbCurrentValid
= true;
4869 case ANGLEELLIPSETO
:
4872 if (rnPairIndex
+ 2 >= rPairs
.getLength())
4877 rCustomShape2d
.GetParameter(fCx
, rPairs
[rnPairIndex
].First
, bReplaceGeoWidth
, false);
4879 rCustomShape2d
.GetParameter(fCy
, rPairs
[rnPairIndex
].Second
, false, bReplaceGeoHeight
);
4881 rCustomShape2d
.GetParameter(fWR
, rPairs
[rnPairIndex
+ 1].First
, false, false);
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
4897 nSwingAng
= std::lround((fEndAngle
- fStartAngle
) * 60000);
4899 nSwingAng
+= 360 * 60000;
4902 // calculate start point on ellipse
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
);
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;
4937 case CLOCKWISEARCTO
:
4940 if (rnPairIndex
+ 3 >= rPairs
.getLength())
4945 rCustomShape2d
.GetParameter(fX1
, rPairs
[rnPairIndex
].First
, bReplaceGeoWidth
, false);
4947 rCustomShape2d
.GetParameter(fY1
, rPairs
[rnPairIndex
].Second
, false, bReplaceGeoHeight
);
4949 rCustomShape2d
.GetParameter(fX2
, rPairs
[rnPairIndex
+ 1].First
, bReplaceGeoWidth
,
4952 rCustomShape2d
.GetParameter(fY2
, rPairs
[rnPairIndex
+ 1].Second
, false,
4955 rCustomShape2d
.GetParameter(fX3
, rPairs
[rnPairIndex
+ 2].First
, bReplaceGeoWidth
,
4958 rCustomShape2d
.GetParameter(fY3
, rPairs
[rnPairIndex
+ 2].Second
, false,
4961 rCustomShape2d
.GetParameter(fX4
, rPairs
[rnPairIndex
+ 3].First
, bReplaceGeoWidth
,
4964 rCustomShape2d
.GetParameter(fY4
, rPairs
[rnPairIndex
+ 3].Second
, false,
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;
4975 getEllipsePointAndAngleFromRayPoint(fStartAngle
, fPx
, fPy
, fWR
, fHR
, fCx
, fCy
, fX3
,
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
);
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;
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
));
5011 rbCurrentValid
= true;
5015 case ELLIPTICALQUADRANTX
:
5016 case ELLIPTICALQUADRANTY
:
5018 if (rnPairIndex
>= rPairs
.getLength())
5023 rCustomShape2d
.GetParameter(fX
, rPairs
[rnPairIndex
].First
, bReplaceGeoWidth
, false);
5025 rCustomShape2d
.GetParameter(fY
, rPairs
[rnPairIndex
].Second
, false, bReplaceGeoHeight
);
5027 // Prepare parameters for arcTo
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;
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
));
5061 // faulty path, but we continue with the target point
5062 mpFS
->startElementNS(XML_a
, XML_moveTo
);
5063 WriteCustomGeometryPoint(rPairs
[rnPairIndex
], rCustomShape2d
, bReplaceGeoWidth
,
5065 mpFS
->endElementNS(XML_a
, XML_moveTo
);
5069 rbCurrentValid
= true;
5073 case QUADRATICCURVETO
:
5075 if (rnPairIndex
+ 1 >= rPairs
.getLength())
5078 mpFS
->startElementNS(XML_a
, XML_quadBezTo
);
5079 for (sal_uInt8 i
= 0; i
< 2; ++i
)
5081 WriteCustomGeometryPoint(rPairs
[rnPairIndex
+ i
], rCustomShape2d
, bReplaceGeoWidth
,
5084 mpFS
->endElementNS(XML_a
, XML_quadBezTo
);
5085 rCustomShape2d
.GetParameter(rfCurrentX
, rPairs
[rnPairIndex
+ 1].First
, bReplaceGeoWidth
,
5087 rCustomShape2d
.GetParameter(rfCurrentY
, rPairs
[rnPairIndex
+ 1].Second
, false,
5089 rbCurrentValid
= true;
5095 if (rnPairIndex
+ 1 >= rPairs
.getLength())
5099 rCustomShape2d
.GetParameter(fWR
, rPairs
[rnPairIndex
].First
, false, false);
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
));
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;
5129 void DrawingML::WriteCustomGeometryPoint(
5130 const drawing::EnhancedCustomShapeParameterPair
& rParamPair
,
5131 const EnhancedCustomShape2d
& rCustomShape2d
, const bool bReplaceGeoWidth
,
5132 const bool bReplaceGeoHeight
)
5135 = GetCustomGeometryPointValue(rParamPair
.First
, rCustomShape2d
, bReplaceGeoWidth
, false);
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
,
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
)
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
;
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
);
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
);
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
) );
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
;
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
);
5286 sRelationshipId
= GetFB()->addRelation( sRelationshipType
, sRelativeStream
);
5288 if( pRelationshipId
)
5289 *pRelationshipId
= sRelationshipId
;
5291 sax_fastparser::FSHelperPtr p
= GetFB()->openFragmentStreamWithSerializer( sFullStream
, sContentType
);
5296 void DrawingML::WriteFill(const Reference
<XPropertySet
>& xPropSet
, const awt::Size
& rSize
)
5298 if ( !GetProperty( xPropSet
, "FillStyle" ) )
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
5329 xPropSet
->getPropertyValue( "FillTransparence" ) >>= nVal
;
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
);
5344 case FillStyle_GRADIENT
:
5345 WriteGradientFill( xPropSet
);
5347 case FillStyle_BITMAP
:
5348 WriteBlipFill( xPropSet
, "FillBitmap", rSize
);
5350 case FillStyle_HATCH
:
5351 WritePattFill( xPropSet
);
5353 case FillStyle_NONE
:
5354 if (!bUseBackground
) // attribute `useBgFill` will be written at parent p:sp shape
5355 mpFS
->singleElementNS(XML_a
, XML_noFill
);
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
);
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" ) )
5395 // extract the relevant properties from the grab bag
5396 Sequence
< PropertyValue
> aGrabBag
;
5397 Sequence
< PropertyValue
> aFillRefProperties
, aLnRefProperties
, aEffectRefProperties
;
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() )
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
;
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" )
5464 rOuterShdwProp
.Value
>>= sVal
;
5465 aOuterShdwAttrList
->add( XML_algn
, sVal
);
5467 else if( rOuterShdwProp
.Name
== "blurRad" )
5470 rOuterShdwProp
.Value
>>= nVal
;
5471 aOuterShdwAttrList
->add( XML_blurRad
, OString::number( nVal
) );
5473 else if( rOuterShdwProp
.Name
== "dir" )
5476 rOuterShdwProp
.Value
>>= nVal
;
5477 aOuterShdwAttrList
->add( XML_dir
, OString::number( nVal
) );
5479 else if( rOuterShdwProp
.Name
== "dist" )
5482 rOuterShdwProp
.Value
>>= nVal
;
5483 aOuterShdwAttrList
->add( XML_dist
, OString::number( nVal
) );
5485 else if( rOuterShdwProp
.Name
== "kx" )
5488 rOuterShdwProp
.Value
>>= nVal
;
5489 aOuterShdwAttrList
->add( XML_kx
, OString::number( nVal
) );
5491 else if( rOuterShdwProp
.Name
== "ky" )
5494 rOuterShdwProp
.Value
>>= nVal
;
5495 aOuterShdwAttrList
->add( XML_ky
, OString::number( nVal
) );
5497 else if( rOuterShdwProp
.Name
== "rotWithShape" )
5500 rOuterShdwProp
.Value
>>= nVal
;
5501 aOuterShdwAttrList
->add( XML_rotWithShape
, OString::number( nVal
) );
5503 else if( rOuterShdwProp
.Name
== "sx" )
5506 rOuterShdwProp
.Value
>>= nVal
;
5507 aOuterShdwAttrList
->add( XML_sx
, OString::number( nVal
) );
5509 else if( rOuterShdwProp
.Name
== "sy" )
5512 rOuterShdwProp
.Value
>>= nVal
;
5513 aOuterShdwAttrList
->add( XML_sy
, OString::number( nVal
) );
5515 else if( rOuterShdwProp
.Name
== "rad" )
5518 rOuterShdwProp
.Value
>>= nVal
;
5519 aOuterShdwAttrList
->add( XML_rad
, OString::number( nVal
) );
5521 else if( rOuterShdwProp
.Name
== "endA" )
5524 rOuterShdwProp
.Value
>>= nVal
;
5525 aOuterShdwAttrList
->add( XML_endA
, OString::number( nVal
) );
5527 else if( rOuterShdwProp
.Name
== "endPos" )
5530 rOuterShdwProp
.Value
>>= nVal
;
5531 aOuterShdwAttrList
->add( XML_endPos
, OString::number( nVal
) );
5533 else if( rOuterShdwProp
.Name
== "fadeDir" )
5536 rOuterShdwProp
.Value
>>= nVal
;
5537 aOuterShdwAttrList
->add( XML_fadeDir
, OString::number( nVal
) );
5539 else if( rOuterShdwProp
.Name
== "stA" )
5542 rOuterShdwProp
.Value
>>= nVal
;
5543 aOuterShdwAttrList
->add( XML_stA
, OString::number( nVal
) );
5545 else if( rOuterShdwProp
.Name
== "stPos" )
5548 rOuterShdwProp
.Value
>>= nVal
;
5549 aOuterShdwAttrList
->add( XML_stPos
, OString::number( nVal
) );
5551 else if( rOuterShdwProp
.Name
== "grow" )
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 )
5583 mpFS
->startElement( nEffectToken
, aOuterShdwAttrList
);
5585 if( bContainsColor
)
5587 if( sSchemeClr
.isEmpty() )
5588 WriteColor( nRgbClr
, nAlpha
);
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"))
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"))
5638 bHasEffects
= rad
> 0;
5640 if (!bHasEffects
&& GetProperty(rXPropSet
, "SoftEdgeRadius"))
5644 bHasEffects
= rad
> 0;
5649 mpFS
->startElementNS(XML_a
, XML_effectLst
);
5650 WriteGlowEffect(rXPropSet
);
5653 double dX
= +0.0, dY
= +0.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
);
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;
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
) )
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
);
5741 Sequence
< PropertyValue
> aEffectProps
;
5742 rEffect
.Value
>>= aEffectProps
;
5743 WriteShapeEffect( rEffect
.Name
, aEffectProps
);
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"))
5762 rXPropSet
->getPropertyValue("GlowEffectRadius") >>= nRad
;
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"))
5786 rXPropSet
->getPropertyValue("SoftEdgeRadius") >>= nRad
;
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",
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" ) )
5804 // extract the relevant properties from the grab bag
5805 Sequence
< PropertyValue
> aGrabBag
, aEffectProps
, aLightRigProps
, aShape3DProps
;
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() )
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" )
5837 rEffectProp
.Value
>>= sVal
;
5838 aCameraAttrList
->add(XML_prst
, sVal
);
5840 else if( rEffectProp
.Name
== "fov" )
5843 rEffectProp
.Value
>>= fVal
;
5844 aCameraAttrList
->add( XML_fov
, OString::number( fVal
* 60000 ) );
5846 else if( rEffectProp
.Name
== "zoom" )
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" )
5860 else if( rEffectProp
.Name
== "rotLon" )
5862 else if( rEffectProp
.Name
== "rotRev" )
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" )
5877 sal_Int32 nToken
= XML_none
;
5878 rLightRigProp
.Value
>>= sVal
;
5879 if( rLightRigProp
.Name
== "rig" )
5881 else if( rLightRigProp
.Name
== "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" )
5893 else if( rLightRigProp
.Name
== "rotLon" )
5895 else if( rLightRigProp
.Name
== "rotRev" )
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
);
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
);
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() )
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" )
5956 aShape3DAttrList
->add( nToken
, OString::number( nVal
) );
5958 else if( rShape3DProp
.Name
== "prstMaterial" )
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() )
5979 rtl::Reference
<sax_fastparser::FastAttributeList
> aBevelAttrList
;
5980 if( rShape3DProp
.Name
== "bevelT" )
5982 bBevelTPresent
= true;
5983 aBevelAttrList
= aBevelTAttrList
;
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" )
5998 else if( rBevelProp
.Name
== "h" )
6000 aBevelAttrList
->add( nToken
, OString::number( nVal
) );
6002 else if( rBevelProp
.Name
== "prst" )
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
;
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
) );
6044 WriteColor( sSchemeClr
, aColorTransformations
);
6046 mpFS
->endElementNS( XML_a
, XML_extrusionClr
);
6048 if( aContourColorProps
.hasElements() )
6050 OUString sSchemeClr
;
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
) );
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" ) )
6082 PropertyValue aEffect
;
6083 Sequence
< PropertyValue
> 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
)
6093 Sequence
< PropertyValue
> aAttrs
;
6094 aEffect
.Value
>>= aAttrs
;
6095 rtl::Reference
<sax_fastparser::FastAttributeList
> aAttrList
= FastSerializerHelper::createAttrList();
6097 for( const auto& rAttr
: std::as_const(aAttrs
) )
6099 sal_Int32 nToken
= ArtisticEffectProperties::getEffectToken( rAttr
.Name
);
6100 if( nToken
!= XML_none
)
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
);
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
),
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
;
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())
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
)));
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
)),
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),
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());
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
>());
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
>());
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())
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
];
6403 OUString sExtension
;
6404 diagramDataRelTuple
[0] >>= sRelId
;
6405 diagramDataRelTuple
[2] >>= sExtension
;
6406 OUString sContentType
;
6407 if (sExtension
.equalsIgnoreAsciiCase(".WMF"))
6408 sContentType
= "image/x-wmf";
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
);
6453 Degree100 nRotation
= pObj
->GetRotateAngle();
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: */