summaryrefslogtreecommitdiffstats
path: root/oox/source/export
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /oox/source/export
parentInitial commit. (diff)
downloadlibreoffice-upstream.tar.xz
libreoffice-upstream.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'oox/source/export')
-rw-r--r--oox/source/export/ColorPropertySet.cxx178
-rw-r--r--oox/source/export/ColorPropertySet.hxx87
-rw-r--r--oox/source/export/DMLPresetShapeExport.cxx1658
-rw-r--r--oox/source/export/README2
-rw-r--r--oox/source/export/chartexport.cxx4685
-rw-r--r--oox/source/export/drawingml.cxx6125
-rw-r--r--oox/source/export/ooxml-export-notes.txt235
-rw-r--r--oox/source/export/preset-definitions-to-shape-types.pl1237
-rw-r--r--oox/source/export/presetTextWarpDefinitions.xml1885
-rw-r--r--oox/source/export/shapes.cxx2728
-rw-r--r--oox/source/export/vmlexport.cxx1529
11 files changed, 20349 insertions, 0 deletions
diff --git a/oox/source/export/ColorPropertySet.cxx b/oox/source/export/ColorPropertySet.cxx
new file mode 100644
index 000000000..a0998f195
--- /dev/null
+++ b/oox/source/export/ColorPropertySet.cxx
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+// FIXME? This file is nearly identical to xmloff/source/chart/ColorPropertySet.cxx
+
+#include "ColorPropertySet.hxx"
+
+#include <cppuhelper/implbase.hxx>
+#include <osl/diagnose.h>
+#include <com/sun/star/drawing/FillStyle.hpp>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::beans;
+
+using ::com::sun::star::uno::Reference;
+using ::com::sun::star::uno::Sequence;
+
+namespace
+{
+class lcl_ColorPropertySetInfo : public ::cppu::WeakImplHelper<
+ XPropertySetInfo >
+{
+public:
+ explicit lcl_ColorPropertySetInfo( bool bFillColor );
+
+protected:
+ // ____ XPropertySetInfo ____
+ virtual Sequence< Property > SAL_CALL getProperties() override;
+ virtual Property SAL_CALL getPropertyByName( const OUString& aName ) override;
+ virtual sal_Bool SAL_CALL hasPropertyByName( const OUString& Name ) override;
+
+private:
+ OUString m_aColorPropName;
+ Property m_aColorProp;
+};
+
+lcl_ColorPropertySetInfo::lcl_ColorPropertySetInfo( bool bFillColor ) :
+ // note: length of FillColor and LineColor is 9
+ m_aColorPropName( (bFillColor ? "FillColor" : "LineColor"), 9, RTL_TEXTENCODING_ASCII_US ),
+ m_aColorProp( m_aColorPropName, -1,
+ cppu::UnoType<sal_Int32>::get(), 0)
+{}
+
+Sequence< Property > SAL_CALL lcl_ColorPropertySetInfo::getProperties()
+{
+
+ return Sequence< Property >( & m_aColorProp, 1 );
+}
+
+Property SAL_CALL lcl_ColorPropertySetInfo::getPropertyByName( const OUString& aName )
+{
+ if( aName == m_aColorPropName )
+ return m_aColorProp;
+ throw UnknownPropertyException( m_aColorPropName, static_cast< uno::XWeak * >( this ));
+}
+
+sal_Bool SAL_CALL lcl_ColorPropertySetInfo::hasPropertyByName( const OUString& Name )
+{
+ return Name == m_aColorPropName;
+}
+
+} // anonymous namespace
+
+namespace oox::drawingml
+{
+
+ColorPropertySet::ColorPropertySet( ::Color nColor, bool bFillColor /* = true */ ) :
+ // note: length of FillColor and LineColor is 9
+ m_aColorPropName( (bFillColor ? "FillColor" : "LineColor"), 9, RTL_TEXTENCODING_ASCII_US ),
+ m_nColor( nColor ),
+ m_bIsFillColor( bFillColor ),
+ m_nDefaultColor( 0x0099ccff ) // blue 8
+{}
+
+ColorPropertySet::~ColorPropertySet()
+{}
+
+// ____ XPropertySet ____
+
+Reference< XPropertySetInfo > SAL_CALL ColorPropertySet::getPropertySetInfo()
+{
+ if( ! m_xInfo.is())
+ m_xInfo.set( new lcl_ColorPropertySetInfo( m_bIsFillColor ));
+
+ return m_xInfo;
+}
+
+void SAL_CALL ColorPropertySet::setPropertyValue( const OUString& rPropertyName, const uno::Any& aValue )
+{
+ if (rPropertyName != m_aColorPropName)
+ {
+ // trying to catch these cases in the next crash testing
+ assert(false);
+ return;
+ }
+
+ aValue >>= m_nColor;
+}
+
+uno::Any SAL_CALL ColorPropertySet::getPropertyValue( const OUString& aPropertyName )
+{
+ if( aPropertyName == "FillStyle" && m_bIsFillColor )
+ {
+ return uno::Any(css::drawing::FillStyle_SOLID);
+ }
+ else if (aPropertyName == m_aColorPropName)
+ return uno::Any( m_nColor );
+
+ throw UnknownPropertyException(aPropertyName);
+}
+
+void SAL_CALL ColorPropertySet::addPropertyChangeListener( const OUString& /* aPropertyName */, const Reference< XPropertyChangeListener >& /* xListener */ )
+{
+ OSL_FAIL( "Not Implemented" );
+}
+
+void SAL_CALL ColorPropertySet::removePropertyChangeListener( const OUString& /* aPropertyName */, const Reference< XPropertyChangeListener >& /* aListener */ )
+{
+ OSL_FAIL( "Not Implemented" );
+}
+
+void SAL_CALL ColorPropertySet::addVetoableChangeListener( const OUString& /* PropertyName */, const Reference< XVetoableChangeListener >& /* aListener */ )
+{
+ OSL_FAIL( "Not Implemented" );
+}
+
+void SAL_CALL ColorPropertySet::removeVetoableChangeListener( const OUString& /* PropertyName */, const Reference< XVetoableChangeListener >& /* aListener */ )
+{
+ OSL_FAIL( "Not Implemented" );
+}
+
+// ____ XPropertyState ____
+
+PropertyState SAL_CALL ColorPropertySet::getPropertyState( const OUString& /* PropertyName */ )
+{
+ return PropertyState_DIRECT_VALUE;
+}
+
+Sequence< PropertyState > SAL_CALL ColorPropertySet::getPropertyStates( const Sequence< OUString >& /* aPropertyName */ )
+{
+ PropertyState aState = PropertyState_DIRECT_VALUE;
+ // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence
+ return Sequence<PropertyState>(&aState, 1);
+}
+
+void SAL_CALL ColorPropertySet::setPropertyToDefault( const OUString& PropertyName )
+{
+ if( PropertyName == m_aColorPropName )
+ m_nColor = m_nDefaultColor;
+}
+
+uno::Any SAL_CALL ColorPropertySet::getPropertyDefault( const OUString& aPropertyName )
+{
+ if( aPropertyName == m_aColorPropName )
+ return uno::Any( m_nDefaultColor );
+
+ return uno::Any();
+}
+
+} // namespace oox::drawingml
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/export/ColorPropertySet.hxx b/oox/source/export/ColorPropertySet.hxx
new file mode 100644
index 000000000..70213a731
--- /dev/null
+++ b/oox/source/export/ColorPropertySet.hxx
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#ifndef XMLOFF_COLORPROPERTYSET_HXX
+#define XMLOFF_COLORPROPERTYSET_HXX
+
+// FIXME? this file is identical to xmloff/source/chart/ColorPropertySet.hxx
+
+#include <cppuhelper/implbase.hxx>
+#include <tools/color.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/XPropertyState.hpp>
+
+namespace oox::drawingml
+{
+
+class ColorPropertySet : public ::cppu::WeakImplHelper<
+ css::beans::XPropertySet,
+ css::beans::XPropertyState >
+{
+public:
+ // if bFillColor == false, the color is a LineColor
+ explicit ColorPropertySet( ::Color nColor, bool bFillColor = true );
+ virtual ~ColorPropertySet() override;
+
+protected:
+ // ____ XPropertySet ____
+ virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override;
+ virtual void SAL_CALL setPropertyValue(
+ const OUString& aPropertyName,
+ const css::uno::Any& aValue ) override;
+ virtual css::uno::Any SAL_CALL getPropertyValue(
+ const OUString& PropertyName ) override;
+ virtual void SAL_CALL addPropertyChangeListener(
+ const OUString& aPropertyName,
+ const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override;
+ virtual void SAL_CALL removePropertyChangeListener(
+ const OUString& aPropertyName,
+ const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override;
+ virtual void SAL_CALL addVetoableChangeListener(
+ const OUString& PropertyName,
+ const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override;
+ virtual void SAL_CALL removeVetoableChangeListener(
+ const OUString& PropertyName,
+ const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override;
+
+ // ____ XPropertyState ____
+ virtual css::beans::PropertyState SAL_CALL getPropertyState(
+ const OUString& PropertyName ) override;
+ virtual css::uno::Sequence< css::beans::PropertyState > SAL_CALL getPropertyStates(
+ const css::uno::Sequence< OUString >& aPropertyName ) override;
+ virtual void SAL_CALL setPropertyToDefault(
+ const OUString& PropertyName ) override;
+ virtual css::uno::Any SAL_CALL getPropertyDefault(
+ const OUString& aPropertyName ) override;
+
+private:
+ css::uno::Reference< css::beans::XPropertySetInfo > m_xInfo;
+ OUString m_aColorPropName;
+ ::Color m_nColor;
+ bool m_bIsFillColor;
+ ::Color m_nDefaultColor;
+};
+
+} // namespace xmloff::chart
+
+// XMLOFF_COLORPROPERTYSET_HXX
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/export/DMLPresetShapeExport.cxx b/oox/source/export/DMLPresetShapeExport.cxx
new file mode 100644
index 000000000..a2d08ec3e
--- /dev/null
+++ b/oox/source/export/DMLPresetShapeExport.cxx
@@ -0,0 +1,1658 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+* This file is part of the LibreOffice project.
+*
+* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+#include <oox/export/DMLPresetShapeExport.hxx>
+#include <oox/token/tokens.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+
+#include <osl/diagnose.h>
+#include <filter/msfilter/util.hxx>
+
+#include <string_view>
+
+using namespace ::css;
+using namespace ::css::drawing;
+
+namespace oox::drawingml
+{
+// DMLPresetShapeExporter class
+
+// ctor
+DMLPresetShapeExporter::DMLPresetShapeExporter(DrawingML* pDMLExporter,
+ css::uno::Reference<css::drawing::XShape> xShape)
+ : m_pDMLexporter(pDMLExporter)
+{
+ // This class only work with custom shapes!
+ OSL_ASSERT(xShape->getShapeType() == "com.sun.star.drawing.CustomShape");
+
+ m_xShape = xShape;
+ m_bHasHandleValues = false;
+ uno::Reference<beans::XPropertySet> xShapeProps(m_xShape, uno::UNO_QUERY);
+ css::uno::Sequence<css::beans::PropertyValue> aCustomShapeGeometry
+ = xShapeProps->getPropertyValue("CustomShapeGeometry")
+ .get<uno::Sequence<beans::PropertyValue>>();
+
+ for (sal_uInt32 i = 0; i < aCustomShapeGeometry.size(); i++)
+ {
+ if (aCustomShapeGeometry[i].Name == "Type")
+ {
+ m_sPresetShapeType = aCustomShapeGeometry[i].Value.get<OUString>();
+ }
+ if (aCustomShapeGeometry[i].Name == "Handles")
+ {
+ m_bHasHandleValues = true;
+ m_HandleValues
+ = aCustomShapeGeometry[i]
+ .Value
+ .get<css::uno::Sequence<css::uno::Sequence<css::beans::PropertyValue>>>();
+ }
+ if (aCustomShapeGeometry[i].Name == "AdjustmentValues")
+ {
+ m_AdjustmentValues
+ = aCustomShapeGeometry[i]
+ .Value
+ .get<css::uno::Sequence<css::drawing::EnhancedCustomShapeAdjustmentValue>>();
+ }
+ if (aCustomShapeGeometry[i].Name == "MirroredX")
+ {
+ m_bIsFlipped.first = aCustomShapeGeometry[i].Value.get<bool>();
+ }
+ if (aCustomShapeGeometry[i].Name == "MirroredY")
+ {
+ m_bIsFlipped.second = aCustomShapeGeometry[i].Value.get<bool>();
+ }
+ //if (aCustomShapeGeometry[i].Name == "Equations")
+ //{
+ // m_Equations = aCustomShapeGeometry[i].Value.get<css::uno::Sequence<OUString>>();
+ //}
+ //if (aCustomShapeGeometry[i].Name == "Path")
+ //{
+ // m_Path = aCustomShapeGeometry[i]
+ // .Value.get<css::uno::Sequence<css::beans::PropertyValue>>();
+ //}
+ //if (aCustomShapeGeometry[i].Name == "ViewBox")
+ //{
+ // m_ViewBox = aCustomShapeGeometry[i].Value.get<css::awt::Rectangle>();
+ //}
+ }
+};
+
+// dtor
+DMLPresetShapeExporter::~DMLPresetShapeExporter(){
+ // Do nothing
+};
+
+bool DMLPresetShapeExporter::HasHandleValue() const { return m_bHasHandleValues; }
+
+const OUString& DMLPresetShapeExporter::GetShapeType() const { return m_sPresetShapeType; }
+
+const css::uno::Sequence<css::uno::Sequence<css::beans::PropertyValue>>&
+DMLPresetShapeExporter::GetHandleValues() const
+{
+ return m_HandleValues;
+};
+
+const css::uno::Sequence<css::drawing::EnhancedCustomShapeAdjustmentValue>&
+DMLPresetShapeExporter::GetAdjustmentValues() const
+{
+ return m_AdjustmentValues;
+};
+
+css::uno::Any DMLPresetShapeExporter::GetHandleValueOfModificationPoint(sal_Int32 nPoint,
+ std::u16string_view sType)
+{
+ uno::Any aRet;
+ if (GetHandleValues().getLength() > nPoint)
+ {
+ for (sal_Int32 i = 0; i < GetHandleValues()[nPoint].getLength(); i++)
+ {
+ if (GetHandleValues()[nPoint][i].Name == sType)
+ {
+ aRet = GetHandleValues()[nPoint][i].Value;
+ break;
+ }
+ }
+ }
+ return aRet;
+};
+
+DMLPresetShapeExporter::RadiusAdjustmentValue
+DMLPresetShapeExporter::GetAdjustmentPointRadiusValue(sal_Int32 nPoint)
+{
+ RadiusAdjustmentValue aRet;
+ try
+ {
+ auto aValPos = GetHandleValueOfModificationPoint(nPoint, u"Position")
+ .get<EnhancedCustomShapeParameterPair>();
+ aRet.nMinVal = GetHandleValueOfModificationPoint(nPoint, u"RadiusRangeMinimum")
+ .get<EnhancedCustomShapeParameter>()
+ .Value.get<double>();
+ aRet.nMaxVal = GetHandleValueOfModificationPoint(nPoint, u"RadiusRangeMaximum")
+ .get<EnhancedCustomShapeParameter>()
+ .Value.get<double>();
+ aRet.nCurrVal = GetAdjustmentValues()[aValPos.First.Value.get<long>()].Value.get<double>();
+ }
+ catch (...)
+ {
+ // Do nothing.
+ }
+ return aRet;
+};
+
+DMLPresetShapeExporter::AngleAdjustmentValue
+DMLPresetShapeExporter::GetAdjustmentPointAngleValue(sal_Int32 nPoint)
+{
+ AngleAdjustmentValue aRet;
+ try
+ {
+ auto aValPos = GetHandleValueOfModificationPoint(nPoint, u"Position")
+ .get<EnhancedCustomShapeParameterPair>();
+ aRet.nMinVal = 0;
+ aRet.nMaxVal = 360;
+ aRet.nCurrVal = GetAdjustmentValues()[aValPos.Second.Value.get<long>()].Value.get<double>();
+ }
+ catch (...)
+ {
+ // Do nothing.
+ }
+ return aRet;
+};
+
+DMLPresetShapeExporter::XAdjustmentValue
+DMLPresetShapeExporter::GetAdjustmentPointXValue(sal_Int32 nPoint)
+{
+ XAdjustmentValue aRet;
+ try
+ {
+ auto aValPos = GetHandleValueOfModificationPoint(nPoint, u"Position")
+ .get<EnhancedCustomShapeParameterPair>();
+ aRet.nMinVal = GetHandleValueOfModificationPoint(nPoint, u"RangeXMinimum")
+ .get<EnhancedCustomShapeParameter>()
+ .Value.get<double>();
+ aRet.nMaxVal = GetHandleValueOfModificationPoint(nPoint, u"RangeXMaximum")
+ .get<EnhancedCustomShapeParameter>()
+ .Value.get<double>();
+ aRet.nCurrVal = GetAdjustmentValues()[aValPos.First.Value.get<long>()].Value.get<double>();
+ }
+ catch (...)
+ {
+ // Do nothing.
+ }
+ return aRet;
+};
+
+DMLPresetShapeExporter::YAdjustmentValue
+DMLPresetShapeExporter::GetAdjustmentPointYValue(sal_Int32 nPoint)
+{
+ YAdjustmentValue aRet;
+ try
+ {
+ auto aValPos = GetHandleValueOfModificationPoint(nPoint, u"Position")
+ .get<EnhancedCustomShapeParameterPair>();
+ aRet.nMinVal = GetHandleValueOfModificationPoint(nPoint, u"RangeYMinimum")
+ .get<EnhancedCustomShapeParameter>()
+ .Value.get<double>();
+ aRet.nMaxVal = GetHandleValueOfModificationPoint(nPoint, u"RangeYMaximum")
+ .get<EnhancedCustomShapeParameter>()
+ .Value.get<double>();
+ aRet.nCurrVal = GetAdjustmentValues()[aValPos.Second.Value.get<long>()].Value.get<double>();
+ }
+ catch (...)
+ {
+ // Do nothing.
+ }
+ return aRet;
+};
+
+bool DMLPresetShapeExporter::WriteShape()
+{
+ if (m_pDMLexporter && m_xShape)
+ {
+ // Case 1: We do not have adjustment points of the shape: just export it as preset
+ if (!m_bHasHandleValues)
+ {
+ OUString sShapeType = GetShapeType();
+ const OString& sPresetShape = msfilter::util::GetOOXMLPresetGeometry(sShapeType);
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ m_pDMLexporter->WritePresetShape(sPresetShape);
+ return true;
+ }
+ else // Case2: There are adjustment points what have to be converted and exported.
+ {
+ return WriteShapeWithAVlist();
+ }
+ }
+ return false;
+};
+
+bool DMLPresetShapeExporter::WriteAV(const OUString& sValName, const OUString& sVal)
+{
+ try
+ {
+ m_pDMLexporter->GetFS()->singleElementNS(XML_a, XML_gd, XML_name, sValName, XML_fmla, sVal);
+ return true;
+ }
+ catch (...)
+ {
+ return false;
+ }
+};
+
+bool DMLPresetShapeExporter::StartAVListWriting()
+{
+ try
+ {
+ const OString& pShape = msfilter::util::GetOOXMLPresetGeometry(GetShapeType());
+ m_pDMLexporter->GetFS()->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
+ m_pDMLexporter->GetFS()->startElementNS(XML_a, XML_avLst);
+ return true;
+ }
+ catch (...)
+ {
+ return false;
+ }
+};
+bool DMLPresetShapeExporter::EndAVListWriting()
+{
+ try
+ {
+ m_pDMLexporter->GetFS()->endElementNS(XML_a, XML_avLst);
+ m_pDMLexporter->GetFS()->endElementNS(XML_a, XML_prstGeom);
+ return true;
+ }
+ catch (...)
+ {
+ return false;
+ }
+};
+
+bool DMLPresetShapeExporter::WriteShapeWithAVlist()
+{
+ // Remark: This method is under development. If a shape type is implemented, the corresponding,
+ // return must be set to true. False means nothing done true, export done. There are many
+ // types which do not have pairs in LO, they are do not have to be mapped, because import
+ // filter it does with GrabBag, this method only maps the SDR ones to OOXML shapes.
+
+ OString sShapeType(msfilter::util::GetOOXMLPresetGeometry(GetShapeType()));
+
+ // OOXML uses 60th of degree, so 360 degree is 21 600 000 60thdeg
+ const tools::Long nConstOfMaxDegreeOf60th = 21600000;
+ try
+ {
+ if (sShapeType == "accentBorderCallout1")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "accentBorderCallout2")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "accentBorderCallout3")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "accentCallout1")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "accentCallout2")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "accentCallout3")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "actionButtonBackPrevious")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "actionButtonBeginning")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "actionButtonBlank")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "actionButtonDocument")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "actionButtonEnd")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "actionButtonForwardNext")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "actionButtonHelp")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "actionButtonHome")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "actionButtonInformation")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "actionButtonMovie")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "actionButtonReturn")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "actionButtonSound")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "arc")
+ {
+ // LO does not have handle points for this, so CustGeom is enough.
+ return false;
+ }
+ if (sShapeType == "bentArrow")
+ {
+ // LO has only one type, which have to be rotated, without handling points
+ // So CustGeom enough.
+ return false;
+ }
+ if (sShapeType == "bentConnector2")
+ {
+ // CustGeom Enough
+ return false;
+ }
+ if (sShapeType == "bentConnector3")
+ {
+ // CustGeom Enough
+ return false;
+ }
+ if (sShapeType == "bentConnector4")
+ {
+ // CustGeom Enough
+ return false;
+ }
+ if (sShapeType == "bentConnector5")
+ {
+ // CustGeom Enough
+ return false;
+ }
+ if (sShapeType == "bentUpArrow")
+ {
+ // CustGeom Enough, no handle points
+ return false;
+ }
+ if (sShapeType == "bevel")
+ {
+ auto aPoint1 = GetAdjustmentPointXValue(0);
+ if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value()
+ || !aPoint1.nMinVal.has_value())
+ return false;
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+
+ tools::Long nVal1
+ = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * 50000);
+ return StartAVListWriting()
+ && WriteAV(u"adj", OUString(u"val " + OUString::number(nVal1)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "blockArc")
+ {
+ auto aPointR = GetAdjustmentPointRadiusValue(0);
+ auto aPointA = GetAdjustmentPointAngleValue(0);
+ if (!aPointA.nCurrVal.has_value() || !aPointA.nMaxVal.has_value()
+ || !aPointA.nMinVal.has_value() || !aPointR.nCurrVal.has_value()
+ || !aPointR.nMaxVal.has_value() || !aPointR.nMinVal.has_value())
+ return false;
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nVal1
+ = std::lround((*aPointA.nCurrVal < 0 ? 360 + *aPointA.nCurrVal : *aPointA.nCurrVal)
+ / (*aPointA.nMaxVal - *aPointA.nMinVal) * nConstOfMaxDegreeOf60th);
+ tools::Long nVal2 = std::lround(
+ (*aPointA.nCurrVal > 180 ? 360 - *aPointA.nCurrVal : 180 - *aPointA.nCurrVal)
+ / (*aPointA.nMaxVal - *aPointA.nMinVal) * nConstOfMaxDegreeOf60th);
+ tools::Long nVal3 = std::lround(
+ 50000 - (*aPointR.nCurrVal / (*aPointR.nMaxVal - *aPointR.nMinVal) * 50000));
+ return StartAVListWriting()
+ && WriteAV(u"adj1", OUString(u"val " + OUString::number(nVal1)))
+ && WriteAV(u"adj2", OUString(u"val " + OUString::number(nVal2)))
+ && WriteAV(u"adj3", OUString(u"val " + OUString::number(nVal3)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "borderCallout1")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "borderCallout2")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "borderCallout3")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "bracePair")
+ {
+ auto aPoint1 = GetAdjustmentPointYValue(0);
+ if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value()
+ || !aPoint1.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nVal1
+ = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * 25000);
+ return StartAVListWriting()
+ && WriteAV(u"adj", OUString(u"val " + OUString::number(nVal1)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "bracketPair")
+ {
+ auto aPoint1 = GetAdjustmentPointYValue(0);
+ if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value()
+ || !aPoint1.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nVal1
+ = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * 50000);
+ return StartAVListWriting()
+ && WriteAV(u"adj", OUString(u"val " + OUString::number(nVal1)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "callout1")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "callout2")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "callout3")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "can")
+ {
+ return false;
+ // Do the export as before.
+ }
+ if (sShapeType == "chartPlus")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "chartStar")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "chartX")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "chord")
+ {
+ // CustGeom, because LO does not have handle points
+ return false;
+ }
+ if (sShapeType == "circularArrow")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "cloud")
+ {
+ // CustGeom enough
+ return false;
+ }
+ if (sShapeType == "cloudCallout")
+ {
+ return false;
+ // Works fine without this, so export it like before.
+ }
+ if (sShapeType == "cornerTabs")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "cube")
+ {
+ // Works fine without this, so export it like before.
+ return false;
+ }
+ if (sShapeType == "curvedConnector2")
+ {
+ // Not necessary to be mapped
+ return false;
+ }
+ if (sShapeType == "curvedConnector3")
+ {
+ // Not necessary to be mapped
+ return false;
+ }
+ if (sShapeType == "curvedConnector4")
+ {
+ // Not necessary to be mapped
+ return false;
+ }
+ if (sShapeType == "curvedConnector5")
+ {
+ // Not necessary to be mapped
+ return false;
+ }
+ if (sShapeType == "curvedDownArrow")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "curvedLeftArrow")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "curvedRightArrow")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "curvedUpArrow")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "decagon")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "diagStripe")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "diamond")
+ {
+ // It does not have handle points so it do not have to be mapped.
+ return false;
+ }
+ if (sShapeType == "dodecagon")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "donut")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "doubleWave")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "downArrow")
+ {
+ auto aPointX = GetAdjustmentPointXValue(0);
+ auto aPointY = GetAdjustmentPointYValue(0);
+ if (!aPointX.nCurrVal.has_value() || !aPointX.nMaxVal.has_value()
+ || !aPointX.nMinVal.has_value() || !aPointY.nCurrVal.has_value()
+ || !aPointY.nMaxVal.has_value() || !aPointY.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nMaxVal1 = 100000;
+ tools::Long nMaxVal2
+ = 100000 * m_xShape->getSize().Height
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nVal1 = std::lround((*aPointX.nMaxVal - *aPointX.nCurrVal)
+ / (*aPointX.nMaxVal - *aPointX.nMinVal) * nMaxVal1);
+ tools::Long nVal2 = std::lround((*aPointY.nMaxVal - *aPointY.nCurrVal)
+ / (*aPointY.nMaxVal - *aPointY.nMinVal) * nMaxVal2);
+ return StartAVListWriting()
+ && WriteAV(u"adj1", OUString(u"val " + OUString::number(nVal1)))
+ && WriteAV(u"adj2", OUString(u"val " + OUString::number(nVal2)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "downArrowCallout")
+ {
+ auto aNeckFromBox = GetAdjustmentPointXValue(1);
+ auto aHeadFromNeck = GetAdjustmentPointXValue(2);
+ auto aHeadHeight = GetAdjustmentPointYValue(1);
+ auto aBoxHeight = GetAdjustmentPointYValue(0);
+ if (!aNeckFromBox.nCurrVal.has_value() || !aNeckFromBox.nMaxVal.has_value()
+ || !aNeckFromBox.nMinVal.has_value() || !aHeadFromNeck.nCurrVal.has_value()
+ || !aHeadFromNeck.nMaxVal.has_value() || !aHeadFromNeck.nMinVal.has_value()
+ || !aHeadHeight.nCurrVal.has_value() || !aHeadHeight.nMaxVal.has_value()
+ || !aHeadHeight.nMinVal.has_value() || !aBoxHeight.nCurrVal.has_value()
+ || !aBoxHeight.nMaxVal.has_value() || !aBoxHeight.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nMaxVal1
+ = 100000 * m_xShape->getSize().Width
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nMaxVal2
+ = 50000 * m_xShape->getSize().Width
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nMaxVal3
+ = 100000 * m_xShape->getSize().Height
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nVal1
+ = std::lround((*aNeckFromBox.nMaxVal - *aNeckFromBox.nCurrVal)
+ / (*aNeckFromBox.nMaxVal - *aNeckFromBox.nMinVal) * nMaxVal1);
+ tools::Long nVal2 = std::lround((10800 - *aHeadFromNeck.nCurrVal)
+ / (10800 - *aHeadFromNeck.nMinVal) * nMaxVal2);
+ tools::Long nVal3
+ = std::lround((*aHeadHeight.nMaxVal - *aHeadHeight.nCurrVal)
+ / (*aHeadHeight.nMaxVal - *aHeadHeight.nMinVal) * nMaxVal3);
+ tools::Long nVal4 = std::lround((*aBoxHeight.nCurrVal - *aBoxHeight.nMinVal)
+ / (21600 - *aBoxHeight.nMinVal) * 100000);
+ return StartAVListWriting()
+ && WriteAV(u"adj1", OUString(u"val " + OUString::number(nVal1)))
+ && WriteAV(u"adj2", OUString(u"val " + OUString::number(nVal2)))
+ && WriteAV(u"adj3", OUString(u"val " + OUString::number(nVal3)))
+ && WriteAV(u"adj4", OUString(u"val " + OUString::number(nVal4)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "ellipse")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "ellipseRibbon")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "ellipseRibbon2")
+ {
+ // LO does not have this type, so it does not necessary to be mapped.
+ return false;
+ }
+ if (sShapeType == "flowChartAlternateProcess")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartCollate")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartConnector")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartDecision")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartDecision")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartDelay")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartDisplay")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartDocument")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartExtract")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartInputOutput")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartInternalStorage")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartMagneticDisk")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartMagneticDrum")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartMagneticTape")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartManualInput")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartManualOperation")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartMerge")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartMultidocument")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartOfflineStorage")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartOffpageConnector")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartOnlineStorage")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartOr")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartDecision")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartPredefinedProcess")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartPreparation")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartPunchedCard")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartPunchedTape")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartSort")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartSummingJunction")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "flowChartTerminator")
+ {
+ // Does not have handle points, so preset enough.
+ return false;
+ }
+ if (sShapeType == "foldedCorner")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "frame")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "funnel")
+ {
+ // Not found in word
+ return false;
+ }
+ if (sShapeType == "gear6")
+ {
+ // Not found in word
+ return false;
+ }
+ if (sShapeType == "gear9")
+ {
+ // Not found in word
+ return false;
+ }
+ if (sShapeType == "halfFrame")
+ {
+ // LO does not have this type, not necessary to map
+ return false;
+ }
+ if (sShapeType == "heart")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "heptagon")
+ {
+ // LO does not have this type, not necessary to map
+ return false;
+ }
+ if (sShapeType == "hexagon")
+ {
+ auto aPoint1 = GetAdjustmentPointXValue(0);
+ if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value()
+ || !aPoint1.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nMaxVal = 50000 * m_xShape->getSize().Width
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nVal1
+ = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * nMaxVal);
+ return StartAVListWriting()
+ && WriteAV(u"adj", OUString(u"val " + OUString::number(nVal1)))
+ && WriteAV(u"vf", OUString(u"val " + OUString::number(115470)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "homePlate")
+ {
+ // Not found in word
+ return false;
+ }
+ if (sShapeType == "horizontalScroll")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "irregularSeal1")
+ {
+ // Not found in word
+ return false;
+ }
+ if (sShapeType == "irregularSeal2")
+ {
+ // Not found in word
+ return false;
+ }
+ if (sShapeType == "leftArrow")
+ {
+ auto aPointX = GetAdjustmentPointXValue(0);
+ auto aPointY = GetAdjustmentPointYValue(0);
+ if (!aPointX.nCurrVal.has_value() || !aPointX.nMaxVal.has_value()
+ || !aPointX.nMinVal.has_value() || !aPointY.nCurrVal.has_value()
+ || !aPointY.nMaxVal.has_value() || !aPointY.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nMaxVal1 = 100000;
+ tools::Long nMaxVal2
+ = 100000
+ * (double(m_xShape->getSize().Width)
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height));
+ tools::Long nVal1 = std::lround((*aPointY.nMaxVal - *aPointY.nCurrVal)
+ / (*aPointY.nMaxVal - *aPointY.nMinVal) * nMaxVal1);
+ tools::Long nVal2 = std::lround((*aPointX.nCurrVal - *aPointX.nMinVal)
+ / (*aPointX.nMaxVal - *aPointX.nMinVal) * nMaxVal2);
+ return StartAVListWriting()
+ && WriteAV(u"adj1", OUString(u"val " + OUString::number(nVal1)))
+ && WriteAV(u"adj2", OUString(u"val " + OUString::number(nVal2)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "leftArrowCallout")
+ {
+ auto aBoxWidth = GetAdjustmentPointXValue(0);
+ auto aNeckLength = GetAdjustmentPointXValue(1);
+ auto aNeckFromBox = GetAdjustmentPointYValue(1);
+ auto aHeadFromNeck = GetAdjustmentPointYValue(2);
+ if (!aBoxWidth.nCurrVal.has_value() || !aBoxWidth.nMaxVal.has_value()
+ || !aBoxWidth.nMinVal.has_value() || !aNeckLength.nCurrVal.has_value()
+ || !aNeckLength.nMaxVal.has_value() || !aNeckLength.nMinVal.has_value()
+ || !aNeckFromBox.nCurrVal.has_value() || !aNeckFromBox.nMaxVal.has_value()
+ || !aNeckFromBox.nMinVal.has_value() || !aHeadFromNeck.nCurrVal.has_value()
+ || !aHeadFromNeck.nMaxVal.has_value() || !aHeadFromNeck.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nMaxVal1
+ = 100000 * m_xShape->getSize().Height
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nMaxVal2
+ = 50000 * m_xShape->getSize().Height
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nMaxVal3
+ = 100000 * m_xShape->getSize().Width
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nVal1
+ = std::lround((*aNeckFromBox.nMaxVal - *aNeckFromBox.nCurrVal)
+ / (*aNeckFromBox.nMaxVal - *aNeckFromBox.nMinVal) * nMaxVal1);
+ tools::Long nVal2 = std::lround((10800 - *aHeadFromNeck.nCurrVal)
+ / (10800 - *aHeadFromNeck.nMinVal) * nMaxVal2);
+ tools::Long nVal3 = std::lround((*aNeckLength.nCurrVal - *aNeckLength.nMinVal)
+ / (21600 - *aNeckLength.nMinVal) * nMaxVal3);
+ tools::Long nVal4 = std::lround((*aBoxWidth.nMaxVal - *aBoxWidth.nCurrVal)
+ / (*aBoxWidth.nMaxVal - *aBoxWidth.nMinVal) * 100000);
+ return StartAVListWriting()
+ && WriteAV(u"adj1", OUString(u"val " + OUString::number(nVal1)))
+ && WriteAV(u"adj2", OUString(u"val " + OUString::number(nVal2)))
+ && WriteAV(u"adj3", OUString(u"val " + OUString::number(nVal3)))
+ && WriteAV(u"adj4", OUString(u"val " + OUString::number(nVal4)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "leftBrace")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "leftBracket")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "leftCircularArrow")
+ {
+ // LO does not have this type, not necessary to map
+ return false;
+ }
+ if (sShapeType == "leftRightArrow")
+ {
+ auto aPointX = GetAdjustmentPointXValue(0);
+ auto aPointY = GetAdjustmentPointYValue(0);
+ if (!aPointX.nCurrVal.has_value() || !aPointX.nMaxVal.has_value()
+ || !aPointX.nMinVal.has_value() || !aPointY.nCurrVal.has_value()
+ || !aPointY.nMaxVal.has_value() || !aPointY.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nMaxVal1 = 100000;
+ tools::Long nMaxVal2
+ = 50000
+ * (double(m_xShape->getSize().Width)
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height));
+ tools::Long nVal1 = std::lround((*aPointY.nMaxVal - *aPointY.nCurrVal)
+ / (*aPointY.nMaxVal - *aPointY.nMinVal) * nMaxVal1);
+ tools::Long nVal2 = std::lround((*aPointX.nCurrVal - *aPointX.nMinVal)
+ / (*aPointX.nMaxVal - *aPointX.nMinVal) * nMaxVal2);
+ return StartAVListWriting()
+ && WriteAV(u"adj1", OUString(u"val " + OUString::number(nVal1)))
+ && WriteAV(u"adj2", OUString(u"val " + OUString::number(nVal2)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "leftRightArrowCallout")
+ {
+ auto aNeckFromBox = GetAdjustmentPointXValue(1);
+ auto aHeadFromNeck = GetAdjustmentPointXValue(2);
+ auto aHeadHeight = GetAdjustmentPointYValue(1);
+ auto aBoxHeight = GetAdjustmentPointYValue(0);
+ if (!aNeckFromBox.nCurrVal.has_value() || !aNeckFromBox.nMaxVal.has_value()
+ || !aNeckFromBox.nMinVal.has_value() || !aHeadFromNeck.nCurrVal.has_value()
+ || !aHeadFromNeck.nMaxVal.has_value() || !aHeadFromNeck.nMinVal.has_value()
+ || !aHeadHeight.nCurrVal.has_value() || !aHeadHeight.nMaxVal.has_value()
+ || !aHeadHeight.nMinVal.has_value() || !aBoxHeight.nCurrVal.has_value()
+ || !aBoxHeight.nMaxVal.has_value() || !aBoxHeight.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nMaxVal1
+ = 100000 * m_xShape->getSize().Width
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nMaxVal2
+ = 50000 * m_xShape->getSize().Width
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nMaxVal3
+ = 100000 * m_xShape->getSize().Height
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nVal1
+ = std::lround((*aNeckFromBox.nMaxVal - *aNeckFromBox.nCurrVal)
+ / (*aNeckFromBox.nMaxVal - *aNeckFromBox.nMinVal) * nMaxVal1);
+ tools::Long nVal2 = std::lround((10800 - *aHeadFromNeck.nCurrVal)
+ / (10800 - *aHeadFromNeck.nMinVal) * nMaxVal2);
+ tools::Long nVal3 = std::lround((*aHeadHeight.nCurrVal - *aHeadHeight.nMinVal)
+ / (21600 - *aHeadHeight.nMinVal) * nMaxVal3);
+ tools::Long nVal4 = std::lround((*aBoxHeight.nCurrVal - *aBoxHeight.nMinVal)
+ / (10800 - *aBoxHeight.nMinVal) * 100000);
+ return StartAVListWriting()
+ && WriteAV(u"adj1", OUString(u"val " + OUString::number(nVal1)))
+ && WriteAV(u"adj2", OUString(u"val " + OUString::number(nVal2)))
+ && WriteAV(u"adj3", OUString(u"val " + OUString::number(nVal3)))
+ && WriteAV(u"adj4", OUString(u"val " + OUString::number(nVal4)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "leftRightCircularArrow")
+ {
+ // Not found in word
+ return false;
+ }
+ if (sShapeType == "leftRightRibbon")
+ {
+ // LO does not have this type so mapping not necessary
+ return false;
+ }
+ if (sShapeType == "leftRightUpArrow")
+ {
+ // TODO?
+ // MS Word stretches the arrow to fit the bounding box; LO doesn't
+ return false;
+ }
+ if (sShapeType == "leftUpArrow")
+ {
+ // MS Word's and LO's interpretations of what a leftUpArrow should look like
+ // are too different to find a compromise :(
+ }
+ if (sShapeType == "lightningBolt")
+ {
+ // Difference between the SDR and OOXML variants, custgeom?
+ return false;
+ }
+ if (sShapeType == "line")
+ {
+ // Not necessary
+ return false;
+ }
+ if (sShapeType == "lineInv")
+ {
+ // Not necessary
+ return false;
+ }
+ if (sShapeType == "mathDivide")
+ {
+ // LO does not have this type so mapping not necessary
+ return false;
+ }
+ if (sShapeType == "mathEqual")
+ {
+ // LO does not have this type so mapping not necessary
+ return false;
+ }
+ if (sShapeType == "mathMinus")
+ {
+ // LO does not have this type so mapping not necessary
+ return false;
+ }
+ if (sShapeType == "mathMultiply")
+ {
+ // LO does not have this type so mapping not necessary
+ return false;
+ }
+ if (sShapeType == "mathNotEqual")
+ {
+ // LO does not have this type so mapping not necessary
+ return false;
+ }
+ if (sShapeType == "mathPlus")
+ {
+ // LO does not have this type so mapping not necessary
+ return false;
+ }
+ if (sShapeType == "nonIsoscelesTrapezoid")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "noSmoking")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "notchedRightArrow")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "octagon")
+ {
+ auto aPoint1 = GetAdjustmentPointXValue(0);
+ if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value()
+ || !aPoint1.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nVal1
+ = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * 50000);
+ return StartAVListWriting()
+ && WriteAV(u"adj", OUString(u"val " + OUString::number(nVal1)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "parallelogram")
+ {
+ auto aPoint1 = GetAdjustmentPointXValue(0);
+ if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value()
+ || !aPoint1.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nMaxVal = 100000 * m_xShape->getSize().Width
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nVal1
+ = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * nMaxVal);
+ return StartAVListWriting()
+ && WriteAV(u"adj", OUString(u"val " + OUString::number(nVal1)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "pentagon")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "pie")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "pieWedge")
+ {
+ // Not found in word.
+ return false;
+ }
+ if (sShapeType == "plaque")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "plaqueTabs")
+ {
+ // LO does not have this, so not necessary to map.
+ return false;
+ }
+ if (sShapeType == "plus")
+ {
+ auto aPoint1 = GetAdjustmentPointXValue(0);
+ if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value()
+ || !aPoint1.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nVal1
+ = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * 50000);
+ return StartAVListWriting()
+ && WriteAV(u"adj", OUString(u"val " + OUString::number(nVal1)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "quadArrow")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "quadArrowCallout")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "rect")
+ {
+ // preset enough without AV points.
+ return false;
+ }
+ if (sShapeType == "ribbon")
+ {
+ // LO does not have this, so not necessary to map.
+ return false;
+ }
+ if (sShapeType == "ribbon2")
+ {
+ // LO does not have this, so not necessary to map.
+ return false;
+ }
+ if (sShapeType == "rightArrow")
+ {
+ auto aPointX = GetAdjustmentPointXValue(0);
+ auto aPointY = GetAdjustmentPointYValue(0);
+ if (!aPointX.nCurrVal.has_value() || !aPointX.nMaxVal.has_value()
+ || !aPointX.nMinVal.has_value() || !aPointY.nCurrVal.has_value()
+ || !aPointY.nMaxVal.has_value() || !aPointY.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nMaxVal1 = 100000;
+ tools::Long nMaxVal2
+ = 100000
+ * (double(m_xShape->getSize().Width)
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height));
+ tools::Long nVal1 = std::lround((*aPointY.nMaxVal - *aPointY.nCurrVal)
+ / (*aPointY.nMaxVal - *aPointY.nMinVal) * nMaxVal1);
+ tools::Long nVal2 = std::lround((*aPointX.nMaxVal - *aPointX.nCurrVal)
+ / (*aPointX.nMaxVal - *aPointX.nMinVal) * nMaxVal2);
+ return StartAVListWriting()
+ && WriteAV(u"adj1", OUString(u"val " + OUString::number(nVal1)))
+ && WriteAV(u"adj2", OUString(u"val " + OUString::number(nVal2)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "rightArrowCallout")
+ {
+ auto aBoxWidth = GetAdjustmentPointXValue(0);
+ auto aNeckLength = GetAdjustmentPointXValue(1);
+ auto aNeckFromBox = GetAdjustmentPointYValue(1);
+ auto aHeadFromNeck = GetAdjustmentPointYValue(2);
+ if (!aBoxWidth.nCurrVal.has_value() || !aBoxWidth.nMaxVal.has_value()
+ || !aBoxWidth.nMinVal.has_value() || !aNeckLength.nCurrVal.has_value()
+ || !aNeckLength.nMaxVal.has_value() || !aNeckLength.nMinVal.has_value()
+ || !aNeckFromBox.nCurrVal.has_value() || !aNeckFromBox.nMaxVal.has_value()
+ || !aNeckFromBox.nMinVal.has_value() || !aHeadFromNeck.nCurrVal.has_value()
+ || !aHeadFromNeck.nMaxVal.has_value() || !aHeadFromNeck.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nMaxVal1
+ = 100000 * m_xShape->getSize().Height
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nMaxVal2
+ = 50000 * m_xShape->getSize().Height
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nMaxVal3
+ = 100000 * m_xShape->getSize().Width
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nVal1
+ = std::lround((*aNeckFromBox.nMaxVal - *aNeckFromBox.nCurrVal)
+ / (*aNeckFromBox.nMaxVal - *aNeckFromBox.nMinVal) * nMaxVal1);
+ tools::Long nVal2 = std::lround((10800 - *aHeadFromNeck.nCurrVal)
+ / (10800 - *aHeadFromNeck.nMinVal) * nMaxVal2);
+ tools::Long nVal3
+ = std::lround((*aNeckLength.nMaxVal - *aNeckLength.nCurrVal)
+ / (*aNeckLength.nMaxVal - *aNeckLength.nMinVal) * nMaxVal3);
+ tools::Long nVal4 = std::lround((*aBoxWidth.nCurrVal - *aBoxWidth.nMinVal)
+ / (21600 - *aBoxWidth.nMinVal) * 100000);
+ return StartAVListWriting()
+ && WriteAV(u"adj1", OUString(u"val " + OUString::number(nVal1)))
+ && WriteAV(u"adj2", OUString(u"val " + OUString::number(nVal2)))
+ && WriteAV(u"adj3", OUString(u"val " + OUString::number(nVal3)))
+ && WriteAV(u"adj4", OUString(u"val " + OUString::number(nVal4)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "rightBrace")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "rightBracket")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "round1Rect")
+ {
+ // LO does not have this, so not necessary to map.
+ return false;
+ }
+ if (sShapeType == "round2DiagRect")
+ {
+ // LO does not have this, so not necessary to map.
+ return false;
+ }
+ if (sShapeType == "round2SameRect")
+ {
+ // LO does not have this, so not necessary to map.
+ return false;
+ }
+ if (sShapeType == "roundRect")
+ {
+ tools::Long nVal1 = 0;
+ if (m_xShape->getSize().Width >= m_xShape->getSize().Height)
+ {
+ auto aPointX = GetAdjustmentPointXValue(0);
+ if (!aPointX.nCurrVal.has_value() || !aPointX.nMaxVal.has_value()
+ || !aPointX.nMinVal.has_value())
+ return false;
+ nVal1 = std::lround(*aPointX.nCurrVal / (*aPointX.nMaxVal - *aPointX.nMinVal)
+ * 50000);
+ }
+ else
+ {
+ auto aPointY = GetAdjustmentPointYValue(0);
+ if (!aPointY.nCurrVal.has_value() || !aPointY.nMaxVal.has_value()
+ || !aPointY.nMinVal.has_value())
+ return false;
+ nVal1 = std::lround(*aPointY.nCurrVal / (*aPointY.nMaxVal - *aPointY.nMinVal)
+ * 50000);
+ }
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ return StartAVListWriting()
+ && WriteAV(u"adj", OUString(u"val " + OUString::number(nVal1)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "rtTriangle")
+ {
+ // Does not have AV points not necessary to map
+ return false;
+ }
+ if (sShapeType == "smileyFace")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "snip1Rect")
+ {
+ // LO does not have this, so not necessary to map.
+ return false;
+ }
+ if (sShapeType == "snip2DiagRect")
+ {
+ // LO does not have this, so not necessary to map.
+ return false;
+ }
+ if (sShapeType == "snip2SameRect")
+ {
+ // LO does not have this, so not necessary to map.
+ return false;
+ }
+ if (sShapeType == "snipRoundRect")
+ {
+ // LO does not have this, so not necessary to map.
+ return false;
+ }
+ if (sShapeType == "squareTabs")
+ {
+ // LO does not have this, so not necessary to map.
+ return false;
+ }
+ if (sShapeType == "star10")
+ {
+ // LO does not have this, so not necessary to map.
+ return false;
+ }
+ if (sShapeType == "star12")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "star16")
+ {
+ // LO does not have this, so not necessary to map.
+ return false;
+ }
+ if (sShapeType == "star24")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "star32")
+ {
+ // LO does not have this, so not necessary to map.
+ return false;
+ }
+ if (sShapeType == "star4")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "star5")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "star6")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "star7")
+ {
+ // LO does not have this, so not necessary to map.
+ return false;
+ }
+ if (sShapeType == "star8")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "straightConnector1")
+ {
+ // Not necessary to map.
+ return false;
+ }
+ if (sShapeType == "stripedRightArrow")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "sun")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "swooshArrow")
+ {
+ // Not found in word.
+ return false;
+ }
+ if (sShapeType == "teardrop")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "trapezoid")
+ {
+ // Preset enough.
+ return false;
+ }
+ if (sShapeType == "triangle")
+ {
+ auto aPoint1 = GetAdjustmentPointXValue(0);
+ if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value()
+ || !aPoint1.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nMaxVal = 100000;
+ tools::Long nVal1
+ = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * nMaxVal);
+ return StartAVListWriting()
+ && WriteAV(u"adj", OUString(u"val " + OUString::number(nVal1)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "upArrowCallout")
+ {
+ auto aNeckFromBox = GetAdjustmentPointXValue(1);
+ auto aHeadFromNeck = GetAdjustmentPointXValue(2);
+ auto aHeadHeight = GetAdjustmentPointYValue(1);
+ auto aBoxHeight = GetAdjustmentPointYValue(0);
+ if (!aNeckFromBox.nCurrVal.has_value() || !aNeckFromBox.nMaxVal.has_value()
+ || !aNeckFromBox.nMinVal.has_value() || !aHeadFromNeck.nCurrVal.has_value()
+ || !aHeadFromNeck.nMaxVal.has_value() || !aHeadFromNeck.nMinVal.has_value()
+ || !aHeadHeight.nCurrVal.has_value() || !aHeadHeight.nMaxVal.has_value()
+ || !aHeadHeight.nMinVal.has_value() || !aBoxHeight.nCurrVal.has_value()
+ || !aBoxHeight.nMaxVal.has_value() || !aBoxHeight.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nMaxVal1
+ = 100000 * m_xShape->getSize().Width
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nMaxVal2
+ = 50000 * m_xShape->getSize().Width
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nMaxVal3
+ = 100000 * m_xShape->getSize().Height
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nVal1
+ = std::lround((*aNeckFromBox.nMaxVal - *aNeckFromBox.nCurrVal)
+ / (*aNeckFromBox.nMaxVal - *aNeckFromBox.nMinVal) * nMaxVal1);
+ tools::Long nVal2 = std::lround((10800 - *aHeadFromNeck.nCurrVal)
+ / (10800 - *aHeadFromNeck.nMinVal) * nMaxVal2);
+ tools::Long nVal3 = std::lround((*aHeadHeight.nCurrVal - *aHeadHeight.nMinVal)
+ / (21600 - *aHeadHeight.nMinVal) * nMaxVal3);
+ tools::Long nVal4 = std::lround((*aBoxHeight.nCurrVal - *aBoxHeight.nMinVal)
+ / (10800 - *aBoxHeight.nMinVal) * 100000);
+ return StartAVListWriting()
+ && WriteAV(u"adj1", OUString(u"val " + OUString::number(nVal1)))
+ && WriteAV(u"adj2", OUString(u"val " + OUString::number(nVal2)))
+ && WriteAV(u"adj3", OUString(u"val " + OUString::number(nVal3)))
+ && WriteAV(u"adj4", OUString(u"val " + OUString::number(nVal4)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "upDownArrow")
+ {
+ auto aPointX = GetAdjustmentPointXValue(0);
+ auto aPointY = GetAdjustmentPointYValue(0);
+ if (!aPointX.nCurrVal.has_value() || !aPointX.nMaxVal.has_value()
+ || !aPointX.nMinVal.has_value() || !aPointY.nCurrVal.has_value()
+ || !aPointY.nMaxVal.has_value() || !aPointY.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nMaxVal1 = 100000;
+ tools::Long nMaxVal2
+ = 50000 * m_xShape->getSize().Height
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nVal1 = std::lround((*aPointX.nMaxVal - *aPointX.nCurrVal)
+ / (*aPointX.nMaxVal - *aPointX.nMinVal) * nMaxVal1);
+ tools::Long nVal2 = std::lround((*aPointY.nCurrVal - *aPointY.nMinVal)
+ / (*aPointY.nMaxVal - *aPointY.nMinVal) * nMaxVal2);
+ return StartAVListWriting()
+ && WriteAV(u"adj1", OUString(u"val " + OUString::number(nVal1)))
+ && WriteAV(u"adj2", OUString(u"val " + OUString::number(nVal2)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "upArrow")
+ {
+ auto aPointX = GetAdjustmentPointXValue(0);
+ auto aPointY = GetAdjustmentPointYValue(0);
+ if (!aPointX.nCurrVal.has_value() || !aPointX.nMaxVal.has_value()
+ || !aPointX.nMinVal.has_value() || !aPointY.nCurrVal.has_value()
+ || !aPointY.nMaxVal.has_value() || !aPointY.nMinVal.has_value())
+ return false;
+
+ m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(),
+ false, false);
+ tools::Long nMaxVal1 = 100000;
+ tools::Long nMaxVal2
+ = 100000 * m_xShape->getSize().Height
+ / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height);
+ tools::Long nVal1 = std::lround((*aPointX.nMaxVal - *aPointX.nCurrVal)
+ / (*aPointX.nMaxVal - *aPointX.nMinVal) * nMaxVal1);
+ tools::Long nVal2 = std::lround((*aPointY.nCurrVal - *aPointY.nMinVal)
+ / (*aPointY.nMaxVal - *aPointY.nMinVal) * nMaxVal2);
+ return StartAVListWriting()
+ && WriteAV(u"adj1", OUString(u"val " + OUString::number(nVal1)))
+ && WriteAV(u"adj2", OUString(u"val " + OUString::number(nVal2)))
+ && EndAVListWriting();
+ }
+ if (sShapeType == "upDownArrowCallout")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "uturnArrow")
+ {
+ // LO does not have like this.
+ return false;
+ }
+ if (sShapeType == "verticalScroll")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "wave")
+ {
+ // LO does not have.
+ return false;
+ }
+ if (sShapeType == "wedgeEllipseCallout")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "wedgeRectCallout")
+ {
+ // TODO
+ return false;
+ }
+ if (sShapeType == "wedgeRoundRectCallout")
+ {
+ // TODO
+ return false;
+ }
+ }
+ catch (...)
+ {
+ // Problem detected with the writing, aborting and trying to find another way.
+ return false;
+ }
+
+ // Default, nothing happened return.
+ return false;
+};
+}
diff --git a/oox/source/export/README b/oox/source/export/README
new file mode 100644
index 000000000..56195b977
--- /dev/null
+++ b/oox/source/export/README
@@ -0,0 +1,2 @@
+presetTextWarpDefinitions.xml and presetShapeDefinitions.xml come from the
+OOXML documentation (TC45).
diff --git a/oox/source/export/chartexport.cxx b/oox/source/export/chartexport.cxx
new file mode 100644
index 000000000..8a6d5fa7d
--- /dev/null
+++ b/oox/source/export/chartexport.cxx
@@ -0,0 +1,4685 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <oox/token/namespaces.hxx>
+#include <oox/token/properties.hxx>
+#include <oox/token/tokens.hxx>
+#include <oox/core/xmlfilterbase.hxx>
+#include <oox/export/chartexport.hxx>
+#include <oox/token/relationship.hxx>
+#include <oox/export/utils.hxx>
+#include <drawingml/chart/typegroupconverter.hxx>
+
+#include <cstdio>
+#include <limits>
+
+#include <com/sun/star/awt/Gradient.hpp>
+#include <com/sun/star/chart/XChartDocument.hpp>
+#include <com/sun/star/chart/ChartLegendPosition.hpp>
+#include <com/sun/star/chart/XTwoAxisXSupplier.hpp>
+#include <com/sun/star/chart/XTwoAxisYSupplier.hpp>
+#include <com/sun/star/chart/XAxisZSupplier.hpp>
+#include <com/sun/star/chart/ChartDataRowSource.hpp>
+#include <com/sun/star/chart/X3DDisplay.hpp>
+#include <com/sun/star/chart/XStatisticDisplay.hpp>
+#include <com/sun/star/chart/XSecondAxisTitleSupplier.hpp>
+#include <com/sun/star/chart/ChartSymbolType.hpp>
+#include <com/sun/star/chart/ChartAxisMarks.hpp>
+#include <com/sun/star/chart/ChartAxisLabelPosition.hpp>
+#include <com/sun/star/chart/ChartAxisPosition.hpp>
+#include <com/sun/star/chart/ChartSolidType.hpp>
+#include <com/sun/star/chart/DataLabelPlacement.hpp>
+#include <com/sun/star/chart/ErrorBarStyle.hpp>
+#include <com/sun/star/chart/MissingValueTreatment.hpp>
+#include <com/sun/star/chart/XDiagramPositioning.hpp>
+#include <com/sun/star/chart/TimeIncrement.hpp>
+#include <com/sun/star/chart/TimeInterval.hpp>
+#include <com/sun/star/chart/TimeUnit.hpp>
+
+#include <com/sun/star/chart2/RelativePosition.hpp>
+#include <com/sun/star/chart2/RelativeSize.hpp>
+#include <com/sun/star/chart2/XChartDocument.hpp>
+#include <com/sun/star/chart2/XDiagram.hpp>
+#include <com/sun/star/chart2/XCoordinateSystemContainer.hpp>
+#include <com/sun/star/chart2/XRegressionCurveContainer.hpp>
+#include <com/sun/star/chart2/XChartTypeContainer.hpp>
+#include <com/sun/star/chart2/XDataSeriesContainer.hpp>
+#include <com/sun/star/chart2/DataPointLabel.hpp>
+#include <com/sun/star/chart2/XDataPointCustomLabelField.hpp>
+#include <com/sun/star/chart2/DataPointCustomLabelFieldType.hpp>
+#include <com/sun/star/chart2/Symbol.hpp>
+#include <com/sun/star/chart2/data/XDataSource.hpp>
+#include <com/sun/star/chart2/data/XDataProvider.hpp>
+#include <com/sun/star/chart2/data/XTextualDataSequence.hpp>
+#include <com/sun/star/chart2/data/XNumericalDataSequence.hpp>
+#include <com/sun/star/chart2/data/XLabeledDataSequence.hpp>
+#include <com/sun/star/chart2/XAnyDescriptionAccess.hpp>
+#include <com/sun/star/chart2/AxisType.hpp>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/drawing/XShapes.hpp>
+#include <com/sun/star/drawing/FillStyle.hpp>
+#include <com/sun/star/drawing/LineStyle.hpp>
+#include <com/sun/star/awt/XBitmap.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XServiceName.hpp>
+
+#include <com/sun/star/table/CellAddress.hpp>
+#include <com/sun/star/sheet/XFormulaParser.hpp>
+#include <com/sun/star/sheet/FormulaToken.hpp>
+#include <com/sun/star/sheet/AddressConvention.hpp>
+
+#include <com/sun/star/container/XNamed.hpp>
+#include <com/sun/star/embed/XVisualObject.hpp>
+#include <com/sun/star/embed/Aspects.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/random.hxx>
+#include <utility>
+#include <xmloff/SchXMLSeriesHelper.hxx>
+#include "ColorPropertySet.hxx"
+
+#include <svl/numformat.hxx>
+#include <svl/numuno.hxx>
+#include <tools/diagnose_ex.h>
+#include <sal/log.hxx>
+
+#include <set>
+#include <unordered_set>
+
+#include <o3tl/temporary.hxx>
+#include <o3tl/sorted_vector.hxx>
+
+using namespace css;
+using namespace css::uno;
+using namespace css::drawing;
+using namespace ::oox::core;
+using css::beans::PropertyValue;
+using css::beans::XPropertySet;
+using css::container::XNamed;
+using css::table::CellAddress;
+using css::sheet::XFormulaParser;
+using ::oox::core::XmlFilterBase;
+using ::sax_fastparser::FSHelperPtr;
+
+namespace cssc = css::chart;
+
+namespace oox::drawingml {
+
+namespace {
+
+bool isPrimaryAxes(sal_Int32 nIndex)
+{
+ assert(nIndex == 0 || nIndex == 1);
+ return nIndex != 1;
+}
+
+class lcl_MatchesRole
+{
+public:
+ explicit lcl_MatchesRole( const OUString & aRole ) :
+ m_aRole( aRole )
+ {}
+
+ bool operator () ( const Reference< chart2::data::XLabeledDataSequence > & xSeq ) const
+ {
+ if( !xSeq.is() )
+ return false;
+ Reference< beans::XPropertySet > xProp( xSeq->getValues(), uno::UNO_QUERY );
+ OUString aRole;
+
+ return ( xProp.is() &&
+ (xProp->getPropertyValue( "Role" ) >>= aRole ) &&
+ m_aRole == aRole );
+ }
+
+private:
+ OUString m_aRole;
+};
+
+}
+
+static Reference< chart2::data::XLabeledDataSequence > lcl_getCategories( const Reference< chart2::XDiagram > & xDiagram, bool& bHasDateCategories )
+{
+ bHasDateCategories = false;
+ Reference< chart2::data::XLabeledDataSequence > xResult;
+ try
+ {
+ Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
+ xDiagram, uno::UNO_QUERY_THROW );
+ const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
+ xCooSysCnt->getCoordinateSystems());
+ for( const auto& xCooSys : aCooSysSeq )
+ {
+ OSL_ASSERT( xCooSys.is());
+ for( sal_Int32 nN = xCooSys->getDimension(); nN--; )
+ {
+ const sal_Int32 nMaxAxisIndex = xCooSys->getMaximumAxisIndexByDimension(nN);
+ for(sal_Int32 nI=0; nI<=nMaxAxisIndex; ++nI)
+ {
+ Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension( nN, nI );
+ OSL_ASSERT( xAxis.is());
+ if( xAxis.is())
+ {
+ chart2::ScaleData aScaleData = xAxis->getScaleData();
+ if( aScaleData.Categories.is())
+ {
+ bHasDateCategories = aScaleData.AxisType == chart2::AxisType::DATE;
+ xResult.set( aScaleData.Categories );
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+
+ return xResult;
+}
+
+static Reference< chart2::data::XLabeledDataSequence >
+ lcl_getDataSequenceByRole(
+ const Sequence< Reference< chart2::data::XLabeledDataSequence > > & aLabeledSeq,
+ const OUString & rRole )
+{
+ Reference< chart2::data::XLabeledDataSequence > aNoResult;
+
+ const Reference< chart2::data::XLabeledDataSequence > * pBegin = aLabeledSeq.getConstArray();
+ const Reference< chart2::data::XLabeledDataSequence > * pEnd = pBegin + aLabeledSeq.getLength();
+ const Reference< chart2::data::XLabeledDataSequence > * pMatch =
+ ::std::find_if( pBegin, pEnd, lcl_MatchesRole( rRole ));
+
+ if( pMatch != pEnd )
+ return *pMatch;
+
+ return aNoResult;
+}
+
+static bool lcl_hasCategoryLabels( const Reference< chart2::XChartDocument >& xChartDoc )
+{
+ //categories are always the first sequence
+ Reference< chart2::XDiagram > xDiagram( xChartDoc->getFirstDiagram());
+ bool bDateCategories;
+ Reference< chart2::data::XLabeledDataSequence > xCategories( lcl_getCategories( xDiagram, bDateCategories ) );
+ return xCategories.is();
+}
+
+static bool lcl_isCategoryAxisShifted( const Reference< chart2::XDiagram >& xDiagram )
+{
+ bool bCategoryPositionShifted = false;
+ try
+ {
+ Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
+ xDiagram, uno::UNO_QUERY_THROW);
+ const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
+ xCooSysCnt->getCoordinateSystems());
+ for (const auto& xCooSys : aCooSysSeq)
+ {
+ OSL_ASSERT(xCooSys.is());
+ if( 0 < xCooSys->getDimension() && 0 <= xCooSys->getMaximumAxisIndexByDimension(0) )
+ {
+ Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(0, 0);
+ OSL_ASSERT(xAxis.is());
+ if (xAxis.is())
+ {
+ chart2::ScaleData aScaleData = xAxis->getScaleData();
+ bCategoryPositionShifted = aScaleData.ShiftedCategoryPosition;
+ break;
+ }
+ }
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+
+ return bCategoryPositionShifted;
+}
+
+static sal_Int32 lcl_getCategoryAxisType( const Reference< chart2::XDiagram >& xDiagram, sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex )
+{
+ sal_Int32 nAxisType = -1;
+ try
+ {
+ Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
+ xDiagram, uno::UNO_QUERY_THROW);
+ const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
+ xCooSysCnt->getCoordinateSystems());
+ for( const auto& xCooSys : aCooSysSeq )
+ {
+ OSL_ASSERT(xCooSys.is());
+ if( nDimensionIndex < xCooSys->getDimension() && nAxisIndex <= xCooSys->getMaximumAxisIndexByDimension(nDimensionIndex) )
+ {
+ Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(nDimensionIndex, nAxisIndex);
+ OSL_ASSERT(xAxis.is());
+ if( xAxis.is() )
+ {
+ chart2::ScaleData aScaleData = xAxis->getScaleData();
+ nAxisType = aScaleData.AxisType;
+ break;
+ }
+ }
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+
+ return nAxisType;
+}
+
+static OUString lclGetTimeUnitToken( sal_Int32 nTimeUnit )
+{
+ switch( nTimeUnit )
+ {
+ case cssc::TimeUnit::DAY: return "days";
+ case cssc::TimeUnit::MONTH: return "months";
+ case cssc::TimeUnit::YEAR: return "years";
+ default: OSL_ENSURE(false, "lclGetTimeUnitToken - unexpected time unit");
+ }
+ return "days";
+}
+
+static cssc::TimeIncrement lcl_getDateTimeIncrement( const Reference< chart2::XDiagram >& xDiagram, sal_Int32 nAxisIndex )
+{
+ cssc::TimeIncrement aTimeIncrement;
+ try
+ {
+ Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(
+ xDiagram, uno::UNO_QUERY_THROW);
+ const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(
+ xCooSysCnt->getCoordinateSystems());
+ for( const auto& xCooSys : aCooSysSeq )
+ {
+ OSL_ASSERT(xCooSys.is());
+ if( 0 < xCooSys->getDimension() && nAxisIndex <= xCooSys->getMaximumAxisIndexByDimension(0) )
+ {
+ Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(0, nAxisIndex);
+ OSL_ASSERT(xAxis.is());
+ if( xAxis.is() )
+ {
+ chart2::ScaleData aScaleData = xAxis->getScaleData();
+ aTimeIncrement = aScaleData.TimeIncrement;
+ break;
+ }
+ }
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+
+ return aTimeIncrement;
+}
+
+static bool lcl_isSeriesAttachedToFirstAxis(
+ const Reference< chart2::XDataSeries > & xDataSeries )
+{
+ bool bResult=true;
+
+ try
+ {
+ sal_Int32 nAxisIndex = 0;
+ Reference< beans::XPropertySet > xProp( xDataSeries, uno::UNO_QUERY_THROW );
+ xProp->getPropertyValue("AttachedAxisIndex") >>= nAxisIndex;
+ bResult = (0==nAxisIndex);
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+
+ return bResult;
+}
+
+static OUString lcl_flattenStringSequence( const Sequence< OUString > & rSequence )
+{
+ OUStringBuffer aResult;
+ bool bPrecedeWithSpace = false;
+ for( const auto& rString : rSequence )
+ {
+ if( !rString.isEmpty())
+ {
+ if( bPrecedeWithSpace )
+ aResult.append( ' ' );
+ aResult.append( rString );
+ bPrecedeWithSpace = true;
+ }
+ }
+ return aResult.makeStringAndClear();
+}
+
+static Sequence< OUString > lcl_getLabelSequence( const Reference< chart2::data::XDataSequence > & xLabelSeq )
+{
+ Sequence< OUString > aLabels;
+
+ uno::Reference< chart2::data::XTextualDataSequence > xTextualDataSequence( xLabelSeq, uno::UNO_QUERY );
+ if( xTextualDataSequence.is())
+ {
+ aLabels = xTextualDataSequence->getTextualData();
+ }
+ else if( xLabelSeq.is())
+ {
+ const Sequence< uno::Any > aAnies( xLabelSeq->getData());
+ aLabels.realloc( aAnies.getLength());
+ auto pLabels = aLabels.getArray();
+ for( sal_Int32 i=0; i<aAnies.getLength(); ++i )
+ aAnies[i] >>= pLabels[i];
+ }
+
+ return aLabels;
+}
+
+static void lcl_fillCategoriesIntoStringVector(
+ const Reference< chart2::data::XDataSequence > & xCategories,
+ ::std::vector< OUString > & rOutCategories )
+{
+ OSL_ASSERT( xCategories.is());
+ if( !xCategories.is())
+ return;
+ Reference< chart2::data::XTextualDataSequence > xTextualDataSequence( xCategories, uno::UNO_QUERY );
+ if( xTextualDataSequence.is())
+ {
+ rOutCategories.clear();
+ const Sequence< OUString > aTextData( xTextualDataSequence->getTextualData());
+ rOutCategories.insert( rOutCategories.end(), aTextData.begin(), aTextData.end() );
+ }
+ else
+ {
+ Sequence< uno::Any > aAnies( xCategories->getData());
+ rOutCategories.resize( aAnies.getLength());
+ for( sal_Int32 i=0; i<aAnies.getLength(); ++i )
+ aAnies[i] >>= rOutCategories[i];
+ }
+}
+
+static ::std::vector< double > lcl_getAllValuesFromSequence( const Reference< chart2::data::XDataSequence > & xSeq )
+{
+ ::std::vector< double > aResult;
+
+ Reference< chart2::data::XNumericalDataSequence > xNumSeq( xSeq, uno::UNO_QUERY );
+ if( xNumSeq.is())
+ {
+ const Sequence< double > aValues( xNumSeq->getNumericalData());
+ aResult.insert( aResult.end(), aValues.begin(), aValues.end() );
+ }
+ else if( xSeq.is())
+ {
+ Sequence< uno::Any > aAnies( xSeq->getData());
+ aResult.resize( aAnies.getLength(), std::numeric_limits<double>::quiet_NaN() );
+ for( sal_Int32 i=0; i<aAnies.getLength(); ++i )
+ aAnies[i] >>= aResult[i];
+ }
+ return aResult;
+}
+
+static sal_Int32 lcl_getChartType( std::u16string_view sChartType )
+{
+ chart::TypeId eChartTypeId = chart::TYPEID_UNKNOWN;
+ if( sChartType == u"com.sun.star.chart.BarDiagram"
+ || sChartType == u"com.sun.star.chart2.ColumnChartType" )
+ eChartTypeId = chart::TYPEID_BAR;
+ else if( sChartType == u"com.sun.star.chart.AreaDiagram"
+ || sChartType == u"com.sun.star.chart2.AreaChartType" )
+ eChartTypeId = chart::TYPEID_AREA;
+ else if( sChartType == u"com.sun.star.chart.LineDiagram"
+ || sChartType == u"com.sun.star.chart2.LineChartType" )
+ eChartTypeId = chart::TYPEID_LINE;
+ else if( sChartType == u"com.sun.star.chart.PieDiagram"
+ || sChartType == u"com.sun.star.chart2.PieChartType" )
+ eChartTypeId = chart::TYPEID_PIE;
+ else if( sChartType == u"com.sun.star.chart.DonutDiagram"
+ || sChartType == u"com.sun.star.chart2.DonutChartType" )
+ eChartTypeId = chart::TYPEID_DOUGHNUT;
+ else if( sChartType == u"com.sun.star.chart.XYDiagram"
+ || sChartType == u"com.sun.star.chart2.ScatterChartType" )
+ eChartTypeId = chart::TYPEID_SCATTER;
+ else if( sChartType == u"com.sun.star.chart.NetDiagram"
+ || sChartType == u"com.sun.star.chart2.NetChartType" )
+ eChartTypeId = chart::TYPEID_RADARLINE;
+ else if( sChartType == u"com.sun.star.chart.FilledNetDiagram"
+ || sChartType == u"com.sun.star.chart2.FilledNetChartType" )
+ eChartTypeId = chart::TYPEID_RADARAREA;
+ else if( sChartType == u"com.sun.star.chart.StockDiagram"
+ || sChartType == u"com.sun.star.chart2.CandleStickChartType" )
+ eChartTypeId = chart::TYPEID_STOCK;
+ else if( sChartType == u"com.sun.star.chart.BubbleDiagram"
+ || sChartType == u"com.sun.star.chart2.BubbleChartType" )
+ eChartTypeId = chart::TYPEID_BUBBLE;
+
+ return eChartTypeId;
+}
+
+static sal_Int32 lcl_generateRandomValue()
+{
+ return comphelper::rng::uniform_int_distribution(0, 100000000-1);
+}
+
+static sal_Int32 lcl_getAlphaFromTransparenceGradient(const awt::Gradient& rGradient, bool bStart)
+{
+ // Our alpha is a gray color value.
+ sal_uInt8 nRed = ::Color(ColorTransparency, bStart ? rGradient.StartColor : rGradient.EndColor).GetRed();
+ // drawingML alpha is a percentage on a 0..100000 scale.
+ return (255 - nRed) * oox::drawingml::MAX_PERCENT / 255;
+}
+
+bool DataLabelsRange::empty() const
+{
+ return maLabels.empty();
+}
+
+size_t DataLabelsRange::count() const
+{
+ return maLabels.size();
+}
+
+bool DataLabelsRange::hasLabel(sal_Int32 nIndex) const
+{
+ return maLabels.find(nIndex) != maLabels.end();
+}
+
+const OUString & DataLabelsRange::getRange() const
+{
+ return maRange;
+}
+
+void DataLabelsRange::setRange(const OUString& rRange)
+{
+ maRange = rRange;
+}
+
+void DataLabelsRange::setLabel(sal_Int32 nIndex, const OUString& rText)
+{
+ maLabels.emplace(nIndex, rText);
+}
+
+DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::begin() const
+{
+ return maLabels.begin();
+}
+
+DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::end() const
+{
+ return maLabels.end();
+}
+
+ChartExport::ChartExport( sal_Int32 nXmlNamespace, FSHelperPtr pFS, Reference< frame::XModel > const & xModel, XmlFilterBase* pFB, DocumentType eDocumentType )
+ : DrawingML( std::move(pFS), pFB, eDocumentType )
+ , mnXmlNamespace( nXmlNamespace )
+ , mnSeriesCount(0)
+ , mxChartModel( xModel )
+ , mpURLTransformer(std::make_shared<URLTransformer>())
+ , mbHasCategoryLabels( false )
+ , mbHasZAxis( false )
+ , mbIs3DChart( false )
+ , mbStacked(false)
+ , mbPercent(false)
+ , mbHasDateCategories(false)
+{
+}
+
+void ChartExport::SetURLTranslator(const std::shared_ptr<URLTransformer>& pTransformer)
+{
+ mpURLTransformer = pTransformer;
+}
+
+sal_Int32 ChartExport::getChartType( )
+{
+ OUString sChartType = mxDiagram->getDiagramType();
+ return lcl_getChartType( sChartType );
+}
+
+namespace {
+
+uno::Sequence< beans::PropertyValue > createArguments(
+ const OUString & rRangeRepresentation, bool bUseColumns)
+{
+ css::chart::ChartDataRowSource eRowSource = css::chart::ChartDataRowSource_ROWS;
+ if (bUseColumns)
+ eRowSource = css::chart::ChartDataRowSource_COLUMNS;
+
+ uno::Sequence<beans::PropertyValue> aArguments{
+ { "DataRowSource", -1, uno::Any(eRowSource), beans::PropertyState_DIRECT_VALUE },
+ { "FirstCellAsLabel", -1, uno::Any(false), beans::PropertyState_DIRECT_VALUE },
+ { "HasCategories", -1, uno::Any(false), beans::PropertyState_DIRECT_VALUE },
+ { "CellRangeRepresentation", -1, uno::Any(rRangeRepresentation),
+ beans::PropertyState_DIRECT_VALUE }
+ };
+
+ return aArguments;
+}
+
+Reference<chart2::XDataSeries> getPrimaryDataSeries(const Reference<chart2::XChartType>& xChartType)
+{
+ Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY_THROW);
+
+ // export dataseries for current chart-type
+ const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries());
+ for (const auto& rSeries : aSeriesSeq)
+ {
+ Reference<chart2::XDataSeries> xSource(rSeries, uno::UNO_QUERY);
+ if (xSource.is())
+ return xSource;
+ }
+
+ return Reference<chart2::XDataSeries>();
+}
+
+}
+
+Sequence< Sequence< OUString > > ChartExport::getSplitCategoriesList( const OUString& rRange )
+{
+ Reference< chart2::XChartDocument > xChartDoc(getModel(), uno::UNO_QUERY);
+ OSL_ASSERT(xChartDoc.is());
+ if (xChartDoc.is())
+ {
+ Reference< chart2::data::XDataProvider > xDataProvider(xChartDoc->getDataProvider());
+ OSL_ENSURE(xDataProvider.is(), "No DataProvider");
+ if (xDataProvider.is())
+ {
+ //detect whether the first series is a row or a column
+ bool bSeriesUsesColumns = true;
+ Reference< chart2::XDiagram > xDiagram(xChartDoc->getFirstDiagram());
+ try
+ {
+ Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(xDiagram, uno::UNO_QUERY_THROW);
+ const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(xCooSysCnt->getCoordinateSystems());
+ for (const auto& rCooSys : aCooSysSeq)
+ {
+ const Reference< chart2::XChartTypeContainer > xCTCnt(rCooSys, uno::UNO_QUERY_THROW);
+ const Sequence< Reference< chart2::XChartType > > aChartTypeSeq(xCTCnt->getChartTypes());
+ for (const auto& rChartType : aChartTypeSeq)
+ {
+ Reference< chart2::XDataSeries > xDataSeries = getPrimaryDataSeries(rChartType);
+ if (xDataSeries.is())
+ {
+ uno::Reference< chart2::data::XDataSource > xSeriesSource(xDataSeries, uno::UNO_QUERY);
+ const uno::Sequence< beans::PropertyValue > rArguments = xDataProvider->detectArguments(xSeriesSource);
+ for (const beans::PropertyValue& rProperty : rArguments)
+ {
+ if (rProperty.Name == "DataRowSource")
+ {
+ css::chart::ChartDataRowSource eRowSource;
+ if (rProperty.Value >>= eRowSource)
+ {
+ bSeriesUsesColumns = (eRowSource == css::chart::ChartDataRowSource_COLUMNS);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ DBG_UNHANDLED_EXCEPTION("chart2");
+ }
+ // detect we have an inner data table or not
+ if (xChartDoc->hasInternalDataProvider() && rRange == "categories")
+ {
+ try
+ {
+ css::uno::Reference< css::chart2::XAnyDescriptionAccess > xDataAccess(xChartDoc->getDataProvider(), uno::UNO_QUERY);
+ const Sequence< Sequence< uno::Any > >aAnyCategories(bSeriesUsesColumns ? xDataAccess->getAnyRowDescriptions() : xDataAccess->getAnyColumnDescriptions());
+ auto pMax = std::max_element(aAnyCategories.begin(), aAnyCategories.end(),
+ [](const Sequence<uno::Any>& a, const Sequence<uno::Any>& b) {
+ return a.getLength() < b.getLength(); });
+
+ //minimum is 1!
+ if (pMax != aAnyCategories.end() && pMax->getLength() > 1)
+ {
+ sal_Int32 nLevelCount = pMax->getLength();
+ //we have complex categories
+ //sort the categories name
+ Sequence<Sequence<OUString>>aFinalSplitSource(nLevelCount);
+ auto pFinalSplitSource = aFinalSplitSource.getArray();
+ for (sal_Int32 i = 0; i < nLevelCount; i++)
+ {
+ sal_Int32 nElemLabel = 0;
+ pFinalSplitSource[nLevelCount - i - 1].realloc(aAnyCategories.getLength());
+ auto pSeq = pFinalSplitSource[nLevelCount - i - 1].getArray();
+ for (auto const& elemLabel : aAnyCategories)
+ {
+ // make sure elemLabel[i] exists!
+ if (elemLabel.getLength() > i)
+ {
+ pSeq[nElemLabel] = elemLabel[i].get<OUString>();
+ nElemLabel++;
+ }
+ }
+ }
+ return aFinalSplitSource;
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+ }
+ else
+ {
+ try
+ {
+ uno::Reference< chart2::data::XDataSource > xCategoriesSource(xDataProvider->createDataSource(
+ createArguments(rRange, bSeriesUsesColumns)));
+
+ if (xCategoriesSource.is())
+ {
+ const Sequence< Reference< chart2::data::XLabeledDataSequence >> aCategories = xCategoriesSource->getDataSequences();
+ if (aCategories.getLength() > 1)
+ {
+ //we have complex categories
+ //sort the categories name
+ Sequence<Sequence<OUString>> aFinalSplitSource(aCategories.getLength());
+ std::transform(aCategories.begin(), aCategories.end(),
+ std::reverse_iterator(asNonConstRange(aFinalSplitSource).end()),
+ [](const Reference<chart2::data::XLabeledDataSequence>& xCat) {
+ return lcl_getLabelSequence(xCat->getValues()); });
+ return aFinalSplitSource;
+ }
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+ }
+ }
+ }
+
+ return Sequence< Sequence< OUString>>(0);
+}
+
+OUString ChartExport::parseFormula( const OUString& rRange )
+{
+ OUString aResult;
+ Reference< XFormulaParser > xParser;
+ uno::Reference< lang::XMultiServiceFactory > xSF = GetFB()->getModelFactory();
+ if( xSF.is() )
+ {
+ try
+ {
+ xParser.set( xSF->createInstance("com.sun.star.sheet.FormulaParser"), UNO_QUERY );
+ }
+ catch( Exception& )
+ {
+ }
+ }
+
+ SAL_WARN_IF(!xParser.is(), "oox", "creating formula parser failed");
+
+ if( xParser.is() )
+ {
+ Reference< XPropertySet > xParserProps( xParser, uno::UNO_QUERY );
+ // rRange is the result of a
+ // css::chart2::data::XDataSequence::getSourceRangeRepresentation()
+ // call that returns the range in the document's current UI notation.
+ // Creating a FormulaParser defaults to the same notation, for
+ // parseFormula() do not attempt to override the FormulaConvention
+ // property with css::sheet::AddressConvention::OOO or some such.
+ /* TODO: it would be much better to introduce a
+ * getSourceRangeRepresentation(css::sheet::AddressConvention) to
+ * return the ranges in a specific convention than converting them with
+ * the overhead of creating an XFormulaParser for each... */
+ uno::Sequence<sheet::FormulaToken> aTokens = xParser->parseFormula( rRange, CellAddress( 0, 0, 0 ) );
+ if( xParserProps.is() )
+ {
+ xParserProps->setPropertyValue("FormulaConvention", uno::Any(css::sheet::AddressConvention::XL_OOX) );
+ // For referencing named ranges correctly with special excel chart syntax.
+ xParserProps->setPropertyValue("RefConventionChartOOXML", uno::Any(true) );
+ }
+ aResult = xParser->printFormula( aTokens, CellAddress( 0, 0, 0 ) );
+ }
+ else
+ {
+ //FIXME: currently just using simple converter, e.g $Sheet1.$A$1:$C$1 -> Sheet1!$A$1:$C$1
+ OUString aRange( rRange );
+ if( aRange.startsWith("$") )
+ aRange = aRange.copy(1);
+ aRange = aRange.replaceAll(".$", "!$" );
+ aResult = aRange;
+ }
+
+ return aResult;
+}
+
+void ChartExport::WriteChartObj( const Reference< XShape >& xShape, sal_Int32 nID, sal_Int32 nChartCount )
+{
+ FSHelperPtr pFS = GetFS();
+
+ Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY );
+
+ pFS->startElementNS(mnXmlNamespace, XML_graphicFrame);
+
+ pFS->startElementNS(mnXmlNamespace, XML_nvGraphicFramePr);
+
+ // TODO: get the correct chart name chart id
+ OUString sName = "Object 1";
+ Reference< XNamed > xNamed( xShape, UNO_QUERY );
+ if (xNamed.is())
+ sName = xNamed->getName();
+
+ pFS->startElementNS( mnXmlNamespace, XML_cNvPr,
+ XML_id, OString::number(nID),
+ XML_name, sName);
+
+ OUString sURL;
+ if ( GetProperty( xShapeProps, "URL" ) )
+ mAny >>= sURL;
+ if( !sURL.isEmpty() )
+ {
+ OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(),
+ oox::getRelationship(Relationship::HYPERLINK),
+ mpURLTransformer->getTransformedString(sURL),
+ mpURLTransformer->isExternalURL(sURL));
+
+ mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
+ }
+ pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
+
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvGraphicFramePr);
+
+ if( GetDocumentType() == DOCUMENT_PPTX )
+ pFS->singleElementNS(mnXmlNamespace, XML_nvPr);
+ pFS->endElementNS( mnXmlNamespace, XML_nvGraphicFramePr );
+
+ // visual chart properties
+ WriteShapeTransformation( xShape, mnXmlNamespace );
+
+ // writer chart object
+ pFS->startElement(FSNS(XML_a, XML_graphic));
+ pFS->startElement( FSNS( XML_a, XML_graphicData ),
+ XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/chart" );
+ OUString sId;
+ const char* sFullPath = nullptr;
+ const char* sRelativePath = nullptr;
+ switch( GetDocumentType() )
+ {
+ case DOCUMENT_DOCX:
+ {
+ sFullPath = "word/charts/chart";
+ sRelativePath = "charts/chart";
+ break;
+ }
+ case DOCUMENT_PPTX:
+ {
+ sFullPath = "ppt/charts/chart";
+ sRelativePath = "../charts/chart";
+ break;
+ }
+ case DOCUMENT_XLSX:
+ {
+ sFullPath = "xl/charts/chart";
+ sRelativePath = "../charts/chart";
+ break;
+ }
+ default:
+ {
+ sFullPath = "charts/chart";
+ sRelativePath = "charts/chart";
+ break;
+ }
+ }
+ OUString sFullStream = OUStringBuffer()
+ .appendAscii(sFullPath)
+ .append(OUString::number(nChartCount) + ".xml")
+ .makeStringAndClear();
+ OUString sRelativeStream = OUStringBuffer()
+ .appendAscii(sRelativePath)
+ .append(OUString::number(nChartCount) + ".xml" )
+ .makeStringAndClear();
+ FSHelperPtr pChart = CreateOutputStream(
+ sFullStream,
+ sRelativeStream,
+ pFS->getOutputStream(),
+ "application/vnd.openxmlformats-officedocument.drawingml.chart+xml",
+ OUStringToOString(oox::getRelationship(Relationship::CHART), RTL_TEXTENCODING_UTF8).getStr(),
+ &sId );
+
+ XmlFilterBase* pFB = GetFB();
+ pFS->singleElement( FSNS( XML_c, XML_chart ),
+ FSNS(XML_xmlns, XML_c), pFB->getNamespaceURL(OOX_NS(dmlChart)),
+ FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel)),
+ FSNS(XML_r, XML_id), sId );
+
+ pFS->endElement( FSNS( XML_a, XML_graphicData ) );
+ pFS->endElement( FSNS( XML_a, XML_graphic ) );
+ pFS->endElementNS( mnXmlNamespace, XML_graphicFrame );
+
+ SetFS( pChart );
+ ExportContent();
+}
+
+void ChartExport::InitRangeSegmentationProperties( const Reference< chart2::XChartDocument > & xChartDoc )
+{
+ if( !xChartDoc.is())
+ return;
+
+ try
+ {
+ Reference< chart2::data::XDataProvider > xDataProvider( xChartDoc->getDataProvider() );
+ OSL_ENSURE( xDataProvider.is(), "No DataProvider" );
+ if( xDataProvider.is())
+ {
+ mbHasCategoryLabels = lcl_hasCategoryLabels( xChartDoc );
+ }
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION("oox");
+ }
+}
+
+void ChartExport::ExportContent()
+{
+ Reference< chart2::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY );
+ OSL_ASSERT( xChartDoc.is() );
+ if( !xChartDoc.is() )
+ return;
+ InitRangeSegmentationProperties( xChartDoc );
+ // TODO: export chart
+ ExportContent_( );
+}
+
+void ChartExport::ExportContent_()
+{
+ Reference< css::chart::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY );
+ if( xChartDoc.is())
+ {
+ // determine if data comes from the outside
+ bool bIncludeTable = true;
+
+ Reference< chart2::XChartDocument > xNewDoc( xChartDoc, uno::UNO_QUERY );
+ if( xNewDoc.is())
+ {
+ // check if we have own data. If so we must not export the complete
+ // range string, as this is our only indicator for having own or
+ // external data. @todo: fix this in the file format!
+ Reference< lang::XServiceInfo > xDPServiceInfo( xNewDoc->getDataProvider(), uno::UNO_QUERY );
+ if( ! (xDPServiceInfo.is() && xDPServiceInfo->getImplementationName() == "com.sun.star.comp.chart.InternalDataProvider" ))
+ {
+ bIncludeTable = false;
+ }
+ }
+ exportChartSpace( xChartDoc, bIncludeTable );
+ }
+ else
+ {
+ OSL_FAIL( "Couldn't export chart due to wrong XModel" );
+ }
+}
+
+void ChartExport::exportChartSpace( const Reference< css::chart::XChartDocument >& xChartDoc,
+ bool bIncludeTable )
+{
+ FSHelperPtr pFS = GetFS();
+ XmlFilterBase* pFB = GetFB();
+ pFS->startElement( FSNS( XML_c, XML_chartSpace ),
+ FSNS( XML_xmlns, XML_c ), pFB->getNamespaceURL(OOX_NS(dmlChart)),
+ FSNS( XML_xmlns, XML_a ), pFB->getNamespaceURL(OOX_NS(dml)),
+ FSNS( XML_xmlns, XML_r ), pFB->getNamespaceURL(OOX_NS(officeRel)));
+ // TODO: get the correct editing language
+ pFS->singleElement(FSNS(XML_c, XML_lang), XML_val, "en-US");
+
+ pFS->singleElement(FSNS(XML_c, XML_roundedCorners), XML_val, "0");
+
+ if( !bIncludeTable )
+ {
+ // TODO:external data
+ }
+ //XML_chart
+ exportChart(xChartDoc);
+
+ // TODO: printSettings
+ // TODO: style
+ // TODO: text properties
+ // TODO: shape properties
+ Reference< XPropertySet > xPropSet = xChartDoc->getArea();
+ if( xPropSet.is() )
+ exportShapeProps( xPropSet );
+
+ //XML_externalData
+ exportExternalData(xChartDoc);
+
+ // export additional shapes in chart
+ exportAdditionalShapes(xChartDoc);
+
+ pFS->endElement( FSNS( XML_c, XML_chartSpace ) );
+}
+
+void ChartExport::exportExternalData( const Reference< css::chart::XChartDocument >& xChartDoc )
+{
+ // Embedded external data is grab bagged for docx file hence adding export part of
+ // external data for docx files only.
+ if(GetDocumentType() != DOCUMENT_DOCX)
+ return;
+
+ OUString externalDataPath;
+ Reference< beans::XPropertySet > xDocPropSet( xChartDoc->getDiagram(), uno::UNO_QUERY );
+ if( xDocPropSet.is())
+ {
+ try
+ {
+ Any aAny( xDocPropSet->getPropertyValue( "ExternalData" ));
+ aAny >>= externalDataPath;
+ }
+ catch( beans::UnknownPropertyException & )
+ {
+ SAL_WARN("oox", "Required property not found in ChartDocument");
+ }
+ }
+ if(externalDataPath.isEmpty())
+ return;
+
+ // Here adding external data entry to relationship.
+ OUString relationPath = externalDataPath;
+ // Converting absolute path to relative path.
+ if( externalDataPath[ 0 ] != '.' && externalDataPath[ 1 ] != '.')
+ {
+ sal_Int32 nSepPos = externalDataPath.indexOf( '/', 0 );
+ if( nSepPos > 0)
+ {
+ relationPath = relationPath.copy( nSepPos, ::std::max< sal_Int32 >( externalDataPath.getLength(), 0 ) - nSepPos );
+ relationPath = ".." + relationPath;
+ }
+ }
+ FSHelperPtr pFS = GetFS();
+ OUString type = oox::getRelationship(Relationship::PACKAGE);
+ if (relationPath.endsWith(".bin"))
+ type = oox::getRelationship(Relationship::OLEOBJECT);
+
+ OUString sRelId = GetFB()->addRelation(pFS->getOutputStream(),
+ type,
+ relationPath);
+ pFS->singleElementNS(XML_c, XML_externalData, FSNS(XML_r, XML_id), sRelId);
+}
+
+void ChartExport::exportAdditionalShapes( const Reference< css::chart::XChartDocument >& xChartDoc )
+{
+ Reference< beans::XPropertySet > xDocPropSet(xChartDoc, uno::UNO_QUERY);
+ if (!xDocPropSet.is())
+ return;
+
+ css::uno::Reference< css::drawing::XShapes > mxAdditionalShapes;
+ // get a sequence of non-chart shapes
+ try
+ {
+ Any aShapesAny = xDocPropSet->getPropertyValue("AdditionalShapes");
+ if( (aShapesAny >>= mxAdditionalShapes) && mxAdditionalShapes.is() )
+ {
+ OUString sId;
+ const char* sFullPath = nullptr;
+ const char* sRelativePath = nullptr;
+ sal_Int32 nDrawing = getNewDrawingUniqueId();
+
+ switch (GetDocumentType())
+ {
+ case DOCUMENT_DOCX:
+ {
+ sFullPath = "word/drawings/drawing";
+ sRelativePath = "../drawings/drawing";
+ break;
+ }
+ case DOCUMENT_PPTX:
+ {
+ sFullPath = "ppt/drawings/drawing";
+ sRelativePath = "../drawings/drawing";
+ break;
+ }
+ case DOCUMENT_XLSX:
+ {
+ sFullPath = "xl/drawings/drawing";
+ sRelativePath = "../drawings/drawing";
+ break;
+ }
+ default:
+ {
+ sFullPath = "drawings/drawing";
+ sRelativePath = "drawings/drawing";
+ break;
+ }
+ }
+ OUString sFullStream = OUStringBuffer()
+ .appendAscii(sFullPath)
+ .append(OUString::number(nDrawing) + ".xml")
+ .makeStringAndClear();
+ OUString sRelativeStream = OUStringBuffer()
+ .appendAscii(sRelativePath)
+ .append(OUString::number(nDrawing) + ".xml")
+ .makeStringAndClear();
+
+ sax_fastparser::FSHelperPtr pDrawing = CreateOutputStream(
+ sFullStream,
+ sRelativeStream,
+ GetFS()->getOutputStream(),
+ "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml",
+ OUStringToOString(oox::getRelationship(Relationship::CHARTUSERSHAPES), RTL_TEXTENCODING_UTF8).getStr(),
+ &sId);
+
+ GetFS()->singleElementNS(XML_c, XML_userShapes, FSNS(XML_r, XML_id), sId);
+
+ XmlFilterBase* pFB = GetFB();
+ pDrawing->startElement(FSNS(XML_c, XML_userShapes),
+ FSNS(XML_xmlns, XML_cdr), pFB->getNamespaceURL(OOX_NS(dmlChartDr)),
+ FSNS(XML_xmlns, XML_a), pFB->getNamespaceURL(OOX_NS(dml)),
+ FSNS(XML_xmlns, XML_c), pFB->getNamespaceURL(OOX_NS(dmlChart)),
+ FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel)));
+
+ const sal_Int32 nShapeCount(mxAdditionalShapes->getCount());
+ for (sal_Int32 nShapeId = 0; nShapeId < nShapeCount; nShapeId++)
+ {
+ Reference< drawing::XShape > xShape;
+ mxAdditionalShapes->getByIndex(nShapeId) >>= xShape;
+ SAL_WARN_IF(!xShape.is(), "xmloff.chart", "Shape without an XShape?");
+ if (!xShape.is())
+ continue;
+
+ // TODO: absSizeAnchor: we import both (absSizeAnchor and relSizeAnchor), but there is no essential difference between them.
+ pDrawing->startElement(FSNS(XML_cdr, XML_relSizeAnchor));
+ uno::Reference< beans::XPropertySet > xShapeProperties(xShape, uno::UNO_QUERY);
+ if( xShapeProperties.is() )
+ {
+ Reference<embed::XVisualObject> xVisObject(mxChartModel, uno::UNO_QUERY);
+ awt::Size aPageSize = xVisObject->getVisualAreaSize(embed::Aspects::MSOLE_CONTENT);
+ WriteFromTo( xShape, aPageSize, pDrawing );
+
+ ShapeExport aExport(XML_cdr, pDrawing, nullptr, GetFB(), GetDocumentType(), nullptr, true);
+ aExport.WriteShape(xShape);
+ }
+ pDrawing->endElement(FSNS(XML_cdr, XML_relSizeAnchor));
+ }
+ pDrawing->endElement(FSNS(XML_c, XML_userShapes));
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_INFO_EXCEPTION("xmloff.chart", "AdditionalShapes not found");
+ }
+}
+
+void ChartExport::exportChart( const Reference< css::chart::XChartDocument >& xChartDoc )
+{
+ Reference< chart2::XChartDocument > xNewDoc( xChartDoc, uno::UNO_QUERY );
+ mxDiagram.set( xChartDoc->getDiagram() );
+ if( xNewDoc.is())
+ mxNewDiagram.set( xNewDoc->getFirstDiagram());
+
+ // get Properties of ChartDocument
+ bool bHasMainTitle = false;
+ OUString aSubTitle;
+ bool bHasLegend = false;
+ Reference< beans::XPropertySet > xDocPropSet( xChartDoc, uno::UNO_QUERY );
+ if( xDocPropSet.is())
+ {
+ try
+ {
+ Any aAny( xDocPropSet->getPropertyValue("HasMainTitle"));
+ aAny >>= bHasMainTitle;
+ aAny = xDocPropSet->getPropertyValue("HasLegend");
+ aAny >>= bHasLegend;
+ }
+ catch( beans::UnknownPropertyException & )
+ {
+ SAL_WARN("oox", "Required property not found in ChartDocument");
+ }
+ } // if( xDocPropSet.is())
+
+ Reference< beans::XPropertySet > xPropSubTitle( xChartDoc->getSubTitle(), UNO_QUERY );
+ if( xPropSubTitle.is())
+ {
+ try
+ {
+ xPropSubTitle->getPropertyValue("String") >>= aSubTitle;
+ }
+ catch( beans::UnknownPropertyException & )
+ {
+ }
+ }
+
+ // chart element
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_chart));
+
+ // titles
+ if( bHasMainTitle )
+ {
+ exportTitle( xChartDoc->getTitle(), !aSubTitle.isEmpty() ? &aSubTitle : nullptr );
+ pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "0");
+ }
+ else if( !aSubTitle.isEmpty() )
+ {
+ exportTitle( xChartDoc->getSubTitle(), nullptr );
+ pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "0");
+ }
+ else
+ {
+ pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "1");
+ }
+
+ InitPlotArea( );
+ if( mbIs3DChart )
+ {
+ exportView3D();
+
+ // floor
+ Reference< beans::XPropertySet > xFloor = mxNewDiagram->getFloor();
+ if( xFloor.is() )
+ {
+ pFS->startElement(FSNS(XML_c, XML_floor));
+ exportShapeProps( xFloor );
+ pFS->endElement( FSNS( XML_c, XML_floor ) );
+ }
+
+ // LibreOffice doesn't distinguish between sideWall and backWall (both are using the same color).
+ // It is controlled by the same Wall property.
+ Reference< beans::XPropertySet > xWall = mxNewDiagram->getWall();
+ if( xWall.is() )
+ {
+ // sideWall
+ pFS->startElement(FSNS(XML_c, XML_sideWall));
+ exportShapeProps( xWall );
+ pFS->endElement( FSNS( XML_c, XML_sideWall ) );
+
+ // backWall
+ pFS->startElement(FSNS(XML_c, XML_backWall));
+ exportShapeProps( xWall );
+ pFS->endElement( FSNS( XML_c, XML_backWall ) );
+ }
+
+ }
+ // plot area
+ exportPlotArea( xChartDoc );
+ // legend
+ if( bHasLegend )
+ exportLegend( xChartDoc );
+
+ uno::Reference<beans::XPropertySet> xDiagramPropSet(xChartDoc->getDiagram(), uno::UNO_QUERY);
+ uno::Any aPlotVisOnly = xDiagramPropSet->getPropertyValue("IncludeHiddenCells");
+ bool bIncludeHiddenCells = false;
+ aPlotVisOnly >>= bIncludeHiddenCells;
+ pFS->singleElement(FSNS(XML_c, XML_plotVisOnly), XML_val, ToPsz10(!bIncludeHiddenCells));
+
+ exportMissingValueTreatment(Reference<beans::XPropertySet>(mxDiagram, uno::UNO_QUERY));
+
+ pFS->endElement( FSNS( XML_c, XML_chart ) );
+}
+
+void ChartExport::exportMissingValueTreatment(const uno::Reference<beans::XPropertySet>& xPropSet)
+{
+ if (!xPropSet.is())
+ return;
+
+ sal_Int32 nVal = 0;
+ uno::Any aAny = xPropSet->getPropertyValue("MissingValueTreatment");
+ if (!(aAny >>= nVal))
+ return;
+
+ const char* pVal = nullptr;
+ switch (nVal)
+ {
+ case cssc::MissingValueTreatment::LEAVE_GAP:
+ pVal = "gap";
+ break;
+ case cssc::MissingValueTreatment::USE_ZERO:
+ pVal = "zero";
+ break;
+ case cssc::MissingValueTreatment::CONTINUE:
+ pVal = "span";
+ break;
+ default:
+ SAL_WARN("oox", "unknown MissingValueTreatment value");
+ break;
+ }
+
+ FSHelperPtr pFS = GetFS();
+ pFS->singleElement(FSNS(XML_c, XML_dispBlanksAs), XML_val, pVal);
+}
+
+void ChartExport::exportLegend( const Reference< css::chart::XChartDocument >& xChartDoc )
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_legend));
+
+ Reference< beans::XPropertySet > xProp( xChartDoc->getLegend(), uno::UNO_QUERY );
+ if( xProp.is() )
+ {
+ // position
+ css::chart::ChartLegendPosition aLegendPos = css::chart::ChartLegendPosition_NONE;
+ try
+ {
+ Any aAny( xProp->getPropertyValue( "Alignment" ));
+ aAny >>= aLegendPos;
+ }
+ catch( beans::UnknownPropertyException & )
+ {
+ SAL_WARN("oox", "Property Align not found in ChartLegend");
+ }
+
+ const char* strPos = nullptr;
+ switch( aLegendPos )
+ {
+ case css::chart::ChartLegendPosition_LEFT:
+ strPos = "l";
+ break;
+ case css::chart::ChartLegendPosition_RIGHT:
+ strPos = "r";
+ break;
+ case css::chart::ChartLegendPosition_TOP:
+ strPos = "t";
+ break;
+ case css::chart::ChartLegendPosition_BOTTOM:
+ strPos = "b";
+ break;
+ case css::chart::ChartLegendPosition_NONE:
+ case css::chart::ChartLegendPosition::ChartLegendPosition_MAKE_FIXED_SIZE:
+ // nothing
+ break;
+ }
+
+ if( strPos != nullptr )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_legendPos), XML_val, strPos);
+ }
+
+ // legendEntry
+ Reference<chart2::XCoordinateSystemContainer> xCooSysContainer(mxNewDiagram, UNO_QUERY_THROW);
+ const Sequence<Reference<chart2::XCoordinateSystem>> xCooSysSequence(xCooSysContainer->getCoordinateSystems());
+
+ sal_Int32 nIndex = 0;
+ bool bShowLegendEntry;
+ for (const auto& rCooSys : xCooSysSequence)
+ {
+ PropertySet aCooSysProp(rCooSys);
+ bool bSwapXAndY = aCooSysProp.getBoolProperty(PROP_SwapXAndYAxis);
+
+ Reference<chart2::XChartTypeContainer> xChartTypeContainer(rCooSys, UNO_QUERY_THROW);
+ const Sequence<Reference<chart2::XChartType>> xChartTypeSequence(xChartTypeContainer->getChartTypes());
+ if (!xChartTypeSequence.hasElements())
+ continue;
+
+ for (const auto& rCT : xChartTypeSequence)
+ {
+ Reference<chart2::XDataSeriesContainer> xDSCont(rCT, UNO_QUERY);
+ if (!xDSCont.is())
+ continue;
+
+ OUString aChartType(rCT->getChartType());
+ bool bIsPie = lcl_getChartType(aChartType) == chart::TYPEID_PIE;
+ if (bIsPie)
+ {
+ PropertySet xChartTypeProp(rCT);
+ bIsPie = !xChartTypeProp.getBoolProperty(PROP_UseRings);
+ }
+ const Sequence<Reference<chart2::XDataSeries>> aDataSeriesSeq = xDSCont->getDataSeries();
+ if (bSwapXAndY)
+ nIndex += aDataSeriesSeq.getLength() - 1;
+ for (const auto& rDataSeries : aDataSeriesSeq)
+ {
+ PropertySet aSeriesProp(rDataSeries);
+ bool bVaryColorsByPoint = aSeriesProp.getBoolProperty(PROP_VaryColorsByPoint);
+ if (bVaryColorsByPoint || bIsPie)
+ {
+ Sequence<sal_Int32> deletedLegendEntriesSeq;
+ aSeriesProp.getProperty(deletedLegendEntriesSeq, PROP_DeletedLegendEntries);
+ for (const auto& deletedLegendEntry : std::as_const(deletedLegendEntriesSeq))
+ {
+ pFS->startElement(FSNS(XML_c, XML_legendEntry));
+ pFS->singleElement(FSNS(XML_c, XML_idx), XML_val,
+ OString::number(nIndex + deletedLegendEntry));
+ pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, "1");
+ pFS->endElement(FSNS(XML_c, XML_legendEntry));
+ }
+ Reference<chart2::data::XDataSource> xDSrc(rDataSeries, UNO_QUERY);
+ if (!xDSrc.is())
+ continue;
+
+ const Sequence<Reference<chart2::data::XLabeledDataSequence>> aDataSeqs = xDSrc->getDataSequences();
+ for (const auto& rDataSeq : aDataSeqs)
+ {
+ Reference<chart2::data::XDataSequence> xValues = rDataSeq->getValues();
+ if (!xValues.is())
+ continue;
+
+ sal_Int32 nDataSeqSize = xValues->getData().getLength();
+ nIndex += nDataSeqSize;
+ }
+ }
+ else
+ {
+ bShowLegendEntry = aSeriesProp.getBoolProperty(PROP_ShowLegendEntry);
+ if (!bShowLegendEntry)
+ {
+ pFS->startElement(FSNS(XML_c, XML_legendEntry));
+ pFS->singleElement(FSNS(XML_c, XML_idx), XML_val,
+ OString::number(nIndex));
+ pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, "1");
+ pFS->endElement(FSNS(XML_c, XML_legendEntry));
+ }
+ bSwapXAndY ? nIndex-- : nIndex++;
+ }
+ }
+ if (bSwapXAndY)
+ nIndex += aDataSeriesSeq.getLength() + 1;
+ }
+ }
+
+ uno::Any aRelativePos = xProp->getPropertyValue("RelativePosition");
+ if (aRelativePos.hasValue())
+ {
+ pFS->startElement(FSNS(XML_c, XML_layout));
+ pFS->startElement(FSNS(XML_c, XML_manualLayout));
+
+ pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge");
+ pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge");
+ chart2::RelativePosition aPos = aRelativePos.get<chart2::RelativePosition>();
+
+ const double x = aPos.Primary;
+ const double y = aPos.Secondary;
+
+ pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x));
+ pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y));
+
+ uno::Any aRelativeSize = xProp->getPropertyValue("RelativeSize");
+ if (aRelativeSize.hasValue())
+ {
+ chart2::RelativeSize aSize = aRelativeSize.get<chart2::RelativeSize>();
+
+ const double w = aSize.Primary;
+ const double h = aSize.Secondary;
+
+ pFS->singleElement(FSNS(XML_c, XML_w), XML_val, OString::number(w));
+
+ pFS->singleElement(FSNS(XML_c, XML_h), XML_val, OString::number(h));
+ }
+
+ SAL_WARN_IF(aPos.Anchor != css::drawing::Alignment_TOP_LEFT, "oox", "unsupported anchor position");
+
+ pFS->endElement(FSNS(XML_c, XML_manualLayout));
+ pFS->endElement(FSNS(XML_c, XML_layout));
+ }
+
+ if (strPos != nullptr)
+ {
+ uno::Any aOverlay = xProp->getPropertyValue("Overlay");
+ if(aOverlay.get<bool>())
+ pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, "1");
+ else
+ pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, "0");
+ }
+
+ // shape properties
+ exportShapeProps( xProp );
+
+ // draw-chart:txPr text properties
+ exportTextProps( xProp );
+ }
+
+ pFS->endElement( FSNS( XML_c, XML_legend ) );
+}
+
+void ChartExport::exportTitle( const Reference< XShape >& xShape, const OUString* pSubText)
+{
+ OUString sText;
+ Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY );
+ if( xPropSet.is())
+ {
+ xPropSet->getPropertyValue("String") >>= sText;
+ }
+
+ // tdf#101322: add subtitle to title
+ if( pSubText )
+ sText = sText.isEmpty() ? *pSubText : sText + "\n" + *pSubText;
+
+ if( sText.isEmpty() )
+ return;
+
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_title));
+
+ pFS->startElement(FSNS(XML_c, XML_tx));
+ pFS->startElement(FSNS(XML_c, XML_rich));
+
+ // TODO: bodyPr
+ const char* sWritingMode = nullptr;
+ bool bVertical = false;
+ xPropSet->getPropertyValue("StackedText") >>= bVertical;
+ if( bVertical )
+ sWritingMode = "wordArtVert";
+
+ sal_Int32 nRotation = 0;
+ xPropSet->getPropertyValue("TextRotation") >>= nRotation;
+
+ pFS->singleElement( FSNS( XML_a, XML_bodyPr ),
+ XML_vert, sWritingMode,
+ XML_rot, oox::drawingml::calcRotationValue(nRotation) );
+ // TODO: lstStyle
+ pFS->singleElement(FSNS(XML_a, XML_lstStyle));
+ // FIXME: handle multiple paragraphs to parse aText
+ pFS->startElement(FSNS(XML_a, XML_p));
+
+ pFS->startElement(FSNS(XML_a, XML_pPr));
+
+ bool bDummy = false;
+ sal_Int32 nDummy;
+ WriteRunProperties(xPropSet, false, XML_defRPr, true, bDummy, nDummy );
+
+ pFS->endElement( FSNS( XML_a, XML_pPr ) );
+
+ pFS->startElement(FSNS(XML_a, XML_r));
+ bDummy = false;
+ WriteRunProperties( xPropSet, false, XML_rPr, true, bDummy, nDummy );
+ pFS->startElement(FSNS(XML_a, XML_t));
+ pFS->writeEscaped( sText );
+ pFS->endElement( FSNS( XML_a, XML_t ) );
+ pFS->endElement( FSNS( XML_a, XML_r ) );
+
+ pFS->endElement( FSNS( XML_a, XML_p ) );
+
+ pFS->endElement( FSNS( XML_c, XML_rich ) );
+ pFS->endElement( FSNS( XML_c, XML_tx ) );
+
+ uno::Any aManualLayout = xPropSet->getPropertyValue("RelativePosition");
+ if (aManualLayout.hasValue())
+ {
+ pFS->startElement(FSNS(XML_c, XML_layout));
+ pFS->startElement(FSNS(XML_c, XML_manualLayout));
+ pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge");
+ pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge");
+
+ Reference<embed::XVisualObject> xVisObject(mxChartModel, uno::UNO_QUERY);
+ awt::Size aPageSize = xVisObject->getVisualAreaSize(embed::Aspects::MSOLE_CONTENT);
+
+ awt::Size aSize = xShape->getSize();
+ awt::Point aPos2 = xShape->getPosition();
+ // rotated shapes need special handling...
+ double fSin = fabs(sin(basegfx::deg2rad<100>(nRotation)));
+ // remove part of height from X direction, if title is rotated down
+ if( nRotation*0.01 > 180.0 )
+ aPos2.X -= static_cast<sal_Int32>(fSin * aSize.Height + 0.5);
+ // remove part of width from Y direction, if title is rotated up
+ else if( nRotation*0.01 > 0.0 )
+ aPos2.Y -= static_cast<sal_Int32>(fSin * aSize.Width + 0.5);
+
+ double x = static_cast<double>(aPos2.X) / static_cast<double>(aPageSize.Width);
+ double y = static_cast<double>(aPos2.Y) / static_cast<double>(aPageSize.Height);
+ /*
+ pFS->singleElement(FSNS(XML_c, XML_wMode), XML_val, "edge");
+ pFS->singleElement(FSNS(XML_c, XML_hMode), XML_val, "edge");
+ */
+ pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x));
+ pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y));
+ /*
+ pFS->singleElement(FSNS(XML_c, XML_w), XML_val, "");
+ pFS->singleElement(FSNS(XML_c, XML_h), XML_val, "");
+ */
+ pFS->endElement(FSNS(XML_c, XML_manualLayout));
+ pFS->endElement(FSNS(XML_c, XML_layout));
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, "0");
+
+ // shape properties
+ if( xPropSet.is() )
+ {
+ exportShapeProps( xPropSet );
+ }
+
+ pFS->endElement( FSNS( XML_c, XML_title ) );
+}
+
+namespace {
+
+ std::vector<Sequence<Reference<chart2::XDataSeries> > > splitDataSeriesByAxis(const Reference< chart2::XChartType >& xChartType)
+ {
+ std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitSeries;
+ std::map<sal_Int32, size_t> aMapAxisToIndex;
+
+ Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY);
+ if (xDSCnt.is())
+ {
+ sal_Int32 nAxisIndexOfFirstSeries = -1;
+ const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries());
+ for (const uno::Reference<chart2::XDataSeries>& xSeries : aSeriesSeq)
+ {
+ Reference<beans::XPropertySet> xPropSet(xSeries, uno::UNO_QUERY);
+ if (!xPropSet.is())
+ continue;
+
+ sal_Int32 nAxisIndex = -1;
+ uno::Any aAny = xPropSet->getPropertyValue("AttachedAxisIndex");
+ aAny >>= nAxisIndex;
+ size_t nVectorPos = 0;
+ if (nAxisIndexOfFirstSeries == -1)
+ {
+ nAxisIndexOfFirstSeries = nAxisIndex;
+ }
+
+ auto it = aMapAxisToIndex.find(nAxisIndex);
+ if (it == aMapAxisToIndex.end())
+ {
+ aSplitSeries.emplace_back();
+ nVectorPos = aSplitSeries.size() - 1;
+ aMapAxisToIndex.insert(std::pair<sal_Int32, size_t>(nAxisIndex, nVectorPos));
+ }
+ else
+ {
+ nVectorPos = it->second;
+ }
+
+ uno::Sequence<Reference<chart2::XDataSeries> >& rAxisSeriesSeq = aSplitSeries[nVectorPos];
+ sal_Int32 nLength = rAxisSeriesSeq.getLength();
+ rAxisSeriesSeq.realloc(nLength + 1);
+ rAxisSeriesSeq.getArray()[nLength] = xSeries;
+ }
+ // if the first series attached to secondary axis, then export those series first, which are attached to primary axis
+ // also the MS Office export every time in this order
+ if (aSplitSeries.size() > 1 && nAxisIndexOfFirstSeries == 1)
+ {
+ std::swap(aSplitSeries[0], aSplitSeries[1]);
+ }
+ }
+
+ return aSplitSeries;
+ }
+
+}
+
+void ChartExport::exportPlotArea(const Reference< css::chart::XChartDocument >& xChartDoc)
+{
+ Reference< chart2::XCoordinateSystemContainer > xBCooSysCnt( mxNewDiagram, uno::UNO_QUERY );
+ if( ! xBCooSysCnt.is())
+ return;
+
+ // plot-area element
+
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_plotArea));
+
+ Reference<beans::XPropertySet> xWall(mxNewDiagram, uno::UNO_QUERY);
+ if( xWall.is() )
+ {
+ uno::Any aAny = xWall->getPropertyValue("RelativePosition");
+ if (aAny.hasValue())
+ {
+ chart2::RelativePosition aPos = aAny.get<chart2::RelativePosition>();
+ aAny = xWall->getPropertyValue("RelativeSize");
+ chart2::RelativeSize aSize = aAny.get<chart2::RelativeSize>();
+ uno::Reference< css::chart::XDiagramPositioning > xDiagramPositioning( xChartDoc->getDiagram(), uno::UNO_QUERY );
+ exportManualLayout(aPos, aSize, xDiagramPositioning->isExcludingDiagramPositioning() );
+ }
+ }
+
+ // chart type
+ const Sequence< Reference< chart2::XCoordinateSystem > >
+ aCooSysSeq( xBCooSysCnt->getCoordinateSystems());
+
+ // tdf#123647 Save empty chart as empty bar chart.
+ if (!aCooSysSeq.hasElements())
+ {
+ pFS->startElement(FSNS(XML_c, XML_barChart));
+ pFS->singleElement(FSNS(XML_c, XML_barDir), XML_val, "col");
+ pFS->singleElement(FSNS(XML_c, XML_grouping), XML_val, "clustered");
+ pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, "0");
+ exportAxesId(true);
+ pFS->endElement(FSNS(XML_c, XML_barChart));
+ }
+
+ for( const auto& rCS : aCooSysSeq )
+ {
+ Reference< chart2::XChartTypeContainer > xCTCnt( rCS, uno::UNO_QUERY );
+ if( ! xCTCnt.is())
+ continue;
+ mnSeriesCount=0;
+ const Sequence< Reference< chart2::XChartType > > aCTSeq( xCTCnt->getChartTypes());
+ for( const auto& rCT : aCTSeq )
+ {
+ Reference< chart2::XDataSeriesContainer > xDSCnt( rCT, uno::UNO_QUERY );
+ if( ! xDSCnt.is())
+ return;
+ Reference< chart2::XChartType > xChartType( rCT, uno::UNO_QUERY );
+ if( ! xChartType.is())
+ continue;
+ // note: if xDSCnt.is() then also aCTSeq[nCTIdx]
+ OUString aChartType( xChartType->getChartType());
+ sal_Int32 eChartType = lcl_getChartType( aChartType );
+ switch( eChartType )
+ {
+ case chart::TYPEID_BAR:
+ {
+ exportBarChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_AREA:
+ {
+ exportAreaChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_LINE:
+ {
+ exportLineChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_BUBBLE:
+ {
+ exportBubbleChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_OFPIE:
+ {
+ break;
+ }
+ case chart::TYPEID_DOUGHNUT:
+ case chart::TYPEID_PIE:
+ {
+ exportPieChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_RADARLINE:
+ case chart::TYPEID_RADARAREA:
+ {
+ exportRadarChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_SCATTER:
+ {
+ exportScatterChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_STOCK:
+ {
+ exportStockChart( xChartType );
+ break;
+ }
+ case chart::TYPEID_SURFACE:
+ {
+ exportSurfaceChart( xChartType );
+ break;
+ }
+ default:
+ {
+ SAL_WARN("oox", "ChartExport::exportPlotArea -- not support chart type");
+ break;
+ }
+ }
+
+ }
+ }
+ //Axis Data
+ exportAxes( );
+ // Data Table
+ exportDataTable();
+
+ // shape properties
+ /*
+ * Export the Plot area Shape Properties
+ * eg: Fill and Outline
+ */
+ Reference< css::chart::X3DDisplay > xWallFloorSupplier( mxDiagram, uno::UNO_QUERY );
+ // tdf#114139 For 2D charts Plot Area equivalent is Chart Wall.
+ // Unfortunately LibreOffice doesn't have Plot Area equivalent for 3D charts.
+ // It means that Plot Area couldn't be displayed and changed for 3D chars in LibreOffice.
+ // We cannot write Wall attributes into Plot Area for 3D charts, because Wall us used as background wall.
+ if( !mbIs3DChart && xWallFloorSupplier.is() )
+ {
+ Reference< beans::XPropertySet > xWallPropSet = xWallFloorSupplier->getWall();
+ if( xWallPropSet.is() )
+ {
+ uno::Any aAny = xWallPropSet->getPropertyValue("LineStyle");
+ sal_Int32 eChartType = getChartType( );
+ // Export LineStyle_NONE instead of default linestyle of PlotArea border, because LibreOffice
+ // make invisible the Wall shape properties, in case of these charts. Or in the future set
+ // the default LineStyle of these charts to LineStyle_NONE.
+ bool noSupportWallProp = ( (eChartType == chart::TYPEID_PIE) || (eChartType == chart::TYPEID_RADARLINE) || (eChartType == chart::TYPEID_RADARAREA) );
+ if ( noSupportWallProp && (aAny != drawing::LineStyle_NONE) )
+ {
+ xWallPropSet->setPropertyValue( "LineStyle", uno::Any(drawing::LineStyle_NONE) );
+ }
+ exportShapeProps( xWallPropSet );
+ }
+ }
+
+ pFS->endElement( FSNS( XML_c, XML_plotArea ) );
+
+}
+
+void ChartExport::exportManualLayout(const css::chart2::RelativePosition& rPos,
+ const css::chart2::RelativeSize& rSize,
+ const bool bIsExcludingDiagramPositioning)
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_layout));
+ pFS->startElement(FSNS(XML_c, XML_manualLayout));
+
+ // By default layoutTarget is set to "outer" and we shouldn't save it in that case
+ if ( bIsExcludingDiagramPositioning )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_layoutTarget), XML_val, "inner");
+ }
+ pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge");
+ pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge");
+
+ double x = rPos.Primary;
+ double y = rPos.Secondary;
+ const double w = rSize.Primary;
+ const double h = rSize.Secondary;
+ switch (rPos.Anchor)
+ {
+ case drawing::Alignment_LEFT:
+ y -= (h/2);
+ break;
+ case drawing::Alignment_TOP_LEFT:
+ break;
+ case drawing::Alignment_BOTTOM_LEFT:
+ y -= h;
+ break;
+ case drawing::Alignment_TOP:
+ x -= (w/2);
+ break;
+ case drawing::Alignment_CENTER:
+ x -= (w/2);
+ y -= (h/2);
+ break;
+ case drawing::Alignment_BOTTOM:
+ x -= (w/2);
+ y -= h;
+ break;
+ case drawing::Alignment_TOP_RIGHT:
+ x -= w;
+ break;
+ case drawing::Alignment_BOTTOM_RIGHT:
+ x -= w;
+ y -= h;
+ break;
+ case drawing::Alignment_RIGHT:
+ y -= (h/2);
+ x -= w;
+ break;
+ default:
+ SAL_WARN("oox", "unhandled alignment case for manual layout export " << static_cast<sal_uInt16>(rPos.Anchor));
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x));
+
+ pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y));
+
+ pFS->singleElement(FSNS(XML_c, XML_w), XML_val, OString::number(w));
+
+ pFS->singleElement(FSNS(XML_c, XML_h), XML_val, OString::number(h));
+
+ pFS->endElement(FSNS(XML_c, XML_manualLayout));
+ pFS->endElement(FSNS(XML_c, XML_layout));
+}
+
+void ChartExport::exportFill( const Reference< XPropertySet >& xPropSet )
+{
+ // Similar to DrawingML::WriteFill, but gradient access via name
+ if (!GetProperty( xPropSet, "FillStyle" ))
+ return;
+ FillStyle aFillStyle(FillStyle_NONE);
+ mAny >>= aFillStyle;
+
+ // map full transparent background to no fill
+ if (aFillStyle == FillStyle_SOLID && GetProperty( xPropSet, "FillTransparence" ))
+ {
+ sal_Int16 nVal = 0;
+ mAny >>= nVal;
+ if ( nVal == 100 )
+ aFillStyle = FillStyle_NONE;
+ }
+ OUString sFillTransparenceGradientName;
+ if (aFillStyle == FillStyle_SOLID
+ && GetProperty(xPropSet, "FillTransparenceGradientName") && (mAny >>= sFillTransparenceGradientName)
+ && !sFillTransparenceGradientName.isEmpty())
+ {
+ awt::Gradient aTransparenceGradient;
+ uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
+ uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance("com.sun.star.drawing.TransparencyGradientTable"), uno::UNO_QUERY);
+ uno::Any rTransparenceValue = xTransparenceGradient->getByName(sFillTransparenceGradientName);
+ rTransparenceValue >>= aTransparenceGradient;
+ if (aTransparenceGradient.StartColor == 0xffffff && aTransparenceGradient.EndColor == 0xffffff)
+ aFillStyle = FillStyle_NONE;
+ }
+ switch( aFillStyle )
+ {
+ case FillStyle_SOLID:
+ exportSolidFill(xPropSet);
+ break;
+ case FillStyle_GRADIENT :
+ exportGradientFill( xPropSet );
+ break;
+ case FillStyle_BITMAP :
+ exportBitmapFill( xPropSet );
+ break;
+ case FillStyle_HATCH:
+ exportHatch(xPropSet);
+ break;
+ case FillStyle_NONE:
+ mpFS->singleElementNS(XML_a, XML_noFill);
+ break;
+ default:
+ ;
+ }
+}
+
+void ChartExport::exportSolidFill(const Reference< XPropertySet >& xPropSet)
+{
+ // Similar to DrawingML::WriteSolidFill, but gradient access via name
+ // and currently no InteropGrabBag
+ // get fill color
+ if (!GetProperty( xPropSet, "FillColor" ))
+ return;
+ sal_uInt32 nFillColor = mAny.get<sal_uInt32>();
+
+ sal_Int32 nAlpha = MAX_PERCENT;
+ if (GetProperty( xPropSet, "FillTransparence" ))
+ {
+ sal_Int32 nTransparency = 0;
+ mAny >>= nTransparency;
+ // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
+ nAlpha = (MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
+ }
+ // OOXML has no separate transparence gradient but uses transparency in the gradient stops.
+ // So we merge transparency and color and use gradient fill in such case.
+ awt::Gradient aTransparenceGradient;
+ bool bNeedGradientFill(false);
+ OUString sFillTransparenceGradientName;
+ if (GetProperty(xPropSet, "FillTransparenceGradientName")
+ && (mAny >>= sFillTransparenceGradientName)
+ && !sFillTransparenceGradientName.isEmpty())
+ {
+ uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
+ uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance("com.sun.star.drawing.TransparencyGradientTable"), uno::UNO_QUERY);
+ uno::Any rTransparenceValue = xTransparenceGradient->getByName(sFillTransparenceGradientName);
+ rTransparenceValue >>= aTransparenceGradient;
+ if (aTransparenceGradient.StartColor != aTransparenceGradient.EndColor)
+ bNeedGradientFill = true;
+ else if (aTransparenceGradient.StartColor != 0)
+ nAlpha = lcl_getAlphaFromTransparenceGradient(aTransparenceGradient, true);
+ }
+ // write XML
+ if (bNeedGradientFill)
+ {
+ awt::Gradient aPseudoColorGradient;
+ aPseudoColorGradient.XOffset = aTransparenceGradient.XOffset;
+ aPseudoColorGradient.YOffset = aTransparenceGradient.YOffset;
+ aPseudoColorGradient.StartIntensity = 100;
+ aPseudoColorGradient.EndIntensity = 100;
+ aPseudoColorGradient.Angle = aTransparenceGradient.Angle;
+ aPseudoColorGradient.Border = aTransparenceGradient.Border;
+ aPseudoColorGradient.Style = aTransparenceGradient.Style;
+ aPseudoColorGradient.StartColor = nFillColor;
+ aPseudoColorGradient.EndColor = nFillColor;
+ aPseudoColorGradient.StepCount = aTransparenceGradient.StepCount;
+ mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
+ WriteGradientFill(aPseudoColorGradient, aTransparenceGradient);
+ mpFS->endElementNS(XML_a, XML_gradFill);
+ }
+ else
+ WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha);
+}
+
+void ChartExport::exportHatch( const Reference< XPropertySet >& xPropSet )
+{
+ if (!xPropSet.is())
+ return;
+
+ if (GetProperty(xPropSet, "FillHatchName"))
+ {
+ OUString aHatchName;
+ mAny >>= aHatchName;
+ uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
+ uno::Reference< container::XNameAccess > xHatchTable( xFact->createInstance("com.sun.star.drawing.HatchTable"), uno::UNO_QUERY );
+ uno::Any rValue = xHatchTable->getByName(aHatchName);
+ css::drawing::Hatch aHatch;
+ rValue >>= aHatch;
+ WritePattFill(xPropSet, aHatch);
+ }
+
+}
+
+void ChartExport::exportBitmapFill( const Reference< XPropertySet >& xPropSet )
+{
+ if( !xPropSet.is() )
+ return;
+
+ OUString sFillBitmapName;
+ xPropSet->getPropertyValue("FillBitmapName") >>= sFillBitmapName;
+
+ uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
+ try
+ {
+ uno::Reference< container::XNameAccess > xBitmapTable( xFact->createInstance("com.sun.star.drawing.BitmapTable"), uno::UNO_QUERY );
+ uno::Any rValue = xBitmapTable->getByName( sFillBitmapName );
+ if (rValue.has<uno::Reference<awt::XBitmap>>())
+ {
+ uno::Reference<awt::XBitmap> xBitmap = rValue.get<uno::Reference<awt::XBitmap>>();
+ uno::Reference<graphic::XGraphic> xGraphic(xBitmap, uno::UNO_QUERY);
+ if (xGraphic.is())
+ {
+ WriteXGraphicBlipFill(xPropSet, xGraphic, XML_a, true, true);
+ }
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION("oox", "ChartExport::exportBitmapFill");
+ }
+}
+
+void ChartExport::exportGradientFill( const Reference< XPropertySet >& xPropSet )
+{
+ if( !xPropSet.is() )
+ return;
+
+ OUString sFillGradientName;
+ xPropSet->getPropertyValue("FillGradientName") >>= sFillGradientName;
+
+ uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY );
+ try
+ {
+ uno::Reference< container::XNameAccess > xGradient( xFact->createInstance("com.sun.star.drawing.GradientTable"), uno::UNO_QUERY );
+ uno::Any rGradientValue = xGradient->getByName( sFillGradientName );
+ awt::Gradient aGradient;
+ if( rGradientValue >>= aGradient )
+ {
+ awt::Gradient aTransparenceGradient;
+ mpFS->startElementNS(XML_a, XML_gradFill);
+ OUString sFillTransparenceGradientName;
+ if( (xPropSet->getPropertyValue("FillTransparenceGradientName") >>= sFillTransparenceGradientName) && !sFillTransparenceGradientName.isEmpty())
+ {
+ uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance("com.sun.star.drawing.TransparencyGradientTable"), uno::UNO_QUERY);
+ uno::Any rTransparenceValue = xTransparenceGradient->getByName(sFillTransparenceGradientName);
+ rTransparenceValue >>= aTransparenceGradient;
+ WriteGradientFill(aGradient, aTransparenceGradient);
+ }
+ else
+ {
+ WriteGradientFill(aGradient, aTransparenceGradient, xPropSet);
+ }
+ mpFS->endElementNS(XML_a, XML_gradFill);
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_INFO_EXCEPTION("oox", "ChartExport::exportGradientFill");
+ }
+}
+
+void ChartExport::exportDataTable( )
+{
+ FSHelperPtr pFS = GetFS();
+ Reference< beans::XPropertySet > aPropSet( mxDiagram, uno::UNO_QUERY );
+
+ bool bShowVBorder = false;
+ bool bShowHBorder = false;
+ bool bShowOutline = false;
+
+ if (GetProperty( aPropSet, "DataTableHBorder"))
+ mAny >>= bShowHBorder;
+ if (GetProperty( aPropSet, "DataTableVBorder"))
+ mAny >>= bShowVBorder;
+ if (GetProperty( aPropSet, "DataTableOutline"))
+ mAny >>= bShowOutline;
+
+ if (!(bShowVBorder || bShowHBorder || bShowOutline))
+ return;
+
+ pFS->startElement(FSNS(XML_c, XML_dTable));
+ if (bShowHBorder)
+ pFS->singleElement( FSNS( XML_c, XML_showHorzBorder ),
+ XML_val, "1" );
+ if (bShowVBorder)
+ pFS->singleElement(FSNS(XML_c, XML_showVertBorder), XML_val, "1");
+ if (bShowOutline)
+ pFS->singleElement(FSNS(XML_c, XML_showOutline), XML_val, "1");
+
+ pFS->endElement( FSNS( XML_c, XML_dTable));
+
+}
+void ChartExport::exportAreaChart( const Reference< chart2::XChartType >& xChartType )
+{
+ FSHelperPtr pFS = GetFS();
+ const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
+ for (const auto& splitDataSeries : aSplitDataSeries)
+ {
+ if (!splitDataSeries.hasElements())
+ continue;
+
+ sal_Int32 nTypeId = XML_areaChart;
+ if (mbIs3DChart)
+ nTypeId = XML_area3DChart;
+ pFS->startElement(FSNS(XML_c, nTypeId));
+
+ exportGrouping();
+ bool bPrimaryAxes = true;
+ exportSeries(xChartType, splitDataSeries, bPrimaryAxes);
+ exportAxesId(bPrimaryAxes);
+
+ pFS->endElement(FSNS(XML_c, nTypeId));
+ }
+}
+
+void ChartExport::exportBarChart(const Reference< chart2::XChartType >& xChartType)
+{
+ sal_Int32 nTypeId = XML_barChart;
+ if (mbIs3DChart)
+ nTypeId = XML_bar3DChart;
+ FSHelperPtr pFS = GetFS();
+
+ const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
+ for (const auto& splitDataSeries : aSplitDataSeries)
+ {
+ if (!splitDataSeries.hasElements())
+ continue;
+
+ pFS->startElement(FSNS(XML_c, nTypeId));
+ // bar direction
+ bool bVertical = false;
+ Reference< XPropertySet > xPropSet(mxDiagram, uno::UNO_QUERY);
+ if (GetProperty(xPropSet, "Vertical"))
+ mAny >>= bVertical;
+
+ const char* bardir = bVertical ? "bar" : "col";
+ pFS->singleElement(FSNS(XML_c, XML_barDir), XML_val, bardir);
+
+ exportGrouping(true);
+
+ exportVaryColors(xChartType);
+
+ bool bPrimaryAxes = true;
+ exportSeries(xChartType, splitDataSeries, bPrimaryAxes);
+
+ Reference< XPropertySet > xTypeProp(xChartType, uno::UNO_QUERY);
+
+ if (xTypeProp.is() && GetProperty(xTypeProp, "GapwidthSequence"))
+ {
+ uno::Sequence< sal_Int32 > aBarPositionSequence;
+ mAny >>= aBarPositionSequence;
+ if (aBarPositionSequence.hasElements())
+ {
+ sal_Int32 nGapWidth = aBarPositionSequence[0];
+ pFS->singleElement(FSNS(XML_c, XML_gapWidth), XML_val, OString::number(nGapWidth));
+ }
+ }
+
+ if (mbIs3DChart)
+ {
+ // Shape
+ namespace cssc = css::chart;
+ sal_Int32 nGeom3d = cssc::ChartSolidType::RECTANGULAR_SOLID;
+ if (xPropSet.is() && GetProperty(xPropSet, "SolidType"))
+ mAny >>= nGeom3d;
+ const char* sShapeType = nullptr;
+ switch (nGeom3d)
+ {
+ case cssc::ChartSolidType::RECTANGULAR_SOLID:
+ sShapeType = "box";
+ break;
+ case cssc::ChartSolidType::CONE:
+ sShapeType = "cone";
+ break;
+ case cssc::ChartSolidType::CYLINDER:
+ sShapeType = "cylinder";
+ break;
+ case cssc::ChartSolidType::PYRAMID:
+ sShapeType = "pyramid";
+ break;
+ }
+ pFS->singleElement(FSNS(XML_c, XML_shape), XML_val, sShapeType);
+ }
+
+ //overlap
+ if (!mbIs3DChart && xTypeProp.is() && GetProperty(xTypeProp, "OverlapSequence"))
+ {
+ uno::Sequence< sal_Int32 > aBarPositionSequence;
+ mAny >>= aBarPositionSequence;
+ if (aBarPositionSequence.hasElements())
+ {
+ sal_Int32 nOverlap = aBarPositionSequence[0];
+ // Stacked/Percent Bar/Column chart Overlap-workaround
+ // Export the Overlap value with 100% for stacked charts,
+ // because the default overlap value of the Bar/Column chart is 0% and
+ // LibreOffice do nothing with the overlap value in Stacked charts case,
+ // unlike the MS Office, which is interpreted differently.
+ if ((mbStacked || mbPercent) && nOverlap != 100)
+ {
+ nOverlap = 100;
+ pFS->singleElement(FSNS(XML_c, XML_overlap), XML_val, OString::number(nOverlap));
+ }
+ else // Normal bar chart
+ {
+ pFS->singleElement(FSNS(XML_c, XML_overlap), XML_val, OString::number(nOverlap));
+ }
+ }
+ }
+
+ exportAxesId(bPrimaryAxes);
+
+ pFS->endElement(FSNS(XML_c, nTypeId));
+ }
+}
+
+void ChartExport::exportBubbleChart( const Reference< chart2::XChartType >& xChartType )
+{
+ FSHelperPtr pFS = GetFS();
+ const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
+ for (const auto& splitDataSeries : aSplitDataSeries)
+ {
+ if (!splitDataSeries.hasElements())
+ continue;
+
+ pFS->startElement(FSNS(XML_c, XML_bubbleChart));
+
+ exportVaryColors(xChartType);
+
+ bool bPrimaryAxes = true;
+ exportSeries(xChartType, splitDataSeries, bPrimaryAxes);
+
+ exportAxesId(bPrimaryAxes);
+
+ pFS->endElement(FSNS(XML_c, XML_bubbleChart));
+ }
+}
+
+void ChartExport::exportDoughnutChart( const Reference< chart2::XChartType >& xChartType )
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_doughnutChart));
+
+ exportVaryColors(xChartType);
+
+ bool bPrimaryAxes = true;
+ exportAllSeries(xChartType, bPrimaryAxes);
+ // firstSliceAng
+ exportFirstSliceAng( );
+ //FIXME: holeSize
+ pFS->singleElement(FSNS(XML_c, XML_holeSize), XML_val, OString::number(50));
+
+ pFS->endElement( FSNS( XML_c, XML_doughnutChart ) );
+}
+
+namespace {
+
+void writeDataLabelsRange(const FSHelperPtr& pFS, const XmlFilterBase* pFB, DataLabelsRange& rDLblsRange)
+{
+ if (rDLblsRange.empty())
+ return;
+
+ pFS->startElement(FSNS(XML_c, XML_extLst));
+ pFS->startElement(FSNS(XML_c, XML_ext), XML_uri, "{02D57815-91ED-43cb-92C2-25804820EDAC}", FSNS(XML_xmlns, XML_c15), pFB->getNamespaceURL(OOX_NS(c15)));
+ pFS->startElement(FSNS(XML_c15, XML_datalabelsRange));
+
+ // Write cell range.
+ pFS->startElement(FSNS(XML_c15, XML_f));
+ pFS->writeEscaped(rDLblsRange.getRange());
+ pFS->endElement(FSNS(XML_c15, XML_f));
+
+ // Write all labels.
+ pFS->startElement(FSNS(XML_c15, XML_dlblRangeCache));
+ pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(rDLblsRange.count()));
+ for (const auto& rLabelKV: rDLblsRange)
+ {
+ pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(rLabelKV.first));
+ pFS->startElement(FSNS(XML_c, XML_v));
+ pFS->writeEscaped(rLabelKV.second);
+ pFS->endElement(FSNS( XML_c, XML_v ));
+ pFS->endElement(FSNS(XML_c, XML_pt));
+ }
+
+ pFS->endElement(FSNS(XML_c15, XML_dlblRangeCache));
+
+ pFS->endElement(FSNS(XML_c15, XML_datalabelsRange));
+ pFS->endElement(FSNS(XML_c, XML_ext));
+ pFS->endElement(FSNS(XML_c, XML_extLst));
+}
+
+}
+
+void ChartExport::exportLineChart( const Reference< chart2::XChartType >& xChartType )
+{
+ FSHelperPtr pFS = GetFS();
+ const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
+ for (const auto& splitDataSeries : aSplitDataSeries)
+ {
+ if (!splitDataSeries.hasElements())
+ continue;
+
+ sal_Int32 nTypeId = XML_lineChart;
+ if( mbIs3DChart )
+ nTypeId = XML_line3DChart;
+ pFS->startElement(FSNS(XML_c, nTypeId));
+
+ exportGrouping( );
+
+ exportVaryColors(xChartType);
+ // TODO: show marker symbol in series?
+ bool bPrimaryAxes = true;
+ exportSeries(xChartType, splitDataSeries, bPrimaryAxes);
+
+ // show marker?
+ sal_Int32 nSymbolType = css::chart::ChartSymbolType::NONE;
+ Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
+ if( GetProperty( xPropSet, "SymbolType" ) )
+ mAny >>= nSymbolType;
+
+ if( !mbIs3DChart )
+ {
+ exportHiLowLines();
+ exportUpDownBars(xChartType);
+ const char* marker = nSymbolType == css::chart::ChartSymbolType::NONE? "0":"1";
+ pFS->singleElement(FSNS(XML_c, XML_marker), XML_val, marker);
+ }
+
+ exportAxesId(bPrimaryAxes, true);
+
+ pFS->endElement( FSNS( XML_c, nTypeId ) );
+ }
+}
+
+void ChartExport::exportPieChart( const Reference< chart2::XChartType >& xChartType )
+{
+ sal_Int32 eChartType = getChartType( );
+ if(eChartType == chart::TYPEID_DOUGHNUT)
+ {
+ exportDoughnutChart( xChartType );
+ return;
+ }
+ FSHelperPtr pFS = GetFS();
+ sal_Int32 nTypeId = XML_pieChart;
+ if( mbIs3DChart )
+ nTypeId = XML_pie3DChart;
+ pFS->startElement(FSNS(XML_c, nTypeId));
+
+ exportVaryColors(xChartType);
+
+ bool bPrimaryAxes = true;
+ exportAllSeries(xChartType, bPrimaryAxes);
+
+ if( !mbIs3DChart )
+ {
+ // firstSliceAng
+ exportFirstSliceAng( );
+ }
+
+ pFS->endElement( FSNS( XML_c, nTypeId ) );
+}
+
+void ChartExport::exportRadarChart( const Reference< chart2::XChartType >& xChartType)
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_radarChart));
+
+ // radarStyle
+ sal_Int32 eChartType = getChartType( );
+ const char* radarStyle = nullptr;
+ if( eChartType == chart::TYPEID_RADARAREA )
+ radarStyle = "filled";
+ else
+ radarStyle = "marker";
+ pFS->singleElement(FSNS(XML_c, XML_radarStyle), XML_val, radarStyle);
+
+ exportVaryColors(xChartType);
+ bool bPrimaryAxes = true;
+ exportAllSeries(xChartType, bPrimaryAxes);
+ exportAxesId(bPrimaryAxes);
+
+ pFS->endElement( FSNS( XML_c, XML_radarChart ) );
+}
+
+void ChartExport::exportScatterChartSeries( const Reference< chart2::XChartType >& xChartType,
+ const css::uno::Sequence<css::uno::Reference<chart2::XDataSeries>>* pSeries)
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_scatterChart));
+ // TODO:scatterStyle
+
+ sal_Int32 nSymbolType = css::chart::ChartSymbolType::NONE;
+ Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
+ if( GetProperty( xPropSet, "SymbolType" ) )
+ mAny >>= nSymbolType;
+
+ const char* scatterStyle = "lineMarker";
+ if (nSymbolType == css::chart::ChartSymbolType::NONE)
+ {
+ scatterStyle = "line";
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_scatterStyle), XML_val, scatterStyle);
+
+ exportVaryColors(xChartType);
+ // FIXME: should export xVal and yVal
+ bool bPrimaryAxes = true;
+ if (pSeries)
+ exportSeries(xChartType, *pSeries, bPrimaryAxes);
+ exportAxesId(bPrimaryAxes);
+
+ pFS->endElement( FSNS( XML_c, XML_scatterChart ) );
+}
+
+void ChartExport::exportScatterChart( const Reference< chart2::XChartType >& xChartType )
+{
+ const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
+ bool bExported = false;
+ for (const auto& splitDataSeries : aSplitDataSeries)
+ {
+ if (!splitDataSeries.hasElements())
+ continue;
+
+ bExported = true;
+ exportScatterChartSeries(xChartType, &splitDataSeries);
+ }
+ if (!bExported)
+ exportScatterChartSeries(xChartType, nullptr);
+}
+
+void ChartExport::exportStockChart( const Reference< chart2::XChartType >& xChartType )
+{
+ FSHelperPtr pFS = GetFS();
+ const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType);
+ for (const auto& splitDataSeries : aSplitDataSeries)
+ {
+ if (!splitDataSeries.hasElements())
+ continue;
+
+ pFS->startElement(FSNS(XML_c, XML_stockChart));
+
+ bool bPrimaryAxes = true;
+ exportCandleStickSeries(splitDataSeries, bPrimaryAxes);
+
+ // export stock properties
+ Reference< css::chart::XStatisticDisplay > xStockPropProvider(mxDiagram, uno::UNO_QUERY);
+ if (xStockPropProvider.is())
+ {
+ exportHiLowLines();
+ exportUpDownBars(xChartType);
+ }
+
+ exportAxesId(bPrimaryAxes);
+
+ pFS->endElement(FSNS(XML_c, XML_stockChart));
+ }
+}
+
+void ChartExport::exportHiLowLines()
+{
+ FSHelperPtr pFS = GetFS();
+ // export the chart property
+ Reference< css::chart::XStatisticDisplay > xChartPropProvider( mxDiagram, uno::UNO_QUERY );
+
+ if (!xChartPropProvider.is())
+ return;
+
+ Reference< beans::XPropertySet > xStockPropSet = xChartPropProvider->getMinMaxLine();
+ if( !xStockPropSet.is() )
+ return;
+
+ pFS->startElement(FSNS(XML_c, XML_hiLowLines));
+ exportShapeProps( xStockPropSet );
+ pFS->endElement( FSNS( XML_c, XML_hiLowLines ) );
+}
+
+void ChartExport::exportUpDownBars( const Reference< chart2::XChartType >& xChartType)
+{
+ if(xChartType->getChartType() != "com.sun.star.chart2.CandleStickChartType")
+ return;
+
+ FSHelperPtr pFS = GetFS();
+ // export the chart property
+ Reference< css::chart::XStatisticDisplay > xChartPropProvider( mxDiagram, uno::UNO_QUERY );
+ if(!xChartPropProvider.is())
+ return;
+
+ // updownbar
+ pFS->startElement(FSNS(XML_c, XML_upDownBars));
+ // TODO: gapWidth
+ pFS->singleElement(FSNS(XML_c, XML_gapWidth), XML_val, OString::number(150));
+
+ Reference< beans::XPropertySet > xChartPropSet = xChartPropProvider->getUpBar();
+ if( xChartPropSet.is() )
+ {
+ pFS->startElement(FSNS(XML_c, XML_upBars));
+ // For Linechart with UpDownBars, spPr is not getting imported
+ // so no need to call the exportShapeProps() for LineChart
+ if(xChartType->getChartType() == "com.sun.star.chart2.CandleStickChartType")
+ {
+ exportShapeProps(xChartPropSet);
+ }
+ pFS->endElement( FSNS( XML_c, XML_upBars ) );
+ }
+ xChartPropSet = xChartPropProvider->getDownBar();
+ if( xChartPropSet.is() )
+ {
+ pFS->startElement(FSNS(XML_c, XML_downBars));
+ if(xChartType->getChartType() == "com.sun.star.chart2.CandleStickChartType")
+ {
+ exportShapeProps(xChartPropSet);
+ }
+ pFS->endElement( FSNS( XML_c, XML_downBars ) );
+ }
+ pFS->endElement( FSNS( XML_c, XML_upDownBars ) );
+}
+
+void ChartExport::exportSurfaceChart( const Reference< chart2::XChartType >& xChartType )
+{
+ FSHelperPtr pFS = GetFS();
+ sal_Int32 nTypeId = XML_surfaceChart;
+ if( mbIs3DChart )
+ nTypeId = XML_surface3DChart;
+ pFS->startElement(FSNS(XML_c, nTypeId));
+ exportVaryColors(xChartType);
+ bool bPrimaryAxes = true;
+ exportAllSeries(xChartType, bPrimaryAxes);
+ exportAxesId(bPrimaryAxes);
+
+ pFS->endElement( FSNS( XML_c, nTypeId ) );
+}
+
+void ChartExport::exportAllSeries(const Reference<chart2::XChartType>& xChartType, bool& rPrimaryAxes)
+{
+ Reference< chart2::XDataSeriesContainer > xDSCnt( xChartType, uno::UNO_QUERY );
+ if( ! xDSCnt.is())
+ return;
+
+ // export dataseries for current chart-type
+ Sequence< Reference< chart2::XDataSeries > > aSeriesSeq( xDSCnt->getDataSeries());
+ exportSeries(xChartType, aSeriesSeq, rPrimaryAxes);
+}
+
+void ChartExport::exportVaryColors(const Reference<chart2::XChartType>& xChartType)
+{
+ FSHelperPtr pFS = GetFS();
+ try
+ {
+ Reference<chart2::XDataSeries> xDataSeries = getPrimaryDataSeries(xChartType);
+ Reference<beans::XPropertySet> xDataSeriesProps(xDataSeries, uno::UNO_QUERY_THROW);
+ Any aAnyVaryColors = xDataSeriesProps->getPropertyValue("VaryColorsByPoint");
+ bool bVaryColors = false;
+ aAnyVaryColors >>= bVaryColors;
+ pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, ToPsz10(bVaryColors));
+ }
+ catch (...)
+ {
+ pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, "0");
+ }
+}
+
+void ChartExport::exportSeries( const Reference<chart2::XChartType>& xChartType,
+ const Sequence<Reference<chart2::XDataSeries> >& rSeriesSeq, bool& rPrimaryAxes )
+{
+ OUString aLabelRole = xChartType->getRoleOfSequenceForSeriesLabel();
+ OUString aChartType( xChartType->getChartType());
+ sal_Int32 eChartType = lcl_getChartType( aChartType );
+
+ for( const auto& rSeries : rSeriesSeq )
+ {
+ // export series
+ Reference< chart2::data::XDataSource > xSource( rSeries, uno::UNO_QUERY );
+ if( xSource.is())
+ {
+ Reference< chart2::XDataSeries > xDataSeries( xSource, uno::UNO_QUERY );
+ Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt(
+ xSource->getDataSequences());
+ // search for main sequence and create a series element
+ {
+ sal_Int32 nMainSequenceIndex = -1;
+ sal_Int32 nSeriesLength = 0;
+ Reference< chart2::data::XDataSequence > xValuesSeq;
+ Reference< chart2::data::XDataSequence > xLabelSeq;
+ sal_Int32 nSeqIdx=0;
+ for( ; nSeqIdx<aSeqCnt.getLength(); ++nSeqIdx )
+ {
+ Reference< chart2::data::XDataSequence > xTempValueSeq( aSeqCnt[nSeqIdx]->getValues() );
+ if( nMainSequenceIndex==-1 )
+ {
+ Reference< beans::XPropertySet > xSeqProp( xTempValueSeq, uno::UNO_QUERY );
+ OUString aRole;
+ if( xSeqProp.is())
+ xSeqProp->getPropertyValue("Role") >>= aRole;
+ // "main" sequence
+ if( aRole == aLabelRole )
+ {
+ xValuesSeq.set( xTempValueSeq );
+ xLabelSeq.set( aSeqCnt[nSeqIdx]->getLabel());
+ nMainSequenceIndex = nSeqIdx;
+ }
+ }
+ sal_Int32 nSequenceLength = (xTempValueSeq.is()? xTempValueSeq->getData().getLength() : sal_Int32(0));
+ if( nSeriesLength < nSequenceLength )
+ nSeriesLength = nSequenceLength;
+ }
+
+ // have found the main sequence, then xValuesSeq and
+ // xLabelSeq contain those. Otherwise both are empty
+ {
+ FSHelperPtr pFS = GetFS();
+
+ pFS->startElement(FSNS(XML_c, XML_ser));
+
+ // TODO: idx and order
+ pFS->singleElement( FSNS( XML_c, XML_idx ),
+ XML_val, OString::number(mnSeriesCount) );
+ pFS->singleElement( FSNS( XML_c, XML_order ),
+ XML_val, OString::number(mnSeriesCount++) );
+
+ // export label
+ if( xLabelSeq.is() )
+ exportSeriesText( xLabelSeq );
+
+ Reference<XPropertySet> xPropSet(xDataSeries, UNO_QUERY_THROW);
+ if( GetProperty( xPropSet, "AttachedAxisIndex") )
+ {
+ sal_Int32 nLocalAttachedAxis = 0;
+ mAny >>= nLocalAttachedAxis;
+ rPrimaryAxes = isPrimaryAxes(nLocalAttachedAxis);
+ }
+
+ // export shape properties
+ Reference< XPropertySet > xOldPropSet = SchXMLSeriesHelper::createOldAPISeriesPropertySet(
+ rSeries, getModel() );
+ if( xOldPropSet.is() )
+ {
+ exportShapeProps( xOldPropSet );
+ }
+
+ switch( eChartType )
+ {
+ case chart::TYPEID_BUBBLE:
+ case chart::TYPEID_HORBAR:
+ case chart::TYPEID_BAR:
+ {
+ pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0");
+ }
+ break;
+ case chart::TYPEID_LINE:
+ {
+ exportMarker(xOldPropSet);
+ break;
+ }
+ case chart::TYPEID_PIE:
+ case chart::TYPEID_DOUGHNUT:
+ {
+ if( xOldPropSet.is() && GetProperty( xOldPropSet, "SegmentOffset") )
+ {
+ sal_Int32 nOffset = 0;
+ mAny >>= nOffset;
+ pFS->singleElement( FSNS( XML_c, XML_explosion ),
+ XML_val, OString::number( nOffset ) );
+ }
+ break;
+ }
+ case chart::TYPEID_SCATTER:
+ {
+ exportMarker(xOldPropSet);
+ break;
+ }
+ case chart::TYPEID_RADARLINE:
+ {
+ exportMarker(xOldPropSet);
+ break;
+ }
+ }
+
+ // export data points
+ exportDataPoints( uno::Reference< beans::XPropertySet >( rSeries, uno::UNO_QUERY ), nSeriesLength, eChartType );
+
+ DataLabelsRange aDLblsRange;
+ // export data labels
+ exportDataLabels(rSeries, nSeriesLength, eChartType, aDLblsRange);
+
+ exportTrendlines( rSeries );
+
+ if( eChartType != chart::TYPEID_PIE &&
+ eChartType != chart::TYPEID_RADARLINE )
+ {
+ //export error bars here
+ Reference< XPropertySet > xSeriesPropSet( xSource, uno::UNO_QUERY );
+ Reference< XPropertySet > xErrorBarYProps;
+ xSeriesPropSet->getPropertyValue("ErrorBarY") >>= xErrorBarYProps;
+ if(xErrorBarYProps.is())
+ exportErrorBar(xErrorBarYProps, true);
+ if (eChartType != chart::TYPEID_BAR &&
+ eChartType != chart::TYPEID_HORBAR)
+ {
+ Reference< XPropertySet > xErrorBarXProps;
+ xSeriesPropSet->getPropertyValue("ErrorBarX") >>= xErrorBarXProps;
+ if(xErrorBarXProps.is())
+ exportErrorBar(xErrorBarXProps, false);
+ }
+ }
+
+ // export categories
+ if( eChartType != chart::TYPEID_SCATTER && eChartType != chart::TYPEID_BUBBLE && mxCategoriesValues.is() )
+ exportSeriesCategory( mxCategoriesValues );
+
+ if( (eChartType == chart::TYPEID_SCATTER)
+ || (eChartType == chart::TYPEID_BUBBLE) )
+ {
+ // export xVal
+ Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, "values-x" ) );
+ if( xSequence.is() )
+ {
+ Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() );
+ if( xValues.is() )
+ exportSeriesValues( xValues, XML_xVal );
+ }
+ else if( mxCategoriesValues.is() )
+ exportSeriesCategory( mxCategoriesValues, XML_xVal );
+ }
+
+ if( eChartType == chart::TYPEID_BUBBLE )
+ {
+ // export yVal
+ Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, "values-y" ) );
+ if( xSequence.is() )
+ {
+ Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() );
+ if( xValues.is() )
+ exportSeriesValues( xValues, XML_yVal );
+ }
+ }
+
+ // export values
+ if( xValuesSeq.is() )
+ {
+ sal_Int32 nYValueType = XML_val;
+ if( eChartType == chart::TYPEID_SCATTER )
+ nYValueType = XML_yVal;
+ else if( eChartType == chart::TYPEID_BUBBLE )
+ nYValueType = XML_bubbleSize;
+ exportSeriesValues( xValuesSeq, nYValueType );
+ }
+
+ if( eChartType == chart::TYPEID_SCATTER
+ || eChartType == chart::TYPEID_LINE )
+ exportSmooth();
+
+ // tdf103988: "corrupted" files with Bubble chart opening in MSO
+ if( eChartType == chart::TYPEID_BUBBLE )
+ pFS->singleElement(FSNS(XML_c, XML_bubble3D), XML_val, "0");
+
+ if (!aDLblsRange.empty())
+ writeDataLabelsRange(pFS, GetFB(), aDLblsRange);
+
+ pFS->endElement( FSNS( XML_c, XML_ser ) );
+ }
+ }
+ }
+ }
+}
+
+void ChartExport::exportCandleStickSeries(
+ const Sequence< Reference< chart2::XDataSeries > > & aSeriesSeq,
+ bool& rPrimaryAxes)
+{
+ for( const Reference< chart2::XDataSeries >& xSeries : aSeriesSeq )
+ {
+ rPrimaryAxes = lcl_isSeriesAttachedToFirstAxis(xSeries);
+
+ Reference< chart2::data::XDataSource > xSource( xSeries, uno::UNO_QUERY );
+ if( xSource.is())
+ {
+ // export series in correct order (as we don't store roles)
+ // with japanese candlesticks: open, low, high, close
+ // otherwise: low, high, close
+ Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt(
+ xSource->getDataSequences());
+
+ const char* sSeries[] = {"values-first","values-max","values-min","values-last",nullptr};
+
+ for( sal_Int32 idx = 0; sSeries[idx] != nullptr ; idx++ )
+ {
+ Reference< chart2::data::XLabeledDataSequence > xLabeledSeq( lcl_getDataSequenceByRole( aSeqCnt, OUString::createFromAscii(sSeries[idx]) ) );
+ if( xLabeledSeq.is())
+ {
+ Reference< chart2::data::XDataSequence > xLabelSeq( xLabeledSeq->getLabel());
+ Reference< chart2::data::XDataSequence > xValueSeq( xLabeledSeq->getValues());
+ {
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_ser));
+
+ // TODO: idx and order
+ // idx attribute should start from 1 and not from 0.
+ pFS->singleElement( FSNS( XML_c, XML_idx ),
+ XML_val, OString::number(idx+1) );
+ pFS->singleElement( FSNS( XML_c, XML_order ),
+ XML_val, OString::number(idx+1) );
+
+ // export label
+ if( xLabelSeq.is() )
+ exportSeriesText( xLabelSeq );
+
+ // TODO:export shape properties
+
+ // export categories
+ if( mxCategoriesValues.is() )
+ exportSeriesCategory( mxCategoriesValues );
+
+ // export values
+ if( xValueSeq.is() )
+ exportSeriesValues( xValueSeq );
+
+ pFS->endElement( FSNS( XML_c, XML_ser ) );
+ }
+ }
+ }
+ }
+ }
+}
+
+void ChartExport::exportSeriesText( const Reference< chart2::data::XDataSequence > & xValueSeq )
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_tx));
+
+ OUString aCellRange = xValueSeq->getSourceRangeRepresentation();
+ aCellRange = parseFormula( aCellRange );
+ pFS->startElement(FSNS(XML_c, XML_strRef));
+
+ pFS->startElement(FSNS(XML_c, XML_f));
+ pFS->writeEscaped( aCellRange );
+ pFS->endElement( FSNS( XML_c, XML_f ) );
+
+ OUString aLabelString = lcl_flattenStringSequence(lcl_getLabelSequence(xValueSeq));
+ pFS->startElement(FSNS(XML_c, XML_strCache));
+ pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, "1");
+ pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, "0");
+ pFS->startElement(FSNS(XML_c, XML_v));
+ pFS->writeEscaped( aLabelString );
+ pFS->endElement( FSNS( XML_c, XML_v ) );
+ pFS->endElement( FSNS( XML_c, XML_pt ) );
+ pFS->endElement( FSNS( XML_c, XML_strCache ) );
+ pFS->endElement( FSNS( XML_c, XML_strRef ) );
+ pFS->endElement( FSNS( XML_c, XML_tx ) );
+}
+
+void ChartExport::exportSeriesCategory( const Reference< chart2::data::XDataSequence > & xValueSeq, sal_Int32 nValueType )
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, nValueType));
+
+ OUString aCellRange = xValueSeq.is() ? xValueSeq->getSourceRangeRepresentation() : OUString();
+ const Sequence< Sequence< OUString >> aFinalSplitSource = (nValueType == XML_cat) ? getSplitCategoriesList(aCellRange) : Sequence< Sequence< OUString>>(0);
+ aCellRange = parseFormula( aCellRange );
+
+ if(aFinalSplitSource.getLength() > 1)
+ {
+ // export multi level category axis labels
+ pFS->startElement(FSNS(XML_c, XML_multiLvlStrRef));
+
+ pFS->startElement(FSNS(XML_c, XML_f));
+ pFS->writeEscaped(aCellRange);
+ pFS->endElement(FSNS(XML_c, XML_f));
+
+ pFS->startElement(FSNS(XML_c, XML_multiLvlStrCache));
+ pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(aFinalSplitSource[0].getLength()));
+ for(const auto& rSeq : aFinalSplitSource)
+ {
+ pFS->startElement(FSNS(XML_c, XML_lvl));
+ for(sal_Int32 j = 0; j < rSeq.getLength(); j++)
+ {
+ if(!rSeq[j].isEmpty())
+ {
+ pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(j));
+ pFS->startElement(FSNS(XML_c, XML_v));
+ pFS->writeEscaped(rSeq[j]);
+ pFS->endElement(FSNS(XML_c, XML_v));
+ pFS->endElement(FSNS(XML_c, XML_pt));
+ }
+ }
+ pFS->endElement(FSNS(XML_c, XML_lvl));
+ }
+
+ pFS->endElement(FSNS(XML_c, XML_multiLvlStrCache));
+ pFS->endElement(FSNS(XML_c, XML_multiLvlStrRef));
+ }
+ else
+ {
+ // export single category axis labels
+ bool bWriteDateCategories = mbHasDateCategories && (nValueType == XML_cat);
+ OUString aNumberFormatString;
+ if (bWriteDateCategories)
+ {
+ Reference< css::chart::XAxisXSupplier > xAxisXSupp( mxDiagram, uno::UNO_QUERY );
+ if( xAxisXSupp.is())
+ {
+ Reference< XPropertySet > xAxisProp = xAxisXSupp->getXAxis();
+ if (GetProperty(xAxisProp, "NumberFormat"))
+ {
+ sal_Int32 nKey = 0;
+ mAny >>= nKey;
+ aNumberFormatString = getNumberFormatCode(nKey);
+ }
+ }
+ if (aNumberFormatString.isEmpty())
+ bWriteDateCategories = false;
+ }
+
+ pFS->startElement(FSNS(XML_c, bWriteDateCategories ? XML_numRef : XML_strRef));
+
+ pFS->startElement(FSNS(XML_c, XML_f));
+ pFS->writeEscaped(aCellRange);
+ pFS->endElement(FSNS(XML_c, XML_f));
+
+ ::std::vector< OUString > aCategories;
+ lcl_fillCategoriesIntoStringVector(xValueSeq, aCategories);
+ sal_Int32 ptCount = aCategories.size();
+ pFS->startElement(FSNS(XML_c, bWriteDateCategories ? XML_numCache : XML_strCache));
+ if (bWriteDateCategories)
+ {
+ pFS->startElement(FSNS(XML_c, XML_formatCode));
+ pFS->writeEscaped(aNumberFormatString);
+ pFS->endElement(FSNS(XML_c, XML_formatCode));
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(ptCount));
+ for (sal_Int32 i = 0; i < ptCount; i++)
+ {
+ pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(i));
+ pFS->startElement(FSNS(XML_c, XML_v));
+ pFS->writeEscaped(aCategories[i]);
+ pFS->endElement(FSNS(XML_c, XML_v));
+ pFS->endElement(FSNS(XML_c, XML_pt));
+ }
+
+ pFS->endElement(FSNS(XML_c, bWriteDateCategories ? XML_numCache : XML_strCache));
+ pFS->endElement(FSNS(XML_c, bWriteDateCategories ? XML_numRef : XML_strRef));
+ }
+
+ pFS->endElement( FSNS( XML_c, nValueType ) );
+}
+
+void ChartExport::exportSeriesValues( const Reference< chart2::data::XDataSequence > & xValueSeq, sal_Int32 nValueType )
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, nValueType));
+
+ OUString aCellRange = xValueSeq.is() ? xValueSeq->getSourceRangeRepresentation() : OUString();
+ aCellRange = parseFormula( aCellRange );
+ // TODO: need to handle XML_multiLvlStrRef according to aCellRange
+ pFS->startElement(FSNS(XML_c, XML_numRef));
+
+ pFS->startElement(FSNS(XML_c, XML_f));
+ pFS->writeEscaped( aCellRange );
+ pFS->endElement( FSNS( XML_c, XML_f ) );
+
+ ::std::vector< double > aValues = lcl_getAllValuesFromSequence( xValueSeq );
+ sal_Int32 ptCount = aValues.size();
+ pFS->startElement(FSNS(XML_c, XML_numCache));
+ pFS->startElement(FSNS(XML_c, XML_formatCode));
+ // TODO: what format code?
+ pFS->writeEscaped( "General" );
+ pFS->endElement( FSNS( XML_c, XML_formatCode ) );
+ pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(ptCount));
+
+ for( sal_Int32 i = 0; i < ptCount; i++ )
+ {
+ if (!std::isnan(aValues[i]))
+ {
+ pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(i));
+ pFS->startElement(FSNS(XML_c, XML_v));
+ pFS->write(aValues[i]);
+ pFS->endElement(FSNS(XML_c, XML_v));
+ pFS->endElement(FSNS(XML_c, XML_pt));
+ }
+ }
+
+ pFS->endElement( FSNS( XML_c, XML_numCache ) );
+ pFS->endElement( FSNS( XML_c, XML_numRef ) );
+ pFS->endElement( FSNS( XML_c, nValueType ) );
+}
+
+void ChartExport::exportShapeProps( const Reference< XPropertySet >& xPropSet )
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_spPr));
+
+ exportFill( xPropSet );
+ WriteOutline( xPropSet, getModel() );
+
+ pFS->endElement( FSNS( XML_c, XML_spPr ) );
+}
+
+void ChartExport::exportTextProps(const Reference<XPropertySet>& xPropSet)
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_txPr));
+
+ sal_Int32 nRotation = 0;
+ const char* textWordWrap = nullptr;
+
+ if (auto xServiceInfo = uno::Reference<lang::XServiceInfo>(xPropSet, uno::UNO_QUERY))
+ {
+ double fMultiplier = 0.0;
+ // We have at least two possible units of returned value: degrees (e.g., for data labels),
+ // and 100ths of degree (e.g., for axes labels). The latter is returned as an Any wrapping
+ // a sal_Int32 value (see WrappedTextRotationProperty::convertInnerToOuterValue), while
+ // the former is double. So we could test the contained type to decide which multiplier to
+ // use. But testing the service info should be more robust.
+ if (xServiceInfo->supportsService("com.sun.star.chart.ChartAxis"))
+ fMultiplier = -600.0;
+ else if (xServiceInfo->supportsService("com.sun.star.chart2.DataSeries") || xServiceInfo->supportsService("com.sun.star.chart2.DataPointProperties"))
+ {
+ fMultiplier = -60000.0;
+ bool bTextWordWrap = false;
+ if ((xPropSet->getPropertyValue("TextWordWrap") >>= bTextWordWrap) && bTextWordWrap)
+ textWordWrap = "square";
+ else
+ textWordWrap = "none";
+ }
+
+ if (fMultiplier)
+ {
+ double fTextRotation = 0.0;
+ uno::Any aAny = xPropSet->getPropertyValue("TextRotation");
+ if (aAny.hasValue() && (aAny >>= fTextRotation))
+ {
+ fTextRotation *= fMultiplier;
+ // The MS Office UI allows values only in range of [-90,90].
+ if (fTextRotation < -5400000.0 && fTextRotation > -16200000.0)
+ {
+ // Reflect the angle if the value is between 90° and 270°
+ fTextRotation += 10800000.0;
+ }
+ else if (fTextRotation <= -16200000.0)
+ {
+ fTextRotation += 21600000.0;
+ }
+ nRotation = std::round(fTextRotation);
+ }
+ }
+ }
+
+ if (nRotation)
+ pFS->singleElement(FSNS(XML_a, XML_bodyPr), XML_rot, OString::number(nRotation), XML_wrap, textWordWrap);
+ else
+ pFS->singleElement(FSNS(XML_a, XML_bodyPr), XML_wrap, textWordWrap);
+
+ pFS->singleElement(FSNS(XML_a, XML_lstStyle));
+
+ pFS->startElement(FSNS(XML_a, XML_p));
+ pFS->startElement(FSNS(XML_a, XML_pPr));
+
+ WriteRunProperties(xPropSet, false, XML_defRPr, true, o3tl::temporary(false),
+ o3tl::temporary(sal_Int32()));
+
+ pFS->endElement(FSNS(XML_a, XML_pPr));
+ pFS->endElement(FSNS(XML_a, XML_p));
+ pFS->endElement(FSNS(XML_c, XML_txPr));
+}
+
+void ChartExport::InitPlotArea( )
+{
+ Reference< XPropertySet > xDiagramProperties (mxDiagram, uno::UNO_QUERY);
+
+ // Check for supported services and then the properties provided by this service.
+ Reference<lang::XServiceInfo> xServiceInfo (mxDiagram, uno::UNO_QUERY);
+ if (xServiceInfo.is())
+ {
+ if (xServiceInfo->supportsService("com.sun.star.chart.ChartAxisZSupplier"))
+ {
+ xDiagramProperties->getPropertyValue("HasZAxis") >>= mbHasZAxis;
+ }
+ }
+
+ xDiagramProperties->getPropertyValue("Dim3D") >>= mbIs3DChart;
+
+ if( mbHasCategoryLabels && mxNewDiagram.is())
+ {
+ Reference< chart2::data::XLabeledDataSequence > xCategories( lcl_getCategories( mxNewDiagram, mbHasDateCategories ) );
+ if( xCategories.is() )
+ {
+ mxCategoriesValues.set( xCategories->getValues() );
+ }
+ }
+}
+
+void ChartExport::exportAxes( )
+{
+ sal_Int32 nSize = maAxes.size();
+ // let's export the axis types in the right order
+ for ( sal_Int32 nSortIdx = AXIS_PRIMARY_X; nSortIdx <= AXIS_SECONDARY_Y; nSortIdx++ )
+ {
+ for ( sal_Int32 nIdx = 0; nIdx < nSize; nIdx++ )
+ {
+ if (nSortIdx == maAxes[nIdx].nAxisType)
+ exportAxis( maAxes[nIdx] );
+ }
+ }
+}
+
+namespace {
+
+sal_Int32 getXAxisTypeByChartType(sal_Int32 eChartType)
+{
+ if( (eChartType == chart::TYPEID_SCATTER)
+ || (eChartType == chart::TYPEID_BUBBLE) )
+ return XML_valAx;
+ else if( eChartType == chart::TYPEID_STOCK )
+ return XML_dateAx;
+
+ return XML_catAx;
+}
+
+sal_Int32 getRealXAxisType(sal_Int32 nAxisType)
+{
+ if( nAxisType == chart2::AxisType::CATEGORY )
+ return XML_catAx;
+ else if( nAxisType == chart2::AxisType::DATE )
+ return XML_dateAx;
+ else if( nAxisType == chart2::AxisType::SERIES )
+ return XML_serAx;
+
+ return XML_valAx;
+}
+
+}
+
+void ChartExport::exportAxis(const AxisIdPair& rAxisIdPair)
+{
+ // get some properties from document first
+ bool bHasXAxisTitle = false,
+ bHasYAxisTitle = false,
+ bHasZAxisTitle = false,
+ bHasSecondaryXAxisTitle = false,
+ bHasSecondaryYAxisTitle = false;
+ bool bHasXAxisMajorGrid = false,
+ bHasXAxisMinorGrid = false,
+ bHasYAxisMajorGrid = false,
+ bHasYAxisMinorGrid = false,
+ bHasZAxisMajorGrid = false,
+ bHasZAxisMinorGrid = false;
+
+ Reference< XPropertySet > xDiagramProperties (mxDiagram, uno::UNO_QUERY);
+
+ xDiagramProperties->getPropertyValue("HasXAxisTitle") >>= bHasXAxisTitle;
+ xDiagramProperties->getPropertyValue("HasYAxisTitle") >>= bHasYAxisTitle;
+ xDiagramProperties->getPropertyValue("HasZAxisTitle") >>= bHasZAxisTitle;
+ xDiagramProperties->getPropertyValue("HasSecondaryXAxisTitle") >>= bHasSecondaryXAxisTitle;
+ xDiagramProperties->getPropertyValue("HasSecondaryYAxisTitle") >>= bHasSecondaryYAxisTitle;
+
+ xDiagramProperties->getPropertyValue("HasXAxisGrid") >>= bHasXAxisMajorGrid;
+ xDiagramProperties->getPropertyValue("HasYAxisGrid") >>= bHasYAxisMajorGrid;
+ xDiagramProperties->getPropertyValue("HasZAxisGrid") >>= bHasZAxisMajorGrid;
+
+ xDiagramProperties->getPropertyValue("HasXAxisHelpGrid") >>= bHasXAxisMinorGrid;
+ xDiagramProperties->getPropertyValue("HasYAxisHelpGrid") >>= bHasYAxisMinorGrid;
+ xDiagramProperties->getPropertyValue("HasZAxisHelpGrid") >>= bHasZAxisMinorGrid;
+
+ Reference< XPropertySet > xAxisProp;
+ Reference< drawing::XShape > xAxisTitle;
+ Reference< beans::XPropertySet > xMajorGrid;
+ Reference< beans::XPropertySet > xMinorGrid;
+ sal_Int32 nAxisType = XML_catAx;
+ const char* sAxPos = nullptr;
+
+ switch( rAxisIdPair.nAxisType )
+ {
+ case AXIS_PRIMARY_X:
+ {
+ Reference< css::chart::XAxisXSupplier > xAxisXSupp( mxDiagram, uno::UNO_QUERY );
+ if( xAxisXSupp.is())
+ xAxisProp = xAxisXSupp->getXAxis();
+ if( bHasXAxisTitle )
+ xAxisTitle = xAxisXSupp->getXAxisTitle();
+ if( bHasXAxisMajorGrid )
+ xMajorGrid = xAxisXSupp->getXMainGrid();
+ if( bHasXAxisMinorGrid )
+ xMinorGrid = xAxisXSupp->getXHelpGrid();
+
+ nAxisType = lcl_getCategoryAxisType(mxNewDiagram, 0, 0);
+ if( nAxisType != -1 )
+ nAxisType = getRealXAxisType(nAxisType);
+ else
+ nAxisType = getXAxisTypeByChartType( getChartType() );
+ // FIXME: axPos, need to check axis direction
+ sAxPos = "b";
+ break;
+ }
+ case AXIS_PRIMARY_Y:
+ {
+ Reference< css::chart::XAxisYSupplier > xAxisYSupp( mxDiagram, uno::UNO_QUERY );
+ if( xAxisYSupp.is())
+ xAxisProp = xAxisYSupp->getYAxis();
+ if( bHasYAxisTitle )
+ xAxisTitle = xAxisYSupp->getYAxisTitle();
+ if( bHasYAxisMajorGrid )
+ xMajorGrid = xAxisYSupp->getYMainGrid();
+ if( bHasYAxisMinorGrid )
+ xMinorGrid = xAxisYSupp->getYHelpGrid();
+
+ nAxisType = XML_valAx;
+ // FIXME: axPos, need to check axis direction
+ sAxPos = "l";
+ break;
+ }
+ case AXIS_PRIMARY_Z:
+ {
+ Reference< css::chart::XAxisZSupplier > xAxisZSupp( mxDiagram, uno::UNO_QUERY );
+ if( xAxisZSupp.is())
+ xAxisProp = xAxisZSupp->getZAxis();
+ if( bHasZAxisTitle )
+ xAxisTitle = xAxisZSupp->getZAxisTitle();
+ if( bHasZAxisMajorGrid )
+ xMajorGrid = xAxisZSupp->getZMainGrid();
+ if( bHasZAxisMinorGrid )
+ xMinorGrid = xAxisZSupp->getZHelpGrid();
+
+ sal_Int32 eChartType = getChartType( );
+ if( (eChartType == chart::TYPEID_SCATTER)
+ || (eChartType == chart::TYPEID_BUBBLE) )
+ nAxisType = XML_valAx;
+ else if( eChartType == chart::TYPEID_STOCK )
+ nAxisType = XML_dateAx;
+ else if( eChartType == chart::TYPEID_BAR || eChartType == chart::TYPEID_AREA )
+ nAxisType = XML_serAx;
+ // FIXME: axPos, need to check axis direction
+ sAxPos = "b";
+ break;
+ }
+ case AXIS_SECONDARY_X:
+ {
+ Reference< css::chart::XTwoAxisXSupplier > xAxisTwoXSupp( mxDiagram, uno::UNO_QUERY );
+ if( xAxisTwoXSupp.is())
+ xAxisProp = xAxisTwoXSupp->getSecondaryXAxis();
+ if( bHasSecondaryXAxisTitle )
+ {
+ Reference< css::chart::XSecondAxisTitleSupplier > xAxisSupp( mxDiagram, uno::UNO_QUERY );
+ xAxisTitle = xAxisSupp->getSecondXAxisTitle();
+ }
+
+ nAxisType = lcl_getCategoryAxisType(mxNewDiagram, 0, 1);
+ if( nAxisType != -1 )
+ nAxisType = getRealXAxisType(nAxisType);
+ else
+ nAxisType = getXAxisTypeByChartType( getChartType() );
+ // FIXME: axPos, need to check axis direction
+ sAxPos = "t";
+ break;
+ }
+ case AXIS_SECONDARY_Y:
+ {
+ Reference< css::chart::XTwoAxisYSupplier > xAxisTwoYSupp( mxDiagram, uno::UNO_QUERY );
+ if( xAxisTwoYSupp.is())
+ xAxisProp = xAxisTwoYSupp->getSecondaryYAxis();
+ if( bHasSecondaryYAxisTitle )
+ {
+ Reference< css::chart::XSecondAxisTitleSupplier > xAxisSupp( mxDiagram, uno::UNO_QUERY );
+ xAxisTitle = xAxisSupp->getSecondYAxisTitle();
+ }
+
+ nAxisType = XML_valAx;
+ // FIXME: axPos, need to check axis direction
+ sAxPos = "r";
+ break;
+ }
+ }
+
+ _exportAxis(xAxisProp, xAxisTitle, xMajorGrid, xMinorGrid, nAxisType, sAxPos, rAxisIdPair);
+}
+
+void ChartExport::_exportAxis(
+ const Reference< XPropertySet >& xAxisProp,
+ const Reference< drawing::XShape >& xAxisTitle,
+ const Reference< XPropertySet >& xMajorGrid,
+ const Reference< XPropertySet >& xMinorGrid,
+ sal_Int32 nAxisType,
+ const char* sAxisPos,
+ const AxisIdPair& rAxisIdPair )
+{
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, nAxisType));
+ pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(rAxisIdPair.nAxisId));
+
+ pFS->startElement(FSNS(XML_c, XML_scaling));
+
+ // logBase, min, max
+ if(GetProperty( xAxisProp, "Logarithmic" ) )
+ {
+ bool bLogarithmic = false;
+ mAny >>= bLogarithmic;
+ if( bLogarithmic )
+ {
+ // default value is 10?
+ pFS->singleElement(FSNS(XML_c, XML_logBase), XML_val, OString::number(10));
+ }
+ }
+
+ // orientation: minMax, maxMin
+ bool bReverseDirection = false;
+ if(GetProperty( xAxisProp, "ReverseDirection" ) )
+ mAny >>= bReverseDirection;
+
+ const char* orientation = bReverseDirection ? "maxMin":"minMax";
+ pFS->singleElement(FSNS(XML_c, XML_orientation), XML_val, orientation);
+
+ bool bAutoMax = false;
+ if(GetProperty( xAxisProp, "AutoMax" ) )
+ mAny >>= bAutoMax;
+
+ if( !bAutoMax && (GetProperty( xAxisProp, "Max" ) ) )
+ {
+ double dMax = 0;
+ mAny >>= dMax;
+ pFS->singleElement(FSNS(XML_c, XML_max), XML_val, OString::number(dMax));
+ }
+
+ bool bAutoMin = false;
+ if(GetProperty( xAxisProp, "AutoMin" ) )
+ mAny >>= bAutoMin;
+
+ if( !bAutoMin && (GetProperty( xAxisProp, "Min" ) ) )
+ {
+ double dMin = 0;
+ mAny >>= dMin;
+ pFS->singleElement(FSNS(XML_c, XML_min), XML_val, OString::number(dMin));
+ }
+
+ pFS->endElement( FSNS( XML_c, XML_scaling ) );
+
+ bool bVisible = true;
+ if( xAxisProp.is() )
+ {
+ xAxisProp->getPropertyValue("Visible") >>= bVisible;
+ }
+
+ // only export each axis only once non-deleted
+ bool bDeleted = maExportedAxis.find(rAxisIdPair.nAxisType) != maExportedAxis.end();
+
+ if (!bDeleted)
+ maExportedAxis.insert(rAxisIdPair.nAxisType);
+
+ pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, !bDeleted && bVisible ? "0" : "1");
+
+ // FIXME: axPos, need to check the property "ReverseDirection"
+ pFS->singleElement(FSNS(XML_c, XML_axPos), XML_val, sAxisPos);
+ // major grid line
+ if( xMajorGrid.is())
+ {
+ pFS->startElement(FSNS(XML_c, XML_majorGridlines));
+ exportShapeProps( xMajorGrid );
+ pFS->endElement( FSNS( XML_c, XML_majorGridlines ) );
+ }
+
+ // minor grid line
+ if( xMinorGrid.is())
+ {
+ pFS->startElement(FSNS(XML_c, XML_minorGridlines));
+ exportShapeProps( xMinorGrid );
+ pFS->endElement( FSNS( XML_c, XML_minorGridlines ) );
+ }
+
+ // title
+ if( xAxisTitle.is() )
+ exportTitle( xAxisTitle );
+
+ bool bLinkedNumFmt = true;
+ if (GetProperty(xAxisProp, "LinkNumberFormatToSource"))
+ mAny >>= bLinkedNumFmt;
+
+ OUString aNumberFormatString("General");
+ if (GetProperty(xAxisProp, "NumberFormat"))
+ {
+ sal_Int32 nKey = 0;
+ mAny >>= nKey;
+ aNumberFormatString = getNumberFormatCode(nKey);
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_numFmt),
+ XML_formatCode, aNumberFormatString,
+ XML_sourceLinked, bLinkedNumFmt ? "1" : "0");
+
+ // majorTickMark
+ sal_Int32 nValue = 0;
+ if(GetProperty( xAxisProp, "Marks" ) )
+ {
+ mAny >>= nValue;
+ bool bInner = nValue & css::chart::ChartAxisMarks::INNER;
+ bool bOuter = nValue & css::chart::ChartAxisMarks::OUTER;
+ const char* majorTickMark = nullptr;
+ if( bInner && bOuter )
+ majorTickMark = "cross";
+ else if( bInner )
+ majorTickMark = "in";
+ else if( bOuter )
+ majorTickMark = "out";
+ else
+ majorTickMark = "none";
+ pFS->singleElement(FSNS(XML_c, XML_majorTickMark), XML_val, majorTickMark);
+ }
+ // minorTickMark
+ if(GetProperty( xAxisProp, "HelpMarks" ) )
+ {
+ mAny >>= nValue;
+ bool bInner = nValue & css::chart::ChartAxisMarks::INNER;
+ bool bOuter = nValue & css::chart::ChartAxisMarks::OUTER;
+ const char* minorTickMark = nullptr;
+ if( bInner && bOuter )
+ minorTickMark = "cross";
+ else if( bInner )
+ minorTickMark = "in";
+ else if( bOuter )
+ minorTickMark = "out";
+ else
+ minorTickMark = "none";
+ pFS->singleElement(FSNS(XML_c, XML_minorTickMark), XML_val, minorTickMark);
+ }
+ // tickLblPos
+ const char* sTickLblPos = nullptr;
+ bool bDisplayLabel = true;
+ if(GetProperty( xAxisProp, "DisplayLabels" ) )
+ mAny >>= bDisplayLabel;
+ if( bDisplayLabel && (GetProperty( xAxisProp, "LabelPosition" ) ) )
+ {
+ css::chart::ChartAxisLabelPosition eLabelPosition = css::chart::ChartAxisLabelPosition_NEAR_AXIS;
+ mAny >>= eLabelPosition;
+ switch( eLabelPosition )
+ {
+ case css::chart::ChartAxisLabelPosition_NEAR_AXIS:
+ case css::chart::ChartAxisLabelPosition_NEAR_AXIS_OTHER_SIDE:
+ sTickLblPos = "nextTo";
+ break;
+ case css::chart::ChartAxisLabelPosition_OUTSIDE_START:
+ sTickLblPos = "low";
+ break;
+ case css::chart::ChartAxisLabelPosition_OUTSIDE_END:
+ sTickLblPos = "high";
+ break;
+ default:
+ sTickLblPos = "nextTo";
+ break;
+ }
+ }
+ else
+ {
+ sTickLblPos = "none";
+ }
+ pFS->singleElement(FSNS(XML_c, XML_tickLblPos), XML_val, sTickLblPos);
+
+ // shape properties
+ exportShapeProps( xAxisProp );
+
+ exportTextProps(xAxisProp);
+
+ pFS->singleElement(FSNS(XML_c, XML_crossAx), XML_val, OString::number(rAxisIdPair.nCrossAx));
+
+ // crosses & crossesAt
+ bool bCrossesValue = false;
+ const char* sCrosses = nullptr;
+ // do not export the CrossoverPosition/CrossoverValue, if the axis is deleted and not visible
+ if( GetProperty( xAxisProp, "CrossoverPosition" ) && !bDeleted && bVisible )
+ {
+ css::chart::ChartAxisPosition ePosition( css::chart::ChartAxisPosition_ZERO );
+ mAny >>= ePosition;
+ switch( ePosition )
+ {
+ case css::chart::ChartAxisPosition_START:
+ sCrosses = "min";
+ break;
+ case css::chart::ChartAxisPosition_END:
+ sCrosses = "max";
+ break;
+ case css::chart::ChartAxisPosition_ZERO:
+ sCrosses = "autoZero";
+ break;
+ default:
+ bCrossesValue = true;
+ break;
+ }
+ }
+
+ if( bCrossesValue && GetProperty( xAxisProp, "CrossoverValue" ) )
+ {
+ double dValue = 0;
+ mAny >>= dValue;
+ pFS->singleElement(FSNS(XML_c, XML_crossesAt), XML_val, OString::number(dValue));
+ }
+ else
+ {
+ if(sCrosses)
+ {
+ pFS->singleElement(FSNS(XML_c, XML_crosses), XML_val, sCrosses);
+ }
+ }
+
+ if( ( nAxisType == XML_catAx )
+ || ( nAxisType == XML_dateAx ) )
+ {
+ // FIXME: seems not support? use default value,
+ const char* const isAuto = "1";
+ pFS->singleElement(FSNS(XML_c, XML_auto), XML_val, isAuto);
+
+ if( nAxisType == XML_catAx )
+ {
+ // FIXME: seems not support? lblAlgn
+ const char* const sLblAlgn = "ctr";
+ pFS->singleElement(FSNS(XML_c, XML_lblAlgn), XML_val, sLblAlgn);
+ }
+
+ // FIXME: seems not support? lblOffset
+ pFS->singleElement(FSNS(XML_c, XML_lblOffset), XML_val, OString::number(100));
+
+ // export baseTimeUnit, majorTimeUnit, minorTimeUnit of Date axis
+ if( nAxisType == XML_dateAx )
+ {
+ sal_Int32 nAxisIndex = -1;
+ if( rAxisIdPair.nAxisType == AXIS_PRIMARY_X )
+ nAxisIndex = 0;
+ else if( rAxisIdPair.nAxisType == AXIS_SECONDARY_X )
+ nAxisIndex = 1;
+
+ cssc::TimeIncrement aTimeIncrement = lcl_getDateTimeIncrement( mxNewDiagram, nAxisIndex );
+ sal_Int32 nTimeResolution = css::chart::TimeUnit::DAY;
+ if( aTimeIncrement.TimeResolution >>= nTimeResolution )
+ pFS->singleElement(FSNS(XML_c, XML_baseTimeUnit), XML_val, lclGetTimeUnitToken(nTimeResolution));
+
+ cssc::TimeInterval aInterval;
+ if( aTimeIncrement.MajorTimeInterval >>= aInterval )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_majorUnit), XML_val, OString::number(aInterval.Number));
+ pFS->singleElement(FSNS(XML_c, XML_majorTimeUnit), XML_val, lclGetTimeUnitToken(aInterval.TimeUnit));
+ }
+ if( aTimeIncrement.MinorTimeInterval >>= aInterval )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_minorUnit), XML_val, OString::number(aInterval.Number));
+ pFS->singleElement(FSNS(XML_c, XML_minorTimeUnit), XML_val, lclGetTimeUnitToken(aInterval.TimeUnit));
+ }
+ }
+
+ // FIXME: seems not support? noMultiLvlLbl
+ pFS->singleElement(FSNS(XML_c, XML_noMultiLvlLbl), XML_val, OString::number(0));
+ }
+
+ // crossBetween
+ if( nAxisType == XML_valAx )
+ {
+ if( lcl_isCategoryAxisShifted( mxNewDiagram ))
+ pFS->singleElement(FSNS(XML_c, XML_crossBetween), XML_val, "between");
+ else
+ pFS->singleElement(FSNS(XML_c, XML_crossBetween), XML_val, "midCat");
+ }
+
+ // majorUnit
+ bool bAutoStepMain = false;
+ if(GetProperty( xAxisProp, "AutoStepMain" ) )
+ mAny >>= bAutoStepMain;
+
+ if( !bAutoStepMain && (GetProperty( xAxisProp, "StepMain" ) ) )
+ {
+ double dMajorUnit = 0;
+ mAny >>= dMajorUnit;
+ pFS->singleElement(FSNS(XML_c, XML_majorUnit), XML_val, OString::number(dMajorUnit));
+ }
+ // minorUnit
+ bool bAutoStepHelp = false;
+ if(GetProperty( xAxisProp, "AutoStepHelp" ) )
+ mAny >>= bAutoStepHelp;
+
+ if( !bAutoStepHelp && (GetProperty( xAxisProp, "StepHelp" ) ) )
+ {
+ double dMinorUnit = 0;
+ mAny >>= dMinorUnit;
+ if( GetProperty( xAxisProp, "StepHelpCount" ) )
+ {
+ sal_Int32 dMinorUnitCount = 0;
+ mAny >>= dMinorUnitCount;
+ // tdf#114168 Don't save minor unit if number of step help count is 5 (which is default for MS Excel),
+ // to allow proper .xlsx import. If minorUnit is set and majorUnit not, then it is impossible
+ // to calculate StepHelpCount.
+ if( dMinorUnitCount != 5 )
+ {
+ pFS->singleElement( FSNS( XML_c, XML_minorUnit ),
+ XML_val, OString::number( dMinorUnit ) );
+ }
+ }
+ }
+
+ if( nAxisType == XML_valAx && GetProperty( xAxisProp, "DisplayUnits" ) )
+ {
+ bool bDisplayUnits = false;
+ mAny >>= bDisplayUnits;
+ if(bDisplayUnits)
+ {
+ if(GetProperty( xAxisProp, "BuiltInUnit" ))
+ {
+ OUString aVal;
+ mAny >>= aVal;
+ if(!aVal.isEmpty())
+ {
+ pFS->startElement(FSNS(XML_c, XML_dispUnits));
+
+ pFS->singleElement(FSNS(XML_c, XML_builtInUnit), XML_val, aVal);
+
+ pFS->singleElement(FSNS( XML_c, XML_dispUnitsLbl ));
+ pFS->endElement( FSNS( XML_c, XML_dispUnits ) );
+ }
+ }
+ }
+ }
+
+ pFS->endElement( FSNS( XML_c, nAxisType ) );
+}
+
+namespace {
+
+struct LabelPlacementParam
+{
+ bool mbExport;
+ sal_Int32 meDefault;
+
+ std::unordered_set<sal_Int32> maAllowedValues;
+
+ LabelPlacementParam(bool bExport, sal_Int32 nDefault) :
+ mbExport(bExport),
+ meDefault(nDefault),
+ maAllowedValues(
+ {
+ css::chart::DataLabelPlacement::OUTSIDE,
+ css::chart::DataLabelPlacement::INSIDE,
+ css::chart::DataLabelPlacement::CENTER,
+ css::chart::DataLabelPlacement::NEAR_ORIGIN,
+ css::chart::DataLabelPlacement::TOP,
+ css::chart::DataLabelPlacement::BOTTOM,
+ css::chart::DataLabelPlacement::LEFT,
+ css::chart::DataLabelPlacement::RIGHT,
+ css::chart::DataLabelPlacement::AVOID_OVERLAP
+ }
+ )
+ {}
+};
+
+const char* toOOXMLPlacement( sal_Int32 nPlacement )
+{
+ switch (nPlacement)
+ {
+ case css::chart::DataLabelPlacement::OUTSIDE: return "outEnd";
+ case css::chart::DataLabelPlacement::INSIDE: return "inEnd";
+ case css::chart::DataLabelPlacement::CENTER: return "ctr";
+ case css::chart::DataLabelPlacement::NEAR_ORIGIN: return "inBase";
+ case css::chart::DataLabelPlacement::TOP: return "t";
+ case css::chart::DataLabelPlacement::BOTTOM: return "b";
+ case css::chart::DataLabelPlacement::LEFT: return "l";
+ case css::chart::DataLabelPlacement::RIGHT: return "r";
+ case css::chart::DataLabelPlacement::CUSTOM:
+ case css::chart::DataLabelPlacement::AVOID_OVERLAP: return "bestFit";
+ default:
+ ;
+ }
+
+ return "outEnd";
+}
+
+OUString getFieldTypeString( const chart2::DataPointCustomLabelFieldType aType )
+{
+ switch (aType)
+ {
+ case chart2::DataPointCustomLabelFieldType_CATEGORYNAME:
+ return "CATEGORYNAME";
+
+ case chart2::DataPointCustomLabelFieldType_SERIESNAME:
+ return "SERIESNAME";
+
+ case chart2::DataPointCustomLabelFieldType_VALUE:
+ return "VALUE";
+
+ case chart2::DataPointCustomLabelFieldType_CELLREF:
+ return "CELLREF";
+
+ case chart2::DataPointCustomLabelFieldType_CELLRANGE:
+ return "CELLRANGE";
+
+ default:
+ break;
+ }
+ return OUString();
+}
+
+void writeRunProperties( ChartExport* pChartExport, Reference<XPropertySet> const & xPropertySet )
+{
+ bool bDummy = false;
+ sal_Int32 nDummy;
+ pChartExport->WriteRunProperties(xPropertySet, false, XML_rPr, true, bDummy, nDummy);
+}
+
+void writeCustomLabel( const FSHelperPtr& pFS, ChartExport* pChartExport,
+ const Sequence<Reference<chart2::XDataPointCustomLabelField>>& rCustomLabelFields,
+ sal_Int32 nLabelIndex, DataLabelsRange& rDLblsRange )
+{
+ pFS->startElement(FSNS(XML_c, XML_tx));
+ pFS->startElement(FSNS(XML_c, XML_rich));
+
+ // TODO: body properties?
+ pFS->singleElement(FSNS(XML_a, XML_bodyPr));
+
+ OUString sFieldType;
+ OUString sContent;
+ pFS->startElement(FSNS(XML_a, XML_p));
+
+ for (auto& rField : rCustomLabelFields)
+ {
+ Reference<XPropertySet> xPropertySet(rField, UNO_QUERY);
+ chart2::DataPointCustomLabelFieldType aType = rField->getFieldType();
+ sFieldType.clear();
+ sContent.clear();
+ bool bNewParagraph = false;
+
+ if (aType == chart2::DataPointCustomLabelFieldType_CELLRANGE &&
+ rField->getDataLabelsRange())
+ {
+ if (rDLblsRange.getRange().isEmpty())
+ rDLblsRange.setRange(rField->getCellRange());
+
+ if (!rDLblsRange.hasLabel(nLabelIndex))
+ rDLblsRange.setLabel(nLabelIndex, rField->getString());
+
+ sContent = "[CELLRANGE]";
+ }
+ else
+ {
+ sContent = rField->getString();
+ }
+
+ if (aType == chart2::DataPointCustomLabelFieldType_NEWLINE)
+ bNewParagraph = true;
+ else if (aType != chart2::DataPointCustomLabelFieldType_TEXT)
+ sFieldType = getFieldTypeString(aType);
+
+ if (bNewParagraph)
+ {
+ pFS->endElement(FSNS(XML_a, XML_p));
+ pFS->startElement(FSNS(XML_a, XML_p));
+ continue;
+ }
+
+ if (sFieldType.isEmpty())
+ {
+ // Normal text run
+ pFS->startElement(FSNS(XML_a, XML_r));
+ writeRunProperties(pChartExport, xPropertySet);
+
+ pFS->startElement(FSNS(XML_a, XML_t));
+ pFS->writeEscaped(sContent);
+ pFS->endElement(FSNS(XML_a, XML_t));
+
+ pFS->endElement(FSNS(XML_a, XML_r));
+ }
+ else
+ {
+ // Field
+ pFS->startElement(FSNS(XML_a, XML_fld), XML_id, rField->getGuid(), XML_type,
+ sFieldType);
+ writeRunProperties(pChartExport, xPropertySet);
+
+ pFS->startElement(FSNS(XML_a, XML_t));
+ pFS->writeEscaped(sContent);
+ pFS->endElement(FSNS(XML_a, XML_t));
+
+ pFS->endElement(FSNS(XML_a, XML_fld));
+ }
+ }
+
+ pFS->endElement(FSNS(XML_a, XML_p));
+ pFS->endElement(FSNS(XML_c, XML_rich));
+ pFS->endElement(FSNS(XML_c, XML_tx));
+}
+
+void writeLabelProperties( const FSHelperPtr& pFS, ChartExport* pChartExport,
+ const uno::Reference<beans::XPropertySet>& xPropSet, const LabelPlacementParam& rLabelParam,
+ sal_Int32 nLabelIndex, DataLabelsRange& rDLblsRange )
+{
+ if (!xPropSet.is())
+ return;
+
+ chart2::DataPointLabel aLabel;
+ Sequence<Reference<chart2::XDataPointCustomLabelField>> aCustomLabelFields;
+ sal_Int32 nLabelBorderWidth = 0;
+ sal_Int32 nLabelBorderColor = 0x00FFFFFF;
+ sal_Int32 nLabelFillColor = -1;
+
+ xPropSet->getPropertyValue("Label") >>= aLabel;
+ xPropSet->getPropertyValue("CustomLabelFields") >>= aCustomLabelFields;
+ xPropSet->getPropertyValue("LabelBorderWidth") >>= nLabelBorderWidth;
+ xPropSet->getPropertyValue("LabelBorderColor") >>= nLabelBorderColor;
+ xPropSet->getPropertyValue("LabelFillColor") >>= nLabelFillColor;
+
+ if (nLabelBorderWidth > 0 || nLabelFillColor != -1)
+ {
+ pFS->startElement(FSNS(XML_c, XML_spPr));
+
+ if (nLabelFillColor != -1)
+ {
+ pFS->startElement(FSNS(XML_a, XML_solidFill));
+
+ OString aStr = OString::number(nLabelFillColor, 16).toAsciiUpperCase();
+ pFS->singleElement(FSNS(XML_a, XML_srgbClr), XML_val, aStr);
+
+ pFS->endElement(FSNS(XML_a, XML_solidFill));
+ }
+
+ if (nLabelBorderWidth > 0)
+ {
+ pFS->startElement(FSNS(XML_a, XML_ln), XML_w,
+ OString::number(convertHmmToEmu(nLabelBorderWidth)));
+
+ if (nLabelBorderColor != -1)
+ {
+ pFS->startElement(FSNS(XML_a, XML_solidFill));
+
+ OString aStr = OString::number(nLabelBorderColor, 16).toAsciiUpperCase();
+ pFS->singleElement(FSNS(XML_a, XML_srgbClr), XML_val, aStr);
+
+ pFS->endElement(FSNS(XML_a, XML_solidFill));
+ }
+
+ pFS->endElement(FSNS(XML_a, XML_ln));
+ }
+
+ pFS->endElement(FSNS(XML_c, XML_spPr));
+ }
+
+ pChartExport->exportTextProps(xPropSet);
+
+ if (aCustomLabelFields.hasElements())
+ writeCustomLabel(pFS, pChartExport, aCustomLabelFields, nLabelIndex, rDLblsRange);
+
+ if (rLabelParam.mbExport)
+ {
+ sal_Int32 nLabelPlacement = rLabelParam.meDefault;
+ if (xPropSet->getPropertyValue("LabelPlacement") >>= nLabelPlacement)
+ {
+ if (!rLabelParam.maAllowedValues.count(nLabelPlacement))
+ nLabelPlacement = rLabelParam.meDefault;
+ pFS->singleElement(FSNS(XML_c, XML_dLblPos), XML_val, toOOXMLPlacement(nLabelPlacement));
+ }
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_showLegendKey), XML_val, ToPsz10(aLabel.ShowLegendSymbol));
+ pFS->singleElement(FSNS(XML_c, XML_showVal), XML_val, ToPsz10(aLabel.ShowNumber));
+ pFS->singleElement(FSNS(XML_c, XML_showCatName), XML_val, ToPsz10(aLabel.ShowCategoryName));
+ pFS->singleElement(FSNS(XML_c, XML_showSerName), XML_val, ToPsz10(aLabel.ShowSeriesName));
+ pFS->singleElement(FSNS(XML_c, XML_showPercent), XML_val, ToPsz10(aLabel.ShowNumberInPercent));
+
+ // Export the text "separator" if exists
+ uno::Any aAny = xPropSet->getPropertyValue("LabelSeparator");
+ if( aAny.hasValue() )
+ {
+ OUString nLabelSeparator;
+ aAny >>= nLabelSeparator;
+ pFS->startElement(FSNS(XML_c, XML_separator));
+ pFS->writeEscaped( nLabelSeparator );
+ pFS->endElement( FSNS( XML_c, XML_separator ) );
+ }
+
+ if (rDLblsRange.hasLabel(nLabelIndex))
+ {
+ pFS->startElement(FSNS(XML_c, XML_extLst));
+ pFS->startElement(FSNS(XML_c, XML_ext), XML_uri,
+ "{CE6537A1-D6FC-4f65-9D91-7224C49458BB}", FSNS(XML_xmlns, XML_c15),
+ pChartExport->GetFB()->getNamespaceURL(OOX_NS(c15)));
+
+ pFS->singleElement(FSNS(XML_c15, XML_showDataLabelsRange), XML_val, "1");
+
+ pFS->endElement(FSNS(XML_c, XML_ext));
+ pFS->endElement(FSNS(XML_c, XML_extLst));
+ }
+}
+
+}
+
+void ChartExport::exportDataLabels(
+ const uno::Reference<chart2::XDataSeries> & xSeries, sal_Int32 nSeriesLength, sal_Int32 eChartType,
+ DataLabelsRange& rDLblsRange)
+{
+ if (!xSeries.is() || nSeriesLength <= 0)
+ return;
+
+ uno::Reference<beans::XPropertySet> xPropSet(xSeries, uno::UNO_QUERY);
+ if (!xPropSet.is())
+ return;
+
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_dLbls));
+
+ bool bLinkedNumFmt = true;
+ if (GetProperty(xPropSet, "LinkNumberFormatToSource"))
+ mAny >>= bLinkedNumFmt;
+
+ chart2::DataPointLabel aLabel;
+ bool bLabelIsNumberFormat = true;
+ if( xPropSet->getPropertyValue("Label") >>= aLabel )
+ bLabelIsNumberFormat = aLabel.ShowNumber;
+
+ if (GetProperty(xPropSet, bLabelIsNumberFormat ? OUString("NumberFormat") : OUString("PercentageNumberFormat")))
+ {
+ sal_Int32 nKey = 0;
+ mAny >>= nKey;
+
+ OUString aNumberFormatString = getNumberFormatCode(nKey);
+
+ pFS->singleElement(FSNS(XML_c, XML_numFmt),
+ XML_formatCode, aNumberFormatString,
+ XML_sourceLinked, ToPsz10(bLinkedNumFmt));
+ }
+
+ uno::Sequence<sal_Int32> aAttrLabelIndices;
+ xPropSet->getPropertyValue("AttributedDataPoints") >>= aAttrLabelIndices;
+
+ // We must not export label placement property when the chart type doesn't
+ // support this option in MS Office, else MS Office would think the file
+ // is corrupt & refuse to open it.
+
+ const chart::TypeGroupInfo& rInfo = chart::GetTypeGroupInfo(static_cast<chart::TypeId>(eChartType));
+ LabelPlacementParam aParam(!mbIs3DChart, rInfo.mnDefLabelPos);
+ switch (eChartType) // diagram chart type
+ {
+ case chart::TYPEID_PIE:
+ if(getChartType() == chart::TYPEID_DOUGHNUT)
+ aParam.mbExport = false;
+ else
+ // All pie charts support label placement.
+ aParam.mbExport = true;
+ break;
+ case chart::TYPEID_AREA:
+ case chart::TYPEID_RADARLINE:
+ case chart::TYPEID_RADARAREA:
+ // These chart types don't support label placement.
+ aParam.mbExport = false;
+ break;
+ case chart::TYPEID_BAR:
+ if (mbStacked || mbPercent)
+ {
+ aParam.maAllowedValues.clear();
+ aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::CENTER);
+ aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::INSIDE);
+ aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::NEAR_ORIGIN);
+ aParam.meDefault = css::chart::DataLabelPlacement::CENTER;
+ }
+ else // Clustered bar chart
+ {
+ aParam.maAllowedValues.clear();
+ aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::CENTER);
+ aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::INSIDE);
+ aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::OUTSIDE);
+ aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::NEAR_ORIGIN);
+ aParam.meDefault = css::chart::DataLabelPlacement::OUTSIDE;
+ }
+ break;
+ default:
+ ;
+ }
+
+ for (const sal_Int32 nIdx : std::as_const(aAttrLabelIndices))
+ {
+ uno::Reference<beans::XPropertySet> xLabelPropSet = xSeries->getDataPointByIndex(nIdx);
+
+ if (!xLabelPropSet.is())
+ continue;
+
+ pFS->startElement(FSNS(XML_c, XML_dLbl));
+ pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nIdx));
+
+ // export custom position of data label
+ if( eChartType != chart::TYPEID_PIE )
+ {
+ chart2::RelativePosition aCustomLabelPosition;
+ if( xLabelPropSet->getPropertyValue("CustomLabelPosition") >>= aCustomLabelPosition )
+ {
+ pFS->startElement(FSNS(XML_c, XML_layout));
+ pFS->startElement(FSNS(XML_c, XML_manualLayout));
+
+ pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(aCustomLabelPosition.Primary));
+ pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(aCustomLabelPosition.Secondary));
+
+ SAL_WARN_IF(aCustomLabelPosition.Anchor != css::drawing::Alignment_TOP_LEFT, "oox", "unsupported anchor position");
+
+ pFS->endElement(FSNS(XML_c, XML_manualLayout));
+ pFS->endElement(FSNS(XML_c, XML_layout));
+ }
+ }
+
+ if( GetProperty(xLabelPropSet, "LinkNumberFormatToSource") )
+ mAny >>= bLinkedNumFmt;
+
+ if( xLabelPropSet->getPropertyValue("Label") >>= aLabel )
+ bLabelIsNumberFormat = aLabel.ShowNumber;
+ else
+ bLabelIsNumberFormat = true;
+
+ if (GetProperty(xLabelPropSet, bLabelIsNumberFormat ? OUString("NumberFormat") : OUString("PercentageNumberFormat")))
+ {
+ sal_Int32 nKey = 0;
+ mAny >>= nKey;
+
+ OUString aNumberFormatString = getNumberFormatCode(nKey);
+
+ pFS->singleElement(FSNS(XML_c, XML_numFmt), XML_formatCode, aNumberFormatString,
+ XML_sourceLinked, ToPsz10(bLinkedNumFmt));
+ }
+
+ // Individual label property that overwrites the baseline.
+ writeLabelProperties(pFS, this, xLabelPropSet, aParam, nIdx, rDLblsRange);
+ pFS->endElement(FSNS(XML_c, XML_dLbl));
+ }
+
+ // Baseline label properties for all labels.
+ writeLabelProperties(pFS, this, xPropSet, aParam, -1, rDLblsRange);
+
+ bool bShowLeaderLines = false;
+ xPropSet->getPropertyValue("ShowCustomLeaderLines") >>= bShowLeaderLines;
+ pFS->singleElement(FSNS(XML_c, XML_showLeaderLines), XML_val, ToPsz10(bShowLeaderLines));
+
+ // Export leader line
+ if( eChartType != chart::TYPEID_PIE )
+ {
+ pFS->startElement(FSNS(XML_c, XML_extLst));
+ pFS->startElement(FSNS(XML_c, XML_ext), XML_uri, "{CE6537A1-D6FC-4f65-9D91-7224C49458BB}", FSNS(XML_xmlns, XML_c15), GetFB()->getNamespaceURL(OOX_NS(c15)));
+ pFS->singleElement(FSNS(XML_c15, XML_showLeaderLines), XML_val, ToPsz10(bShowLeaderLines));
+ pFS->endElement(FSNS(XML_c, XML_ext));
+ pFS->endElement(FSNS(XML_c, XML_extLst));
+ }
+ pFS->endElement(FSNS(XML_c, XML_dLbls));
+}
+
+void ChartExport::exportDataPoints(
+ const uno::Reference< beans::XPropertySet > & xSeriesProperties,
+ sal_Int32 nSeriesLength, sal_Int32 eChartType )
+{
+ uno::Reference< chart2::XDataSeries > xSeries( xSeriesProperties, uno::UNO_QUERY );
+ bool bVaryColorsByPoint = false;
+ Sequence< sal_Int32 > aDataPointSeq;
+ if( xSeriesProperties.is())
+ {
+ Any aAny = xSeriesProperties->getPropertyValue( "AttributedDataPoints" );
+ aAny >>= aDataPointSeq;
+ xSeriesProperties->getPropertyValue( "VaryColorsByPoint" ) >>= bVaryColorsByPoint;
+ }
+
+ const sal_Int32 * pPoints = aDataPointSeq.getConstArray();
+ sal_Int32 nElement;
+ Reference< chart2::XColorScheme > xColorScheme;
+ if( mxNewDiagram.is())
+ xColorScheme.set( mxNewDiagram->getDefaultColorScheme());
+
+ if( bVaryColorsByPoint && xColorScheme.is() )
+ {
+ o3tl::sorted_vector< sal_Int32 > aAttrPointSet;
+ aAttrPointSet.reserve(aDataPointSeq.getLength());
+ for (auto p = pPoints; p < pPoints + aDataPointSeq.getLength(); ++p)
+ aAttrPointSet.insert(*p);
+ const auto aEndIt = aAttrPointSet.end();
+ for( nElement = 0; nElement < nSeriesLength; ++nElement )
+ {
+ uno::Reference< beans::XPropertySet > xPropSet;
+ if( aAttrPointSet.find( nElement ) != aEndIt )
+ {
+ try
+ {
+ xPropSet = SchXMLSeriesHelper::createOldAPIDataPointPropertySet(
+ xSeries, nElement, getModel() );
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION( "oox", "Exception caught during Export of data point" );
+ }
+ }
+ else
+ {
+ // property set only containing the color
+ xPropSet.set( new ColorPropertySet( ColorTransparency, xColorScheme->getColorByIndex( nElement )));
+ }
+
+ if( xPropSet.is() )
+ {
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_dPt));
+ pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nElement));
+
+ switch (eChartType)
+ {
+ case chart::TYPEID_PIE:
+ case chart::TYPEID_DOUGHNUT:
+ {
+ if( xPropSet.is() && GetProperty( xPropSet, "SegmentOffset") )
+ {
+ sal_Int32 nOffset = 0;
+ mAny >>= nOffset;
+ if (nOffset)
+ pFS->singleElement( FSNS( XML_c, XML_explosion ),
+ XML_val, OString::number( nOffset ) );
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ exportShapeProps( xPropSet );
+
+ pFS->endElement( FSNS( XML_c, XML_dPt ) );
+ }
+ }
+ }
+
+ // Export Data Point Property in Charts even if the VaryColors is false
+ if( bVaryColorsByPoint )
+ return;
+
+ o3tl::sorted_vector< sal_Int32 > aAttrPointSet;
+ aAttrPointSet.reserve(aDataPointSeq.getLength());
+ for (auto p = pPoints; p < pPoints + aDataPointSeq.getLength(); ++p)
+ aAttrPointSet.insert(*p);
+ const auto aEndIt = aAttrPointSet.end();
+ for( nElement = 0; nElement < nSeriesLength; ++nElement )
+ {
+ uno::Reference< beans::XPropertySet > xPropSet;
+ if( aAttrPointSet.find( nElement ) != aEndIt )
+ {
+ try
+ {
+ xPropSet = SchXMLSeriesHelper::createOldAPIDataPointPropertySet(
+ xSeries, nElement, getModel() );
+ }
+ catch( const uno::Exception & )
+ {
+ DBG_UNHANDLED_EXCEPTION( "oox", "Exception caught during Export of data point" );
+ }
+ }
+
+ if( xPropSet.is() )
+ {
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_dPt));
+ pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nElement));
+
+ switch( eChartType )
+ {
+ case chart::TYPEID_BUBBLE:
+ case chart::TYPEID_HORBAR:
+ case chart::TYPEID_BAR:
+ pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0");
+ exportShapeProps(xPropSet);
+ break;
+
+ case chart::TYPEID_LINE:
+ case chart::TYPEID_SCATTER:
+ case chart::TYPEID_RADARLINE:
+ exportMarker(xPropSet);
+ break;
+
+ default:
+ exportShapeProps(xPropSet);
+ break;
+ }
+
+ pFS->endElement( FSNS( XML_c, XML_dPt ) );
+ }
+ }
+}
+
+void ChartExport::exportAxesId(bool bPrimaryAxes, bool bCheckCombinedAxes)
+{
+ sal_Int32 nAxisIdx, nAxisIdy;
+ bool bPrimaryAxisExists = false;
+ bool bSecondaryAxisExists = false;
+ // let's check which axis already exists and which axis is attached to the actual dataseries
+ if (maAxes.size() >= 2)
+ {
+ bPrimaryAxisExists = bPrimaryAxes && maAxes[1].nAxisType == AXIS_PRIMARY_Y;
+ bSecondaryAxisExists = !bPrimaryAxes && maAxes[1].nAxisType == AXIS_SECONDARY_Y;
+ }
+ // tdf#114181 keep axes of combined charts
+ if ( bCheckCombinedAxes && ( bPrimaryAxisExists || bSecondaryAxisExists ) )
+ {
+ nAxisIdx = maAxes[0].nAxisId;
+ nAxisIdy = maAxes[1].nAxisId;
+ }
+ else
+ {
+ nAxisIdx = lcl_generateRandomValue();
+ nAxisIdy = lcl_generateRandomValue();
+ AxesType eXAxis = bPrimaryAxes ? AXIS_PRIMARY_X : AXIS_SECONDARY_X;
+ AxesType eYAxis = bPrimaryAxes ? AXIS_PRIMARY_Y : AXIS_SECONDARY_Y;
+ maAxes.emplace_back( eXAxis, nAxisIdx, nAxisIdy );
+ maAxes.emplace_back( eYAxis, nAxisIdy, nAxisIdx );
+ }
+ FSHelperPtr pFS = GetFS();
+ pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdx));
+ pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdy));
+ if (mbHasZAxis)
+ {
+ sal_Int32 nAxisIdz = 0;
+ if( isDeep3dChart() )
+ {
+ nAxisIdz = lcl_generateRandomValue();
+ maAxes.emplace_back( AXIS_PRIMARY_Z, nAxisIdz, nAxisIdy );
+ }
+ pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdz));
+ }
+}
+
+void ChartExport::exportGrouping( bool isBar )
+{
+ FSHelperPtr pFS = GetFS();
+ Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
+ // grouping
+ if( GetProperty( xPropSet, "Stacked" ) )
+ mAny >>= mbStacked;
+ if( GetProperty( xPropSet, "Percent" ) )
+ mAny >>= mbPercent;
+
+ const char* grouping = nullptr;
+ if (mbStacked)
+ grouping = "stacked";
+ else if (mbPercent)
+ grouping = "percentStacked";
+ else
+ {
+ if( isBar && !isDeep3dChart() )
+ {
+ grouping = "clustered";
+ }
+ else
+ grouping = "standard";
+ }
+ pFS->singleElement(FSNS(XML_c, XML_grouping), XML_val, grouping);
+}
+
+void ChartExport::exportTrendlines( const Reference< chart2::XDataSeries >& xSeries )
+{
+ FSHelperPtr pFS = GetFS();
+ Reference< chart2::XRegressionCurveContainer > xRegressionCurveContainer( xSeries, UNO_QUERY );
+ if( !xRegressionCurveContainer.is() )
+ return;
+
+ const Sequence< Reference< chart2::XRegressionCurve > > aRegCurveSeq = xRegressionCurveContainer->getRegressionCurves();
+ for( const Reference< chart2::XRegressionCurve >& xRegCurve : aRegCurveSeq )
+ {
+ if (!xRegCurve.is())
+ continue;
+
+ Reference< XPropertySet > xProperties( xRegCurve , uno::UNO_QUERY );
+
+ OUString aService;
+ Reference< lang::XServiceName > xServiceName( xProperties, UNO_QUERY );
+ if( !xServiceName.is() )
+ continue;
+
+ aService = xServiceName->getServiceName();
+
+ if(aService != "com.sun.star.chart2.LinearRegressionCurve" &&
+ aService != "com.sun.star.chart2.ExponentialRegressionCurve" &&
+ aService != "com.sun.star.chart2.LogarithmicRegressionCurve" &&
+ aService != "com.sun.star.chart2.PotentialRegressionCurve" &&
+ aService != "com.sun.star.chart2.PolynomialRegressionCurve" &&
+ aService != "com.sun.star.chart2.MovingAverageRegressionCurve")
+ continue;
+
+ pFS->startElement(FSNS(XML_c, XML_trendline));
+
+ OUString aName;
+ xProperties->getPropertyValue("CurveName") >>= aName;
+ if(!aName.isEmpty())
+ {
+ pFS->startElement(FSNS(XML_c, XML_name));
+ pFS->writeEscaped(aName);
+ pFS->endElement( FSNS( XML_c, XML_name) );
+ }
+
+ exportShapeProps( xProperties );
+
+ if( aService == "com.sun.star.chart2.LinearRegressionCurve" )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "linear");
+ }
+ else if( aService == "com.sun.star.chart2.ExponentialRegressionCurve" )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "exp");
+ }
+ else if( aService == "com.sun.star.chart2.LogarithmicRegressionCurve" )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "log");
+ }
+ else if( aService == "com.sun.star.chart2.PotentialRegressionCurve" )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "power");
+ }
+ else if( aService == "com.sun.star.chart2.PolynomialRegressionCurve" )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "poly");
+
+ sal_Int32 aDegree = 2;
+ xProperties->getPropertyValue( "PolynomialDegree") >>= aDegree;
+ pFS->singleElement(FSNS(XML_c, XML_order), XML_val, OString::number(aDegree));
+ }
+ else if( aService == "com.sun.star.chart2.MovingAverageRegressionCurve" )
+ {
+ pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "movingAvg");
+
+ sal_Int32 aPeriod = 2;
+ xProperties->getPropertyValue( "MovingAveragePeriod") >>= aPeriod;
+
+ pFS->singleElement(FSNS(XML_c, XML_period), XML_val, OString::number(aPeriod));
+ }
+ else
+ {
+ // should never happen
+ // This would produce invalid OOXML files so we check earlier for the type
+ assert(false);
+ }
+
+ double fExtrapolateForward = 0.0;
+ double fExtrapolateBackward = 0.0;
+
+ xProperties->getPropertyValue("ExtrapolateForward") >>= fExtrapolateForward;
+ xProperties->getPropertyValue("ExtrapolateBackward") >>= fExtrapolateBackward;
+
+ pFS->singleElement( FSNS( XML_c, XML_forward ),
+ XML_val, OString::number(fExtrapolateForward) );
+
+ pFS->singleElement( FSNS( XML_c, XML_backward ),
+ XML_val, OString::number(fExtrapolateBackward) );
+
+ bool bForceIntercept = false;
+ xProperties->getPropertyValue("ForceIntercept") >>= bForceIntercept;
+
+ if (bForceIntercept)
+ {
+ double fInterceptValue = 0.0;
+ xProperties->getPropertyValue("InterceptValue") >>= fInterceptValue;
+
+ pFS->singleElement( FSNS( XML_c, XML_intercept ),
+ XML_val, OString::number(fInterceptValue) );
+ }
+
+ // Equation properties
+ Reference< XPropertySet > xEquationProperties( xRegCurve->getEquationProperties() );
+
+ // Show Equation
+ bool bShowEquation = false;
+ xEquationProperties->getPropertyValue("ShowEquation") >>= bShowEquation;
+
+ // Show R^2
+ bool bShowCorrelationCoefficient = false;
+ xEquationProperties->getPropertyValue("ShowCorrelationCoefficient") >>= bShowCorrelationCoefficient;
+
+ pFS->singleElement( FSNS( XML_c, XML_dispRSqr ),
+ XML_val, ToPsz10(bShowCorrelationCoefficient) );
+
+ pFS->singleElement(FSNS(XML_c, XML_dispEq), XML_val, ToPsz10(bShowEquation));
+
+ pFS->endElement( FSNS( XML_c, XML_trendline ) );
+ }
+}
+
+void ChartExport::exportMarker(const Reference< XPropertySet >& xPropSet)
+{
+ chart2::Symbol aSymbol;
+ if( GetProperty( xPropSet, "Symbol" ) )
+ mAny >>= aSymbol;
+
+ if(aSymbol.Style != chart2::SymbolStyle_STANDARD && aSymbol.Style != chart2::SymbolStyle_NONE)
+ return;
+
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_marker));
+
+ sal_Int32 nSymbol = aSymbol.StandardSymbol;
+ // TODO: more properties support for marker
+ const char* pSymbolType; // no initialization here, to let compiler warn if we have a code path
+ // where it stays uninitialized
+ switch( nSymbol )
+ {
+ case 0:
+ pSymbolType = "square";
+ break;
+ case 1:
+ pSymbolType = "diamond";
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ pSymbolType = "triangle";
+ break;
+ case 8:
+ pSymbolType = "circle";
+ break;
+ case 9:
+ pSymbolType = "star";
+ break;
+ case 10:
+ pSymbolType = "x"; // in MS office 2010 built in symbol marker 'X' is represented as 'x'
+ break;
+ case 11:
+ pSymbolType = "plus";
+ break;
+ case 13:
+ pSymbolType = "dash";
+ break;
+ default:
+ pSymbolType = "square";
+ break;
+ }
+
+ bool bSkipFormatting = false;
+ if (aSymbol.Style == chart2::SymbolStyle_NONE)
+ {
+ bSkipFormatting = true;
+ pSymbolType = "none";
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_symbol), XML_val, pSymbolType);
+
+ if (!bSkipFormatting)
+ {
+ awt::Size aSymbolSize = aSymbol.Size;
+ sal_Int32 nSize = std::max( aSymbolSize.Width, aSymbolSize.Height );
+
+ nSize = nSize/250.0*7.0 + 1; // just guessed based on some test cases,
+ //the value is always 1 less than the actual value.
+ nSize = std::clamp( int(nSize), 2, 72 );
+ pFS->singleElement(FSNS(XML_c, XML_size), XML_val, OString::number(nSize));
+
+ pFS->startElement(FSNS(XML_c, XML_spPr));
+
+ util::Color aColor = aSymbol.FillColor;
+ if (GetProperty(xPropSet, "Color"))
+ mAny >>= aColor;
+
+ if (aColor == -1)
+ {
+ pFS->singleElement(FSNS(XML_a, XML_noFill));
+ }
+ else
+ WriteSolidFill(::Color(ColorTransparency, aColor));
+
+ pFS->endElement( FSNS( XML_c, XML_spPr ) );
+ }
+
+ pFS->endElement( FSNS( XML_c, XML_marker ) );
+}
+
+void ChartExport::exportSmooth()
+{
+ FSHelperPtr pFS = GetFS();
+ Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY );
+ sal_Int32 nSplineType = 0;
+ if( GetProperty( xPropSet, "SplineType" ) )
+ mAny >>= nSplineType;
+ const char* pVal = nSplineType != 0 ? "1" : "0";
+ pFS->singleElement(FSNS(XML_c, XML_smooth), XML_val, pVal);
+}
+
+void ChartExport::exportFirstSliceAng( )
+{
+ FSHelperPtr pFS = GetFS();
+ sal_Int32 nStartingAngle = 0;
+ Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
+ if( GetProperty( xPropSet, "StartingAngle" ) )
+ mAny >>= nStartingAngle;
+
+ // convert to ooxml angle
+ nStartingAngle = (450 - nStartingAngle ) % 360;
+ pFS->singleElement(FSNS(XML_c, XML_firstSliceAng), XML_val, OString::number(nStartingAngle));
+}
+
+namespace {
+
+const char* getErrorBarStyle(sal_Int32 nErrorBarStyle)
+{
+ switch(nErrorBarStyle)
+ {
+ case cssc::ErrorBarStyle::NONE:
+ return nullptr;
+ case cssc::ErrorBarStyle::VARIANCE:
+ break;
+ case cssc::ErrorBarStyle::STANDARD_DEVIATION:
+ return "stdDev";
+ case cssc::ErrorBarStyle::ABSOLUTE:
+ return "fixedVal";
+ case cssc::ErrorBarStyle::RELATIVE:
+ return "percentage";
+ case cssc::ErrorBarStyle::ERROR_MARGIN:
+ break;
+ case cssc::ErrorBarStyle::STANDARD_ERROR:
+ return "stdErr";
+ case cssc::ErrorBarStyle::FROM_DATA:
+ return "cust";
+ default:
+ assert(false && "can't happen");
+ }
+ return nullptr;
+}
+
+Reference< chart2::data::XDataSequence> getLabeledSequence(
+ const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > >& aSequences,
+ bool bPositive )
+{
+ OUString aDirection;
+ if(bPositive)
+ aDirection = "positive";
+ else
+ aDirection = "negative";
+
+ for( const auto& rSequence : aSequences )
+ {
+ if( rSequence.is())
+ {
+ uno::Reference< chart2::data::XDataSequence > xSequence( rSequence->getValues());
+ uno::Reference< beans::XPropertySet > xSeqProp( xSequence, uno::UNO_QUERY_THROW );
+ OUString aRole;
+ if( ( xSeqProp->getPropertyValue( "Role" ) >>= aRole ) &&
+ aRole.match( "error-bars" ) && aRole.indexOf(aDirection) >= 0 )
+ {
+ return xSequence;
+ }
+ }
+ }
+
+ return Reference< chart2::data::XDataSequence > ();
+}
+
+}
+
+void ChartExport::exportErrorBar(const Reference< XPropertySet>& xErrorBarProps, bool bYError)
+{
+ sal_Int32 nErrorBarStyle = cssc::ErrorBarStyle::NONE;
+ xErrorBarProps->getPropertyValue("ErrorBarStyle") >>= nErrorBarStyle;
+ const char* pErrorBarStyle = getErrorBarStyle(nErrorBarStyle);
+ if(!pErrorBarStyle)
+ return;
+
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_errBars));
+ pFS->singleElement(FSNS(XML_c, XML_errDir), XML_val, bYError ? "y" : "x");
+ bool bPositive = false, bNegative = false;
+ xErrorBarProps->getPropertyValue("ShowPositiveError") >>= bPositive;
+ xErrorBarProps->getPropertyValue("ShowNegativeError") >>= bNegative;
+ const char* pErrBarType;
+ if(bPositive && bNegative)
+ pErrBarType = "both";
+ else if(bPositive)
+ pErrBarType = "plus";
+ else if(bNegative)
+ pErrBarType = "minus";
+ else
+ {
+ // what the hell should we do now?
+ // at least this makes the file valid
+ pErrBarType = "both";
+ }
+ pFS->singleElement(FSNS(XML_c, XML_errBarType), XML_val, pErrBarType);
+ pFS->singleElement(FSNS(XML_c, XML_errValType), XML_val, pErrorBarStyle);
+ pFS->singleElement(FSNS(XML_c, XML_noEndCap), XML_val, "0");
+ if(nErrorBarStyle == cssc::ErrorBarStyle::FROM_DATA)
+ {
+ uno::Reference< chart2::data::XDataSource > xDataSource(xErrorBarProps, uno::UNO_QUERY);
+ Sequence< Reference < chart2::data::XLabeledDataSequence > > aSequences =
+ xDataSource->getDataSequences();
+
+ if(bPositive)
+ {
+ exportSeriesValues(getLabeledSequence(aSequences, true), XML_plus);
+ }
+
+ if(bNegative)
+ {
+ exportSeriesValues(getLabeledSequence(aSequences, false), XML_minus);
+ }
+ }
+ else
+ {
+ double nVal = 0.0;
+ if(nErrorBarStyle == cssc::ErrorBarStyle::STANDARD_DEVIATION)
+ {
+ xErrorBarProps->getPropertyValue("Weight") >>= nVal;
+ }
+ else
+ {
+ if(bPositive)
+ xErrorBarProps->getPropertyValue("PositiveError") >>= nVal;
+ else
+ xErrorBarProps->getPropertyValue("NegativeError") >>= nVal;
+ }
+
+ pFS->singleElement(FSNS(XML_c, XML_val), XML_val, OString::number(nVal));
+ }
+
+ exportShapeProps( xErrorBarProps );
+
+ pFS->endElement( FSNS( XML_c, XML_errBars) );
+}
+
+void ChartExport::exportView3D()
+{
+ Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
+ if( !xPropSet.is() )
+ return;
+ FSHelperPtr pFS = GetFS();
+ pFS->startElement(FSNS(XML_c, XML_view3D));
+ sal_Int32 eChartType = getChartType( );
+ // rotX
+ if( GetProperty( xPropSet, "RotationHorizontal" ) )
+ {
+ sal_Int32 nRotationX = 0;
+ mAny >>= nRotationX;
+ if( nRotationX < 0 )
+ {
+ if(eChartType == chart::TYPEID_PIE)
+ {
+ /* In OOXML we get value in 0..90 range for pie chart X rotation , whereas we expect it to be in -90..90 range,
+ so we convert that during import. It is modified in View3DConverter::convertFromModel()
+ here we convert it back to 0..90 as we received in import */
+ nRotationX += 90; // X rotation (map Chart2 [-179,180] to OOXML [0..90])
+ }
+ else
+ nRotationX += 360; // X rotation (map Chart2 [-179,180] to OOXML [-90..90])
+ }
+ pFS->singleElement(FSNS(XML_c, XML_rotX), XML_val, OString::number(nRotationX));
+ }
+ // rotY
+ if( GetProperty( xPropSet, "RotationVertical" ) )
+ {
+ // Y rotation (map Chart2 [-179,180] to OOXML [0..359])
+ if( eChartType == chart::TYPEID_PIE && GetProperty( xPropSet, "StartingAngle" ) )
+ {
+ // Y rotation used as 'first pie slice angle' in 3D pie charts
+ sal_Int32 nStartingAngle=0;
+ mAny >>= nStartingAngle;
+ // convert to ooxml angle
+ nStartingAngle = (450 - nStartingAngle ) % 360;
+ pFS->singleElement(FSNS(XML_c, XML_rotY), XML_val, OString::number(nStartingAngle));
+ }
+ else
+ {
+ sal_Int32 nRotationY = 0;
+ mAny >>= nRotationY;
+ // Y rotation (map Chart2 [-179,180] to OOXML [0..359])
+ if( nRotationY < 0 )
+ nRotationY += 360;
+ pFS->singleElement(FSNS(XML_c, XML_rotY), XML_val, OString::number(nRotationY));
+ }
+ }
+ // rAngAx
+ if( GetProperty( xPropSet, "RightAngledAxes" ) )
+ {
+ bool bRightAngled = false;
+ mAny >>= bRightAngled;
+ const char* sRightAngled = bRightAngled ? "1":"0";
+ pFS->singleElement(FSNS(XML_c, XML_rAngAx), XML_val, sRightAngled);
+ }
+ // perspective
+ if( GetProperty( xPropSet, "Perspective" ) )
+ {
+ sal_Int32 nPerspective = 0;
+ mAny >>= nPerspective;
+ // map Chart2 [0,100] to OOXML [0..200]
+ nPerspective *= 2;
+ pFS->singleElement(FSNS(XML_c, XML_perspective), XML_val, OString::number(nPerspective));
+ }
+ pFS->endElement( FSNS( XML_c, XML_view3D ) );
+}
+
+bool ChartExport::isDeep3dChart()
+{
+ bool isDeep = false;
+ if( mbIs3DChart )
+ {
+ Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY);
+ if( GetProperty( xPropSet, "Deep" ) )
+ mAny >>= isDeep;
+ }
+ return isDeep;
+}
+
+OUString ChartExport::getNumberFormatCode(sal_Int32 nKey) const
+{
+ /* XXX if this was called more than one or two times per export the two
+ * SvNumberFormatter instances and NfKeywordTable should be member
+ * variables and initialized only once. */
+
+ OUString aCode("General"); // init with fallback
+ uno::Reference<util::XNumberFormatsSupplier> xNumberFormatsSupplier(mxChartModel, uno::UNO_QUERY_THROW);
+ SvNumberFormatsSupplierObj* pSupplierObj = comphelper::getFromUnoTunnel<SvNumberFormatsSupplierObj>( xNumberFormatsSupplier);
+ if (!pSupplierObj)
+ return aCode;
+
+ SvNumberFormatter* pNumberFormatter = pSupplierObj->GetNumberFormatter();
+ if (!pNumberFormatter)
+ return aCode;
+
+ SvNumberFormatter aTempFormatter( comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US);
+ NfKeywordTable aKeywords;
+ aTempFormatter.FillKeywordTableForExcel( aKeywords);
+ aCode = pNumberFormatter->GetFormatStringForExcel( nKey, aKeywords, aTempFormatter);
+
+ return aCode;
+}
+
+}// oox
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx
new file mode 100644
index 000000000..85207b7c7
--- /dev/null
+++ b/oox/source/export/drawingml.cxx
@@ -0,0 +1,6125 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_features.h>
+
+#include <config_folders.h>
+#include <rtl/bootstrap.hxx>
+#include <sal/log.hxx>
+#include <oox/core/xmlfilterbase.hxx>
+#include <oox/export/drawingml.hxx>
+#include <oox/export/utils.hxx>
+#include <oox/helper/propertyset.hxx>
+#include <oox/drawingml/color.hxx>
+#include <drawingml/fillproperties.hxx>
+#include <drawingml/textparagraph.hxx>
+#include <oox/token/namespaces.hxx>
+#include <oox/token/properties.hxx>
+#include <oox/token/relationship.hxx>
+#include <oox/token/tokens.hxx>
+#include <oox/drawingml/drawingmltypes.hxx>
+#include <svtools/unitconv.hxx>
+#include <sax/fastattribs.hxx>
+#include <tools/diagnose_ex.h>
+#include <comphelper/processfactory.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/range/b2drange.hxx>
+
+#include <numeric>
+#include <string_view>
+
+#include <com/sun/star/awt/CharSet.hpp>
+#include <com/sun/star/awt/FontDescriptor.hpp>
+#include <com/sun/star/awt/FontSlant.hpp>
+#include <com/sun/star/awt/FontStrikeout.hpp>
+#include <com/sun/star/awt/FontWeight.hpp>
+#include <com/sun/star/awt/FontUnderline.hpp>
+#include <com/sun/star/awt/Gradient.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/XPropertyState.hpp>
+#include <com/sun/star/beans/XPropertySetInfo.hpp>
+#include <com/sun/star/container/XEnumerationAccess.hpp>
+#include <com/sun/star/container/XIndexAccess.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/drawing/BitmapMode.hpp>
+#include <com/sun/star/drawing/ColorMode.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp>
+#include <com/sun/star/drawing/Hatch.hpp>
+#include <com/sun/star/drawing/LineDash.hpp>
+#include <com/sun/star/drawing/LineJoint.hpp>
+#include <com/sun/star/drawing/LineStyle.hpp>
+#include <com/sun/star/drawing/TextFitToSizeType.hpp>
+#include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
+#include <com/sun/star/drawing/TextVerticalAdjust.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/drawing/XShapes.hpp>
+#include <com/sun/star/drawing/FillStyle.hpp>
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/i18n/BreakIterator.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/style/LineSpacing.hpp>
+#include <com/sun/star/style/LineSpacingMode.hpp>
+#include <com/sun/star/text/WritingMode.hpp>
+#include <com/sun/star/text/WritingMode2.hpp>
+#include <com/sun/star/text/GraphicCrop.hpp>
+#include <com/sun/star/text/XText.hpp>
+#include <com/sun/star/text/XTextColumns.hpp>
+#include <com/sun/star/text/XTextContent.hpp>
+#include <com/sun/star/text/XTextField.hpp>
+#include <com/sun/star/text/XTextRange.hpp>
+#include <com/sun/star/text/XTextFrame.hpp>
+#include <com/sun/star/style/CaseMap.hpp>
+#include <com/sun/star/xml/dom/XNodeList.hpp>
+#include <com/sun/star/xml/sax/Writer.hpp>
+#include <com/sun/star/xml/sax/XSAXSerializable.hpp>
+#include <com/sun/star/container/XNamed.hpp>
+#include <com/sun/star/drawing/XDrawPages.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/drawing/RectanglePoint.hpp>
+
+#include <comphelper/propertyvalue.hxx>
+#include <comphelper/random.hxx>
+#include <comphelper/seqstream.hxx>
+#include <comphelper/storagehelper.hxx>
+#include <comphelper/xmltools.hxx>
+#include <o3tl/any.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/string_view.hxx>
+#include <tools/stream.hxx>
+#include <tools/UnitConversion.hxx>
+#include <unotools/fontdefs.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/svapp.hxx>
+#include <rtl/strbuf.hxx>
+#include <filter/msfilter/escherex.hxx>
+#include <filter/msfilter/util.hxx>
+#include <editeng/outlobj.hxx>
+#include <editeng/svxenum.hxx>
+#include <editeng/unonames.hxx>
+#include <editeng/unoprnms.hxx>
+#include <editeng/flditem.hxx>
+#include <editeng/escapementitem.hxx>
+#include <editeng/unonrule.hxx>
+#include <svx/svdoashp.hxx>
+#include <svx/svdomedia.hxx>
+#include <svx/unoshape.hxx>
+#include <svx/EnhancedCustomShape2d.hxx>
+#include <drawingml/presetgeometrynames.hxx>
+
+using namespace ::css;
+using namespace ::css::beans;
+using namespace ::css::drawing;
+using namespace ::css::i18n;
+using namespace ::css::style;
+using namespace ::css::text;
+using namespace ::css::uno;
+using namespace ::css::container;
+using namespace ::com::sun::star::drawing::EnhancedCustomShapeSegmentCommand;
+
+using ::css::io::XOutputStream;
+using ::sax_fastparser::FSHelperPtr;
+using ::sax_fastparser::FastSerializerHelper;
+
+namespace
+{
+/// Extracts start or end alpha information from a transparency gradient.
+sal_Int32 GetAlphaFromTransparenceGradient(const awt::Gradient& rGradient, bool bStart)
+{
+ // Our alpha is a gray color value.
+ sal_uInt8 nRed = ::Color(ColorTransparency, bStart ? rGradient.StartColor : rGradient.EndColor).GetRed();
+ // drawingML alpha is a percentage on a 0..100000 scale.
+ return (255 - nRed) * oox::drawingml::MAX_PERCENT / 255;
+}
+
+const char* g_aPredefinedClrNames[] = {
+ "dk1",
+ "lt1",
+ "dk2",
+ "lt2",
+ "accent1",
+ "accent2",
+ "accent3",
+ "accent4",
+ "accent5",
+ "accent6",
+ "hlink",
+ "folHlink",
+};
+}
+
+namespace oox::drawingml {
+
+URLTransformer::~URLTransformer()
+{
+}
+
+OUString URLTransformer::getTransformedString(const OUString& rString) const
+{
+ return rString;
+}
+
+bool URLTransformer::isExternalURL(const OUString& rURL) const
+{
+ bool bExternal = true;
+ if (rURL.startsWith("#"))
+ bExternal = false;
+ return bExternal;
+}
+
+static css::uno::Any getLineDash( const css::uno::Reference<css::frame::XModel>& xModel, const OUString& rDashName )
+ {
+ css::uno::Reference<css::lang::XMultiServiceFactory> xFact(xModel, css::uno::UNO_QUERY);
+ css::uno::Reference<css::container::XNameAccess> xNameAccess(
+ xFact->createInstance("com.sun.star.drawing.DashTable"),
+ css::uno::UNO_QUERY );
+ if(xNameAccess.is())
+ {
+ if (!xNameAccess->hasByName(rDashName))
+ return css::uno::Any();
+
+ return xNameAccess->getByName(rDashName);
+ }
+
+ return css::uno::Any();
+ }
+
+namespace
+{
+void WriteGradientPath(const awt::Gradient& rGradient, const FSHelperPtr& pFS, const bool bCircle)
+{
+ pFS->startElementNS(XML_a, XML_path, XML_path, bCircle ? "circle" : "rect");
+
+ // Write the focus rectangle. Work with the focus point, and assume
+ // that it extends 50% in all directions. The below
+ // left/top/right/bottom values are percentages, where 0 means the
+ // edge of the tile rectangle and 100% means the center of it.
+ rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList(
+ sax_fastparser::FastSerializerHelper::createAttrList());
+ sal_Int32 nLeftPercent = rGradient.XOffset;
+ pAttributeList->add(XML_l, OString::number(nLeftPercent * PER_PERCENT));
+ sal_Int32 nTopPercent = rGradient.YOffset;
+ pAttributeList->add(XML_t, OString::number(nTopPercent * PER_PERCENT));
+ sal_Int32 nRightPercent = 100 - rGradient.XOffset;
+ pAttributeList->add(XML_r, OString::number(nRightPercent * PER_PERCENT));
+ sal_Int32 nBottomPercent = 100 - rGradient.YOffset;
+ pAttributeList->add(XML_b, OString::number(nBottomPercent * PER_PERCENT));
+ pFS->singleElementNS(XML_a, XML_fillToRect, pAttributeList);
+
+ pFS->endElementNS(XML_a, XML_path);
+}
+}
+
+// not thread safe
+std::stack<sal_Int32> DrawingML::mnImageCounter;
+std::stack<sal_Int32> DrawingML::mnWdpImageCounter;
+std::stack<std::map<OUString, OUString>> DrawingML::maWdpCache;
+sal_Int32 DrawingML::mnDrawingMLCount = 0;
+sal_Int32 DrawingML::mnVmlCount = 0;
+std::stack<std::unordered_map<BitmapChecksum, OUString>> DrawingML::maExportGraphics;
+
+sal_Int16 DrawingML::GetScriptType(const OUString& rStr)
+{
+ if (rStr.getLength() > 0)
+ {
+ static Reference<css::i18n::XBreakIterator> xBreakIterator =
+ css::i18n::BreakIterator::create(comphelper::getProcessComponentContext());
+
+ sal_Int16 nScriptType = xBreakIterator->getScriptType(rStr, 0);
+
+ if (nScriptType == css::i18n::ScriptType::WEAK)
+ {
+ sal_Int32 nPos = xBreakIterator->nextScript(rStr, 0, nScriptType);
+ if (nPos < rStr.getLength())
+ nScriptType = xBreakIterator->getScriptType(rStr, nPos);
+
+ }
+
+ if (nScriptType != css::i18n::ScriptType::WEAK)
+ return nScriptType;
+ }
+
+ return css::i18n::ScriptType::LATIN;
+}
+
+void DrawingML::ResetMlCounters()
+{
+ mnDrawingMLCount = 0;
+ mnVmlCount = 0;
+}
+
+void DrawingML::PushExportGraphics()
+{
+ mnImageCounter.push(1);
+ maExportGraphics.emplace();
+
+ mnWdpImageCounter.push(1);
+ maWdpCache.emplace();
+}
+
+void DrawingML::PopExportGraphics()
+{
+ mnImageCounter.pop();
+ maExportGraphics.pop();
+
+ mnWdpImageCounter.pop();
+ maWdpCache.pop();
+}
+
+bool DrawingML::GetProperty( const Reference< XPropertySet >& rXPropertySet, const OUString& aName )
+{
+ try
+ {
+ mAny = rXPropertySet->getPropertyValue(aName);
+ if (mAny.hasValue())
+ return true;
+ }
+ catch( const Exception& )
+ {
+ /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
+ }
+ return false;
+}
+
+bool DrawingML::GetPropertyAndState( const Reference< XPropertySet >& rXPropertySet, const Reference< XPropertyState >& rXPropertyState, const OUString& aName, PropertyState& eState )
+{
+ try
+ {
+ mAny = rXPropertySet->getPropertyValue(aName);
+ if (mAny.hasValue())
+ {
+ eState = rXPropertyState->getPropertyState(aName);
+ return true;
+ }
+ }
+ catch( const Exception& )
+ {
+ /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
+ }
+ return false;
+}
+
+namespace
+{
+/// Gets hexa value of color on string format.
+OString getColorStr(const ::Color nColor)
+{
+ // Transparency is a separate element.
+ OString sColor = OString::number(sal_uInt32(nColor) & 0x00FFFFFF, 16);
+ if (sColor.getLength() < 6)
+ {
+ OStringBuffer sBuf("0");
+ int remains = 5 - sColor.getLength();
+
+ while (remains > 0)
+ {
+ sBuf.append("0");
+ remains--;
+ }
+
+ sBuf.append(sColor);
+
+ sColor = sBuf.getStr();
+ }
+ return sColor;
+}
+}
+
+void DrawingML::WriteColor( ::Color nColor, sal_Int32 nAlpha )
+{
+ const auto sColor = getColorStr(nColor);
+ if( nAlpha < MAX_PERCENT )
+ {
+ mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
+ mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
+ mpFS->endElementNS( XML_a, XML_srgbClr );
+
+ }
+ else
+ {
+ mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor);
+ }
+}
+
+void DrawingML::WriteColor( const OUString& sColorSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
+{
+ // prevent writing a tag with empty val attribute
+ if( sColorSchemeName.isEmpty() )
+ return;
+
+ if( aTransformations.hasElements() )
+ {
+ mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
+ WriteColorTransformations( aTransformations, nAlpha );
+ mpFS->endElementNS( XML_a, XML_schemeClr );
+ }
+ else if(nAlpha < MAX_PERCENT)
+ {
+ mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
+ mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
+ mpFS->endElementNS( XML_a, XML_schemeClr );
+ }
+ else
+ {
+ mpFS->singleElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
+ }
+}
+
+void DrawingML::WriteColor( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
+{
+ const auto sColor = getColorStr(nColor);
+ if( aTransformations.hasElements() )
+ {
+ mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
+ WriteColorTransformations(aTransformations, nAlpha);
+ mpFS->endElementNS(XML_a, XML_srgbClr);
+ }
+ else if(nAlpha < MAX_PERCENT)
+ {
+ mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
+ mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
+ mpFS->endElementNS(XML_a, XML_srgbClr);
+ }
+ else
+ {
+ mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor);
+ }
+}
+
+void DrawingML::WriteColorTransformations( const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
+{
+ for( const auto& rTransformation : aTransformations )
+ {
+ sal_Int32 nToken = Color::getColorTransformationToken( rTransformation.Name );
+ if( nToken != XML_TOKEN_INVALID && rTransformation.Value.hasValue() )
+ {
+ if(nToken == XML_alpha && nAlpha < MAX_PERCENT)
+ {
+ mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nAlpha));
+ }
+ else
+ {
+ sal_Int32 nValue = rTransformation.Value.get<sal_Int32>();
+ mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nValue));
+ }
+ }
+ }
+}
+
+bool DrawingML::WriteCharColor(const css::uno::Reference<css::beans::XPropertySet>& xPropertySet)
+{
+ if (!xPropertySet->getPropertySetInfo()->hasPropertyByName("CharColorTheme"))
+ {
+ return false;
+ }
+
+ sal_Int32 nCharColorTheme = -1;
+ xPropertySet->getPropertyValue("CharColorTheme") >>= nCharColorTheme;
+ if (nCharColorTheme < 0 || nCharColorTheme > 11)
+ {
+ return false;
+ }
+
+ const char* pColorName = g_aPredefinedClrNames[nCharColorTheme];
+
+ sal_Int32 nCharColorTintOrShade{};
+ xPropertySet->getPropertyValue("CharColorTintOrShade") >>= nCharColorTintOrShade;
+ if (nCharColorTintOrShade != 0)
+ {
+ return false;
+ }
+
+ mpFS->startElementNS(XML_a, XML_solidFill);
+ mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, pColorName);
+
+ sal_Int32 nCharColorLumMod{};
+ xPropertySet->getPropertyValue("CharColorLumMod") >>= nCharColorLumMod;
+ if (nCharColorLumMod != 10000)
+ {
+ mpFS->singleElementNS(XML_a, XML_lumMod, XML_val, OString::number(nCharColorLumMod * 10));
+ }
+
+ sal_Int32 nCharColorLumOff{};
+ xPropertySet->getPropertyValue("CharColorLumOff") >>= nCharColorLumOff;
+ if (nCharColorLumOff != 0)
+ {
+ mpFS->singleElementNS(XML_a, XML_lumOff, XML_val, OString::number(nCharColorLumOff * 10));
+ }
+
+ mpFS->endElementNS(XML_a, XML_schemeClr);
+ mpFS->endElementNS(XML_a, XML_solidFill);
+
+ return true;
+}
+
+void DrawingML::WriteSolidFill( ::Color nColor, sal_Int32 nAlpha )
+{
+ mpFS->startElementNS(XML_a, XML_solidFill);
+ WriteColor( nColor, nAlpha );
+ mpFS->endElementNS( XML_a, XML_solidFill );
+}
+
+void DrawingML::WriteSolidFill( const OUString& sSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
+{
+ mpFS->startElementNS(XML_a, XML_solidFill);
+ WriteColor( sSchemeName, aTransformations, nAlpha );
+ mpFS->endElementNS( XML_a, XML_solidFill );
+}
+
+void DrawingML::WriteSolidFill( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
+{
+ mpFS->startElementNS(XML_a, XML_solidFill);
+ WriteColor(nColor, aTransformations, nAlpha);
+ mpFS->endElementNS(XML_a, XML_solidFill);
+}
+
+void DrawingML::WriteSolidFill( const Reference< XPropertySet >& rXPropSet )
+{
+ // get fill color
+ if ( !GetProperty( rXPropSet, "FillColor" ) )
+ return;
+ sal_uInt32 nFillColor = mAny.get<sal_uInt32>();
+
+ // get InteropGrabBag and search the relevant attributes
+ OUString sColorFillScheme;
+ sal_uInt32 nOriginalColor = 0;
+ Sequence< PropertyValue > aStyleProperties, aTransformations;
+ if ( GetProperty( rXPropSet, "InteropGrabBag" ) )
+ {
+ Sequence< PropertyValue > aGrabBag;
+ mAny >>= aGrabBag;
+ for( const auto& rProp : std::as_const(aGrabBag) )
+ {
+ if( rProp.Name == "SpPrSolidFillSchemeClr" )
+ rProp.Value >>= sColorFillScheme;
+ else if( rProp.Name == "OriginalSolidFillClr" )
+ rProp.Value >>= nOriginalColor;
+ else if( rProp.Name == "StyleFillRef" )
+ rProp.Value >>= aStyleProperties;
+ else if( rProp.Name == "SpPrSolidFillSchemeClrTransformations" )
+ rProp.Value >>= aTransformations;
+ }
+ }
+
+ sal_Int32 nAlpha = MAX_PERCENT;
+ if( GetProperty( rXPropSet, "FillTransparence" ) )
+ {
+ sal_Int32 nTransparency = 0;
+ mAny >>= nTransparency;
+ // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
+ nAlpha = (MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
+ }
+
+ // OOXML has no separate transparence gradient but uses transparency in the gradient stops.
+ // So we merge transparency and color and use gradient fill in such case.
+ awt::Gradient aTransparenceGradient;
+ bool bNeedGradientFill(false);
+ if (GetProperty(rXPropSet, "FillTransparenceGradient"))
+ {
+ mAny >>= aTransparenceGradient;
+ if (aTransparenceGradient.StartColor != aTransparenceGradient.EndColor)
+ bNeedGradientFill = true;
+ else if (aTransparenceGradient.StartColor != 0)
+ nAlpha = GetAlphaFromTransparenceGradient(aTransparenceGradient, true);
+ }
+
+ // write XML
+ if (bNeedGradientFill)
+ {
+ awt::Gradient aPseudoColorGradient;
+ aPseudoColorGradient.XOffset = aTransparenceGradient.XOffset;
+ aPseudoColorGradient.YOffset = aTransparenceGradient.YOffset;
+ aPseudoColorGradient.StartIntensity = 100;
+ aPseudoColorGradient.EndIntensity = 100;
+ aPseudoColorGradient.Angle = aTransparenceGradient.Angle;
+ aPseudoColorGradient.Border = aTransparenceGradient.Border;
+ aPseudoColorGradient.Style = aTransparenceGradient.Style;
+ aPseudoColorGradient.StartColor = nFillColor;
+ aPseudoColorGradient.EndColor = nFillColor;
+ aPseudoColorGradient.StepCount = aTransparenceGradient.StepCount;
+ mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
+ WriteGradientFill(aPseudoColorGradient, aTransparenceGradient);
+ mpFS->endElementNS( XML_a, XML_gradFill );
+ }
+ else if ( nFillColor != nOriginalColor )
+ {
+ // the user has set a different color for the shape
+ if (!WriteFillColor(rXPropSet))
+ {
+ WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha);
+ }
+ }
+ else if ( !sColorFillScheme.isEmpty() )
+ {
+ // the shape had a scheme color and the user didn't change it
+ WriteSolidFill( sColorFillScheme, aTransformations, nAlpha );
+ }
+ else
+ {
+ // the shape had a custom color and the user didn't change it
+ // tdf#124013
+ WriteSolidFill( ::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha );
+ }
+}
+
+bool DrawingML::WriteFillColor(const uno::Reference<beans::XPropertySet>& xPropertySet)
+{
+ if (!xPropertySet->getPropertySetInfo()->hasPropertyByName("FillColorTheme"))
+ {
+ return false;
+ }
+
+ sal_Int32 nFillColorTheme = -1;
+ xPropertySet->getPropertyValue("FillColorTheme") >>= nFillColorTheme;
+ if (nFillColorTheme < 0 || nFillColorTheme > 11)
+ {
+ return false;
+ }
+
+ const char* pColorName = g_aPredefinedClrNames[nFillColorTheme];
+
+ mpFS->startElementNS(XML_a, XML_solidFill);
+ mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, pColorName);
+
+ sal_Int32 nFillColorLumMod{};
+ xPropertySet->getPropertyValue("FillColorLumMod") >>= nFillColorLumMod;
+ if (nFillColorLumMod != 10000)
+ {
+ mpFS->singleElementNS(XML_a, XML_lumMod, XML_val, OString::number(nFillColorLumMod * 10));
+ }
+
+ sal_Int32 nFillColorLumOff{};
+ xPropertySet->getPropertyValue("FillColorLumOff") >>= nFillColorLumOff;
+ if (nFillColorLumOff != 0)
+ {
+ mpFS->singleElementNS(XML_a, XML_lumOff, XML_val, OString::number(nFillColorLumOff * 10));
+ }
+
+ mpFS->endElementNS(XML_a, XML_schemeClr);
+ mpFS->endElementNS(XML_a, XML_solidFill);
+
+ return true;
+}
+
+void DrawingML::WriteGradientStop(sal_uInt16 nStop, ::Color nColor, sal_Int32 nAlpha)
+{
+ mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nStop * 1000));
+ WriteColor(nColor, nAlpha);
+ mpFS->endElementNS( XML_a, XML_gs );
+}
+
+::Color DrawingML::ColorWithIntensity( sal_uInt32 nColor, sal_uInt32 nIntensity )
+{
+ return ::Color(ColorTransparency, ( ( ( nColor & 0xff ) * nIntensity ) / 100 )
+ | ( ( ( ( ( nColor & 0xff00 ) >> 8 ) * nIntensity ) / 100 ) << 8 )
+ | ( ( ( ( ( nColor & 0xff0000 ) >> 8 ) * nIntensity ) / 100 ) << 8 ));
+}
+
+bool DrawingML::EqualGradients( awt::Gradient aGradient1, awt::Gradient aGradient2 )
+{
+ return aGradient1.Style == aGradient2.Style &&
+ aGradient1.StartColor == aGradient2.StartColor &&
+ aGradient1.EndColor == aGradient2.EndColor &&
+ aGradient1.Angle == aGradient2.Angle &&
+ aGradient1.Border == aGradient2.Border &&
+ aGradient1.XOffset == aGradient2.XOffset &&
+ aGradient1.YOffset == aGradient2.YOffset &&
+ aGradient1.StartIntensity == aGradient2.StartIntensity &&
+ aGradient1.EndIntensity == aGradient2.EndIntensity &&
+ aGradient1.StepCount == aGradient2.StepCount;
+}
+
+void DrawingML::WriteGradientFill( const Reference< XPropertySet >& rXPropSet )
+{
+ awt::Gradient aGradient;
+ if (!GetProperty(rXPropSet, "FillGradient"))
+ return;
+
+ aGradient = *o3tl::doAccess<awt::Gradient>(mAny);
+
+ // get InteropGrabBag and search the relevant attributes
+ awt::Gradient aOriginalGradient;
+ Sequence< PropertyValue > aGradientStops;
+ if ( GetProperty( rXPropSet, "InteropGrabBag" ) )
+ {
+ Sequence< PropertyValue > aGrabBag;
+ mAny >>= aGrabBag;
+ for( const auto& rProp : std::as_const(aGrabBag) )
+ if( rProp.Name == "GradFillDefinition" )
+ rProp.Value >>= aGradientStops;
+ else if( rProp.Name == "OriginalGradFill" )
+ rProp.Value >>= aOriginalGradient;
+ }
+
+ // check if an ooxml gradient had been imported and if the user has modified it
+ // Gradient grab-bag depends on theme grab-bag, which is implemented
+ // only for DOCX.
+ if( EqualGradients( aOriginalGradient, aGradient ) && GetDocumentType() == DOCUMENT_DOCX)
+ {
+ // If we have no gradient stops that means original gradient were defined by a theme.
+ if( aGradientStops.hasElements() )
+ {
+ mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
+ WriteGrabBagGradientFill(aGradientStops, aGradient);
+ mpFS->endElementNS( XML_a, XML_gradFill );
+ }
+ }
+ else
+ {
+ awt::Gradient aTransparenceGradient;
+ mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
+ OUString sFillTransparenceGradientName;
+ if (GetProperty(rXPropSet, "FillTransparenceGradientName")
+ && (mAny >>= sFillTransparenceGradientName)
+ && !sFillTransparenceGradientName.isEmpty())
+ {
+ if (GetProperty(rXPropSet, "FillTransparenceGradient"))
+ aTransparenceGradient = *o3tl::doAccess<awt::Gradient>(mAny);
+ }
+ else if (GetProperty(rXPropSet, "FillTransparence"))
+ {
+ // currently only StartColor and EndColor are evaluated in WriteGradientFill()
+ sal_Int32 nTransparency = 0;
+ mAny >>= nTransparency;
+ // convert percent to gray color
+ nTransparency = nTransparency * 255/100;
+ const sal_Int32 aGrayColor = static_cast<sal_Int32>( nTransparency | nTransparency << 8 | nTransparency << 16 );
+ aTransparenceGradient.StartColor = aGrayColor;
+ aTransparenceGradient.EndColor = aGrayColor;
+ }
+ WriteGradientFill(aGradient, aTransparenceGradient);
+ mpFS->endElementNS(XML_a, XML_gradFill);
+ }
+}
+
+void DrawingML::WriteGrabBagGradientFill( const Sequence< PropertyValue >& aGradientStops, awt::Gradient rGradient )
+{
+ // write back the original gradient
+ mpFS->startElementNS(XML_a, XML_gsLst);
+
+ // get original stops and write them
+ for( const auto& rGradientStop : aGradientStops )
+ {
+ Sequence< PropertyValue > aGradientStop;
+ rGradientStop.Value >>= aGradientStop;
+
+ // get values
+ OUString sSchemeClr;
+ double nPos = 0;
+ sal_Int16 nTransparency = 0;
+ ::Color nRgbClr;
+ Sequence< PropertyValue > aTransformations;
+ for( const auto& rProp : std::as_const(aGradientStop) )
+ {
+ if( rProp.Name == "SchemeClr" )
+ rProp.Value >>= sSchemeClr;
+ else if( rProp.Name == "RgbClr" )
+ rProp.Value >>= nRgbClr;
+ else if( rProp.Name == "Pos" )
+ rProp.Value >>= nPos;
+ else if( rProp.Name == "Transparency" )
+ rProp.Value >>= nTransparency;
+ else if( rProp.Name == "Transformations" )
+ rProp.Value >>= aTransformations;
+ }
+ // write stop
+ mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nPos * 100000.0).getStr());
+ if( sSchemeClr.isEmpty() )
+ {
+ // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
+ sal_Int32 nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency );
+ WriteColor( nRgbClr, nAlpha );
+ }
+ else
+ {
+ WriteColor( sSchemeClr, aTransformations );
+ }
+ mpFS->endElementNS( XML_a, XML_gs );
+ }
+ mpFS->endElementNS( XML_a, XML_gsLst );
+
+ switch (rGradient.Style)
+ {
+ default:
+ mpFS->singleElementNS(
+ XML_a, XML_lin, XML_ang,
+ OString::number(((3600 - rGradient.Angle + 900) * 6000) % 21600000));
+ break;
+ case awt::GradientStyle_RADIAL:
+ WriteGradientPath(rGradient, mpFS, true);
+ break;
+ }
+}
+
+void DrawingML::WriteGradientFill(awt::Gradient rGradient, awt::Gradient rTransparenceGradient,
+ const uno::Reference<beans::XPropertySet>& rXPropSet)
+{
+ sal_Int32 nStartAlpha;
+ sal_Int32 nEndAlpha;
+ if( rXPropSet.is() && GetProperty(rXPropSet, "FillTransparence") )
+ {
+ sal_Int32 nTransparency = 0;
+ mAny >>= nTransparency;
+ nStartAlpha = nEndAlpha = (MAX_PERCENT - (PER_PERCENT * nTransparency));
+ }
+ else
+ {
+ nStartAlpha = GetAlphaFromTransparenceGradient(rTransparenceGradient, true);
+ nEndAlpha = GetAlphaFromTransparenceGradient(rTransparenceGradient, false);
+ }
+ switch( rGradient.Style )
+ {
+ default:
+ case awt::GradientStyle_LINEAR:
+ {
+ mpFS->startElementNS(XML_a, XML_gsLst);
+ WriteGradientStop(rGradient.Border, ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
+ nStartAlpha);
+ WriteGradientStop(100, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
+ nEndAlpha);
+ mpFS->endElementNS( XML_a, XML_gsLst );
+ mpFS->singleElementNS(
+ XML_a, XML_lin, XML_ang,
+ OString::number(((3600 - rGradient.Angle + 900) * 6000) % 21600000));
+ break;
+ }
+
+ case awt::GradientStyle_AXIAL:
+ {
+ mpFS->startElementNS(XML_a, XML_gsLst);
+ WriteGradientStop(0, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
+ nEndAlpha);
+ if (rGradient.Border > 0 && rGradient.Border < 100)
+ {
+ WriteGradientStop(rGradient.Border/2,
+ ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
+ nEndAlpha);
+ }
+ WriteGradientStop(50, ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
+ nStartAlpha);
+ if (rGradient.Border > 0 && rGradient.Border < 100)
+ {
+ WriteGradientStop(100 - rGradient.Border/2,
+ ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
+ nEndAlpha);
+ }
+ WriteGradientStop(100, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
+ nEndAlpha);
+ mpFS->endElementNS(XML_a, XML_gsLst);
+ mpFS->singleElementNS(
+ XML_a, XML_lin, XML_ang,
+ OString::number(((3600 - rGradient.Angle + 900) * 6000) % 21600000));
+ break;
+ }
+
+ case awt::GradientStyle_RADIAL:
+ case awt::GradientStyle_ELLIPTICAL:
+ case awt::GradientStyle_RECT:
+ case awt::GradientStyle_SQUARE:
+ {
+ mpFS->startElementNS(XML_a, XML_gsLst);
+ WriteGradientStop(0, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
+ nEndAlpha);
+ if (rGradient.Border > 0 && rGradient.Border < 100)
+ {
+ // Map border to an additional gradient stop, which has the
+ // same color as the final stop.
+ WriteGradientStop(100 - rGradient.Border,
+ ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
+ nStartAlpha);
+ }
+ WriteGradientStop(100,
+ ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
+ nStartAlpha);
+ mpFS->endElementNS(XML_a, XML_gsLst);
+
+ WriteGradientPath(rGradient, mpFS, rGradient.Style == awt::GradientStyle_RADIAL || rGradient.Style == awt::GradientStyle_ELLIPTICAL);
+ break;
+ }
+ }
+}
+
+void DrawingML::WriteLineArrow( const Reference< XPropertySet >& rXPropSet, bool bLineStart )
+{
+ ESCHER_LineEnd eLineEnd;
+ sal_Int32 nArrowLength;
+ sal_Int32 nArrowWidth;
+
+ if ( !EscherPropertyContainer::GetLineArrow( bLineStart, rXPropSet, eLineEnd, nArrowLength, nArrowWidth ) )
+ return;
+
+ const char* len;
+ const char* type;
+ const char* width;
+
+ switch( nArrowLength )
+ {
+ case ESCHER_LineShortArrow:
+ len = "sm";
+ break;
+ default:
+ case ESCHER_LineMediumLenArrow:
+ len = "med";
+ break;
+ case ESCHER_LineLongArrow:
+ len = "lg";
+ break;
+ }
+
+ switch( eLineEnd )
+ {
+ default:
+ case ESCHER_LineNoEnd:
+ type = "none";
+ break;
+ case ESCHER_LineArrowEnd:
+ type = "triangle";
+ break;
+ case ESCHER_LineArrowStealthEnd:
+ type = "stealth";
+ break;
+ case ESCHER_LineArrowDiamondEnd:
+ type = "diamond";
+ break;
+ case ESCHER_LineArrowOvalEnd:
+ type = "oval";
+ break;
+ case ESCHER_LineArrowOpenEnd:
+ type = "arrow";
+ break;
+ }
+
+ switch( nArrowWidth )
+ {
+ case ESCHER_LineNarrowArrow:
+ width = "sm";
+ break;
+ default:
+ case ESCHER_LineMediumWidthArrow:
+ width = "med";
+ break;
+ case ESCHER_LineWideArrow:
+ width = "lg";
+ break;
+ }
+
+ mpFS->singleElementNS( XML_a, bLineStart ? XML_headEnd : XML_tailEnd,
+ XML_len, len,
+ XML_type, type,
+ XML_w, width );
+}
+
+void DrawingML::WriteOutline( const Reference<XPropertySet>& rXPropSet, Reference< frame::XModel > const & xModel )
+{
+ drawing::LineStyle aLineStyle( drawing::LineStyle_NONE );
+ if (GetProperty(rXPropSet, "LineStyle"))
+ mAny >>= aLineStyle;
+
+ const LineCap aLineCap = GetProperty(rXPropSet, "LineCap") ? mAny.get<drawing::LineCap>() : LineCap_BUTT;
+
+ sal_uInt32 nLineWidth = 0;
+ sal_uInt32 nEmuLineWidth = 0;
+ ::Color nColor;
+ sal_Int32 nColorAlpha = MAX_PERCENT;
+ bool bColorSet = false;
+ const char* cap = nullptr;
+ drawing::LineDash aLineDash;
+ bool bDashSet = false;
+ bool bNoFill = false;
+
+
+ // get InteropGrabBag and search the relevant attributes
+ OUString sColorFillScheme;
+ ::Color aResolvedColorFillScheme;
+
+ ::Color nOriginalColor;
+ ::Color nStyleColor;
+ sal_uInt32 nStyleLineWidth = 0;
+
+ Sequence<PropertyValue> aStyleProperties;
+ Sequence<PropertyValue> aTransformations;
+
+ drawing::LineStyle aStyleLineStyle(drawing::LineStyle_NONE);
+ drawing::LineJoint aStyleLineJoint(drawing::LineJoint_NONE);
+
+ if (GetProperty(rXPropSet, "InteropGrabBag"))
+ {
+ Sequence<PropertyValue> aGrabBag;
+ mAny >>= aGrabBag;
+
+ for (const auto& rProp : std::as_const(aGrabBag))
+ {
+ if( rProp.Name == "SpPrLnSolidFillSchemeClr" )
+ rProp.Value >>= sColorFillScheme;
+ if( rProp.Name == "SpPrLnSolidFillResolvedSchemeClr" )
+ rProp.Value >>= aResolvedColorFillScheme;
+ else if( rProp.Name == "OriginalLnSolidFillClr" )
+ rProp.Value >>= nOriginalColor;
+ else if( rProp.Name == "StyleLnRef" )
+ rProp.Value >>= aStyleProperties;
+ else if( rProp.Name == "SpPrLnSolidFillSchemeClrTransformations" )
+ rProp.Value >>= aTransformations;
+ else if( rProp.Name == "EmuLineWidth" )
+ rProp.Value >>= nEmuLineWidth;
+ }
+ for (const auto& rStyleProp : std::as_const(aStyleProperties))
+ {
+ if( rStyleProp.Name == "Color" )
+ rStyleProp.Value >>= nStyleColor;
+ else if( rStyleProp.Name == "LineStyle" )
+ rStyleProp.Value >>= aStyleLineStyle;
+ else if( rStyleProp.Name == "LineJoint" )
+ rStyleProp.Value >>= aStyleLineJoint;
+ else if( rStyleProp.Name == "LineWidth" )
+ rStyleProp.Value >>= nStyleLineWidth;
+ }
+ }
+
+ if (GetProperty(rXPropSet, "LineWidth"))
+ mAny >>= nLineWidth;
+
+ switch (aLineStyle)
+ {
+ case drawing::LineStyle_NONE:
+ bNoFill = true;
+ break;
+ case drawing::LineStyle_DASH:
+ if (GetProperty(rXPropSet, "LineDash"))
+ {
+ aLineDash = mAny.get<drawing::LineDash>();
+ //this query is good for shapes, but in the case of charts it returns 0 values
+ if (aLineDash.Dots == 0 && aLineDash.DotLen == 0 && aLineDash.Dashes == 0 && aLineDash.DashLen == 0 && aLineDash.Distance == 0) {
+ OUString aLineDashName;
+ if (GetProperty(rXPropSet, "LineDashName"))
+ mAny >>= aLineDashName;
+ if (!aLineDashName.isEmpty() && xModel) {
+ css::uno::Any aAny = getLineDash(xModel, aLineDashName);
+ aAny >>= aLineDash;
+ }
+ }
+ }
+ else
+ {
+ //export the linestyle of chart wall (plot area) and chart page
+ OUString aLineDashName;
+ if (GetProperty(rXPropSet, "LineDashName"))
+ mAny >>= aLineDashName;
+ if (!aLineDashName.isEmpty() && xModel) {
+ css::uno::Any aAny = getLineDash(xModel, aLineDashName);
+ aAny >>= aLineDash;
+ }
+ }
+ bDashSet = true;
+ if (aLineDash.Style == DashStyle_ROUND || aLineDash.Style == DashStyle_ROUNDRELATIVE)
+ {
+ cap = "rnd";
+ }
+
+ SAL_INFO("oox.shape", "dash dots: " << aLineDash.Dots << " dashes: " << aLineDash.Dashes
+ << " dotlen: " << aLineDash.DotLen << " dashlen: " << aLineDash.DashLen << " distance: " << aLineDash.Distance);
+
+ [[fallthrough]];
+ case drawing::LineStyle_SOLID:
+ default:
+ if (GetProperty(rXPropSet, "LineColor"))
+ {
+ nColor = ::Color(ColorTransparency, mAny.get<sal_uInt32>() & 0xffffff);
+ bColorSet = true;
+ }
+ if (GetProperty(rXPropSet, "LineTransparence"))
+ {
+ nColorAlpha = MAX_PERCENT - (mAny.get<sal_Int16>() * PER_PERCENT);
+ }
+ if (aLineCap == LineCap_ROUND)
+ cap = "rnd";
+ else if (aLineCap == LineCap_SQUARE)
+ cap = "sq";
+ break;
+ }
+
+ // if the line-width was not modified after importing then the original EMU value will be exported to avoid unexpected conversion (rounding) error
+ if (nEmuLineWidth == 0 || static_cast<sal_uInt32>(oox::drawingml::convertEmuToHmm(nEmuLineWidth)) != nLineWidth)
+ nEmuLineWidth = oox::drawingml::convertHmmToEmu(nLineWidth);
+ mpFS->startElementNS( XML_a, XML_ln,
+ XML_cap, cap,
+ XML_w, sax_fastparser::UseIf(OString::number(nEmuLineWidth),
+ nLineWidth == 0 || (nLineWidth > 1 && nStyleLineWidth != nLineWidth)) );
+
+ if( bColorSet )
+ {
+ if( nColor != nOriginalColor )
+ {
+ // the user has set a different color for the line
+ WriteSolidFill( nColor, nColorAlpha );
+ }
+ else if( !sColorFillScheme.isEmpty() )
+ {
+ // the line had a scheme color and the user didn't change it
+ WriteSolidFill( aResolvedColorFillScheme, aTransformations );
+ }
+ else
+ {
+ WriteSolidFill( nColor, nColorAlpha );
+ }
+ }
+
+ if( bDashSet && aStyleLineStyle != drawing::LineStyle_DASH )
+ {
+ // Try to detect if it might come from ms preset line style import.
+ // MS Office styles are always relative, both binary and OOXML.
+ // "dot" is always the first dash and "dash" the second one. All OOXML presets linestyles
+ // start with the longer one. Definitions are in OOXML part 1, 20.1.10.49
+ // The tests are strict, for to not catch styles from standard.sod (as of Aug 2019).
+ bool bIsConverted = false;
+
+ bool bIsRelative(aLineDash.Style == DashStyle_RECTRELATIVE || aLineDash.Style == DashStyle_ROUNDRELATIVE);
+ if ( bIsRelative && aLineDash.Dots == 1)
+ { // The length were tweaked on import in case of prstDash. Revert it here.
+ sal_uInt32 nDotLen = aLineDash.DotLen;
+ sal_uInt32 nDashLen = aLineDash.DashLen;
+ sal_uInt32 nDistance = aLineDash.Distance;
+ if (aLineCap != LineCap_BUTT && nDistance >= 99)
+ {
+ nDistance -= 99;
+ nDotLen += 99;
+ if (nDashLen > 0)
+ nDashLen += 99;
+ }
+ // LO uses length 0 for 100%, if the attribute is missing in ODF.
+ // Other applications might write 100%. Make is unique for the conditions.
+ if (nDotLen == 0)
+ nDotLen = 100;
+ if (nDashLen == 0 && aLineDash.Dashes > 0)
+ nDashLen = 100;
+ bIsConverted = true;
+ if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
+ {
+ mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dot");
+ }
+ else if (nDotLen == 400 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
+ {
+ mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dash");
+ }
+ else if (nDotLen == 400 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
+ {
+ mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dashDot");
+ }
+ else if (nDotLen == 800 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
+ {
+ mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDash");
+ }
+ else if (nDotLen == 800 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
+ {
+ mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDot");
+ }
+ else if (nDotLen == 800 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 300)
+ {
+ mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDotDot");
+ }
+ else if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
+ {
+ mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDot");
+ }
+ else if (nDotLen == 300 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
+ {
+ mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDash");
+ }
+ else if (nDotLen == 300 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 100)
+ {
+ mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDot");
+ }
+ else if (nDotLen == 300 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 100)
+ {
+ mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDotDot");
+ }
+ else
+ bIsConverted = false;
+ }
+ // Do not map our own line styles to OOXML prstDash values, because custDash gives better results.
+ if (!bIsConverted)
+ {
+ mpFS->startElementNS(XML_a, XML_custDash);
+ // In case of hairline we would need the current pixel size. Instead use a reasonable
+ // ersatz for it. The value is the same as SMALLEST_DASH_WIDTH in xattr.cxx.
+ // (And it makes sure fLineWidth is not zero in below division.)
+ double fLineWidth = nLineWidth > 0 ? nLineWidth : 26.95;
+ int i;
+ double fSp = bIsRelative ? aLineDash.Distance : aLineDash.Distance * 100.0 / fLineWidth;
+ // LO uses line width, in case Distance is zero. MS Office would use a space of zero length.
+ // So set 100% explicitly.
+ if (aLineDash.Distance <= 0)
+ fSp = 100.0;
+ // In case of custDash, round caps are included in dash length in MS Office. Square caps are added
+ // to dash length, same as in ODF. Change the length values accordingly.
+ if (aLineCap == LineCap_ROUND && fSp > 99.0)
+ fSp -= 99.0;
+
+ if (aLineDash.Dots > 0)
+ {
+ double fD = bIsRelative ? aLineDash.DotLen : aLineDash.DotLen * 100.0 / fLineWidth;
+ // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended.
+ if (aLineDash.DotLen == 0)
+ fD = 100.0;
+ // Tweak dash length, see above.
+ if (aLineCap == LineCap_ROUND && fSp > 99.0)
+ fD += 99.0;
+
+ for( i = 0; i < aLineDash.Dots; i ++ )
+ {
+ mpFS->singleElementNS( XML_a, XML_ds,
+ XML_d , write1000thOfAPercent(fD),
+ XML_sp, write1000thOfAPercent(fSp) );
+ }
+ }
+ if ( aLineDash.Dashes > 0 )
+ {
+ double fD = bIsRelative ? aLineDash.DashLen : aLineDash.DashLen * 100.0 / fLineWidth;
+ // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended.
+ if (aLineDash.DashLen == 0)
+ fD = 100.0;
+ // Tweak dash length, see above.
+ if (aLineCap == LineCap_ROUND && fSp > 99.0)
+ fD += 99.0;
+
+ for( i = 0; i < aLineDash.Dashes; i ++ )
+ {
+ mpFS->singleElementNS( XML_a , XML_ds,
+ XML_d , write1000thOfAPercent(fD),
+ XML_sp, write1000thOfAPercent(fSp) );
+ }
+ }
+
+ SAL_WARN_IF(nLineWidth <= 0,
+ "oox.shape", "while writing outline - custom dash - line width was < 0 : " << nLineWidth);
+ SAL_WARN_IF(aLineDash.Dashes < 0,
+ "oox.shape", "while writing outline - custom dash - number of dashes was < 0 : " << aLineDash.Dashes);
+ SAL_WARN_IF(aLineDash.Dashes > 0 && aLineDash.DashLen <= 0,
+ "oox.shape", "while writing outline - custom dash - dash length was < 0 : " << aLineDash.DashLen);
+ SAL_WARN_IF(aLineDash.Dots < 0,
+ "oox.shape", "while writing outline - custom dash - number of dots was < 0 : " << aLineDash.Dots);
+ SAL_WARN_IF(aLineDash.Dots > 0 && aLineDash.DotLen <= 0,
+ "oox.shape", "while writing outline - custom dash - dot length was < 0 : " << aLineDash.DotLen);
+ SAL_WARN_IF(aLineDash.Distance <= 0,
+ "oox.shape", "while writing outline - custom dash - distance was < 0 : " << aLineDash.Distance);
+
+ mpFS->endElementNS( XML_a, XML_custDash );
+ }
+ }
+
+ if (!bNoFill && nLineWidth > 1 && GetProperty(rXPropSet, "LineJoint"))
+ {
+ LineJoint eLineJoint = mAny.get<LineJoint>();
+
+ if( aStyleLineJoint == LineJoint_NONE || aStyleLineJoint != eLineJoint )
+ {
+ // style-defined line joint does not exist, or is different from the shape's joint
+ switch( eLineJoint )
+ {
+ case LineJoint_NONE:
+ case LineJoint_BEVEL:
+ mpFS->singleElementNS(XML_a, XML_bevel);
+ break;
+ default:
+ case LineJoint_MIDDLE:
+ case LineJoint_MITER:
+ mpFS->singleElementNS(XML_a, XML_miter);
+ break;
+ case LineJoint_ROUND:
+ mpFS->singleElementNS(XML_a, XML_round);
+ break;
+ }
+ }
+ }
+
+ if( !bNoFill )
+ {
+ WriteLineArrow( rXPropSet, true );
+ WriteLineArrow( rXPropSet, false );
+ }
+ else
+ {
+ mpFS->singleElementNS(XML_a, XML_noFill);
+ }
+
+ mpFS->endElementNS( XML_a, XML_ln );
+}
+
+const char* DrawingML::GetComponentDir() const
+{
+ switch ( meDocumentType )
+ {
+ case DOCUMENT_DOCX: return "word";
+ case DOCUMENT_PPTX: return "ppt";
+ case DOCUMENT_XLSX: return "xl";
+ }
+
+ return "unknown";
+}
+
+const char* DrawingML::GetRelationCompPrefix() const
+{
+ switch ( meDocumentType )
+ {
+ case DOCUMENT_DOCX: return "";
+ case DOCUMENT_PPTX:
+ case DOCUMENT_XLSX: return "../";
+ }
+
+ return "unknown";
+}
+
+OUString DrawingML::WriteImage( const Graphic& rGraphic , bool bRelPathToMedia )
+{
+ GfxLink aLink = rGraphic.GetGfxLink ();
+ BitmapChecksum aChecksum = rGraphic.GetChecksum();
+ OUString sMediaType;
+ const char* pExtension = "";
+ OUString sRelId;
+ OUString sPath;
+
+ // tdf#74670 tdf#91286 Save image only once
+ if (!maExportGraphics.empty())
+ {
+ auto aIterator = maExportGraphics.top().find(aChecksum);
+ if (aIterator != maExportGraphics.top().end())
+ sPath = aIterator->second;
+ }
+
+ if (sPath.isEmpty())
+ {
+ SvMemoryStream aStream;
+ const void* aData = aLink.GetData();
+ std::size_t nDataSize = aLink.GetDataSize();
+
+ switch (aLink.GetType())
+ {
+ case GfxLinkType::NativeGif:
+ sMediaType = "image/gif";
+ pExtension = ".gif";
+ break;
+
+ // #i15508# added BMP type for better exports
+ // export not yet active, so adding for reference (not checked)
+ case GfxLinkType::NativeBmp:
+ sMediaType = "image/bmp";
+ pExtension = ".bmp";
+ break;
+
+ case GfxLinkType::NativeJpg:
+ sMediaType = "image/jpeg";
+ pExtension = ".jpeg";
+ break;
+ case GfxLinkType::NativePng:
+ sMediaType = "image/png";
+ pExtension = ".png";
+ break;
+ case GfxLinkType::NativeTif:
+ sMediaType = "image/tiff";
+ pExtension = ".tif";
+ break;
+ case GfxLinkType::NativeWmf:
+ sMediaType = "image/x-wmf";
+ pExtension = ".wmf";
+ break;
+ case GfxLinkType::NativeMet:
+ sMediaType = "image/x-met";
+ pExtension = ".met";
+ break;
+ case GfxLinkType::NativePct:
+ sMediaType = "image/x-pict";
+ pExtension = ".pct";
+ break;
+ case GfxLinkType::NativeMov:
+ sMediaType = "application/movie";
+ pExtension = ".MOV";
+ break;
+ default:
+ {
+ GraphicType aType = rGraphic.GetType();
+ if (aType == GraphicType::Bitmap || aType == GraphicType::GdiMetafile)
+ {
+ if (aType == GraphicType::Bitmap)
+ {
+ (void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::PNG);
+ sMediaType = "image/png";
+ pExtension = ".png";
+ }
+ else
+ {
+ (void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::EMF);
+ sMediaType = "image/x-emf";
+ pExtension = ".emf";
+ }
+ }
+ else
+ {
+ SAL_WARN("oox.shape", "unhandled graphic type " << static_cast<int>(aType));
+ /*Earlier, even in case of unhandled graphic types we were
+ proceeding to write the image, which would eventually
+ write an empty image with a zero size, and return a valid
+ relationID, which is incorrect.
+ */
+ return sRelId;
+ }
+
+ aData = aStream.GetData();
+ nDataSize = aStream.GetEndOfData();
+ break;
+ }
+ }
+
+ Reference<XOutputStream> xOutStream = mpFB->openFragmentStream(
+ OUStringBuffer()
+ .appendAscii(GetComponentDir())
+ .append("/media/image" + OUString::number(mnImageCounter.top()))
+ .appendAscii(pExtension)
+ .makeStringAndClear(),
+ sMediaType);
+ xOutStream->writeBytes(Sequence<sal_Int8>(static_cast<const sal_Int8*>(aData), nDataSize));
+ xOutStream->closeOutput();
+
+ const OString sRelPathToMedia = "media/image";
+ OString sRelationCompPrefix;
+ if (bRelPathToMedia)
+ sRelationCompPrefix = "../";
+ else
+ sRelationCompPrefix = GetRelationCompPrefix();
+ sPath = OUStringBuffer()
+ .appendAscii(sRelationCompPrefix.getStr())
+ .appendAscii(sRelPathToMedia.getStr())
+ .append(static_cast<sal_Int32>(mnImageCounter.top()++))
+ .appendAscii(pExtension)
+ .makeStringAndClear();
+
+ if (!maExportGraphics.empty())
+ maExportGraphics.top()[aChecksum] = sPath;
+ }
+
+ sRelId = mpFB->addRelation( mpFS->getOutputStream(),
+ oox::getRelationship(Relationship::IMAGE),
+ sPath );
+
+ return sRelId;
+}
+
+void DrawingML::WriteMediaNonVisualProperties(const css::uno::Reference<css::drawing::XShape>& xShape)
+{
+ SdrMediaObj* pMediaObj = dynamic_cast<SdrMediaObj*>(SdrObject::getSdrObjectFromXShape(xShape));
+ if (!pMediaObj)
+ return;
+
+ // extension
+ OUString aExtension;
+ const OUString& rURL(pMediaObj->getURL());
+ int nLastDot = rURL.lastIndexOf('.');
+ if (nLastDot >= 0)
+ aExtension = rURL.copy(nLastDot);
+
+ bool bEmbed = rURL.startsWith("vnd.sun.star.Package:");
+ Relationship eMediaType = Relationship::VIDEO;
+
+ // mime type
+#if HAVE_FEATURE_AVMEDIA
+ OUString aMimeType(pMediaObj->getMediaProperties().getMimeType());
+#else
+ OUString aMimeType("none");
+#endif
+ if (aMimeType == "application/vnd.sun.star.media")
+ {
+ // try to set something better
+ // TODO fix the importer to actually set the mimetype on import
+ if (aExtension.equalsIgnoreAsciiCase(".avi"))
+ aMimeType = "video/x-msvideo";
+ else if (aExtension.equalsIgnoreAsciiCase(".flv"))
+ aMimeType = "video/x-flv";
+ else if (aExtension.equalsIgnoreAsciiCase(".mp4"))
+ aMimeType = "video/mp4";
+ else if (aExtension.equalsIgnoreAsciiCase(".mov"))
+ aMimeType = "video/quicktime";
+ else if (aExtension.equalsIgnoreAsciiCase(".ogv"))
+ aMimeType = "video/ogg";
+ else if (aExtension.equalsIgnoreAsciiCase(".wmv"))
+ aMimeType = "video/x-ms-wmv";
+ else if (aExtension.equalsIgnoreAsciiCase(".wav"))
+ {
+ aMimeType = "audio/x-wav";
+ eMediaType = Relationship::AUDIO;
+ }
+ else if (aExtension.equalsIgnoreAsciiCase(".m4a"))
+ {
+ aMimeType = "audio/mp4";
+ eMediaType = Relationship::AUDIO;
+ }
+ else if (aExtension.equalsIgnoreAsciiCase(".mp3"))
+ {
+ aMimeType = "audio/mp3";
+ eMediaType = Relationship::AUDIO;
+ }
+ }
+
+ OUString aVideoFileRelId;
+ OUString aMediaRelId;
+
+ if (bEmbed)
+ {
+ // copy the video stream
+ Reference<XOutputStream> xOutStream = mpFB->openFragmentStream(OUStringBuffer()
+ .appendAscii(GetComponentDir())
+ .append("/media/media" +
+ OUString::number(mnImageCounter.top()) +
+ aExtension)
+ .makeStringAndClear(),
+ aMimeType);
+
+ uno::Reference<io::XInputStream> xInputStream(pMediaObj->GetInputStream());
+ comphelper::OStorageHelper::CopyInputToOutput(xInputStream, xOutStream);
+
+ xOutStream->closeOutput();
+
+ // create the relation
+ OUString aPath = OUStringBuffer().appendAscii(GetRelationCompPrefix())
+ .append("media/media" + OUString::number(mnImageCounter.top()++) + aExtension)
+ .makeStringAndClear();
+ aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), aPath);
+ aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), aPath);
+ }
+ else
+ {
+ aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), rURL, true);
+ aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), rURL, true);
+ }
+
+ GetFS()->startElementNS(XML_p, XML_nvPr);
+
+ GetFS()->singleElementNS(XML_a, eMediaType == Relationship::VIDEO ? XML_videoFile : XML_audioFile,
+ FSNS(XML_r, XML_link), aVideoFileRelId);
+
+ GetFS()->startElementNS(XML_p, XML_extLst);
+ // media extensions; google this ID for details
+ GetFS()->startElementNS(XML_p, XML_ext, XML_uri, "{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}");
+
+ GetFS()->singleElementNS(XML_p14, XML_media,
+ bEmbed? FSNS(XML_r, XML_embed): FSNS(XML_r, XML_link), aMediaRelId);
+
+ GetFS()->endElementNS(XML_p, XML_ext);
+ GetFS()->endElementNS(XML_p, XML_extLst);
+
+ GetFS()->endElementNS(XML_p, XML_nvPr);
+}
+
+void DrawingML::WriteImageBrightnessContrastTransparence(uno::Reference<beans::XPropertySet> const & rXPropSet)
+{
+ sal_Int16 nBright = 0;
+ sal_Int32 nContrast = 0;
+ sal_Int32 nTransparence = 0;
+
+ if (GetProperty(rXPropSet, "AdjustLuminance"))
+ nBright = mAny.get<sal_Int16>();
+ if (GetProperty(rXPropSet, "AdjustContrast"))
+ nContrast = mAny.get<sal_Int32>();
+ // Used for shapes with picture fill
+ if (GetProperty(rXPropSet, "FillTransparence"))
+ nTransparence = mAny.get<sal_Int32>();
+ // Used for pictures
+ if (nTransparence == 0 && GetProperty(rXPropSet, "Transparency"))
+ nTransparence = static_cast<sal_Int32>(mAny.get<sal_Int16>());
+
+ if (GetProperty(rXPropSet, "GraphicColorMode"))
+ {
+ drawing::ColorMode aColorMode;
+ mAny >>= aColorMode;
+ if (aColorMode == drawing::ColorMode_GREYS)
+ mpFS->singleElementNS(XML_a, XML_grayscl);
+ else if (aColorMode == drawing::ColorMode_MONO)
+ //black/white has a 0,5 threshold in LibreOffice
+ mpFS->singleElementNS(XML_a, XML_biLevel, XML_thresh, OString::number(50000));
+ else if (aColorMode == drawing::ColorMode_WATERMARK)
+ {
+ //map watermark with mso washout
+ nBright = 70;
+ nContrast = -70;
+ }
+ }
+
+
+ if (nBright || nContrast)
+ {
+ mpFS->singleElementNS(XML_a, XML_lum,
+ XML_bright, sax_fastparser::UseIf(OString::number(nBright * 1000), nBright != 0),
+ XML_contrast, sax_fastparser::UseIf(OString::number(nContrast * 1000), nContrast != 0));
+ }
+
+ if (nTransparence)
+ {
+ sal_Int32 nAlphaMod = (100 - nTransparence ) * PER_PERCENT;
+ mpFS->singleElementNS(XML_a, XML_alphaModFix, XML_amt, OString::number(nAlphaMod));
+ }
+}
+
+OUString DrawingML::WriteXGraphicBlip(uno::Reference<beans::XPropertySet> const & rXPropSet,
+ uno::Reference<graphic::XGraphic> const & rxGraphic,
+ bool bRelPathToMedia)
+{
+ OUString sRelId;
+
+ if (!rxGraphic.is())
+ return sRelId;
+
+ Graphic aGraphic(rxGraphic);
+ sRelId = WriteImage(aGraphic, bRelPathToMedia);
+
+ mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId);
+
+ WriteImageBrightnessContrastTransparence(rXPropSet);
+
+ WriteArtisticEffect(rXPropSet);
+
+ mpFS->endElementNS(XML_a, XML_blip);
+
+ return sRelId;
+}
+
+void DrawingML::WriteXGraphicBlipMode(uno::Reference<beans::XPropertySet> const & rXPropSet,
+ uno::Reference<graphic::XGraphic> const & rxGraphic)
+{
+ BitmapMode eBitmapMode(BitmapMode_NO_REPEAT);
+ if (GetProperty(rXPropSet, "FillBitmapMode"))
+ mAny >>= eBitmapMode;
+
+ SAL_INFO("oox.shape", "fill bitmap mode: " << int(eBitmapMode));
+
+ switch (eBitmapMode)
+ {
+ case BitmapMode_REPEAT:
+ WriteXGraphicTile(rXPropSet, rxGraphic);
+ break;
+ case BitmapMode_STRETCH:
+ WriteXGraphicStretch(rXPropSet, rxGraphic);
+ break;
+ default:
+ break;
+ }
+}
+
+void DrawingML::WriteBlipOrNormalFill( const Reference< XPropertySet >& xPropSet, const OUString& rURLPropName )
+{
+ // check for blip and otherwise fall back to normal fill
+ // we always store normal fill properties but OOXML
+ // uses a choice between our fill props and BlipFill
+ if (GetProperty ( xPropSet, rURLPropName ))
+ WriteBlipFill( xPropSet, rURLPropName );
+ else
+ WriteFill(xPropSet);
+}
+
+void DrawingML::WriteBlipFill( const Reference< XPropertySet >& rXPropSet, const OUString& sURLPropName )
+{
+ WriteBlipFill( rXPropSet, sURLPropName, XML_a );
+}
+
+void DrawingML::WriteBlipFill( const Reference< XPropertySet >& rXPropSet, const OUString& sURLPropName, sal_Int32 nXmlNamespace )
+{
+ if ( !GetProperty( rXPropSet, sURLPropName ) )
+ return;
+
+ uno::Reference<graphic::XGraphic> xGraphic;
+ if (mAny.has<uno::Reference<awt::XBitmap>>())
+ {
+ uno::Reference<awt::XBitmap> xBitmap = mAny.get<uno::Reference<awt::XBitmap>>();
+ if (xBitmap.is())
+ xGraphic.set(xBitmap, uno::UNO_QUERY);
+ }
+ else if (mAny.has<uno::Reference<graphic::XGraphic>>())
+ {
+ xGraphic = mAny.get<uno::Reference<graphic::XGraphic>>();
+ }
+
+ if (xGraphic.is())
+ {
+ bool bWriteMode = false;
+ if (sURLPropName == "FillBitmap" || sURLPropName == "BackGraphic")
+ bWriteMode = true;
+ WriteXGraphicBlipFill(rXPropSet, xGraphic, nXmlNamespace, bWriteMode);
+ }
+}
+
+void DrawingML::WriteXGraphicBlipFill(uno::Reference<beans::XPropertySet> const & rXPropSet,
+ uno::Reference<graphic::XGraphic> const & rxGraphic,
+ sal_Int32 nXmlNamespace, bool bWriteMode, bool bRelPathToMedia)
+{
+ if (!rxGraphic.is() )
+ return;
+
+ mpFS->startElementNS(nXmlNamespace , XML_blipFill, XML_rotWithShape, "0");
+
+ WriteXGraphicBlip(rXPropSet, rxGraphic, bRelPathToMedia);
+
+ if (GetDocumentType() != DOCUMENT_DOCX)
+ {
+ // Write the crop rectangle of Impress as a source rectangle.
+ WriteSrcRectXGraphic(rXPropSet, rxGraphic);
+ }
+
+ if (bWriteMode)
+ {
+ WriteXGraphicBlipMode(rXPropSet, rxGraphic);
+ }
+ else if(GetProperty(rXPropSet, "FillBitmapStretch"))
+ {
+ bool bStretch = mAny.get<bool>();
+
+ if (bStretch)
+ {
+ WriteXGraphicStretch(rXPropSet, rxGraphic);
+ }
+ }
+ mpFS->endElementNS(nXmlNamespace, XML_blipFill);
+}
+
+void DrawingML::WritePattFill( const Reference< XPropertySet >& rXPropSet )
+{
+ if ( GetProperty( rXPropSet, "FillHatch" ) )
+ {
+ drawing::Hatch aHatch;
+ mAny >>= aHatch;
+ WritePattFill(rXPropSet, aHatch);
+ }
+}
+
+void DrawingML::WritePattFill(const Reference<XPropertySet>& rXPropSet, const css::drawing::Hatch& rHatch)
+{
+ mpFS->startElementNS(XML_a, XML_pattFill, XML_prst, GetHatchPattern(rHatch));
+
+ sal_Int32 nAlpha = MAX_PERCENT;
+ if (GetProperty(rXPropSet, "FillTransparence"))
+ {
+ sal_Int32 nTransparency = 0;
+ mAny >>= nTransparency;
+ nAlpha = (MAX_PERCENT - (PER_PERCENT * nTransparency));
+ }
+
+ mpFS->startElementNS(XML_a, XML_fgClr);
+ WriteColor(::Color(ColorTransparency, rHatch.Color), nAlpha);
+ mpFS->endElementNS( XML_a , XML_fgClr );
+
+ ::Color nColor = COL_WHITE;
+
+ if ( GetProperty( rXPropSet, "FillBackground" ) )
+ {
+ bool isBackgroundFilled = false;
+ mAny >>= isBackgroundFilled;
+ if( isBackgroundFilled )
+ {
+ if( GetProperty( rXPropSet, "FillColor" ) )
+ {
+ mAny >>= nColor;
+ }
+ }
+ else
+ nAlpha = 0;
+ }
+
+ mpFS->startElementNS(XML_a, XML_bgClr);
+ WriteColor(nColor, nAlpha);
+ mpFS->endElementNS( XML_a , XML_bgClr );
+
+ mpFS->endElementNS( XML_a , XML_pattFill );
+}
+
+void DrawingML::WriteGraphicCropProperties(uno::Reference<beans::XPropertySet> const & rXPropSet,
+ Size const & rOriginalSize,
+ MapMode const & rMapMode)
+{
+ if (!GetProperty(rXPropSet, "GraphicCrop"))
+ return;
+
+ css::text::GraphicCrop aGraphicCropStruct;
+ mAny >>= aGraphicCropStruct;
+
+ if(GetProperty(rXPropSet, "CustomShapeGeometry"))
+ {
+ // tdf#134210 GraphicCrop property is handled in import filter because of LibreOffice has not core
+ // feature. We cropped the bitmap physically and MSO shouldn't crop bitmap one more time. When we
+ // have core feature for graphic cropping in custom shapes, we should uncomment the code anymore.
+
+ mpFS->singleElementNS( XML_a, XML_srcRect);
+ }
+ else
+ {
+ Size aOriginalSize(rOriginalSize);
+
+ // GraphicCrop is in mm100, so in case the original size is in pixels, convert it over.
+ if (rMapMode.GetMapUnit() == MapUnit::MapPixel)
+ aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, MapMode(MapUnit::Map100thMM));
+
+ if ( (0 != aGraphicCropStruct.Left) || (0 != aGraphicCropStruct.Top) || (0 != aGraphicCropStruct.Right) || (0 != aGraphicCropStruct.Bottom) )
+ {
+ mpFS->singleElementNS( XML_a, XML_srcRect,
+ XML_l, OString::number(rtl::math::round(aGraphicCropStruct.Left * 100000.0 / aOriginalSize.Width())),
+ XML_t, OString::number(rtl::math::round(aGraphicCropStruct.Top * 100000.0 / aOriginalSize.Height())),
+ XML_r, OString::number(rtl::math::round(aGraphicCropStruct.Right * 100000.0 / aOriginalSize.Width())),
+ XML_b, OString::number(rtl::math::round(aGraphicCropStruct.Bottom * 100000.0 / aOriginalSize.Height())) );
+ }
+ }
+}
+
+void DrawingML::WriteSrcRectXGraphic(uno::Reference<beans::XPropertySet> const & rxPropertySet,
+ uno::Reference<graphic::XGraphic> const & rxGraphic)
+{
+ Graphic aGraphic(rxGraphic);
+ Size aOriginalSize = aGraphic.GetPrefSize();
+ const MapMode& rMapMode = aGraphic.GetPrefMapMode();
+ WriteGraphicCropProperties(rxPropertySet, aOriginalSize, rMapMode);
+}
+
+void DrawingML::WriteXGraphicStretch(uno::Reference<beans::XPropertySet> const & rXPropSet,
+ uno::Reference<graphic::XGraphic> const & rxGraphic)
+{
+ if (GetDocumentType() != DOCUMENT_DOCX)
+ {
+ // Limiting the area used for stretching is not supported in Impress.
+ mpFS->singleElementNS(XML_a, XML_stretch);
+ return;
+ }
+
+ mpFS->startElementNS(XML_a, XML_stretch);
+
+ bool bCrop = false;
+ if (GetProperty(rXPropSet, "GraphicCrop"))
+ {
+ css::text::GraphicCrop aGraphicCropStruct;
+ mAny >>= aGraphicCropStruct;
+
+ if ((0 != aGraphicCropStruct.Left)
+ || (0 != aGraphicCropStruct.Top)
+ || (0 != aGraphicCropStruct.Right)
+ || (0 != aGraphicCropStruct.Bottom))
+ {
+ Graphic aGraphic(rxGraphic);
+ Size aOriginalSize(aGraphic.GetPrefSize());
+ mpFS->singleElementNS(XML_a, XML_fillRect,
+ XML_l, OString::number(((aGraphicCropStruct.Left) * 100000) / aOriginalSize.Width()),
+ XML_t, OString::number(((aGraphicCropStruct.Top) * 100000) / aOriginalSize.Height()),
+ XML_r, OString::number(((aGraphicCropStruct.Right) * 100000) / aOriginalSize.Width()),
+ XML_b, OString::number(((aGraphicCropStruct.Bottom) * 100000) / aOriginalSize.Height()));
+ bCrop = true;
+ }
+ }
+
+ if (!bCrop)
+ {
+ mpFS->singleElementNS(XML_a, XML_fillRect);
+ }
+
+ mpFS->endElementNS(XML_a, XML_stretch);
+}
+
+static OUString lclConvertRectanglePointToToken(RectanglePoint eRectanglePoint)
+{
+ OUString sAlignment;
+ switch (eRectanglePoint)
+ {
+ case RectanglePoint_LEFT_TOP:
+ sAlignment = "tl";
+ break;
+ case RectanglePoint_MIDDLE_TOP:
+ sAlignment = "t";
+ break;
+ case RectanglePoint_RIGHT_TOP:
+ sAlignment = "tr";
+ break;
+ case RectanglePoint_LEFT_MIDDLE:
+ sAlignment = "l";
+ break;
+ case RectanglePoint_MIDDLE_MIDDLE:
+ sAlignment = "ctr";
+ break;
+ case RectanglePoint_RIGHT_MIDDLE:
+ sAlignment = "r";
+ break;
+ case RectanglePoint_LEFT_BOTTOM:
+ sAlignment = "bl";
+ break;
+ case RectanglePoint_MIDDLE_BOTTOM:
+ sAlignment = "b";
+ break;
+ case RectanglePoint_RIGHT_BOTTOM:
+ sAlignment = "br";
+ break;
+ default:
+ break;
+ }
+ return sAlignment;
+}
+
+void DrawingML::WriteXGraphicTile(uno::Reference<beans::XPropertySet> const& rXPropSet,
+ uno::Reference<graphic::XGraphic> const& rxGraphic)
+{
+ Graphic aGraphic(rxGraphic);
+ Size aOriginalSize(aGraphic.GetPrefSize());
+ const MapMode& rMapMode = aGraphic.GetPrefMapMode();
+ // if the original size is in pixel, convert it to mm100
+ if (rMapMode.GetMapUnit() == MapUnit::MapPixel)
+ aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize,
+ MapMode(MapUnit::Map100thMM));
+ sal_Int32 nOffsetX = 0;
+ if (GetProperty(rXPropSet, "FillBitmapPositionOffsetX"))
+ nOffsetX = (*o3tl::doAccess<sal_Int32>(mAny)) * aOriginalSize.Width() * 3.6;
+
+ sal_Int32 nOffsetY = 0;
+ if (GetProperty(rXPropSet, "FillBitmapPositionOffsetY"))
+ nOffsetY = (*o3tl::doAccess<sal_Int32>(mAny)) * aOriginalSize.Height() * 3.6;
+
+ // convert the X size of bitmap to a percentage
+ sal_Int32 nSizeX = 0;
+ if (GetProperty(rXPropSet, "FillBitmapSizeX"))
+ {
+ mAny >>= nSizeX;
+ if (nSizeX > 0)
+ nSizeX = double(nSizeX) / aOriginalSize.Width() * 100000;
+ else if (nSizeX < 0)
+ nSizeX *= 1000;
+ else
+ nSizeX = 100000;
+ }
+
+ // convert the Y size of bitmap to a percentage
+ sal_Int32 nSizeY = 0;
+ if (GetProperty(rXPropSet, "FillBitmapSizeY"))
+ {
+ mAny >>= nSizeY;
+ if (nSizeY > 0)
+ nSizeY = double(nSizeY) / aOriginalSize.Height() * 100000;
+ else if (nSizeY < 0)
+ nSizeY *= 1000;
+ else
+ nSizeY = 100000;
+ }
+
+ // if the "Scale" setting is checked in the images settings dialog.
+ if (nSizeX < 0 && nSizeY < 0)
+ {
+ Reference<drawing::XDrawPagesSupplier> xDPS(GetFB()->getModel(), UNO_QUERY_THROW);
+ Reference<drawing::XDrawPages> xDrawPages(xDPS->getDrawPages(), UNO_SET_THROW);
+ // in this case, the size of the first slide is enough, because all slides are the same size
+ Reference<XDrawPage> xDrawPage(xDrawPages->getByIndex(0), UNO_QUERY);
+ css::uno::Reference<css::beans::XPropertySet> xPagePropSet(xDrawPage, UNO_QUERY);
+ if (xPagePropSet)
+ {
+ double nPageWidth, nPageHeight;
+ xPagePropSet->getPropertyValue("Width") >>= nPageWidth;
+ xPagePropSet->getPropertyValue("Height") >>= nPageHeight;
+ nSizeX = nPageWidth / aOriginalSize.Width() * std::abs(nSizeX);
+ nSizeY = nPageHeight / aOriginalSize.Height() * std::abs(nSizeY);
+ }
+ }
+
+ OUString sRectanglePoint;
+ if (GetProperty(rXPropSet, "FillBitmapRectanglePoint"))
+ sRectanglePoint = lclConvertRectanglePointToToken(*o3tl::doAccess<RectanglePoint>(mAny));
+
+ mpFS->singleElementNS(XML_a, XML_tile, XML_tx, OUString::number(nOffsetX), XML_ty,
+ OUString::number(nOffsetY), XML_sx, OUString::number(nSizeX), XML_sy,
+ OUString::number(nSizeY), XML_algn, sRectanglePoint);
+}
+
+namespace
+{
+bool IsTopGroupObj(const uno::Reference<drawing::XShape>& xShape)
+{
+ SdrObject* pObject = SdrObject::getSdrObjectFromXShape(xShape);
+ if (!pObject)
+ return false;
+
+ if (pObject->getParentSdrObjectFromSdrObject())
+ return false;
+
+ return pObject->IsGroupObject();
+}
+}
+
+void DrawingML::WriteTransformation(const Reference< XShape >& xShape, const tools::Rectangle& rRect,
+ sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, sal_Int32 nRotation, bool bIsGroupShape)
+{
+
+ mpFS->startElementNS( nXmlNamespace, XML_xfrm,
+ XML_flipH, sax_fastparser::UseIf("1", bFlipH),
+ XML_flipV, sax_fastparser::UseIf("1", bFlipV),
+ XML_rot, sax_fastparser::UseIf(OString::number(nRotation), nRotation % 21600000 != 0));
+
+ sal_Int32 nLeft = rRect.Left();
+ sal_Int32 nTop = rRect.Top();
+ if (GetDocumentType() == DOCUMENT_DOCX && !m_xParent.is())
+ {
+ nLeft = 0;
+ nTop = 0;
+ }
+ sal_Int32 nChildLeft = nLeft;
+ sal_Int32 nChildTop = nTop;
+
+ mpFS->singleElementNS(XML_a, XML_off,
+ XML_x, OString::number(oox::drawingml::convertHmmToEmu(nLeft)),
+ XML_y, OString::number(oox::drawingml::convertHmmToEmu(nTop)));
+ mpFS->singleElementNS(XML_a, XML_ext,
+ XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())),
+ XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight())));
+
+ if (bIsGroupShape && (GetDocumentType() != DOCUMENT_DOCX || IsTopGroupObj(xShape)))
+ {
+ mpFS->singleElementNS(XML_a, XML_chOff,
+ XML_x, OString::number(oox::drawingml::convertHmmToEmu(nChildLeft)),
+ XML_y, OString::number(oox::drawingml::convertHmmToEmu(nChildTop)));
+ mpFS->singleElementNS(XML_a, XML_chExt,
+ XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())),
+ XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight())));
+ }
+
+ mpFS->endElementNS( nXmlNamespace, XML_xfrm );
+}
+
+void DrawingML::WriteShapeTransformation( const Reference< XShape >& rXShape, sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, bool bSuppressRotation, bool bSuppressFlipping, bool bFlippedBeforeRotation )
+{
+ SAL_INFO("oox.shape", "write shape transformation");
+
+ Degree100 nRotation;
+ Degree100 nCameraRotation;
+ awt::Point aPos = rXShape->getPosition();
+ awt::Size aSize = rXShape->getSize();
+
+ bool bFlipHWrite = bFlipH && !bSuppressFlipping;
+ bool bFlipVWrite = bFlipV && !bSuppressFlipping;
+ bFlipH = bFlipH && !bFlippedBeforeRotation;
+ bFlipV = bFlipV && !bFlippedBeforeRotation;
+
+ if (GetDocumentType() == DOCUMENT_DOCX && m_xParent.is())
+ {
+ awt::Point aParentPos = m_xParent->getPosition();
+ aPos.X -= aParentPos.X;
+ aPos.Y -= aParentPos.Y;
+ }
+
+ if ( aSize.Width < 0 )
+ aSize.Width = 1000;
+ if ( aSize.Height < 0 )
+ aSize.Height = 1000;
+ if (!bSuppressRotation)
+ {
+ SdrObject* pShape = SdrObject::getSdrObjectFromXShape(rXShape);
+ nRotation = pShape ? pShape->GetRotateAngle() : 0_deg100;
+ if ( GetDocumentType() != DOCUMENT_DOCX )
+ {
+ int faccos=bFlipV ? -1 : 1;
+ int facsin=bFlipH ? -1 : 1;
+ aPos.X-=(1-faccos*cos(toRadians(nRotation)))*aSize.Width/2-facsin*sin(toRadians(nRotation))*aSize.Height/2;
+ aPos.Y-=(1-faccos*cos(toRadians(nRotation)))*aSize.Height/2+facsin*sin(toRadians(nRotation))*aSize.Width/2;
+ }
+ else if (m_xParent.is() && nRotation != 0_deg100)
+ {
+ // Position for rotated shapes inside group is not set by DocxSdrExport.
+ basegfx::B2DRange aRect(-aSize.Width / 2.0, -aSize.Height / 2.0, aSize.Width / 2.0,
+ aSize.Height / 2.0);
+ basegfx::B2DHomMatrix aRotateMatrix =
+ basegfx::utils::createRotateB2DHomMatrix(toRadians(nRotation));
+ aRect.transform(aRotateMatrix);
+ aPos.X += -aSize.Width / 2.0 - aRect.getMinX();
+ aPos.Y += -aSize.Height / 2.0 - aRect.getMinY();
+ }
+
+ // The RotateAngle property's value is independent from any flipping, and that's exactly what we need here.
+ uno::Reference<beans::XPropertySet> xPropertySet(rXShape, uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
+ if (xPropertySetInfo->hasPropertyByName("RotateAngle"))
+ {
+ sal_Int32 nTmp;
+ if (xPropertySet->getPropertyValue("RotateAngle") >>= nTmp)
+ nRotation = Degree100(nTmp);
+ }
+ // tdf#133037: restore original rotate angle before output
+ if (nRotation && xPropertySetInfo->hasPropertyByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG))
+ {
+ uno::Sequence<beans::PropertyValue> aGrabBagProps;
+ xPropertySet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= aGrabBagProps;
+ auto p3DEffectProps = std::find_if(std::cbegin(aGrabBagProps), std::cend(aGrabBagProps),
+ [](const PropertyValue& rProp) { return rProp.Name == "3DEffectProperties"; });
+ if (p3DEffectProps != std::cend(aGrabBagProps))
+ {
+ uno::Sequence<beans::PropertyValue> a3DEffectProps;
+ p3DEffectProps->Value >>= a3DEffectProps;
+ auto pCameraProps = std::find_if(std::cbegin(a3DEffectProps), std::cend(a3DEffectProps),
+ [](const PropertyValue& rProp) { return rProp.Name == "Camera"; });
+ if (pCameraProps != std::cend(a3DEffectProps))
+ {
+ uno::Sequence<beans::PropertyValue> aCameraProps;
+ pCameraProps->Value >>= aCameraProps;
+ auto pZRotationProp = std::find_if(std::cbegin(aCameraProps), std::cend(aCameraProps),
+ [](const PropertyValue& rProp) { return rProp.Name == "rotRev"; });
+ if (pZRotationProp != std::cend(aCameraProps))
+ {
+ sal_Int32 nTmp = 0;
+ pZRotationProp->Value >>= nTmp;
+ nCameraRotation = NormAngle36000(Degree100(nTmp / -600));
+ }
+ }
+ }
+ }
+ }
+
+ // OOXML flips shapes before rotating them.
+ if(bFlipH != bFlipV)
+ nRotation = 36000_deg100 - nRotation;
+
+ WriteTransformation(rXShape, tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), nXmlNamespace,
+ bFlipHWrite, bFlipVWrite, ExportRotateClockwisify(nRotation + nCameraRotation), IsGroupShape( rXShape ));
+}
+
+static OUString lcl_GetTarget(const css::uno::Reference<css::frame::XModel>& xModel, OUString& rURL)
+{
+ Reference<drawing::XDrawPagesSupplier> xDPS(xModel, uno::UNO_QUERY_THROW);
+ Reference<drawing::XDrawPages> xDrawPages(xDPS->getDrawPages(), uno::UNO_SET_THROW);
+ sal_uInt32 nPageCount = xDrawPages->getCount();
+ OUString sTarget;
+
+ for (sal_uInt32 i = 0; i < nPageCount; ++i)
+ {
+ Reference<XDrawPage> xDrawPage;
+ xDrawPages->getByIndex(i) >>= xDrawPage;
+ Reference<container::XNamed> xNamed(xDrawPage, UNO_QUERY);
+ if (!xNamed)
+ continue;
+ OUString sSlideName = "#" + xNamed->getName();
+ if (rURL == sSlideName)
+ {
+ sTarget = "slide" + OUString::number(i + 1) + ".xml";
+ break;
+ }
+ }
+ if (sTarget.isEmpty())
+ {
+ sal_Int32 nSplit = rURL.lastIndexOf(' ');
+ if (nSplit > -1)
+ sTarget = OUString::Concat("slide") + rURL.subView(nSplit + 1) + ".xml";
+ }
+
+ return sTarget;
+}
+
+void DrawingML::WriteRunProperties( const Reference< XPropertySet >& rRun, bool bIsField, sal_Int32 nElement,
+ bool bCheckDirect,bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
+ sal_Int16 nScriptType, const Reference< XPropertySet >& rXShapePropSet)
+{
+ Reference< XPropertySet > rXPropSet = rRun;
+ Reference< XPropertyState > rXPropState( rRun, UNO_QUERY );
+ OUString usLanguage;
+ PropertyState eState;
+ bool bComplex = ( nScriptType == css::i18n::ScriptType::COMPLEX );
+ const char* bold = "0";
+ const char* italic = nullptr;
+ const char* underline = nullptr;
+ const char* strikeout = nullptr;
+ const char* cap = nullptr;
+ sal_Int32 nSize = 1800;
+ sal_Int32 nCharEscapement = 0;
+ sal_Int32 nCharKerning = 0;
+ sal_Int32 nCharEscapementHeight = 0;
+
+ if ( nElement == XML_endParaRPr && rbOverridingCharHeight )
+ {
+ nSize = rnCharHeight;
+ }
+ else if (GetProperty(rXPropSet, "CharHeight"))
+ {
+ nSize = static_cast<sal_Int32>(100*(*o3tl::doAccess<float>(mAny)));
+ if ( nElement == XML_rPr || nElement == XML_defRPr )
+ {
+ rbOverridingCharHeight = true;
+ rnCharHeight = nSize;
+ }
+ }
+
+ if (GetProperty(rXPropSet, "CharKerning"))
+ nCharKerning = static_cast<sal_Int32>(*o3tl::doAccess<sal_Int16>(mAny));
+ /** While setting values in propertymap,
+ * CharKerning converted using GetTextSpacingPoint
+ * i.e set @ https://opengrok.libreoffice.org/xref/core/oox/source/drawingml/textcharacterproperties.cxx#129
+ * therefore to get original value CharKerning need to be convert.
+ * https://opengrok.libreoffice.org/xref/core/oox/source/drawingml/drawingmltypes.cxx#95
+ **/
+ nCharKerning = ((nCharKerning * 720)-360) / 254;
+
+ if ((bComplex && GetProperty(rXPropSet, "CharWeightComplex"))
+ || GetProperty(rXPropSet, "CharWeight"))
+ {
+ if ( *o3tl::doAccess<float>(mAny) >= awt::FontWeight::SEMIBOLD )
+ bold = "1";
+ }
+
+ if ((bComplex && GetProperty(rXPropSet, "CharPostureComplex"))
+ || GetProperty(rXPropSet, "CharPosture"))
+ switch ( *o3tl::doAccess<awt::FontSlant>(mAny) )
+ {
+ case awt::FontSlant_OBLIQUE :
+ case awt::FontSlant_ITALIC :
+ italic = "1";
+ break;
+ default:
+ break;
+ }
+
+ if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharUnderline", eState)
+ && eState == beans::PropertyState_DIRECT_VALUE)
+ || GetProperty(rXPropSet, "CharUnderline"))
+ {
+ switch ( *o3tl::doAccess<sal_Int16>(mAny) )
+ {
+ case awt::FontUnderline::SINGLE :
+ underline = "sng";
+ break;
+ case awt::FontUnderline::DOUBLE :
+ underline = "dbl";
+ break;
+ case awt::FontUnderline::DOTTED :
+ underline = "dotted";
+ break;
+ case awt::FontUnderline::DASH :
+ underline = "dash";
+ break;
+ case awt::FontUnderline::LONGDASH :
+ underline = "dashLong";
+ break;
+ case awt::FontUnderline::DASHDOT :
+ underline = "dotDash";
+ break;
+ case awt::FontUnderline::DASHDOTDOT :
+ underline = "dotDotDash";
+ break;
+ case awt::FontUnderline::WAVE :
+ underline = "wavy";
+ break;
+ case awt::FontUnderline::DOUBLEWAVE :
+ underline = "wavyDbl";
+ break;
+ case awt::FontUnderline::BOLD :
+ underline = "heavy";
+ break;
+ case awt::FontUnderline::BOLDDOTTED :
+ underline = "dottedHeavy";
+ break;
+ case awt::FontUnderline::BOLDDASH :
+ underline = "dashHeavy";
+ break;
+ case awt::FontUnderline::BOLDLONGDASH :
+ underline = "dashLongHeavy";
+ break;
+ case awt::FontUnderline::BOLDDASHDOT :
+ underline = "dotDashHeavy";
+ break;
+ case awt::FontUnderline::BOLDDASHDOTDOT :
+ underline = "dotDotDashHeavy";
+ break;
+ case awt::FontUnderline::BOLDWAVE :
+ underline = "wavyHeavy";
+ break;
+ }
+ }
+
+ if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharStrikeout", eState)
+ && eState == beans::PropertyState_DIRECT_VALUE)
+ || GetProperty(rXPropSet, "CharStrikeout"))
+ {
+ switch ( *o3tl::doAccess<sal_Int16>(mAny) )
+ {
+ case awt::FontStrikeout::NONE :
+ strikeout = "noStrike";
+ break;
+ case awt::FontStrikeout::SINGLE :
+ // LibO supports further values of character
+ // strikeout, OOXML standard (20.1.10.78,
+ // ST_TextStrikeType) however specifies only
+ // 3 - single, double and none. Approximate
+ // the remaining ones by single strike (better
+ // some strike than none at all).
+ // TODO: figure out how to do this better
+ case awt::FontStrikeout::BOLD :
+ case awt::FontStrikeout::SLASH :
+ case awt::FontStrikeout::X :
+ strikeout = "sngStrike";
+ break;
+ case awt::FontStrikeout::DOUBLE :
+ strikeout = "dblStrike";
+ break;
+ }
+ }
+
+ bool bLang = false;
+ switch(nScriptType)
+ {
+ case css::i18n::ScriptType::ASIAN:
+ bLang = GetProperty(rXPropSet, "CharLocaleAsian"); break;
+ case css::i18n::ScriptType::COMPLEX:
+ bLang = GetProperty(rXPropSet, "CharLocaleComplex"); break;
+ default:
+ bLang = GetProperty(rXPropSet, "CharLocale"); break;
+ }
+
+ if (bLang)
+ {
+ css::lang::Locale aLocale;
+ mAny >>= aLocale;
+ LanguageTag aLanguageTag( aLocale);
+ if (!aLanguageTag.isSystemLocale())
+ usLanguage = aLanguageTag.getBcp47MS();
+ }
+
+ if (GetPropertyAndState(rXPropSet, rXPropState, "CharEscapement", eState)
+ && eState == beans::PropertyState_DIRECT_VALUE)
+ mAny >>= nCharEscapement;
+
+ if (GetPropertyAndState(rXPropSet, rXPropState, "CharEscapementHeight", eState)
+ && eState == beans::PropertyState_DIRECT_VALUE)
+ mAny >>= nCharEscapementHeight;
+
+ if (DFLT_ESC_AUTO_SUPER == nCharEscapement)
+ {
+ // Raised by the differences between the ascenders (ascent = baseline to top of highest letter).
+ // The ascent is generally about 80% of the total font height.
+ // That is why DFLT_ESC_PROP (58) leads to 33% (DFLT_ESC_SUPER)
+ nCharEscapement = .8 * (100 - nCharEscapementHeight);
+ }
+ else if (DFLT_ESC_AUTO_SUB == nCharEscapement)
+ {
+ // Lowered by the differences between the descenders (descent = baseline to bottom of lowest letter).
+ // The descent is generally about 20% of the total font height.
+ // That is why DFLT_ESC_PROP (58) leads to 8% (DFLT_ESC_SUB)
+ nCharEscapement = .2 * -(100 - nCharEscapementHeight);
+ }
+
+ if (nCharEscapement && nCharEscapementHeight)
+ {
+ nSize = (nSize * nCharEscapementHeight) / 100;
+ // MSO uses default ~58% size
+ nSize = (nSize / 0.58);
+ }
+
+ if (GetProperty(rXPropSet, "CharCaseMap"))
+ {
+ switch ( *o3tl::doAccess<sal_Int16>(mAny) )
+ {
+ case CaseMap::UPPERCASE :
+ cap = "all";
+ break;
+ case CaseMap::SMALLCAPS :
+ cap = "small";
+ break;
+ }
+ }
+
+ mpFS->startElementNS( XML_a, nElement,
+ XML_b, bold,
+ XML_i, italic,
+ XML_lang, sax_fastparser::UseIf(usLanguage, !usLanguage.isEmpty()),
+ XML_sz, OString::number(nSize),
+ // For Condensed character spacing spc value is negative.
+ XML_spc, sax_fastparser::UseIf(OString::number(nCharKerning), nCharKerning != 0),
+ XML_strike, strikeout,
+ XML_u, underline,
+ XML_baseline, sax_fastparser::UseIf(OString::number(nCharEscapement*1000), nCharEscapement != 0),
+ XML_cap, cap );
+
+ // Fontwork-shapes in LO have text outline and fill from shape stroke and shape fill
+ // PowerPoint has this as run properties
+ if (IsFontworkShape(rXShapePropSet))
+ {
+ WriteOutline(rXShapePropSet);
+ WriteBlipOrNormalFill(rXShapePropSet, "Graphic");
+ WriteShapeEffects(rXShapePropSet);
+ }
+ else
+ {
+ // mso doesn't like text color to be placed after typeface
+ if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharColor", eState)
+ && eState == beans::PropertyState_DIRECT_VALUE)
+ || GetProperty(rXPropSet, "CharColor"))
+ {
+ ::Color color( ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny) );
+ SAL_INFO("oox.shape", "run color: " << sal_uInt32(color) << " auto: " << sal_uInt32(COL_AUTO));
+
+ // WriteSolidFill() handles MAX_PERCENT as "no transparency".
+ sal_Int32 nTransparency = MAX_PERCENT;
+ if (rXPropSet->getPropertySetInfo()->hasPropertyByName("CharTransparence"))
+ {
+ rXPropSet->getPropertyValue("CharTransparence") >>= nTransparency;
+ // UNO scale is 0..100, OOXML scale is 0..100000; also UNO tracks transparency, OOXML
+ // tracks opacity.
+ nTransparency = MAX_PERCENT - (nTransparency * PER_PERCENT);
+ }
+
+ bool bContoured = false;
+ if (GetProperty(rXPropSet, "CharContoured"))
+ bContoured = *o3tl::doAccess<bool>(mAny);
+
+ // tdf#127696 If the CharContoured is true, then the text color is white and the outline color is the CharColor.
+ if (bContoured)
+ {
+ mpFS->startElementNS(XML_a, XML_ln);
+ if (color == COL_AUTO)
+ {
+ mbIsBackgroundDark ? WriteSolidFill(COL_WHITE) : WriteSolidFill(COL_BLACK);
+ }
+ else
+ {
+ color.SetAlpha(255);
+ if (!WriteCharColor(rXPropSet))
+ WriteSolidFill(color, nTransparency);
+ }
+ mpFS->endElementNS(XML_a, XML_ln);
+
+ WriteSolidFill(COL_WHITE);
+ }
+ // tdf#104219 In LibreOffice and MS Office, there are two types of colors:
+ // Automatic and Fixed. OOXML is setting automatic color, by not providing color.
+ else if( color != COL_AUTO )
+ {
+ color.SetAlpha(255);
+ // TODO: special handle embossed/engraved
+ if (!WriteCharColor(rXPropSet))
+ {
+ WriteSolidFill(color, nTransparency);
+ }
+ }
+ else if (GetDocumentType() == DOCUMENT_PPTX)
+ {
+ // Resolve COL_AUTO for PPTX since MS Powerpoint doesn't have automatic colors.
+ bool bIsTextBackgroundDark = mbIsBackgroundDark;
+ if (rXShapePropSet.is() && GetProperty(rXShapePropSet, "FillStyle")
+ && mAny.get<FillStyle>() != FillStyle_NONE
+ && GetProperty(rXShapePropSet, "FillColor"))
+ {
+ ::Color aShapeFillColor(ColorTransparency, mAny.get<sal_uInt32>());
+ bIsTextBackgroundDark = aShapeFillColor.IsDark();
+ }
+
+ if (bIsTextBackgroundDark)
+ WriteSolidFill(COL_WHITE);
+ else
+ WriteSolidFill(COL_BLACK);
+ }
+ }
+ }
+
+ // tdf#128096, exporting XML_highlight to docx already works fine,
+ // so make sure this code is only run when exporting to pptx, just in case
+ if (GetDocumentType() == DOCUMENT_PPTX)
+ {
+ if (GetProperty(rXPropSet, "CharBackColor"))
+ {
+ ::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny));
+ if( color != COL_AUTO )
+ {
+ mpFS->startElementNS(XML_a, XML_highlight);
+ WriteColor( color );
+ mpFS->endElementNS( XML_a, XML_highlight );
+ }
+ }
+ }
+
+ if (underline
+ && ((bCheckDirect
+ && GetPropertyAndState(rXPropSet, rXPropState, "CharUnderlineColor", eState)
+ && eState == beans::PropertyState_DIRECT_VALUE)
+ || GetProperty(rXPropSet, "CharUnderlineColor")))
+ {
+ ::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny));
+ // if color is automatic, then we shouldn't write information about color but to take color from character
+ if( color != COL_AUTO )
+ {
+ mpFS->startElementNS(XML_a, XML_uFill);
+ WriteSolidFill( color );
+ mpFS->endElementNS( XML_a, XML_uFill );
+ }
+ else
+ {
+ mpFS->singleElementNS(XML_a, XML_uFillTx);
+ }
+ }
+
+ if (GetProperty(rXPropSet, "CharFontName"))
+ {
+ const char* const pitch = nullptr;
+ const char* const charset = nullptr;
+ OUString usTypeface;
+
+ mAny >>= usTypeface;
+ OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) );
+ if (!aSubstName.isEmpty())
+ usTypeface = aSubstName;
+
+ mpFS->singleElementNS( XML_a, XML_latin,
+ XML_typeface, usTypeface,
+ XML_pitchFamily, pitch,
+ XML_charset, charset );
+ }
+
+ if ((bComplex
+ && (GetPropertyAndState(rXPropSet, rXPropState, "CharFontNameComplex", eState)
+ && eState == beans::PropertyState_DIRECT_VALUE))
+ || (!bComplex
+ && (GetPropertyAndState(rXPropSet, rXPropState, "CharFontNameAsian", eState)
+ && eState == beans::PropertyState_DIRECT_VALUE)))
+ {
+ const char* const pitch = nullptr;
+ const char* const charset = nullptr;
+ OUString usTypeface;
+
+ mAny >>= usTypeface;
+ OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) );
+ if (!aSubstName.isEmpty())
+ usTypeface = aSubstName;
+
+ mpFS->singleElementNS( XML_a, bComplex ? XML_cs : XML_ea,
+ XML_typeface, usTypeface,
+ XML_pitchFamily, pitch,
+ XML_charset, charset );
+ }
+
+ if( bIsField )
+ {
+ Reference< XTextField > rXTextField;
+ if (GetProperty(rXPropSet, "TextField"))
+ mAny >>= rXTextField;
+ if( rXTextField.is() )
+ rXPropSet.set( rXTextField, UNO_QUERY );
+ }
+
+ // field properties starts here
+ if (GetProperty(rXPropSet, "URL"))
+ {
+ OUString sURL;
+
+ mAny >>= sURL;
+ if (!sURL.isEmpty())
+ {
+ if (!sURL.match("#action?jump="))
+ {
+ bool bExtURL = URLTransformer().isExternalURL(sURL);
+ sURL = bExtURL ? sURL : lcl_GetTarget(GetFB()->getModel(), sURL);
+
+ OUString sRelId
+ = mpFB->addRelation(mpFS->getOutputStream(),
+ bExtURL ? oox::getRelationship(Relationship::HYPERLINK)
+ : oox::getRelationship(Relationship::SLIDE),
+ sURL, bExtURL);
+
+ if (bExtURL)
+ mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
+ else
+ mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId,
+ XML_action, "ppaction://hlinksldjump");
+ }
+ else
+ {
+ sal_Int32 nIndex = sURL.indexOf('=');
+ std::u16string_view aDestination(sURL.subView(nIndex + 1));
+ mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action,
+ OUString::Concat("ppaction://hlinkshowjump?jump=") + aDestination);
+ }
+ }
+ }
+ mpFS->endElementNS( XML_a, nElement );
+}
+
+OUString DrawingML::GetFieldValue( const css::uno::Reference< css::text::XTextRange >& rRun, bool& bIsURLField )
+{
+ Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY );
+ OUString aFieldType, aFieldValue;
+
+ if (GetProperty(rXPropSet, "TextPortionType"))
+ {
+ aFieldType = *o3tl::doAccess<OUString>(mAny);
+ SAL_INFO("oox.shape", "field type: " << aFieldType);
+ }
+
+ if( aFieldType == "TextField" )
+ {
+ Reference< XTextField > rXTextField;
+ if (GetProperty(rXPropSet, "TextField"))
+ mAny >>= rXTextField;
+ if( rXTextField.is() )
+ {
+ rXPropSet.set( rXTextField, UNO_QUERY );
+ if( rXPropSet.is() )
+ {
+ OUString aFieldKind( rXTextField->getPresentation( true ) );
+ SAL_INFO("oox.shape", "field kind: " << aFieldKind);
+ if( aFieldKind == "Page" )
+ {
+ aFieldValue = "slidenum";
+ }
+ else if( aFieldKind == "Pages" )
+ {
+ aFieldValue = "slidecount";
+ }
+ else if( aFieldKind == "PageName" )
+ {
+ aFieldValue = "slidename";
+ }
+ else if( aFieldKind == "URL" )
+ {
+ bIsURLField = true;
+ if (GetProperty(rXPropSet, "Representation"))
+ mAny >>= aFieldValue;
+
+ }
+ else if(aFieldKind == "Date")
+ {
+ sal_Int32 nNumFmt = -1;
+ rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt;
+ aFieldValue = GetDatetimeTypeFromDate(static_cast<SvxDateFormat>(nNumFmt));
+ }
+ else if(aFieldKind == "ExtTime")
+ {
+ sal_Int32 nNumFmt = -1;
+ rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt;
+ aFieldValue = GetDatetimeTypeFromTime(static_cast<SvxTimeFormat>(nNumFmt));
+ }
+ else if(aFieldKind == "ExtFile")
+ {
+ sal_Int32 nNumFmt = -1;
+ rXPropSet->getPropertyValue(UNO_TC_PROP_FILE_FORMAT) >>= nNumFmt;
+ switch(nNumFmt)
+ {
+ case 0: aFieldValue = "file"; // Path/File name
+ break;
+ case 1: aFieldValue = "file1"; // Path
+ break;
+ case 2: aFieldValue = "file2"; // File name without extension
+ break;
+ case 3: aFieldValue = "file3"; // File name with extension
+ }
+ }
+ else if(aFieldKind == "Author")
+ {
+ aFieldValue = "author";
+ }
+ }
+ }
+ }
+ return aFieldValue;
+}
+
+OUString DrawingML::GetDatetimeTypeFromDate(SvxDateFormat eDate)
+{
+ return GetDatetimeTypeFromDateTime(eDate, SvxTimeFormat::AppDefault);
+}
+
+OUString DrawingML::GetDatetimeTypeFromTime(SvxTimeFormat eTime)
+{
+ return GetDatetimeTypeFromDateTime(SvxDateFormat::AppDefault, eTime);
+}
+
+OUString DrawingML::GetDatetimeTypeFromDateTime(SvxDateFormat eDate, SvxTimeFormat eTime)
+{
+ OUString aDateField;
+ switch (eDate)
+ {
+ case SvxDateFormat::StdSmall:
+ case SvxDateFormat::A:
+ aDateField = "datetime";
+ break;
+ case SvxDateFormat::B:
+ aDateField = "datetime1"; // 13/02/1996
+ break;
+ case SvxDateFormat::C:
+ aDateField = "datetime5";
+ break;
+ case SvxDateFormat::D:
+ aDateField = "datetime3"; // 13 February 1996
+ break;
+ case SvxDateFormat::StdBig:
+ case SvxDateFormat::E:
+ case SvxDateFormat::F:
+ aDateField = "datetime2";
+ break;
+ default:
+ break;
+ }
+
+ OUString aTimeField;
+ switch (eTime)
+ {
+ case SvxTimeFormat::Standard:
+ case SvxTimeFormat::HH24_MM_SS:
+ case SvxTimeFormat::HH24_MM_SS_00:
+ aTimeField = "datetime11"; // 13:49:38
+ break;
+ case SvxTimeFormat::HH24_MM:
+ aTimeField = "datetime10"; // 13:49
+ break;
+ case SvxTimeFormat::HH12_MM:
+ case SvxTimeFormat::HH12_MM_AMPM:
+ aTimeField = "datetime12"; // 01:49 PM
+ break;
+ case SvxTimeFormat::HH12_MM_SS:
+ case SvxTimeFormat::HH12_MM_SS_AMPM:
+ case SvxTimeFormat::HH12_MM_SS_00:
+ case SvxTimeFormat::HH12_MM_SS_00_AMPM:
+ aTimeField = "datetime13"; // 01:49:38 PM
+ break;
+ default:
+ break;
+ }
+
+ if (!aDateField.isEmpty() && aTimeField.isEmpty())
+ return aDateField;
+ else if (!aTimeField.isEmpty() && aDateField.isEmpty())
+ return aTimeField;
+ else if (!aDateField.isEmpty() && !aTimeField.isEmpty())
+ {
+ if (aTimeField == "datetime11" || aTimeField == "datetime13")
+ // only datetime format that has Date and HH:MM:SS
+ return "datetime9"; // dd/mm/yyyy H:MM:SS
+ else
+ // only datetime format that has Date and HH:MM
+ return "datetime8"; // dd/mm/yyyy H:MM
+ }
+ else
+ return "";
+}
+
+void DrawingML::WriteRun( const Reference< XTextRange >& rRun,
+ bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
+ const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet)
+{
+ Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY );
+ sal_Int16 nLevel = -1;
+ if (GetProperty(rXPropSet, "NumberingLevel"))
+ mAny >>= nLevel;
+
+ bool bNumberingIsNumber = true;
+ if (GetProperty(rXPropSet, "NumberingIsNumber"))
+ mAny >>= bNumberingIsNumber;
+
+ float nFontSize = -1;
+ if (GetProperty(rXPropSet, "CharHeight"))
+ mAny >>= nFontSize;
+
+ bool bIsURLField = false;
+ OUString sFieldValue = GetFieldValue( rRun, bIsURLField );
+ bool bWriteField = !( sFieldValue.isEmpty() || bIsURLField );
+
+ OUString sText = rRun->getString();
+
+ //if there is no text following the bullet, add a space after the bullet
+ if (nLevel !=-1 && bNumberingIsNumber && sText.isEmpty() )
+ sText=" ";
+
+ if ( bIsURLField )
+ sText = sFieldValue;
+
+ if( sText.isEmpty())
+ {
+ Reference< XPropertySet > xPropSet( rRun, UNO_QUERY );
+
+ try
+ {
+ if( !xPropSet.is() || !( xPropSet->getPropertyValue( "PlaceholderText" ) >>= sText ) )
+ return;
+ if( sText.isEmpty() )
+ return;
+ }
+ catch (const Exception &)
+ {
+ return;
+ }
+ }
+
+ if (sText == "\n")
+ {
+ // Empty run? Do not forget to write the font size in case of pptx:
+ if ((GetDocumentType() == DOCUMENT_PPTX) && (nFontSize != -1))
+ {
+ mpFS->startElementNS(XML_a, XML_br);
+ mpFS->singleElementNS(XML_a, XML_rPr, XML_sz,
+ OString::number(nFontSize * 100).getStr());
+ mpFS->endElementNS(XML_a, XML_br);
+ }
+ else
+ mpFS->singleElementNS(XML_a, XML_br);
+ }
+ else
+ {
+ if( bWriteField )
+ {
+ OString sUUID(comphelper::xml::generateGUIDString());
+ mpFS->startElementNS( XML_a, XML_fld,
+ XML_id, sUUID.getStr(),
+ XML_type, sFieldValue );
+ }
+ else
+ {
+ mpFS->startElementNS(XML_a, XML_r);
+ }
+
+ Reference< XPropertySet > xPropSet( rRun, uno::UNO_QUERY );
+
+ WriteRunProperties( xPropSet, bIsURLField, XML_rPr, true, rbOverridingCharHeight, rnCharHeight, GetScriptType(sText), rXShapePropSet);
+ mpFS->startElementNS(XML_a, XML_t);
+ mpFS->writeEscaped( sText );
+ mpFS->endElementNS( XML_a, XML_t );
+
+ if( bWriteField )
+ mpFS->endElementNS( XML_a, XML_fld );
+ else
+ mpFS->endElementNS( XML_a, XML_r );
+ }
+}
+
+static OUString GetAutoNumType(SvxNumType nNumberingType, bool bSDot, bool bPBehind, bool bPBoth)
+{
+ OUString sPrefixSuffix;
+
+ if (bPBoth)
+ sPrefixSuffix = "ParenBoth";
+ else if (bPBehind)
+ sPrefixSuffix = "ParenR";
+ else if (bSDot)
+ sPrefixSuffix = "Period";
+
+ switch( nNumberingType )
+ {
+ case SVX_NUM_CHARS_UPPER_LETTER_N :
+ case SVX_NUM_CHARS_UPPER_LETTER :
+ return "alphaUc" + sPrefixSuffix;
+
+ case SVX_NUM_CHARS_LOWER_LETTER_N :
+ case SVX_NUM_CHARS_LOWER_LETTER :
+ return "alphaLc" + sPrefixSuffix;
+
+ case SVX_NUM_ROMAN_UPPER :
+ return "romanUc" + sPrefixSuffix;
+
+ case SVX_NUM_ROMAN_LOWER :
+ return "romanLc" + sPrefixSuffix;
+
+ case SVX_NUM_ARABIC :
+ {
+ if (sPrefixSuffix.isEmpty())
+ return "arabicPlain";
+ else
+ return "arabic" + sPrefixSuffix;
+ }
+ default:
+ break;
+ }
+
+ return OUString();
+}
+
+void DrawingML::WriteParagraphNumbering(const Reference< XPropertySet >& rXPropSet, float fFirstCharHeight, sal_Int16 nLevel )
+{
+ if (nLevel < 0 || !GetProperty(rXPropSet, "NumberingRules"))
+ return;
+
+ Reference< XIndexAccess > rXIndexAccess;
+
+ if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount())
+ return;
+
+ SAL_INFO("oox.shape", "numbering rules");
+
+ Sequence<PropertyValue> aPropertySequence;
+ rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence;
+
+ if (!aPropertySequence.hasElements())
+ return;
+
+ SvxNumType nNumberingType = SVX_NUM_NUMBER_NONE;
+ bool bSDot = false;
+ bool bPBehind = false;
+ bool bPBoth = false;
+ sal_Unicode aBulletChar = 0x2022; // a bullet
+ awt::FontDescriptor aFontDesc;
+ bool bHasFontDesc = false;
+ uno::Reference<graphic::XGraphic> xGraphic;
+ sal_Int16 nBulletRelSize = 0;
+ sal_Int16 nStartWith = 1;
+ ::Color nBulletColor;
+ bool bHasBulletColor = false;
+ awt::Size aGraphicSize;
+
+ for ( const PropertyValue& rPropValue : std::as_const(aPropertySequence) )
+ {
+ OUString aPropName( rPropValue.Name );
+ SAL_INFO("oox.shape", "pro name: " << aPropName);
+ if ( aPropName == "NumberingType" )
+ {
+ nNumberingType = static_cast<SvxNumType>(*o3tl::doAccess<sal_Int16>(rPropValue.Value));
+ }
+ else if ( aPropName == "Prefix" )
+ {
+ if( *o3tl::doAccess<OUString>(rPropValue.Value) == ")")
+ bPBoth = true;
+ }
+ else if ( aPropName == "Suffix" )
+ {
+ auto s = o3tl::doAccess<OUString>(rPropValue.Value);
+ if( *s == ".")
+ bSDot = true;
+ else if( *s == ")")
+ bPBehind = true;
+ }
+ else if(aPropName == "BulletColor")
+ {
+ nBulletColor = ::Color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(rPropValue.Value));
+ bHasBulletColor = true;
+ }
+ else if ( aPropName == "BulletChar" )
+ {
+ aBulletChar = (*o3tl::doAccess<OUString>(rPropValue.Value))[ 0 ];
+ }
+ else if ( aPropName == "BulletFont" )
+ {
+ aFontDesc = *o3tl::doAccess<awt::FontDescriptor>(rPropValue.Value);
+ bHasFontDesc = true;
+
+ // Our numbullet dialog has set the wrong textencoding for our "StarSymbol" font,
+ // instead of a Unicode encoding the encoding RTL_TEXTENCODING_SYMBOL was used.
+ // Because there might exist a lot of damaged documents I added this two lines
+ // which fixes the bullet problem for the export.
+ if ( aFontDesc.Name.equalsIgnoreAsciiCase("StarSymbol") )
+ aFontDesc.CharSet = RTL_TEXTENCODING_MS_1252;
+
+ }
+ else if ( aPropName == "BulletRelSize" )
+ {
+ nBulletRelSize = *o3tl::doAccess<sal_Int16>(rPropValue.Value);
+ }
+ else if ( aPropName == "StartWith" )
+ {
+ nStartWith = *o3tl::doAccess<sal_Int16>(rPropValue.Value);
+ }
+ else if (aPropName == "GraphicBitmap")
+ {
+ auto xBitmap = rPropValue.Value.get<uno::Reference<awt::XBitmap>>();
+ xGraphic.set(xBitmap, uno::UNO_QUERY);
+ }
+ else if ( aPropName == "GraphicSize" )
+ {
+ aGraphicSize = *o3tl::doAccess<awt::Size>(rPropValue.Value);
+ SAL_INFO("oox.shape", "graphic size: " << aGraphicSize.Width << "x" << aGraphicSize.Height);
+ }
+ }
+
+ if (nNumberingType == SVX_NUM_NUMBER_NONE)
+ return;
+
+ Graphic aGraphic(xGraphic);
+ if (xGraphic.is() && aGraphic.GetType() != GraphicType::NONE)
+ {
+ tools::Long nFirstCharHeightMm = TransformMetric(fFirstCharHeight * 100.f, FieldUnit::POINT, FieldUnit::MM);
+ float fBulletSizeRel = aGraphicSize.Height / static_cast<float>(nFirstCharHeightMm) / OOX_BULLET_LIST_SCALE_FACTOR;
+
+ OUString sRelationId;
+
+ if (fBulletSizeRel < 1.0f)
+ {
+ // Add padding to get the bullet point centered in PPT
+ Size aDestSize(64, 64);
+ float fBulletSizeRelX = fBulletSizeRel / aGraphicSize.Height * aGraphicSize.Width;
+ tools::Long nPaddingX = std::max<tools::Long>(0, std::lround((aDestSize.Width() - fBulletSizeRelX * aDestSize.Width()) / 2.f));
+ tools::Long nPaddingY = std::lround((aDestSize.Height() - fBulletSizeRel * aDestSize.Height()) / 2.f);
+ tools::Rectangle aDestRect(nPaddingX, nPaddingY, aDestSize.Width() - nPaddingX, aDestSize.Height() - nPaddingY);
+
+ AlphaMask aMask(aDestSize);
+ aMask.Erase(255);
+ BitmapEx aSourceBitmap(aGraphic.GetBitmapEx());
+ aSourceBitmap.Scale(aDestRect.GetSize());
+ tools::Rectangle aSourceRect(Point(0, 0), aDestRect.GetSize());
+ BitmapEx aDestBitmap(Bitmap(aDestSize, vcl::PixelFormat::N24_BPP), aMask);
+ aDestBitmap.CopyPixel(aDestRect, aSourceRect, &aSourceBitmap);
+ Graphic aDestGraphic(aDestBitmap);
+ sRelationId = WriteImage(aDestGraphic);
+ fBulletSizeRel = 1.0f;
+ }
+ else
+ {
+ sRelationId = WriteImage(aGraphic);
+ }
+
+ mpFS->singleElementNS( XML_a, XML_buSzPct,
+ XML_val, OString::number(std::min<sal_Int32>(std::lround(100000.f * fBulletSizeRel), 400000)));
+ mpFS->startElementNS(XML_a, XML_buBlip);
+ mpFS->singleElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelationId);
+ mpFS->endElementNS( XML_a, XML_buBlip );
+ }
+ else
+ {
+ if(bHasBulletColor)
+ {
+ if (nBulletColor == COL_AUTO )
+ {
+ nBulletColor = ::Color(ColorTransparency, mbIsBackgroundDark ? 0xffffff : 0x000000);
+ }
+ mpFS->startElementNS(XML_a, XML_buClr);
+ WriteColor( nBulletColor );
+ mpFS->endElementNS( XML_a, XML_buClr );
+ }
+
+ if( nBulletRelSize && nBulletRelSize != 100 )
+ mpFS->singleElementNS( XML_a, XML_buSzPct,
+ XML_val, OString::number(std::clamp<sal_Int32>(1000*nBulletRelSize, 25000, 400000)));
+ if( bHasFontDesc )
+ {
+ if ( SVX_NUM_CHAR_SPECIAL == nNumberingType )
+ aBulletChar = SubstituteBullet( aBulletChar, aFontDesc );
+ mpFS->singleElementNS( XML_a, XML_buFont,
+ XML_typeface, aFontDesc.Name,
+ XML_charset, sax_fastparser::UseIf("2", aFontDesc.CharSet == awt::CharSet::SYMBOL));
+ }
+
+ OUString aAutoNumType = GetAutoNumType( nNumberingType, bSDot, bPBehind, bPBoth );
+
+ if (!aAutoNumType.isEmpty())
+ {
+ mpFS->singleElementNS(XML_a, XML_buAutoNum,
+ XML_type, aAutoNumType,
+ XML_startAt, sax_fastparser::UseIf(OString::number(nStartWith), nStartWith > 1));
+ }
+ else
+ {
+ mpFS->singleElementNS(XML_a, XML_buChar, XML_char, OUString(aBulletChar));
+ }
+ }
+}
+
+void DrawingML::WriteParagraphTabStops(const Reference<XPropertySet>& rXPropSet)
+{
+ css::uno::Sequence<css::style::TabStop> aTabStops;
+ if (GetProperty(rXPropSet, "ParaTabStops"))
+ aTabStops = *o3tl::doAccess<css::uno::Sequence<css::style::TabStop>>(mAny);
+
+ if (aTabStops.getLength() > 0)
+ mpFS->startElementNS(XML_a, XML_tabLst);
+
+ for (const css::style::TabStop& rTabStop : std::as_const(aTabStops))
+ {
+ OString sPosition = OString::number(GetPointFromCoordinate(rTabStop.Position));
+ OString sAlignment;
+ switch (rTabStop.Alignment)
+ {
+ case css::style::TabAlign_DECIMAL:
+ sAlignment = "dec";
+ break;
+ case css::style::TabAlign_RIGHT:
+ sAlignment = "r";
+ break;
+ case css::style::TabAlign_CENTER:
+ sAlignment = "ctr";
+ break;
+ case css::style::TabAlign_LEFT:
+ default:
+ sAlignment = "l";
+ }
+ mpFS->singleElementNS(XML_a, XML_tab, XML_algn, sAlignment, XML_pos, sPosition);
+ }
+ if (aTabStops.getLength() > 0)
+ mpFS->endElementNS(XML_a, XML_tabLst);
+}
+
+bool DrawingML::IsGroupShape( const Reference< XShape >& rXShape )
+{
+ bool bRet = false;
+ if ( rXShape.is() )
+ {
+ uno::Reference<lang::XServiceInfo> xServiceInfo(rXShape, uno::UNO_QUERY_THROW);
+ bRet = xServiceInfo->supportsService("com.sun.star.drawing.GroupShape");
+ }
+ return bRet;
+}
+
+sal_Int32 DrawingML::getBulletMarginIndentation (const Reference< XPropertySet >& rXPropSet,sal_Int16 nLevel, std::u16string_view propName)
+{
+ if (nLevel < 0 || !GetProperty(rXPropSet, "NumberingRules"))
+ return 0;
+
+ Reference< XIndexAccess > rXIndexAccess;
+
+ if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount())
+ return 0;
+
+ SAL_INFO("oox.shape", "numbering rules");
+
+ Sequence<PropertyValue> aPropertySequence;
+ rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence;
+
+ if (!aPropertySequence.hasElements())
+ return 0;
+
+ for ( const PropertyValue& rPropValue : std::as_const(aPropertySequence) )
+ {
+ OUString aPropName( rPropValue.Name );
+ SAL_INFO("oox.shape", "pro name: " << aPropName);
+ if ( aPropName == propName )
+ return *o3tl::doAccess<sal_Int32>(rPropValue.Value);
+ }
+
+ return 0;
+}
+
+const char* DrawingML::GetAlignment( style::ParagraphAdjust nAlignment )
+{
+ const char* sAlignment = nullptr;
+
+ switch( nAlignment )
+ {
+ case style::ParagraphAdjust_CENTER:
+ sAlignment = "ctr";
+ break;
+ case style::ParagraphAdjust_RIGHT:
+ sAlignment = "r";
+ break;
+ case style::ParagraphAdjust_BLOCK:
+ sAlignment = "just";
+ break;
+ default:
+ ;
+ }
+
+ return sAlignment;
+}
+
+void DrawingML::WriteLinespacing(const LineSpacing& rSpacing, float fFirstCharHeight)
+{
+ if( rSpacing.Mode == LineSpacingMode::PROP )
+ {
+ mpFS->singleElementNS( XML_a, XML_spcPct,
+ XML_val, OString::number(static_cast<sal_Int32>(rSpacing.Height)*1000));
+ }
+ else if (rSpacing.Mode == LineSpacingMode::MINIMUM
+ && fFirstCharHeight > static_cast<float>(rSpacing.Height) * 0.001 * 72.0 / 2.54)
+ {
+ // 100% proportional line spacing = single line spacing
+ mpFS->singleElementNS(XML_a, XML_spcPct, XML_val,
+ OString::number(static_cast<sal_Int32>(100000)));
+ }
+ else
+ {
+ mpFS->singleElementNS( XML_a, XML_spcPts,
+ XML_val, OString::number(std::lround(rSpacing.Height / 25.4 * 72)));
+ }
+}
+
+bool DrawingML::WriteParagraphProperties(const Reference<XTextContent>& rParagraph, const Reference<XPropertySet>& rXShapePropSet, float fFirstCharHeight, sal_Int32 nElement)
+{
+ Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY );
+ Reference< XPropertyState > rXPropState( rParagraph, UNO_QUERY );
+ PropertyState eState;
+
+ if( !rXPropSet.is() || !rXPropState.is() )
+ return false;
+
+ sal_Int16 nLevel = -1;
+ if (GetProperty(rXPropSet, "NumberingLevel"))
+ mAny >>= nLevel;
+
+ bool bWriteNumbering = true;
+ bool bForceZeroIndent = false;
+ if (mbPlaceholder)
+ {
+ Reference< text::XTextRange > xParaText(rParagraph, UNO_QUERY);
+ if (xParaText)
+ {
+ bool bNumberingOnThisLevel = false;
+ if (nLevel > -1)
+ {
+ Reference< XIndexAccess > xNumberingRules(rXPropSet->getPropertyValue("NumberingRules"), UNO_QUERY);
+ const PropertyValues& rNumRuleOfLevel = xNumberingRules->getByIndex(nLevel).get<PropertyValues>();
+ for (const PropertyValue& rRule : rNumRuleOfLevel)
+ if (rRule.Name == "NumberingType" && rRule.Value.hasValue())
+ bNumberingOnThisLevel = rRule.Value.get<sal_uInt16>() != style::NumberingType::NUMBER_NONE;
+ }
+
+ const bool bIsNumberingVisible = rXPropSet->getPropertyValue("NumberingIsNumber").get<bool>();
+ const bool bIsLineEmpty = !xParaText->getString().getLength();
+
+ bWriteNumbering = !bIsLineEmpty && bIsNumberingVisible && (nLevel != -1);
+ bForceZeroIndent = (!bIsNumberingVisible || bIsLineEmpty || !bNumberingOnThisLevel);
+ }
+
+ }
+
+ sal_Int16 nTmp = sal_Int16(style::ParagraphAdjust_LEFT);
+ if (GetProperty(rXPropSet, "ParaAdjust"))
+ mAny >>= nTmp;
+ style::ParagraphAdjust nAlignment = static_cast<style::ParagraphAdjust>(nTmp);
+
+ bool bHasLinespacing = false;
+ LineSpacing aLineSpacing;
+ if (GetPropertyAndState(rXPropSet, rXPropState, "ParaLineSpacing", eState)
+ && (mAny >>= aLineSpacing)
+ && (eState == beans::PropertyState_DIRECT_VALUE ||
+ // only export if it differs from the default 100% line spacing
+ aLineSpacing.Mode != LineSpacingMode::PROP || aLineSpacing.Height != 100))
+ bHasLinespacing = true;
+
+ bool bRtl = false;
+ if (GetProperty(rXPropSet, "WritingMode"))
+ {
+ sal_Int16 nWritingMode;
+ if( ( mAny >>= nWritingMode ) && nWritingMode == text::WritingMode2::RL_TB )
+ {
+ bRtl = true;
+ }
+ }
+
+ sal_Int32 nParaLeftMargin = 0;
+ sal_Int32 nParaFirstLineIndent = 0;
+
+ if (GetProperty(rXPropSet, "ParaLeftMargin"))
+ mAny >>= nParaLeftMargin;
+ if (GetProperty(rXPropSet, "ParaFirstLineIndent"))
+ mAny >>= nParaFirstLineIndent;
+
+ sal_Int32 nParaTopMargin = 0;
+ sal_Int32 nParaBottomMargin = 0;
+
+ if (GetProperty(rXPropSet, "ParaTopMargin"))
+ mAny >>= nParaTopMargin;
+ if (GetProperty(rXPropSet, "ParaBottomMargin"))
+ mAny >>= nParaBottomMargin;
+
+ sal_Int32 nLeftMargin = getBulletMarginIndentation ( rXPropSet, nLevel,u"LeftMargin");
+ sal_Int32 nLineIndentation = getBulletMarginIndentation ( rXPropSet, nLevel,u"FirstLineOffset");
+
+ if (bWriteNumbering && !bForceZeroIndent)
+ {
+ if (!(nLevel != -1
+ || nAlignment != style::ParagraphAdjust_LEFT
+ || bHasLinespacing))
+ return false;
+ }
+
+ // for autofitted textboxes, scale the indents
+ if (GetProperty(rXShapePropSet, "TextFitToSize") && mAny.get<TextFitToSizeType>() == TextFitToSizeType_AUTOFIT)
+ {
+ SvxShapeText* pTextShape = dynamic_cast<SvxShapeText*>(rXShapePropSet.get());
+ if (pTextShape)
+ {
+ SdrTextObj* pTextObject = dynamic_cast<SdrTextObj*>(pTextShape->GetSdrObject());
+ if (pTextObject)
+ {
+ const auto nFontScaleY = pTextObject->GetFontScaleY();
+ nLeftMargin = nLeftMargin * nFontScaleY / 100;
+ nLineIndentation = nLineIndentation * nFontScaleY / 100;
+ nParaLeftMargin = nParaLeftMargin * nFontScaleY / 100;
+ nParaFirstLineIndent = nParaFirstLineIndent * nFontScaleY / 100;
+ }
+ }
+ }
+
+ if (nParaLeftMargin) // For Paragraph
+ mpFS->startElementNS( XML_a, nElement,
+ XML_lvl, sax_fastparser::UseIf(OString::number(nLevel), nLevel > 0),
+ XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaLeftMargin)), nParaLeftMargin > 0),
+ XML_indent, sax_fastparser::UseIf(OString::number(!bForceZeroIndent ? oox::drawingml::convertHmmToEmu(nParaFirstLineIndent) : 0), (bForceZeroIndent || (nParaFirstLineIndent != 0))),
+ XML_algn, GetAlignment( nAlignment ),
+ XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl));
+ else
+ mpFS->startElementNS( XML_a, nElement,
+ XML_lvl, sax_fastparser::UseIf(OString::number(nLevel), nLevel > 0),
+ XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeftMargin)), nLeftMargin > 0),
+ XML_indent, sax_fastparser::UseIf(OString::number(!bForceZeroIndent ? oox::drawingml::convertHmmToEmu(nLineIndentation) : 0), (bForceZeroIndent || ( nLineIndentation != 0))),
+ XML_algn, GetAlignment( nAlignment ),
+ XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl));
+
+
+ if( bHasLinespacing )
+ {
+ mpFS->startElementNS(XML_a, XML_lnSpc);
+ WriteLinespacing(aLineSpacing, fFirstCharHeight);
+ mpFS->endElementNS( XML_a, XML_lnSpc );
+ }
+
+ if( nParaTopMargin != 0 )
+ {
+ mpFS->startElementNS(XML_a, XML_spcBef);
+ {
+ mpFS->singleElementNS( XML_a, XML_spcPts,
+ XML_val, OString::number(std::lround(nParaTopMargin / 25.4 * 72)));
+ }
+ mpFS->endElementNS( XML_a, XML_spcBef );
+ }
+
+ if( nParaBottomMargin != 0 )
+ {
+ mpFS->startElementNS(XML_a, XML_spcAft);
+ {
+ mpFS->singleElementNS( XML_a, XML_spcPts,
+ XML_val, OString::number(std::lround(nParaBottomMargin / 25.4 * 72)));
+ }
+ mpFS->endElementNS( XML_a, XML_spcAft );
+ }
+
+ if (!bWriteNumbering)
+ mpFS->singleElementNS(XML_a, XML_buNone);
+ else
+ WriteParagraphNumbering( rXPropSet, fFirstCharHeight, nLevel );
+
+ WriteParagraphTabStops( rXPropSet );
+
+ // do not end element for lstStyles since, defRPr should be stacked inside it
+ if( nElement != XML_lvl1pPr )
+ mpFS->endElementNS( XML_a, nElement );
+
+ return true;
+}
+
+void DrawingML::WriteLstStyles(const css::uno::Reference<css::text::XTextContent>& rParagraph,
+ bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
+ const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet)
+{
+ Reference<XEnumerationAccess> xAccess(rParagraph, UNO_QUERY);
+ if (!xAccess.is())
+ return;
+
+ Reference<XEnumeration> xEnumeration(xAccess->createEnumeration());
+ if (!xEnumeration.is())
+ return;
+
+
+ Reference<XTextRange> rRun;
+
+ if (!xEnumeration->hasMoreElements())
+ return;
+
+ Any aAny(xEnumeration->nextElement());
+ if (aAny >>= rRun)
+ {
+ float fFirstCharHeight = rnCharHeight / 1000.;
+ Reference<XPropertySet> xFirstRunPropSet(rRun, UNO_QUERY);
+ Reference<XPropertySetInfo> xFirstRunPropSetInfo
+ = xFirstRunPropSet->getPropertySetInfo();
+
+ if (xFirstRunPropSetInfo->hasPropertyByName("CharHeight"))
+ fFirstCharHeight = xFirstRunPropSet->getPropertyValue("CharHeight").get<float>();
+
+ mpFS->startElementNS(XML_a, XML_lstStyle);
+ if( !WriteParagraphProperties(rParagraph, rXShapePropSet, fFirstCharHeight, XML_lvl1pPr) )
+ mpFS->startElementNS(XML_a, XML_lvl1pPr);
+ WriteRunProperties(xFirstRunPropSet, false, XML_defRPr, true, rbOverridingCharHeight,
+ rnCharHeight, GetScriptType(rRun->getString()), rXShapePropSet);
+ mpFS->endElementNS(XML_a, XML_lvl1pPr);
+ mpFS->endElementNS(XML_a, XML_lstStyle);
+ }
+}
+
+void DrawingML::WriteParagraph( const Reference< XTextContent >& rParagraph,
+ bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
+ const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet)
+{
+ Reference< XEnumerationAccess > access( rParagraph, UNO_QUERY );
+ if( !access.is() )
+ return;
+
+ Reference< XEnumeration > enumeration( access->createEnumeration() );
+ if( !enumeration.is() )
+ return;
+
+ mpFS->startElementNS(XML_a, XML_p);
+
+ bool bPropertiesWritten = false;
+ while( enumeration->hasMoreElements() )
+ {
+ Reference< XTextRange > run;
+ Any any ( enumeration->nextElement() );
+
+ if (any >>= run)
+ {
+ if( !bPropertiesWritten )
+ {
+ float fFirstCharHeight = rnCharHeight / 1000.;
+ Reference< XPropertySet > xFirstRunPropSet (run, UNO_QUERY);
+ Reference< XPropertySetInfo > xFirstRunPropSetInfo = xFirstRunPropSet->getPropertySetInfo();
+ if( xFirstRunPropSetInfo->hasPropertyByName("CharHeight") )
+ {
+ fFirstCharHeight = xFirstRunPropSet->getPropertyValue("CharHeight").get<float>();
+ rnCharHeight = 100 * fFirstCharHeight;
+ rbOverridingCharHeight = true;
+ }
+ WriteParagraphProperties(rParagraph, rXShapePropSet, fFirstCharHeight, XML_pPr);
+ bPropertiesWritten = true;
+ }
+ WriteRun( run, rbOverridingCharHeight, rnCharHeight, rXShapePropSet);
+ }
+ }
+ Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY );
+ sal_Int16 nDummy = -1;
+ WriteRunProperties(rXPropSet, false, XML_endParaRPr, false, rbOverridingCharHeight,
+ rnCharHeight, nDummy, rXShapePropSet);
+
+ mpFS->endElementNS( XML_a, XML_p );
+}
+
+bool DrawingML::IsFontworkShape(const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet)
+{
+ bool bResult(false);
+ if (rXShapePropSet.is())
+ {
+ Sequence<PropertyValue> aCustomShapeGeometryProps;
+ if (GetProperty(rXShapePropSet, "CustomShapeGeometry"))
+ {
+ mAny >>= aCustomShapeGeometryProps;
+ uno::Sequence<beans::PropertyValue> aTextPathSeq;
+ for (const auto& rProp : std::as_const(aCustomShapeGeometryProps))
+ {
+ if (rProp.Name == "TextPath")
+ {
+ rProp.Value >>= aTextPathSeq;
+ for (const auto& rTextPathItem : std::as_const(aTextPathSeq))
+ {
+ if (rTextPathItem.Name == "TextPath")
+ {
+ rTextPathItem.Value >>= bResult;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ return bResult;
+}
+
+void DrawingML::WriteText(const Reference<XInterface>& rXIface, bool bBodyPr, bool bText,
+ sal_Int32 nXmlNamespace, bool bWritePropertiesAsLstStyles)
+{
+ // ToDo: Fontwork in DOCX
+ uno::Reference<XText> xXText(rXIface, UNO_QUERY);
+ if( !xXText.is() )
+ return;
+
+ uno::Reference<drawing::XShape> xShape(rXIface, UNO_QUERY);
+ uno::Reference<XPropertySet> rXPropSet(rXIface, UNO_QUERY);
+
+ sal_Int32 nTextPreRotateAngle = 0;
+ double nTextRotateAngle = 0;
+
+ constexpr const sal_Int32 constDefaultLeftRightInset = 254;
+ constexpr const sal_Int32 constDefaultTopBottomInset = 127;
+ sal_Int32 nLeft = constDefaultLeftRightInset;
+ sal_Int32 nRight = constDefaultLeftRightInset;
+ sal_Int32 nTop = constDefaultTopBottomInset;
+ sal_Int32 nBottom = constDefaultTopBottomInset;
+
+ // top inset looks a bit different compared to ppt export
+ // check if something related doesn't work as expected
+ if (GetProperty(rXPropSet, "TextLeftDistance"))
+ mAny >>= nLeft;
+ if (GetProperty(rXPropSet, "TextRightDistance"))
+ mAny >>= nRight;
+ if (GetProperty(rXPropSet, "TextUpperDistance"))
+ mAny >>= nTop;
+ if (GetProperty(rXPropSet, "TextLowerDistance"))
+ mAny >>= nBottom;
+
+ // Transform the text distance values so they are compatible with OOXML insets
+ if (xShape.is())
+ {
+ sal_Int32 nTextHeight = xShape->getSize().Height; // Hmm, default
+
+ // CustomShape can have text area different from shape rectangle
+ auto* pCustomShape
+ = dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape));
+ if (pCustomShape)
+ {
+ const EnhancedCustomShape2d aCustomShape2d(*pCustomShape);
+ nTextHeight = aCustomShape2d.GetTextRect().getHeight();
+ if (DOCUMENT_DOCX == meDocumentType)
+ nTextHeight = convertTwipToMm100(nTextHeight);
+ }
+
+ if (nTop + nBottom >= nTextHeight)
+ {
+ // Effective bottom would be above effective top of text area. LO normalizes the
+ // effective text area in such case implicitely for rendering. MS needs indents so that
+ // the result is the normalized effective text area.
+ std::swap(nTop, nBottom);
+ nTop = nTextHeight - nTop;
+ nBottom = nTextHeight - nBottom;
+ }
+ }
+
+ TextVerticalAdjust eVerticalAlignment( TextVerticalAdjust_TOP );
+ const char* sVerticalAlignment = nullptr;
+ if (GetProperty(rXPropSet, "TextVerticalAdjust"))
+ mAny >>= eVerticalAlignment;
+ sVerticalAlignment = GetTextVerticalAdjust(eVerticalAlignment);
+
+ const char* sWritingMode = nullptr;
+ bool bVertical = false;
+ if (GetProperty(rXPropSet, "TextWritingMode"))
+ {
+ WritingMode eMode;
+
+ if( ( mAny >>= eMode ) && eMode == WritingMode_TB_RL )
+ {
+ sWritingMode = "eaVert";
+ bVertical = true;
+ }
+ }
+
+ bool bIsFontworkShape(IsFontworkShape(rXPropSet));
+ Sequence<drawing::EnhancedCustomShapeAdjustmentValue> aAdjustmentSeq;
+ uno::Sequence<beans::PropertyValue> aTextPathSeq;
+ bool bScaleX(false);
+ OUString sShapeType("non-primitive");
+ // ToDo move to InteropGrabBag
+ OUString sMSWordPresetTextWarp;
+
+ if (GetProperty(rXPropSet, "CustomShapeGeometry"))
+ {
+ Sequence< PropertyValue > aProps;
+ if ( mAny >>= aProps )
+ {
+ for ( const auto& rProp : std::as_const(aProps) )
+ {
+ if ( rProp.Name == "TextPreRotateAngle" && ( rProp.Value >>= nTextPreRotateAngle ) )
+ {
+ if ( nTextPreRotateAngle == -90 )
+ {
+ sWritingMode = "vert";
+ bVertical = true;
+ }
+ else if ( nTextPreRotateAngle == -270 )
+ {
+ sWritingMode = "vert270";
+ bVertical = true;
+ }
+ }
+ else if (rProp.Name == "AdjustmentValues")
+ rProp.Value >>= aAdjustmentSeq;
+ else if( rProp.Name == "TextRotateAngle" )
+ rProp.Value >>= nTextRotateAngle;
+ else if (rProp.Name == "Type")
+ rProp.Value >>= sShapeType;
+ else if (rProp.Name == "TextPath")
+ {
+ rProp.Value >>= aTextPathSeq;
+ for (const auto& rTextPathItem : std::as_const(aTextPathSeq))
+ {
+ if (rTextPathItem.Name == "ScaleX")
+ rTextPathItem.Value >>= bScaleX;
+ }
+ }
+ else if (rProp.Name == "PresetTextWarp")
+ rProp.Value >>= sMSWordPresetTextWarp;
+ }
+ }
+ }
+ else
+ {
+ if (mpTextExport)
+ {
+ if (xShape)
+ {
+ auto xTextFrame = mpTextExport->GetUnoTextFrame(xShape);
+ if (xTextFrame)
+ {
+ uno::Reference<beans::XPropertySet> xPropSet(xTextFrame, uno::UNO_QUERY);
+ auto aAny = xPropSet->getPropertyValue("WritingMode");
+ sal_Int16 nWritingMode;
+ if (aAny >>= nWritingMode)
+ {
+ switch (nWritingMode)
+ {
+ case WritingMode2::TB_RL:
+ sWritingMode = "vert";
+ bVertical = true;
+ break;
+ case WritingMode2::BT_LR:
+ sWritingMode = "vert270";
+ bVertical = true;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ OUString sPresetWarp(PresetGeometryTypeNames::GetMsoName(sShapeType));
+ // ODF may have user defined TextPath, use "textPlain" as ersatz.
+ if (sPresetWarp.isEmpty())
+ sPresetWarp = bIsFontworkShape ? std::u16string_view(u"textPlain") : std::u16string_view(u"textNoShape");
+
+ bool bFromWordArt = !bScaleX
+ && ( sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp"
+ || sPresetWarp == "textButton" || sPresetWarp == "textCircle");
+
+ TextHorizontalAdjust eHorizontalAlignment( TextHorizontalAdjust_CENTER );
+ bool bHorizontalCenter = false;
+ if (GetProperty(rXPropSet, "TextHorizontalAdjust"))
+ mAny >>= eHorizontalAlignment;
+ if( eHorizontalAlignment == TextHorizontalAdjust_CENTER )
+ bHorizontalCenter = true;
+ else if( bVertical && eHorizontalAlignment == TextHorizontalAdjust_LEFT )
+ sVerticalAlignment = "b";
+
+ bool bHasWrap = false;
+ bool bWrap = false;
+ // Only custom shapes obey the TextWordWrap option, normal text always wraps.
+ if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, "TextWordWrap"))
+ {
+ mAny >>= bWrap;
+ bHasWrap = true;
+ }
+
+ if (bBodyPr)
+ {
+ const char* pWrap = (bHasWrap && !bWrap) || bIsFontworkShape ? "none" : nullptr;
+ if (GetDocumentType() == DOCUMENT_DOCX)
+ {
+ // In case of DOCX, if we want to have the same effect as
+ // TextShape's automatic word wrapping, then we need to set
+ // wrapping to square.
+ uno::Reference<lang::XServiceInfo> xServiceInfo(rXIface, uno::UNO_QUERY);
+ if (xServiceInfo.is() && xServiceInfo->supportsService("com.sun.star.drawing.TextShape"))
+ pWrap = "square";
+ }
+
+ std::optional<OUString> sHorzOverflow;
+ std::optional<OUString> sVertOverflow;
+ sal_Int32 nShapeRotateAngle = rXPropSet->getPropertyValue("RotateAngle").get<sal_Int32>() / 300;
+ sal_Int16 nCols = 0;
+ sal_Int32 nColSpacing = -1;
+ if (GetProperty(rXPropSet, "TextColumns"))
+ {
+ if (css::uno::Reference<css::text::XTextColumns> xCols{ mAny, css::uno::UNO_QUERY })
+ {
+ nCols = xCols->getColumnCount();
+ if (css::uno::Reference<css::beans::XPropertySet> xProps{ mAny,
+ css::uno::UNO_QUERY })
+ {
+ if (GetProperty(xProps, "AutomaticDistance"))
+ mAny >>= nColSpacing;
+ }
+ }
+ }
+
+ std::optional<OString> isUpright;
+ if (GetProperty(rXPropSet, "InteropGrabBag"))
+ {
+ if (rXPropSet->getPropertySetInfo()->hasPropertyByName("InteropGrabBag"))
+ {
+ bool bUpright = false;
+ sal_Int32 nOldShapeRotation = 0;
+ sal_Int32 nOldTextRotation = 0;
+ uno::Sequence<beans::PropertyValue> aGrabBag;
+ rXPropSet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
+ for (const auto& aProp : std::as_const(aGrabBag))
+ {
+ if (aProp.Name == "Upright")
+ {
+ aProp.Value >>= bUpright;
+ isUpright = OString(bUpright ? "1" : "0");
+ }
+ else if (aProp.Name == "horzOverflow")
+ {
+ OUString sValue;
+ aProp.Value >>= sValue;
+ sHorzOverflow = sValue;
+ }
+ else if (aProp.Name == "vertOverflow")
+ {
+ OUString sValue;
+ aProp.Value >>= sValue;
+ sVertOverflow = sValue;
+ }
+ }
+ if (bUpright)
+ {
+ for (const auto& aProp : std::as_const(aGrabBag))
+ {
+ if (aProp.Name == "nShapeRotationAtImport")
+ aProp.Value >>= nOldShapeRotation;
+ else if (aProp.Name == "nTextRotationAtImport")
+ aProp.Value >>= nOldTextRotation;
+ }
+ // So our shape with the textbox in it was not rotated.
+ // Keep upright and make the preRotateAngle 0, it is an attribute
+ // of textBodyPr and must be 0 when upright is true, otherwise
+ // bad rotation happens in MSO.
+ if (nShapeRotateAngle == nOldShapeRotation && nShapeRotateAngle == nOldTextRotation)
+ nTextPreRotateAngle = 0;
+ // So we rotated the shape, in this case lose upright and do
+ // as LO normally does.
+ else
+ isUpright.reset();
+ }
+ }
+ }
+
+ mpFS->startElementNS( (nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr,
+ XML_numCol, sax_fastparser::UseIf(OString::number(nCols), nCols > 0),
+ XML_spcCol, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nColSpacing)), nCols > 0 && nColSpacing >= 0),
+ XML_wrap, pWrap,
+ XML_horzOverflow, sHorzOverflow,
+ XML_vertOverflow, sVertOverflow,
+ XML_fromWordArt, sax_fastparser::UseIf("1", bFromWordArt),
+ XML_lIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeft)), nLeft != constDefaultLeftRightInset),
+ XML_rIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nRight)), nRight != constDefaultLeftRightInset),
+ XML_tIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nTop)), nTop != constDefaultTopBottomInset),
+ XML_bIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nBottom)), nBottom != constDefaultTopBottomInset),
+ XML_anchor, sVerticalAlignment,
+ XML_anchorCtr, sax_fastparser::UseIf("1", bHorizontalCenter),
+ XML_vert, sWritingMode,
+ XML_upright, isUpright,
+ XML_rot, sax_fastparser::UseIf(oox::drawingml::calcRotationValue((nTextPreRotateAngle + nTextRotateAngle) * 100), (nTextPreRotateAngle + nTextRotateAngle) != 0));
+ if (bIsFontworkShape)
+ {
+ if (aAdjustmentSeq.hasElements())
+ {
+ mpFS->startElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp);
+ mpFS->startElementNS(XML_a, XML_avLst);
+ bool bHasTwoHandles(
+ sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour"
+ || sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour"
+ || sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1"
+ || sPresetWarp == "textWave2" || sPresetWarp == "textWave4");
+ for (sal_Int32 i = 0, nElems = aAdjustmentSeq.getLength(); i < nElems; ++i )
+ {
+ OString sName = "adj" + (bHasTwoHandles ? OString::number(i + 1) : OString());
+ double fValue(0.0);
+ if (aAdjustmentSeq[i].Value.getValueTypeClass() == TypeClass_DOUBLE)
+ aAdjustmentSeq[i].Value >>= fValue;
+ else
+ {
+ sal_Int32 nNumber(0);
+ aAdjustmentSeq[i].Value >>= nNumber;
+ fValue = static_cast<double>(nNumber);
+ }
+ // Convert from binary coordinate system with viewBox "0 0 21600 21600" and simple degree
+ // to DrawingML with coordinate range 0..100000 and angle in 1/60000 degree.
+ // Reverse to conversion in lcl_createPresetShape in drawingml/shape.cxx on import.
+ if (sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp"
+ || sPresetWarp == "textButton" || sPresetWarp == "textCircle"
+ || ((i == 0)
+ && (sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour"
+ || sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour")))
+ {
+ fValue *= 60000.0;
+ if (fValue < 0)
+ fValue += 21600000;
+ }
+ else if ((i == 1)
+ && (sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1"
+ || sPresetWarp == "textWave2" || sPresetWarp == "textWave4"))
+ {
+ fValue = fValue / 0.216 - 50000.0;
+ }
+ else if ((i == 1)
+ && (sPresetWarp == "textArchDownPour"
+ || sPresetWarp == "textArchUpPour"
+ || sPresetWarp == "textButtonPour"
+ || sPresetWarp == "textCirclePour"))
+ {
+ fValue /= 0.108;
+ }
+ else
+ {
+ fValue /= 0.216;
+ }
+ OString sFmla = "val " + OString::number(std::lround(fValue));
+ mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla);
+ // There exists faulty Favorite shapes with one handle but two adjustment values.
+ if (!bHasTwoHandles)
+ break;
+ }
+ mpFS->endElementNS(XML_a, XML_avLst);
+ mpFS->endElementNS(XML_a, XML_prstTxWarp);
+ }
+ else
+ {
+ mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp);
+ }
+ }
+ else if (GetDocumentType() == DOCUMENT_DOCX)
+ {
+ // interim solution for fdo#80897, roundtrip DOCX > LO > DOCX
+ if (!sMSWordPresetTextWarp.isEmpty())
+ mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sMSWordPresetTextWarp);
+ }
+
+ if (GetDocumentType() == DOCUMENT_DOCX || GetDocumentType() == DOCUMENT_XLSX)
+ {
+ // tdf#112312: only custom shapes obey the TextAutoGrowHeight option
+ bool bTextAutoGrowHeight = false;
+ auto pSdrObjCustomShape = xShape.is() ? dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape)) : nullptr;
+ if (pSdrObjCustomShape && GetProperty(rXPropSet, "TextAutoGrowHeight"))
+ {
+ mAny >>= bTextAutoGrowHeight;
+ }
+ mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit));
+ }
+ if (GetDocumentType() == DOCUMENT_PPTX)
+ {
+ TextFitToSizeType eFit = TextFitToSizeType_NONE;
+ if (GetProperty(rXPropSet, "TextFitToSize"))
+ mAny >>= eFit;
+
+ if (eFit == TextFitToSizeType_AUTOFIT)
+ {
+ const sal_Int32 MAX_SCALE_VAL = 100000;
+ sal_Int32 nFontScale = MAX_SCALE_VAL;
+ SvxShapeText* pTextShape = dynamic_cast<SvxShapeText*>(rXIface.get());
+ if (pTextShape)
+ {
+ SdrTextObj* pTextObject = dynamic_cast<SdrTextObj*>(pTextShape->GetSdrObject());
+ if (pTextObject)
+ nFontScale = pTextObject->GetFontScaleY() * 1000;
+ }
+
+ mpFS->singleElementNS(XML_a, XML_normAutofit, XML_fontScale,
+ sax_fastparser::UseIf(OString::number(nFontScale), nFontScale < MAX_SCALE_VAL && nFontScale > 0));
+ }
+ else
+ {
+ // tdf#127030: Only custom shapes obey the TextAutoGrowHeight option.
+ bool bTextAutoGrowHeight = false;
+ if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, "TextAutoGrowHeight"))
+ mAny >>= bTextAutoGrowHeight;
+ mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit));
+ }
+ }
+
+ Write3DEffects( rXPropSet, /*bIsText=*/true );
+
+ mpFS->endElementNS((nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr);
+ }
+
+ Reference< XEnumerationAccess > access( xXText, UNO_QUERY );
+ if( !access.is() || !bText )
+ return;
+
+ Reference< XEnumeration > enumeration( access->createEnumeration() );
+ if( !enumeration.is() )
+ return;
+
+ SdrObject* pSdrObject = xShape.is() ? SdrObject::getSdrObjectFromXShape(xShape) : nullptr;
+ const SdrTextObj* pTxtObj = dynamic_cast<SdrTextObj*>( pSdrObject );
+ if (pTxtObj && mpTextExport)
+ {
+ std::optional<OutlinerParaObject> pParaObj;
+
+ /*
+ #i13885#
+ When the object is actively being edited, that text is not set into
+ the objects normal text object, but lives in a separate object.
+ */
+ if (pTxtObj->IsTextEditActive())
+ {
+ pParaObj = pTxtObj->CreateEditOutlinerParaObject();
+ }
+ else if (pTxtObj->GetOutlinerParaObject())
+ pParaObj = *pTxtObj->GetOutlinerParaObject();
+
+ if (pParaObj)
+ {
+ // this is reached only in case some text is attached to the shape
+ mpTextExport->WriteOutliner(*pParaObj);
+ }
+ return;
+ }
+
+ bool bOverridingCharHeight = false;
+ sal_Int32 nCharHeight = -1;
+ bool bFirstParagraph = true;
+
+ // tdf#144092 For shapes without text: Export run properties (into
+ // endParaRPr) from the shape's propset instead of the paragraph's.
+ if(xXText->getString().isEmpty() && enumeration->hasMoreElements())
+ {
+ Any aAny (enumeration->nextElement());
+ Reference<XTextContent> xParagraph;
+ if( aAny >>= xParagraph )
+ {
+ mpFS->startElementNS(XML_a, XML_p);
+ WriteParagraphProperties(xParagraph, rXPropSet, nCharHeight, XML_pPr);
+ sal_Int16 nDummy = -1;
+ WriteRunProperties(rXPropSet, false, XML_endParaRPr, false,
+ bOverridingCharHeight, nCharHeight, nDummy, rXPropSet);
+ mpFS->endElementNS(XML_a, XML_p);
+ }
+ return;
+ }
+
+ while( enumeration->hasMoreElements() )
+ {
+ Reference< XTextContent > paragraph;
+ Any any ( enumeration->nextElement() );
+
+ if( any >>= paragraph)
+ {
+ if (bFirstParagraph && bWritePropertiesAsLstStyles)
+ WriteLstStyles(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet);
+
+ WriteParagraph(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet);
+ bFirstParagraph = false;
+ }
+ }
+}
+
+void DrawingML::WritePresetShape( const OString& pShape , std::vector< std::pair<sal_Int32,sal_Int32>> & rAvList )
+{
+ mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
+ if ( !rAvList.empty() )
+ {
+
+ mpFS->startElementNS(XML_a, XML_avLst);
+ for (auto const& elem : rAvList)
+ {
+ OString sName = "adj" + ( ( elem.first > 0 ) ? OString::number(elem.first) : OString() );
+ OString sFmla = "val " + OString::number( elem.second );
+
+ mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla);
+ }
+ mpFS->endElementNS( XML_a, XML_avLst );
+ }
+ else
+ mpFS->singleElementNS(XML_a, XML_avLst);
+
+ mpFS->endElementNS( XML_a, XML_prstGeom );
+}
+
+void DrawingML::WritePresetShape( const OString& pShape )
+{
+ mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
+ mpFS->singleElementNS(XML_a, XML_avLst);
+ mpFS->endElementNS( XML_a, XML_prstGeom );
+}
+
+static std::map< OString, std::vector<OString> > lcl_getAdjNames()
+{
+ std::map< OString, std::vector<OString> > aRet;
+
+ OUString aPath("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/filter/oox-drawingml-adj-names");
+ rtl::Bootstrap::expandMacros(aPath);
+ SvFileStream aStream(aPath, StreamMode::READ);
+ if (aStream.GetError() != ERRCODE_NONE)
+ SAL_WARN("oox.shape", "failed to open oox-drawingml-adj-names");
+ OStringBuffer aLine;
+ bool bNotDone = aStream.ReadLine(aLine);
+ while (bNotDone)
+ {
+ sal_Int32 nIndex = 0;
+ // Each line is in a "key\tvalue" format: read the key, the rest is the value.
+ OString aKey( o3tl::getToken(aLine, 0, '\t', nIndex) );
+ OString aValue( std::string_view(aLine).substr(nIndex) );
+ aRet[aKey].push_back(aValue);
+ bNotDone = aStream.ReadLine(aLine);
+ }
+ return aRet;
+}
+
+void DrawingML::WritePresetShape( const OString& pShape, MSO_SPT eShapeType, bool bPredefinedHandlesUsed, const PropertyValue& rProp )
+{
+ static std::map< OString, std::vector<OString> > aAdjMap = lcl_getAdjNames();
+ // If there are predefined adj names for this shape type, look them up now.
+ std::vector<OString> aAdjustments;
+ if (aAdjMap.find(pShape) != aAdjMap.end())
+ aAdjustments = aAdjMap[pShape];
+
+ mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
+ mpFS->startElementNS(XML_a, XML_avLst);
+
+ Sequence< drawing::EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq;
+ if ( ( rProp.Value >>= aAdjustmentSeq )
+ && eShapeType != mso_sptActionButtonForwardNext // we have adjustments values for these type of shape, but MSO doesn't like them
+ && eShapeType != mso_sptActionButtonBackPrevious // so they are now disabled
+ && pShape != "rect" //some shape types are commented out in pCustomShapeTypeTranslationTable[] & are being defaulted to rect & rect does not have adjustment values/name.
+ )
+ {
+ SAL_INFO("oox.shape", "adj seq len: " << aAdjustmentSeq.getLength());
+ sal_Int32 nAdjustmentsWhichNeedsToBeConverted = 0;
+ if ( bPredefinedHandlesUsed )
+ EscherPropertyContainer::LookForPolarHandles( eShapeType, nAdjustmentsWhichNeedsToBeConverted );
+
+ sal_Int32 nValue, nLength = aAdjustmentSeq.getLength();
+ // 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.
+ // Sometimes there are more values than needed, so we ignore the excessive ones.
+ if (aAdjustments.size() <= o3tl::make_unsigned(nLength))
+ {
+ for (sal_Int32 i = 0; i < static_cast<sal_Int32>(aAdjustments.size()); i++)
+ {
+ if( EscherPropertyContainer::GetAdjustmentValue( aAdjustmentSeq[ i ], i, nAdjustmentsWhichNeedsToBeConverted, nValue ) )
+ {
+ // If the document model doesn't have an adjustment name (e.g. shape was created from VML), then take it from the predefined list.
+ OString aAdjName = aAdjustmentSeq[i].Name.isEmpty()
+ ? aAdjustments[i]
+ : aAdjustmentSeq[i].Name.toUtf8();
+
+ mpFS->singleElementNS( XML_a, XML_gd,
+ XML_name, aAdjName,
+ XML_fmla, "val " + OString::number(nValue));
+ }
+ }
+ }
+ }
+
+ mpFS->endElementNS( XML_a, XML_avLst );
+ mpFS->endElementNS( XML_a, XML_prstGeom );
+}
+
+namespace // helpers for DrawingML::WriteCustomGeometry
+{
+sal_Int32
+FindNextCommandEndSubpath(const sal_Int32 nStart,
+ const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments)
+{
+ sal_Int32 i = nStart < 0 ? 0 : nStart;
+ while (i < rSegments.getLength() && rSegments[i].Command != ENDSUBPATH)
+ i++;
+ return i;
+}
+
+bool HasCommandInSubPath(const sal_Int16 nCommand, const sal_Int32 nFirst, const sal_Int32 nLast,
+ const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments)
+{
+ for (sal_Int32 i = nFirst < 0 ? 0 : nFirst; i <= nLast && i < rSegments.getLength(); i++)
+ {
+ if (rSegments[i].Command == nCommand)
+ return true;
+ }
+ return false;
+}
+
+// Ellipse is given by radii fwR and fhR and center (fCx|fCy). The ray from center through point RayP
+// intersects the ellipse in point S and this point S has angle fAngleDeg in degrees.
+void getEllipsePointAndAngleFromRayPoint(double& rfAngleDeg, double& rfSx, double& rfSy,
+ const double fWR, const double fHR, const double fCx,
+ const double fCy, const double fRayPx, const double fRayPy)
+{
+ if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR))
+ {
+ rfSx = fCx; // needed for getting new 'current point'
+ rfSy = fCy;
+ }
+ else
+ {
+ // center ellipse at origin, stretch in y-direction to circle, flip to Math orientation
+ // and get angle
+ double fCircleMathAngle = atan2(-fWR / fHR * (fRayPy - fCy), fRayPx - fCx);
+ // use angle for intersection point on circle and stretch back to ellipse
+ double fPointMathEllipse_x = fWR * cos(fCircleMathAngle);
+ double fPointMathEllipse_y = fHR * sin(fCircleMathAngle);
+ // get angle of intersection point on ellipse
+ double fEllipseMathAngle = atan2(fPointMathEllipse_y, fPointMathEllipse_x);
+ // convert from Math to View orientation and shift ellipse back from origin
+ rfAngleDeg = -basegfx::rad2deg(fEllipseMathAngle);
+ rfSx = fPointMathEllipse_x + fCx;
+ rfSy = -fPointMathEllipse_y + fCy;
+ }
+}
+
+void getEllipsePointFromViewAngle(double& rfSx, double& rfSy, const double fWR, const double fHR,
+ const double fCx, const double fCy, const double fViewAngleDeg)
+{
+ if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR))
+ {
+ rfSx = fCx; // needed for getting new 'current point'
+ rfSy = fCy;
+ }
+ else
+ {
+ double fX = cos(basegfx::deg2rad(fViewAngleDeg)) / fWR;
+ double fY = sin(basegfx::deg2rad(fViewAngleDeg)) / fHR;
+ double fRadius = 1.0 / std::hypot(fX, fY);
+ rfSx = fCx + fRadius * cos(basegfx::deg2rad(fViewAngleDeg));
+ rfSy = fCy + fRadius * sin(basegfx::deg2rad(fViewAngleDeg));
+ }
+}
+
+sal_Int32 GetCustomGeometryPointValue(const css::drawing::EnhancedCustomShapeParameter& rParam,
+ const EnhancedCustomShape2d& rCustomShape2d,
+ const bool bReplaceGeoWidth, const bool bReplaceGeoHeight)
+{
+ double fValue = 0.0;
+ rCustomShape2d.GetParameter(fValue, rParam, bReplaceGeoWidth, bReplaceGeoHeight);
+ sal_Int32 nValue(std::lround(fValue));
+
+ return nValue;
+}
+
+struct TextAreaRect
+{
+ OString left;
+ OString top;
+ OString right;
+ OString bottom;
+};
+
+struct Guide
+{
+ OString sName;
+ OString sFormula;
+};
+
+void prepareTextArea(const EnhancedCustomShape2d& rEnhancedCustomShape2d,
+ std::vector<Guide>& rGuideList, TextAreaRect& rTextAreaRect)
+{
+ tools::Rectangle aTextAreaLO(rEnhancedCustomShape2d.GetTextRect());
+ tools::Rectangle aLogicRectLO(rEnhancedCustomShape2d.GetLogicRect());
+ if (aTextAreaLO == aLogicRectLO)
+ {
+ rTextAreaRect.left = "l";
+ rTextAreaRect.top = "t";
+ rTextAreaRect.right = "r";
+ rTextAreaRect.bottom = "b";
+ return;
+ }
+ // Flip aTextAreaLO if shape is flipped
+ if (rEnhancedCustomShape2d.IsFlipHorz())
+ aTextAreaLO.Move((aLogicRectLO.Center().X() - aTextAreaLO.Center().X()) * 2, 0);
+ if (rEnhancedCustomShape2d.IsFlipVert())
+ aTextAreaLO.Move(0, (aLogicRectLO.Center().Y() - aTextAreaLO.Center().Y()) * 2);
+
+ Guide aGuide;
+ // horizontal
+ const sal_Int32 nWidth = aLogicRectLO.Right() - aLogicRectLO.Left();
+ const OString sWidth = OString::number(oox::drawingml::convertHmmToEmu(nWidth));
+
+ // left
+ aGuide.sName = "textAreaLeft";
+ sal_Int32 nHelp = aTextAreaLO.Left() - aLogicRectLO.Left();
+ const OString sLeft = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
+ aGuide.sFormula = "*/ " + sLeft + " w " + sWidth;
+ rTextAreaRect.left = aGuide.sName;
+ rGuideList.push_back(aGuide);
+
+ // right
+ aGuide.sName = "textAreaRight";
+ nHelp = aTextAreaLO.Right() - aLogicRectLO.Left();
+ const OString sRight = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
+ aGuide.sFormula = "*/ " + sRight + " w " + sWidth;
+ rTextAreaRect.right = aGuide.sName;
+ rGuideList.push_back(aGuide);
+
+ // vertical
+ const sal_Int32 nHeight = aLogicRectLO.Bottom() - aLogicRectLO.Top();
+ const OString sHeight = OString::number(oox::drawingml::convertHmmToEmu(nHeight));
+
+ // top
+ aGuide.sName = "textAreaTop";
+ nHelp = aTextAreaLO.Top() - aLogicRectLO.Top();
+ const OString sTop = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
+ aGuide.sFormula = "*/ " + sTop + " h " + sHeight;
+ rTextAreaRect.top = aGuide.sName;
+ rGuideList.push_back(aGuide);
+
+ // bottom
+ aGuide.sName = "textAreaBottom";
+ nHelp = aTextAreaLO.Bottom() - aLogicRectLO.Top();
+ const OString sBottom = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
+ aGuide.sFormula = "*/ " + sBottom + " h " + sHeight;
+ rTextAreaRect.bottom = aGuide.sName;
+ rGuideList.push_back(aGuide);
+
+ return;
+}
+}
+
+bool DrawingML::WriteCustomGeometry(
+ const Reference< XShape >& rXShape,
+ const SdrObjCustomShape& rSdrObjCustomShape)
+{
+ uno::Reference< beans::XPropertySet > aXPropSet;
+ uno::Any aAny( rXShape->queryInterface(cppu::UnoType<beans::XPropertySet>::get()));
+
+ if ( ! (aAny >>= aXPropSet) )
+ return false;
+
+ try
+ {
+ aAny = aXPropSet->getPropertyValue( "CustomShapeGeometry" );
+ if ( !aAny.hasValue() )
+ return false;
+ }
+ catch( const ::uno::Exception& )
+ {
+ return false;
+ }
+
+ auto pGeometrySeq = o3tl::tryAccess<uno::Sequence<beans::PropertyValue>>(aAny);
+ if (!pGeometrySeq)
+ return false;
+
+ auto pPathProp = std::find_if(std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq),
+ [](const PropertyValue& rProp) { return rProp.Name == "Path"; });
+ if (pPathProp == std::cend(*pGeometrySeq))
+ return false;
+
+ uno::Sequence<beans::PropertyValue> aPathProp;
+ pPathProp->Value >>= aPathProp;
+
+ uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs;
+ uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments;
+ uno::Sequence<awt::Size> aPathSize;
+ bool bReplaceGeoWidth = false;
+ bool bReplaceGeoHeight = false;
+ for (const beans::PropertyValue& rPathProp : std::as_const(aPathProp))
+ {
+ if (rPathProp.Name == "Coordinates")
+ rPathProp.Value >>= aPairs;
+ else if (rPathProp.Name == "Segments")
+ rPathProp.Value >>= aSegments;
+ else if (rPathProp.Name == "SubViewSize")
+ rPathProp.Value >>= aPathSize;
+ else if (rPathProp.Name == "StretchX")
+ bReplaceGeoWidth = true;
+ else if (rPathProp.Name == "StretchY")
+ bReplaceGeoHeight = true;
+ }
+
+ if ( !aPairs.hasElements() )
+ return false;
+
+ if ( !aSegments.hasElements() )
+ {
+ aSegments = uno::Sequence<drawing::EnhancedCustomShapeSegment>
+ {
+ { MOVETO, 1 },
+ { LINETO,
+ static_cast<sal_Int16>(std::min( aPairs.getLength() - 1, sal_Int32(32767) )) },
+ { CLOSESUBPATH, 0 },
+ { ENDSUBPATH, 0 }
+ };
+ };
+
+ int nExpectedPairCount = std::accumulate(std::cbegin(aSegments), std::cend(aSegments), 0,
+ [](const int nSum, const drawing::EnhancedCustomShapeSegment& rSegment) { return nSum + rSegment.Count; });
+
+ if ( nExpectedPairCount > aPairs.getLength() )
+ {
+ SAL_WARN("oox.shape", "Segments need " << nExpectedPairCount << " coordinates, but Coordinates have only " << aPairs.getLength() << " pairs.");
+ return false;
+ }
+
+ // A EnhancedCustomShape2d caches the equation results. Therefore we use only one of it for the
+ // entire method.
+ const EnhancedCustomShape2d aCustomShape2d(const_cast<SdrObjCustomShape&>(rSdrObjCustomShape));
+
+ TextAreaRect aTextAreaRect;
+ std::vector<Guide> aGuideList; // for now only for <a:rect>
+ prepareTextArea(aCustomShape2d, aGuideList, aTextAreaRect);
+ mpFS->startElementNS(XML_a, XML_custGeom);
+ mpFS->singleElementNS(XML_a, XML_avLst);
+ if (aGuideList.empty())
+ {
+ mpFS->singleElementNS(XML_a, XML_gdLst);
+ }
+ else
+ {
+ mpFS->startElementNS(XML_a, XML_gdLst);
+ for (auto const& elem : aGuideList)
+ {
+ mpFS->singleElementNS(XML_a, XML_gd, XML_name, elem.sName, XML_fmla, elem.sFormula);
+ }
+ mpFS->endElementNS(XML_a, XML_gdLst);
+ }
+ mpFS->singleElementNS(XML_a, XML_ahLst);
+ mpFS->singleElementNS(XML_a, XML_rect, XML_l, aTextAreaRect.left, XML_t, aTextAreaRect.top,
+ XML_r, aTextAreaRect.right, XML_b, aTextAreaRect.bottom);
+ mpFS->startElementNS(XML_a, XML_pathLst);
+
+ // Prepare width and height for <a:path>
+ bool bUseGlobalViewBox(false);
+
+ // nViewBoxWidth must be integer otherwise ReplaceGeoWidth in aCustomShape2d.GetParameter() is not
+ // triggered; same for height.
+ sal_Int32 nViewBoxWidth(0);
+ sal_Int32 nViewBoxHeight(0);
+ if (!aPathSize.hasElements())
+ {
+ bUseGlobalViewBox = true;
+ // If draw:viewBox is missing in draw:enhancedGeometry, then import sets
+ // viewBox="0 0 21600 21600". Missing ViewBox can only occur, if user has manipulated
+ // current file via macro. Author of macro has to fix it.
+ auto pProp = std::find_if(
+ std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq),
+ [](const beans::PropertyValue& rGeomProp) { return rGeomProp.Name == "ViewBox"; });
+ if (pProp != std::cend(*pGeometrySeq))
+ {
+ css::awt::Rectangle aViewBox;
+ if (pProp->Value >>= aViewBox)
+ {
+ nViewBoxWidth = aViewBox.Width;
+ nViewBoxHeight = aViewBox.Height;
+ css::drawing::EnhancedCustomShapeParameter aECSP;
+ aECSP.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL;
+ aECSP.Value <<= nViewBoxWidth;
+ double fRetValue;
+ aCustomShape2d.GetParameter(fRetValue, aECSP, true, false);
+ nViewBoxWidth = basegfx::fround(fRetValue);
+ aECSP.Value <<= nViewBoxHeight;
+ aCustomShape2d.GetParameter(fRetValue, aECSP, false, true);
+ nViewBoxHeight = basegfx::fround(fRetValue);
+ }
+ }
+ // Import from oox or documents, which are imported from oox and saved to strict ODF, might
+ // have no subViewSize but viewBox="0 0 0 0". We need to generate width and height in those
+ // cases. Even if that is fixed, we need the substitute for old documents.
+ if ((nViewBoxWidth == 0 && nViewBoxHeight == 0) || pProp == std::cend(*pGeometrySeq))
+ {
+ // Generate a substitute based on point coordinates
+ sal_Int32 nXMin(0);
+ aPairs[0].First.Value >>= nXMin;
+ sal_Int32 nXMax = nXMin;
+ sal_Int32 nYMin(0);
+ aPairs[0].Second.Value >>= nYMin;
+ sal_Int32 nYMax = nYMin;
+
+ for (const auto& rPair : std::as_const(aPairs))
+ {
+ sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, aCustomShape2d,
+ bReplaceGeoWidth, false);
+ sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, aCustomShape2d, false,
+ bReplaceGeoHeight);
+ if (nX < nXMin)
+ nXMin = nX;
+ if (nY < nYMin)
+ nYMin = nY;
+ if (nX > nXMax)
+ nXMax = nX;
+ if (nY > nYMax)
+ nYMax = nY;
+ }
+ nViewBoxWidth = std::max(nXMax, nXMax - nXMin);
+ nViewBoxHeight = std::max(nYMax, nYMax - nYMin);
+ }
+ // ToDo: Other values of left,top than 0,0 are not considered yet. Such would require a
+ // shift of the resulting path coordinates.
+ }
+
+ // Iterate over subpaths
+ sal_Int32 nPairIndex = 0; // index over "Coordinates"
+ sal_Int32 nPathSizeIndex = 0; // index over "SubViewSize"
+ sal_Int32 nSubpathStartIndex(0); // index over "Segments"
+ sal_Int32 nSubPathIndex(0); // serial number of current subpath
+ do
+ {
+ bool bOK(true); // catch faulty paths were commands do not correspond to points
+ // get index of next command ENDSUBPATH; if such doesn't exist use index behind last segment
+ sal_Int32 nNextNcommandIndex = FindNextCommandEndSubpath(nSubpathStartIndex, aSegments);
+
+ // Prepare attributes for a:path start element
+ // NOFILL or one of the LIGHTEN commands
+ std::optional<OString> sFill;
+ if (HasCommandInSubPath(NOFILL, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
+ sFill = "none";
+ else if (HasCommandInSubPath(DARKEN, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
+ sFill = "darken";
+ else if (HasCommandInSubPath(DARKENLESS, nSubpathStartIndex, nNextNcommandIndex - 1,
+ aSegments))
+ sFill = "darkenLess";
+ else if (HasCommandInSubPath(LIGHTEN, nSubpathStartIndex, nNextNcommandIndex - 1,
+ aSegments))
+ sFill = "lighten";
+ else if (HasCommandInSubPath(LIGHTENLESS, nSubpathStartIndex, nNextNcommandIndex - 1,
+ aSegments))
+ sFill = "lightenLess";
+ else
+ {
+ // shading info might be in object type, e.g. "Octagon Bevel".
+ sal_Int32 nLuminanceChange(aCustomShape2d.GetLuminanceChange(nSubPathIndex));
+ if (nLuminanceChange <= -40)
+ sFill = "darken";
+ else if (nLuminanceChange <= -10)
+ sFill = "darkenLess";
+ else if (nLuminanceChange >= 40)
+ sFill = "lighten";
+ else if (nLuminanceChange >= 10)
+ sFill = "lightenLess";
+ }
+ // NOSTROKE
+ std::optional<OString> sStroke;
+ if (HasCommandInSubPath(NOSTROKE, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
+ sStroke = "0";
+
+ // Write a:path start element
+ mpFS->startElementNS(
+ XML_a, XML_path, XML_fill, sFill, XML_stroke, sStroke, XML_w,
+ OString::number(bUseGlobalViewBox ? nViewBoxWidth : aPathSize[nPathSizeIndex].Width),
+ XML_h,
+ OString::number(bUseGlobalViewBox ? nViewBoxHeight : aPathSize[nPathSizeIndex].Height));
+
+ // Arcs drawn by commands ELLIPTICALQUADRANTX and ELLIPTICALQUADRANTY depend on the position
+ // of the target point in regard to the current point. Therefore we need to track the
+ // current point. A current point is not defined in the beginning.
+ double fCurrentX(0.0);
+ double fCurrentY(0.0);
+ bool bCurrentValid(false);
+ // Actually write the subpath
+ for (sal_Int32 nSegmentIndex = nSubpathStartIndex; nSegmentIndex < nNextNcommandIndex;
+ ++nSegmentIndex)
+ {
+ const auto& rSegment(aSegments[nSegmentIndex]);
+ if (rSegment.Command == CLOSESUBPATH)
+ {
+ mpFS->singleElementNS(XML_a, XML_close); // command Z has no parameter
+ // ODF 1.4 specifies, that the start of the subpath becomes the current point.
+ // But that is not implemented yet. Currently LO keeps the last current point.
+ }
+ for (sal_Int32 k = 0; k < rSegment.Count && bOK; ++k)
+ {
+ bOK = WriteCustomGeometrySegment(rSegment.Command, k, aPairs, nPairIndex, fCurrentX,
+ fCurrentY, bCurrentValid, aCustomShape2d,
+ bReplaceGeoWidth, bReplaceGeoHeight);
+ }
+ } // end loop over all commands of subpath
+ // finish this subpath in any case
+ mpFS->endElementNS(XML_a, XML_path);
+
+ if (!bOK)
+ break; // exit loop if not enough values in aPairs
+
+ // step forward to next subpath
+ nSubpathStartIndex = nNextNcommandIndex + 1;
+ nPathSizeIndex++;
+ nSubPathIndex++;
+ } while (nSubpathStartIndex < aSegments.getLength());
+
+ mpFS->endElementNS(XML_a, XML_pathLst);
+ mpFS->endElementNS(XML_a, XML_custGeom);
+ return true; // We have written custGeom even if path is poorly structured.
+}
+
+bool DrawingML::WriteCustomGeometrySegment(
+ const sal_Int16 eCommand, const sal_Int32 nCount,
+ const uno::Sequence<css::drawing::EnhancedCustomShapeParameterPair>& rPairs,
+ sal_Int32& rnPairIndex, double& rfCurrentX, double& rfCurrentY, bool& rbCurrentValid,
+ const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth,
+ const bool bReplaceGeoHeight)
+{
+ switch (eCommand)
+ {
+ case MOVETO:
+ {
+ if (rnPairIndex >= rPairs.getLength())
+ return false;
+
+ mpFS->startElementNS(XML_a, XML_moveTo);
+ WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
+ bReplaceGeoHeight);
+ mpFS->endElementNS(XML_a, XML_moveTo);
+ rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth,
+ false);
+ rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false,
+ bReplaceGeoHeight);
+ rbCurrentValid = true;
+ rnPairIndex++;
+ break;
+ }
+ case LINETO:
+ {
+ if (rnPairIndex >= rPairs.getLength())
+ return false;
+ // LINETO without valid current point is a faulty path. LO is tolerant and makes a
+ // moveTo instead. Do the same on export. MS OFFICE requires a current point for lnTo,
+ // otherwise it shows nothing of the shape.
+ if (rbCurrentValid)
+ {
+ mpFS->startElementNS(XML_a, XML_lnTo);
+ WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
+ bReplaceGeoHeight);
+ mpFS->endElementNS(XML_a, XML_lnTo);
+ }
+ else
+ {
+ mpFS->startElementNS(XML_a, XML_moveTo);
+ WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
+ bReplaceGeoHeight);
+ mpFS->endElementNS(XML_a, XML_moveTo);
+ }
+ rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth,
+ false);
+ rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false,
+ bReplaceGeoHeight);
+ rbCurrentValid = true;
+ rnPairIndex++;
+ break;
+ }
+ case CURVETO:
+ {
+ if (rnPairIndex + 2 >= rPairs.getLength())
+ return false;
+
+ mpFS->startElementNS(XML_a, XML_cubicBezTo);
+ for (sal_uInt8 i = 0; i <= 2; ++i)
+ {
+ WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth,
+ bReplaceGeoHeight);
+ }
+ mpFS->endElementNS(XML_a, XML_cubicBezTo);
+ rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth,
+ false);
+ rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 2].Second, false,
+ bReplaceGeoHeight);
+ rbCurrentValid = true;
+ rnPairIndex += 3;
+ break;
+ }
+ case ANGLEELLIPSETO:
+ case ANGLEELLIPSE:
+ {
+ if (rnPairIndex + 2 >= rPairs.getLength())
+ return false;
+
+ // Read parameters
+ double fCx = 0.0;
+ rCustomShape2d.GetParameter(fCx, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
+ double fCy = 0.0;
+ rCustomShape2d.GetParameter(fCy, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
+ double fWR = 0.0;
+ rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex + 1].First, false, false);
+ double fHR = 0.0;
+ rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex + 1].Second, false, false);
+ double fStartAngle = 0.0;
+ rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 2].First, false, false);
+ double fEndAngle = 0.0;
+ rCustomShape2d.GetParameter(fEndAngle, rPairs[rnPairIndex + 2].Second, false, false);
+
+ // Prepare start and swing angle
+ sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
+ sal_Int32 nSwingAng = 0;
+ if (basegfx::fTools::equalZero(fStartAngle)
+ && basegfx::fTools::equalZero(fEndAngle - 360.0))
+ nSwingAng = 360 * 60000; // special case full circle
+ else
+ {
+ nSwingAng = std::lround((fEndAngle - fStartAngle) * 60000);
+ if (nSwingAng < 0)
+ nSwingAng += 360 * 60000;
+ }
+
+ // calculate start point on ellipse
+ double fSx = 0.0;
+ double fSy = 0.0;
+ getEllipsePointFromViewAngle(fSx, fSy, fWR, fHR, fCx, fCy, fStartAngle);
+
+ // write markup for going to start point
+ // lnTo requires a valid current point
+ if (eCommand == ANGLEELLIPSETO && rbCurrentValid)
+ {
+ mpFS->startElementNS(XML_a, XML_lnTo);
+ mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)),
+ XML_y, OString::number(std::lround(fSy)));
+ mpFS->endElementNS(XML_a, XML_lnTo);
+ }
+ else
+ {
+ mpFS->startElementNS(XML_a, XML_moveTo);
+ mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)),
+ XML_y, OString::number(std::lround(fSy)));
+ mpFS->endElementNS(XML_a, XML_moveTo);
+ }
+ // write markup for arcTo
+ if (!basegfx::fTools::equalZero(fWR) && !basegfx::fTools::equalZero(fHR))
+ mpFS->singleElement(
+ FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR,
+ OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng),
+ XML_swAng, OString::number(nSwingAng));
+
+ getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy, fEndAngle);
+ rbCurrentValid = true;
+ rnPairIndex += 3;
+ break;
+ }
+ case ARCTO:
+ case ARC:
+ case CLOCKWISEARCTO:
+ case CLOCKWISEARC:
+ {
+ if (rnPairIndex + 3 >= rPairs.getLength())
+ return false;
+
+ // read parameters
+ double fX1 = 0.0;
+ rCustomShape2d.GetParameter(fX1, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
+ double fY1 = 0.0;
+ rCustomShape2d.GetParameter(fY1, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
+ double fX2 = 0.0;
+ rCustomShape2d.GetParameter(fX2, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth,
+ false);
+ double fY2 = 0.0;
+ rCustomShape2d.GetParameter(fY2, rPairs[rnPairIndex + 1].Second, false,
+ bReplaceGeoHeight);
+ double fX3 = 0.0;
+ rCustomShape2d.GetParameter(fX3, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth,
+ false);
+ double fY3 = 0.0;
+ rCustomShape2d.GetParameter(fY3, rPairs[rnPairIndex + 2].Second, false,
+ bReplaceGeoHeight);
+ double fX4 = 0.0;
+ rCustomShape2d.GetParameter(fX4, rPairs[rnPairIndex + 3].First, bReplaceGeoWidth,
+ false);
+ double fY4 = 0.0;
+ rCustomShape2d.GetParameter(fY4, rPairs[rnPairIndex + 3].Second, false,
+ bReplaceGeoHeight);
+ // calculate ellipse parameter
+ const double fWR = (fX2 - fX1) / 2.0;
+ const double fHR = (fY2 - fY1) / 2.0;
+ const double fCx = (fX1 + fX2) / 2.0;
+ const double fCy = (fY1 + fY2) / 2.0;
+ // calculate start angle
+ double fStartAngle = 0.0;
+ double fPx = 0.0;
+ double fPy = 0.0;
+ getEllipsePointAndAngleFromRayPoint(fStartAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX3,
+ fY3);
+ // markup for going to start point
+ // lnTo requires a valid current point.
+ if ((eCommand == ARCTO || eCommand == CLOCKWISEARCTO) && rbCurrentValid)
+ {
+ mpFS->startElementNS(XML_a, XML_lnTo);
+ mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)),
+ XML_y, OString::number(std::lround(fPy)));
+ mpFS->endElementNS(XML_a, XML_lnTo);
+ }
+ else
+ {
+ mpFS->startElementNS(XML_a, XML_moveTo);
+ mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)),
+ XML_y, OString::number(std::lround(fPy)));
+ mpFS->endElementNS(XML_a, XML_moveTo);
+ }
+ // calculate swing angle
+ double fEndAngle = 0.0;
+ getEllipsePointAndAngleFromRayPoint(fEndAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX4, fY4);
+ double fSwingAngle(fEndAngle - fStartAngle);
+ const bool bIsClockwise(eCommand == CLOCKWISEARCTO || eCommand == CLOCKWISEARC);
+ if (bIsClockwise && fSwingAngle < 0)
+ fSwingAngle += 360.0;
+ else if (!bIsClockwise && fSwingAngle > 0)
+ fSwingAngle -= 360.0;
+ // markup for arcTo
+ // ToDo: write markup for case zero width or height of ellipse
+ const sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
+ const sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000));
+ mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)),
+ XML_hR, OString::number(std::lround(fHR)), XML_stAng,
+ OString::number(nStartAng), XML_swAng, OString::number(nSwingAng));
+ rfCurrentX = fPx;
+ rfCurrentY = fPy;
+ rbCurrentValid = true;
+ rnPairIndex += 4;
+ break;
+ }
+ case ELLIPTICALQUADRANTX:
+ case ELLIPTICALQUADRANTY:
+ {
+ if (rnPairIndex >= rPairs.getLength())
+ return false;
+
+ // read parameters
+ double fX = 0.0;
+ rCustomShape2d.GetParameter(fX, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
+ double fY = 0.0;
+ rCustomShape2d.GetParameter(fY, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
+
+ // Prepare parameters for arcTo
+ if (rbCurrentValid)
+ {
+ double fWR = std::abs(rfCurrentX - fX);
+ double fHR = std::abs(rfCurrentY - fY);
+ double fStartAngle(0.0);
+ double fSwingAngle(0.0);
+ // The starting direction of the arc toggles between X and Y
+ if ((eCommand == ELLIPTICALQUADRANTX && !(nCount % 2))
+ || (eCommand == ELLIPTICALQUADRANTY && (nCount % 2)))
+ {
+ // arc starts horizontal
+ fStartAngle = fY < rfCurrentY ? 90.0 : 270.0;
+ const bool bClockwise = (fX < rfCurrentX && fY < rfCurrentY)
+ || (fX > rfCurrentX && fY > rfCurrentY);
+ fSwingAngle = bClockwise ? 90.0 : -90.0;
+ }
+ else
+ {
+ // arc starts vertical
+ fStartAngle = fX < rfCurrentX ? 0.0 : 180.0;
+ const bool bClockwise = (fX < rfCurrentX && fY > rfCurrentY)
+ || (fX > rfCurrentX && fY < rfCurrentY);
+ fSwingAngle = bClockwise ? 90.0 : -90.0;
+ }
+ sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
+ sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000));
+ mpFS->singleElement(
+ FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR,
+ OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng),
+ XML_swAng, OString::number(nSwingAng));
+ }
+ else
+ {
+ // faulty path, but we continue with the target point
+ mpFS->startElementNS(XML_a, XML_moveTo);
+ WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
+ bReplaceGeoHeight);
+ mpFS->endElementNS(XML_a, XML_moveTo);
+ }
+ rfCurrentX = fX;
+ rfCurrentY = fY;
+ rbCurrentValid = true;
+ rnPairIndex++;
+ break;
+ }
+ case QUADRATICCURVETO:
+ {
+ if (rnPairIndex + 1 >= rPairs.getLength())
+ return false;
+
+ mpFS->startElementNS(XML_a, XML_quadBezTo);
+ for (sal_uInt8 i = 0; i < 2; ++i)
+ {
+ WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth,
+ bReplaceGeoHeight);
+ }
+ mpFS->endElementNS(XML_a, XML_quadBezTo);
+ rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth,
+ false);
+ rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 1].Second, false,
+ bReplaceGeoHeight);
+ rbCurrentValid = true;
+ rnPairIndex += 2;
+ break;
+ }
+ case ARCANGLETO:
+ {
+ if (rnPairIndex + 1 >= rPairs.getLength())
+ return false;
+
+ double fWR = 0.0;
+ rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex].First, false, false);
+ double fHR = 0.0;
+ rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex].Second, false, false);
+ double fStartAngle = 0.0;
+ rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 1].First, false, false);
+ sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
+ double fSwingAng = 0.0;
+ rCustomShape2d.GetParameter(fSwingAng, rPairs[rnPairIndex + 1].Second, false, false);
+ sal_Int32 nSwingAng(std::lround(fSwingAng * 60000));
+ mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(fWR), XML_hR,
+ OString::number(fHR), XML_stAng, OString::number(nStartAng),
+ XML_swAng, OString::number(nSwingAng));
+ double fPx = 0.0;
+ double fPy = 0.0;
+ getEllipsePointFromViewAngle(fPx, fPy, fWR, fHR, 0.0, 0.0, fStartAngle);
+ double fCx = rfCurrentX - fPx;
+ double fCy = rfCurrentY - fPy;
+ getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy,
+ fStartAngle + fSwingAng);
+ rbCurrentValid = true;
+ rnPairIndex += 2;
+ break;
+ }
+ default:
+ // do nothing
+ break;
+ }
+ return true;
+}
+
+void DrawingML::WriteCustomGeometryPoint(
+ const drawing::EnhancedCustomShapeParameterPair& rParamPair,
+ const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth,
+ const bool bReplaceGeoHeight)
+{
+ sal_Int32 nX
+ = GetCustomGeometryPointValue(rParamPair.First, rCustomShape2d, bReplaceGeoWidth, false);
+ sal_Int32 nY
+ = GetCustomGeometryPointValue(rParamPair.Second, rCustomShape2d, false, bReplaceGeoHeight);
+
+ mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(nX), XML_y, OString::number(nY));
+}
+
+void DrawingML::WriteEmptyCustomGeometry()
+{
+ // This method is used for export to docx in case WriteCustomGeometry fails.
+ mpFS->startElementNS(XML_a, XML_custGeom);
+ mpFS->singleElementNS(XML_a, XML_avLst);
+ mpFS->singleElementNS(XML_a, XML_gdLst);
+ mpFS->singleElementNS(XML_a, XML_ahLst);
+ mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b");
+ mpFS->singleElementNS(XML_a, XML_pathLst);
+ mpFS->endElementNS(XML_a, XML_custGeom);
+}
+
+// version for SdrPathObj
+void DrawingML::WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape,
+ const bool bClosed)
+{
+ tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(rXShape);
+ // In case of Writer, the parent element is <wps:spPr>, and there the
+ // <a:custGeom> element is not optional.
+ if (aPolyPolygon.Count() < 1 && GetDocumentType() != DOCUMENT_DOCX)
+ return;
+
+ mpFS->startElementNS(XML_a, XML_custGeom);
+ mpFS->singleElementNS(XML_a, XML_avLst);
+ mpFS->singleElementNS(XML_a, XML_gdLst);
+ mpFS->singleElementNS(XML_a, XML_ahLst);
+ mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b");
+
+ mpFS->startElementNS(XML_a, XML_pathLst);
+
+ awt::Size aSize = rXShape->getSize();
+ awt::Point aPos = rXShape->getPosition();
+ Reference<XPropertySet> xPropertySet(rXShape, UNO_QUERY);
+ uno::Reference<XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
+ if (xPropertySetInfo->hasPropertyByName("AnchorPosition"))
+ {
+ awt::Point aAnchorPosition;
+ xPropertySet->getPropertyValue("AnchorPosition") >>= aAnchorPosition;
+ aPos.X += aAnchorPosition.X;
+ aPos.Y += aAnchorPosition.Y;
+ }
+
+ // Only closed SdrPathObj can be filled
+ std::optional<OString> sFill;
+ if (!bClosed)
+ sFill = "none"; // for possible values see ST_PathFillMode in OOXML standard
+
+ // Put all polygons of rPolyPolygon in the same path element
+ // to subtract the overlapped areas.
+ mpFS->startElementNS(XML_a, XML_path, XML_fill, sFill, XML_w, OString::number(aSize.Width),
+ XML_h, OString::number(aSize.Height));
+
+ for (sal_uInt16 i = 0; i < aPolyPolygon.Count(); i++)
+ {
+ const tools::Polygon& aPoly = aPolyPolygon[i];
+
+ if (aPoly.GetSize() > 0)
+ {
+ mpFS->startElementNS(XML_a, XML_moveTo);
+
+ mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[0].X() - aPos.X),
+ XML_y, OString::number(aPoly[0].Y() - aPos.Y));
+
+ mpFS->endElementNS(XML_a, XML_moveTo);
+ }
+
+ for (sal_uInt16 j = 1; j < aPoly.GetSize(); j++)
+ {
+ PolyFlags flags = aPoly.GetFlags(j);
+ if (flags == PolyFlags::Control)
+ {
+ // a:cubicBezTo can only contain 3 a:pt elements, so we need to make sure of this
+ if (j + 2 < aPoly.GetSize() && aPoly.GetFlags(j + 1) == PolyFlags::Control
+ && aPoly.GetFlags(j + 2) != PolyFlags::Control)
+ {
+ mpFS->startElementNS(XML_a, XML_cubicBezTo);
+ for (sal_uInt8 k = 0; k <= 2; ++k)
+ {
+ mpFS->singleElementNS(XML_a, XML_pt, XML_x,
+ OString::number(aPoly[j + k].X() - aPos.X), XML_y,
+ OString::number(aPoly[j + k].Y() - aPos.Y));
+ }
+ mpFS->endElementNS(XML_a, XML_cubicBezTo);
+ j += 2;
+ }
+ }
+ else if (flags == PolyFlags::Normal)
+ {
+ mpFS->startElementNS(XML_a, XML_lnTo);
+ mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[j].X() - aPos.X),
+ XML_y, OString::number(aPoly[j].Y() - aPos.Y));
+ mpFS->endElementNS(XML_a, XML_lnTo);
+ }
+ }
+ }
+ if (bClosed)
+ mpFS->singleElementNS(XML_a, XML_close);
+ mpFS->endElementNS(XML_a, XML_path);
+
+ mpFS->endElementNS(XML_a, XML_pathLst);
+
+ mpFS->endElementNS(XML_a, XML_custGeom);
+}
+
+void DrawingML::WriteConnectorConnections( sal_uInt32 nStartGlueId, sal_Int32 nEndGlueId, sal_Int32 nStartID, sal_Int32 nEndID )
+{
+ if( nStartID != -1 )
+ {
+ mpFS->singleElementNS( XML_a, XML_stCxn,
+ XML_id, OString::number(nStartID),
+ XML_idx, OString::number(nStartGlueId) );
+ }
+ if( nEndID != -1 )
+ {
+ mpFS->singleElementNS( XML_a, XML_endCxn,
+ XML_id, OString::number(nEndID),
+ XML_idx, OString::number(nEndGlueId) );
+ }
+}
+
+sal_Unicode DrawingML::SubstituteBullet( sal_Unicode cBulletId, css::awt::FontDescriptor& rFontDesc )
+{
+ if ( IsStarSymbol(rFontDesc.Name) )
+ {
+ rtl_TextEncoding eCharSet = rFontDesc.CharSet;
+ cBulletId = msfilter::util::bestFitOpenSymbolToMSFont(cBulletId, eCharSet, rFontDesc.Name);
+ rFontDesc.CharSet = eCharSet;
+ }
+
+ return cBulletId;
+}
+
+sax_fastparser::FSHelperPtr DrawingML::CreateOutputStream (
+ const OUString& sFullStream,
+ std::u16string_view sRelativeStream,
+ const Reference< XOutputStream >& xParentRelation,
+ const char* sContentType,
+ const char* sRelationshipType,
+ OUString* pRelationshipId )
+{
+ OUString sRelationshipId;
+ if (xParentRelation.is())
+ sRelationshipId = GetFB()->addRelation( xParentRelation, OUString::createFromAscii( sRelationshipType), sRelativeStream );
+ else
+ sRelationshipId = GetFB()->addRelation( OUString::createFromAscii( sRelationshipType ), sRelativeStream );
+
+ if( pRelationshipId )
+ *pRelationshipId = sRelationshipId;
+
+ sax_fastparser::FSHelperPtr p = GetFB()->openFragmentStreamWithSerializer( sFullStream, OUString::createFromAscii( sContentType ) );
+
+ return p;
+}
+
+void DrawingML::WriteFill( const Reference< XPropertySet >& xPropSet )
+{
+ if ( !GetProperty( xPropSet, "FillStyle" ) )
+ return;
+ FillStyle aFillStyle( FillStyle_NONE );
+ xPropSet->getPropertyValue( "FillStyle" ) >>= aFillStyle;
+
+ // map full transparent background to no fill
+ if ( aFillStyle == FillStyle_SOLID && GetProperty( xPropSet, "FillTransparence" ) )
+ {
+ sal_Int16 nVal = 0;
+ xPropSet->getPropertyValue( "FillTransparence" ) >>= nVal;
+ if ( nVal == 100 )
+ aFillStyle = FillStyle_NONE;
+ }
+ if (aFillStyle == FillStyle_SOLID && GetProperty( xPropSet, "FillTransparenceGradient"))
+ {
+ awt::Gradient aTransparenceGradient;
+ mAny >>= aTransparenceGradient;
+ if (aTransparenceGradient.StartColor == 0xffffff && aTransparenceGradient.EndColor == 0xffffff)
+ aFillStyle = FillStyle_NONE;
+ }
+
+ bool bUseBackground(false);
+ if (GetProperty(xPropSet, "FillUseSlideBackground"))
+ xPropSet->getPropertyValue("FillUseSlideBackground") >>= bUseBackground;
+
+ switch( aFillStyle )
+ {
+ case FillStyle_SOLID :
+ WriteSolidFill( xPropSet );
+ break;
+ case FillStyle_GRADIENT :
+ WriteGradientFill( xPropSet );
+ break;
+ case FillStyle_BITMAP :
+ WriteBlipFill( xPropSet, "FillBitmap" );
+ break;
+ case FillStyle_HATCH :
+ WritePattFill( xPropSet );
+ break;
+ case FillStyle_NONE:
+ if (!bUseBackground) // attribute `useBgFill` will be written at parent p:sp shape
+ mpFS->singleElementNS(XML_a, XML_noFill);
+ break;
+ default:
+ ;
+ }
+}
+
+void DrawingML::WriteStyleProperties( sal_Int32 nTokenId, const Sequence< PropertyValue >& aProperties )
+{
+ if( aProperties.hasElements() )
+ {
+ OUString sSchemeClr;
+ sal_uInt32 nIdx = 0;
+ Sequence< PropertyValue > aTransformations;
+ for( const auto& rProp : aProperties)
+ {
+ if( rProp.Name == "SchemeClr" )
+ rProp.Value >>= sSchemeClr;
+ else if( rProp.Name == "Idx" )
+ rProp.Value >>= nIdx;
+ else if( rProp.Name == "Transformations" )
+ rProp.Value >>= aTransformations;
+ }
+ mpFS->startElementNS(XML_a, nTokenId, XML_idx, OString::number(nIdx));
+ WriteColor(sSchemeClr, aTransformations);
+ mpFS->endElementNS( XML_a, nTokenId );
+ }
+ else
+ {
+ // write mock <a:*Ref> tag
+ mpFS->singleElementNS(XML_a, nTokenId, XML_idx, OString::number(0));
+ }
+}
+
+void DrawingML::WriteShapeStyle( const Reference< XPropertySet >& xPropSet )
+{
+ // check existence of the grab bag
+ if ( !GetProperty( xPropSet, "InteropGrabBag" ) )
+ return;
+
+ // extract the relevant properties from the grab bag
+ Sequence< PropertyValue > aGrabBag;
+ Sequence< PropertyValue > aFillRefProperties, aLnRefProperties, aEffectRefProperties;
+ mAny >>= aGrabBag;
+ for( const auto& rProp : std::as_const(aGrabBag))
+ {
+ if( rProp.Name == "StyleFillRef" )
+ rProp.Value >>= aFillRefProperties;
+ else if( rProp.Name == "StyleLnRef" )
+ rProp.Value >>= aLnRefProperties;
+ else if( rProp.Name == "StyleEffectRef" )
+ rProp.Value >>= aEffectRefProperties;
+ }
+
+ WriteStyleProperties( XML_lnRef, aLnRefProperties );
+ WriteStyleProperties( XML_fillRef, aFillRefProperties );
+ WriteStyleProperties( XML_effectRef, aEffectRefProperties );
+
+ // write mock <a:fontRef>
+ mpFS->singleElementNS(XML_a, XML_fontRef, XML_idx, "minor");
+}
+
+void DrawingML::WriteShapeEffect( std::u16string_view sName, const Sequence< PropertyValue >& aEffectProps )
+{
+ if( !aEffectProps.hasElements() )
+ return;
+
+ // assign the proper tag and enable bContainsColor if necessary
+ sal_Int32 nEffectToken = 0;
+ bool bContainsColor = false;
+ if( sName == u"outerShdw" )
+ {
+ nEffectToken = FSNS( XML_a, XML_outerShdw );
+ bContainsColor = true;
+ }
+ else if( sName == u"innerShdw" )
+ {
+ nEffectToken = FSNS( XML_a, XML_innerShdw );
+ bContainsColor = true;
+ }
+ else if( sName == u"glow" )
+ {
+ nEffectToken = FSNS( XML_a, XML_glow );
+ bContainsColor = true;
+ }
+ else if( sName == u"softEdge" )
+ nEffectToken = FSNS( XML_a, XML_softEdge );
+ else if( sName == u"reflection" )
+ nEffectToken = FSNS( XML_a, XML_reflection );
+ else if( sName == u"blur" )
+ nEffectToken = FSNS( XML_a, XML_blur );
+
+ OUString sSchemeClr;
+ ::Color nRgbClr;
+ sal_Int32 nAlpha = MAX_PERCENT;
+ Sequence< PropertyValue > aTransformations;
+ rtl::Reference<sax_fastparser::FastAttributeList> aOuterShdwAttrList = FastSerializerHelper::createAttrList();
+ for( const auto& rEffectProp : aEffectProps )
+ {
+ if( rEffectProp.Name == "Attribs" )
+ {
+ // read tag attributes
+ uno::Sequence< beans::PropertyValue > aOuterShdwProps;
+ rEffectProp.Value >>= aOuterShdwProps;
+ for( const auto& rOuterShdwProp : std::as_const(aOuterShdwProps) )
+ {
+ if( rOuterShdwProp.Name == "algn" )
+ {
+ OUString sVal;
+ rOuterShdwProp.Value >>= sVal;
+ aOuterShdwAttrList->add( XML_algn, sVal );
+ }
+ else if( rOuterShdwProp.Name == "blurRad" )
+ {
+ sal_Int64 nVal = 0;
+ rOuterShdwProp.Value >>= nVal;
+ aOuterShdwAttrList->add( XML_blurRad, OString::number( nVal ).getStr() );
+ }
+ else if( rOuterShdwProp.Name == "dir" )
+ {
+ sal_Int32 nVal = 0;
+ rOuterShdwProp.Value >>= nVal;
+ aOuterShdwAttrList->add( XML_dir, OString::number( nVal ).getStr() );
+ }
+ else if( rOuterShdwProp.Name == "dist" )
+ {
+ sal_Int32 nVal = 0;
+ rOuterShdwProp.Value >>= nVal;
+ aOuterShdwAttrList->add( XML_dist, OString::number( nVal ).getStr() );
+ }
+ else if( rOuterShdwProp.Name == "kx" )
+ {
+ sal_Int32 nVal = 0;
+ rOuterShdwProp.Value >>= nVal;
+ aOuterShdwAttrList->add( XML_kx, OString::number( nVal ).getStr() );
+ }
+ else if( rOuterShdwProp.Name == "ky" )
+ {
+ sal_Int32 nVal = 0;
+ rOuterShdwProp.Value >>= nVal;
+ aOuterShdwAttrList->add( XML_ky, OString::number( nVal ).getStr() );
+ }
+ else if( rOuterShdwProp.Name == "rotWithShape" )
+ {
+ sal_Int32 nVal = 0;
+ rOuterShdwProp.Value >>= nVal;
+ aOuterShdwAttrList->add( XML_rotWithShape, OString::number( nVal ).getStr() );
+ }
+ else if( rOuterShdwProp.Name == "sx" )
+ {
+ sal_Int32 nVal = 0;
+ rOuterShdwProp.Value >>= nVal;
+ aOuterShdwAttrList->add( XML_sx, OString::number( nVal ).getStr() );
+ }
+ else if( rOuterShdwProp.Name == "sy" )
+ {
+ sal_Int32 nVal = 0;
+ rOuterShdwProp.Value >>= nVal;
+ aOuterShdwAttrList->add( XML_sy, OString::number( nVal ).getStr() );
+ }
+ else if( rOuterShdwProp.Name == "rad" )
+ {
+ sal_Int64 nVal = 0;
+ rOuterShdwProp.Value >>= nVal;
+ aOuterShdwAttrList->add( XML_rad, OString::number( nVal ).getStr() );
+ }
+ else if( rOuterShdwProp.Name == "endA" )
+ {
+ sal_Int32 nVal = 0;
+ rOuterShdwProp.Value >>= nVal;
+ aOuterShdwAttrList->add( XML_endA, OString::number( nVal ).getStr() );
+ }
+ else if( rOuterShdwProp.Name == "endPos" )
+ {
+ sal_Int32 nVal = 0;
+ rOuterShdwProp.Value >>= nVal;
+ aOuterShdwAttrList->add( XML_endPos, OString::number( nVal ).getStr() );
+ }
+ else if( rOuterShdwProp.Name == "fadeDir" )
+ {
+ sal_Int32 nVal = 0;
+ rOuterShdwProp.Value >>= nVal;
+ aOuterShdwAttrList->add( XML_fadeDir, OString::number( nVal ).getStr() );
+ }
+ else if( rOuterShdwProp.Name == "stA" )
+ {
+ sal_Int32 nVal = 0;
+ rOuterShdwProp.Value >>= nVal;
+ aOuterShdwAttrList->add( XML_stA, OString::number( nVal ).getStr() );
+ }
+ else if( rOuterShdwProp.Name == "stPos" )
+ {
+ sal_Int32 nVal = 0;
+ rOuterShdwProp.Value >>= nVal;
+ aOuterShdwAttrList->add( XML_stPos, OString::number( nVal ).getStr() );
+ }
+ else if( rOuterShdwProp.Name == "grow" )
+ {
+ sal_Int32 nVal = 0;
+ rOuterShdwProp.Value >>= nVal;
+ aOuterShdwAttrList->add( XML_grow, OString::number( nVal ).getStr() );
+ }
+ }
+ }
+ else if(rEffectProp.Name == "RgbClr")
+ {
+ rEffectProp.Value >>= nRgbClr;
+ }
+ else if(rEffectProp.Name == "RgbClrTransparency")
+ {
+ sal_Int32 nTransparency;
+ if (rEffectProp.Value >>= nTransparency)
+ // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
+ nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency );
+ }
+ else if(rEffectProp.Name == "SchemeClr")
+ {
+ rEffectProp.Value >>= sSchemeClr;
+ }
+ else if(rEffectProp.Name == "SchemeClrTransformations")
+ {
+ rEffectProp.Value >>= aTransformations;
+ }
+ }
+
+ if( nEffectToken <= 0 )
+ return;
+
+ mpFS->startElement( nEffectToken, aOuterShdwAttrList );
+
+ if( bContainsColor )
+ {
+ if( sSchemeClr.isEmpty() )
+ WriteColor( nRgbClr, nAlpha );
+ else
+ WriteColor( sSchemeClr, aTransformations );
+ }
+
+ mpFS->endElement( nEffectToken );
+}
+
+static sal_Int32 lcl_CalculateDist(const double dX, const double dY)
+{
+ return static_cast< sal_Int32 >(sqrt(dX*dX + dY*dY) * 360);
+}
+
+static sal_Int32 lcl_CalculateDir(const double dX, const double dY)
+{
+ return (static_cast< sal_Int32 >(basegfx::rad2deg<60000>(atan2(dY,dX))) + 21600000) % 21600000;
+}
+
+void DrawingML::WriteShapeEffects( const Reference< XPropertySet >& rXPropSet )
+{
+ Sequence< PropertyValue > aGrabBag, aEffects, aOuterShdwProps;
+ bool bHasInteropGrabBag = rXPropSet->getPropertySetInfo()->hasPropertyByName("InteropGrabBag");
+ if (bHasInteropGrabBag && GetProperty(rXPropSet, "InteropGrabBag"))
+ {
+ mAny >>= aGrabBag;
+ auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
+ [](const PropertyValue& rProp) { return rProp.Name == "EffectProperties"; });
+ if (pProp != std::cend(aGrabBag))
+ {
+ pProp->Value >>= aEffects;
+ auto pEffect = std::find_if(std::cbegin(aEffects), std::cend(aEffects),
+ [](const PropertyValue& rEffect) { return rEffect.Name == "outerShdw"; });
+ if (pEffect != std::cend(aEffects))
+ pEffect->Value >>= aOuterShdwProps;
+ }
+ }
+
+ // tdf#132201: the order of effects is important. Effects order (CT_EffectList in ECMA-376):
+ // blur -> fillOverlay -> glow -> innerShdw -> outerShdw -> prstShdw -> reflection -> softEdge
+
+ if( !aEffects.hasElements() )
+ {
+ bool bHasShadow = false;
+ if( GetProperty( rXPropSet, "Shadow" ) )
+ mAny >>= bHasShadow;
+ bool bHasEffects = bHasShadow;
+ if (!bHasEffects && GetProperty(rXPropSet, "GlowEffectRadius"))
+ {
+ sal_Int32 rad = 0;
+ mAny >>= rad;
+ bHasEffects = rad > 0;
+ }
+ if (!bHasEffects && GetProperty(rXPropSet, "SoftEdgeRadius"))
+ {
+ sal_Int32 rad = 0;
+ mAny >>= rad;
+ bHasEffects = rad > 0;
+ }
+
+ if (bHasEffects)
+ {
+ mpFS->startElementNS(XML_a, XML_effectLst);
+ WriteGlowEffect(rXPropSet);
+ if( bHasShadow )
+ {
+ double dX = +0.0, dY = +0.0;
+ sal_Int32 nBlur =0;
+ rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX;
+ rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY;
+ rXPropSet->getPropertyValue( "ShadowBlur" ) >>= nBlur;
+
+ Sequence< PropertyValue > aShadowAttribsGrabBag{
+ comphelper::makePropertyValue("dist", lcl_CalculateDist(dX, dY)),
+ comphelper::makePropertyValue("dir", lcl_CalculateDir(dX, dY)),
+ comphelper::makePropertyValue("blurRad", oox::drawingml::convertHmmToEmu(nBlur)),
+ comphelper::makePropertyValue("rotWithShape", false) //ooxml default is 'true', so must write it
+ };
+
+ Sequence< PropertyValue > aShadowGrabBag{
+ comphelper::makePropertyValue("Attribs", aShadowAttribsGrabBag),
+ comphelper::makePropertyValue("RgbClr", rXPropSet->getPropertyValue( "ShadowColor" )),
+ comphelper::makePropertyValue("RgbClrTransparency", rXPropSet->getPropertyValue( "ShadowTransparence" ))
+ };
+
+ WriteShapeEffect( u"outerShdw", aShadowGrabBag );
+ }
+ WriteSoftEdgeEffect(rXPropSet);
+ mpFS->endElementNS(XML_a, XML_effectLst);
+ }
+ }
+ else
+ {
+ for( auto& rOuterShdwProp : asNonConstRange(aOuterShdwProps) )
+ {
+ if( rOuterShdwProp.Name == "Attribs" )
+ {
+ Sequence< PropertyValue > aAttribsProps;
+ rOuterShdwProp.Value >>= aAttribsProps;
+
+ double dX = +0.0, dY = +0.0;
+ sal_Int32 nBlur =0;
+ rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX;
+ rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY;
+ rXPropSet->getPropertyValue( "ShadowBlur" ) >>= nBlur;
+
+
+ for( auto& rAttribsProp : asNonConstRange(aAttribsProps) )
+ {
+ if( rAttribsProp.Name == "dist" )
+ {
+ rAttribsProp.Value <<= lcl_CalculateDist(dX, dY);
+ }
+ else if( rAttribsProp.Name == "dir" )
+ {
+ rAttribsProp.Value <<= lcl_CalculateDir(dX, dY);
+ }
+ else if( rAttribsProp.Name == "blurRad" )
+ {
+ rAttribsProp.Value <<= oox::drawingml::convertHmmToEmu(nBlur);
+ }
+ }
+
+ rOuterShdwProp.Value <<= aAttribsProps;
+ }
+ else if( rOuterShdwProp.Name == "RgbClr" )
+ {
+ rOuterShdwProp.Value = rXPropSet->getPropertyValue( "ShadowColor" );
+ }
+ else if( rOuterShdwProp.Name == "RgbClrTransparency" )
+ {
+ rOuterShdwProp.Value = rXPropSet->getPropertyValue( "ShadowTransparence" );
+ }
+ }
+
+ mpFS->startElementNS(XML_a, XML_effectLst);
+ bool bGlowWritten = false;
+ for( const auto& rEffect : std::as_const(aEffects) )
+ {
+ if (!bGlowWritten
+ && (rEffect.Name == "innerShdw" || rEffect.Name == "outerShdw"
+ || rEffect.Name == "prstShdw" || rEffect.Name == "reflection"
+ || rEffect.Name == "softEdge"))
+ {
+ WriteGlowEffect(rXPropSet);
+ bGlowWritten = true;
+ }
+
+ if( rEffect.Name == "outerShdw" )
+ {
+ WriteShapeEffect( rEffect.Name, aOuterShdwProps );
+ }
+ else
+ {
+ Sequence< PropertyValue > aEffectProps;
+ rEffect.Value >>= aEffectProps;
+ WriteShapeEffect( rEffect.Name, aEffectProps );
+ }
+ }
+ if (!bGlowWritten)
+ WriteGlowEffect(rXPropSet);
+ WriteSoftEdgeEffect(rXPropSet); // the last
+
+ mpFS->endElementNS(XML_a, XML_effectLst);
+ }
+}
+
+void DrawingML::WriteGlowEffect(const Reference< XPropertySet >& rXPropSet)
+{
+ if (!rXPropSet->getPropertySetInfo()->hasPropertyByName("GlowEffectRadius"))
+ {
+ return;
+ }
+
+ sal_Int32 nRad = 0;
+ rXPropSet->getPropertyValue("GlowEffectRadius") >>= nRad;
+ if (!nRad)
+ return;
+
+ Sequence< PropertyValue > aGlowAttribs{ comphelper::makePropertyValue(
+ "rad", oox::drawingml::convertHmmToEmu(nRad)) };
+ Sequence< PropertyValue > aGlowProps{
+ comphelper::makePropertyValue("Attribs", aGlowAttribs),
+ comphelper::makePropertyValue("RgbClr", rXPropSet->getPropertyValue("GlowEffectColor")),
+ comphelper::makePropertyValue("RgbClrTransparency", rXPropSet->getPropertyValue("GlowEffectTransparency"))
+ };
+ // TODO other stuff like saturation or luminance
+
+ WriteShapeEffect(u"glow", aGlowProps);
+}
+
+void DrawingML::WriteSoftEdgeEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet)
+{
+ if (!rXPropSet->getPropertySetInfo()->hasPropertyByName("SoftEdgeRadius"))
+ {
+ return;
+ }
+
+ sal_Int32 nRad = 0;
+ rXPropSet->getPropertyValue("SoftEdgeRadius") >>= nRad;
+ if (!nRad)
+ return;
+
+ css::uno::Sequence<css::beans::PropertyValue> aAttribs{ comphelper::makePropertyValue(
+ "rad", oox::drawingml::convertHmmToEmu(nRad)) };
+ css::uno::Sequence<css::beans::PropertyValue> aProps{ comphelper::makePropertyValue("Attribs",
+ aAttribs) };
+
+ WriteShapeEffect(u"softEdge", aProps);
+}
+
+void DrawingML::Write3DEffects( const Reference< XPropertySet >& xPropSet, bool bIsText )
+{
+ // check existence of the grab bag
+ if( !GetProperty( xPropSet, "InteropGrabBag" ) )
+ return;
+
+ // extract the relevant properties from the grab bag
+ Sequence< PropertyValue > aGrabBag, aEffectProps, aLightRigProps, aShape3DProps;
+ mAny >>= aGrabBag;
+
+ auto pShapeProp = std::find_if( std::cbegin(aGrabBag), std::cend(aGrabBag),
+ [bIsText](const PropertyValue& rProp)
+ { return rProp.Name == (bIsText ? u"Text3DEffectProperties" : u"3DEffectProperties"); });
+ if (pShapeProp != std::cend(aGrabBag))
+ {
+ Sequence< PropertyValue > a3DEffectProps;
+ pShapeProp->Value >>= a3DEffectProps;
+ for( const auto& r3DEffectProp : std::as_const(a3DEffectProps) )
+ {
+ if( r3DEffectProp.Name == "Camera" )
+ r3DEffectProp.Value >>= aEffectProps;
+ else if( r3DEffectProp.Name == "LightRig" )
+ r3DEffectProp.Value >>= aLightRigProps;
+ else if( r3DEffectProp.Name == "Shape3D" )
+ r3DEffectProp.Value >>= aShape3DProps;
+ }
+ }
+
+ if( !aEffectProps.hasElements() && !aLightRigProps.hasElements() && !aShape3DProps.hasElements() )
+ return;
+
+ bool bCameraRotationPresent = false;
+ rtl::Reference<sax_fastparser::FastAttributeList> aCameraAttrList = FastSerializerHelper::createAttrList();
+ rtl::Reference<sax_fastparser::FastAttributeList> aCameraRotationAttrList = FastSerializerHelper::createAttrList();
+ for( const auto& rEffectProp : std::as_const(aEffectProps) )
+ {
+ if( rEffectProp.Name == "prst" )
+ {
+ OUString sVal;
+ rEffectProp.Value >>= sVal;
+ aCameraAttrList->add(XML_prst, sVal);
+ }
+ else if( rEffectProp.Name == "fov" )
+ {
+ float fVal = 0;
+ rEffectProp.Value >>= fVal;
+ aCameraAttrList->add( XML_fov, OString::number( fVal * 60000 ).getStr() );
+ }
+ else if( rEffectProp.Name == "zoom" )
+ {
+ float fVal = 1;
+ rEffectProp.Value >>= fVal;
+ aCameraAttrList->add( XML_zoom, OString::number( fVal * 100000 ).getStr() );
+ }
+ else if( rEffectProp.Name == "rotLat" ||
+ rEffectProp.Name == "rotLon" ||
+ rEffectProp.Name == "rotRev" )
+ {
+ sal_Int32 nVal = 0, nToken = XML_none;
+ rEffectProp.Value >>= nVal;
+ if( rEffectProp.Name == "rotLat" )
+ nToken = XML_lat;
+ else if( rEffectProp.Name == "rotLon" )
+ nToken = XML_lon;
+ else if( rEffectProp.Name == "rotRev" )
+ nToken = XML_rev;
+ aCameraRotationAttrList->add( nToken, OString::number( nVal ).getStr() );
+ bCameraRotationPresent = true;
+ }
+ }
+
+ bool bLightRigRotationPresent = false;
+ rtl::Reference<sax_fastparser::FastAttributeList> aLightRigAttrList = FastSerializerHelper::createAttrList();
+ rtl::Reference<sax_fastparser::FastAttributeList> aLightRigRotationAttrList = FastSerializerHelper::createAttrList();
+ for( const auto& rLightRigProp : std::as_const(aLightRigProps) )
+ {
+ if( rLightRigProp.Name == "rig" || rLightRigProp.Name == "dir" )
+ {
+ OUString sVal;
+ sal_Int32 nToken = XML_none;
+ rLightRigProp.Value >>= sVal;
+ if( rLightRigProp.Name == "rig" )
+ nToken = XML_rig;
+ else if( rLightRigProp.Name == "dir" )
+ nToken = XML_dir;
+ aLightRigAttrList->add(nToken, sVal);
+ }
+ else if( rLightRigProp.Name == "rotLat" ||
+ rLightRigProp.Name == "rotLon" ||
+ rLightRigProp.Name == "rotRev" )
+ {
+ sal_Int32 nVal = 0, nToken = XML_none;
+ rLightRigProp.Value >>= nVal;
+ if( rLightRigProp.Name == "rotLat" )
+ nToken = XML_lat;
+ else if( rLightRigProp.Name == "rotLon" )
+ nToken = XML_lon;
+ else if( rLightRigProp.Name == "rotRev" )
+ nToken = XML_rev;
+ aLightRigRotationAttrList->add( nToken, OString::number( nVal ).getStr() );
+ bLightRigRotationPresent = true;
+ }
+ }
+
+ mpFS->startElementNS(XML_a, XML_scene3d);
+
+ if( aEffectProps.hasElements() )
+ {
+ mpFS->startElementNS( XML_a, XML_camera, aCameraAttrList );
+ if( bCameraRotationPresent )
+ {
+ mpFS->singleElementNS( XML_a, XML_rot, aCameraRotationAttrList );
+ }
+ mpFS->endElementNS( XML_a, XML_camera );
+ }
+ else
+ {
+ // a:camera with Word default values - Word won't open the document if this is not present
+ mpFS->singleElementNS(XML_a, XML_camera, XML_prst, "orthographicFront");
+ }
+
+ if( aEffectProps.hasElements() )
+ {
+ mpFS->startElementNS( XML_a, XML_lightRig, aLightRigAttrList );
+ if( bLightRigRotationPresent )
+ {
+ mpFS->singleElementNS( XML_a, XML_rot, aLightRigRotationAttrList );
+ }
+ mpFS->endElementNS( XML_a, XML_lightRig );
+ }
+ else
+ {
+ // a:lightRig with Word default values - Word won't open the document if this is not present
+ mpFS->singleElementNS(XML_a, XML_lightRig, XML_rig, "threePt", XML_dir, "t");
+ }
+
+ mpFS->endElementNS( XML_a, XML_scene3d );
+
+ if( !aShape3DProps.hasElements() )
+ return;
+
+ bool bBevelTPresent = false, bBevelBPresent = false;
+ Sequence< PropertyValue > aExtrusionColorProps, aContourColorProps;
+ rtl::Reference<sax_fastparser::FastAttributeList> aBevelTAttrList = FastSerializerHelper::createAttrList();
+ rtl::Reference<sax_fastparser::FastAttributeList> aBevelBAttrList = FastSerializerHelper::createAttrList();
+ rtl::Reference<sax_fastparser::FastAttributeList> aShape3DAttrList = FastSerializerHelper::createAttrList();
+ for( const auto& rShape3DProp : std::as_const(aShape3DProps) )
+ {
+ if( rShape3DProp.Name == "extrusionH" || rShape3DProp.Name == "contourW" || rShape3DProp.Name == "z" )
+ {
+ sal_Int32 nVal = 0, nToken = XML_none;
+ rShape3DProp.Value >>= nVal;
+ if( rShape3DProp.Name == "extrusionH" )
+ nToken = XML_extrusionH;
+ else if( rShape3DProp.Name == "contourW" )
+ nToken = XML_contourW;
+ else if( rShape3DProp.Name == "z" )
+ nToken = XML_z;
+ aShape3DAttrList->add( nToken, OString::number( nVal ).getStr() );
+ }
+ else if( rShape3DProp.Name == "prstMaterial" )
+ {
+ OUString sVal;
+ rShape3DProp.Value >>= sVal;
+ aShape3DAttrList->add(XML_prstMaterial, sVal);
+ }
+ else if( rShape3DProp.Name == "extrusionClr" )
+ {
+ rShape3DProp.Value >>= aExtrusionColorProps;
+ }
+ else if( rShape3DProp.Name == "contourClr" )
+ {
+ rShape3DProp.Value >>= aContourColorProps;
+ }
+ else if( rShape3DProp.Name == "bevelT" || rShape3DProp.Name == "bevelB" )
+ {
+ Sequence< PropertyValue > aBevelProps;
+ rShape3DProp.Value >>= aBevelProps;
+ if ( !aBevelProps.hasElements() )
+ continue;
+
+ rtl::Reference<sax_fastparser::FastAttributeList> aBevelAttrList;
+ if( rShape3DProp.Name == "bevelT" )
+ {
+ bBevelTPresent = true;
+ aBevelAttrList = aBevelTAttrList;
+ }
+ else
+ {
+ bBevelBPresent = true;
+ aBevelAttrList = aBevelBAttrList;
+ }
+ for( const auto& rBevelProp : std::as_const(aBevelProps) )
+ {
+ if( rBevelProp.Name == "w" || rBevelProp.Name == "h" )
+ {
+ sal_Int32 nVal = 0, nToken = XML_none;
+ rBevelProp.Value >>= nVal;
+ if( rBevelProp.Name == "w" )
+ nToken = XML_w;
+ else if( rBevelProp.Name == "h" )
+ nToken = XML_h;
+ aBevelAttrList->add( nToken, OString::number( nVal ).getStr() );
+ }
+ else if( rBevelProp.Name == "prst" )
+ {
+ OUString sVal;
+ rBevelProp.Value >>= sVal;
+ aBevelAttrList->add(XML_prst, sVal);
+ }
+ }
+
+ }
+ }
+
+ mpFS->startElementNS( XML_a, XML_sp3d, aShape3DAttrList );
+ if( bBevelTPresent )
+ {
+ mpFS->singleElementNS( XML_a, XML_bevelT, aBevelTAttrList );
+ }
+ if( bBevelBPresent )
+ {
+ mpFS->singleElementNS( XML_a, XML_bevelB, aBevelBAttrList );
+ }
+ if( aExtrusionColorProps.hasElements() )
+ {
+ OUString sSchemeClr;
+ ::Color nColor;
+ sal_Int32 nTransparency(0);
+ Sequence< PropertyValue > aColorTransformations;
+ for( const auto& rExtrusionColorProp : std::as_const(aExtrusionColorProps) )
+ {
+ if( rExtrusionColorProp.Name == "schemeClr" )
+ rExtrusionColorProp.Value >>= sSchemeClr;
+ else if( rExtrusionColorProp.Name == "schemeClrTransformations" )
+ rExtrusionColorProp.Value >>= aColorTransformations;
+ else if( rExtrusionColorProp.Name == "rgbClr" )
+ rExtrusionColorProp.Value >>= nColor;
+ else if( rExtrusionColorProp.Name == "rgbClrTransparency" )
+ rExtrusionColorProp.Value >>= nTransparency;
+ }
+ mpFS->startElementNS(XML_a, XML_extrusionClr);
+
+ if( sSchemeClr.isEmpty() )
+ WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
+ else
+ WriteColor( sSchemeClr, aColorTransformations );
+
+ mpFS->endElementNS( XML_a, XML_extrusionClr );
+ }
+ if( aContourColorProps.hasElements() )
+ {
+ OUString sSchemeClr;
+ ::Color nColor;
+ sal_Int32 nTransparency(0);
+ Sequence< PropertyValue > aColorTransformations;
+ for( const auto& rContourColorProp : std::as_const(aContourColorProps) )
+ {
+ if( rContourColorProp.Name == "schemeClr" )
+ rContourColorProp.Value >>= sSchemeClr;
+ else if( rContourColorProp.Name == "schemeClrTransformations" )
+ rContourColorProp.Value >>= aColorTransformations;
+ else if( rContourColorProp.Name == "rgbClr" )
+ rContourColorProp.Value >>= nColor;
+ else if( rContourColorProp.Name == "rgbClrTransparency" )
+ rContourColorProp.Value >>= nTransparency;
+ }
+ mpFS->startElementNS(XML_a, XML_contourClr);
+
+ if( sSchemeClr.isEmpty() )
+ WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
+ else
+ WriteColor( sSchemeClr, aContourColorProps );
+
+ mpFS->endElementNS( XML_a, XML_contourClr );
+ }
+ mpFS->endElementNS( XML_a, XML_sp3d );
+}
+
+void DrawingML::WriteArtisticEffect( const Reference< XPropertySet >& rXPropSet )
+{
+ if( !GetProperty( rXPropSet, "InteropGrabBag" ) )
+ return;
+
+ PropertyValue aEffect;
+ Sequence< PropertyValue > aGrabBag;
+ mAny >>= aGrabBag;
+ auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
+ [](const PropertyValue& rProp) { return rProp.Name == "ArtisticEffectProperties"; });
+ if (pProp != std::cend(aGrabBag))
+ pProp->Value >>= aEffect;
+ sal_Int32 nEffectToken = ArtisticEffectProperties::getEffectToken( aEffect.Name );
+ if( nEffectToken == XML_none )
+ return;
+
+ Sequence< PropertyValue > aAttrs;
+ aEffect.Value >>= aAttrs;
+ rtl::Reference<sax_fastparser::FastAttributeList> aAttrList = FastSerializerHelper::createAttrList();
+ OString sRelId;
+ for( const auto& rAttr : std::as_const(aAttrs) )
+ {
+ sal_Int32 nToken = ArtisticEffectProperties::getEffectToken( rAttr.Name );
+ if( nToken != XML_none )
+ {
+ sal_Int32 nVal = 0;
+ rAttr.Value >>= nVal;
+ aAttrList->add( nToken, OString::number( nVal ).getStr() );
+ }
+ else if( rAttr.Name == "OriginalGraphic" )
+ {
+ Sequence< PropertyValue > aGraphic;
+ rAttr.Value >>= aGraphic;
+ Sequence< sal_Int8 > aGraphicData;
+ OUString sGraphicId;
+ for( const auto& rProp : std::as_const(aGraphic) )
+ {
+ if( rProp.Name == "Id" )
+ rProp.Value >>= sGraphicId;
+ else if( rProp.Name == "Data" )
+ rProp.Value >>= aGraphicData;
+ }
+ sRelId = WriteWdpPicture( sGraphicId, aGraphicData );
+ }
+ }
+
+ mpFS->startElementNS(XML_a, XML_extLst);
+ mpFS->startElementNS(XML_a, XML_ext, XML_uri, "{BEBA8EAE-BF5A-486C-A8C5-ECC9F3942E4B}");
+ mpFS->startElementNS( XML_a14, XML_imgProps,
+ FSNS(XML_xmlns, XML_a14), mpFB->getNamespaceURL(OOX_NS(a14)) );
+ mpFS->startElementNS(XML_a14, XML_imgLayer, FSNS(XML_r, XML_embed), sRelId);
+ mpFS->startElementNS(XML_a14, XML_imgEffect);
+
+ mpFS->singleElementNS( XML_a14, nEffectToken, aAttrList );
+
+ mpFS->endElementNS( XML_a14, XML_imgEffect );
+ mpFS->endElementNS( XML_a14, XML_imgLayer );
+ mpFS->endElementNS( XML_a14, XML_imgProps );
+ mpFS->endElementNS( XML_a, XML_ext );
+ mpFS->endElementNS( XML_a, XML_extLst );
+}
+
+OString DrawingML::WriteWdpPicture( const OUString& rFileId, const Sequence< sal_Int8 >& rPictureData )
+{
+ if (!maWdpCache.empty())
+ {
+ std::map<OUString, OUString>::iterator aCachedItem = maWdpCache.top().find(rFileId);
+ if (aCachedItem != maWdpCache.top().end())
+ return OUStringToOString(aCachedItem->second, RTL_TEXTENCODING_UTF8);
+ }
+
+ OUString sFileName = "media/hdphoto" + OUString::number( mnWdpImageCounter.top()++ ) + ".wdp";
+ Reference< XOutputStream > xOutStream = mpFB->openFragmentStream( OUStringBuffer()
+ .appendAscii( GetComponentDir() )
+ .append( "/" + sFileName )
+ .makeStringAndClear(),
+ "image/vnd.ms-photo" );
+ OUString sId;
+ xOutStream->writeBytes( rPictureData );
+ xOutStream->closeOutput();
+
+ sId = mpFB->addRelation( mpFS->getOutputStream(),
+ oox::getRelationship(Relationship::HDPHOTO),
+ OUStringBuffer()
+ .appendAscii( GetRelationCompPrefix() )
+ .append( sFileName )
+ .makeStringAndClear() );
+
+ if (!maWdpCache.empty())
+ maWdpCache.top()[rFileId] = sId;
+
+ return OUStringToOString( sId, RTL_TEXTENCODING_UTF8 );
+}
+
+void DrawingML::WriteDiagram(const css::uno::Reference<css::drawing::XShape>& rXShape, int nDiagramId)
+{
+ uno::Reference<beans::XPropertySet> xPropSet(rXShape, uno::UNO_QUERY);
+
+ uno::Reference<xml::dom::XDocument> dataDom;
+ uno::Reference<xml::dom::XDocument> layoutDom;
+ uno::Reference<xml::dom::XDocument> styleDom;
+ uno::Reference<xml::dom::XDocument> colorDom;
+ uno::Reference<xml::dom::XDocument> drawingDom;
+ uno::Sequence<uno::Sequence<uno::Any>> xDataRelSeq;
+ uno::Sequence<uno::Any> diagramDrawing;
+
+ // retrieve the doms from the GrabBag
+ uno::Sequence<beans::PropertyValue> propList;
+ xPropSet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= propList;
+ for (const auto& rProp : std::as_const(propList))
+ {
+ OUString propName = rProp.Name;
+ if (propName == "OOXData")
+ rProp.Value >>= dataDom;
+ else if (propName == "OOXLayout")
+ rProp.Value >>= layoutDom;
+ else if (propName == "OOXStyle")
+ rProp.Value >>= styleDom;
+ else if (propName == "OOXColor")
+ rProp.Value >>= colorDom;
+ else if (propName == "OOXDrawing")
+ {
+ rProp.Value >>= diagramDrawing;
+ diagramDrawing[0]
+ >>= drawingDom; // if there is OOXDrawing property then set drawingDom here only.
+ }
+ else if (propName == "OOXDiagramDataRels")
+ rProp.Value >>= xDataRelSeq;
+ }
+
+ // check that we have the 4 mandatory XDocuments
+ // if not, there was an error importing and we won't output anything
+ if (!dataDom.is() || !layoutDom.is() || !styleDom.is() || !colorDom.is())
+ return;
+
+ // generate a unique id
+ rtl::Reference<sax_fastparser::FastAttributeList> pDocPrAttrList
+ = sax_fastparser::FastSerializerHelper::createAttrList();
+ pDocPrAttrList->add(XML_id, OString::number(nDiagramId).getStr());
+ OString sName = "Diagram" + OString::number(nDiagramId);
+ pDocPrAttrList->add(XML_name, sName);
+
+ if (GetDocumentType() == DOCUMENT_DOCX)
+ {
+ mpFS->singleElementNS(XML_wp, XML_docPr, pDocPrAttrList);
+ mpFS->singleElementNS(XML_wp, XML_cNvGraphicFramePr);
+
+ mpFS->startElementNS(XML_a, XML_graphic, FSNS(XML_xmlns, XML_a),
+ mpFB->getNamespaceURL(OOX_NS(dml)));
+ }
+ else
+ {
+ mpFS->startElementNS(XML_p, XML_nvGraphicFramePr);
+
+ mpFS->singleElementNS(XML_p, XML_cNvPr, pDocPrAttrList);
+ mpFS->singleElementNS(XML_p, XML_cNvGraphicFramePr);
+
+ mpFS->startElementNS(XML_p, XML_nvPr);
+ mpFS->startElementNS(XML_p, XML_extLst);
+ // change tracking extension - required in PPTX
+ mpFS->startElementNS(XML_p, XML_ext, XML_uri, "{D42A27DB-BD31-4B8C-83A1-F6EECF244321}");
+ mpFS->singleElementNS(XML_p14, XML_modId,
+ FSNS(XML_xmlns, XML_p14), mpFB->getNamespaceURL(OOX_NS(p14)),
+ XML_val,
+ OString::number(comphelper::rng::uniform_uint_distribution(1, SAL_MAX_UINT32)));
+ mpFS->endElementNS(XML_p, XML_ext);
+ mpFS->endElementNS(XML_p, XML_extLst);
+ mpFS->endElementNS(XML_p, XML_nvPr);
+
+ mpFS->endElementNS(XML_p, XML_nvGraphicFramePr);
+
+ // store size and position of background shape instead of group shape
+ // as some shapes may be outside
+ css::uno::Reference<css::drawing::XShapes> xShapes(rXShape, uno::UNO_QUERY);
+ if (xShapes.is() && xShapes->hasElements())
+ {
+ css::uno::Reference<css::drawing::XShape> xShapeBg(xShapes->getByIndex(0),
+ uno::UNO_QUERY);
+ awt::Point aPos = xShapeBg->getPosition();
+ awt::Size aSize = xShapeBg->getSize();
+ WriteTransformation(
+ xShapeBg, tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)),
+ XML_p, false, false, 0, false);
+ }
+
+ mpFS->startElementNS(XML_a, XML_graphic);
+ }
+
+ mpFS->startElementNS(XML_a, XML_graphicData, XML_uri,
+ "http://schemas.openxmlformats.org/drawingml/2006/diagram");
+
+ OUString sRelationCompPrefix = OUString::createFromAscii(GetRelationCompPrefix());
+
+ // add data relation
+ OUString dataFileName = "diagrams/data" + OUString::number(nDiagramId) + ".xml";
+ OUString dataRelId =
+ mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDATA),
+ OUStringConcatenation(sRelationCompPrefix + dataFileName));
+
+ // add layout relation
+ OUString layoutFileName = "diagrams/layout" + OUString::number(nDiagramId) + ".xml";
+ OUString layoutRelId = mpFB->addRelation(mpFS->getOutputStream(),
+ oox::getRelationship(Relationship::DIAGRAMLAYOUT),
+ OUStringConcatenation(sRelationCompPrefix + layoutFileName));
+
+ // add style relation
+ OUString styleFileName = "diagrams/quickStyle" + OUString::number(nDiagramId) + ".xml";
+ OUString styleRelId = mpFB->addRelation(mpFS->getOutputStream(),
+ oox::getRelationship(Relationship::DIAGRAMQUICKSTYLE),
+ OUStringConcatenation(sRelationCompPrefix + styleFileName));
+
+ // add color relation
+ OUString colorFileName = "diagrams/colors" + OUString::number(nDiagramId) + ".xml";
+ OUString colorRelId = mpFB->addRelation(mpFS->getOutputStream(),
+ oox::getRelationship(Relationship::DIAGRAMCOLORS),
+ OUStringConcatenation(sRelationCompPrefix + colorFileName));
+
+ OUString drawingFileName;
+ if (drawingDom.is())
+ {
+ // add drawing relation
+ drawingFileName = "diagrams/drawing" + OUString::number(nDiagramId) + ".xml";
+ OUString drawingRelId = mpFB->addRelation(
+ mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDRAWING),
+ OUStringConcatenation(sRelationCompPrefix + drawingFileName));
+
+ // the data dom contains a reference to the drawing relation. We need to update it with the new generated
+ // relation value before writing the dom to a file
+
+ // Get the dsp:damaModelExt node from the dom
+ uno::Reference<xml::dom::XNodeList> nodeList = dataDom->getElementsByTagNameNS(
+ "http://schemas.microsoft.com/office/drawing/2008/diagram", "dataModelExt");
+
+ // There must be one element only so get it
+ uno::Reference<xml::dom::XNode> node = nodeList->item(0);
+
+ // Get the list of attributes of the node
+ uno::Reference<xml::dom::XNamedNodeMap> nodeMap = node->getAttributes();
+
+ // Get the node with the relId attribute and set its new value
+ uno::Reference<xml::dom::XNode> relIdNode = nodeMap->getNamedItem("relId");
+ relIdNode->setNodeValue(drawingRelId);
+ }
+
+ mpFS->singleElementNS(XML_dgm, XML_relIds,
+ FSNS(XML_xmlns, XML_dgm), mpFB->getNamespaceURL(OOX_NS(dmlDiagram)),
+ FSNS(XML_xmlns, XML_r), mpFB->getNamespaceURL(OOX_NS(officeRel)),
+ FSNS(XML_r, XML_dm), dataRelId, FSNS(XML_r, XML_lo), layoutRelId,
+ FSNS(XML_r, XML_qs), styleRelId, FSNS(XML_r, XML_cs), colorRelId);
+
+ mpFS->endElementNS(XML_a, XML_graphicData);
+ mpFS->endElementNS(XML_a, XML_graphic);
+
+ uno::Reference<xml::sax::XSAXSerializable> serializer;
+ uno::Reference<xml::sax::XWriter> writer
+ = xml::sax::Writer::create(comphelper::getProcessComponentContext());
+
+ OUString sDir = OUString::createFromAscii(GetComponentDir());
+
+ // write data file
+ serializer.set(dataDom, uno::UNO_QUERY);
+ uno::Reference<io::XOutputStream> xDataOutputStream = mpFB->openFragmentStream(
+ sDir + "/" + dataFileName,
+ "application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml");
+ writer->setOutputStream(xDataOutputStream);
+ serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
+ uno::Sequence<beans::StringPair>());
+
+ // write the associated Images and rels for data file
+ writeDiagramRels(xDataRelSeq, xDataOutputStream, u"OOXDiagramDataRels", nDiagramId);
+
+ // write layout file
+ serializer.set(layoutDom, uno::UNO_QUERY);
+ writer->setOutputStream(mpFB->openFragmentStream(
+ sDir + "/" + layoutFileName,
+ "application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml"));
+ serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
+ uno::Sequence<beans::StringPair>());
+
+ // write style file
+ serializer.set(styleDom, uno::UNO_QUERY);
+ writer->setOutputStream(mpFB->openFragmentStream(
+ sDir + "/" + styleFileName,
+ "application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml"));
+ serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
+ uno::Sequence<beans::StringPair>());
+
+ // write color file
+ serializer.set(colorDom, uno::UNO_QUERY);
+ writer->setOutputStream(mpFB->openFragmentStream(
+ sDir + "/" + colorFileName,
+ "application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml"));
+ serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
+ uno::Sequence<beans::StringPair>());
+
+ // write drawing file
+ if (!drawingDom.is())
+ return;
+
+ serializer.set(drawingDom, uno::UNO_QUERY);
+ uno::Reference<io::XOutputStream> xDrawingOutputStream = mpFB->openFragmentStream(
+ sDir + "/" + drawingFileName, "application/vnd.ms-office.drawingml.diagramDrawing+xml");
+ writer->setOutputStream(xDrawingOutputStream);
+ serializer->serialize(
+ uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
+ uno::Sequence<beans::StringPair>());
+
+ // write the associated Images and rels for drawing file
+ uno::Sequence<uno::Sequence<uno::Any>> xDrawingRelSeq;
+ diagramDrawing[1] >>= xDrawingRelSeq;
+ writeDiagramRels(xDrawingRelSeq, xDrawingOutputStream, u"OOXDiagramDrawingRels", nDiagramId);
+}
+
+void DrawingML::writeDiagramRels(const uno::Sequence<uno::Sequence<uno::Any>>& xRelSeq,
+ const uno::Reference<io::XOutputStream>& xOutStream,
+ std::u16string_view sGrabBagProperyName, int nDiagramId)
+{
+ // add image relationships of OOXData, OOXDiagram
+ OUString sType(oox::getRelationship(Relationship::IMAGE));
+ uno::Reference<xml::sax::XWriter> xWriter
+ = xml::sax::Writer::create(comphelper::getProcessComponentContext());
+ xWriter->setOutputStream(xOutStream);
+
+ // retrieve the relationships from Sequence
+ for (sal_Int32 j = 0; j < xRelSeq.getLength(); j++)
+ {
+ // diagramDataRelTuple[0] => RID,
+ // diagramDataRelTuple[1] => xInputStream
+ // diagramDataRelTuple[2] => extension
+ uno::Sequence<uno::Any> diagramDataRelTuple = xRelSeq[j];
+
+ OUString sRelId;
+ OUString sExtension;
+ diagramDataRelTuple[0] >>= sRelId;
+ diagramDataRelTuple[2] >>= sExtension;
+ OUString sContentType;
+ if (sExtension.equalsIgnoreAsciiCase(".WMF"))
+ sContentType = "image/x-wmf";
+ else
+ sContentType = OUString::Concat("image/") + sExtension.subView(1);
+ sRelId = sRelId.copy(3);
+
+ StreamDataSequence dataSeq;
+ diagramDataRelTuple[1] >>= dataSeq;
+ uno::Reference<io::XInputStream> dataImagebin(
+ new ::comphelper::SequenceInputStream(dataSeq));
+
+ //nDiagramId is used to make the name unique irrespective of the number of smart arts.
+ OUString sFragment = OUString::Concat("media/") + sGrabBagProperyName
+ + OUString::number(nDiagramId) + "_"
+ + OUString::number(j) + sExtension;
+
+ PropertySet aProps(xOutStream);
+ aProps.setAnyProperty(PROP_RelId, uno::Any(sRelId.toInt32()));
+
+ mpFB->addRelation(xOutStream, sType, OUStringConcatenation("../" + sFragment));
+
+ OUString sDir = OUString::createFromAscii(GetComponentDir());
+ uno::Reference<io::XOutputStream> xBinOutStream
+ = mpFB->openFragmentStream(sDir + "/" + sFragment, sContentType);
+
+ try
+ {
+ comphelper::OStorageHelper::CopyInputToOutput(dataImagebin, xBinOutStream);
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("oox.drawingml", "DrawingML::writeDiagramRels Failed to copy grabbaged Image");
+ }
+ dataImagebin->closeInput();
+ }
+}
+
+void DrawingML::WriteFromTo(const uno::Reference<css::drawing::XShape>& rXShape, const awt::Size& aPageSize,
+ const FSHelperPtr& pDrawing)
+{
+ awt::Point aTopLeft = rXShape->getPosition();
+ awt::Size aSize = rXShape->getSize();
+
+ SdrObject* pObj = SdrObject::getSdrObjectFromXShape(rXShape);
+ if (pObj)
+ {
+ Degree100 nRotation = pObj->GetRotateAngle();
+ if (nRotation)
+ {
+ sal_Int16 nHalfWidth = aSize.Width / 2;
+ sal_Int16 nHalfHeight = aSize.Height / 2;
+ // aTopLeft needs correction for rotated customshapes
+ if (pObj->GetObjIdentifier() == SdrObjKind::CustomShape)
+ {
+ // Center of bounding box of the rotated shape
+ const auto aSnapRectCenter(pObj->GetSnapRect().Center());
+ aTopLeft.X = aSnapRectCenter.X() - nHalfWidth;
+ aTopLeft.Y = aSnapRectCenter.Y() - nHalfHeight;
+ }
+
+ // MSO changes the anchor positions at these angles and that does an extra 90 degrees
+ // rotation on our shapes, so we output it in such position that MSO
+ // can draw this shape correctly.
+ if ((nRotation >= 4500_deg100 && nRotation < 13500_deg100) || (nRotation >= 22500_deg100 && nRotation < 31500_deg100))
+ {
+ aTopLeft.X = aTopLeft.X - nHalfHeight + nHalfWidth;
+ aTopLeft.Y = aTopLeft.Y - nHalfWidth + nHalfHeight;
+
+ std::swap(aSize.Width, aSize.Height);
+ }
+ }
+ }
+
+ tools::Rectangle aLocation(aTopLeft.X, aTopLeft.Y, aTopLeft.X + aSize.Width, aTopLeft.Y + aSize.Height);
+ double nXpos = static_cast<double>(aLocation.TopLeft().getX()) / static_cast<double>(aPageSize.Width);
+ double nYpos = static_cast<double>(aLocation.TopLeft().getY()) / static_cast<double>(aPageSize.Height);
+
+ pDrawing->startElement(FSNS(XML_cdr, XML_from));
+ pDrawing->startElement(FSNS(XML_cdr, XML_x));
+ pDrawing->write(nXpos);
+ pDrawing->endElement(FSNS(XML_cdr, XML_x));
+ pDrawing->startElement(FSNS(XML_cdr, XML_y));
+ pDrawing->write(nYpos);
+ pDrawing->endElement(FSNS(XML_cdr, XML_y));
+ pDrawing->endElement(FSNS(XML_cdr, XML_from));
+
+ nXpos = static_cast<double>(aLocation.BottomRight().getX()) / static_cast<double>(aPageSize.Width);
+ nYpos = static_cast<double>(aLocation.BottomRight().getY()) / static_cast<double>(aPageSize.Height);
+
+ pDrawing->startElement(FSNS(XML_cdr, XML_to));
+ pDrawing->startElement(FSNS(XML_cdr, XML_x));
+ pDrawing->write(nXpos);
+ pDrawing->endElement(FSNS(XML_cdr, XML_x));
+ pDrawing->startElement(FSNS(XML_cdr, XML_y));
+ pDrawing->write(nYpos);
+ pDrawing->endElement(FSNS(XML_cdr, XML_y));
+ pDrawing->endElement(FSNS(XML_cdr, XML_to));
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/export/ooxml-export-notes.txt b/oox/source/export/ooxml-export-notes.txt
new file mode 100644
index 000000000..36c42fc53
--- /dev/null
+++ b/oox/source/export/ooxml-export-notes.txt
@@ -0,0 +1,235 @@
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+OOXML generally
+---------------
+
+- http://www.ecma-international.org/publications/standards/Ecma-376.htm
+- http://www.asahi-net.or.jp/~eb2m-mrt/ooxml/dependencies.html
+
+Related modules
+---------------
+
+- oox
+ - .xlsx and .pptx import
+
+- writerfilter
+ - import of .docx, uses also oox for the graphics etc.
+
+- filter
+ - the configuration stuff (so that the filters appear in the filepicker)
+
+Old binary filters (export)
+---------------------------
+
+- doc export
+ - sw/source/filter/ww8/wrtww8*
+ - wrtww8.cxx:2191 [SwWW8Writer::StoreDoc()] is the entry point
+
+ - eg.
+ #0 SwWW8Writer::WriteText (this=0x2aaab3dfb7c0) at /local/ooxml/ooxml/sw/source/filter/ww8/wrtww8.cxx:1846
+ #1 0x00002aaaae75a545 in SwWW8Writer::WriteMainText (this=0x2aaab3d6a870)
+ at /local/ooxml/ooxml/sw/source/filter/ww8/wrtww8.cxx:1925
+ #2 0x00002aaaae75e357 in SwWW8Writer::StoreDoc1 (this=0x2aaab3d6a870)
+ at /local/ooxml/ooxml/sw/source/filter/ww8/wrtww8.cxx:2076
+ #3 0x00002aaaae7605ec in SwWW8Writer::StoreDoc (this=0x2aaab3d6a870)
+ at /local/ooxml/ooxml/sw/source/filter/ww8/wrtww8.cxx:2383
+ #4 0x00002aaaae760fd5 in SwWW8Writer::WriteStorage (this=0x2aaab3d6a870)
+ at /local/ooxml/ooxml/sw/source/filter/ww8/wrtww8.cxx:2547
+ #5 0x00002aaaae70b793 in StgWriter::Write (this=0x2aaab3d6a870, rPaM=@0x2b3802a2b640, rStg=@0x2aaab3d621c0,
+ pFName=0x7fffb1b285c0) at /local/ooxml/ooxml/sw/source/filter/writer/writer.cxx:653
+ #6 0x00002aaaae70b84d in Writer::Write (this=0x2aaab3d6a870, rPaM=@0x2b3802a2b640, rStrm=@0x2aaaad979d20,
+ pFName=0x7fffb1b285c0) at /local/ooxml/ooxml/sw/source/filter/writer/writer.cxx:358
+ #7 0x00002aaaae70b993 in Writer::Write (this=0x2aaab3d6a870, rPam=@0x2b3802a2b640, rMed=@0x2aaaad999f30,
+ pFileName=0x7fffb1b285c0) at /local/ooxml/ooxml/sw/source/filter/writer/writer.cxx:385
+ #8 0x00002aaaae6375d7 in SwWriter::Write (this=0x7fffb1b28410, rxWriter=@0x7fffb1b285d0,
+ pRealFileName=0x7fffb1b285c0) at /local/ooxml/ooxml/sw/source/filter/basflt/shellio.cxx:963
+ #9 0x00002aaaae87cc1e in SwDocShell::ConvertTo (this=0xcc27f0, rMedium=@0x2aaaad999f30)
+ at /local/ooxml/ooxml/sw/source/ui/app/docsh.cxx:924
+ #10 0x00002b37faae6b58 in SfxObjectShell::DoLoad ()
+ from /local/ooxml/inst/openoffice.org3.0/program/../basis-link/program//libsfxlx.so
+
+- xls export
+ - sc/source/filter/excel/xe*
+
+ - eg.
+ #0 XclExpRecord::Save (this=0x11ae4c0, rStrm=@0x7fff5e6335d0)
+ at /local/ooxml/ooxml/sc/source/filter/excel/xerecord.cxx:88
+ #1 0x00002aaaae562c4a in ExcRecord::Save (this=0x11ae4c0, rStrm=@0x7fff5e6335d0)
+ at /local/ooxml/ooxml/sc/source/filter/excel/excrecds.cxx:168
+ #2 0x00002aaaae54b0fa in XclExpRecordList<XclExpRecordBase>::Save (this=0x11c5d18, rStrm=@0x7fff5e6335d0)
+ at ../inc/xerecord.hxx:281
+ #3 0x00002aaaae547541 in ExcTable::Write (this=0x11c5cf8, rStr=@0x7fff5e6335d0)
+ at /local/ooxml/ooxml/sc/source/filter/excel/excdoc.cxx:455
+ #4 0x00002aaaae5475fb in ExcDocument::Write (this=0x11c5ce0, rSvStrm=@0x2aaab3dcd070)
+ at /local/ooxml/ooxml/sc/source/filter/excel/excdoc.cxx:525
+ #5 0x00002aaaae568add in ExportBiff5::Write (this=0x7fff5e6339c0)
+ at /local/ooxml/ooxml/sc/source/filter/excel/expop2.cxx:119
+ #6 0x00002aaaae54f4af in ScExportExcel5 (rMedium=@0x2aaab3d87410, pDocument=0xce6a00, bBiff8=1 '\001', eNach=1)
+ at /local/ooxml/ooxml/sc/source/filter/excel/excel.cxx:252
+ #7 0x00002aaaadf1b70a in ScDocShell::ConvertTo (this=0xce6990, rMed=@0x2aaab3d87410)
+ at /local/ooxml/ooxml/sc/source/ui/docshell/docsh.cxx:2080
+ #8 0x00002b354dfd8b58 in SfxObjectShell::DoLoad ()
+ from /local/ooxml/inst/openoffice.org3.0/program/../basis-link/program//libsfxlx.so
+
+ - Current approach is to add a XclExpRecordBase::SaveXml() method, which
+ would be used to write the XML content (while Save() would continue
+ writing the BIFF format).
+ - Q: How do we get to the Save()/SaveXml() methods (e.g. the SST export code)
+ #0 XclExpSstImpl::Save (this=0x1b170b0, rStrm=@0x7fffd4d5c4a0)
+ at /home/jon/Development/OpenOffice.org/ooxml/sc/source/filter/excel/xecontent.cxx:224
+ #1 0x00007f68b7e46ff7 in XclExpSst::Save (this=0x1abc300,
+ rStrm=@0x7fffd4d5c4a0)
+ at /home/jon/Development/OpenOffice.org/ooxml/sc/source/filter/excel/xecontent.cxx:351
+ #2 0x00007f68b7de5090 in XclExpRecordList<XclExpRecordBase>::Save (
+ this=0x1b2d168, rStrm=@0x7fffd4d5c4a0) at ../inc/xerecord.hxx:282
+ // as above, starting at frame 2
+
+ - Thus, to get to the SaveXml() method, we need to add a slew of WriteXml()
+ methods that will (eventually) invoke the SaveXml() methods.
+
+ - ZipStorage for XML handling and StorageRef (XStorage interface)
+ - To construct ZipStorage, need XMultiServiceFactory (!), and
+ XInputStream.
+ - Have an SvStream; need to wrap SvStream with XInputStream
+ - OInputStreamWrapper in <unotools/streamwrap.hxx>
+ - Where do I get XMultiServiceFactory?
+ - Lots of places -- just grep
+ - perhaps XmlFilterBase _does_ make sense here.
+ - Do it anyway.
+ - Looking into having XclExpXmlStream inherit from ZipFilterBase
+ - problem: exception during construction, because ZipStorage hates me:
+ #0 OStorageFactory::createInstanceWithArguments (this=0x10612a0,
+ aArguments=@0x7fffe2ef76d0)
+ at /home/jon/Development/OpenOffice.org/ooxml/package/source/xstor/xfactory.cxx:275
+ #1 0x00007f12d93f0d5c in comphelper::OStorageHelper::GetStorageOfFormatFromStream (aFormat=@0x7fffe2ef7780, xStream=@0x1a502d8, nStorageMode=15,
+ xFactory=@0x1a502c0)
+ at /home/jon/Development/OpenOffice.org/ooxml/comphelper/source/misc/storagehelper.cxx:342
+ #2 0x00007f12c33d1a6d in ZipStorage (this=0x1a92550, rxFactory=@0x1a502c0,
+ rxStream=@0x1a502d8)
+ at /home/jon/Development/OpenOffice.org/ooxml/oox/source/helper/zipstorage.cxx:87
+ #3 0x00007f12c33f089e in oox::core::XmlFilterBase::implCreateStorage (
+ this=0x7fffe2ef7930, rxInStream=@0x1a502d0, rxStream=@0x1a502d8)
+ at /home/jon/Development/OpenOffice.org/ooxml/oox/source/core/xmlfilterbase.cxx:298
+ #4 0x00007f12c33dd204 in oox::core::FilterBase::filter (this=0x7fffe2ef7930,
+ rDescriptor=@0x7fffe2ef78d0)
+ at /home/jon/Development/OpenOffice.org/ooxml/oox/source/core/filterbase.cxx:284
+ #5 0x00007f12c68097a2 in XclExpXmlStream (this=0x7fffe2ef7930,
+ rSMgr=@0x7fffe2ef79a0, rStrm=@0x18d6f90)
+ at /home/jon/Development/OpenOffice.org/ooxml/sc/source/filter/excel/xestream.cxx:659
+ #6 0x00007f12c674c8c1 in ExcDocument::WriteXml (this=0x15911f0,
+ rStrm=@0x18d6f90)
+ at /home/jon/Development/OpenOffice.org/ooxml/sc/source/filter/excel/excdoc.cxx:575
+ ...
+ - Actual problem: xfactory.cxx:274, the CheckPackageSignature_Impl() call.
+ - fails because the empty file has content (!), thus fails the "package
+ signature check" (which tries to ensure the file format is correct).
+ - Underlying file is an SvFileStream, created in
+ SfxMedium::GetOutStream().
+ - So why's CheckPackageSignature_Impl() fail? Because
+ lcl_ExportExcel2007Xml() had the code:
+
+ tools::SvRef<SotStorage> xRootStrg = new SotStorage( pMedStrm, FALSE );
+
+ That is, it was creating an OLE Structured Storage document over the
+ SvStream, and then (later) used the *same* SvStream and passed it to
+ ZipStorage. This caused ZipStorage to complain because OLESS data was
+ already present in the file, with a different file signature than what
+ ZipPackage was expecting (go figure).
+
+- ppt export
+ - sd/source/filter/eppt/*
+ - svx/source/msfilter
+ - for eg. Escher export
+ - Escher: http://chicago.sourceforge.net/devel/docs/escher/index.html
+
+ - eg.
+ #0 PPTWriter (this=0x15807d0, rSvStorage=@0x7fff894f5340, rXModel=@0x142a2e8, rXStatInd=@0x142a2f0, pVBA=0x0,
+ nCnvrtFlags=15) at /local/ooxml/ooxml/sd/source/filter/eppt/eppt.cxx:268
+ #1 0x00002aaab3895719 in ExportPPT (rSvStorage=@0x7fff894f5340, rXModel=@0x142a2e8, rXStatInd=@0x142a2f0,
+ pVBA=0x0, nCnvrtFlags=15) at /local/ooxml/ooxml/sd/source/filter/eppt/eppt.cxx:2503
+ #2 0x00002aaaadef85b7 in SdPage::onParagraphRemoving ()
+ from /local/ooxml/inst/openoffice.org3.0/program/../basis-link/program/libsdlx.so
+ #3 0x00002aaaade202e3 in sd::DrawDocShell::ConvertTo ()
+ from /local/ooxml/inst/openoffice.org3.0/program/../basis-link/program/libsdlx.so
+ #4 0x00002aec23119b58 in SfxObjectShell::DoLoad ()
+ from /local/ooxml/inst/openoffice.org3.0/program/../basis-link/program//libsfxlx.so
+
+- pptx export
+ - sd/source/filter/eppt/pptx-epptooxml.cxx,
+ oox::core::PowerPointExport::exportDocument()
+
+- odp export
+ #0 ZipPackage (this=0x1805e80, xNewFactory=@0x7fffe284e990) at /home/rodo/git/ooxml/package/source/zippackage/ZipPackage.cxx:279
+ #1 0x00002aaaadd3dc94 in ZipPackage_createInstance (xMgr=@0x7fffe284e990) at /home/rodo/git/ooxml/package/source/zippackage/ZipPackage.cxx:1546
+ #2 0x00002b0fca7ab6b3 in ?? () from /opt/libreoffice/program/../basis-link/program/../ure-link/lib/libuno_cppuhelpergcc3.so.3
+ #3 0x00002b0fca7a7fda in ?? () from /opt/libreoffice/program/../basis-link/program/../ure-link/lib/libuno_cppuhelpergcc3.so.3
+ #4 0x00002b0fca7a811e in ?? () from /opt/libreoffice/program/../basis-link/program/../ure-link/lib/libuno_cppuhelpergcc3.so.3
+ #5 0x00002b0fca7aa7cc in ?? () from /opt/libreoffice/program/../basis-link/program/../ure-link/lib/libuno_cppuhelpergcc3.so.3
+ #6 0x00002b0fca7aacbe in ?? () from /opt/libreoffice/program/../basis-link/program/../ure-link/lib/libuno_cppuhelpergcc3.so.3
+ #7 0x00002b0fca7aa035 in ?? () from /opt/libreoffice/program/../basis-link/program/../ure-link/lib/libuno_cppuhelpergcc3.so.3
+ #8 0x00002aaaaadae1b3 in ?? () from /opt/libreoffice/ure/lib/bootstrap.uno.so
+ #9 0x00002aaaaadaa84c in ?? () from /opt/libreoffice/ure/lib/bootstrap.uno.so
+ #10 0x00002aaab5c7a7e5 in OStorage_Impl::OpenOwnPackage (this=0x185cac0) at /home/rodo/git/ooxml/package/source/xstor/xstorage.cxx:549
+ #11 0x00002aaab5c7ab3e in OStorage_Impl::ReadContents (this=0x185cac0) at /home/rodo/git/ooxml/package/source/xstor/xstorage.cxx:649
+ #12 0x00002aaab5c7d32f in OStorage_Impl::FindElement (this=0x185cac0, rName=@0x7fffe284f280) at /home/rodo/git/ooxml/package/source/xstor/xstorage.cxx:1387
+ #13 0x00002aaab5c7dc45 in OStorage::hasByName (this=0x1808880, aName=@0x7fffe284f280) at /home/rodo/git/ooxml/package/source/xstor/xstorage.cxx:4045
+ #14 0x00002aaab1fde8c5 in XMLVersionListPersistence::load () from /opt/libreoffice/program/../basis-link/program/libxolx.so
+ #15 0x00002b0fcb058bb2 in SfxMedium::GetVersionList (this=0x1750050, _bNoReload=false) at /home/rodo/git/ooxml/sfx2/source/doc/docfile.cxx:3247
+ #16 0x00002b0fcb0571b5 in SfxMedium::GetStorage (this=0x1750050) at /home/rodo/git/ooxml/sfx2/source/doc/docfile.cxx:1328
+ #17 0x00002b0fcb05d0d7 in SfxMedium::GetOutputStorage (this=0x1750050) at /home/rodo/git/ooxml/sfx2/source/doc/docfile.cxx:1068
+ #18 0x00002b0fcb091227 in SfxObjectShell::SaveTo_Impl (this=0xf44d60, rMedium=@0x1750050, pSet=0x0) at /home/rodo/git/ooxml/sfx2/source/doc/objstor.cxx:1557
+ #19 0x00002b0fcb09443c in SfxObjectShell::PreDoSaveAs_Impl (this=0xf44d60, rFileName=@0x7fffe2850700, aFilterName=@0x7fffe28507f0, pParams=0xf10c10)
+ at /home/rodo/git/ooxml/sfx2/source/doc/objstor.cxx:2984
+ #20 0x00002b0fcb094ea5 in SfxObjectShell::CommonSaveAs_Impl (this=0xf44d60, aURL=@0x7fffe2850870, aFilterName=@0x7fffe28507f0, aParams=0x1740310)
+ at /home/rodo/git/ooxml/sfx2/source/doc/objstor.cxx:2855
+ #21 0x00002b0fcb0a1da2 in SfxObjectShell::APISaveAs_Impl (this=0xf44d60, aFileName=@0x7fffe2850b70, aParams=0x1740310)
+ at /home/rodo/git/ooxml/sfx2/source/doc/objserv.cxx:432
+ #22 0x00002b0fcb0e74c8 in SfxBaseModel::impl_store (this=0xf96a00, sURL=@0x7fffe28516b0, seqArguments=@0x7fffe2851ae0, bSaveTo=0 '\0')
+ at /home/rodo/git/ooxml/sfx2/source/doc/sfxbasemodel.cxx:2591
+ #23 0x00002b0fcb0f124b in SfxBaseModel::storeAsURL (this=0xf96a00, rURL=@0x7fffe28516b0, rArgs=@0x7fffe2851ae0)
+ at /home/rodo/git/ooxml/sfx2/source/doc/sfxbasemodel.cxx:1568
+ #24 0x00002b0fcb101d3d in SfxStoringHelper::GUIStoreModel (this=0x7fffe28519f0, xModel=@0xf18798, aSlotName=@0x7fffe2852200, aArgsSequence=@0x7fffe2851ae0,
+ bPreselectPassword=0 '\0') at /home/rodo/git/ooxml/sfx2/source/doc/guisaveas.cxx:1529
+ #25 0x00002b0fcb0a4051 in SfxObjectShell::ExecFile_Impl (this=0xf44d60, rReq=@0x1484f20) at /home/rodo/git/ooxml/sfx2/source/doc/objserv.cxx:744
+ #26 0x00002b0fcb0a5c73 in SfxStubSfxObjectShellExecFile_Impl (pShell=0xf44d60, rReq=@0x1484f20) at ../../unxlngx6.pro/inc/sfxslots.hxx:161
+ #27 0x00002b0fcb17f398 in SfxShell::CallExec (this=0xf44d60, pFunc=0x2b0fcb0a5c56 <SfxStubSfxObjectShellExecFile_Impl(SfxShell*, SfxRequest&)>, rReq=@0x1484f20)
+ at ../../inc/sfx2/shell.hxx:226
+ #28 0x00002b0fcb17cec3 in SfxDispatcher::Call_Impl (this=0x110fde0, rShell=@0xf44d60, rSlot=@0x2b0fcb576368, rReq=@0x1484f20, bRecord=1 '\001')
+ at /home/rodo/git/ooxml/sfx2/source/control/dispatch.cxx:338
+ #29 0x00002b0fcb17d3f2 in SfxDispatcher::PostMsgHandler (this=0x110fde0, pReq=0x1484f20) at /home/rodo/git/ooxml/sfx2/source/control/dispatch.cxx:1643
+ #30 0x00002b0fcb17d51d in SfxDispatcher::LinkStubPostMsgHandler (pThis=0x110fde0, pCaller=0x1484f20) at /home/rodo/git/ooxml/sfx2/source/control/dispatch.cxx:1610
+ #31 0x00002b0fcafb3e70 in Link::Call (this=0x11488f8, pCaller=0x1484f20) at /home/rodo/git/ooxml/solver/300/unxlngx6.pro/inc/tools/link.hxx:158
+ #32 0x00002b0fcb1a9952 in GenLink::Call (this=0x11488f8, pCaller=0x1484f20) at ../../inc/sfx2/genlink.hxx:63
+ #33 0x00002b0fcb1a9773 in SfxHintPoster::Event (this=0x11488e0, pPostedHint=0x1484f20) at /home/rodo/git/ooxml/sfx2/source/notify/hintpost.cxx:98
+ #34 0x00002b0fcb1a9984 in SfxHintPoster::DoEvent_Impl (this=0x11488e0, pPostedHint=0x1484f20) at /home/rodo/git/ooxml/sfx2/source/notify/hintpost.cxx:88
+ #35 0x00002b0fcb1a974f in SfxHintPoster::LinkStubDoEvent_Impl (pThis=0x11488e0, pCaller=0x1484f20) at /home/rodo/git/ooxml/sfx2/source/notify/hintpost.cxx:92
+ #36 0x00002b0fccef69f8 in ImplWindowFrameProc () from /opt/libreoffice/program/../basis-link/program/libvcllx.so
+ #37 0x00002b0fd3f91f8f in SalDisplay::DispatchInternalEvent () from /opt/libreoffice/basis3.0/program/libvclplug_genlx.so
+ #38 0x00002b0fd0fa4a84 in GtkXLib::userEventFn () from /opt/libreoffice/basis3.0/program/libvclplug_gtklx.so
+ #39 0x00002b0fd3cb0204 in g_main_context_dispatch () from /usr/lib64/libglib-2.0.so.0
+ #40 0x00002b0fd3cb34fd in ?? () from /usr/lib64/libglib-2.0.so.0
+ #41 0x00002b0fd3cb39ce in g_main_context_iteration () from /usr/lib64/libglib-2.0.so.0
+ #42 0x00002b0fd0fa4fd9 in GtkXLib::Yield () from /opt/libreoffice/basis3.0/program/libvclplug_gtklx.so
+ #43 0x00002b0fccd1859e in Application::Yield () from /opt/libreoffice/program/../basis-link/program/libvcllx.so
+ #44 0x00002b0fccd18677 in Application::Execute () from /opt/libreoffice/program/../basis-link/program/libvcllx.so
+ #45 0x00002b0fc86fd803 in ?? () from /opt/libreoffice/program/../basis-link/program/libsoffice.so
+ #46 0x00002b0fccd1da24 in ImplSVMain () from /opt/libreoffice/program/../basis-link/program/libvcllx.so
+ #47 0x00002b0fccd1db15 in SVMain () from /opt/libreoffice/program/../basis-link/program/libvcllx.so
+ #48 0x00002b0fc872fe6c in soffice_main () from /opt/libreoffice/program/../basis-link/program/libsoffice.so
+ #49 0x000000000040114b in main ()
diff --git a/oox/source/export/preset-definitions-to-shape-types.pl b/oox/source/export/preset-definitions-to-shape-types.pl
new file mode 100644
index 000000000..5760a8609
--- /dev/null
+++ b/oox/source/export/preset-definitions-to-shape-types.pl
@@ -0,0 +1,1237 @@
+#!/usr/bin/env perl -w
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This file incorporates work covered by the following license notice:
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed
+# with this work for additional information regarding copyright
+# ownership. The ASF licenses this file to you under the Apache
+# License, Version 2.0 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.apache.org/licenses/LICENSE-2.0 .
+#
+
+use strict;
+use warnings;
+
+sub usage() {
+ print STDERR <<EOF;
+Usage: preset-definitions-to-shape-types.pl [ --drawingml-adj-names-data | --vml-shape-types-data ] <shapes> <text>
+
+Converts presetShapeDefinitions.xml and presetTextWarpDefinitions.xml to a
+.cxx that contains VML with the definitions of the shapes. The result is
+written to stdout.
+
+<shapes> presetShapeDefinitions.xml (including the path to it)
+<text> presetTextWarpDefinitions.xml (including the path to it)
+EOF
+ exit 1;
+}
+
+sub show_call_stack
+{
+ my ( $path, $line, $subr );
+ my $max_depth = 30;
+ my $i = 1;
+ print STDERR "--- Begin stack trace ---\n";
+ while ( (my @call_details = (caller($i++))) && ($i<$max_depth) ) {
+ print STDERR "$call_details[1] line $call_details[2] in function $call_details[3]\n";
+ }
+ print STDERR "--- End stack trace ---\n";
+}
+
+my $drawingml_adj_names_data = 0;
+my $vml_shape_types_data = 0;
+my $src_shapes = shift;
+if ($src_shapes eq "--drawingml-adj-names-data") {
+ $drawingml_adj_names_data = 1;
+ $src_shapes = shift;
+} elsif ($src_shapes eq "--vml-shape-types-data") {
+ $vml_shape_types_data = 1;
+ $src_shapes = shift;
+}
+my $src_text = shift;
+
+usage() if ( !defined( $src_shapes ) || !defined( $src_text ) ||
+ $src_shapes eq "-h" || $src_shapes eq "--help" ||
+ !-f $src_shapes || !-f $src_text );
+
+# Global variables
+my @levels = ();
+my $shape_name = "";
+my $state = "";
+my $path = "";
+my $adjust = "";
+my %adj_names;
+my $max_adj_no = 0;
+my @formulas = ();
+my %variables = ();
+my $ignore_this_shape = 0;
+my $handles = "";
+my $textboxrect = "";
+my $last_pos_x = "";
+my $last_pos_y = "";
+my $no_stroke = 0;
+my $no_fill = 0;
+my $path_w = 1;
+my $path_h = 1;
+my @quadratic_bezier = ();
+
+my %result_shapes = ();
+
+my %shapes_ids = (
+ 0 => 'notPrimitive',
+ 1 => 'rectangle',
+ 2 => 'roundRectangle',
+ 3 => 'ellipse',
+ 4 => 'diamond',
+ 5 => 'triangle',
+ 6 => 'rtTriangle',
+ 7 => 'parallelogram',
+ 8 => 'trapezoid',
+ 9 => 'hexagon',
+ 10 => 'octagon',
+ 11 => 'plus',
+ 12 => 'star5',
+ 13 => 'rightArrow',
+ 14 => 'thickArrow', # should not be used
+ 15 => 'homePlate',
+ 16 => 'cube',
+ 17 => 'wedgeRoundRectCallout', # balloon
+ 18 => 'star16', # seal
+ 19 => 'arc',
+ 20 => 'line',
+ 21 => 'plaque',
+ 22 => 'can',
+ 23 => 'donut',
+ 24 => 'textPlain', # textSimple - FIXME MS Office 2007 converts these to textboxes with unstyled text, so is it actually correct to map it to a real style?
+ 25 => 'textStop', # textOctagon FIXME see 24
+ 26 => 'textTriangle', # textHexagon FIXMME see 24
+ 27 => 'textCanDown', # textCurve FIXMME see 24
+ 28 => 'textWave1', # textWave FIXMME see 24
+ 29 => 'textArchUpPour', # textRing FIXMME see 24
+ 30 => 'textCanDown', # textOnCurve FIXMME see 24
+ 31 => 'textArchUp', # textOnRing FIXMME see 24
+ 32 => 'straightConnector1',
+ 33 => 'bentConnector2',
+ 34 => 'bentConnector3',
+ 35 => 'bentConnector4',
+ 36 => 'bentConnector5',
+ 37 => 'curvedConnector2',
+ 38 => 'curvedConnector3',
+ 39 => 'curvedConnector4',
+ 40 => 'curvedConnector5',
+ 41 => 'callout1',
+ 42 => 'callout2',
+ 43 => 'callout3',
+ 44 => 'accentCallout1',
+ 45 => 'accentCallout2',
+ 46 => 'accentCallout3',
+ 47 => 'borderCallout1',
+ 48 => 'borderCallout2',
+ 49 => 'borderCallout3',
+ 50 => 'accentBorderCallout1',
+ 51 => 'accentBorderCallout2',
+ 52 => 'accentBorderCallout3',
+ 53 => 'ribbon',
+ 54 => 'ribbon2',
+ 55 => 'chevron',
+ 56 => 'pentagon',
+ 57 => 'noSmoking',
+ 58 => 'star8', # seal8
+ 59 => 'star16', # seal16
+ 60 => 'star32', # seal32
+ 61 => 'wedgeRectCallout',
+ 62 => 'wedgeRoundRectCallout', # wedgeRRectCallout
+ 63 => 'wedgeEllipseCallout',
+ 64 => 'wave',
+ 65 => 'foldedCorner',
+ 66 => 'leftArrow',
+ 67 => 'downArrow',
+ 68 => 'upArrow',
+ 69 => 'leftRightArrow',
+ 70 => 'upDownArrow',
+ 71 => 'irregularSeal1',
+ 72 => 'irregularSeal2',
+ 73 => 'lightningBolt',
+ 74 => 'heart',
+ 75 => 'pictureFrame',
+ 76 => 'quadArrow',
+ 77 => 'leftArrowCallout',
+ 78 => 'rightArrowCallout',
+ 79 => 'upArrowCallout',
+ 80 => 'downArrowCallout',
+ 81 => 'leftRightArrowCallout',
+ 82 => 'upDownArrowCallout',
+ 83 => 'quadArrowCallout',
+ 84 => 'bevel',
+ 85 => 'leftBracket',
+ 86 => 'rightBracket',
+ 87 => 'leftBrace',
+ 88 => 'rightBrace',
+ 89 => 'leftUpArrow',
+ 90 => 'bentUpArrow',
+ 91 => 'bentArrow',
+ 92 => 'star24', # seal24
+ 93 => 'stripedRightArrow',
+ 94 => 'notchedRightArrow',
+ 95 => 'blockArc',
+ 96 => 'smileyFace',
+ 97 => 'verticalScroll',
+ 98 => 'horizontalScroll',
+ 99 => 'circularArrow',
+ 100 => 'notchedCircularArrow', # should not be used
+ 101 => 'uturnArrow',
+ 102 => 'curvedRightArrow',
+ 103 => 'curvedLeftArrow',
+ 104 => 'curvedUpArrow',
+ 105 => 'curvedDownArrow',
+ 106 => 'cloudCallout',
+ 107 => 'ellipseRibbon',
+ 108 => 'ellipseRibbon2',
+ 109 => 'flowChartProcess',
+ 110 => 'flowChartDecision',
+ 111 => 'flowChartInputOutput',
+ 112 => 'flowChartPredefinedProcess',
+ 113 => 'flowChartInternalStorage',
+ 114 => 'flowChartDocument',
+ 115 => 'flowChartMultidocument',
+ 116 => 'flowChartTerminator',
+ 117 => 'flowChartPreparation',
+ 118 => 'flowChartManualInput',
+ 119 => 'flowChartManualOperation',
+ 120 => 'flowChartConnector',
+ 121 => 'flowChartPunchedCard',
+ 122 => 'flowChartPunchedTape',
+ 123 => 'flowChartSummingJunction',
+ 124 => 'flowChartOr',
+ 125 => 'flowChartCollate',
+ 126 => 'flowChartSort',
+ 127 => 'flowChartExtract',
+ 128 => 'flowChartMerge',
+ 129 => 'flowChartOfflineStorage',
+ 130 => 'flowChartOnlineStorage',
+ 131 => 'flowChartMagneticTape',
+ 132 => 'flowChartMagneticDisk',
+ 133 => 'flowChartMagneticDrum',
+ 134 => 'flowChartDisplay',
+ 135 => 'flowChartDelay',
+ 136 => 'textPlain', # textPlainText
+ 137 => 'textStop',
+ 138 => 'textTriangle',
+ 139 => 'textTriangleInverted',
+ 140 => 'textChevron',
+ 141 => 'textChevronInverted',
+ 142 => 'textRingInside',
+ 143 => 'textRingOutside',
+ 144 => 'textArchUp', # textArchUpCurve
+ 145 => 'textArchDown', # textArchDownCurve
+ 146 => 'textCircle', # textCircleCurve
+ 147 => 'textButton', # textButtonCurve
+ 148 => 'textArchUpPour',
+ 149 => 'textArchDownPour',
+ 150 => 'textCirclePour',
+ 151 => 'textButtonPour',
+ 152 => 'textCurveUp',
+ 153 => 'textCurveDown',
+ 154 => 'textCascadeUp',
+ 155 => 'textCascadeDown',
+ 156 => 'textWave1',
+ 157 => 'textWave2',
+ 158 => 'textWave3',
+ 159 => 'textWave4',
+ 160 => 'textInflate',
+ 161 => 'textDeflate',
+ 162 => 'textInflateBottom',
+ 163 => 'textDeflateBottom',
+ 164 => 'textInflateTop',
+ 165 => 'textDeflateTop',
+ 166 => 'textDeflateInflate',
+ 167 => 'textDeflateInflateDeflate',
+ 168 => 'textFadeRight',
+ 169 => 'textFadeLeft',
+ 170 => 'textFadeUp',
+ 171 => 'textFadeDown',
+ 172 => 'textSlantUp',
+ 173 => 'textSlantDown',
+ 174 => 'textCanUp',
+ 175 => 'textCanDown',
+ 176 => 'flowChartAlternateProcess',
+ 177 => 'flowChartOffpageConnector',
+ 178 => 'callout1', # callout90
+ 179 => 'accentCallout1', # accentCallout90
+ 180 => 'borderCallout1', # borderCallout90
+ 181 => 'accentBorderCallout1', # accentBorderCallout90
+ 182 => 'leftRightUpArrow',
+ 183 => 'sun',
+ 184 => 'moon',
+ 185 => 'bracketPair',
+ 186 => 'bracePair',
+ 187 => 'star4', # seal4
+ 188 => 'doubleWave',
+ 189 => 'actionButtonBlank',
+ 190 => 'actionButtonHome',
+ 191 => 'actionButtonHelp',
+ 192 => 'actionButtonInformation',
+ 193 => 'actionButtonForwardNext',
+ 194 => 'actionButtonBackPrevious',
+ 195 => 'actionButtonEnd',
+ 196 => 'actionButtonBeginning',
+ 197 => 'actionButtonReturn',
+ 198 => 'actionButtonDocument',
+ 199 => 'actionButtonSound',
+ 200 => 'actionButtonMovie',
+ 201 => 'hostControl',
+ 202 => 'textBox'
+);
+# An error occurred, we have to ignore this shape
+sub error( $ )
+{
+ my ( $msg ) = @_;
+
+ $ignore_this_shape = 1;
+ print STDERR "ERROR (in $shape_name ): $msg\n";
+}
+
+# Setup the %variables map with predefined values
+sub setup_variables()
+{
+ %variables = (
+ 'l' => 0,
+ 't' => 0,
+ 'r' => 21600,
+ 'b' => 21600,
+
+ 'w' => 21600,
+ 'h' => 21600,
+ 'ss' => 21600,
+ 'ls' => 21600,
+
+ 'ssd2' => 10800, # 1/2
+ 'ssd4' => 5400, # 1/4
+ 'ssd6' => 3600, # 1/6
+ 'ssd8' => 2700, # 1/8
+ 'ssd16' => 1350, # 1/16
+ 'ssd32' => 675, # 1/32
+
+ 'hc' => 10800, # horizontal center
+ 'vc' => 10800, # vertical center
+
+ 'wd2' => 10800, # 1/2
+ 'wd3' => 7200, # 1/3
+ 'wd4' => 5400, # 1/4
+ 'wd5' => 4320, # 1/5
+ 'wd6' => 3600, # 1/6
+ 'wd8' => 2700, # 1/8
+ 'wd10' => 2160, # 1/10
+ 'wd12' => 1800, # 1/12
+ 'wd32' => 675, # 1/32
+
+ 'hd2' => 10800, # 1/2
+ 'hd3' => 7200, # 1/3
+ 'hd4' => 5400, # 1/4
+ 'hd5' => 4320, # 1/5
+ 'hd6' => 3600, # 1/6
+ 'hd8' => 2700, # 1/8
+ 'hd10' => 2160, # 1/10
+ 'hd12' => 1800, # 1/12
+ 'hd32' => 675, # 1/32
+
+ '25000' => 5400,
+ '12500' => 2700,
+
+ 'cd4' => 90, # 1/4 of a circle
+ 'cd2' => 180, # 1/2 of a circle
+ '3cd4' => 270, # 3/4 of a circle
+
+ 'cd8' => 45, # 1/8 of a circle
+ '3cd8' => 135, # 3/8 of a circle
+ '5cd8' => 225, # 5/8 of a circle
+ '7cd8' => 315, # 7/8 of a circle
+
+ '-5400000' => -90,
+ '-10800000'=> -180,
+ '-16200000'=> -270,
+ '-21600000'=> -360,
+ '-21599999'=> -360,
+
+ '5400000' => 90,
+ '10800000' => 180,
+ '16200000' => 270,
+ '21600000' => 360,
+ '21599999' => 360
+#
+# '21600000' => 360, # angle conversions
+# '27000000' => 450,
+# '32400000' => 540,
+# '37800000' => 630
+ );
+}
+
+# Convert the (predefined) value to a number
+sub value( $ )
+{
+ my ( $val ) = @_;
+
+ my $result = $variables{$val};
+ return $result if ( defined( $result ) );
+
+ return $val if ( $val =~ /^[0-9-]+$/ );
+
+ error( "Unknown variable '$val'." );
+
+ show_call_stack();
+ return $val;
+}
+
+# Convert the DrawingML formula to a VML one
+my %command_variables = (
+ 'w' => 'width',
+ 'h' => 'height',
+ 'r' => 'width',
+ 'b' => 'height'
+);
+
+# The same as value(), but some of the hardcoded values can have a name
+sub command_value( $ )
+{
+ my ( $value ) = @_;
+
+ return "" if ( $value eq "" );
+
+ return $value if ( $value =~ /^@/ );
+
+ my $command_val = $command_variables{$value};
+ if ( defined( $command_val ) ) {
+ return $command_val;
+ }
+
+ return value( $value );
+}
+
+# Insert the new formula to the list of formulas
+# Creates the name if it's empty...
+sub insert_formula( $$ )
+{
+ my ( $name, $fmla ) = @_;
+
+ my $i = 0;
+ foreach my $f ( @formulas ) {
+ if ( $f eq $fmla ) {
+ if ( $name ne "" ) {
+ $variables{$name} = "@" . $i;
+ }
+ return "@" . $i;
+ }
+ ++$i;
+ }
+
+ if ( $name eq "" ) {
+ $name = "@" . ( $#formulas + 1 );
+ }
+
+ $variables{$name} = "@" . ( $#formulas + 1 );
+ push @formulas, $fmla;
+
+ if ( $#formulas > 127 ) {
+ error( "Reached the maximum amount of formulas, have to ignore the shape '$shape_name'" );
+ }
+
+ return $variables{$name};
+}
+
+# The same as insert_formula(), but converts the params
+sub insert_formula_params( $$$$$ )
+{
+ my ( $name, $command, $p1, $p2, $p3 ) = @_;
+
+ my $result = $command;
+ if ( $p1 ne "" ) {
+ $result .= " " . command_value( $p1 );
+ if ( $p2 ne "" ) {
+ $result .= " " . command_value( $p2 );
+ if ( $p3 ne "" ) {
+ $result .= " " . command_value( $p3 );
+ }
+ }
+ }
+
+ return insert_formula( $name, $result );
+}
+
+# Convert the formula from DrawingML to VML
+sub convert_formula( $$ )
+{
+ my ( $name, $fmla ) = @_;
+
+ if ( $fmla =~ /^([^ ]+)/ ) {
+ my $command = $1;
+
+ # parse the parameters
+ ( my $values = $fmla ) =~ s/^([^ ]+) *//;
+ my $p1 = "";
+ my $p2 = "";
+ my $p3 = "";
+ if ( $values =~ /^([^ ]+)/ ) {
+ $p1 = $1;
+ $values =~ s/^([^ ]+) *//;
+ if ( $values =~ /^([^ ]+)/ ) {
+ $p2 = $1;
+ $values =~ s/^([^ ]+) *//;
+ if ( $values =~ /^([^ ]+)/ ) {
+ $p3 = $1;
+ }
+ }
+ }
+
+ # now convert the formula
+ if ( $command eq "+-" ) {
+ if ( $p1 eq "100000" ) {
+ $p1 = value( 'w' );
+ }
+ insert_formula_params( $name, "sum", $p1, $p2, $p3 );
+ return;
+ }
+ elsif ( $command eq "*/" ) {
+ if ( ( $p2 =~ /^(w|h|ss|hd2|wd2|vc)$/ ) && defined( $variables{$p1} ) ) {
+ # switch it ;-) - presetTextWarpDefinitions.xml has it in other order
+ my $tmp = $p1;
+ $p1 = $p2;
+ $p2 = $tmp;
+ }
+
+ if ( ( $p1 =~ /^(w|h|ss|hd2|wd2|vc)$/ ) && defined( $variables{$p2} ) ) {
+ my $val3 = $p3;
+ if ( $val3 =~ /^[0-9-]+$/ ) {
+ $val3 *= ( value( 'w' ) / value( $p1 ) );
+
+ # Oh yes, I'm too lazy to implement the full GCD here ;-)
+ if ( ( $val3 % 100000 ) == 0 ) {
+ $p1 = 1;
+ $p3 = sprintf( "%.0f", ( $val3 / 100000 ) );
+ }
+ elsif ( $val3 < 100000 ) {
+ $p3 = 1;
+ while ( ( ( $p3 * 100000 ) % $val3 ) != 0 ) {
+ ++$p3
+ }
+ $p1 = ( $p3 * 100000 ) / $val3;
+ }
+ else {
+ error( "Need to count the greatest common divisor." );
+ }
+ }
+ }
+ elsif ( $p3 eq "100000" && $p2 =~ /^[0-9-]+$/ ) {
+ # prevent overflows in some shapes
+ $p2 = sprintf( "%.0f", ( $p2 / 10 ) );
+ $p3 /= 10;
+ }
+ elsif ( $p3 eq "32768" && $p2 =~ /^[0-9-]+$/ ) {
+ # prevent overflows in some shapes
+ $p2 = sprintf( "%.0f", ( $p2 / 8 ) );
+ $p3 /= 8;
+ }
+ elsif ( $p3 eq "50000" ) {
+ $p3 = 10800;
+ }
+ elsif ( $name =~ /^maxAdj/ ) {
+ my $val = value( $p1 );
+ if ( $val =~ /^[0-9-]+$/ ) {
+ $p1 = sprintf( "%.0f", ( value( 'w' ) * $val / 100000 ) );
+ }
+ }
+
+ if ( ( value( $p1 ) eq value( $p3 ) ) || ( value( $p2 ) eq value( $p3 ) ) ) {
+ my $val = value( ( value( $p1 ) eq value( $p3 ) )? $p2: $p1 );
+ if ( $val =~ /^@([0-9]+)$/ ) {
+ insert_formula( $name, $formulas[$1] );
+ }
+ else {
+ insert_formula( $name, "val $val" );
+ }
+ }
+ else {
+ insert_formula_params( $name, "prod", $p1, $p2, $p3 );
+ }
+ return;
+ }
+ elsif ( $command eq "+/" ) {
+ # we have to split this into 2 formulas - 'sum' and 'prod'
+ my $constructed = insert_formula_params( "", "sum", $p1, $p2, "0" );
+ insert_formula_params( $name, "prod", 1, $constructed, $p3); # references the 'sum' formula
+ return;
+ }
+ elsif ( $command eq "?:" ) {
+ insert_formula_params( $name, "if", $p1, $p2, $p3 );
+ return;
+ }
+ elsif ( $command eq "sin" || $command eq "cos" ) {
+ if ( $p2 =~ /^[0-9-]+$/ && ( ( $p2 % 60000 ) == 0 ) ) {
+ $p2 /= 60000;
+ }
+ else {
+ $p2 = insert_formula_params( "", "prod", "1", $p2, "60000" );
+ }
+ # we have to use 'sumangle' even for the case when $p2 is const
+ # and theoretically could be written as such; but Word does not
+ # accept it :-(
+ my $conv = insert_formula_params( "", "sumangle", "0", $p2, "0" );
+
+ $p2 = $conv;
+
+ insert_formula_params( $name, $command, $p1, $p2, "" );
+ return;
+ }
+ elsif ( $command eq "abs" ) {
+ insert_formula_params( $name, $command, $p1, "", "" );
+ return;
+ }
+ elsif ( $command eq "max" || $command eq "min" ) {
+ insert_formula_params( $name, $command, $p1, $p2, "" );
+ return;
+ }
+ elsif ( $command eq "at2" ) {
+ insert_formula_params( $name, "atan2", $p1, $p2, "" );
+ return;
+ }
+ elsif ( $command eq "cat2" ) {
+ insert_formula_params( $name, "cosatan2", $p1, $p2, $p3 );
+ return;
+ }
+ elsif ( $command eq "sat2" ) {
+ insert_formula_params( $name, "sinatan2", $p1, $p2, $p3 );
+ return;
+ }
+ elsif ( $command eq "sqrt" ) {
+ insert_formula_params( $name, "sqrt", $p1, "", "" );
+ return;
+ }
+ elsif ( $command eq "mod" ) {
+ insert_formula_params( $name, "mod", $p1, $p2, $p3 );
+ return;
+ }
+ elsif ( $command eq "val" ) {
+ insert_formula_params( $name, "val", value( $p1 ), "", "" );
+ return;
+ }
+ else {
+ error( "Unknown formula '$name', '$fmla'." );
+ }
+ }
+ else {
+ error( "Cannot convert formula's command '$name', '$fmla'." );
+ }
+}
+
+# There's no exact equivalent of 'arcTo' in VML, we have to do some special casing...
+my %convert_arcTo = (
+ '0' => {
+ '90' => {
+ 'path' => 'qy',
+ 'op' => [ 'sum 0 __last_x__ __wR__', 'sum __hR__ __last_y__ 0' ],
+ },
+ '-90' => {
+ 'path' => 'qy',
+ 'op' => [ 'sum 0 __last_x__ __wR__', 'sum 0 __last_y__ __hR__' ],
+ },
+ },
+ '90' => {
+ '90' => {
+ 'path' => 'qx',
+ 'op' => [ 'sum 0 __last_x__ __wR__', 'sum 0 __last_y__ __hR__' ],
+ },
+ '-90' => {
+ 'path' => 'qx',
+ 'op' => [ 'sum __wR__ __last_x__ 0', 'sum 0 __last_y__ __hR__' ],
+ },
+ },
+ '180' => {
+ '90' => {
+ 'path' => 'qy',
+ 'op' => [ 'sum __wR__ __last_x__ 0', 'sum 0 __last_y__ __hR__' ],
+ },
+ '-90' => {
+ 'path' => 'qy',
+ 'op' => [ 'sum __wR__ __last_x__ 0', 'sum __hR__ __last_y__ 0' ],
+ },
+ },
+ '270' => {
+ '90' => {
+ 'path' => 'qx',
+ 'op' => [ 'sum __wR__ __last_x__ 0', 'sum __hR__ __last_y__ 0' ],
+ },
+ '-90' => {
+ 'path' => 'qx',
+ 'op' => [ 'sum 0 __last_x__ __wR__', 'sum __hR__ __last_y__ 0' ],
+ },
+ },
+);
+
+# Elliptic quadrant
+# FIXME optimize so that we compute the const values when possible
+sub elliptic_quadrant( $$$$ )
+{
+ my ( $wR, $hR, $stAng, $swAng ) = @_;
+
+ if ( defined( $convert_arcTo{$stAng} ) && defined( $convert_arcTo{$stAng}{$swAng} ) ) {
+ my $conv_path = $convert_arcTo{$stAng}{$swAng}{'path'};
+ my $conv_op_ref = $convert_arcTo{$stAng}{$swAng}{'op'};
+
+ $path .= "$conv_path";
+
+ my $pos_x = $last_pos_x;
+ my $pos_y = $last_pos_y;
+ for ( my $i = 0; $i <= $#{$conv_op_ref}; ++$i ) {
+ my $op = $conv_op_ref->[$i];
+
+ $op =~ s/__last_x__/$last_pos_x/g;
+ $op =~ s/__last_y__/$last_pos_y/g;
+ $op =~ s/__wR__/$wR/g;
+ $op =~ s/__hR__/$hR/g;
+
+ my $fmla = insert_formula( "", $op );
+
+ $path .= $fmla;
+
+ # so far it's sufficient just to rotate the positions
+ # FIXME if not ;-)
+ $pos_x = $pos_y;
+ $pos_y = $fmla;
+ }
+ $last_pos_x = $pos_x;
+ $last_pos_y = $pos_y;
+ }
+ else {
+ error( "Unhandled elliptic_quadrant(), input is ($wR, $hR, $stAng, $swAng)." );
+ }
+}
+
+# Convert the quadratic bezier to cubic (exact)
+# No idea why, but the 'qb' did not work for me :-(
+sub quadratic_to_cubic_bezier( $ )
+{
+ my ( $axis ) = @_;
+
+ my $a0 = $quadratic_bezier[0]->{$axis};
+ my $a1 = $quadratic_bezier[1]->{$axis};
+ my $a2 = $quadratic_bezier[2]->{$axis};
+
+ my $b0 = $a0;
+
+ # $b1 = $a0 + 2/3 * ( $a1 - $a0 ), but in VML
+ # FIXME optimize for constants - compute directly
+ my $b1_1 = insert_formula_params( "", "sum", "0", $a1, $a0 );
+ my $b1_2 = insert_formula_params( "", "prod", "2", $b1_1, "3" );
+ my $b1 = insert_formula_params( "", "sum", $a0, $b1_2, "0" );
+
+ # $b2 = $b1 + 1/3 * ( $a2 - $a0 );
+ # FIXME optimize for constants - compute directly
+ my $b2_1 = insert_formula_params( "", "sum", "0", $a2, $a0 );
+ my $b2_2 = insert_formula_params( "", "prod", "1", $b2_1, "3" );
+ my $b2 = insert_formula_params( "", "sum", $b1, $b2_2, "0" );
+
+ my $b3 = $a2;
+
+ return ( $b0, $b1, $b2, $b3 );
+}
+
+# Extend $path by one more point
+sub add_point_to_path( $$ )
+{
+ my ( $x, $y ) = @_;
+
+ if ( $path =~ /[0-9]$/ && $x =~ /^[0-9-]/ ) {
+ $path .= ",";
+ }
+ $path .= $x;
+
+ if ( $path =~ /[0-9]$/ && $y =~ /^[0-9-]/ ) {
+ $path .= ",";
+ }
+ $path .= $y;
+}
+
+# Start of an element
+sub start_element( $% )
+{
+ my ( $element, %attr ) = @_;
+
+ push @levels, $element;
+
+ #print "element: $element\n";
+
+ if ( @levels > 1 && ( $levels[-2] eq "presetShapeDefinitons" ||
+ $levels[-2] eq "presetTextWarpDefinitions" ) ) {
+ $shape_name = $element;
+
+ $state = "";
+ $ignore_this_shape = 0;
+ $path = "";
+ $adjust = "";
+ $max_adj_no = 0;
+ @formulas = ();
+ $handles = "";
+ $textboxrect = "";
+ $last_pos_x = "";
+ $last_pos_y = "";
+ $no_stroke = 0;
+ $no_fill = 0;
+ @quadratic_bezier = ();
+
+ setup_variables();
+
+ if ( $shape_name eq "sun" ) {
+ # hack for this shape
+ $variables{'100000'} = "21600";
+ $variables{'50000'} = "10800";
+ $variables{'25000'} = "5400";
+ $variables{'12500'} = "2700";
+ $variables{'3662'} = "791";
+ }
+
+ my $found = 0;
+ foreach my $name ( values( %shapes_ids ) ) {
+ if ( $name eq $shape_name ) {
+ $found = 1;
+ last;
+ }
+ }
+ if ( !$found ) {
+ error( "Unknown shape '$shape_name'." );
+ }
+ }
+ elsif ( $element eq "pathLst" ) {
+ $state = "path";
+ }
+ elsif ( $element eq "avLst" ) {
+ $state = "adjust";
+ }
+ elsif ( $element eq "gdLst" ) {
+ $state = "formulas";
+ }
+ elsif ( $element eq "ahLst" ) {
+ $state = "handles";
+ }
+ elsif ( $element eq "rect" ) {
+ $textboxrect = value( $attr{'l'} ) . "," . value( $attr{'t'} ) . "," .
+ value( $attr{'r'} ) . "," . value( $attr{'b'} );
+ }
+ elsif ( $state eq "path" ) {
+ if ( $element eq "path" ) {
+ $no_stroke = ( defined( $attr{'stroke'} ) && $attr{'stroke'} eq 'false' );
+ $no_fill = ( defined( $attr{'fill'} ) && $attr{'fill'} eq 'none' );
+ $path_w = $attr{'w'};
+ $path_h = $attr{'h'};
+ }
+ elsif ( $element eq "moveTo" ) {
+ $path .= "m";
+ }
+ elsif ( $element eq "lnTo" ) {
+ $path .= "l";
+ }
+ elsif ( $element eq "cubicBezTo" ) {
+ $path .= "c";
+ }
+ elsif ( $element eq "quadBezTo" ) {
+ my %points = ( 'x' => $last_pos_x, 'y' => $last_pos_y );
+ @quadratic_bezier = ( \%points );
+ }
+ elsif ( $element eq "close" ) {
+ $path .= "x";
+ }
+ elsif ( $element eq "pt" ) {
+ # remember the last position for the arcTo
+ $last_pos_x = value( $attr{'x'} );
+ $last_pos_y = value( $attr{'y'} );
+
+ $last_pos_x *= ( value( 'w' ) / $path_w ) if ( defined( $path_w ) );
+ $last_pos_y *= ( value( 'h' ) / $path_h ) if ( defined( $path_h ) );
+
+ if ( $#quadratic_bezier >= 0 ) {
+ my %points = ( 'x' => $last_pos_x, 'y' => $last_pos_y );
+ push( @quadratic_bezier, \%points );
+ }
+ else {
+ add_point_to_path( $last_pos_x, $last_pos_y );
+ }
+ }
+ elsif ( ( $element eq "arcTo" ) && ( $last_pos_x ne "" ) && ( $last_pos_y ne "" ) ) {
+ # there's no exact equivalent of arcTo in VML, so we have to
+ # compute here a bit...
+ my $stAng = value( $attr{'stAng'} );
+ my $swAng = value( $attr{'swAng'} );
+ my $wR = value( $attr{'wR'} );
+ my $hR = value( $attr{'hR'} );
+
+ $wR *= ( value( 'w' ) / $path_w ) if ( defined( $path_w ) );
+ $hR *= ( value( 'h' ) / $path_h ) if ( defined( $path_h ) );
+
+ if ( ( $stAng =~ /^[0-9-]+$/ ) && ( $swAng =~ /^[0-9-]+$/ ) ) {
+ if ( ( ( $stAng % 90 ) == 0 ) && ( ( $swAng % 90 ) == 0 ) && ( $swAng != 0 ) ) {
+ my $end = $stAng + $swAng;
+ my $step = ( $swAng > 0 )? 90: -90;
+
+ for ( my $cur = $stAng; $cur != $end; $cur += $step ) {
+ elliptic_quadrant( $wR, $hR, ( $cur % 360 ), $step );
+ }
+ }
+ else {
+ error( "Unsupported numeric 'arcTo' ($attr{'wR'}, $attr{'hR'}, $stAng, $swAng)." );
+ }
+ }
+ else {
+ error( "Unsupported 'arcTo' conversion ($attr{'wR'}, $attr{'hR'}, $stAng, $swAng)." );
+ }
+ }
+ else {
+ error( "Unhandled path element '$element'." );
+ }
+ }
+ elsif ( $state eq "adjust" ) {
+ if ( $element eq "gd" ) {
+ my $adj_no = $attr{'name'};
+
+ # Save this adj number for this type for later use.
+ push(@{$adj_names{$shape_name}}, $adj_no);
+
+ my $is_const = 0;
+
+ $adj_no =~ s/^adj//;
+ if ( $adj_no eq "" ) {
+ $max_adj_no = 0;
+ }
+ elsif ( !( $adj_no =~ /^[0-9]*$/ ) ) {
+ ++$max_adj_no;
+ $is_const = 1;
+ }
+ elsif ( $adj_no != $max_adj_no + 1 ) {
+ error( "Wrong order of adj values." );
+ ++$max_adj_no;
+ }
+ else {
+ $max_adj_no = $adj_no;
+ }
+
+ if ( $attr{'fmla'} =~ /^val ([0-9-]*)$/ ) {
+ my $val = sprintf( "%.0f", ( 21600 * $1 ) / 100000 );
+ if ( $is_const ) {
+ $variables{$adj_no} = $val;
+ }
+ elsif ( $adjust eq "" ) {
+ $adjust = $val;
+ }
+ else {
+ $adjust = "$val,$adjust";
+ }
+ }
+ else {
+ error( "Wrong fmla '$attr{'fmla'}'." );
+ }
+ }
+ else {
+ error( "Unhandled adjust element '$element'." );
+ }
+ }
+ elsif ( $state eq "formulas" ) {
+ if ( $element eq "gd" ) {
+ if ( $attr{'fmla'} =~ /^\*\/ (h|w|ss) adj([0-9]+) 100000$/ ) {
+ insert_formula( $attr{'name'}, "val #" . ( $max_adj_no - $2 ) );
+ }
+ elsif ( $attr{'fmla'} =~ /^pin [^ ]+ ([^ ]+) / ) {
+ print STDERR "TODO Map 'pin' to VML as xrange for handles.\n";
+ my $pin_val = $1;
+ if ( $pin_val eq "adj" ) {
+ insert_formula( $attr{'name'}, "val #0" );
+ }
+ elsif ( $pin_val =~ /^adj([0-9]+)/ ) {
+ insert_formula( $attr{'name'}, "val #" . ( $max_adj_no - $1 ) );
+ }
+ else {
+ insert_formula( $attr{'name'}, "val " . value( $pin_val ) );
+ }
+ }
+ elsif ( $attr{'fmla'} =~ /adj/ ) {
+ error( "Non-standard usage of adj in '$attr{'fmla'}'." );
+ }
+ else {
+ convert_formula( $attr{'name'}, $attr{'fmla'} );
+ }
+ }
+ }
+ elsif ( $state eq "handles" ) {
+ if ( $element eq "pos" ) {
+ $handles .= "<v:h position=\"" . value( $attr{'x'} ) . "," . value( $attr{'y'} ) . "\"/>\n";
+ }
+ }
+}
+
+# End of an element
+sub end_element( $ )
+{
+ my ( $element ) = @_;
+
+ pop @levels;
+
+ if ( $element eq $shape_name ) {
+ if ( !$ignore_this_shape ) {
+ # we have all the info, generate the shape now
+ $state = "";
+
+ # shape path
+ my $out = "<v:shapetype id=\"_x0000_t__ID__\" coordsize=\"21600,21600\" o:spt=\"__ID__\" ";
+ if ( $adjust ne "" ) {
+ $out .= "adj=\"$adjust\" ";
+ }
+
+ # optimize it [yes, we need this twice ;-)]
+ $path =~ s/([^0-9-@])0([^0-9-@])/$1$2/g;
+ $path =~ s/([^0-9-@])0([^0-9-@])/$1$2/g;
+
+ $out .= "path=\"$path\">\n";
+
+ # stroke
+ $out .= "<v:stroke joinstyle=\"miter\"/>\n";
+
+ # formulas
+ if ( $#formulas >= 0 )
+ {
+ $out .= "<v:formulas>\n";
+ foreach my $fmla ( @formulas ) {
+ $out .= "<v:f eqn=\"$fmla\"/>\n"
+ }
+ $out .= "</v:formulas>\n";
+ }
+
+ # path
+ if ( $textboxrect ne "" ) { # TODO connectlocs, connectangles
+ $out .= "<v:path gradientshapeok=\"t\" o:connecttype=\"rect\" textboxrect=\"$textboxrect\"/>\n";
+ }
+
+ # handles
+ if ( $handles ne "" ) {
+ $out .= "<v:handles>\n$handles</v:handles>\n";
+ }
+
+ $out .="</v:shapetype>";
+
+ # hooray! :-)
+ $result_shapes{$shape_name} = $out;
+ }
+ else {
+ print STDERR "Shape '$shape_name' ignored; see the above error(s) for the reason.\n";
+ }
+ $shape_name = "";
+ }
+ elsif ( $state eq "path" ) {
+ if ( $element eq "path" ) {
+ $path .= "ns" if ( $no_stroke );
+ $path .= "nf" if ( $no_fill );
+ $path .= "e";
+ }
+ elsif ( $element eq "quadBezTo" ) {
+ # we have to convert the quadratic bezier to cubic
+ if ( $#quadratic_bezier == 2 ) {
+ my @points_x = quadratic_to_cubic_bezier( 'x' );
+ my @points_y = quadratic_to_cubic_bezier( 'y' );
+
+ $path .= "c";
+ # ignore the starting point
+ for ( my $i = 1; $i < 4; ++$i ) {
+ add_point_to_path( $points_x[$i], $points_y[$i] );
+ }
+ }
+ else {
+ error( "Wrong number of points of the quadratic bezier." );
+ }
+ @quadratic_bezier = ();
+ }
+ }
+ elsif ( $element eq "avLst" ) {
+ $state = "";
+ }
+ elsif ( $element eq "gdLst" ) {
+ $state = "";
+ }
+ elsif ( $element eq "ahLst" ) {
+ $state = "";
+ }
+}
+
+# Text inside an element
+sub characters( $ )
+{
+ #my ( $text ) = @_;
+}
+
+#################### A trivial XML parser ####################
+
+# Parse the attributes
+sub parse_start_element( $ )
+{
+ # split the string containing both the elements and attributes
+ my ( $element_tmp ) = @_;
+
+ $element_tmp =~ s/\s*$//;
+ $element_tmp =~ s/^\s*//;
+
+ ( my $element = $element_tmp ) =~ s/\s.*$//;
+ if ( $element_tmp =~ /\s/ ) {
+ $element_tmp =~ s/^[^\s]*\s//;
+ }
+ else {
+ $element_tmp = "";
+ }
+
+ # we have the element, now the attributes
+ my %attr;
+ my $is_key = 1;
+ my $key = "";
+ foreach my $tmp ( split( /"/, $element_tmp ) ) {
+ if ( $is_key ) {
+ $key = $tmp;
+ $key =~ s/^\s*//;
+ $key =~ s/\s*=\s*$//;
+ }
+ else {
+ $attr{$key} = $tmp;
+ }
+ $is_key = !$is_key;
+ }
+
+ if ( $element ne "" ) {
+ start_element( $element, %attr );
+ }
+}
+
+# Parse the file
+sub parse( $ )
+{
+ my ( $file ) = @_;
+
+ my $in_comment = 0;
+ my $line = "";
+ while (<$file>) {
+ # ignore comments
+ s/<\?[^>]*\?>//g;
+ s/<!--[^>]*-->//g;
+ if ( /<!--/ ) {
+ $in_comment = 1;
+ s/<!--.*//;
+ }
+ elsif ( /-->/ && $in_comment ) {
+ $in_comment = 0;
+ s/.*-->//;
+ }
+ elsif ( $in_comment ) {
+ next;
+ }
+ # ignore empty lines
+ chomp;
+ s/^\s*//;
+ s/\s*$//;
+ next if ( $_ eq "" );
+
+ # take care of lines where element continues
+ if ( $line ne "" ) {
+ $line .= " " . $_;
+ }
+ else {
+ $line = $_;
+ }
+ next if ( !/>$/ );
+
+ # the actual parsing
+ my @starts = split( /</, $line );
+ $line = "";
+ foreach my $start ( @starts ) {
+ next if ( $start eq "" );
+
+ my @ends = split( />/, $start );
+ my $element = $ends[0];
+ my $data = $ends[1];
+
+ # start or end element
+ if ( $element =~ /^\/(.*)/ ) {
+ end_element( $1 );
+ }
+ elsif ( $element =~ /^(.*)\/$/ ) {
+ parse_start_element( $1 );
+ ( my $end = $1 ) =~ s/\s.*$//;
+ end_element( $end );
+ }
+ else {
+ parse_start_element( $element );
+ }
+
+ # the data
+ characters( $data ) if ( defined( $data ) && $data ne "" );
+ }
+ }
+}
+
+# Do the real work
+my $file;
+open( $file, "<$src_shapes" ) || die "Cannot open $src_shapes: $!";
+parse( $file );
+close( $file );
+
+open( $file, "<$src_text" ) || die "Cannot open $src_text: $!";
+parse( $file );
+close( $file );
+
+if ( !defined( $result_shapes{'textBox'} ) ) {
+ # tdf#114842 shapetype id of the textbox, must be the same as defined
+ $result_shapes{'textBox'} =
+ "<v:shapetype id=\"_x0000_t__ID__\" coordsize=\"21600,21600\" " .
+ "o:spt=\"__ID__\" path=\"m,l,21600l21600,21600l21600,xe\">\n" .
+ "<v:stroke joinstyle=\"miter\"/>\n" .
+ "<v:path gradientshapeok=\"t\" o:connecttype=\"rect\"/>\n" .
+ "</v:shapetype>";
+}
+
+# Generate the data
+if ($drawingml_adj_names_data eq 1) {
+ foreach my $adj_name (sort(keys %adj_names))
+ {
+ foreach my $adj (@{$adj_names{$adj_name}})
+ {
+ print "$adj_name\t$adj\n";
+ }
+ }
+ exit 0;
+} elsif ($vml_shape_types_data eq 1) {
+ for ( my $i = 0; $i < 203; ++$i ) {
+ if ( $i < 4 ) {
+ print "/* $i - $shapes_ids{$i} - handled separately */\nNULL\n";
+ }
+ else {
+ print "/* $i - $shapes_ids{$i} */\n";
+ my $out = $result_shapes{$shapes_ids{$i}};
+ if ( defined( $out ) ) {
+ # set the id
+ $out =~ s/__ID__/$i/g;
+
+ # output as string
+ $out =~ s/\n//g;
+
+ print "$out\n";
+ }
+ else {
+ print "NULL\n";
+ }
+ }
+ }
+ exit 0;
+}
+
+# should not happen
+exit 1;
+
+# vim:set ft=perl shiftwidth=4 softtabstop=4 expandtab: #
diff --git a/oox/source/export/presetTextWarpDefinitions.xml b/oox/source/export/presetTextWarpDefinitions.xml
new file mode 100644
index 000000000..c701c8f82
--- /dev/null
+++ b/oox/source/export/presetTextWarpDefinitions.xml
@@ -0,0 +1,1885 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<presetTextWarpDefinitions>
+ <textArchDown>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 0"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adval" fmla="pin 0 adj 21599999"/>
+ <gd name="v1" fmla="+- 10800000 0 adval"/>
+ <gd name="v2" fmla="+- 32400000 0 adval"/>
+ <gd name="nv1" fmla="+- 0 0 v1"/>
+ <gd name="stAng" fmla="?: nv1 v2 v1"/>
+ <gd name="w1" fmla="+- 5400000 0 adval"/>
+ <gd name="w2" fmla="+- 16200000 0 adval"/>
+ <gd name="d1" fmla="+- adval 0 stAng"/>
+ <gd name="d2" fmla="+- d1 0 21600000"/>
+ <gd name="v3" fmla="+- 0 0 10800000"/>
+ <gd name="c2" fmla="?: w2 d1 d2"/>
+ <gd name="c1" fmla="?: v1 d2 c2"/>
+ <gd name="c0" fmla="?: w1 d1 c1"/>
+ <gd name="swAng" fmla="?: stAng c0 v3"/>
+ <gd name="wt1" fmla="sin wd2 adj"/>
+ <gd name="ht1" fmla="cos hd2 adj"/>
+ <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/>
+ <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/>
+ <gd name="x1" fmla="+- hc dx1 0"/>
+ <gd name="y1" fmla="+- vc dy1 0"/>
+ <gd name="wt2" fmla="sin wd2 stAng"/>
+ <gd name="ht2" fmla="cos hd2 stAng"/>
+ <gd name="dx2" fmla="cat2 wd2 ht2 wt2"/>
+ <gd name="dy2" fmla="sat2 hd2 ht2 wt2"/>
+ <gd name="x2" fmla="+- hc dx2 0"/>
+ <gd name="y2" fmla="+- vc dy2 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahPolar gdRefAng="adj" minAng="0" maxAng="21599999">
+ <pos x="x1" y="y1"/>
+ </ahPolar>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="x2" y="y2"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="hd2" stAng="stAng" swAng="swAng"/>
+ </path>
+ </pathLst>
+ </textArchDown>
+ <textArchDownPour>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj1" fmla="val 0"/>
+ <gd name="adj2" fmla="val 25000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adval" fmla="pin 0 adj1 21599999"/>
+ <gd name="v1" fmla="+- 10800000 0 adval"/>
+ <gd name="v2" fmla="+- 32400000 0 adval"/>
+ <gd name="nv1" fmla="+- 0 0 v1"/>
+ <gd name="stAng" fmla="?: nv1 v2 v1"/>
+ <gd name="w1" fmla="+- 5400000 0 adval"/>
+ <gd name="w2" fmla="+- 16200000 0 adval"/>
+ <gd name="d1" fmla="+- adval 0 stAng"/>
+ <gd name="d2" fmla="+- d1 0 21600000"/>
+ <gd name="v3" fmla="+- 0 0 10800000"/>
+ <gd name="c2" fmla="?: w2 d1 d2"/>
+ <gd name="c1" fmla="?: v1 d2 c2"/>
+ <gd name="c0" fmla="?: w1 d1 c1"/>
+ <gd name="swAng" fmla="?: stAng c0 v3"/>
+ <gd name="wt1" fmla="sin wd2 stAng"/>
+ <gd name="ht1" fmla="cos hd2 stAng"/>
+ <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/>
+ <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/>
+ <gd name="x1" fmla="+- hc dx1 0"/>
+ <gd name="y1" fmla="+- vc dy1 0"/>
+ <gd name="adval2" fmla="pin 0 adj2 99000"/>
+ <gd name="ratio" fmla="*/ adval2 1 100000"/>
+ <gd name="iwd2" fmla="*/ wd2 ratio 1"/>
+ <gd name="ihd2" fmla="*/ hd2 ratio 1"/>
+ <gd name="wt2" fmla="sin iwd2 adval"/>
+ <gd name="ht2" fmla="cos ihd2 adval"/>
+ <gd name="dx2" fmla="cat2 iwd2 ht2 wt2"/>
+ <gd name="dy2" fmla="sat2 ihd2 ht2 wt2"/>
+ <gd name="x2" fmla="+- hc dx2 0"/>
+ <gd name="y2" fmla="+- vc dy2 0"/>
+ <gd name="wt3" fmla="sin iwd2 stAng"/>
+ <gd name="ht3" fmla="cos ihd2 stAng"/>
+ <gd name="dx3" fmla="cat2 iwd2 ht3 wt3"/>
+ <gd name="dy3" fmla="sat2 ihd2 ht3 wt3"/>
+ <gd name="x3" fmla="+- hc dx3 0"/>
+ <gd name="y3" fmla="+- vc dy3 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahPolar gdRefR="adj2" minR="0" maxR="100000" gdRefAng="adj1" minAng="0" maxAng="21599999">
+ <pos x="x2" y="y2"/>
+ </ahPolar>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="x3" y="y3"/>
+ </moveTo>
+ <arcTo wR="iwd2" hR="ihd2" stAng="stAng" swAng="swAng"/>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="x1" y="y1"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="hd2" stAng="stAng" swAng="swAng"/>
+ </path>
+ </pathLst>
+ </textArchDownPour>
+ <textArchUp>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val cd2"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adval" fmla="pin 0 adj 21599999"/>
+ <gd name="v1" fmla="+- 10800000 0 adval"/>
+ <gd name="v2" fmla="+- 32400000 0 adval"/>
+ <gd name="end" fmla="?: v1 v1 v2"/>
+ <gd name="w1" fmla="+- 5400000 0 adval"/>
+ <gd name="w2" fmla="+- 16200000 0 adval"/>
+ <gd name="d1" fmla="+- end 0 adval"/>
+ <gd name="d2" fmla="+- 21600000 d1 0"/>
+ <gd name="c2" fmla="?: w2 d1 d2"/>
+ <gd name="c1" fmla="?: v1 d2 c2"/>
+ <gd name="swAng" fmla="?: w1 d1 c1"/>
+ <gd name="wt1" fmla="sin wd2 adj"/>
+ <gd name="ht1" fmla="cos hd2 adj"/>
+ <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/>
+ <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/>
+ <gd name="x1" fmla="+- hc dx1 0"/>
+ <gd name="y1" fmla="+- vc dy1 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahPolar gdRefAng="adj" minAng="0" maxAng="21599999">
+ <pos x="x1" y="y1"/>
+ </ahPolar>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="x1" y="y1"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="hd2" stAng="adval" swAng="swAng"/>
+ </path>
+ </pathLst>
+ </textArchUp>
+ <textArchUpPour>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj1" fmla="val cd2"/>
+ <gd name="adj2" fmla="val 50000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adval" fmla="pin 0 adj1 21599999"/>
+ <gd name="v1" fmla="+- 10800000 0 adval"/>
+ <gd name="v2" fmla="+- 32400000 0 adval"/>
+ <gd name="end" fmla="?: v1 v1 v2"/>
+ <gd name="w1" fmla="+- 5400000 0 adval"/>
+ <gd name="w2" fmla="+- 16200000 0 adval"/>
+ <gd name="d1" fmla="+- end 0 adval"/>
+ <gd name="d2" fmla="+- 21600000 d1 0"/>
+ <gd name="c2" fmla="?: w2 d1 d2"/>
+ <gd name="c1" fmla="?: v1 d2 c2"/>
+ <gd name="swAng" fmla="?: w1 d1 c1"/>
+ <gd name="wt1" fmla="sin wd2 adval"/>
+ <gd name="ht1" fmla="cos hd2 adval"/>
+ <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/>
+ <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/>
+ <gd name="x1" fmla="+- hc dx1 0"/>
+ <gd name="y1" fmla="+- vc dy1 0"/>
+ <gd name="adval2" fmla="pin 0 adj2 99000"/>
+ <gd name="ratio" fmla="*/ adval2 1 100000"/>
+ <gd name="iwd2" fmla="*/ wd2 ratio 1"/>
+ <gd name="ihd2" fmla="*/ hd2 ratio 1"/>
+ <gd name="wt2" fmla="sin iwd2 adval"/>
+ <gd name="ht2" fmla="cos ihd2 adval"/>
+ <gd name="dx2" fmla="cat2 iwd2 ht2 wt2"/>
+ <gd name="dy2" fmla="sat2 ihd2 ht2 wt2"/>
+ <gd name="x2" fmla="+- hc dx2 0"/>
+ <gd name="y2" fmla="+- vc dy2 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahPolar gdRefR="adj2" minR="0" maxR="100000" gdRefAng="adj1" minAng="0" maxAng="21599999">
+ <pos x="x2" y="y2"/>
+ </ahPolar>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="x1" y="y1"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="hd2" stAng="adval" swAng="swAng"/>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="x2" y="y2"/>
+ </moveTo>
+ <arcTo wR="iwd2" hR="ihd2" stAng="adval" swAng="swAng"/>
+ </path>
+ </pathLst>
+ </textArchUpPour>
+ <textButton>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 10800000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adval" fmla="pin 0 adj 21599999"/>
+ <gd name="bot" fmla="+- 5400000 0 adval"/>
+ <gd name="lef" fmla="+- 10800000 0 adval"/>
+ <gd name="top" fmla="+- 16200000 0 adval"/>
+ <gd name="rig" fmla="+- 21600000 0 adval"/>
+ <gd name="c3" fmla="?: top adval 0"/>
+ <gd name="c2" fmla="?: lef 10800000 c3"/>
+ <gd name="c1" fmla="?: bot rig c2"/>
+ <gd name="stAng" fmla="?: adval c1 0"/>
+ <gd name="w1" fmla="+- 21600000 0 stAng"/>
+ <gd name="stAngB" fmla="?: stAng w1 0"/>
+ <gd name="td1" fmla="*/ bot 2 1"/>
+ <gd name="td2" fmla="*/ top 2 1"/>
+ <gd name="ntd2" fmla="+- 0 0 td2"/>
+ <gd name="w2" fmla="+- 0 0 10800000"/>
+ <gd name="c6" fmla="?: top ntd2 w2"/>
+ <gd name="c5" fmla="?: lef 10800000 c6"/>
+ <gd name="c4" fmla="?: bot td1 c5"/>
+ <gd name="v1" fmla="?: adval c4 10800000"/>
+ <gd name="swAngT" fmla="+- 0 0 v1"/>
+ <gd name="stT" fmla="?: lef stAngB stAng"/>
+ <gd name="stB" fmla="?: lef stAng stAngB"/>
+ <gd name="swT" fmla="?: lef v1 swAngT"/>
+ <gd name="swB" fmla="?: lef swAngT v1"/>
+ <gd name="wt1" fmla="sin wd2 stT"/>
+ <gd name="ht1" fmla="cos hd2 stT"/>
+ <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/>
+ <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/>
+ <gd name="x1" fmla="+- hc dx1 0"/>
+ <gd name="y1" fmla="+- vc dy1 0"/>
+ <gd name="wt2" fmla="sin wd2 stB"/>
+ <gd name="ht2" fmla="cos hd2 stB"/>
+ <gd name="dx2" fmla="cat2 wd2 ht2 wt2"/>
+ <gd name="dy2" fmla="sat2 hd2 ht2 wt2"/>
+ <gd name="x2" fmla="+- hc dx2 0"/>
+ <gd name="y2" fmla="+- vc dy2 0"/>
+ <gd name="wt3" fmla="sin wd2 adj"/>
+ <gd name="ht3" fmla="cos hd2 adj"/>
+ <gd name="dx3" fmla="cat2 wd2 ht3 wt3"/>
+ <gd name="dy3" fmla="sat2 hd2 ht3 wt3"/>
+ <gd name="x3" fmla="+- hc dx3 0"/>
+ <gd name="y3" fmla="+- vc dy3 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahPolar gdRefAng="adj" minAng="0" maxAng="21599999">
+ <pos x="x3" y="y3"/>
+ </ahPolar>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="x1" y="y1"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="hd2" stAng="stT" swAng="swT"/>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="vc"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="vc"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="x2" y="y2"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="hd2" stAng="stB" swAng="swB"/>
+ </path>
+ </pathLst>
+ </textButton>
+ <textButtonPour>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj1" fmla="val cd2"/>
+ <gd name="adj2" fmla="val 50000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adval" fmla="pin 0 adj1 21599999"/>
+ <gd name="bot" fmla="+- 5400000 0 adval"/>
+ <gd name="lef" fmla="+- 10800000 0 adval"/>
+ <gd name="top" fmla="+- 16200000 0 adval"/>
+ <gd name="rig" fmla="+- 21600000 0 adval"/>
+ <gd name="c3" fmla="?: top adval 0"/>
+ <gd name="c2" fmla="?: lef 10800000 c3"/>
+ <gd name="c1" fmla="?: bot rig c2"/>
+ <gd name="stAng" fmla="?: adval c1 0"/>
+ <gd name="w1" fmla="+- 21600000 0 stAng"/>
+ <gd name="stAngB" fmla="?: stAng w1 0"/>
+ <gd name="td1" fmla="*/ bot 2 1"/>
+ <gd name="td2" fmla="*/ top 2 1"/>
+ <gd name="ntd2" fmla="+- 0 0 td2"/>
+ <gd name="w2" fmla="+- 0 0 10800000"/>
+ <gd name="c6" fmla="?: top ntd2 w2"/>
+ <gd name="c5" fmla="?: lef 10800000 c6"/>
+ <gd name="c4" fmla="?: bot td1 c5"/>
+ <gd name="v1" fmla="?: adval c4 10800000"/>
+ <gd name="swAngT" fmla="+- 0 0 v1"/>
+ <gd name="stT" fmla="?: lef stAngB stAng"/>
+ <gd name="stB" fmla="?: lef stAng stAngB"/>
+ <gd name="swT" fmla="?: lef v1 swAngT"/>
+ <gd name="swB" fmla="?: lef swAngT v1"/>
+ <gd name="wt1" fmla="sin wd2 stT"/>
+ <gd name="ht1" fmla="cos hd2 stT"/>
+ <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/>
+ <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/>
+ <gd name="x1" fmla="+- hc dx1 0"/>
+ <gd name="y1" fmla="+- vc dy1 0"/>
+ <gd name="wt6" fmla="sin wd2 stB"/>
+ <gd name="ht6" fmla="cos hd2 stB"/>
+ <gd name="dx6" fmla="cat2 wd2 ht6 wt6"/>
+ <gd name="dy6" fmla="sat2 hd2 ht6 wt6"/>
+ <gd name="x6" fmla="+- hc dx6 0"/>
+ <gd name="y6" fmla="+- vc dy6 0"/>
+ <gd name="adval2" fmla="pin 40000 adj2 99000"/>
+ <gd name="ratio" fmla="*/ adval2 1 100000"/>
+ <gd name="iwd2" fmla="*/ wd2 ratio 1"/>
+ <gd name="ihd2" fmla="*/ hd2 ratio 1"/>
+ <gd name="wt2" fmla="sin iwd2 stT"/>
+ <gd name="ht2" fmla="cos ihd2 stT"/>
+ <gd name="dx2" fmla="cat2 iwd2 ht2 wt2"/>
+ <gd name="dy2" fmla="sat2 ihd2 ht2 wt2"/>
+ <gd name="x2" fmla="+- hc dx2 0"/>
+ <gd name="y2" fmla="+- vc dy2 0"/>
+ <gd name="wt5" fmla="sin iwd2 stB"/>
+ <gd name="ht5" fmla="cos ihd2 stB"/>
+ <gd name="dx5" fmla="cat2 iwd2 ht5 wt5"/>
+ <gd name="dy5" fmla="sat2 ihd2 ht5 wt5"/>
+ <gd name="x5" fmla="+- hc dx5 0"/>
+ <gd name="y5" fmla="+- vc dy5 0"/>
+ <gd name="d1" fmla="+- hd2 0 ihd2"/>
+ <gd name="d12" fmla="*/ d1 1 2"/>
+ <gd name="yu" fmla="+- vc 0 d12"/>
+ <gd name="yd" fmla="+- vc d12 0"/>
+ <gd name="v1" fmla="*/ d12 d12 1"/>
+ <gd name="v2" fmla="*/ ihd2 ihd2 1"/>
+ <gd name="v3" fmla="*/ v1 1 v2"/>
+ <gd name="v4" fmla="+- 1 0 v3"/>
+ <gd name="v5" fmla="*/ iwd2 iwd2 1"/>
+ <gd name="v6" fmla="*/ v4 v5 1"/>
+ <gd name="v7" fmla="sqrt v6"/>
+ <gd name="xl" fmla="+- hc 0 v7"/>
+ <gd name="xr" fmla="+- hc v7 0"/>
+ <gd name="wtadj" fmla="sin iwd2 adj1"/>
+ <gd name="htadj" fmla="cos ihd2 adj1"/>
+ <gd name="dxadj" fmla="cat2 iwd2 htadj wtadj"/>
+ <gd name="dyadj" fmla="sat2 ihd2 htadj wtadj"/>
+ <gd name="xadj" fmla="+- hc dxadj 0"/>
+ <gd name="yadj" fmla="+- vc dyadj 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahPolar gdRefR="adj2" minR="0" maxR="100000" gdRefAng="adj1" minAng="0" maxAng="21599999">
+ <pos x="xadj" y="yadj"/>
+ </ahPolar>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="x1" y="y1"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="hd2" stAng="stT" swAng="swT"/>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="x2" y="y2"/>
+ </moveTo>
+ <arcTo wR="iwd2" hR="ihd2" stAng="stT" swAng="swT"/>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="xl" y="yu"/>
+ </moveTo>
+ <lnTo>
+ <pt x="xr" y="yu"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="xl" y="yd"/>
+ </moveTo>
+ <lnTo>
+ <pt x="xr" y="yd"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="x5" y="y5"/>
+ </moveTo>
+ <arcTo wR="iwd2" hR="ihd2" stAng="stB" swAng="swB"/>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="x6" y="y6"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="hd2" stAng="stB" swAng="swB"/>
+ </path>
+ </pathLst>
+ </textButtonPour>
+ <textCanDown>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 14286"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 0 adj 33333"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="y0" fmla="+- t dy 0"/>
+ <gd name="y1" fmla="+- b 0 dy"/>
+ <gd name="ncd2" fmla="*/ cd2 -1 1"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="0" maxY="33333">
+ <pos x="hc" y="y0"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="t"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="dy" stAng="cd2" swAng="ncd2"/>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="y1"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="dy" stAng="cd2" swAng="ncd2"/>
+ </path>
+ </pathLst>
+ </textCanDown>
+ <textCanUp>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 85714"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 66667 adj 100000"/>
+ <gd name="dy1" fmla="*/ a h 100000"/>
+ <gd name="dy" fmla="+- h 0 dy1"/>
+ <gd name="y0" fmla="+- t dy1 0"/>
+ <gd name="y1" fmla="+- t dy 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="66667" maxY="100000">
+ <pos x="hc" y="y0"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="y1"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="dy" stAng="cd2" swAng="cd2"/>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="b"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="dy" stAng="cd2" swAng="cd2"/>
+ </path>
+ </pathLst>
+ </textCanUp>
+ <textCascadeDown>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 44444"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 28570 adj 100000"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="y1" fmla="+- t dy 0"/>
+ <gd name="dy2" fmla="+- h 0 dy"/>
+ <gd name="dy3" fmla="*/ dy2 1 4"/>
+ <gd name="y2" fmla="+- t dy3 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="28570" maxY="100000">
+ <pos x="l" y="y1"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="t"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="y2"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="y1"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="b"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textCascadeDown>
+ <textCascadeUp>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 44444"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 28570 adj 100000"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="y1" fmla="+- t dy 0"/>
+ <gd name="dy2" fmla="+- h 0 dy"/>
+ <gd name="dy3" fmla="*/ dy2 1 4"/>
+ <gd name="y2" fmla="+- t dy3 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="28570" maxY="100000">
+ <pos x="r" y="y1"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="y2"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="t"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="b"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="y1"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textCascadeUp>
+ <textChevron>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 25000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 0 adj 50000"/>
+ <gd name="y" fmla="*/ a h 100000"/>
+ <gd name="y1" fmla="+- t b y"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="0" maxY="50000">
+ <pos x="l" y="y"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="y"/>
+ </moveTo>
+ <lnTo>
+ <pt x="hc" y="t"/>
+ </lnTo>
+ <lnTo>
+ <pt x="r" y="y"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="b"/>
+ </moveTo>
+ <lnTo>
+ <pt x="hc" y="y1"/>
+ </lnTo>
+ <lnTo>
+ <pt x="r" y="b"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textChevron>
+ <textChevronInverted>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 75000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 50000 adj 100000"/>
+ <gd name="y" fmla="*/ a h 100000"/>
+ <gd name="y1" fmla="+- b 0 y"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="50000" maxY="100000">
+ <pos x="l" y="y"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="t"/>
+ </moveTo>
+ <lnTo>
+ <pt x="hc" y="y1"/>
+ </lnTo>
+ <lnTo>
+ <pt x="r" y="t"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="y"/>
+ </moveTo>
+ <lnTo>
+ <pt x="hc" y="b"/>
+ </lnTo>
+ <lnTo>
+ <pt x="r" y="y"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textChevronInverted>
+ <textCircle>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 10800000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adval" fmla="pin 0 adj 21599999"/>
+ <gd name="d0" fmla="+- adval 0 10800000"/>
+ <gd name="d1" fmla="+- 10800000 0 adval"/>
+ <gd name="d2" fmla="+- 21600000 0 adval"/>
+ <gd name="d3" fmla="?: d1 d1 10799999"/>
+ <gd name="d4" fmla="?: d0 d2 d3"/>
+ <gd name="swAng" fmla="*/ d4 2 1"/>
+ <gd name="wt1" fmla="sin wd2 adj"/>
+ <gd name="ht1" fmla="cos hd2 adj"/>
+ <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/>
+ <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/>
+ <gd name="x1" fmla="+- hc dx1 0"/>
+ <gd name="y1" fmla="+- vc dy1 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahPolar gdRefAng="adj" minAng="0" maxAng="21599999">
+ <pos x="x1" y="y1"/>
+ </ahPolar>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="x1" y="y1"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="hd2" stAng="adval" swAng="swAng"/>
+ </path>
+ </pathLst>
+ </textCircle>
+ <textCirclePour>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj1" fmla="val cd2"/>
+ <gd name="adj2" fmla="val 50000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adval" fmla="pin 0 adj1 21599999"/>
+ <gd name="d0" fmla="+- adval 0 10800000"/>
+ <gd name="d1" fmla="+- 10800000 0 adval"/>
+ <gd name="d2" fmla="+- 21600000 0 adval"/>
+ <gd name="d3" fmla="?: d1 d1 10799999"/>
+ <gd name="d4" fmla="?: d0 d2 d3"/>
+ <gd name="swAng" fmla="*/ d4 2 1"/>
+ <gd name="wt1" fmla="sin wd2 adval"/>
+ <gd name="ht1" fmla="cos hd2 adval"/>
+ <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/>
+ <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/>
+ <gd name="x1" fmla="+- hc dx1 0"/>
+ <gd name="y1" fmla="+- vc dy1 0"/>
+ <gd name="adval2" fmla="pin 0 adj2 99000"/>
+ <gd name="ratio" fmla="*/ adval2 1 100000"/>
+ <gd name="iwd2" fmla="*/ wd2 ratio 1"/>
+ <gd name="ihd2" fmla="*/ hd2 ratio 1"/>
+ <gd name="wt2" fmla="sin iwd2 adval"/>
+ <gd name="ht2" fmla="cos ihd2 adval"/>
+ <gd name="dx2" fmla="cat2 iwd2 ht2 wt2"/>
+ <gd name="dy2" fmla="sat2 ihd2 ht2 wt2"/>
+ <gd name="x2" fmla="+- hc dx2 0"/>
+ <gd name="y2" fmla="+- vc dy2 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahPolar gdRefR="adj2" minR="0" maxR="100000" gdRefAng="adj1" minAng="0" maxAng="21599999">
+ <pos x="x2" y="y2"/>
+ </ahPolar>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="x1" y="y1"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="hd2" stAng="adval" swAng="swAng"/>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="x2" y="y2"/>
+ </moveTo>
+ <arcTo wR="iwd2" hR="ihd2" stAng="adval" swAng="swAng"/>
+ </path>
+ </pathLst>
+ </textCirclePour>
+ <textCurveDown>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 45977"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 0 adj 56338"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="gd1" fmla="*/ dy 3 4"/>
+ <gd name="gd2" fmla="*/ dy 5 4"/>
+ <gd name="gd3" fmla="*/ dy 3 8"/>
+ <gd name="gd4" fmla="*/ dy 1 8"/>
+ <gd name="gd5" fmla="+- h 0 gd3"/>
+ <gd name="gd6" fmla="+- gd4 h 0"/>
+ <gd name="y0" fmla="+- t dy 0"/>
+ <gd name="y1" fmla="+- t gd1 0"/>
+ <gd name="y2" fmla="+- t gd2 0"/>
+ <gd name="y3" fmla="+- t gd3 0"/>
+ <gd name="y4" fmla="+- t gd4 0"/>
+ <gd name="y5" fmla="+- t gd5 0"/>
+ <gd name="y6" fmla="+- t gd6 0"/>
+ <gd name="x1" fmla="+- l wd3 0"/>
+ <gd name="x2" fmla="+- r 0 wd3"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="0" maxY="56338">
+ <pos x="r" y="y0"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="t"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x1" y="y1"/>
+ <pt x="x2" y="y2"/>
+ <pt x="r" y="y0"/>
+ </cubicBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="y5"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x1" y="y6"/>
+ <pt x="x2" y="y6"/>
+ <pt x="r" y="y5"/>
+ </cubicBezTo>
+ </path>
+ </pathLst>
+ </textCurveDown>
+ <textCurveUp>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 45977"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 0 adj 56338"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="gd1" fmla="*/ dy 3 4"/>
+ <gd name="gd2" fmla="*/ dy 5 4"/>
+ <gd name="gd3" fmla="*/ dy 3 8"/>
+ <gd name="gd4" fmla="*/ dy 1 8"/>
+ <gd name="gd5" fmla="+- h 0 gd3"/>
+ <gd name="gd6" fmla="+- gd4 h 0"/>
+ <gd name="y0" fmla="+- t dy 0"/>
+ <gd name="y1" fmla="+- t gd1 0"/>
+ <gd name="y2" fmla="+- t gd2 0"/>
+ <gd name="y3" fmla="+- t gd3 0"/>
+ <gd name="y4" fmla="+- t gd4 0"/>
+ <gd name="y5" fmla="+- t gd5 0"/>
+ <gd name="y6" fmla="+- t gd6 0"/>
+ <gd name="x1" fmla="+- l wd3 0"/>
+ <gd name="x2" fmla="+- r 0 wd3"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="0" maxY="56338">
+ <pos x="l" y="y0"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="y0"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x1" y="y2"/>
+ <pt x="x2" y="y1"/>
+ <pt x="r" y="t"/>
+ </cubicBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="y5"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x1" y="y6"/>
+ <pt x="x2" y="y6"/>
+ <pt x="r" y="y5"/>
+ </cubicBezTo>
+ </path>
+ </pathLst>
+ </textCurveUp>
+ <textDeflate>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 18750"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 0 adj 37500"/>
+ <gd name="dy" fmla="*/ a ss 100000"/>
+ <gd name="gd0" fmla="*/ dy 4 3"/>
+ <gd name="gd1" fmla="+- h 0 gd0"/>
+ <gd name="adjY" fmla="+- t dy 0"/>
+ <gd name="y0" fmla="+- t gd0 0"/>
+ <gd name="y1" fmla="+- t gd1 0"/>
+ <gd name="x0" fmla="+- l wd3 0"/>
+ <gd name="x1" fmla="+- r 0 wd3"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="0" maxY="37500">
+ <pos x="hc" y="adjY"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="t"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x0" y="y0"/>
+ <pt x="x1" y="y0"/>
+ <pt x="r" y="t"/>
+ </cubicBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="b"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x0" y="y1"/>
+ <pt x="x1" y="y1"/>
+ <pt x="r" y="b"/>
+ </cubicBezTo>
+ </path>
+ </pathLst>
+ </textDeflate>
+ <textDeflateBottom>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 50000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 6250 adj 100000"/>
+ <gd name="dy" fmla="*/ a ss 100000"/>
+ <gd name="dy2" fmla="+- h 0 dy"/>
+ <gd name="y1" fmla="+- t dy 0"/>
+ <gd name="cp" fmla="+- y1 0 dy2"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="6250" maxY="100000">
+ <pos x="hc" y="y1"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="t"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="t"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="b"/>
+ </moveTo>
+ <quadBezTo>
+ <pt x="hc" y="cp"/>
+ <pt x="r" y="b"/>
+ </quadBezTo>
+ </path>
+ </pathLst>
+ </textDeflateBottom>
+ <textDeflateInflate>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 35000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 5000 adj 95000"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="del" fmla="*/ h 5 100"/>
+ <gd name="dh1" fmla="*/ h 45 100"/>
+ <gd name="dh2" fmla="*/ h 55 100"/>
+ <gd name="yh" fmla="+- dy 0 del"/>
+ <gd name="yl" fmla="+- dy del 0"/>
+ <gd name="y3" fmla="+- yh yh dh1"/>
+ <gd name="y4" fmla="+- yl yl dh2"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="5000" maxY="95000">
+ <pos x="hc" y="dy"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="t"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="t"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="dh1"/>
+ </moveTo>
+ <quadBezTo>
+ <pt x="hc" y="y3"/>
+ <pt x="r" y="dh1"/>
+ </quadBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="dh2"/>
+ </moveTo>
+ <quadBezTo>
+ <pt x="hc" y="y4"/>
+ <pt x="r" y="dh2"/>
+ </quadBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="b"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="b"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textDeflateInflate>
+ <textDeflateInflateDeflate>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 25000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 3000 adj 47000"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="del" fmla="*/ h 3 100"/>
+ <gd name="ey1" fmla="*/ h 30 100"/>
+ <gd name="ey2" fmla="*/ h 36 100"/>
+ <gd name="ey3" fmla="*/ h 63 100"/>
+ <gd name="ey4" fmla="*/ h 70 100"/>
+ <gd name="by" fmla="+- b 0 dy"/>
+ <gd name="yh1" fmla="+- dy 0 del"/>
+ <gd name="yl1" fmla="+- dy del 0"/>
+ <gd name="yh2" fmla="+- by 0 del"/>
+ <gd name="yl2" fmla="+- by del 0"/>
+ <gd name="y1" fmla="+- yh1 yh1 ey1"/>
+ <gd name="y2" fmla="+- yl1 yl1 ey2"/>
+ <gd name="y3" fmla="+- yh2 yh2 ey3"/>
+ <gd name="y4" fmla="+- yl2 yl2 ey4"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="3000" maxY="47000">
+ <pos x="hc" y="dy"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="t"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="t"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="ey1"/>
+ </moveTo>
+ <quadBezTo>
+ <pt x="hc" y="y1"/>
+ <pt x="r" y="ey1"/>
+ </quadBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="ey2"/>
+ </moveTo>
+ <quadBezTo>
+ <pt x="hc" y="y2"/>
+ <pt x="r" y="ey2"/>
+ </quadBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="ey3"/>
+ </moveTo>
+ <quadBezTo>
+ <pt x="hc" y="y3"/>
+ <pt x="r" y="ey3"/>
+ </quadBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="ey4"/>
+ </moveTo>
+ <quadBezTo>
+ <pt x="hc" y="y4"/>
+ <pt x="r" y="ey4"/>
+ </quadBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="b"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="b"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textDeflateInflateDeflate>
+ <textDeflateTop>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 50000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 0 adj 93750"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="y1" fmla="+- t dy 0"/>
+ <gd name="cp" fmla="+- y1 dy 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="0" maxY="93750">
+ <pos x="hc" y="y1"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="t"/>
+ </moveTo>
+ <quadBezTo>
+ <pt x="hc" y="cp"/>
+ <pt x="r" y="t"/>
+ </quadBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="b"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="b"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textDeflateTop>
+ <textDoubleWave1>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj1" fmla="val 6250"/>
+ <gd name="adj2" fmla="val 0"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a1" fmla="pin 0 adj1 12500"/>
+ <gd name="a2" fmla="pin -10000 adj2 10000"/>
+ <gd name="y1" fmla="*/ h a1 100000"/>
+ <gd name="dy2" fmla="*/ y1 10 3"/>
+ <gd name="y2" fmla="+- y1 0 dy2"/>
+ <gd name="y3" fmla="+- y1 dy2 0"/>
+ <gd name="y4" fmla="+- b 0 y1"/>
+ <gd name="y5" fmla="+- y4 0 dy2"/>
+ <gd name="y6" fmla="+- y4 dy2 0"/>
+ <gd name="of" fmla="*/ w a2 100000"/>
+ <gd name="of2" fmla="*/ w a2 50000"/>
+ <gd name="x1" fmla="abs of"/>
+ <gd name="dx2" fmla="?: of2 0 of2"/>
+ <gd name="x2" fmla="+- l 0 dx2"/>
+ <gd name="dx8" fmla="?: of2 of2 0"/>
+ <gd name="x8" fmla="+- r 0 dx8"/>
+ <gd name="dx3" fmla="+/ dx2 x8 6"/>
+ <gd name="x3" fmla="+- x2 dx3 0"/>
+ <gd name="dx4" fmla="+/ dx2 x8 3"/>
+ <gd name="x4" fmla="+- x2 dx4 0"/>
+ <gd name="x5" fmla="+/ x2 x8 2"/>
+ <gd name="x6" fmla="+- x5 dx3 0"/>
+ <gd name="x7" fmla="+/ x6 x8 2"/>
+ <gd name="x9" fmla="+- l dx8 0"/>
+ <gd name="x15" fmla="+- r dx2 0"/>
+ <gd name="x10" fmla="+- x9 dx3 0"/>
+ <gd name="x11" fmla="+- x9 dx4 0"/>
+ <gd name="x12" fmla="+/ x9 x15 2"/>
+ <gd name="x13" fmla="+- x12 dx3 0"/>
+ <gd name="x14" fmla="+/ x13 x15 2"/>
+ <gd name="x16" fmla="+- r 0 x1"/>
+ <gd name="xAdj" fmla="+- hc of 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj1" minY="0" maxY="12500">
+ <pos x="l" y="y1"/>
+ </ahXY>
+ <ahXY gdRefX="adj2" minX="-10000" maxX="10000">
+ <pos x="xAdj" y="b"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="x2" y="y1"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x3" y="y2"/>
+ <pt x="x4" y="y3"/>
+ <pt x="x5" y="y1"/>
+ </cubicBezTo>
+ <cubicBezTo>
+ <pt x="x6" y="y2"/>
+ <pt x="x7" y="y3"/>
+ <pt x="x8" y="y1"/>
+ </cubicBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="x9" y="y4"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x10" y="y5"/>
+ <pt x="x11" y="y6"/>
+ <pt x="x12" y="y4"/>
+ </cubicBezTo>
+ <cubicBezTo>
+ <pt x="x13" y="y5"/>
+ <pt x="x14" y="y6"/>
+ <pt x="x15" y="y4"/>
+ </cubicBezTo>
+ </path>
+ </pathLst>
+ </textDoubleWave1>
+ <textFadeDown>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 33333"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 0 adj 49999"/>
+ <gd name="dx" fmla="*/ a w 100000"/>
+ <gd name="x1" fmla="+- l dx 0"/>
+ <gd name="x2" fmla="+- r 0 dx"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefX="adj" minX="0" maxX="49999">
+ <pos x="x1" y="b"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="t"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="t"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="x1" y="b"/>
+ </moveTo>
+ <lnTo>
+ <pt x="x2" y="b"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textFadeDown>
+ <textFadeLeft>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 33333"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 0 adj 49999"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="y1" fmla="+- t dy 0"/>
+ <gd name="y2" fmla="+- b 0 dy"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="0" maxY="49999">
+ <pos x="l" y="y1"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="y1"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="t"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="y2"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="b"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textFadeLeft>
+ <textFadeRight>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 33333"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 0 adj 49999"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="y1" fmla="+- t dy 0"/>
+ <gd name="y2" fmla="+- b 0 dy"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="0" maxY="49999">
+ <pos x="r" y="y1"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="t"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="y1"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="b"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="y2"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textFadeRight>
+ <textFadeUp>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 33333"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 0 adj 49999"/>
+ <gd name="dx" fmla="*/ a w 100000"/>
+ <gd name="x1" fmla="+- l dx 0"/>
+ <gd name="x2" fmla="+- r 0 dx"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefX="adj" minX="0" maxX="49999">
+ <pos x="x1" y="t"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="x1" y="t"/>
+ </moveTo>
+ <lnTo>
+ <pt x="x2" y="t"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="b"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="b"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textFadeUp>
+ <textInflate>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 18750"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 0 adj 20000"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="gd" fmla="*/ dy 1 3"/>
+ <gd name="gd0" fmla="+- 0 0 gd"/>
+ <gd name="gd1" fmla="+- h 0 gd0"/>
+ <gd name="ty" fmla="+- t dy 0"/>
+ <gd name="by" fmla="+- b 0 dy"/>
+ <gd name="y0" fmla="+- t gd0 0"/>
+ <gd name="y1" fmla="+- t gd1 0"/>
+ <gd name="x0" fmla="+- l wd3 0"/>
+ <gd name="x1" fmla="+- r 0 wd3"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="0" maxY="20000">
+ <pos x="l" y="ty"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="ty"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x0" y="y0"/>
+ <pt x="x1" y="y0"/>
+ <pt x="r" y="ty"/>
+ </cubicBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="by"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x0" y="y1"/>
+ <pt x="x1" y="y1"/>
+ <pt x="r" y="by"/>
+ </cubicBezTo>
+ </path>
+ </pathLst>
+ </textInflate>
+ <textInflateBottom>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 60000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 60000 adj 100000"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="ty" fmla="+- t dy 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="60000" maxY="100000">
+ <pos x="l" y="ty"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="t"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="t"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="ty"/>
+ </moveTo>
+ <quadBezTo>
+ <pt x="hc" y="b"/>
+ <pt x="r" y="ty"/>
+ </quadBezTo>
+ </path>
+ </pathLst>
+ </textInflateBottom>
+ <textInflateTop>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 40000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 0 adj 50000"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="ty" fmla="+- t dy 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="0" maxY="50000">
+ <pos x="l" y="ty"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="ty"/>
+ </moveTo>
+ <quadBezTo>
+ <pt x="hc" y="t"/>
+ <pt x="r" y="ty"/>
+ </quadBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="b"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="b"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textInflateTop>
+ <textPlain>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 50000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 30000 adj 70000"/>
+ <gd name="mid" fmla="*/ a w 100000"/>
+ <gd name="midDir" fmla="+- mid 0 hc"/>
+ <gd name="dl" fmla="+- mid 0 l"/>
+ <gd name="dr" fmla="+- r 0 mid"/>
+ <gd name="dl2" fmla="*/ dl 2 1"/>
+ <gd name="dr2" fmla="*/ dr 2 1"/>
+ <gd name="dx" fmla="?: midDir dr2 dl2"/>
+ <gd name="xr" fmla="+- l dx 0"/>
+ <gd name="xl" fmla="+- r 0 dx"/>
+ <gd name="tlx" fmla="?: midDir l xl"/>
+ <gd name="trx" fmla="?: midDir xr r"/>
+ <gd name="blx" fmla="?: midDir xl l"/>
+ <gd name="brx" fmla="?: midDir r xr"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefX="adj" minX="30000" maxX="70000">
+ <pos x="mid" y="b"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="tlx" y="t"/>
+ </moveTo>
+ <lnTo>
+ <pt x="trx" y="t"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="blx" y="b"/>
+ </moveTo>
+ <lnTo>
+ <pt x="brx" y="b"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textPlain>
+ <textRingInside>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 60000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 50000 adj 99000"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="y" fmla="+- t dy 0"/>
+ <gd name="r" fmla="*/ dy 1 2"/>
+ <gd name="y1" fmla="+- t r 0"/>
+ <gd name="y2" fmla="+- b 0 r"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="50000" maxY="99000">
+ <pos x="hc" y="y"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="y1"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="r" stAng="10800000" swAng="21599999"/>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="y2"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="r" stAng="10800000" swAng="21599999"/>
+ </path>
+ </pathLst>
+ </textRingInside>
+ <textRingOutside>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 60000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 50000 adj 99000"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="y" fmla="+- t dy 0"/>
+ <gd name="r" fmla="*/ dy 1 2"/>
+ <gd name="y1" fmla="+- t r 0"/>
+ <gd name="y2" fmla="+- b 0 r"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="50000" maxY="99000">
+ <pos x="hc" y="y"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="y1"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="r" stAng="10800000" swAng="-21599999"/>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="y2"/>
+ </moveTo>
+ <arcTo wR="wd2" hR="r" stAng="10800000" swAng="-21599999"/>
+ </path>
+ </pathLst>
+ </textRingOutside>
+ <textSlantDown>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 44445"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 28569 adj 100000"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="y1" fmla="+- t dy 0"/>
+ <gd name="y2" fmla="+- b 0 dy"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="28569" maxY="100000">
+ <pos x="l" y="y1"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="t"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="y2"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="y1"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="b"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textSlantDown>
+ <textSlantUp>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 55555"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 0 adj 71431"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="y1" fmla="+- t dy 0"/>
+ <gd name="y2" fmla="+- b 0 dy"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="0" maxY="71431">
+ <pos x="l" y="y1"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="y1"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="t"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="b"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="y2"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textSlantUp>
+ <textStop>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 25000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 14286 adj 50000"/>
+ <gd name="dx" fmla="*/ w 1 3"/>
+ <gd name="dy" fmla="*/ a h 100000"/>
+ <gd name="x1" fmla="+- l dx 0"/>
+ <gd name="x2" fmla="+- r 0 dx"/>
+ <gd name="y1" fmla="+- t dy 0"/>
+ <gd name="y2" fmla="+- b 0 dy"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="14286" maxY="50000">
+ <pos x="l" y="dy"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="y1"/>
+ </moveTo>
+ <lnTo>
+ <pt x="x1" y="t"/>
+ </lnTo>
+ <lnTo>
+ <pt x="x2" y="t"/>
+ </lnTo>
+ <lnTo>
+ <pt x="r" y="y1"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="y2"/>
+ </moveTo>
+ <lnTo>
+ <pt x="x1" y="b"/>
+ </lnTo>
+ <lnTo>
+ <pt x="x2" y="b"/>
+ </lnTo>
+ <lnTo>
+ <pt x="r" y="y2"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textStop>
+ <textTriangle>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 50000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 0 adj 100000"/>
+ <gd name="y" fmla="*/ a h 100000"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="0" maxY="100000">
+ <pos x="l" y="y"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="y"/>
+ </moveTo>
+ <lnTo>
+ <pt x="hc" y="t"/>
+ </lnTo>
+ <lnTo>
+ <pt x="r" y="y"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="b"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="b"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textTriangle>
+ <textTriangleInverted>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj" fmla="val 50000"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a" fmla="pin 0 adj 100000"/>
+ <gd name="y" fmla="*/ a h 100000"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj" minY="0" maxY="100000">
+ <pos x="l" y="y"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="l" y="t"/>
+ </moveTo>
+ <lnTo>
+ <pt x="r" y="t"/>
+ </lnTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="l" y="y"/>
+ </moveTo>
+ <lnTo>
+ <pt x="hc" y="b"/>
+ </lnTo>
+ <lnTo>
+ <pt x="r" y="y"/>
+ </lnTo>
+ </path>
+ </pathLst>
+ </textTriangleInverted>
+ <textWave1>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj1" fmla="val 12500"/>
+ <gd name="adj2" fmla="val 0"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a1" fmla="pin 0 adj1 20000"/>
+ <gd name="a2" fmla="pin -10000 adj2 10000"/>
+ <gd name="y1" fmla="*/ h a1 100000"/>
+ <gd name="dy2" fmla="*/ y1 10 3"/>
+ <gd name="y2" fmla="+- y1 0 dy2"/>
+ <gd name="y3" fmla="+- y1 dy2 0"/>
+ <gd name="y4" fmla="+- b 0 y1"/>
+ <gd name="y5" fmla="+- y4 0 dy2"/>
+ <gd name="y6" fmla="+- y4 dy2 0"/>
+ <gd name="of" fmla="*/ w a2 100000"/>
+ <gd name="of2" fmla="*/ w a2 50000"/>
+ <gd name="x1" fmla="abs of"/>
+ <gd name="dx2" fmla="?: of2 0 of2"/>
+ <gd name="x2" fmla="+- l 0 dx2"/>
+ <gd name="dx5" fmla="?: of2 of2 0"/>
+ <gd name="x5" fmla="+- r 0 dx5"/>
+ <gd name="dx3" fmla="+/ dx2 x5 3"/>
+ <gd name="x3" fmla="+- x2 dx3 0"/>
+ <gd name="x4" fmla="+/ x3 x5 2"/>
+ <gd name="x6" fmla="+- l dx5 0"/>
+ <gd name="x10" fmla="+- r dx2 0"/>
+ <gd name="x7" fmla="+- x6 dx3 0"/>
+ <gd name="x8" fmla="+/ x7 x10 2"/>
+ <gd name="x9" fmla="+- r 0 x1"/>
+ <gd name="xAdj" fmla="+- hc of 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj1" minY="0" maxY="20000">
+ <pos x="l" y="y1"/>
+ </ahXY>
+ <ahXY gdRefX="adj2" minX="-10000" maxX="10000">
+ <pos x="xAdj" y="b"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="x2" y="y1"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x3" y="y2"/>
+ <pt x="x4" y="y3"/>
+ <pt x="x5" y="y1"/>
+ </cubicBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="x6" y="y4"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x7" y="y5"/>
+ <pt x="x8" y="y6"/>
+ <pt x="x10" y="y4"/>
+ </cubicBezTo>
+ </path>
+ </pathLst>
+ </textWave1>
+ <textWave2>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj1" fmla="val 12500"/>
+ <gd name="adj2" fmla="val 0"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a1" fmla="pin 0 adj1 20000"/>
+ <gd name="a2" fmla="pin -10000 adj2 10000"/>
+ <gd name="y1" fmla="*/ h a1 100000"/>
+ <gd name="dy2" fmla="*/ y1 10 3"/>
+ <gd name="y2" fmla="+- y1 0 dy2"/>
+ <gd name="y3" fmla="+- y1 dy2 0"/>
+ <gd name="y4" fmla="+- b 0 y1"/>
+ <gd name="y5" fmla="+- y4 0 dy2"/>
+ <gd name="y6" fmla="+- y4 dy2 0"/>
+ <gd name="of" fmla="*/ w a2 100000"/>
+ <gd name="of2" fmla="*/ w a2 50000"/>
+ <gd name="x1" fmla="abs of"/>
+ <gd name="dx2" fmla="?: of2 0 of2"/>
+ <gd name="x2" fmla="+- l 0 dx2"/>
+ <gd name="dx5" fmla="?: of2 of2 0"/>
+ <gd name="x5" fmla="+- r 0 dx5"/>
+ <gd name="dx3" fmla="+/ dx2 x5 3"/>
+ <gd name="x3" fmla="+- x2 dx3 0"/>
+ <gd name="x4" fmla="+/ x3 x5 2"/>
+ <gd name="x6" fmla="+- l dx5 0"/>
+ <gd name="x10" fmla="+- r dx2 0"/>
+ <gd name="x7" fmla="+- x6 dx3 0"/>
+ <gd name="x8" fmla="+/ x7 x10 2"/>
+ <gd name="x9" fmla="+- r 0 x1"/>
+ <gd name="xAdj" fmla="+- hc of 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj1" minY="0" maxY="20000">
+ <pos x="l" y="y1"/>
+ </ahXY>
+ <ahXY gdRefX="adj2" minX="-10000" maxX="10000">
+ <pos x="xAdj" y="b"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="x2" y="y1"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x3" y="y3"/>
+ <pt x="x4" y="y2"/>
+ <pt x="x5" y="y1"/>
+ </cubicBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="x6" y="y4"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x7" y="y6"/>
+ <pt x="x8" y="y5"/>
+ <pt x="x10" y="y4"/>
+ </cubicBezTo>
+ </path>
+ </pathLst>
+ </textWave2>
+ <textWave4>
+ <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="adj1" fmla="val 6250"/>
+ <gd name="adj2" fmla="val 0"/>
+ </avLst>
+ <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <gd name="a1" fmla="pin 0 adj1 12500"/>
+ <gd name="a2" fmla="pin -10000 adj2 10000"/>
+ <gd name="y1" fmla="*/ h a1 100000"/>
+ <gd name="dy2" fmla="*/ y1 10 3"/>
+ <gd name="y2" fmla="+- y1 0 dy2"/>
+ <gd name="y3" fmla="+- y1 dy2 0"/>
+ <gd name="y4" fmla="+- b 0 y1"/>
+ <gd name="y5" fmla="+- y4 0 dy2"/>
+ <gd name="y6" fmla="+- y4 dy2 0"/>
+ <gd name="of" fmla="*/ w a2 100000"/>
+ <gd name="of2" fmla="*/ w a2 50000"/>
+ <gd name="x1" fmla="abs of"/>
+ <gd name="dx2" fmla="?: of2 0 of2"/>
+ <gd name="x2" fmla="+- l 0 dx2"/>
+ <gd name="dx8" fmla="?: of2 of2 0"/>
+ <gd name="x8" fmla="+- r 0 dx8"/>
+ <gd name="dx3" fmla="+/ dx2 x8 6"/>
+ <gd name="x3" fmla="+- x2 dx3 0"/>
+ <gd name="dx4" fmla="+/ dx2 x8 3"/>
+ <gd name="x4" fmla="+- x2 dx4 0"/>
+ <gd name="x5" fmla="+/ x2 x8 2"/>
+ <gd name="x6" fmla="+- x5 dx3 0"/>
+ <gd name="x7" fmla="+/ x6 x8 2"/>
+ <gd name="x9" fmla="+- l dx8 0"/>
+ <gd name="x15" fmla="+- r dx2 0"/>
+ <gd name="x10" fmla="+- x9 dx3 0"/>
+ <gd name="x11" fmla="+- x9 dx4 0"/>
+ <gd name="x12" fmla="+/ x9 x15 2"/>
+ <gd name="x13" fmla="+- x12 dx3 0"/>
+ <gd name="x14" fmla="+/ x13 x15 2"/>
+ <gd name="x16" fmla="+- r 0 x1"/>
+ <gd name="xAdj" fmla="+- hc of 0"/>
+ </gdLst>
+ <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <ahXY gdRefY="adj1" minY="0" maxY="12500">
+ <pos x="l" y="y1"/>
+ </ahXY>
+ <ahXY gdRefX="adj2" minX="-10000" maxX="10000">
+ <pos x="xAdj" y="b"/>
+ </ahXY>
+ </ahLst>
+ <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main">
+ <path>
+ <moveTo>
+ <pt x="x2" y="y1"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x3" y="y3"/>
+ <pt x="x4" y="y2"/>
+ <pt x="x5" y="y1"/>
+ </cubicBezTo>
+ <cubicBezTo>
+ <pt x="x6" y="y3"/>
+ <pt x="x7" y="y2"/>
+ <pt x="x8" y="y1"/>
+ </cubicBezTo>
+ </path>
+ <path>
+ <moveTo>
+ <pt x="x9" y="y4"/>
+ </moveTo>
+ <cubicBezTo>
+ <pt x="x10" y="y6"/>
+ <pt x="x11" y="y5"/>
+ <pt x="x12" y="y4"/>
+ </cubicBezTo>
+ <cubicBezTo>
+ <pt x="x13" y="y6"/>
+ <pt x="x14" y="y5"/>
+ <pt x="x15" y="y4"/>
+ </cubicBezTo>
+ </path>
+ </pathLst>
+ </textWave4>
+</presetTextWarpDefinitions>
diff --git a/oox/source/export/shapes.cxx b/oox/source/export/shapes.cxx
new file mode 100644
index 000000000..df7394995
--- /dev/null
+++ b/oox/source/export/shapes.cxx
@@ -0,0 +1,2728 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_wasm_strip.h>
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <filter/msfilter/util.hxx>
+#include <o3tl/string_view.hxx>
+#include <oox/core/xmlfilterbase.hxx>
+#include <oox/export/shapes.hxx>
+#include <oox/export/utils.hxx>
+#include <oox/token/namespaces.hxx>
+#include <oox/token/relationship.hxx>
+#include <oox/token/tokens.hxx>
+
+#include <initializer_list>
+#include <string_view>
+
+#include <com/sun/star/beans/PropertyValues.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/XPropertySetInfo.hpp>
+#include <com/sun/star/beans/XPropertyState.hpp>
+#include <com/sun/star/container/XChild.hpp>
+#include <com/sun/star/document/XExporter.hpp>
+#include <com/sun/star/document/XStorageBasedDocument.hpp>
+#include <com/sun/star/drawing/CircleKind.hpp>
+#include <com/sun/star/drawing/FillStyle.hpp>
+#include <com/sun/star/drawing/ConnectorType.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp>
+#include <com/sun/star/embed/EmbedStates.hpp>
+#include <com/sun/star/embed/XEmbeddedObject.hpp>
+#include <com/sun/star/embed/XEmbedPersist.hpp>
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/io/XOutputStream.hpp>
+#include <com/sun/star/text/XSimpleText.hpp>
+#include <com/sun/star/text/XText.hpp>
+#include <com/sun/star/table/XTable.hpp>
+#include <com/sun/star/table/XMergeableCell.hpp>
+#include <com/sun/star/chart2/XChartDocument.hpp>
+#include <com/sun/star/frame/XModel.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <com/sun/star/drawing/XDrawPages.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/presentation/ClickAction.hpp>
+#include <com/sun/star/drawing/XGluePointsSupplier.hpp>
+#include <com/sun/star/container/XIdentifierAccess.hpp>
+#include <tools/globname.hxx>
+#include <comphelper/classids.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <comphelper/storagehelper.hxx>
+#include <sot/exchange.hxx>
+#include <utility>
+#include <vcl/graph.hxx>
+#include <vcl/outdev.hxx>
+#include <filter/msfilter/escherex.hxx>
+#include <svx/svdoashp.hxx>
+#include <svx/svdoole2.hxx>
+#include <tools/diagnose_ex.h>
+#include <oox/export/chartexport.hxx>
+#include <oox/mathml/export.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <oox/export/DMLPresetShapeExport.hxx>
+
+using namespace ::css;
+using namespace ::css::beans;
+using namespace ::css::uno;
+using namespace ::css::drawing;
+using namespace ::css::i18n;
+using namespace ::css::table;
+using namespace ::css::container;
+using namespace ::css::document;
+using namespace ::css::text;
+
+using ::css::io::XOutputStream;
+using ::css::chart2::XChartDocument;
+using ::css::frame::XModel;
+
+using ::oox::core::XmlFilterBase;
+using ::sax_fastparser::FSHelperPtr;
+
+
+namespace oox {
+
+static void lcl_ConvertProgID(std::u16string_view rProgID,
+ OUString & o_rMediaType, OUString & o_rRelationType, OUString & o_rFileExtension)
+{
+ if (rProgID == u"Excel.Sheet.12")
+ {
+ o_rMediaType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+ o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
+ o_rFileExtension = "xlsx";
+ }
+ else if (o3tl::starts_with(rProgID, u"Excel.SheetBinaryMacroEnabled.12") )
+ {
+ o_rMediaType = "application/vnd.ms-excel.sheet.binary.macroEnabled.12";
+ o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
+ o_rFileExtension = "xlsb";
+ }
+ else if (o3tl::starts_with(rProgID, u"Excel.SheetMacroEnabled.12"))
+ {
+ o_rMediaType = "application/vnd.ms-excel.sheet.macroEnabled.12";
+ o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
+ o_rFileExtension = "xlsm";
+ }
+ else if (o3tl::starts_with(rProgID, u"Excel.Sheet"))
+ {
+ o_rMediaType = "application/vnd.ms-excel";
+ o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
+ o_rFileExtension = "xls";
+ }
+ else if (rProgID == u"PowerPoint.Show.12")
+ {
+ o_rMediaType = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
+ o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
+ o_rFileExtension = "pptx";
+ }
+ else if (rProgID == u"PowerPoint.ShowMacroEnabled.12")
+ {
+ o_rMediaType = "application/vnd.ms-powerpoint.presentation.macroEnabled.12";
+ o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
+ o_rFileExtension = "pptm";
+ }
+ else if (o3tl::starts_with(rProgID, u"PowerPoint.Show"))
+ {
+ o_rMediaType = "application/vnd.ms-powerpoint";
+ o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
+ o_rFileExtension = "ppt";
+ }
+ else if (o3tl::starts_with(rProgID, u"PowerPoint.Slide.12"))
+ {
+ o_rMediaType = "application/vnd.openxmlformats-officedocument.presentationml.slide";
+ o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
+ o_rFileExtension = "sldx";
+ }
+ else if (rProgID == u"PowerPoint.SlideMacroEnabled.12")
+ {
+ o_rMediaType = "application/vnd.ms-powerpoint.slide.macroEnabled.12";
+ o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
+ o_rFileExtension = "sldm";
+ }
+ else if (rProgID == u"Word.DocumentMacroEnabled.12")
+ {
+ o_rMediaType = "application/vnd.ms-word.document.macroEnabled.12";
+ o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
+ o_rFileExtension = "docm";
+ }
+ else if (rProgID == u"Word.Document.12")
+ {
+ o_rMediaType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
+ o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
+ o_rFileExtension = "docx";
+ }
+ else if (rProgID == u"Word.Document.8")
+ {
+ o_rMediaType = "application/msword";
+ o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
+ o_rFileExtension = "doc";
+ }
+ else if (rProgID == u"Excel.Chart.8")
+ {
+ o_rMediaType = "application/vnd.ms-excel";
+ o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
+ o_rFileExtension = "xls";
+ }
+ else if (rProgID == u"AcroExch.Document.11")
+ {
+ o_rMediaType = "application/pdf";
+ o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
+ o_rFileExtension = "pdf";
+ }
+ else
+ {
+ o_rMediaType = "application/vnd.openxmlformats-officedocument.oleObject";
+ o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
+ o_rFileExtension = "bin";
+ }
+}
+
+static uno::Reference<io::XInputStream> lcl_StoreOwnAsOOXML(
+ uno::Reference<uno::XComponentContext> const& xContext,
+ uno::Reference<embed::XEmbeddedObject> const& xObj,
+ char const*& o_rpProgID,
+ OUString & o_rMediaType, OUString & o_rRelationType, OUString & o_rSuffix)
+{
+ static struct {
+ struct {
+ sal_uInt32 n1;
+ sal_uInt16 n2, n3;
+ sal_uInt8 b8, b9, b10, b11, b12, b13, b14, b15;
+ } ClassId;
+ char const* pFilterName;
+ char const* pMediaType;
+ char const* pProgID;
+ char const* pSuffix;
+ } const s_Mapping[] = {
+ { {SO3_SW_CLASSID_60}, "MS Word 2007 XML", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "Word.Document.12", "docx" },
+ { {SO3_SC_CLASSID_60}, "Calc MS Excel 2007 XML", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "Excel.Sheet.12", "xlsx" },
+ { {SO3_SIMPRESS_CLASSID_60}, "Impress MS PowerPoint 2007 XML", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "PowerPoint.Show.12", "pptx" },
+ // FIXME: Draw does not appear to have a MSO format export filter?
+// { {SO3_SDRAW_CLASSID}, "", "", "", "" },
+ { {SO3_SCH_CLASSID_60}, "unused", "", "", "" },
+ { {SO3_SM_CLASSID_60}, "unused", "", "", "" },
+ };
+
+ const char * pFilterName(nullptr);
+ SvGlobalName const classId(xObj->getClassID());
+ for (auto & i : s_Mapping)
+ {
+ auto const& rId(i.ClassId);
+ SvGlobalName const temp(rId.n1, rId.n2, rId.n3, rId.b8, rId.b9, rId.b10, rId.b11, rId.b12, rId.b13, rId.b14, rId.b15);
+ if (temp == classId)
+ {
+ assert(SvGlobalName(SO3_SCH_CLASSID_60) != classId); // chart should be written elsewhere!
+ assert(SvGlobalName(SO3_SM_CLASSID_60) != classId); // formula should be written elsewhere!
+ pFilterName = i.pFilterName;
+ o_rMediaType = OUString::createFromAscii(i.pMediaType);
+ o_rpProgID = i.pProgID;
+ o_rSuffix = OUString::createFromAscii(i.pSuffix);
+ o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
+ break;
+ }
+ }
+
+ if (!pFilterName)
+ {
+ SAL_WARN("oox.shape", "oox::GetOLEObjectStream: unknown ClassId " << classId.GetHexName());
+ return nullptr;
+ }
+
+ if (embed::EmbedStates::LOADED == xObj->getCurrentState())
+ {
+ xObj->changeState(embed::EmbedStates::RUNNING);
+ }
+ // use a temp stream - while it would work to store directly to a
+ // fragment stream, an error during export means we'd have to delete it
+ uno::Reference<io::XStream> const xTempStream(
+ xContext->getServiceManager()->createInstanceWithContext(
+ "com.sun.star.comp.MemoryStream", xContext),
+ uno::UNO_QUERY_THROW);
+ uno::Sequence<beans::PropertyValue> args( comphelper::InitPropertySequence({
+ { "OutputStream", Any(xTempStream->getOutputStream()) },
+ { "FilterName", Any(OUString::createFromAscii(pFilterName)) }
+ }));
+ uno::Reference<frame::XStorable> xStorable(xObj->getComponent(), uno::UNO_QUERY);
+ try
+ {
+ xStorable->storeToURL("private:stream", args);
+ }
+ catch (uno::Exception const&)
+ {
+ TOOLS_WARN_EXCEPTION("oox.shape", "oox::GetOLEObjectStream");
+ return nullptr;
+ }
+ xTempStream->getOutputStream()->closeOutput();
+ return xTempStream->getInputStream();
+}
+
+uno::Reference<io::XInputStream> GetOLEObjectStream(
+ uno::Reference<uno::XComponentContext> const& xContext,
+ uno::Reference<embed::XEmbeddedObject> const& xObj,
+ std::u16string_view i_rProgID,
+ OUString & o_rMediaType,
+ OUString & o_rRelationType,
+ OUString & o_rSuffix,
+ const char *& o_rpProgID)
+{
+ uno::Reference<io::XInputStream> xInStream;
+ try
+ {
+ uno::Reference<document::XStorageBasedDocument> const xParent(
+ uno::Reference<container::XChild>(xObj, uno::UNO_QUERY_THROW)->getParent(),
+ uno::UNO_QUERY_THROW);
+ uno::Reference<embed::XStorage> const xParentStorage(xParent->getDocumentStorage());
+ OUString const entryName(
+ uno::Reference<embed::XEmbedPersist>(xObj, uno::UNO_QUERY_THROW)->getEntryName());
+
+ if (xParentStorage->isStreamElement(entryName))
+ {
+ lcl_ConvertProgID(i_rProgID, o_rMediaType, o_rRelationType, o_rSuffix);
+ xInStream = xParentStorage->cloneStreamElement(entryName)->getInputStream();
+ // TODO: make it possible to take the sMediaType from the stream
+ }
+ else // the object is ODF - either the whole document is
+ { // ODF, or the OLE was edited so it was converted to ODF
+ xInStream = lcl_StoreOwnAsOOXML(xContext, xObj,
+ o_rpProgID, o_rMediaType, o_rRelationType, o_rSuffix);
+ }
+ }
+ catch (uno::Exception const&)
+ {
+ TOOLS_WARN_EXCEPTION("oox.shape", "oox::GetOLEObjectStream");
+ }
+ return xInStream;
+}
+
+} // namespace oox
+
+namespace oox::drawingml {
+
+#define GETA(propName) \
+ GetProperty( rXPropSet, #propName)
+
+#define GETAD(propName) \
+ ( GetPropertyAndState( rXPropSet, rXPropState, #propName, eState ) && eState == beans::PropertyState_DIRECT_VALUE )
+
+#define GET(variable, propName) \
+ if ( GETA(propName) ) \
+ mAny >>= variable;
+
+ShapeExport::ShapeExport( sal_Int32 nXmlNamespace, FSHelperPtr pFS, ShapeHashMap* pShapeMap, XmlFilterBase* pFB, DocumentType eDocumentType, DMLTextExport* pTextExport, bool bUserShapes )
+ : DrawingML( std::move(pFS), pFB, eDocumentType, pTextExport )
+ , m_nEmbeddedObjects(0)
+ , mnShapeIdMax( 1 )
+ , mbUserShapes( bUserShapes )
+ , mnXmlNamespace( nXmlNamespace )
+ , maMapModeSrc( MapUnit::Map100thMM )
+ , maMapModeDest( MapUnit::MapInch, Point(), Fraction( 1, 576 ), Fraction( 1, 576 ) )
+ , mpShapeMap( pShapeMap ? pShapeMap : &maShapeMap )
+{
+ mpURLTransformer = std::make_shared<URLTransformer>();
+}
+
+void ShapeExport::SetURLTranslator(const std::shared_ptr<URLTransformer>& pTransformer)
+{
+ mpURLTransformer = pTransformer;
+}
+
+awt::Size ShapeExport::MapSize( const awt::Size& rSize ) const
+{
+ Size aRetSize( OutputDevice::LogicToLogic( Size( rSize.Width, rSize.Height ), maMapModeSrc, maMapModeDest ) );
+
+ if ( !aRetSize.Width() )
+ aRetSize.AdjustWidth( 1 );
+ if ( !aRetSize.Height() )
+ aRetSize.AdjustHeight( 1 );
+ return awt::Size( aRetSize.Width(), aRetSize.Height() );
+}
+
+static bool IsNonEmptySimpleText(const Reference<XInterface>& xIface)
+{
+ if (Reference<XSimpleText> xText{ xIface, UNO_QUERY })
+ return xText->getString().getLength();
+
+ return false;
+}
+
+bool ShapeExport::NonEmptyText( const Reference< XInterface >& xIface )
+{
+ Reference< XPropertySet > xPropSet( xIface, UNO_QUERY );
+
+ if( xPropSet.is() )
+ {
+ Reference< XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
+ if ( xPropSetInfo.is() )
+ {
+ if ( xPropSetInfo->hasPropertyByName( "IsEmptyPresentationObject" ) )
+ {
+ bool bIsEmptyPresObj = false;
+ if ( xPropSet->getPropertyValue( "IsEmptyPresentationObject" ) >>= bIsEmptyPresObj )
+ {
+ SAL_INFO("oox.shape", "empty presentation object " << bIsEmptyPresObj << " , props:");
+ if( bIsEmptyPresObj )
+ return true;
+ }
+ }
+
+ if ( xPropSetInfo->hasPropertyByName( "IsPresentationObject" ) )
+ {
+ bool bIsPresObj = false;
+ if ( xPropSet->getPropertyValue( "IsPresentationObject" ) >>= bIsPresObj )
+ {
+ SAL_INFO("oox.shape", "presentation object " << bIsPresObj << ", props:");
+ if( bIsPresObj )
+ return true;
+ }
+ }
+ }
+ }
+
+ return IsNonEmptySimpleText(xIface);
+}
+
+ShapeExport& ShapeExport::WritePolyPolygonShape( const Reference< XShape >& xShape, const bool bClosed )
+{
+ SAL_INFO("oox.shape", "write polypolygon shape");
+
+ FSHelperPtr pFS = GetFS();
+ pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp));
+
+ awt::Point aPos = xShape->getPosition();
+ // Position is relative to group for child elements in Word, but absolute in API.
+ if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes && m_xParent.is())
+ {
+ awt::Point aParentPos = m_xParent->getPosition();
+ aPos.X -= aParentPos.X;
+ aPos.Y -= aParentPos.Y;
+ }
+ awt::Size aSize = xShape->getSize();
+ tools::Rectangle aRect(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height));
+
+#if OSL_DEBUG_LEVEL > 0
+ tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(xShape);
+ awt::Size size = MapSize( awt::Size( aRect.GetWidth(), aRect.GetHeight() ) );
+ SAL_INFO("oox.shape", "poly count " << aPolyPolygon.Count());
+ SAL_INFO("oox.shape", "size: " << size.Width << " x " << size.Height);
+#endif
+
+ // non visual shape properties
+ if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
+ {
+ pFS->startElementNS(mnXmlNamespace, XML_nvSpPr);
+ pFS->singleElementNS( mnXmlNamespace, XML_cNvPr,
+ XML_id, OString::number(GetNewShapeID(xShape)),
+ XML_name, GetShapeName(xShape));
+ }
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);
+ if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
+ {
+ WriteNonVisualProperties( xShape );
+ pFS->endElementNS( mnXmlNamespace, XML_nvSpPr );
+ }
+
+ // visual shape properties
+ pFS->startElementNS(mnXmlNamespace, XML_spPr);
+ WriteTransformation( xShape, aRect, XML_a );
+ WritePolyPolygon(xShape, bClosed);
+ Reference< XPropertySet > xProps( xShape, UNO_QUERY );
+ if( xProps.is() ) {
+ if( bClosed )
+ WriteFill( xProps );
+ WriteOutline( xProps );
+ }
+
+ pFS->endElementNS( mnXmlNamespace, XML_spPr );
+
+ // write text
+ WriteTextBox( xShape, mnXmlNamespace );
+
+ pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) );
+
+ return *this;
+}
+
+ShapeExport& ShapeExport::WriteClosedPolyPolygonShape( const Reference< XShape >& xShape )
+{
+ return WritePolyPolygonShape( xShape, true );
+}
+
+ShapeExport& ShapeExport::WriteOpenPolyPolygonShape( const Reference< XShape >& xShape )
+{
+ return WritePolyPolygonShape( xShape, false );
+}
+
+ShapeExport& ShapeExport::WriteGroupShape(const uno::Reference<drawing::XShape>& xShape)
+{
+ FSHelperPtr pFS = GetFS();
+
+ sal_Int32 nGroupShapeToken = XML_grpSp;
+ if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes)
+ {
+ if (!m_xParent.is())
+ nGroupShapeToken = XML_wgp; // toplevel
+ else
+ mnXmlNamespace = XML_wpg;
+ }
+
+ pFS->startElementNS(mnXmlNamespace, nGroupShapeToken);
+
+ // non visual properties
+ if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
+ {
+ pFS->startElementNS(mnXmlNamespace, XML_nvGrpSpPr);
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvPr,
+ XML_id, OString::number(GetNewShapeID(xShape)),
+ XML_name, GetShapeName(xShape));
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvGrpSpPr);
+ WriteNonVisualProperties(xShape );
+ pFS->endElementNS(mnXmlNamespace, XML_nvGrpSpPr);
+ }
+ else
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvGrpSpPr);
+
+ // visual properties
+ pFS->startElementNS(mnXmlNamespace, XML_grpSpPr);
+ WriteShapeTransformation(xShape, XML_a, false, false, true);
+ pFS->endElementNS(mnXmlNamespace, XML_grpSpPr);
+
+ uno::Reference<drawing::XShapes> xGroupShape(xShape, uno::UNO_QUERY_THROW);
+ uno::Reference<drawing::XShape> xParent = m_xParent;
+ m_xParent = xShape;
+ for (sal_Int32 i = 0; i < xGroupShape->getCount(); ++i)
+ {
+ uno::Reference<drawing::XShape> xChild(xGroupShape->getByIndex(i), uno::UNO_QUERY_THROW);
+ sal_Int32 nSavedNamespace = mnXmlNamespace;
+
+ uno::Reference<lang::XServiceInfo> xServiceInfo(xChild, uno::UNO_QUERY_THROW);
+ if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes)
+ {
+ // tdf#128820: WriteGraphicObjectShapePart calls WriteTextShape for non-empty simple
+ // text objects, which needs writing into wps::wsp element, so make sure to use wps
+ // namespace for those objects
+ if (xServiceInfo->supportsService("com.sun.star.drawing.GraphicObjectShape")
+ && !IsNonEmptySimpleText(xChild))
+ mnXmlNamespace = XML_pic;
+ else
+ mnXmlNamespace = XML_wps;
+ }
+ WriteShape(xChild);
+
+ mnXmlNamespace = nSavedNamespace;
+ }
+ m_xParent = xParent;
+
+ pFS->endElementNS(mnXmlNamespace, nGroupShapeToken);
+ return *this;
+}
+
+static bool lcl_IsOnDenylist(OUString const & rShapeType)
+{
+ static const std::initializer_list<std::u16string_view> vDenylist = {
+ u"block-arc",
+ u"rectangle",
+ u"ellipse",
+ u"ring",
+ u"can",
+ u"cube",
+ u"paper",
+ u"frame",
+ u"forbidden",
+ u"smiley",
+ u"sun",
+ u"flower",
+ u"bracket-pair",
+ u"brace-pair",
+ u"quad-bevel",
+ u"round-rectangular-callout",
+ u"rectangular-callout",
+ u"round-callout",
+ u"cloud-callout",
+ u"line-callout-1",
+ u"line-callout-2",
+ u"line-callout-3",
+ u"paper",
+ u"vertical-scroll",
+ u"horizontal-scroll",
+ u"mso-spt34",
+ u"mso-spt75",
+ u"mso-spt164",
+ u"mso-spt180",
+ u"flowchart-process",
+ u"flowchart-alternate-process",
+ u"flowchart-decision",
+ u"flowchart-data",
+ u"flowchart-predefined-process",
+ u"flowchart-internal-storage",
+ u"flowchart-document",
+ u"flowchart-multidocument",
+ u"flowchart-terminator",
+ u"flowchart-preparation",
+ u"flowchart-manual-input",
+ u"flowchart-manual-operation",
+ u"flowchart-connector",
+ u"flowchart-off-page-connector",
+ u"flowchart-card",
+ u"flowchart-punched-tape",
+ u"flowchart-summing-junction",
+ u"flowchart-or",
+ u"flowchart-collate",
+ u"flowchart-sort",
+ u"flowchart-extract",
+ u"flowchart-merge",
+ u"flowchart-stored-data",
+ u"flowchart-delay",
+ u"flowchart-sequential-access",
+ u"flowchart-magnetic-disk",
+ u"flowchart-direct-access-storage",
+ u"flowchart-display"
+ };
+
+ return std::find(vDenylist.begin(), vDenylist.end(), rShapeType) != vDenylist.end();
+}
+
+static bool lcl_IsOnAllowlist(OUString const & rShapeType)
+{
+ static const std::initializer_list<std::u16string_view> vAllowlist = {
+ u"heart",
+ u"puzzle",
+ u"col-60da8460",
+ u"col-502ad400"
+ };
+
+ return std::find(vAllowlist.begin(), vAllowlist.end(), rShapeType) != vAllowlist.end();
+}
+
+static bool lcl_GetHandlePosition( sal_Int32 &nValue, const EnhancedCustomShapeParameter &rParam, const Sequence< EnhancedCustomShapeAdjustmentValue > &rSeq)
+{
+ bool bAdj = false;
+ if ( rParam.Value.getValueTypeClass() == TypeClass_DOUBLE )
+ {
+ double fValue(0.0);
+ if ( rParam.Value >>= fValue )
+ nValue = static_cast<sal_Int32>(fValue);
+ }
+ else
+ rParam.Value >>= nValue;
+
+ if ( rParam.Type == EnhancedCustomShapeParameterType::ADJUSTMENT)
+ {
+ bAdj = true;
+ sal_Int32 nIdx = nValue;
+ if ( nIdx < rSeq.getLength() )
+ {
+ if ( rSeq[ nIdx ] .Value.getValueTypeClass() == TypeClass_DOUBLE )
+ {
+ double fValue(0.0);
+ rSeq[ nIdx ].Value >>= fValue;
+ nValue = fValue;
+
+ }
+ else
+ {
+ rSeq[ nIdx ].Value >>= nValue;
+ }
+ }
+ }
+ return bAdj;
+}
+
+static void lcl_AnalyzeHandles( const uno::Sequence<beans::PropertyValues> & rHandles,
+ std::vector< std::pair< sal_Int32, sal_Int32> > &rHandlePositionList,
+ const Sequence< EnhancedCustomShapeAdjustmentValue > &rSeq)
+{
+ for ( const Sequence< PropertyValue >& rPropSeq : rHandles )
+ {
+ static const OUStringLiteral sPosition( u"Position" );
+ bool bPosition = false;
+ EnhancedCustomShapeParameterPair aPosition;
+ for ( const PropertyValue& rPropVal: rPropSeq )
+ {
+ if ( rPropVal.Name == sPosition )
+ {
+ if ( rPropVal.Value >>= aPosition )
+ bPosition = true;
+ }
+ }
+ if ( bPosition )
+ {
+ sal_Int32 nXPosition = 0;
+ sal_Int32 nYPosition = 0;
+ // For polar handles, nXPosition is radius and nYPosition is angle
+ lcl_GetHandlePosition( nXPosition, aPosition.First , rSeq );
+ lcl_GetHandlePosition( nYPosition, aPosition.Second, rSeq );
+ rHandlePositionList.emplace_back( nXPosition, nYPosition );
+ }
+ }
+}
+
+static void lcl_AppendAdjustmentValue( std::vector< std::pair< sal_Int32, sal_Int32> > &rAvList, sal_Int32 nAdjIdx, sal_Int32 nValue )
+{
+ rAvList.emplace_back( nAdjIdx , nValue );
+}
+
+static sal_Int32 lcl_NormalizeAngle( sal_Int32 nAngle )
+{
+ nAngle = nAngle % 360;
+ return nAngle < 0 ? ( nAngle + 360 ) : nAngle ;
+}
+
+static sal_Int32 lcl_CircleAngle2CustomShapeEllipseAngleOOX(const sal_Int32 nInternAngle, const sal_Int32 nWidth, const sal_Int32 nHeight)
+{
+ if (nWidth != 0 || nHeight != 0)
+ {
+ double fAngle = basegfx::deg2rad<100>(nInternAngle); // intern 1/100 deg to rad
+ fAngle = atan2(nHeight * sin(fAngle), nWidth * cos(fAngle)); // circle to ellipse
+ fAngle = basegfx::rad2deg<60000>(fAngle); // rad to OOXML angle unit
+ sal_Int32 nAngle = basegfx::fround(fAngle); // normalize
+ nAngle = nAngle % 21600000;
+ return nAngle < 0 ? (nAngle + 21600000) : nAngle;
+ }
+ else // should be handled by caller, dummy value
+ return 0;
+}
+
+static OUString lcl_GetTarget(const css::uno::Reference<css::frame::XModel>& xModel,
+ std::u16string_view rURL)
+{
+ Reference<drawing::XDrawPagesSupplier> xDPS(xModel, uno::UNO_QUERY_THROW);
+ Reference<drawing::XDrawPages> xDrawPages(xDPS->getDrawPages(), uno::UNO_SET_THROW);
+ sal_uInt32 nPageCount = xDrawPages->getCount();
+ OUString sTarget;
+
+ for (sal_uInt32 i = 0; i < nPageCount; ++i)
+ {
+ Reference<XDrawPage> xDrawPage;
+ xDrawPages->getByIndex(i) >>= xDrawPage;
+ Reference<container::XNamed> xNamed(xDrawPage, UNO_QUERY);
+ if (!xNamed)
+ continue;
+ OUString sSlideName = "#" + xNamed->getName();
+ if (rURL == sSlideName)
+ {
+ sTarget = "slide" + OUString::number(i + 1) + ".xml";
+ break;
+ }
+ }
+
+ return sTarget;
+}
+
+ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape )
+{
+ SAL_INFO("oox.shape", "write custom shape");
+ Reference< XPropertySet > rXPropSet( xShape, UNO_QUERY );
+ // First check, if this is a Fontwork-shape. For DrawingML, such a shape is a
+ // TextBox shape with body property prstTxWarp.
+ if (IsFontworkShape(rXPropSet))
+ {
+ ShapeExport::WriteTextShape(xShape); // qualifier to prevent PowerPointShapeExport
+ return *this;
+ }
+
+ bool bHasGeometrySeq(false);
+ Sequence< PropertyValue > aGeometrySeq;
+ OUString sShapeType("non-primitive"); // default in ODF
+ if (GETA(CustomShapeGeometry))
+ {
+ SAL_INFO("oox.shape", "got custom shape geometry");
+ if (mAny >>= aGeometrySeq)
+ {
+ bHasGeometrySeq = true;
+ SAL_INFO("oox.shape", "got custom shape geometry sequence");
+ for (const PropertyValue& rProp : std::as_const(aGeometrySeq))
+ {
+ SAL_INFO("oox.shape", "geometry property: " << rProp.Name);
+ if (rProp.Name == "Type")
+ rProp.Value >>= sShapeType;
+ }
+ }
+ }
+
+ bool bPredefinedHandlesUsed = true;
+ bool bHasHandles = false;
+
+ ShapeFlag nMirrorFlags = ShapeFlag::NONE;
+ MSO_SPT eShapeType = EscherPropertyContainer::GetCustomShapeType( xShape, nMirrorFlags, sShapeType );
+ assert(dynamic_cast< SdrObjCustomShape* >(SdrObject::getSdrObjectFromXShape(xShape)) && "Not a SdrObjCustomShape (!)");
+ SdrObjCustomShape& rSdrObjCustomShape(static_cast< SdrObjCustomShape& >(*SdrObject::getSdrObjectFromXShape(xShape)));
+ const bool bIsDefaultObject(
+ EscherPropertyContainer::IsDefaultObject(
+ rSdrObjCustomShape,
+ eShapeType));
+ OString sPresetShape = msfilter::util::GetOOXMLPresetGeometry(sShapeType);
+ SAL_INFO("oox.shape", "custom shape type: " << sShapeType << " ==> " << sPresetShape);
+
+ sal_Int32 nAdjustmentValuesIndex = -1;
+ awt::Rectangle aViewBox;
+ uno::Sequence<beans::PropertyValues> aHandles;
+
+ bool bFlipH = false;
+ bool bFlipV = false;
+
+ if (bHasGeometrySeq)
+ {
+ for (int i = 0; i < aGeometrySeq.getLength(); i++)
+ {
+ const PropertyValue& rProp = aGeometrySeq[ i ];
+ SAL_INFO("oox.shape", "geometry property: " << rProp.Name);
+
+ if ( rProp.Name == "MirroredX" )
+ rProp.Value >>= bFlipH;
+
+ if ( rProp.Name == "MirroredY" )
+ rProp.Value >>= bFlipV;
+ if ( rProp.Name == "AdjustmentValues" )
+ nAdjustmentValuesIndex = i;
+ else if ( rProp.Name == "Handles" )
+ {
+ rProp.Value >>= aHandles;
+ if ( aHandles.hasElements() )
+ bHasHandles = true;
+ if( !bIsDefaultObject )
+ bPredefinedHandlesUsed = false;
+ // TODO: update nAdjustmentsWhichNeedsToBeConverted here
+ }
+ else if ( rProp.Name == "ViewBox" )
+ rProp.Value >>= aViewBox;
+ }
+ }
+
+ FSHelperPtr pFS = GetFS();
+ // non visual shape properties
+ if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
+ {
+ bool bUseBackground = false;
+ if (GETA(FillUseSlideBackground))
+ mAny >>= bUseBackground;
+ if (bUseBackground)
+ mpFS->startElementNS(mnXmlNamespace, XML_sp, XML_useBgFill, "1");
+ else
+ mpFS->startElementNS(mnXmlNamespace, XML_sp);
+
+ bool isVisible = true ;
+ if( GETA (Visible))
+ {
+ mAny >>= isVisible;
+ }
+ pFS->startElementNS( mnXmlNamespace, XML_nvSpPr );
+ pFS->startElementNS(
+ mnXmlNamespace, XML_cNvPr, XML_id,
+ OString::number(GetShapeID(xShape) == -1 ? GetNewShapeID(xShape) : GetShapeID(xShape)),
+ XML_name, GetShapeName(xShape), XML_hidden, sax_fastparser::UseIf("1", !isVisible));
+
+ if( GETA( URL ) )
+ {
+ OUString sURL;
+ mAny >>= sURL;
+ if( !sURL.isEmpty() )
+ {
+ OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(),
+ oox::getRelationship(Relationship::HYPERLINK),
+ mpURLTransformer->getTransformedString(sURL),
+ mpURLTransformer->isExternalURL(sURL));
+
+ mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
+ }
+ }
+
+ OUString sBookmark;
+ if (GETA(Bookmark))
+ mAny >>= sBookmark;
+
+ if (GETA(OnClick))
+ {
+ OUString sPPAction;
+ presentation::ClickAction eClickAction = presentation::ClickAction_NONE;
+ mAny >>= eClickAction;
+ if (eClickAction != presentation::ClickAction_NONE)
+ {
+ switch (eClickAction)
+ {
+ case presentation::ClickAction_STOPPRESENTATION:
+ sPPAction = "ppaction://hlinkshowjump?jump=endshow";
+ break;
+ case presentation::ClickAction_NEXTPAGE:
+ sPPAction = "ppaction://hlinkshowjump?jump=nextslide";
+ break;
+ case presentation::ClickAction_LASTPAGE:
+ sPPAction = "ppaction://hlinkshowjump?jump=lastslide";
+ break;
+ case presentation::ClickAction_PREVPAGE:
+ sPPAction = "ppaction://hlinkshowjump?jump=previousslide";
+ break;
+ case presentation::ClickAction_FIRSTPAGE:
+ sPPAction = "ppaction://hlinkshowjump?jump=firstslide";
+ break;
+ case presentation::ClickAction_BOOKMARK:
+ sBookmark = "#" + sBookmark;
+ break;
+ default:
+ break;
+ }
+ }
+ if (!sPPAction.isEmpty())
+ pFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action,
+ sPPAction);
+ }
+ if (!sBookmark.isEmpty())
+ {
+ bool bExtURL = URLTransformer().isExternalURL(sBookmark);
+ sBookmark = bExtURL ? sBookmark : lcl_GetTarget(GetFB()->getModel(), sBookmark);
+
+ OUString sRelId
+ = mpFB->addRelation(mpFS->getOutputStream(),
+ bExtURL ? oox::getRelationship(Relationship::HYPERLINK)
+ : oox::getRelationship(Relationship::SLIDE),
+ sBookmark, bExtURL);
+ if (bExtURL)
+ mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
+ else
+ mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId,
+ XML_action, "ppaction://hlinksldjump");
+ }
+ pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);
+ WriteNonVisualProperties( xShape );
+ pFS->endElementNS( mnXmlNamespace, XML_nvSpPr );
+ }
+ else
+ {
+ pFS->startElementNS(mnXmlNamespace, XML_wsp);
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);
+ }
+
+ // visual shape properties
+ pFS->startElementNS(mnXmlNamespace, XML_spPr);
+
+ // we export non-primitive shapes to custom geometry
+ // we also export non-ooxml shapes which have handles/equations to custom geometry, because
+ // we cannot convert ODF equations to DrawingML equations. TODO: see what binary DOC export filter does.
+ // but our WritePolyPolygon()/WriteCustomGeometry() functions are incomplete, therefore we use a denylist
+ // we use a allowlist for shapes where mapping to MSO preset shape is not optimal
+ bool bCustGeom = true;
+ bool bOnDenylist = false;
+ if( sShapeType == "ooxml-non-primitive" )
+ bCustGeom = true;
+ else if( sShapeType.startsWith("ooxml") )
+ bCustGeom = false;
+ else if( lcl_IsOnAllowlist(sShapeType) )
+ bCustGeom = true;
+ else if( lcl_IsOnDenylist(sShapeType) )
+ {
+ bCustGeom = false;
+ bOnDenylist = true;
+ }
+
+ bool bPresetWriteSuccessful = false;
+ // Let the custom shapes what has name and preset information in OOXML, to be written
+ // as preset ones with parameters. Try that with this converter class.
+ if (!sShapeType.startsWith("ooxml") && sShapeType != "non-primitive" && !mbUserShapes
+ && xShape->getShapeType() == "com.sun.star.drawing.CustomShape"
+ && !lcl_IsOnAllowlist(sShapeType))
+ {
+ DMLPresetShapeExporter aCustomShapeConverter(this, xShape);
+ bPresetWriteSuccessful = aCustomShapeConverter.WriteShape();
+ }
+ // If preset writing has problems try to write the shape as it done before
+ if (bPresetWriteSuccessful)
+ ;// Already written do nothing.
+ else if (bCustGeom)
+ {
+ WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV );
+ bool bSuccess = WriteCustomGeometry(xShape, rSdrObjCustomShape);
+ // In case of Writer, the parent element is <wps:spPr>, and there the <a:custGeom> element
+ // is not optional.
+ if (!bSuccess && GetDocumentType() == DOCUMENT_DOCX)
+ {
+ WriteEmptyCustomGeometry();
+ }
+ }
+ else if (bOnDenylist && bHasHandles && nAdjustmentValuesIndex !=-1 && !sShapeType.startsWith("mso-spt"))
+ {
+ WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV );
+ Sequence< EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq;
+ std::vector< std::pair< sal_Int32, sal_Int32> > aHandlePositionList;
+ std::vector< std::pair< sal_Int32, sal_Int32> > aAvList;
+ aGeometrySeq[ nAdjustmentValuesIndex ].Value >>= aAdjustmentSeq ;
+
+ lcl_AnalyzeHandles( aHandles, aHandlePositionList, aAdjustmentSeq );
+
+ sal_Int32 nXPosition = 0;
+ sal_Int32 nYPosition = 0;
+ if ( !aHandlePositionList.empty() )
+ {
+ nXPosition = aHandlePositionList[0].first ;
+ nYPosition = aHandlePositionList[0].second ;
+ }
+ switch( eShapeType )
+ {
+ case mso_sptBorderCallout1:
+ {
+ sal_Int32 adj3 = double(nYPosition)/aViewBox.Height *100000;
+ sal_Int32 adj4 = double(nXPosition)/aViewBox.Width *100000;
+ lcl_AppendAdjustmentValue( aAvList, 1, 18750 );
+ lcl_AppendAdjustmentValue( aAvList, 2, -8333 );
+ lcl_AppendAdjustmentValue( aAvList, 3, adj3 );
+ lcl_AppendAdjustmentValue( aAvList, 4, adj4 );
+ break;
+ }
+ case mso_sptBorderCallout2:
+ {
+ sal_Int32 adj5 = double(nYPosition)/aViewBox.Height *100000;
+ sal_Int32 adj6 = double(nXPosition)/aViewBox.Width *100000;
+ sal_Int32 adj3 = 18750;
+ sal_Int32 adj4 = -16667;
+ lcl_AppendAdjustmentValue( aAvList, 1, 18750 );
+ lcl_AppendAdjustmentValue( aAvList, 2, -8333 );
+ if ( aHandlePositionList.size() > 1 )
+ {
+ nXPosition = aHandlePositionList[1].first ;
+ nYPosition = aHandlePositionList[1].second ;
+ adj3 = double(nYPosition)/aViewBox.Height *100000;
+ adj4 = double(nXPosition)/aViewBox.Width *100000;
+ }
+ lcl_AppendAdjustmentValue( aAvList, 3, adj3 );
+ lcl_AppendAdjustmentValue( aAvList, 4, adj4 );
+ lcl_AppendAdjustmentValue( aAvList, 5, adj5 );
+ lcl_AppendAdjustmentValue( aAvList, 6, adj6 );
+ break;
+ }
+ case mso_sptWedgeRectCallout:
+ case mso_sptWedgeRRectCallout:
+ case mso_sptWedgeEllipseCallout:
+ case mso_sptCloudCallout:
+ {
+ sal_Int32 adj1 = (double(nXPosition)/aViewBox.Width -0.5) *100000;
+ sal_Int32 adj2 = (double(nYPosition)/aViewBox.Height -0.5) *100000;
+ lcl_AppendAdjustmentValue( aAvList, 1, adj1 );
+ lcl_AppendAdjustmentValue( aAvList, 2, adj2 );
+ if ( eShapeType == mso_sptWedgeRRectCallout)
+ {
+ lcl_AppendAdjustmentValue( aAvList, 3, 16667);
+ }
+
+ break;
+ }
+ case mso_sptFoldedCorner:
+ {
+ sal_Int32 adj = double( aViewBox.Width - nXPosition) / std::min( aViewBox.Width,aViewBox.Height ) * 100000;
+ lcl_AppendAdjustmentValue( aAvList, 0, adj );
+ break;
+ }
+ case mso_sptDonut:
+ case mso_sptSun:
+ case mso_sptMoon:
+ case mso_sptNoSmoking:
+ case mso_sptHorizontalScroll:
+ case mso_sptBevel:
+ case mso_sptBracketPair:
+ {
+ sal_Int32 adj = double( nXPosition )/aViewBox.Width*100000 ;
+ lcl_AppendAdjustmentValue( aAvList, 0, adj );
+ break;
+ }
+ case mso_sptCan:
+ case mso_sptCube:
+ case mso_sptBracePair:
+ case mso_sptVerticalScroll:
+ {
+ sal_Int32 adj = double( nYPosition )/aViewBox.Height *100000 ;
+ lcl_AppendAdjustmentValue( aAvList, 0, adj );
+ break;
+ }
+ case mso_sptSmileyFace:
+ {
+ sal_Int32 adj = double( nYPosition )/aViewBox.Height *100000 - 76458.0;
+ lcl_AppendAdjustmentValue( aAvList, 0, adj );
+ break;
+ }
+ case mso_sptBlockArc:
+ {
+ sal_Int32 nRadius = 50000 * ( 1 - double(nXPosition) / 10800);
+ sal_Int32 nAngleStart = lcl_NormalizeAngle( nYPosition );
+ sal_Int32 nAngleEnd = lcl_NormalizeAngle( 180 - nAngleStart );
+ lcl_AppendAdjustmentValue( aAvList, 1, 21600000 / 360 * nAngleStart );
+ lcl_AppendAdjustmentValue( aAvList, 2, 21600000 / 360 * nAngleEnd );
+ lcl_AppendAdjustmentValue( aAvList, 3, nRadius );
+ break;
+ }
+ // case mso_sptNil:
+ // case mso_sptBentConnector3:
+ // case mso_sptBorderCallout3:
+ default:
+ {
+ if ( sPresetShape == "frame" )
+ {
+ sal_Int32 adj1 = double( nYPosition )/aViewBox.Height *100000 ;
+ lcl_AppendAdjustmentValue( aAvList, 1, adj1 );
+ }
+ break;
+ }
+ }
+ WritePresetShape( sPresetShape , aAvList );
+ }
+ else // preset geometry
+ {
+ WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV );
+ if( nAdjustmentValuesIndex != -1 )
+ {
+ WritePresetShape( sPresetShape, eShapeType, bPredefinedHandlesUsed,
+ aGeometrySeq[ nAdjustmentValuesIndex ] );
+ }
+ else
+ WritePresetShape( sPresetShape );
+ }
+ if( rXPropSet.is() )
+ {
+ WriteFill( rXPropSet );
+ WriteOutline( rXPropSet );
+ WriteShapeEffects( rXPropSet );
+
+ bool bHas3DEffectinShape = false;
+ uno::Sequence<beans::PropertyValue> grabBag;
+ rXPropSet->getPropertyValue("InteropGrabBag") >>= grabBag;
+
+ for (auto const& it : std::as_const(grabBag))
+ if (it.Name == "3DEffectProperties")
+ bHas3DEffectinShape = true;
+
+ if( bHas3DEffectinShape)
+ Write3DEffects( rXPropSet, /*bIsText=*/false );
+ }
+
+ pFS->endElementNS( mnXmlNamespace, XML_spPr );
+
+ pFS->startElementNS(mnXmlNamespace, XML_style);
+ WriteShapeStyle( rXPropSet );
+ pFS->endElementNS( mnXmlNamespace, XML_style );
+
+ // write text
+ WriteTextBox( xShape, mnXmlNamespace );
+
+ pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) );
+
+ return *this;
+}
+
+ShapeExport& ShapeExport::WriteEllipseShape( const Reference< XShape >& xShape )
+{
+ SAL_INFO("oox.shape", "write ellipse shape");
+
+ FSHelperPtr pFS = GetFS();
+
+ pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp));
+
+ // TODO: connector ?
+
+ // non visual shape properties
+ if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
+ {
+ pFS->startElementNS(mnXmlNamespace, XML_nvSpPr);
+ pFS->singleElementNS( mnXmlNamespace, XML_cNvPr,
+ XML_id, OString::number(GetNewShapeID(xShape)),
+ XML_name, GetShapeName(xShape));
+ pFS->singleElementNS( mnXmlNamespace, XML_cNvSpPr );
+ WriteNonVisualProperties( xShape );
+ pFS->endElementNS( mnXmlNamespace, XML_nvSpPr );
+ }
+ else
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);
+
+ Reference< XPropertySet > xProps( xShape, UNO_QUERY );
+ CircleKind eCircleKind(CircleKind_FULL);
+ if (xProps.is())
+ xProps->getPropertyValue("CircleKind" ) >>= eCircleKind;
+
+ // visual shape properties
+ pFS->startElementNS( mnXmlNamespace, XML_spPr );
+ WriteShapeTransformation( xShape, XML_a );
+
+ if (CircleKind_FULL == eCircleKind)
+ WritePresetShape("ellipse");
+ else
+ {
+ sal_Int32 nStartAngleIntern(9000);
+ sal_Int32 nEndAngleIntern(0);
+ if (xProps.is())
+ {
+ xProps->getPropertyValue("CircleStartAngle" ) >>= nStartAngleIntern;
+ xProps->getPropertyValue("CircleEndAngle") >>= nEndAngleIntern;
+ }
+ std::vector< std::pair<sal_Int32,sal_Int32>> aAvList;
+ awt::Size aSize = xShape->getSize();
+ if (aSize.Width != 0 || aSize.Height != 0)
+ {
+ // Our arc has 90° up, OOXML has 90° down, so mirror it.
+ // API angles are 1/100 degree.
+ sal_Int32 nStartAngleOOXML(lcl_CircleAngle2CustomShapeEllipseAngleOOX(36000 - nEndAngleIntern, aSize.Width, aSize.Height));
+ sal_Int32 nEndAngleOOXML(lcl_CircleAngle2CustomShapeEllipseAngleOOX(36000 - nStartAngleIntern, aSize.Width, aSize.Height));
+ lcl_AppendAdjustmentValue( aAvList, 1, nStartAngleOOXML);
+ lcl_AppendAdjustmentValue( aAvList, 2, nEndAngleOOXML);
+ }
+ switch (eCircleKind)
+ {
+ case CircleKind_ARC :
+ WritePresetShape("arc", aAvList);
+ break;
+ case CircleKind_SECTION :
+ WritePresetShape("pie", aAvList);
+ break;
+ case CircleKind_CUT :
+ WritePresetShape("chord", aAvList);
+ break;
+ default :
+ WritePresetShape("ellipse");
+ }
+ }
+ if( xProps.is() )
+ {
+ if (CircleKind_ARC == eCircleKind)
+ {
+ // An arc in ODF is never filled, even if a fill style other than
+ // "none" is set. OOXML arc can be filled, so set fill explicit to
+ // NONE, otherwise some hidden or inherited filling is shown.
+ FillStyle eFillStyle(FillStyle_NONE);
+ uno::Any aNewValue;
+ aNewValue <<= eFillStyle;
+ xProps->setPropertyValue("FillStyle", aNewValue);
+ }
+ WriteFill( xProps );
+ WriteOutline( xProps );
+ }
+ pFS->endElementNS( mnXmlNamespace, XML_spPr );
+
+ // write text
+ WriteTextBox( xShape, mnXmlNamespace );
+
+ pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) );
+
+ return *this;
+}
+
+ShapeExport& ShapeExport::WriteGraphicObjectShape( const Reference< XShape >& xShape )
+{
+ WriteGraphicObjectShapePart( xShape );
+
+ return *this;
+}
+
+void ShapeExport::WriteGraphicObjectShapePart( const Reference< XShape >& xShape, const Graphic* pGraphic )
+{
+ SAL_INFO("oox.shape", "write graphic object shape");
+
+ if (IsNonEmptySimpleText(xShape))
+ {
+ SAL_INFO("oox.shape", "graphicObject: wrote only text");
+
+ WriteTextShape(xShape);
+
+ return;
+ }
+
+ SAL_INFO("oox.shape", "graphicObject without text");
+
+ uno::Reference<graphic::XGraphic> xGraphic;
+ OUString sMediaURL;
+
+ Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY );
+
+ if (pGraphic)
+ {
+ xGraphic.set(pGraphic->GetXGraphic());
+ }
+ else if (xShapeProps.is() && xShapeProps->getPropertySetInfo()->hasPropertyByName("Graphic"))
+ {
+ xShapeProps->getPropertyValue("Graphic") >>= xGraphic;
+ }
+
+ bool bHasMediaURL = xShapeProps.is() && xShapeProps->getPropertySetInfo()->hasPropertyByName("MediaURL") && (xShapeProps->getPropertyValue("MediaURL") >>= sMediaURL);
+
+ if (!xGraphic.is() && !bHasMediaURL)
+ {
+ SAL_INFO("oox.shape", "no graphic or media URL found");
+ return;
+ }
+
+ FSHelperPtr pFS = GetFS();
+ XmlFilterBase* pFB = GetFB();
+
+ if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
+ pFS->startElementNS(mnXmlNamespace, XML_pic);
+ else
+ pFS->startElementNS(mnXmlNamespace, XML_pic,
+ FSNS(XML_xmlns, XML_pic), pFB->getNamespaceURL(OOX_NS(dmlPicture)));
+
+ pFS->startElementNS(mnXmlNamespace, XML_nvPicPr);
+
+ presentation::ClickAction eClickAction = presentation::ClickAction_NONE;
+ OUString sDescr, sURL, sBookmark, sPPAction;
+ bool bHaveDesc;
+
+ if ( ( bHaveDesc = GetProperty( xShapeProps, "Description" ) ) )
+ mAny >>= sDescr;
+ if ( GetProperty( xShapeProps, "URL" ) )
+ mAny >>= sURL;
+ if (GetProperty(xShapeProps, "Bookmark"))
+ mAny >>= sBookmark;
+ if (GetProperty(xShapeProps, "OnClick"))
+ mAny >>= eClickAction;
+
+ pFS->startElementNS( mnXmlNamespace, XML_cNvPr,
+ XML_id, OString::number(GetNewShapeID(xShape)),
+ XML_name, GetShapeName(xShape),
+ XML_descr, sax_fastparser::UseIf(sDescr, bHaveDesc));
+
+ if (eClickAction != presentation::ClickAction_NONE)
+ {
+ switch (eClickAction)
+ {
+ case presentation::ClickAction_STOPPRESENTATION:
+ sPPAction = "ppaction://hlinkshowjump?jump=endshow";
+ break;
+ case presentation::ClickAction_NEXTPAGE:
+ sPPAction = "ppaction://hlinkshowjump?jump=nextslide";
+ break;
+ case presentation::ClickAction_LASTPAGE:
+ sPPAction = "ppaction://hlinkshowjump?jump=lastslide";
+ break;
+ case presentation::ClickAction_PREVPAGE:
+ sPPAction = "ppaction://hlinkshowjump?jump=previousslide";
+ break;
+ case presentation::ClickAction_FIRSTPAGE:
+ sPPAction = "ppaction://hlinkshowjump?jump=firstslide";
+ break;
+ case presentation::ClickAction_BOOKMARK:
+ sBookmark = "#" + sBookmark;
+ break;
+ default:
+ break;
+ }
+ }
+
+ // OOXTODO: //cNvPr children: XML_extLst, XML_hlinkHover
+ if (bHasMediaURL || !sPPAction.isEmpty())
+ pFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action,
+ bHasMediaURL ? "ppaction://media" : sPPAction);
+ if( !sURL.isEmpty() )
+ {
+ OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(),
+ oox::getRelationship(Relationship::HYPERLINK),
+ mpURLTransformer->getTransformedString(sURL),
+ mpURLTransformer->isExternalURL(sURL));
+
+ mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
+ }
+
+ if (!sBookmark.isEmpty())
+ {
+ bool bExtURL = URLTransformer().isExternalURL(sBookmark);
+ sBookmark = bExtURL ? sBookmark : lcl_GetTarget(GetFB()->getModel(), sBookmark);
+
+ OUString sRelId = mpFB->addRelation(mpFS->getOutputStream(),
+ bExtURL ? oox::getRelationship(Relationship::HYPERLINK)
+ : oox::getRelationship(Relationship::SLIDE),
+ sBookmark, bExtURL);
+
+ if (bExtURL)
+ mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
+ else
+ mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId, XML_action,
+ "ppaction://hlinksldjump");
+ }
+ pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
+
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvPicPr
+ // OOXTODO: XML_preferRelativeSize
+ );
+ if (bHasMediaURL)
+ WriteMediaNonVisualProperties(xShape);
+ else
+ WriteNonVisualProperties(xShape);
+
+ pFS->endElementNS( mnXmlNamespace, XML_nvPicPr );
+
+ pFS->startElementNS(mnXmlNamespace, XML_blipFill);
+
+ if (xGraphic.is())
+ {
+ WriteXGraphicBlip(xShapeProps, xGraphic, mbUserShapes);
+ }
+ else if (bHasMediaURL)
+ {
+ Reference<graphic::XGraphic> xFallbackGraphic;
+ if (xShapeProps->getPropertySetInfo()->hasPropertyByName("FallbackGraphic"))
+ xShapeProps->getPropertyValue("FallbackGraphic") >>= xFallbackGraphic;
+
+ WriteXGraphicBlip(xShapeProps, xFallbackGraphic, mbUserShapes);
+ }
+
+ if (xGraphic.is())
+ {
+ WriteSrcRectXGraphic(xShapeProps, xGraphic);
+ }
+
+ // now we stretch always when we get pGraphic (when changing that
+ // behavior, test n#780830 for regression, where the OLE sheet might get tiled
+ bool bStretch = false;
+ if( !pGraphic && GetProperty( xShapeProps, "FillBitmapStretch" ) )
+ mAny >>= bStretch;
+
+ if ( pGraphic || bStretch )
+ pFS->singleElementNS(XML_a, XML_stretch);
+
+ if (bHasMediaURL)
+ {
+ // Graphic of media shapes is always stretched.
+ pFS->startElementNS(XML_a, XML_stretch);
+ pFS->singleElementNS(XML_a, XML_fillRect);
+ pFS->endElementNS(XML_a, XML_stretch);
+ }
+
+ pFS->endElementNS( mnXmlNamespace, XML_blipFill );
+
+ // visual shape properties
+ pFS->startElementNS(mnXmlNamespace, XML_spPr);
+ bool bFlipH = false;
+ if( xShapeProps->getPropertySetInfo()->hasPropertyByName("IsMirrored") )
+ {
+ xShapeProps->getPropertyValue("IsMirrored") >>= bFlipH;
+ }
+ WriteShapeTransformation( xShape, XML_a, bFlipH, false, false, false, true );
+ WritePresetShape( "rect" );
+ // graphic object can come with the frame (bnc#654525)
+ WriteOutline( xShapeProps );
+
+ WriteShapeEffects( xShapeProps );
+ Write3DEffects( xShapeProps, /*bIsText=*/false );
+
+ pFS->endElementNS( mnXmlNamespace, XML_spPr );
+
+ pFS->endElementNS( mnXmlNamespace, XML_pic );
+}
+
+static void lcl_Rotate(sal_Int32 nAngle, Point center, awt::Point& pt)
+{
+ sal_Int16 nCos, nSin;
+ switch (nAngle)
+ {
+ case 90:
+ nCos = 0;
+ nSin = 1;
+ break;
+ case 180:
+ nCos = -1;
+ nSin = 0;
+ break;
+ case 270:
+ nCos = 0;
+ nSin = -1;
+ break;
+ default:
+ return;
+ }
+ sal_Int32 x = pt.X - center.X();
+ sal_Int32 y = pt.Y - center.Y();
+ pt.X = center.X() + x * nCos - y * nSin;
+ pt.Y = center.Y() + y * nCos + x * nSin;
+}
+
+static void lcl_FlipHFlipV(tools::Polygon aPoly, sal_Int32 nAngle, bool& rFlipH, bool& rFlipV)
+{
+ Point aStart = aPoly[0];
+ Point aEnd = aPoly[aPoly.GetSize() - 1];
+
+ if (aStart.X() > aEnd.X() && aStart.Y() > aEnd.Y())
+ {
+ if (nAngle)
+ {
+ if (nAngle == 90)
+ rFlipH = true;
+ if (nAngle == 270)
+ rFlipV = true;
+ }
+ else // 0°
+ {
+ rFlipH = true;
+ rFlipV = true;
+ }
+ }
+
+ if (aStart.X() < aEnd.X() && aStart.Y() < aEnd.Y())
+ {
+ if (nAngle)
+ {
+ if (nAngle != 270)
+ {
+ rFlipH = true;
+ rFlipV = true;
+ }
+ else
+ rFlipH = true;
+ }
+ }
+
+ if (aStart.Y() < aEnd.Y() && aStart.X() > aEnd.X())
+ {
+ if (nAngle)
+ {
+ if (nAngle == 180)
+ rFlipV = true;
+ if (nAngle == 270)
+ {
+ rFlipV = true;
+ rFlipH = true;
+ }
+ }
+ else // 0°
+ {
+ rFlipH = true;
+ }
+ }
+
+ if (aStart.Y() > aEnd.Y() && aStart.X() < aEnd.X())
+ {
+ if (nAngle)
+ {
+ if (nAngle == 90)
+ {
+ rFlipH = true;
+ rFlipV = true;
+ }
+ if (nAngle == 180)
+ rFlipH = true;
+ }
+ else // 0°
+ rFlipV = true;
+ }
+}
+
+static sal_Int32 lcl_GetAngle(tools::Polygon aPoly)
+{
+ sal_Int32 nAngle;
+ Point aStartPoint = aPoly[0];
+ Point aEndPoint = aPoly[aPoly.GetSize() - 1];
+ if (aStartPoint.X() == aPoly[1].X())
+ {
+ if ((aStartPoint.X() < aEndPoint.X() && aStartPoint.Y() > aEndPoint.Y())
+ || (aStartPoint.X() > aEndPoint.X() && aStartPoint.Y() < aEndPoint.Y()))
+ {
+ nAngle = 90;
+ }
+ else
+ nAngle = 270;
+ }
+ else
+ {
+ if (aStartPoint.X() > aPoly[1].X())
+ nAngle = 180;
+ else
+ nAngle = 0;
+ }
+
+ return nAngle;
+}
+
+// Adjust value decide the position, where the connector should turn.
+static void lcl_GetConnectorAdjustValue(const Reference<XShape>& xShape, tools::Polygon aPoly,
+ ConnectorType eConnectorType,
+ std::vector<std::pair<sal_Int32, sal_Int32>>& rAvList)
+{
+ sal_Int32 nAdjCount = 0;
+ if (eConnectorType == ConnectorType_CURVE)
+ {
+ if (aPoly.GetSize() == 4)
+ {
+ if ((aPoly[0].X() == aPoly[1].X() && aPoly[2].X() == aPoly[3].X())
+ || (aPoly[0].Y() == aPoly[1].Y() && aPoly[2].Y() == aPoly[3].Y()))
+ {
+ nAdjCount = 1; // curvedConnector3
+ }
+ else
+ nAdjCount = 0; // curvedConnector2
+ }
+ else if (aPoly.GetSize() > 4)
+ {
+ if ((aPoly[2].X() == aPoly[3].X() && aPoly[3].X() == aPoly[4].X())
+ || (aPoly[2].Y() == aPoly[3].Y() && aPoly[3].Y() == aPoly[4].Y()))
+ {
+ nAdjCount = 3; // curvedConnector5
+ }
+ else
+ nAdjCount = 2; // curvedConnector4
+ }
+ }
+ else
+ {
+ switch (aPoly.GetSize())
+ {
+ case 3:
+ nAdjCount = 0; // bentConnector2
+ break;
+ case 4:
+ nAdjCount = 1; // bentConnector3
+ break;
+ case 5:
+ nAdjCount = 2; // bentConnector4
+ break;
+ case 6:
+ nAdjCount = 3; // bentConnector5
+ break;
+ }
+ }
+
+ if (nAdjCount)
+ {
+ sal_Int32 nAdjustValue;
+ Point aStart = aPoly[0];
+ Point aEnd = aPoly[aPoly.GetSize() - 1];
+
+ for (sal_Int32 i = 1; i <= nAdjCount; ++i)
+ {
+ Point aPt = aPoly[i];
+
+ if (aEnd.Y() == aStart.Y())
+ aEnd.setY(aStart.Y() + 1);
+ if (aEnd.X() == aStart.X())
+ aEnd.setX(aStart.X() + 1);
+
+ bool bVertical = aPoly[1].X() - aStart.X() != 0 ? true : false;
+ // vertical and horizon alternate
+ if (i % 2 == 1)
+ bVertical = !bVertical;
+
+ if (eConnectorType == ConnectorType_CURVE)
+ {
+ awt::Size aSize = xShape->getSize();
+ awt::Point aShapePosition = xShape->getPosition();
+ tools::Rectangle aBoundRect = aPoly.GetBoundRect();
+
+ if (bVertical)
+ {
+ if ((aBoundRect.GetSize().Height() - aSize.Height) == 1)
+ aPt.setY(aPoly[i + 1].Y());
+ else if (aStart.Y() > aPt.Y())
+ aPt.setY(aShapePosition.Y);
+ else
+ aPt.setY(aShapePosition.Y + aSize.Height);
+ }
+ else
+ {
+ if ((aBoundRect.GetSize().Width() - aSize.Width) == 1)
+ aPt.setX(aPoly[i + 1].X());
+ else if (aStart.X() > aPt.X())
+ aPt.setX(aShapePosition.X);
+ else
+ aPt.setX(aShapePosition.X + aSize.Width);
+ }
+ }
+
+ if (bVertical)
+ nAdjustValue = ((aPt.Y() - aStart.Y()) * 100000) / (aEnd.Y() - aStart.Y());
+ else
+ nAdjustValue = ((aPt.X() - aStart.X()) * 100000) / (aEnd.X() - aStart.X());
+
+ rAvList.emplace_back(i, nAdjustValue);
+ }
+ }
+}
+
+static sal_Int32 lcl_GetGluePointId(sal_Int32 nGluePointId)
+{
+ if (nGluePointId > 3)
+ return nGluePointId - 4;
+ else
+ {
+ // change id of the bounding box (1 <-> 3)
+ if (nGluePointId == 1)
+ return 3; // Right
+ else if (nGluePointId == 3)
+ return 1; // Left
+ }
+
+ return nGluePointId;
+}
+
+ShapeExport& ShapeExport::WriteConnectorShape( const Reference< XShape >& xShape )
+{
+ bool bFlipH = false;
+ bool bFlipV = false;
+ sal_Int32 nAngle = 0;
+ sal_Int32 nStartGlueId = 0;
+ sal_Int32 nEndGlueId = 0;
+
+ SAL_INFO("oox.shape", "write connector shape");
+
+ FSHelperPtr pFS = GetFS();
+
+ OUString sGeometry;
+ std::vector<std::pair<sal_Int32, sal_Int32>> aAdjustValueList;
+ Reference< XPropertySet > rXPropSet( xShape, UNO_QUERY );
+ Reference< XPropertyState > rXPropState( xShape, UNO_QUERY );
+ awt::Point aStartPoint, aEndPoint;
+ Reference< XShape > rXShapeA;
+ Reference< XShape > rXShapeB;
+ PropertyState eState;
+ ConnectorType eConnectorType = ConnectorType_STANDARD;
+ GET(eConnectorType, EdgeKind);
+
+ switch( eConnectorType ) {
+ case ConnectorType_CURVE:
+ sGeometry = "curvedConnector";
+ break;
+ case ConnectorType_LINES:
+ case ConnectorType_STANDARD:
+ sGeometry = "bentConnector";
+ break;
+ default:
+ case ConnectorType_LINE:
+ sGeometry = "straightConnector1";
+ break;
+ }
+
+ if( GETAD( EdgeStartPoint ) ) {
+ mAny >>= aStartPoint;
+ if( GETAD( EdgeEndPoint ) ) {
+ mAny >>= aEndPoint;
+ }
+ }
+ GET( rXShapeA, EdgeStartConnection );
+ GET( rXShapeB, EdgeEndConnection );
+
+ GET(nStartGlueId, StartGluePointIndex);
+ if (nStartGlueId != -1)
+ nStartGlueId = lcl_GetGluePointId(nStartGlueId);
+ GET(nEndGlueId, EndGluePointIndex);
+ if (nEndGlueId != -1)
+ nEndGlueId = lcl_GetGluePointId(nEndGlueId);
+
+ // Position is relative to group in Word, but relative to anchor of group in API.
+ if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes && m_xParent.is())
+ {
+ awt::Point aParentPos = m_xParent->getPosition();
+ aStartPoint.X -= aParentPos.X;
+ aStartPoint.Y -= aParentPos.Y;
+ aEndPoint.X -= aParentPos.X;
+ aEndPoint.Y -= aParentPos.Y;
+ }
+ EscherConnectorListEntry aConnectorEntry( xShape, aStartPoint, rXShapeA, aEndPoint, rXShapeB );
+
+ if (eConnectorType != ConnectorType_LINE)
+ {
+ tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(xShape);
+ if (aPolyPolygon.Count() > 0)
+ {
+ tools::Polygon aPoly = aPolyPolygon.GetObject(0);
+ lcl_GetConnectorAdjustValue(xShape, aPoly, eConnectorType, aAdjustValueList);
+ nAngle = lcl_GetAngle(aPoly);
+ lcl_FlipHFlipV(aPoly, nAngle, bFlipH, bFlipV);
+ if (nAngle)
+ {
+ Point center((aEndPoint.X + aStartPoint.X) / 2, (aEndPoint.Y + aStartPoint.Y) / 2);
+ lcl_Rotate(nAngle, center, aStartPoint);
+ lcl_Rotate(nAngle, center, aEndPoint);
+ nAngle *= 60000;
+ }
+ sGeometry = sGeometry + OUString::number(aAdjustValueList.size() + 2);
+ }
+ }
+
+ tools::Rectangle aRect( Point( aStartPoint.X, aStartPoint.Y ), Point( aEndPoint.X, aEndPoint.Y ) );
+ if( aRect.getWidth() < 0 ) {
+ aRect.SetLeft(aEndPoint.X);
+ aRect.setWidth( aStartPoint.X - aEndPoint.X );
+ if (eConnectorType == ConnectorType_LINE)
+ bFlipH = true;
+ }
+
+ if( aRect.getHeight() < 0 ) {
+ aRect.SetTop(aEndPoint.Y);
+ aRect.setHeight( aStartPoint.Y - aEndPoint.Y );
+ if (eConnectorType == ConnectorType_LINE)
+ bFlipV = true;
+ }
+
+ // tdf#99810 connector shape (cxnSp) is not valid with namespace 'wps'
+ const auto nShapeNode = (mnXmlNamespace == XML_wps ? XML_wsp : XML_cxnSp);
+ pFS->startElementNS(mnXmlNamespace, nShapeNode);
+
+ if (mnXmlNamespace == XML_wps)
+ {
+ // non visual connector shape drawing properties
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvCnPr);
+ }
+ else
+ {
+ // non visual shape properties
+ pFS->startElementNS(mnXmlNamespace, XML_nvCxnSpPr);
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvPr,
+ XML_id, OString::number(GetNewShapeID(xShape)),
+ XML_name, GetShapeName(xShape));
+ // non visual connector shape drawing properties
+ pFS->startElementNS(mnXmlNamespace, XML_cNvCxnSpPr);
+
+ if (GetShapeID(rXShapeA) == -1)
+ GetNewShapeID(rXShapeA);
+ if (GetShapeID(rXShapeB) == -1)
+ GetNewShapeID(rXShapeB);
+ WriteConnectorConnections(nStartGlueId, nEndGlueId, GetShapeID(rXShapeA), GetShapeID(rXShapeB));
+ pFS->endElementNS(mnXmlNamespace, XML_cNvCxnSpPr);
+ if (GetDocumentType() == DOCUMENT_PPTX)
+ pFS->singleElementNS(mnXmlNamespace, XML_nvPr);
+ pFS->endElementNS(mnXmlNamespace, XML_nvCxnSpPr);
+ }
+
+ // visual shape properties
+ pFS->startElementNS(mnXmlNamespace, XML_spPr);
+ WriteTransformation( xShape, aRect, XML_a, bFlipH, bFlipV, nAngle );
+ // TODO: write adjustments (ppt export doesn't work well there either)
+ WritePresetShape( sGeometry.toUtf8(), aAdjustValueList);
+ Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY );
+ if( xShapeProps.is() )
+ WriteOutline( xShapeProps );
+ pFS->endElementNS( mnXmlNamespace, XML_spPr );
+
+ // connector shape (cxnSp) cannot contain text (txBody) (according to schema)
+ if( nShapeNode != XML_cxnSp )
+ {
+ // write text
+ WriteTextBox( xShape, mnXmlNamespace );
+ }
+
+ pFS->endElementNS(mnXmlNamespace, nShapeNode);
+
+ return *this;
+}
+
+ShapeExport& ShapeExport::WriteLineShape( const Reference< XShape >& xShape )
+{
+ bool bFlipH = false;
+ bool bFlipV = false;
+
+ SAL_INFO("oox.shape", "write line shape");
+
+ FSHelperPtr pFS = GetFS();
+
+ pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp));
+
+ tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon( xShape );
+ if( aPolyPolygon.Count() == 1 && aPolyPolygon[ 0 ].GetSize() == 2)
+ {
+ const tools::Polygon& rPoly = aPolyPolygon[ 0 ];
+
+ bFlipH = ( rPoly[ 0 ].X() > rPoly[ 1 ].X() );
+ bFlipV = ( rPoly[ 0 ].Y() > rPoly[ 1 ].Y() );
+ }
+
+ // non visual shape properties
+ if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
+ {
+ pFS->startElementNS(mnXmlNamespace, XML_nvSpPr);
+ pFS->singleElementNS( mnXmlNamespace, XML_cNvPr,
+ XML_id, OString::number(GetNewShapeID(xShape)),
+ XML_name, GetShapeName(xShape));
+ }
+ pFS->singleElementNS( mnXmlNamespace, XML_cNvSpPr );
+ if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
+ {
+ WriteNonVisualProperties( xShape );
+ pFS->endElementNS( mnXmlNamespace, XML_nvSpPr );
+ }
+
+ // visual shape properties
+ pFS->startElementNS(mnXmlNamespace, XML_spPr);
+ WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV, true);
+ WritePresetShape( "line" );
+ Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY );
+ if( xShapeProps.is() )
+ WriteOutline( xShapeProps );
+ pFS->endElementNS( mnXmlNamespace, XML_spPr );
+
+ //write style
+ pFS->startElementNS(mnXmlNamespace, XML_style);
+ WriteShapeStyle( xShapeProps );
+ pFS->endElementNS( mnXmlNamespace, XML_style );
+
+ // write text
+ WriteTextBox( xShape, mnXmlNamespace );
+
+ pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) );
+
+ return *this;
+}
+
+ShapeExport& ShapeExport::WriteNonVisualDrawingProperties( const Reference< XShape >& xShape, const char* pName )
+{
+ GetFS()->singleElementNS( mnXmlNamespace, XML_cNvPr,
+ XML_id, OString::number(GetNewShapeID(xShape)),
+ XML_name, pName );
+
+ return *this;
+}
+
+ShapeExport& ShapeExport::WriteNonVisualProperties( const Reference< XShape >& )
+{
+ // Override to generate //nvPr elements.
+ return *this;
+}
+
+ShapeExport& ShapeExport::WriteRectangleShape( const Reference< XShape >& xShape )
+{
+ SAL_INFO("oox.shape", "write rectangle shape");
+
+ FSHelperPtr pFS = GetFS();
+
+ pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp));
+
+ sal_Int32 nRadius = 0;
+
+ Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY );
+ if( xShapeProps.is() )
+ {
+ xShapeProps->getPropertyValue( "CornerRadius" ) >>= nRadius;
+ }
+
+ if( nRadius )
+ {
+ nRadius = MapSize( awt::Size( nRadius, 0 ) ).Width;
+ }
+ //TODO: use nRadius value more precisely than just deciding whether to use
+ // "rect" or "roundRect" preset shape below
+
+ // non visual shape properties
+ if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes)
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);
+ pFS->startElementNS(mnXmlNamespace, XML_nvSpPr);
+ pFS->singleElementNS( mnXmlNamespace, XML_cNvPr,
+ XML_id, OString::number(GetNewShapeID(xShape)),
+ XML_name, GetShapeName(xShape));
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);
+ WriteNonVisualProperties( xShape );
+ pFS->endElementNS( mnXmlNamespace, XML_nvSpPr );
+
+ // visual shape properties
+ pFS->startElementNS(mnXmlNamespace, XML_spPr);
+ WriteShapeTransformation( xShape, XML_a );
+ WritePresetShape( nRadius == 0 ? "rect" : "roundRect" );
+ Reference< XPropertySet > xProps( xShape, UNO_QUERY );
+ if( xProps.is() )
+ {
+ WriteFill( xProps );
+ WriteOutline( xProps );
+ }
+ pFS->endElementNS( mnXmlNamespace, XML_spPr );
+
+ // write text
+ WriteTextBox( xShape, mnXmlNamespace );
+
+ pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) );
+
+ return *this;
+}
+
+typedef ShapeExport& (ShapeExport::*ShapeConverter)( const Reference< XShape >& );
+typedef std::unordered_map< const char*, ShapeConverter, rtl::CStringHash, rtl::CStringEqual> NameToConvertMapType;
+
+static const NameToConvertMapType& lcl_GetConverters()
+{
+ static NameToConvertMapType const shape_converters
+ {
+ // tdf#98736 export CaptionShape as TextShape, because it is non-ooxml shape and
+ // we can't export this shape as CustomShape
+ // TODO: WriteCaptionShape
+ { "com.sun.star.drawing.CaptionShape" , &ShapeExport::WriteTextShape },
+
+ { "com.sun.star.drawing.ClosedBezierShape" , &ShapeExport::WriteClosedPolyPolygonShape },
+ { "com.sun.star.drawing.ConnectorShape" , &ShapeExport::WriteConnectorShape },
+ { "com.sun.star.drawing.CustomShape" , &ShapeExport::WriteCustomShape },
+ { "com.sun.star.drawing.EllipseShape" , &ShapeExport::WriteEllipseShape },
+ { "com.sun.star.drawing.GraphicObjectShape" , &ShapeExport::WriteGraphicObjectShape },
+ { "com.sun.star.drawing.LineShape" , &ShapeExport::WriteLineShape },
+ { "com.sun.star.drawing.MediaShape" , &ShapeExport::WriteGraphicObjectShape },
+ { "com.sun.star.drawing.OpenBezierShape" , &ShapeExport::WriteOpenPolyPolygonShape },
+ { "com.sun.star.drawing.PolyPolygonShape" , &ShapeExport::WriteClosedPolyPolygonShape },
+ { "com.sun.star.drawing.PolyLineShape" , &ShapeExport::WriteOpenPolyPolygonShape },
+ { "com.sun.star.drawing.RectangleShape" , &ShapeExport::WriteRectangleShape },
+ { "com.sun.star.drawing.OLE2Shape" , &ShapeExport::WriteOLE2Shape },
+ { "com.sun.star.drawing.TableShape" , &ShapeExport::WriteTableShape },
+ { "com.sun.star.drawing.TextShape" , &ShapeExport::WriteTextShape },
+ { "com.sun.star.drawing.GroupShape" , &ShapeExport::WriteGroupShape },
+
+ { "com.sun.star.presentation.GraphicObjectShape" , &ShapeExport::WriteGraphicObjectShape },
+ { "com.sun.star.presentation.MediaShape" , &ShapeExport::WriteGraphicObjectShape },
+ { "com.sun.star.presentation.ChartShape" , &ShapeExport::WriteOLE2Shape },
+ { "com.sun.star.presentation.OLE2Shape" , &ShapeExport::WriteOLE2Shape },
+ { "com.sun.star.presentation.TableShape" , &ShapeExport::WriteTableShape },
+ { "com.sun.star.presentation.TextShape" , &ShapeExport::WriteTextShape },
+
+ { "com.sun.star.presentation.DateTimeShape" , &ShapeExport::WriteTextShape },
+ { "com.sun.star.presentation.FooterShape" , &ShapeExport::WriteTextShape },
+ { "com.sun.star.presentation.HeaderShape" , &ShapeExport::WriteTextShape },
+ { "com.sun.star.presentation.NotesShape" , &ShapeExport::WriteTextShape },
+ { "com.sun.star.presentation.OutlinerShape" , &ShapeExport::WriteTextShape },
+ { "com.sun.star.presentation.SlideNumberShape" , &ShapeExport::WriteTextShape },
+ { "com.sun.star.presentation.TitleTextShape" , &ShapeExport::WriteTextShape },
+ };
+ return shape_converters;
+}
+
+ShapeExport& ShapeExport::WriteShape( const Reference< XShape >& xShape )
+{
+ if (!xShape)
+ throw lang::IllegalArgumentException();
+
+ OUString sShapeType = xShape->getShapeType();
+ SAL_INFO("oox.shape", "write shape: " << sShapeType);
+ NameToConvertMapType::const_iterator aConverter
+ = lcl_GetConverters().find(sShapeType.toUtf8().getStr());
+ if (aConverter == lcl_GetConverters().end())
+ {
+ SAL_INFO("oox.shape", "unknown shape");
+ return WriteUnknownShape( xShape );
+ }
+
+ if (GetDocumentType() == DOCUMENT_PPTX)
+ {
+ Reference< XPropertySet > xShapeProperties(xShape, UNO_QUERY);
+ if (xShapeProperties && xShapeProperties->getPropertySetInfo()
+ && xShapeProperties->getPropertySetInfo()->hasPropertyByName("IsPresentationObject")
+ && xShapeProperties->getPropertyValue("IsPresentationObject").hasValue())
+ mbPlaceholder = xShapeProperties->getPropertyValue("IsPresentationObject").get<bool>();
+ }
+
+ (this->*(aConverter->second))( xShape );
+
+ return *this;
+}
+
+static bool lcl_isTextBox(const Reference<XInterface>& xIface)
+{
+ uno::Reference<beans::XPropertySet> xPropertySet(xIface, uno::UNO_QUERY);
+ if (!xPropertySet.is())
+ return false;
+ uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
+ if (!xPropertySetInfo->hasPropertyByName("TextBox"))
+ return false;
+ css::uno::Any aTextBox(xPropertySet->getPropertyValue("TextBox"));
+ if (!aTextBox.hasValue())
+ return false;
+ return aTextBox.get<bool>();
+}
+
+ShapeExport& ShapeExport::WriteTextBox( const Reference< XInterface >& xIface, sal_Int32 nXmlNamespace, bool bWritePropertiesAsLstStyles )
+{
+ // In case this shape has an associated textbox, then export that, and we're done.
+ if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes && GetTextExport())
+ {
+ if (lcl_isTextBox(xIface))
+ {
+ GetTextExport()->WriteTextBox(uno::Reference<drawing::XShape>(xIface, uno::UNO_QUERY_THROW));
+ WriteText( xIface, /*bBodyPr=*/true, /*bText=*/false, /*nXmlNamespace=*/nXmlNamespace );
+ return *this;
+ }
+ }
+
+ Reference< XText > xXText( xIface, UNO_QUERY );
+ if( (NonEmptyText( xIface ) || GetDocumentType() == DOCUMENT_PPTX)
+ && xXText.is() )
+ {
+ FSHelperPtr pFS = GetFS();
+
+ pFS->startElementNS(nXmlNamespace,
+ (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_txBody : XML_txbx));
+ WriteText(xIface, /*bBodyPr=*/(GetDocumentType() != DOCUMENT_DOCX || mbUserShapes), /*bText=*/true,
+ /*nXmlNamespace=*/0, /*bWritePropertiesAsLstStyles=*/bWritePropertiesAsLstStyles);
+ pFS->endElementNS( nXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_txBody : XML_txbx) );
+ if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes)
+ WriteText( xIface, /*bBodyPr=*/true, /*bText=*/false, /*nXmlNamespace=*/nXmlNamespace );
+ }
+ else if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes)
+ mpFS->singleElementNS(nXmlNamespace, XML_bodyPr);
+
+ return *this;
+}
+
+void ShapeExport::WriteTable( const Reference< XShape >& rXShape )
+{
+ Reference< XTable > xTable;
+ Reference< XPropertySet > xPropSet( rXShape, UNO_QUERY );
+
+ mpFS->startElementNS(XML_a, XML_graphic);
+ mpFS->startElementNS(XML_a, XML_graphicData,
+ XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/table");
+
+ if ( xPropSet.is() && ( xPropSet->getPropertyValue( "Model" ) >>= xTable ) )
+ {
+ mpFS->startElementNS(XML_a, XML_tbl);
+ mpFS->startElementNS(XML_a, XML_tblPr);
+ WriteShapeEffects(xPropSet);
+ mpFS->endElementNS(XML_a, XML_tblPr);
+
+ Reference< container::XIndexAccess > xColumns( xTable->getColumns(), UNO_QUERY_THROW );
+ Reference< container::XIndexAccess > xRows( xTable->getRows(), UNO_QUERY_THROW );
+ sal_uInt16 nRowCount = static_cast< sal_uInt16 >( xRows->getCount() );
+ sal_uInt16 nColumnCount = static_cast< sal_uInt16 >( xColumns->getCount() );
+
+ mpFS->startElementNS(XML_a, XML_tblGrid);
+
+ for ( sal_Int32 x = 0; x < nColumnCount; x++ )
+ {
+ Reference< XPropertySet > xColPropSet( xColumns->getByIndex( x ), UNO_QUERY_THROW );
+ sal_Int32 nWidth(0);
+ xColPropSet->getPropertyValue( "Width" ) >>= nWidth;
+
+ mpFS->singleElementNS(XML_a, XML_gridCol,
+ XML_w, OString::number(oox::drawingml::convertHmmToEmu(nWidth)));
+ }
+
+ mpFS->endElementNS( XML_a, XML_tblGrid );
+
+ // map for holding the transpose index of the merged cells and pair<parentTransposeIndex, parentCell>
+ typedef std::unordered_map<sal_Int32, std::pair<sal_Int32, Reference< XMergeableCell> > > transposeTableMap;
+ transposeTableMap mergedCellMap;
+
+ for( sal_Int32 nRow = 0; nRow < nRowCount; nRow++ )
+ {
+ Reference< XPropertySet > xRowPropSet( xRows->getByIndex( nRow ), UNO_QUERY_THROW );
+ sal_Int32 nRowHeight(0);
+
+ xRowPropSet->getPropertyValue( "Height" ) >>= nRowHeight;
+
+ mpFS->startElementNS(XML_a, XML_tr,
+ XML_h, OString::number(oox::drawingml::convertHmmToEmu(nRowHeight)));
+ for( sal_Int32 nColumn = 0; nColumn < nColumnCount; nColumn++ )
+ {
+ Reference< XMergeableCell > xCell( xTable->getCellByPosition( nColumn, nRow ),
+ UNO_QUERY_THROW );
+ sal_Int32 transposedIndexofCell = (nRow * nColumnCount) + nColumn;
+
+ //assume we will open a cell, set to false below if we won't
+ bool bCellOpened = true;
+
+ if(xCell->getColumnSpan() > 1 && xCell->getRowSpan() > 1)
+ {
+ // having both : horizontal and vertical merge
+ mpFS->startElementNS(XML_a, XML_tc,
+ XML_gridSpan, OString::number(xCell->getColumnSpan()),
+ XML_rowSpan, OString::number(xCell->getRowSpan()));
+ // since, XMergeableCell doesn't have the information about
+ // cell having hMerge or vMerge.
+ // So, Populating the merged cell map in-order to use it to
+ // decide the attribute for the individual cell.
+ for(sal_Int32 columnIndex = nColumn; columnIndex < nColumn+xCell->getColumnSpan(); ++columnIndex)
+ {
+ for(sal_Int32 rowIndex = nRow; rowIndex < nRow+xCell->getRowSpan(); ++rowIndex)
+ {
+ sal_Int32 transposeIndexForMergeCell =
+ (rowIndex * nColumnCount) + columnIndex;
+ mergedCellMap[transposeIndexForMergeCell] =
+ std::make_pair(transposedIndexofCell, xCell);
+ }
+ }
+
+ }
+ else if(xCell->getColumnSpan() > 1)
+ {
+ // having : horizontal merge
+ mpFS->startElementNS(XML_a, XML_tc,
+ XML_gridSpan, OString::number(xCell->getColumnSpan()));
+ for(sal_Int32 columnIndex = nColumn; columnIndex < nColumn + xCell->getColumnSpan(); ++columnIndex) {
+ sal_Int32 transposeIndexForMergeCell = (nRow*nColumnCount) + columnIndex;
+ mergedCellMap[transposeIndexForMergeCell] =
+ std::make_pair(transposedIndexofCell, xCell);
+ }
+ }
+ else if(xCell->getRowSpan() > 1)
+ {
+ // having : vertical merge
+ mpFS->startElementNS(XML_a, XML_tc,
+ XML_rowSpan, OString::number(xCell->getRowSpan()));
+
+ for(sal_Int32 rowIndex = nRow; rowIndex < nRow + xCell->getRowSpan(); ++rowIndex) {
+ sal_Int32 transposeIndexForMergeCell = (rowIndex*nColumnCount) + nColumn;
+ mergedCellMap[transposeIndexForMergeCell] =
+ std::make_pair(transposedIndexofCell, xCell);
+ }
+ }
+ else
+ {
+ // now, the cell can be an independent cell or
+ // it can be a cell which is been merged to some parent cell
+ if(!xCell->isMerged())
+ {
+ // independent cell
+ mpFS->startElementNS(XML_a, XML_tc);
+ }
+ else
+ {
+ // it a merged cell to some parent cell
+ // find the parent cell for the current cell at hand
+ transposeTableMap::iterator it = mergedCellMap.find(transposedIndexofCell);
+ if(it != mergedCellMap.end())
+ {
+ sal_Int32 transposeIndexOfParent = it->second.first;
+ Reference< XMergeableCell > parentCell = it->second.second;
+ // finding the row and column index for the parent cell from transposed index
+ sal_Int32 parentColumnIndex = transposeIndexOfParent % nColumnCount;
+ sal_Int32 parentRowIndex = transposeIndexOfParent / nColumnCount;
+ if(nColumn == parentColumnIndex)
+ {
+ // the cell is vertical merge and it might have gridspan
+ if(parentCell->getColumnSpan() > 1)
+ {
+ // vMerge and has gridSpan
+ mpFS->startElementNS(XML_a, XML_tc,
+ XML_vMerge, OString::number(1),
+ XML_gridSpan, OString::number(xCell->getColumnSpan()));
+ }
+ else
+ {
+ // only vMerge
+ mpFS->startElementNS(XML_a, XML_tc,
+ XML_vMerge, OString::number(1));
+ }
+ }
+ else if(nRow == parentRowIndex)
+ {
+ // the cell is horizontal merge and it might have rowspan
+ if(parentCell->getRowSpan() > 1)
+ {
+ // hMerge and has rowspan
+ mpFS->startElementNS(XML_a, XML_tc,
+ XML_hMerge, OString::number(1),
+ XML_rowSpan, OString::number(xCell->getRowSpan()));
+ }
+ else
+ {
+ // only hMerge
+ mpFS->startElementNS(XML_a, XML_tc,
+ XML_hMerge, OString::number(1));
+ }
+ }
+ else
+ {
+ // has hMerge and vMerge
+ mpFS->startElementNS(XML_a, XML_tc,
+ XML_vMerge, OString::number(1),
+ XML_hMerge, OString::number(1));
+ }
+ }
+ else
+ bCellOpened = false;
+ }
+ }
+
+ if (bCellOpened)
+ {
+ WriteTextBox( xCell, XML_a );
+
+ Reference< XPropertySet > xCellPropSet(xCell, UNO_QUERY_THROW);
+ WriteTableCellProperties(xCellPropSet);
+
+ mpFS->endElementNS( XML_a, XML_tc );
+ }
+ }
+
+ mpFS->endElementNS( XML_a, XML_tr );
+ }
+
+ mpFS->endElementNS( XML_a, XML_tbl );
+ }
+
+ mpFS->endElementNS( XML_a, XML_graphicData );
+ mpFS->endElementNS( XML_a, XML_graphic );
+}
+
+void ShapeExport::WriteTableCellProperties(const Reference< XPropertySet>& xCellPropSet)
+{
+ sal_Int32 nLeftMargin(0), nRightMargin(0);
+ TextVerticalAdjust eVerticalAlignment;
+ const char* sVerticalAlignment;
+
+ Any aLeftMargin = xCellPropSet->getPropertyValue("TextLeftDistance");
+ aLeftMargin >>= nLeftMargin;
+
+ Any aRightMargin = xCellPropSet->getPropertyValue("TextRightDistance");
+ aRightMargin >>= nRightMargin;
+
+ Any aVerticalAlignment = xCellPropSet->getPropertyValue("TextVerticalAdjust");
+ aVerticalAlignment >>= eVerticalAlignment;
+ sVerticalAlignment = GetTextVerticalAdjust(eVerticalAlignment);
+
+ mpFS->startElementNS(XML_a, XML_tcPr, XML_anchor, sVerticalAlignment,
+ XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeftMargin)), nLeftMargin > 0),
+ XML_marR, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nRightMargin)), nRightMargin > 0));
+
+ // Write background fill for table cell.
+ // TODO
+ // tcW : Table cell width
+ WriteTableCellBorders(xCellPropSet);
+ DrawingML::WriteFill(xCellPropSet);
+ mpFS->endElementNS( XML_a, XML_tcPr );
+}
+
+void ShapeExport::WriteBorderLine(const sal_Int32 XML_line, const BorderLine2& rBorderLine)
+{
+// While importing the table cell border line width, it converts EMU->Hmm then divided result by 2.
+// To get original value of LineWidth need to multiple by 2.
+ sal_Int32 nBorderWidth = rBorderLine.LineWidth;
+ nBorderWidth *= 2;
+ nBorderWidth = oox::drawingml::convertHmmToEmu( nBorderWidth );
+
+ if ( nBorderWidth > 0 )
+ {
+ mpFS->startElementNS(XML_a, XML_line, XML_w, OString::number(nBorderWidth));
+ if ( rBorderLine.Color == sal_Int32( COL_AUTO ) )
+ mpFS->singleElementNS(XML_a, XML_noFill);
+ else
+ DrawingML::WriteSolidFill( ::Color(ColorTransparency, rBorderLine.Color) );
+ mpFS->endElementNS( XML_a, XML_line );
+ }
+ else if( nBorderWidth == 0)
+ {
+ mpFS->startElementNS(XML_a, XML_line);
+ mpFS->singleElementNS(XML_a, XML_noFill);
+ mpFS->endElementNS( XML_a, XML_line );
+ }
+}
+
+void ShapeExport::WriteTableCellBorders(const Reference< XPropertySet>& xCellPropSet)
+{
+ BorderLine2 aBorderLine;
+
+// lnL - Left Border Line Properties of table cell
+ xCellPropSet->getPropertyValue("LeftBorder") >>= aBorderLine;
+ WriteBorderLine( XML_lnL, aBorderLine );
+
+// lnR - Right Border Line Properties of table cell
+ xCellPropSet->getPropertyValue("RightBorder") >>= aBorderLine;
+ WriteBorderLine( XML_lnR, aBorderLine );
+
+// lnT - Top Border Line Properties of table cell
+ xCellPropSet->getPropertyValue("TopBorder") >>= aBorderLine;
+ WriteBorderLine( XML_lnT, aBorderLine );
+
+// lnB - Bottom Border Line Properties of table cell
+ xCellPropSet->getPropertyValue("BottomBorder") >>= aBorderLine;
+ WriteBorderLine( XML_lnB, aBorderLine );
+}
+
+ShapeExport& ShapeExport::WriteTableShape( const Reference< XShape >& xShape )
+{
+ FSHelperPtr pFS = GetFS();
+
+ pFS->startElementNS(mnXmlNamespace, XML_graphicFrame);
+
+ pFS->startElementNS(mnXmlNamespace, XML_nvGraphicFramePr);
+
+ pFS->singleElementNS( mnXmlNamespace, XML_cNvPr,
+ XML_id, OString::number(GetNewShapeID(xShape)),
+ XML_name, GetShapeName(xShape));
+
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvGraphicFramePr);
+
+ if( GetDocumentType() == DOCUMENT_PPTX )
+ pFS->singleElementNS(mnXmlNamespace, XML_nvPr);
+ pFS->endElementNS( mnXmlNamespace, XML_nvGraphicFramePr );
+
+ WriteShapeTransformation( xShape, mnXmlNamespace );
+ WriteTable( xShape );
+
+ pFS->endElementNS( mnXmlNamespace, XML_graphicFrame );
+
+ return *this;
+}
+
+ShapeExport& ShapeExport::WriteTextShape( const Reference< XShape >& xShape )
+{
+ FSHelperPtr pFS = GetFS();
+ Reference<XPropertySet> xShapeProps(xShape, UNO_QUERY);
+
+ pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp));
+
+ // non visual shape properties
+ if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
+ {
+ pFS->startElementNS(mnXmlNamespace, XML_nvSpPr);
+ pFS->startElementNS(mnXmlNamespace, XML_cNvPr,
+ XML_id, OString::number(GetNewShapeID(xShape)),
+ XML_name, GetShapeName(xShape));
+ OUString sURL;
+ if (GetProperty(xShapeProps, "URL"))
+ mAny >>= sURL;
+
+ if (!sURL.isEmpty())
+ {
+ OUString sRelId = mpFB->addRelation(mpFS->getOutputStream(),
+ oox::getRelationship(Relationship::HYPERLINK),
+ mpURLTransformer->getTransformedString(sURL),
+ mpURLTransformer->isExternalURL(sURL));
+
+ mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
+ }
+ pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
+ }
+ pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr, XML_txBox, "1");
+ if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
+ {
+ WriteNonVisualProperties( xShape );
+ pFS->endElementNS( mnXmlNamespace, XML_nvSpPr );
+ }
+
+ // visual shape properties
+ pFS->startElementNS(mnXmlNamespace, XML_spPr);
+ WriteShapeTransformation( xShape, XML_a );
+ WritePresetShape( "rect" );
+ uno::Reference<beans::XPropertySet> xPropertySet(xShape, UNO_QUERY);
+ if (!IsFontworkShape(xShapeProps)) // Fontwork needs fill and outline in run properties instead.
+ {
+ WriteBlipOrNormalFill(xPropertySet, "Graphic");
+ WriteOutline(xPropertySet);
+ WriteShapeEffects(xPropertySet);
+ }
+ pFS->endElementNS( mnXmlNamespace, XML_spPr );
+
+ WriteTextBox( xShape, mnXmlNamespace );
+
+ pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) );
+
+ return *this;
+}
+
+void ShapeExport::WriteMathShape(Reference<XShape> const& xShape)
+{
+ Reference<XPropertySet> const xPropSet(xShape, UNO_QUERY);
+ assert(xPropSet.is());
+ Reference<XModel> xMathModel;
+ xPropSet->getPropertyValue("Model") >>= xMathModel;
+ assert(xMathModel.is());
+ assert(GetDocumentType() != DOCUMENT_DOCX); // should be written in DocxAttributeOutput
+ SAL_WARN_IF(GetDocumentType() == DOCUMENT_XLSX, "oox.shape", "Math export to XLSX isn't tested, should it happen here?");
+
+ // ECMA standard does not actually allow oMath outside of
+ // WordProcessingML so write a MCE like PPT 2010 does
+ mpFS->startElementNS(XML_mc, XML_AlternateContent);
+ mpFS->startElementNS(XML_mc, XML_Choice,
+ FSNS(XML_xmlns, XML_a14), mpFB->getNamespaceURL(OOX_NS(a14)),
+ XML_Requires, "a14");
+ mpFS->startElementNS(mnXmlNamespace, XML_sp);
+ mpFS->startElementNS(mnXmlNamespace, XML_nvSpPr);
+ mpFS->singleElementNS(mnXmlNamespace, XML_cNvPr,
+ XML_id, OString::number(GetNewShapeID(xShape)),
+ XML_name, GetShapeName(xShape));
+ mpFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr, XML_txBox, "1");
+ mpFS->singleElementNS(mnXmlNamespace, XML_nvPr);
+ mpFS->endElementNS(mnXmlNamespace, XML_nvSpPr);
+ mpFS->startElementNS(mnXmlNamespace, XML_spPr);
+ WriteShapeTransformation(xShape, XML_a);
+ WritePresetShape("rect");
+ mpFS->endElementNS(mnXmlNamespace, XML_spPr);
+ mpFS->startElementNS(mnXmlNamespace, XML_txBody);
+ mpFS->startElementNS(XML_a, XML_bodyPr);
+ mpFS->endElementNS(XML_a, XML_bodyPr);
+ mpFS->startElementNS(XML_a, XML_p);
+ mpFS->startElementNS(XML_a14, XML_m);
+
+ oox::FormulaExportBase *const pMagic(dynamic_cast<oox::FormulaExportBase*>(xMathModel.get()));
+ assert(pMagic);
+ pMagic->writeFormulaOoxml(GetFS(), GetFB()->getVersion(), GetDocumentType(),
+ FormulaExportBase::eFormulaAlign::INLINE);
+
+ mpFS->endElementNS(XML_a14, XML_m);
+ mpFS->endElementNS(XML_a, XML_p);
+ mpFS->endElementNS(mnXmlNamespace, XML_txBody);
+ mpFS->endElementNS(mnXmlNamespace, XML_sp);
+ mpFS->endElementNS(XML_mc, XML_Choice);
+ mpFS->startElementNS(XML_mc, XML_Fallback);
+ // TODO: export bitmap shape as fallback
+ mpFS->endElementNS(XML_mc, XML_Fallback);
+ mpFS->endElementNS(XML_mc, XML_AlternateContent);
+}
+
+ShapeExport& ShapeExport::WriteOLE2Shape( const Reference< XShape >& xShape )
+{
+ Reference< XPropertySet > xPropSet( xShape, UNO_QUERY );
+ if (!xPropSet.is())
+ return *this;
+
+ enum { CHART, MATH, OTHER } eType(OTHER);
+ OUString clsid;
+ xPropSet->getPropertyValue("CLSID") >>= clsid;
+ if (!clsid.isEmpty())
+ {
+ SvGlobalName aClassID;
+ bool const isValid = aClassID.MakeId(clsid);
+ assert(isValid); (void)isValid;
+ if (SotExchange::IsChart(aClassID))
+ eType = CHART;
+ else if (SotExchange::IsMath(aClassID))
+ eType = MATH;
+ }
+
+ if (CHART == eType)
+ {
+ Reference< XChartDocument > xChartDoc;
+ xPropSet->getPropertyValue("Model") >>= xChartDoc;
+ assert(xChartDoc.is());
+ //export the chart
+#if !ENABLE_WASM_STRIP_CHART
+ // WASM_CHART change
+ // TODO: With Chart extracted this cannot really happen since
+ // no Chart could've been added at all
+ ChartExport aChartExport( mnXmlNamespace, GetFS(), xChartDoc, GetFB(), GetDocumentType() );
+ static sal_Int32 nChartCount = 0;
+ aChartExport.WriteChartObj( xShape, GetNewShapeID( xShape ), ++nChartCount );
+#endif
+ return *this;
+ }
+
+ if (MATH == eType)
+ {
+ WriteMathShape(xShape);
+ return *this;
+ }
+
+ uno::Reference<embed::XEmbeddedObject> const xObj(
+ xPropSet->getPropertyValue("EmbeddedObject"), uno::UNO_QUERY);
+
+ if (!xObj.is())
+ {
+ SAL_WARN("oox.shape", "ShapeExport::WriteOLE2Shape: no object");
+
+ // tdf#152436 Export the preview graphic of the object if the object is missing.
+ SdrObject* pSdrOLE2(SdrObject::getSdrObjectFromXShape(xShape));
+ if (auto pOle2Obj = dynamic_cast<SdrOle2Obj*>(pSdrOLE2))
+ {
+ const Graphic* pGraphic = pOle2Obj->GetGraphic();
+ if (pGraphic)
+ WriteGraphicObjectShapePart(xShape, pGraphic);
+ }
+
+ return *this;
+ }
+
+ uno::Sequence<beans::PropertyValue> grabBag;
+ OUString entryName;
+ try
+ {
+ uno::Reference<beans::XPropertySet> const xParent(
+ uno::Reference<container::XChild>(xObj, uno::UNO_QUERY_THROW)->getParent(),
+ uno::UNO_QUERY_THROW);
+
+ xParent->getPropertyValue("InteropGrabBag") >>= grabBag;
+
+ entryName = uno::Reference<embed::XEmbedPersist>(xObj, uno::UNO_QUERY_THROW)->getEntryName();
+ }
+ catch (uno::Exception const&)
+ {
+ TOOLS_WARN_EXCEPTION("oox.shape", "ShapeExport::WriteOLE2Shape");
+ return *this;
+ }
+
+ OUString progID;
+
+ for (auto const& it : std::as_const(grabBag))
+ {
+ if (it.Name == "EmbeddedObjects")
+ {
+ uno::Sequence<beans::PropertyValue> objects;
+ it.Value >>= objects;
+ for (auto const& object : std::as_const(objects))
+ {
+ if (object.Name == entryName)
+ {
+ uno::Sequence<beans::PropertyValue> props;
+ object.Value >>= props;
+ for (auto const& prop : std::as_const(props))
+ {
+ if (prop.Name == "ProgID")
+ {
+ prop.Value >>= progID;
+ break;
+ }
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ OUString sMediaType;
+ OUString sRelationType;
+ OUString sSuffix;
+ const char * pProgID(nullptr);
+
+ uno::Reference<io::XInputStream> const xInStream =
+ oox::GetOLEObjectStream(
+ mpFB->getComponentContext(), xObj, progID,
+ sMediaType, sRelationType, sSuffix, pProgID);
+
+ if (!xInStream.is())
+ {
+ return *this;
+ }
+
+ OString anotherProgID;
+ if (!pProgID && !progID.isEmpty())
+ {
+ anotherProgID = OUStringToOString(progID, RTL_TEXTENCODING_UTF8);
+ pProgID = anotherProgID.getStr();
+ }
+
+ assert(!sMediaType.isEmpty());
+ assert(!sRelationType.isEmpty());
+ assert(!sSuffix.isEmpty());
+
+ OUString sFileName = "embeddings/oleObject" + OUString::number(++m_nEmbeddedObjects) + "." + sSuffix;
+ uno::Reference<io::XOutputStream> const xOutStream(
+ mpFB->openFragmentStream(
+ OUString::createFromAscii(GetComponentDir()) + "/" + sFileName,
+ sMediaType));
+ assert(xOutStream.is()); // no reason why that could fail
+
+ try {
+ ::comphelper::OStorageHelper::CopyInputToOutput(xInStream, xOutStream);
+ } catch (uno::Exception const&) {
+ TOOLS_WARN_EXCEPTION("oox.shape", "ShapeExport::WriteOLEObject");
+ }
+
+ OUString const sRelId = mpFB->addRelation(
+ mpFS->getOutputStream(), sRelationType,
+ OUStringConcatenation(OUString::createFromAscii(GetRelationCompPrefix()) + sFileName));
+
+ mpFS->startElementNS(mnXmlNamespace, XML_graphicFrame);
+
+ mpFS->startElementNS(mnXmlNamespace, XML_nvGraphicFramePr);
+
+ mpFS->singleElementNS( mnXmlNamespace, XML_cNvPr,
+ XML_id, OString::number(GetNewShapeID(xShape)),
+ XML_name, GetShapeName(xShape));
+
+ mpFS->singleElementNS(mnXmlNamespace, XML_cNvGraphicFramePr);
+
+ if (GetDocumentType() == DOCUMENT_PPTX)
+ mpFS->singleElementNS(mnXmlNamespace, XML_nvPr);
+ mpFS->endElementNS( mnXmlNamespace, XML_nvGraphicFramePr );
+
+ WriteShapeTransformation( xShape, mnXmlNamespace );
+
+ mpFS->startElementNS(XML_a, XML_graphic);
+ mpFS->startElementNS(XML_a, XML_graphicData,
+ XML_uri, "http://schemas.openxmlformats.org/presentationml/2006/ole");
+ if (pProgID)
+ {
+ mpFS->startElementNS( mnXmlNamespace, XML_oleObj,
+ XML_progId, pProgID,
+ FSNS(XML_r, XML_id), sRelId,
+ XML_spid, "" );
+ }
+ else
+ {
+ mpFS->startElementNS( mnXmlNamespace, XML_oleObj,
+//? XML_name, "Document",
+ FSNS(XML_r, XML_id), sRelId,
+ // The spec says that this is a required attribute, but PowerPoint can only handle an empty value.
+ XML_spid, "" );
+ }
+
+ mpFS->singleElementNS( mnXmlNamespace, XML_embed );
+
+ // pic element
+ SdrObject* pSdrOLE2(SdrObject::getSdrObjectFromXShape(xShape));
+ // The spec doesn't allow <p:pic> here, but PowerPoint requires it.
+ bool bEcma = mpFB->getVersion() == oox::core::ECMA_DIALECT;
+ if (bEcma)
+ if (auto pOle2Obj = dynamic_cast<SdrOle2Obj*>(pSdrOLE2))
+ {
+ const Graphic* pGraphic = pOle2Obj->GetGraphic();
+ if (pGraphic)
+ WriteGraphicObjectShapePart( xShape, pGraphic );
+ }
+
+ mpFS->endElementNS( mnXmlNamespace, XML_oleObj );
+
+ mpFS->endElementNS( XML_a, XML_graphicData );
+ mpFS->endElementNS( XML_a, XML_graphic );
+
+ mpFS->endElementNS( mnXmlNamespace, XML_graphicFrame );
+
+ return *this;
+}
+
+ShapeExport& ShapeExport::WriteUnknownShape( const Reference< XShape >& )
+{
+ // Override this method to do something useful.
+ return *this;
+}
+
+sal_Int32 ShapeExport::GetNewShapeID( const Reference< XShape >& rXShape )
+{
+ return GetNewShapeID( rXShape, GetFB() );
+}
+
+sal_Int32 ShapeExport::GetNewShapeID( const Reference< XShape >& rXShape, XmlFilterBase* pFB )
+{
+ if( !rXShape.is() )
+ return -1;
+
+ sal_Int32 nID = pFB->GetUniqueId();
+
+ (*mpShapeMap)[ rXShape ] = nID;
+
+ return nID;
+}
+
+sal_Int32 ShapeExport::GetShapeID( const Reference< XShape >& rXShape )
+{
+ return GetShapeID( rXShape, mpShapeMap );
+}
+
+sal_Int32 ShapeExport::GetShapeID( const Reference< XShape >& rXShape, ShapeHashMap* pShapeMap )
+{
+ if( !rXShape.is() )
+ return -1;
+
+ ShapeHashMap::const_iterator aIter = pShapeMap->find( rXShape );
+
+ if( aIter == pShapeMap->end() )
+ return -1;
+
+ return aIter->second;
+}
+
+OUString ShapeExport::GetShapeName(const Reference<XShape>& xShape)
+{
+ Reference<XPropertySet> rXPropSet(xShape, UNO_QUERY);
+
+ // Empty name keeps the object unnamed.
+ OUString sName;
+
+ if (GetProperty(rXPropSet, "Name"))
+ mAny >>= sName;
+ return sName;
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/export/vmlexport.cxx b/oox/source/export/vmlexport.cxx
new file mode 100644
index 000000000..4f2654fae
--- /dev/null
+++ b/oox/source/export/vmlexport.cxx
@@ -0,0 +1,1529 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_folders.h>
+#include <rtl/bootstrap.hxx>
+#include <svl/itemset.hxx>
+#include <oox/export/drawingml.hxx>
+#include <oox/export/vmlexport.hxx>
+#include <sax/fastattribs.hxx>
+
+#include <oox/token/tokens.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+
+#include <tools/stream.hxx>
+#include <comphelper/sequenceashashmap.hxx>
+#include <svx/svdotext.hxx>
+#include <svx/svdograf.hxx>
+#include <svx/sdmetitm.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <filter/msfilter/msdffimp.hxx>
+#include <filter/msfilter/util.hxx>
+#include <filter/msfilter/escherex.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/beans/XPropertySetInfo.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/text/HoriOrientation.hpp>
+#include <com/sun/star/text/VertOrientation.hpp>
+#include <com/sun/star/text/RelOrientation.hpp>
+#include <com/sun/star/text/WritingMode2.hpp>
+#include <com/sun/star/text/XTextFrame.hpp>
+
+#include <cstdio>
+
+using namespace sax_fastparser;
+using namespace oox::vml;
+using namespace com::sun::star;
+
+const sal_Int32 Tag_Container = 44444;
+const sal_Int32 Tag_Commit = 44445;
+
+VMLExport::VMLExport( ::sax_fastparser::FSHelperPtr const & pSerializer, VMLTextExport* pTextExport )
+ : EscherEx( std::make_shared<EscherExGlobal>(), nullptr, /*bOOXML=*/true )
+ , m_pSerializer( pSerializer )
+ , m_pTextExport( pTextExport )
+ , m_eHOri( 0 )
+ , m_eVOri( 0 )
+ , m_eHRel( 0 )
+ , m_eVRel( 0 )
+ , m_bInline( false )
+ , m_pSdrObject( nullptr )
+ , m_nShapeType( ESCHER_ShpInst_Nil )
+ , m_nShapeFlags(ShapeFlag::NONE)
+ , m_ShapeStyle( 200 )
+ , m_aShapeTypeWritten( ESCHER_ShpInst_COUNT )
+ , m_bSkipwzName( false )
+ , m_bUseHashMarkForType( false )
+ , m_bOverrideShapeIdGeneration( false )
+ , m_nShapeIDCounter( 0 )
+{
+ mnGroupLevel = 1;
+}
+
+void VMLExport::SetFS( const ::sax_fastparser::FSHelperPtr& pSerializer )
+{
+ m_pSerializer = pSerializer;
+}
+
+VMLExport::~VMLExport()
+{
+ delete mpOutStrm;
+ mpOutStrm = nullptr;
+}
+
+void VMLExport::OpenContainer( sal_uInt16 nEscherContainer, int nRecInstance )
+{
+ EscherEx::OpenContainer( nEscherContainer, nRecInstance );
+
+ if ( nEscherContainer != ESCHER_SpContainer )
+ return;
+
+ // opening a shape container
+ SAL_WARN_IF(m_nShapeType != ESCHER_ShpInst_Nil, "oox.vml", "opening shape inside of a shape!");
+ m_nShapeType = ESCHER_ShpInst_Nil;
+ m_pShapeAttrList = FastSerializerHelper::createAttrList();
+
+ m_ShapeStyle.setLength(0);
+ m_ShapeStyle.ensureCapacity(200);
+
+ // postpone the output so that we are able to write even the elements
+ // that we learn inside Commit()
+ m_pSerializer->mark(Tag_Container);
+}
+
+void VMLExport::CloseContainer()
+{
+ if ( mRecTypes.back() == ESCHER_SpContainer )
+ {
+ // write the shape now when we have all the info
+ sal_Int32 nShapeElement = StartShape();
+
+ m_pSerializer->mergeTopMarks(Tag_Container);
+
+ EndShape( nShapeElement );
+
+ // cleanup
+ m_nShapeType = ESCHER_ShpInst_Nil;
+ m_pShapeAttrList = nullptr;
+ }
+
+ EscherEx::CloseContainer();
+}
+
+sal_uInt32 VMLExport::EnterGroup( const OUString& rShapeName, const tools::Rectangle* pRect )
+{
+ sal_uInt32 nShapeId = GenerateShapeId();
+
+ OStringBuffer aStyle( 200 );
+ rtl::Reference<FastAttributeList> pAttrList = FastSerializerHelper::createAttrList();
+
+ pAttrList->add( XML_id, ShapeIdString( nShapeId ) );
+
+ if ( rShapeName.getLength() )
+ pAttrList->add( XML_alt, rShapeName );
+
+ bool rbAbsolutePos = true;
+ //editAs
+ OUString rEditAs = EscherEx::GetEditAs();
+ if (!rEditAs.isEmpty())
+ {
+ pAttrList->add(XML_editas, rEditAs);
+ rbAbsolutePos = false;
+ }
+
+ // style
+ if ( pRect )
+ AddRectangleDimensions( aStyle, *pRect, rbAbsolutePos );
+
+ if ( !aStyle.isEmpty() )
+ pAttrList->add( XML_style, aStyle.makeStringAndClear() );
+
+ // coordorigin/coordsize
+ if ( pRect && ( mnGroupLevel == 1 ) )
+ {
+ pAttrList->add( XML_coordorigin,
+ OString::number( pRect->Left() ) + "," + OString::number( pRect->Top() ) );
+
+ pAttrList->add( XML_coordsize,
+ OString::number( pRect->Right() - pRect->Left() ) + "," +
+ OString::number( pRect->Bottom() - pRect->Top() ) );
+ }
+
+ m_pSerializer->startElementNS( XML_v, XML_group, pAttrList );
+
+ mnGroupLevel++;
+ return nShapeId;
+}
+
+void VMLExport::LeaveGroup()
+{
+ --mnGroupLevel;
+ m_pSerializer->endElementNS( XML_v, XML_group );
+}
+
+void VMLExport::AddShape( sal_uInt32 nShapeType, ShapeFlag nShapeFlags, sal_uInt32 nShapeId )
+{
+ m_nShapeType = nShapeType;
+ m_nShapeFlags = nShapeFlags;
+
+ m_sShapeId = ShapeIdString( nShapeId );
+ // If shape is a watermark object - should keep the original shape's name
+ // because Microsoft detects if it is a watermark by the actual name
+ if (!IsWaterMarkShape(m_pSdrObject->GetName()))
+ {
+ // Not a watermark object
+ m_pShapeAttrList->add( XML_id, m_sShapeId );
+ }
+ else
+ {
+ // A watermark object - store the optional shape ID
+ m_pShapeAttrList->add( XML_id, m_pSdrObject->GetName() );
+ // also ('o:spid')
+ m_pShapeAttrList->addNS( XML_o, XML_spid, m_sShapeId );
+ }
+}
+
+bool VMLExport::IsWaterMarkShape(std::u16string_view rStr)
+{
+ if (rStr.empty() ) return false;
+
+ return o3tl::starts_with(rStr, u"PowerPlusWaterMarkObject") || o3tl::starts_with(rStr, u"WordPictureWatermark");
+}
+
+void VMLExport::OverrideShapeIDGen(bool bOverrideShapeIdGen, const OString& sShapeIDPrefix)
+{
+ m_bOverrideShapeIdGeneration = bOverrideShapeIdGen;
+ if(bOverrideShapeIdGen)
+ {
+ assert(!sShapeIDPrefix.isEmpty());
+ m_sShapeIDPrefix = sShapeIDPrefix;
+ }
+ else
+ m_sShapeIDPrefix.clear();
+}
+
+static void impl_AddArrowHead( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, sal_uInt32 nValue )
+{
+ if ( !pAttrList )
+ return;
+
+ const char *pArrowHead = nullptr;
+ switch ( nValue )
+ {
+ case ESCHER_LineNoEnd: pArrowHead = "none"; break;
+ case ESCHER_LineArrowEnd: pArrowHead = "block"; break;
+ case ESCHER_LineArrowStealthEnd: pArrowHead = "classic"; break;
+ case ESCHER_LineArrowDiamondEnd: pArrowHead = "diamond"; break;
+ case ESCHER_LineArrowOvalEnd: pArrowHead = "oval"; break;
+ case ESCHER_LineArrowOpenEnd: pArrowHead = "open"; break;
+ }
+
+ if ( pArrowHead )
+ pAttrList->add( nElement, pArrowHead );
+}
+
+static void impl_AddArrowLength( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, sal_uInt32 nValue )
+{
+ if ( !pAttrList )
+ return;
+
+ const char *pArrowLength = nullptr;
+ switch ( nValue )
+ {
+ case ESCHER_LineShortArrow: pArrowLength = "short"; break;
+ case ESCHER_LineMediumLenArrow: pArrowLength = "medium"; break;
+ case ESCHER_LineLongArrow: pArrowLength = "long"; break;
+ }
+
+ if ( pArrowLength )
+ pAttrList->add( nElement, pArrowLength );
+}
+
+static void impl_AddArrowWidth( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, sal_uInt32 nValue )
+{
+ if ( !pAttrList )
+ return;
+
+ const char *pArrowWidth = nullptr;
+ switch ( nValue )
+ {
+ case ESCHER_LineNarrowArrow: pArrowWidth = "narrow"; break;
+ case ESCHER_LineMediumWidthArrow: pArrowWidth = "medium"; break;
+ case ESCHER_LineWideArrow: pArrowWidth = "wide"; break;
+ }
+
+ if ( pArrowWidth )
+ pAttrList->add( nElement, pArrowWidth );
+}
+
+static void impl_AddBool( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, bool bValue )
+{
+ if ( !pAttrList )
+ return;
+
+ pAttrList->add( nElement, bValue? "t": "f" );
+}
+
+static void impl_AddColor( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, sal_uInt32 nColor )
+{
+ SAL_WARN_IF( nColor & 0xFF000000 , "oox.vml" , "TODO: this is not a RGB value!");
+
+ if ( !pAttrList || ( nColor & 0xFF000000 ) )
+ return;
+
+ nColor = ( ( nColor & 0xFF ) << 16 ) + ( nColor & 0xFF00 ) + ( ( nColor & 0xFF0000 ) >> 16 );
+
+ const char *pColor = nullptr;
+ char pRgbColor[10];
+ switch ( nColor )
+ {
+ case 0x000000: pColor = "black"; break;
+ case 0xC0C0C0: pColor = "silver"; break;
+ case 0x808080: pColor = "gray"; break;
+ case 0xFFFFFF: pColor = "white"; break;
+ case 0x800000: pColor = "maroon"; break;
+ case 0xFF0000: pColor = "red"; break;
+ case 0x800080: pColor = "purple"; break;
+ case 0xFF00FF: pColor = "fuchsia"; break;
+ case 0x008000: pColor = "green"; break;
+ case 0x00FF00: pColor = "lime"; break;
+ case 0x808000: pColor = "olive"; break;
+ case 0xFFFF00: pColor = "yellow"; break;
+ case 0x000080: pColor = "navy"; break;
+ case 0x0000FF: pColor = "blue"; break;
+ case 0x008080: pColor = "teal"; break;
+ case 0x00FFFF: pColor = "aqua"; break;
+ default:
+ {
+ snprintf( pRgbColor, sizeof( pRgbColor ), "#%06x", static_cast< unsigned int >( nColor ) ); // not too handy to use OString::valueOf() here :-(
+ pColor = pRgbColor;
+ }
+ break;
+ }
+
+ pAttrList->add( nElement, pColor );
+}
+
+static void impl_AddInt( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, sal_uInt32 nValue )
+{
+ if ( !pAttrList )
+ return;
+
+ pAttrList->add( nElement, OString::number( nValue ).getStr() );
+}
+
+static sal_uInt16 impl_GetUInt16( const sal_uInt8* &pVal )
+{
+ sal_uInt16 nRet = *pVal++;
+ nRet += ( *pVal++ ) << 8;
+ return nRet;
+}
+
+static sal_Int32 impl_GetPointComponent( const sal_uInt8* &pVal, sal_uInt16 nPointSize )
+{
+ sal_Int32 nRet = 0;
+ if ( ( nPointSize == 0xfff0 ) || ( nPointSize == 4 ) )
+ {
+ sal_uInt16 nUnsigned = *pVal++;
+ nUnsigned += ( *pVal++ ) << 8;
+
+ nRet = sal_Int16( nUnsigned );
+ }
+ else if ( nPointSize == 8 )
+ {
+ sal_uInt32 nUnsigned = *pVal++;
+ nUnsigned += ( *pVal++ ) << 8;
+ nUnsigned += ( *pVal++ ) << 16;
+ nUnsigned += ( *pVal++ ) << 24;
+
+ nRet = nUnsigned;
+ }
+
+ return nRet;
+}
+
+void VMLExport::AddSdrObjectVMLObject( const SdrObject& rObj)
+{
+ m_pSdrObject = &rObj;
+}
+void VMLExport::Commit( EscherPropertyContainer& rProps, const tools::Rectangle& rRect )
+{
+ if ( m_nShapeType == ESCHER_ShpInst_Nil )
+ return;
+
+ // postpone the output of the embedded elements so that they are written
+ // inside the shapes
+ m_pSerializer->mark(Tag_Commit);
+
+ // dimensions
+ if ( m_nShapeType == ESCHER_ShpInst_Line )
+ AddLineDimensions( rRect );
+ else
+ {
+ if ( IsWaterMarkShape( m_pSdrObject->GetName() ) )
+ {
+ // Watermark need some padding to be compatible with MSO
+ tools::Long nPaddingY = 0;
+ const SfxItemSet& rSet = m_pSdrObject->GetMergedItemSet();
+ if ( const SdrMetricItem* pItem = rSet.GetItem( SDRATTR_TEXT_UPPERDIST ) )
+ nPaddingY += pItem->GetValue();
+
+ tools::Rectangle aRect( rRect );
+ aRect.setHeight( aRect.getHeight() + nPaddingY );
+ AddRectangleDimensions( m_ShapeStyle, aRect );
+ }
+ else
+ AddRectangleDimensions( m_ShapeStyle, rRect );
+ }
+
+ // properties
+ bool bAlreadyWritten[ 0xFFF ] = {};
+ const EscherProperties &rOpts = rProps.GetOpts();
+ for (auto const& opt : rOpts)
+ {
+ sal_uInt16 nId = ( opt.nPropId & 0x0FFF );
+
+ if ( bAlreadyWritten[ nId ] )
+ continue;
+
+ switch ( nId )
+ {
+ case ESCHER_Prop_WrapText: // 133
+ {
+ const char *pWrapType = nullptr;
+ switch ( opt.nPropValue )
+ {
+ case ESCHER_WrapSquare:
+ case ESCHER_WrapByPoints: pWrapType = "square"; break; // these two are equivalent according to the docu
+ case ESCHER_WrapNone: pWrapType = "none"; break;
+ case ESCHER_WrapTopBottom:
+ case ESCHER_WrapThrough:
+ break; // last two are *undefined* in MS-ODRAW, don't exist in VML
+ }
+ if ( pWrapType )
+ {
+ m_ShapeStyle.append(";mso-wrap-style:");
+ m_ShapeStyle.append(pWrapType);
+ }
+ }
+ bAlreadyWritten[ ESCHER_Prop_WrapText ] = true;
+ break;
+
+ case ESCHER_Prop_AnchorText: // 135
+ {
+ char const* pValue(nullptr);
+ switch (opt.nPropValue)
+ {
+ case ESCHER_AnchorTop:
+ pValue = "top";
+ break;
+ case ESCHER_AnchorMiddle:
+ pValue = "middle";
+ break;
+ case ESCHER_AnchorBottom:
+ pValue = "bottom";
+ break;
+ case ESCHER_AnchorTopCentered:
+ pValue = "top-center";
+ break;
+ case ESCHER_AnchorMiddleCentered:
+ pValue = "middle-center";
+ break;
+ case ESCHER_AnchorBottomCentered:
+ pValue = "bottom-center";
+ break;
+ case ESCHER_AnchorTopBaseline:
+ pValue = "top-baseline";
+ break;
+ case ESCHER_AnchorBottomBaseline:
+ pValue = "bottom-baseline";
+ break;
+ case ESCHER_AnchorTopCenteredBaseline:
+ pValue = "top-center-baseline";
+ break;
+ case ESCHER_AnchorBottomCenteredBaseline:
+ pValue = "bottom-center-baseline";
+ break;
+ }
+ m_ShapeStyle.append(";v-text-anchor:");
+ m_ShapeStyle.append(pValue);
+ }
+ break;
+
+ case ESCHER_Prop_txflTextFlow: // 136
+ {
+ // at least "bottom-to-top" only has an effect when it's on the v:textbox element, not on v:shape
+ assert(m_TextboxStyle.isEmpty());
+ switch (opt.nPropValue)
+ {
+ case ESCHER_txflHorzN:
+ m_TextboxStyle.append("layout-flow:horizontal");
+ break;
+ case ESCHER_txflTtoBA:
+ m_TextboxStyle.append("layout-flow:vertical");
+ break;
+ case ESCHER_txflBtoT:
+ m_TextboxStyle.append("mso-layout-flow-alt:bottom-to-top");
+ break;
+ default:
+ assert(false); // unimplemented in escher export
+ break;
+ }
+ }
+ break;
+
+ // coordorigin
+ case ESCHER_Prop_geoLeft: // 320
+ case ESCHER_Prop_geoTop: // 321
+ {
+ sal_uInt32 nLeft = 0, nTop = 0;
+
+ if ( nId == ESCHER_Prop_geoLeft )
+ {
+ nLeft = opt.nPropValue;
+ rProps.GetOpt( ESCHER_Prop_geoTop, nTop );
+ }
+ else
+ {
+ nTop = opt.nPropValue;
+ rProps.GetOpt( ESCHER_Prop_geoLeft, nLeft );
+ }
+ if(nTop!=0 && nLeft!=0)
+ m_pShapeAttrList->add( XML_coordorigin,
+ OString::number( nLeft ) + "," + OString::number( nTop ) );
+ }
+ bAlreadyWritten[ ESCHER_Prop_geoLeft ] = true;
+ bAlreadyWritten[ ESCHER_Prop_geoTop ] = true;
+ break;
+
+ // coordsize
+ case ESCHER_Prop_geoRight: // 322
+ case ESCHER_Prop_geoBottom: // 323
+ {
+ sal_uInt32 nLeft = 0, nRight = 0, nTop = 0, nBottom = 0;
+ rProps.GetOpt( ESCHER_Prop_geoLeft, nLeft );
+ rProps.GetOpt( ESCHER_Prop_geoTop, nTop );
+
+ if ( nId == ESCHER_Prop_geoRight )
+ {
+ nRight = opt.nPropValue;
+ rProps.GetOpt( ESCHER_Prop_geoBottom, nBottom );
+ }
+ else
+ {
+ nBottom = opt.nPropValue;
+ rProps.GetOpt( ESCHER_Prop_geoRight, nRight );
+ }
+
+ if(nBottom!=0 && nRight!=0 )
+ m_pShapeAttrList->add( XML_coordsize,
+ OString::number( nRight - nLeft ) + "," + OString::number( nBottom - nTop ) );
+ }
+ bAlreadyWritten[ ESCHER_Prop_geoRight ] = true;
+ bAlreadyWritten[ ESCHER_Prop_geoBottom ] = true;
+ break;
+
+ case ESCHER_Prop_pVertices: // 325
+ case ESCHER_Prop_pSegmentInfo: // 326
+ {
+ EscherPropSortStruct aVertices;
+ EscherPropSortStruct aSegments;
+
+ if ( rProps.GetOpt( ESCHER_Prop_pVertices, aVertices ) &&
+ rProps.GetOpt( ESCHER_Prop_pSegmentInfo, aSegments ) )
+ {
+ const sal_uInt8 *pVerticesIt = aVertices.nProp.data() + 6;
+ const sal_uInt8 *pSegmentIt = aSegments.nProp.data();
+ OStringBuffer aPath( 512 );
+
+ sal_uInt16 nPointSize = aVertices.nProp[4] + ( aVertices.nProp[5] << 8 );
+
+ // number of segments
+ sal_uInt16 nSegments = impl_GetUInt16( pSegmentIt );
+ pSegmentIt += 4;
+
+ for ( ; nSegments; --nSegments )
+ {
+ sal_uInt16 nSeg = impl_GetUInt16( pSegmentIt );
+
+ // The segment type is stored in the upper 3 bits
+ // and segment count is stored in the lower 13
+ // bits.
+ unsigned char nSegmentType = (nSeg & 0xE000) >> 13;
+ unsigned short nSegmentCount = nSeg & 0x03FF;
+
+ switch (nSegmentType)
+ {
+ case msopathMoveTo:
+ {
+ sal_Int32 nX = impl_GetPointComponent( pVerticesIt, nPointSize );
+ sal_Int32 nY = impl_GetPointComponent( pVerticesIt, nPointSize );
+ if (nX >= 0 && nY >= 0 )
+ aPath.append( "m" + OString::number( nX ) + "," + OString::number( nY ) );
+ break;
+ }
+ case msopathClientEscape:
+ break;
+ case msopathEscape:
+ {
+ // If the segment type is msopathEscape, the lower 13 bits are
+ // divided in a 5 bit escape code and 8 bit
+ // vertex count (not segment count!)
+ unsigned char nEscapeCode = (nSegmentCount & 0x1F00) >> 8;
+ unsigned char nVertexCount = nSegmentCount & 0x00FF;
+ pVerticesIt += nVertexCount;
+
+ switch (nEscapeCode)
+ {
+ case 0xa: // nofill
+ aPath.append( "nf" );
+ break;
+ case 0xb: // nostroke
+ aPath.append( "ns" );
+ break;
+ }
+
+ break;
+ }
+ case msopathLineTo:
+ for (unsigned short i = 0; i < nSegmentCount; ++i)
+ {
+ sal_Int32 nX = impl_GetPointComponent( pVerticesIt, nPointSize );
+ sal_Int32 nY = impl_GetPointComponent( pVerticesIt, nPointSize );
+ aPath.append( "l" + OString::number( nX ) + "," + OString::number( nY ) );
+ }
+ break;
+ case msopathCurveTo:
+ for (unsigned short i = 0; i < nSegmentCount; ++i)
+ {
+ sal_Int32 nX1 = impl_GetPointComponent( pVerticesIt, nPointSize );
+ sal_Int32 nY1 = impl_GetPointComponent( pVerticesIt, nPointSize );
+ sal_Int32 nX2 = impl_GetPointComponent( pVerticesIt, nPointSize );
+ sal_Int32 nY2 = impl_GetPointComponent( pVerticesIt, nPointSize );
+ sal_Int32 nX3 = impl_GetPointComponent( pVerticesIt, nPointSize );
+ sal_Int32 nY3 = impl_GetPointComponent( pVerticesIt, nPointSize );
+ aPath.append( "c" + OString::number( nX1 ) + "," + OString::number( nY1 ) + "," +
+ OString::number( nX2 ) + "," + OString::number( nY2 ) + "," +
+ OString::number( nX3 ) + "," + OString::number( nY3 ) );
+ }
+ break;
+ case msopathClose:
+ aPath.append( "x" );
+ break;
+ case msopathEnd:
+ aPath.append( "e" );
+ break;
+ default:
+ SAL_WARN("oox", "Totally b0rked");
+ break;
+ case msopathInvalid:
+ SAL_WARN("oox", "Invalid - should never be found");
+ break;
+ }
+ }
+ OString pathString = aPath.makeStringAndClear();
+ if ( !pathString.isEmpty() && pathString != "xe" )
+ m_pShapeAttrList->add( XML_path, pathString );
+ }
+ else
+ SAL_WARN("oox.vml", "unhandled shape path, missing either pVertices or pSegmentInfo.");
+ }
+ bAlreadyWritten[ ESCHER_Prop_pVertices ] = true;
+ bAlreadyWritten[ ESCHER_Prop_pSegmentInfo ] = true;
+ break;
+
+ case ESCHER_Prop_fillType: // 384
+ case ESCHER_Prop_fillColor: // 385
+ case ESCHER_Prop_fillBackColor: // 387
+ case ESCHER_Prop_fillBlip: // 390
+ case ESCHER_Prop_fNoFillHitTest: // 447
+ case ESCHER_Prop_fillOpacity: // 386
+ {
+ sal_uInt32 nValue;
+ rtl::Reference<sax_fastparser::FastAttributeList> pAttrList
+ = FastSerializerHelper::createAttrList();
+
+ bool imageData = false;
+ EscherPropSortStruct aStruct;
+ const SdrGrafObj* pSdrGrafObj = dynamic_cast<const SdrGrafObj*>(m_pSdrObject);
+
+ if (pSdrGrafObj && pSdrGrafObj->isSignatureLine() && m_pTextExport)
+ {
+ rtl::Reference<sax_fastparser::FastAttributeList> pAttrListSignatureLine
+ = FastSerializerHelper::createAttrList();
+ pAttrListSignatureLine->add(XML_issignatureline, "t");
+ if (!pSdrGrafObj->getSignatureLineId().isEmpty())
+ {
+ pAttrListSignatureLine->add(
+ XML_id, pSdrGrafObj->getSignatureLineId());
+ }
+ if (!pSdrGrafObj->getSignatureLineSuggestedSignerName().isEmpty())
+ {
+ pAttrListSignatureLine->add(
+ FSNS(XML_o, XML_suggestedsigner),
+ pSdrGrafObj->getSignatureLineSuggestedSignerName());
+ }
+ if (!pSdrGrafObj->getSignatureLineSuggestedSignerTitle().isEmpty())
+ {
+ pAttrListSignatureLine->add(
+ FSNS(XML_o, XML_suggestedsigner2),
+ pSdrGrafObj->getSignatureLineSuggestedSignerTitle());
+ }
+ if (!pSdrGrafObj->getSignatureLineSuggestedSignerEmail().isEmpty())
+ {
+ pAttrListSignatureLine->add(
+ FSNS(XML_o, XML_suggestedsigneremail),
+ pSdrGrafObj->getSignatureLineSuggestedSignerEmail());
+ }
+ if (!pSdrGrafObj->getSignatureLineSigningInstructions().isEmpty())
+ {
+ pAttrListSignatureLine->add(XML_signinginstructionsset, "t");
+ pAttrListSignatureLine->add(
+ FSNS(XML_o, XML_signinginstructions),
+ pSdrGrafObj->getSignatureLineSigningInstructions());
+ }
+ pAttrListSignatureLine->add(
+ XML_showsigndate,
+ pSdrGrafObj->isSignatureLineShowSignDate() ? "t" : "f");
+ pAttrListSignatureLine->add(
+ XML_allowcomments,
+ pSdrGrafObj->isSignatureLineCanAddComment() ? "t" : "f");
+
+ m_pSerializer->singleElementNS(
+ XML_o, XML_signatureline,
+ pAttrListSignatureLine);
+
+ // Get signature line graphic
+ const uno::Reference<graphic::XGraphic>& xGraphic
+ = pSdrGrafObj->getSignatureLineUnsignedGraphic();
+ Graphic aGraphic(xGraphic);
+ OUString aImageId = m_pTextExport->GetDrawingML().WriteImage(aGraphic, false);
+ pAttrList->add(FSNS(XML_r, XML_id), aImageId);
+ imageData = true;
+ }
+ else if (rProps.GetOpt(ESCHER_Prop_fillBlip, aStruct) && m_pTextExport)
+ {
+ SvMemoryStream aStream;
+ // The first bytes are WW8-specific, we're only interested in the PNG
+ int nHeaderSize = 25;
+ aStream.WriteBytes(aStruct.nProp.data() + nHeaderSize,
+ aStruct.nProp.size() - nHeaderSize);
+ aStream.Seek(0);
+ Graphic aGraphic;
+ GraphicConverter::Import(aStream, aGraphic);
+ OUString aImageId = m_pTextExport->GetDrawingML().WriteImage(aGraphic, false);
+ pAttrList->add(FSNS(XML_r, XML_id), aImageId);
+ imageData = true;
+ }
+
+ if (rProps.GetOpt(ESCHER_Prop_fNoFillHitTest, nValue))
+ impl_AddBool(pAttrList.get(), FSNS(XML_o, XML_detectmouseclick), nValue != 0);
+
+ if (imageData)
+ m_pSerializer->singleElementNS( XML_v, XML_imagedata, pAttrList );
+ else
+ {
+ if ( rProps.GetOpt( ESCHER_Prop_fillType, nValue ) )
+ {
+ const char *pFillType = nullptr;
+ switch ( nValue )
+ {
+ case ESCHER_FillSolid: pFillType = "solid"; break;
+ // TODO case ESCHER_FillPattern: pFillType = ""; break;
+ case ESCHER_FillTexture: pFillType = "tile"; break;
+ // TODO case ESCHER_FillPicture: pFillType = ""; break;
+ // TODO case ESCHER_FillShade: pFillType = ""; break;
+ // TODO case ESCHER_FillShadeCenter: pFillType = ""; break;
+ // TODO case ESCHER_FillShadeShape: pFillType = ""; break;
+ // TODO case ESCHER_FillShadeScale: pFillType = ""; break;
+ // TODO case ESCHER_FillShadeTitle: pFillType = ""; break;
+ // TODO case ESCHER_FillBackground: pFillType = ""; break;
+ default:
+ SAL_INFO("oox.vml", "Unhandled fill type: " << nValue);
+ break;
+ }
+ if ( pFillType )
+ pAttrList->add( XML_type, pFillType );
+ }
+ else if (!rProps.GetOpt(ESCHER_Prop_fillColor, nValue))
+ pAttrList->add( XML_on, "false" );
+
+ if ( rProps.GetOpt( ESCHER_Prop_fillColor, nValue ) )
+ impl_AddColor( m_pShapeAttrList.get(), XML_fillcolor, nValue );
+
+ if ( rProps.GetOpt( ESCHER_Prop_fillBackColor, nValue ) )
+ impl_AddColor( pAttrList.get(), XML_color2, nValue );
+
+
+ if (rProps.GetOpt(ESCHER_Prop_fillOpacity, nValue))
+ // Partly undo the transformation at the end of EscherPropertyContainer::CreateFillProperties(): VML opacity is 0..1.
+ pAttrList->add(XML_opacity, OString::number(double((nValue * 100) >> 16) / 100));
+ m_pSerializer->singleElementNS( XML_v, XML_fill, pAttrList );
+
+ }
+ }
+ bAlreadyWritten[ ESCHER_Prop_fillType ] = true;
+ bAlreadyWritten[ ESCHER_Prop_fillColor ] = true;
+ bAlreadyWritten[ ESCHER_Prop_fillBackColor ] = true;
+ bAlreadyWritten[ ESCHER_Prop_fillBlip ] = true;
+ bAlreadyWritten[ ESCHER_Prop_fNoFillHitTest ] = true;
+ bAlreadyWritten[ ESCHER_Prop_fillOpacity ] = true;
+ break;
+
+ case ESCHER_Prop_lineColor: // 448
+ case ESCHER_Prop_lineWidth: // 459
+ case ESCHER_Prop_lineDashing: // 462
+ case ESCHER_Prop_lineStartArrowhead: // 464
+ case ESCHER_Prop_lineEndArrowhead: // 465
+ case ESCHER_Prop_lineStartArrowWidth: // 466
+ case ESCHER_Prop_lineStartArrowLength: // 467
+ case ESCHER_Prop_lineEndArrowWidth: // 468
+ case ESCHER_Prop_lineEndArrowLength: // 469
+ case ESCHER_Prop_lineJoinStyle: // 470
+ case ESCHER_Prop_lineEndCapStyle: // 471
+ {
+ sal_uInt32 nValue;
+ rtl::Reference<sax_fastparser::FastAttributeList> pAttrList = FastSerializerHelper::createAttrList();
+
+ if ( rProps.GetOpt( ESCHER_Prop_lineColor, nValue ) )
+ impl_AddColor( pAttrList.get(), XML_color, nValue );
+
+ if ( rProps.GetOpt( ESCHER_Prop_lineWidth, nValue ) )
+ impl_AddInt( pAttrList.get(), XML_weight, nValue );
+
+ if ( rProps.GetOpt( ESCHER_Prop_lineDashing, nValue ) )
+ {
+ const char *pDashStyle = nullptr;
+ switch ( nValue )
+ {
+ case ESCHER_LineSolid: pDashStyle = "solid"; break;
+ case ESCHER_LineDashSys: pDashStyle = "shortdash"; break;
+ case ESCHER_LineDotSys: pDashStyle = "shortdot"; break;
+ case ESCHER_LineDashDotSys: pDashStyle = "shortdashdot"; break;
+ case ESCHER_LineDashDotDotSys: pDashStyle = "shortdashdotdot"; break;
+ case ESCHER_LineDotGEL: pDashStyle = "dot"; break;
+ case ESCHER_LineDashGEL: pDashStyle = "dash"; break;
+ case ESCHER_LineLongDashGEL: pDashStyle = "longdash"; break;
+ case ESCHER_LineDashDotGEL: pDashStyle = "dashdot"; break;
+ case ESCHER_LineLongDashDotGEL: pDashStyle = "longdashdot"; break;
+ case ESCHER_LineLongDashDotDotGEL: pDashStyle = "longdashdotdot"; break;
+ }
+ if ( pDashStyle )
+ pAttrList->add( XML_dashstyle, pDashStyle );
+ }
+
+ if ( rProps.GetOpt( ESCHER_Prop_lineStartArrowhead, nValue ) )
+ impl_AddArrowHead( pAttrList.get(), XML_startarrow, nValue );
+
+ if ( rProps.GetOpt( ESCHER_Prop_lineEndArrowhead, nValue ) )
+ impl_AddArrowHead( pAttrList.get(), XML_endarrow, nValue );
+
+ if ( rProps.GetOpt( ESCHER_Prop_lineStartArrowWidth, nValue ) )
+ impl_AddArrowWidth( pAttrList.get(), XML_startarrowwidth, nValue );
+
+ if ( rProps.GetOpt( ESCHER_Prop_lineStartArrowLength, nValue ) )
+ impl_AddArrowLength( pAttrList.get(), XML_startarrowlength, nValue );
+
+ if ( rProps.GetOpt( ESCHER_Prop_lineEndArrowWidth, nValue ) )
+ impl_AddArrowWidth( pAttrList.get(), XML_endarrowwidth, nValue );
+
+ if ( rProps.GetOpt( ESCHER_Prop_lineEndArrowLength, nValue ) )
+ impl_AddArrowLength( pAttrList.get(), XML_endarrowlength, nValue );
+
+ if ( rProps.GetOpt( ESCHER_Prop_lineJoinStyle, nValue ) )
+ {
+ const char *pJoinStyle = nullptr;
+ switch ( nValue )
+ {
+ case ESCHER_LineJoinBevel: pJoinStyle = "bevel"; break;
+ case ESCHER_LineJoinMiter: pJoinStyle = "miter"; break;
+ case ESCHER_LineJoinRound: pJoinStyle = "round"; break;
+ }
+ if ( pJoinStyle )
+ pAttrList->add( XML_joinstyle, pJoinStyle );
+ }
+
+ if ( rProps.GetOpt( ESCHER_Prop_lineEndCapStyle, nValue ) )
+ {
+ const char *pEndCap = nullptr;
+ switch ( nValue )
+ {
+ case ESCHER_LineEndCapRound: pEndCap = "round"; break;
+ case ESCHER_LineEndCapSquare: pEndCap = "square"; break;
+ case ESCHER_LineEndCapFlat: pEndCap = "flat"; break;
+ }
+ if ( pEndCap )
+ pAttrList->add( XML_endcap, pEndCap );
+ }
+
+ m_pSerializer->singleElementNS( XML_v, XML_stroke, pAttrList );
+ }
+ bAlreadyWritten[ ESCHER_Prop_lineColor ] = true;
+ bAlreadyWritten[ ESCHER_Prop_lineWidth ] = true;
+ bAlreadyWritten[ ESCHER_Prop_lineDashing ] = true;
+ bAlreadyWritten[ ESCHER_Prop_lineStartArrowhead ] = true;
+ bAlreadyWritten[ ESCHER_Prop_lineEndArrowhead ] = true;
+ bAlreadyWritten[ ESCHER_Prop_lineStartArrowWidth ] = true;
+ bAlreadyWritten[ ESCHER_Prop_lineStartArrowLength ] = true;
+ bAlreadyWritten[ ESCHER_Prop_lineEndArrowWidth ] = true;
+ bAlreadyWritten[ ESCHER_Prop_lineEndArrowLength ] = true;
+ bAlreadyWritten[ ESCHER_Prop_lineJoinStyle ] = true;
+ bAlreadyWritten[ ESCHER_Prop_lineEndCapStyle ] = true;
+ break;
+
+ case ESCHER_Prop_fHidden:
+ if ( !opt.nPropValue )
+ m_ShapeStyle.append( ";visibility:hidden" );
+ break;
+ case ESCHER_Prop_shadowColor:
+ case ESCHER_Prop_fshadowObscured:
+ {
+ sal_uInt32 nValue = 0;
+ bool bShadow = false;
+ bool bObscured = false;
+ if ( rProps.GetOpt( ESCHER_Prop_fshadowObscured, nValue ) )
+ {
+ bShadow = (( nValue & 0x20002 ) == 0x20002 );
+ bObscured = (( nValue & 0x10001 ) == 0x10001 );
+ }
+ if ( bShadow )
+ {
+ rtl::Reference<sax_fastparser::FastAttributeList> pAttrList = FastSerializerHelper::createAttrList();
+ impl_AddBool( pAttrList.get(), XML_on, bShadow );
+ impl_AddBool( pAttrList.get(), XML_obscured, bObscured );
+
+ if ( rProps.GetOpt( ESCHER_Prop_shadowColor, nValue ) )
+ impl_AddColor( pAttrList.get(), XML_color, nValue );
+
+ m_pSerializer->singleElementNS( XML_v, XML_shadow, pAttrList );
+ bAlreadyWritten[ ESCHER_Prop_fshadowObscured ] = true;
+ bAlreadyWritten[ ESCHER_Prop_shadowColor ] = true;
+ }
+ }
+ break;
+ case ESCHER_Prop_gtextUNICODE:
+ case ESCHER_Prop_gtextFont:
+ {
+ EscherPropSortStruct aUnicode;
+ if (rProps.GetOpt(ESCHER_Prop_gtextUNICODE, aUnicode))
+ {
+ SvMemoryStream aStream;
+
+ if(!opt.nProp.empty())
+ {
+ aStream.WriteBytes(opt.nProp.data(), opt.nProp.size());
+ }
+
+ aStream.Seek(0);
+ OUString aTextPathString = SvxMSDffManager::MSDFFReadZString(aStream, opt.nProp.size(), true);
+ aStream.Seek(0);
+
+ m_pSerializer->singleElementNS(XML_v, XML_path, XML_textpathok, "t");
+
+ rtl::Reference<sax_fastparser::FastAttributeList> pAttrList = FastSerializerHelper::createAttrList();
+ pAttrList->add(XML_on, "t");
+ pAttrList->add(XML_fitshape, "t");
+ pAttrList->add(XML_string, aTextPathString);
+ EscherPropSortStruct aFont;
+ OUString aStyle;
+ if (rProps.GetOpt(ESCHER_Prop_gtextFont, aFont))
+ {
+ aStream.WriteBytes(aFont.nProp.data(), aFont.nProp.size());
+ aStream.Seek(0);
+ OUString aTextPathFont = SvxMSDffManager::MSDFFReadZString(aStream, aFont.nProp.size(), true);
+ aStyle += "font-family:\"" + aTextPathFont + "\"";
+ }
+ sal_uInt32 nSize;
+ if (rProps.GetOpt(ESCHER_Prop_gtextSize, nSize))
+ {
+ float nSizeF = static_cast<sal_Int32>(nSize) / 65536.0;
+ OUString aSize = OUString::number(nSizeF);
+ aStyle += ";font-size:" + aSize + "pt";
+ }
+ if (IsWaterMarkShape(m_pSdrObject->GetName()))
+ pAttrList->add(XML_trim, "t");
+
+ if (!aStyle.isEmpty())
+ pAttrList->add(XML_style, aStyle);
+ m_pSerializer->singleElementNS(XML_v, XML_textpath, pAttrList);
+ }
+
+ bAlreadyWritten[ESCHER_Prop_gtextUNICODE] = true;
+ bAlreadyWritten[ESCHER_Prop_gtextFont] = true;
+ }
+ break;
+ case ESCHER_Prop_Rotation:
+ {
+ // The higher half of the variable contains the angle.
+ m_ShapeStyle.append(";rotation:" + OString::number(double(opt.nPropValue >> 16)));
+ bAlreadyWritten[ESCHER_Prop_Rotation] = true;
+ }
+ break;
+ case ESCHER_Prop_fNoLineDrawDash:
+ {
+ // See DffPropertyReader::ApplyLineAttributes().
+ impl_AddBool( m_pShapeAttrList.get(), XML_stroked, (opt.nPropValue & 8) != 0 );
+ bAlreadyWritten[ESCHER_Prop_fNoLineDrawDash] = true;
+ }
+ break;
+ case ESCHER_Prop_wzName:
+ {
+ SvMemoryStream aStream;
+
+ if(!opt.nProp.empty())
+ {
+ aStream.WriteBytes(opt.nProp.data(), opt.nProp.size());
+ }
+
+ aStream.Seek(0);
+ OUString idStr = SvxMSDffManager::MSDFFReadZString(aStream, opt.nProp.size(), true);
+ aStream.Seek(0);
+ if (!IsWaterMarkShape(m_pSdrObject->GetName()) && !m_bSkipwzName)
+ m_pShapeAttrList->add(XML_ID, idStr);
+
+ bAlreadyWritten[ESCHER_Prop_wzName] = true;
+ }
+ break;
+ default:
+#if OSL_DEBUG_LEVEL > 0
+ const size_t opt_nProp_size(opt.nProp.size());
+ const sal_uInt8 opt_nProp_empty(0);
+ fprintf( stderr, "TODO VMLExport::Commit(), unimplemented id: %d, value: %" SAL_PRIuUINT32 ", data: [%zu, %p]\n",
+ nId,
+ opt.nPropValue,
+ opt_nProp_size,
+ 0 == opt_nProp_size ? &opt_nProp_empty : opt.nProp.data());
+ if ( opt.nProp.size() )
+ {
+ const sal_uInt8 *pIt = opt.nProp.data();
+ fprintf( stderr, " ( " );
+ for ( int nCount = opt.nProp.size(); nCount; --nCount )
+ {
+ fprintf( stderr, "%02x ", *pIt );
+ ++pIt;
+ }
+ fprintf( stderr, ")\n" );
+ }
+#endif
+ break;
+ }
+ }
+
+ m_pSerializer->mergeTopMarks(Tag_Commit, sax_fastparser::MergeMarks::POSTPONE );
+}
+
+OString VMLExport::ShapeIdString( sal_uInt32 nId )
+{
+ if(m_bOverrideShapeIdGeneration)
+ return m_sShapeIDPrefix + OString::number( nId );
+ else
+ return "shape_" + OString::number( nId );
+}
+
+void VMLExport::AddFlipXY( )
+{
+ if (m_nShapeFlags & (ShapeFlag::FlipH | ShapeFlag::FlipV))
+ {
+ m_ShapeStyle.append( ";flip:" );
+
+ if (m_nShapeFlags & ShapeFlag::FlipH)
+ m_ShapeStyle.append( "x" );
+
+ if (m_nShapeFlags & ShapeFlag::FlipV)
+ m_ShapeStyle.append( "y" );
+ }
+}
+
+void VMLExport::AddLineDimensions( const tools::Rectangle& rRectangle )
+{
+ // style
+ if (!m_ShapeStyle.isEmpty())
+ m_ShapeStyle.append( ";" );
+
+ m_ShapeStyle.append( "position:absolute" );
+
+ AddFlipXY();
+
+ // the actual dimensions
+ OString aLeft, aTop, aRight, aBottom;
+
+ if ( mnGroupLevel == 1 )
+ {
+ const OString aPt( "pt" );
+ aLeft = OString::number( double( rRectangle.Left() ) / 20 ) + aPt;
+ aTop = OString::number( double( rRectangle.Top() ) / 20 ) + aPt;
+ aRight = OString::number( double( rRectangle.Right() ) / 20 ) + aPt;
+ aBottom = OString::number( double( rRectangle.Bottom() ) / 20 ) + aPt;
+ }
+ else
+ {
+ aLeft = OString::number( rRectangle.Left() );
+ aTop = OString::number( rRectangle.Top() );
+ aRight = OString::number( rRectangle.Right() );
+ aBottom = OString::number( rRectangle.Bottom() );
+ }
+
+ m_pShapeAttrList->add( XML_from, aLeft + "," + aTop );
+
+ m_pShapeAttrList->add( XML_to, aRight + "," + aBottom );
+}
+
+void VMLExport::AddRectangleDimensions( OStringBuffer& rBuffer, const tools::Rectangle& rRectangle, bool rbAbsolutePos)
+{
+ if ( !rBuffer.isEmpty() )
+ rBuffer.append( ";" );
+
+ if (rbAbsolutePos && !m_bInline)
+ {
+ rBuffer.append( "position:absolute;" );
+ }
+
+ if(m_bInline)
+ {
+ rBuffer.append( "width:" + OString::number( double( rRectangle.Right() - rRectangle.Left() ) / 20 ) +
+ "pt;height:" + OString::number( double( rRectangle.Bottom() - rRectangle.Top() ) / 20 ) +
+ "pt" );
+ }
+ else if ( mnGroupLevel == 1 )
+ {
+ rBuffer.append( "margin-left:" + OString::number( double( rRectangle.Left() ) / 20 ) +
+ "pt;margin-top:" + OString::number( double( rRectangle.Top() ) / 20 ) +
+ "pt;width:" + OString::number( double( rRectangle.Right() - rRectangle.Left() ) / 20 ) +
+ "pt;height:" + OString::number( double( rRectangle.Bottom() - rRectangle.Top() ) / 20 ) +
+ "pt" );
+ }
+ else
+ {
+ rBuffer.append( "left:" + OString::number( rRectangle.Left() ) +
+ ";top:" + OString::number( rRectangle.Top() ) +
+ ";width:" + OString::number( rRectangle.Right() - rRectangle.Left() ) +
+ ";height:" + OString::number( rRectangle.Bottom() - rRectangle.Top() ) );
+ }
+
+ AddFlipXY();
+}
+
+void VMLExport::AddShapeAttribute( sal_Int32 nAttribute, const OString& rValue )
+{
+ m_pShapeAttrList->add( nAttribute, rValue );
+}
+
+static std::vector<OString> lcl_getShapeTypes()
+{
+ std::vector<OString> aRet;
+
+ OUString aPath("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/filter/vml-shape-types");
+ rtl::Bootstrap::expandMacros(aPath);
+ SvFileStream aStream(aPath, StreamMode::READ);
+ if (aStream.GetError() != ERRCODE_NONE)
+ SAL_WARN("oox", "failed to open vml-shape-types");
+ OStringBuffer aLine;
+ bool bNotDone = aStream.ReadLine(aLine);
+ while (bNotDone)
+ {
+ // Filter out comments.
+ if (!o3tl::starts_with(aLine, "/"))
+ aRet.push_back(OString(aLine));
+ bNotDone = aStream.ReadLine(aLine);
+ }
+ return aRet;
+}
+
+static bool lcl_isTextBox(const SdrObject* pSdrObject)
+{
+ uno::Reference<beans::XPropertySet> xPropertySet(const_cast<SdrObject*>(pSdrObject)->getUnoShape(), uno::UNO_QUERY);
+ if (!xPropertySet.is())
+ return false;
+ uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
+ if (!xPropertySetInfo->hasPropertyByName("TextBox"))
+ return false;
+ css::uno::Any aTextBox(xPropertySet->getPropertyValue("TextBox"));
+ if (!aTextBox.hasValue())
+ return false;
+ return aTextBox.get<bool>();
+}
+
+static OUString lcl_getAnchorIdFromGrabBag(const SdrObject* pSdrObject)
+{
+ OUString aResult;
+
+ uno::Reference<beans::XPropertySet> xShape(const_cast<SdrObject*>(pSdrObject)->getUnoShape(), uno::UNO_QUERY);
+ if (xShape->getPropertySetInfo()->hasPropertyByName("InteropGrabBag"))
+ {
+ comphelper::SequenceAsHashMap aInteropGrabBag(xShape->getPropertyValue("InteropGrabBag"));
+ if (aInteropGrabBag.find("AnchorId") != aInteropGrabBag.end())
+ aInteropGrabBag["AnchorId"] >>= aResult;
+ }
+
+ return aResult;
+}
+
+sal_uInt32 VMLExport::GenerateShapeId()
+{
+ if(!m_bOverrideShapeIdGeneration)
+ return EscherEx::GenerateShapeId();
+ else
+ return m_nShapeIDCounter++;
+}
+
+OString VMLExport::GetVMLShapeTypeDefinition(
+ std::string_view sShapeID, const bool bIsPictureFrame )
+{
+ OString sShapeType;
+ if ( !bIsPictureFrame )
+ // We don't have a shape definition for host control in presetShapeDefinitions.xml
+ // So use a definition copied from DOCX file created with MSO
+ sShapeType = OString::Concat("<v:shapetype id=\"_x0000_t") + sShapeID +
+ "\" coordsize=\"21600,21600\" o:spt=\"" + sShapeID +
+ "\" path=\"m,l,21600l21600,21600l21600,xe\">\n"
+ "<v:stroke joinstyle=\"miter\"/>\n"
+ "<v:path shadowok=\"f\" o:extrusionok=\"f\" strokeok=\"f\" fillok=\"f\" o:connecttype=\"rect\"/>\n"
+ "<o:lock v:ext=\"edit\" shapetype=\"t\"/>\n"
+ "</v:shapetype>";
+ else
+ // We don't have a shape definition for picture frame in presetShapeDefinitions.xml
+ // So use a definition copied from DOCX file created with MSO
+ sShapeType = OString::Concat("<v:shapetype id=\"_x0000_t") + sShapeID +
+ "\" coordsize=\"21600,21600\" o:spt=\"" + sShapeID +
+ "\" o:preferrelative=\"t\" path=\"m@4@5l@4@11@9@11@9@5xe\" filled=\"f\" stroked=\"f\">\n"
+ "<v:stroke joinstyle=\"miter\"/>\n"
+ "<v:formulas>\n"
+ "<v:f eqn=\"if lineDrawn pixelLineWidth 0\"/>\n"
+ "<v:f eqn=\"sum @0 1 0\"/>\n"
+ "<v:f eqn=\"sum 0 0 @1\"/>\n"
+ "<v:f eqn=\"prod @2 1 2\"/>\n"
+ "<v:f eqn=\"prod @3 21600 pixelWidth\"/>\n"
+ "<v:f eqn=\"prod @3 21600 pixelHeight\"/>\n"
+ "<v:f eqn=\"sum @0 0 1\"/>\n"
+ "<v:f eqn=\"prod @6 1 2\"/>\n"
+ "<v:f eqn=\"prod @7 21600 pixelWidth\"/>\n"
+ "<v:f eqn=\"sum @8 21600 0\"/>\n"
+ "<v:f eqn=\"prod @7 21600 pixelHeight\"/>\n"
+ "<v:f eqn=\"sum @10 21600 0\"/>\n"
+ "</v:formulas>\n"
+ "<v:path o:extrusionok=\"f\" gradientshapeok=\"t\" o:connecttype=\"rect\"/>\n"
+ "<o:lock v:ext=\"edit\" aspectratio=\"t\"/>\n"
+ "</v:shapetype>";
+ return sShapeType;
+}
+
+sal_Int32 VMLExport::StartShape()
+{
+ if ( m_nShapeType == ESCHER_ShpInst_Nil )
+ return -1;
+
+ // some of the shapes have their own name ;-)
+ sal_Int32 nShapeElement = -1;
+ bool bReferToShapeType = false;
+ switch ( m_nShapeType )
+ {
+ case ESCHER_ShpInst_NotPrimitive: nShapeElement = XML_shape; break;
+ case ESCHER_ShpInst_Rectangle: nShapeElement = XML_rect; break;
+ case ESCHER_ShpInst_RoundRectangle: nShapeElement = XML_roundrect; break;
+ case ESCHER_ShpInst_Ellipse: nShapeElement = XML_oval; break;
+ case ESCHER_ShpInst_Arc: nShapeElement = XML_arc; break;
+ case ESCHER_ShpInst_Line: nShapeElement = XML_line; break;
+ case ESCHER_ShpInst_HostControl:
+ {
+ bReferToShapeType = true;
+ nShapeElement = XML_shape;
+ if ( !m_aShapeTypeWritten[ m_nShapeType ] )
+ {
+ m_pSerializer->write(GetVMLShapeTypeDefinition(OString::number(m_nShapeType), false));
+ m_aShapeTypeWritten[ m_nShapeType ] = true;
+ }
+ break;
+ }
+ case ESCHER_ShpInst_PictureFrame:
+ {
+ bReferToShapeType = true;
+ nShapeElement = XML_shape;
+ if ( !m_aShapeTypeWritten[ m_nShapeType ] )
+ {
+ m_pSerializer->write(GetVMLShapeTypeDefinition(OString::number(m_nShapeType), true));
+ m_aShapeTypeWritten[ m_nShapeType ] = true;
+ }
+ break;
+ }
+ default:
+ if ( m_nShapeType < ESCHER_ShpInst_COUNT )
+ {
+ nShapeElement = XML_shape;
+
+ // a predefined shape?
+ static std::vector<OString> aShapeTypes = lcl_getShapeTypes();
+ SAL_WARN_IF(m_nShapeType >= aShapeTypes.size(), "oox.vml", "Unknown shape type!");
+ if (m_nShapeType < aShapeTypes.size() && aShapeTypes[m_nShapeType] != "NULL")
+ {
+ bReferToShapeType = true;
+ if ( !m_aShapeTypeWritten[ m_nShapeType ] )
+ {
+ m_pSerializer->write(aShapeTypes[m_nShapeType]);
+ m_aShapeTypeWritten[ m_nShapeType ] = true;
+ }
+ }
+ else
+ {
+ // rectangle is probably the best fallback...
+ nShapeElement = XML_rect;
+ }
+ }
+ break;
+ }
+
+ // anchoring
+ switch (m_eHOri)
+ {
+ case text::HoriOrientation::LEFT:
+ m_ShapeStyle.append(";mso-position-horizontal:left");
+ break;
+ case text::HoriOrientation::CENTER:
+ m_ShapeStyle.append(";mso-position-horizontal:center");
+ break;
+ case text::HoriOrientation::RIGHT:
+ m_ShapeStyle.append(";mso-position-horizontal:right");
+ break;
+ case text::HoriOrientation::INSIDE:
+ m_ShapeStyle.append(";mso-position-horizontal:inside");
+ break;
+ case text::HoriOrientation::OUTSIDE:
+ m_ShapeStyle.append(";mso-position-horizontal:outside");
+ break;
+ default:
+ case text::HoriOrientation::NONE:
+ break;
+ }
+ switch (m_eHRel)
+ {
+ case text::RelOrientation::PAGE_PRINT_AREA:
+ m_ShapeStyle.append(";mso-position-horizontal-relative:margin");
+ break;
+ case text::RelOrientation::PAGE_FRAME:
+ case text::RelOrientation::PAGE_LEFT:
+ case text::RelOrientation::PAGE_RIGHT:
+ m_ShapeStyle.append(";mso-position-horizontal-relative:page");
+ break;
+ case text::RelOrientation::CHAR:
+ m_ShapeStyle.append(";mso-position-horizontal-relative:char");
+ break;
+ default:
+ break;
+ }
+
+ switch (m_eVOri)
+ {
+ case text::VertOrientation::TOP:
+ case text::VertOrientation::LINE_TOP:
+ case text::VertOrientation::CHAR_TOP:
+ m_ShapeStyle.append(";mso-position-vertical:top");
+ break;
+ case text::VertOrientation::CENTER:
+ case text::VertOrientation::LINE_CENTER:
+ m_ShapeStyle.append(";mso-position-vertical:center");
+ break;
+ case text::VertOrientation::BOTTOM:
+ case text::VertOrientation::LINE_BOTTOM:
+ case text::VertOrientation::CHAR_BOTTOM:
+ m_ShapeStyle.append(";mso-position-vertical:bottom");
+ break;
+ default:
+ case text::VertOrientation::NONE:
+ break;
+ }
+ switch (m_eVRel)
+ {
+ case text::RelOrientation::PAGE_PRINT_AREA:
+ m_ShapeStyle.append(";mso-position-vertical-relative:margin");
+ break;
+ case text::RelOrientation::PAGE_FRAME:
+ m_ShapeStyle.append(";mso-position-vertical-relative:page");
+ break;
+ default:
+ break;
+ }
+
+ if (!m_pSdrObject->getHyperlink().isEmpty())
+ m_pShapeAttrList->add(
+ XML_href, m_pSdrObject->getHyperlink());
+
+ m_pShapeAttrList->addNS(XML_o, XML_allowincell, m_IsFollowingTextFlow ? "t" : "f");
+
+ // add style
+ m_pShapeAttrList->add( XML_style, m_ShapeStyle.makeStringAndClear() );
+
+ OUString sAnchorId = lcl_getAnchorIdFromGrabBag(m_pSdrObject);
+ if (!sAnchorId.isEmpty())
+ m_pShapeAttrList->addNS(XML_wp14, XML_anchorId, sAnchorId);
+
+ if ( nShapeElement >= 0 && !m_pShapeAttrList->hasAttribute( XML_type ) && bReferToShapeType )
+ {
+ OString sType;
+ if (m_bUseHashMarkForType)
+ sType = "#";
+ m_pShapeAttrList->add( XML_type, sType +
+ "_x0000_t" + OString::number( m_nShapeType ) );
+ }
+
+ // start of the shape
+ m_pSerializer->startElementNS( XML_v, nShapeElement, m_pShapeAttrList );
+
+ OString const textboxStyle(m_TextboxStyle.makeStringAndClear());
+
+ // now check if we have some editeng text (not associated textbox) and we have a text exporter registered
+ const SdrTextObj* pTxtObj = dynamic_cast<const SdrTextObj*>( m_pSdrObject );
+ if (pTxtObj && m_pTextExport && msfilter::util::HasTextBoxContent(m_nShapeType) && !IsWaterMarkShape(m_pSdrObject->GetName()) && !lcl_isTextBox(m_pSdrObject))
+ {
+ std::optional<OutlinerParaObject> pParaObj;
+
+ /*
+ #i13885#
+ When the object is actively being edited, that text is not set into
+ the objects normal text object, but lives in a separate object.
+ */
+ if (pTxtObj->IsTextEditActive())
+ {
+ pParaObj = pTxtObj->CreateEditOutlinerParaObject();
+ }
+ else if (pTxtObj->GetOutlinerParaObject())
+ {
+ pParaObj = *pTxtObj->GetOutlinerParaObject();
+ }
+
+ if( pParaObj )
+ {
+ rtl::Reference<sax_fastparser::FastAttributeList> pTextboxAttrList = FastSerializerHelper::createAttrList();
+ if (!textboxStyle.isEmpty())
+ {
+ pTextboxAttrList->add(XML_style, textboxStyle);
+ }
+
+ // this is reached only in case some text is attached to the shape
+ m_pSerializer->startElementNS(XML_v, XML_textbox, pTextboxAttrList);
+ m_pTextExport->WriteOutliner(*pParaObj);
+ m_pSerializer->endElementNS(XML_v, XML_textbox);
+ }
+ }
+
+ return nShapeElement;
+}
+
+void VMLExport::EndShape( sal_Int32 nShapeElement )
+{
+ if ( nShapeElement < 0 )
+ return;
+
+ if (m_pTextExport && lcl_isTextBox(m_pSdrObject))
+ {
+ uno::Reference<drawing::XShape> xShape {const_cast<SdrObject*>(m_pSdrObject)->getUnoShape(), uno::UNO_QUERY};
+ uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
+ bool bBottomToTop = false;
+ if (xPropertySetInfo->hasPropertyByName("CustomShapeGeometry"))
+ {
+ // In this case a DrawingML DOCX was imported.
+ comphelper::SequenceAsHashMap aCustomShapeProperties(
+ xPropertySet->getPropertyValue("CustomShapeGeometry"));
+ if (aCustomShapeProperties.find("TextPreRotateAngle") != aCustomShapeProperties.end())
+ {
+ sal_Int32 nTextRotateAngle = aCustomShapeProperties["TextPreRotateAngle"].get<sal_Int32>();
+ if (nTextRotateAngle == -270)
+ bBottomToTop = true;
+ }
+ }
+ else
+ {
+ // In this case a pure VML DOCX was imported, so there is no CustomShapeGeometry.
+ auto pTextExport = m_pTextExport->GetDrawingML().GetTextExport();
+ // FIXME: somewhy pTextExport is always nullptr, we should find its reason
+ if (pTextExport)
+ {
+ auto xTextFrame = pTextExport->GetUnoTextFrame(xShape);
+ uno::Reference<beans::XPropertySet> xPropSet(xTextFrame, uno::UNO_QUERY);
+ auto aAny = xPropSet->getPropertyValue("WritingMode");
+ sal_Int16 nWritingMode;
+ if (aAny >>= nWritingMode)
+ {
+ switch (nWritingMode)
+ {
+ case text::WritingMode2::BT_LR:
+ bBottomToTop = true;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ rtl::Reference<sax_fastparser::FastAttributeList> pTextboxAttrList = FastSerializerHelper::createAttrList();
+ if (bBottomToTop)
+ pTextboxAttrList->add(XML_style, "mso-layout-flow-alt:bottom-to-top");
+ m_pSerializer->startElementNS(XML_v, XML_textbox, pTextboxAttrList);
+
+ m_pTextExport->WriteVMLTextBox(uno::Reference<drawing::XShape>(xPropertySet, uno::UNO_QUERY_THROW));
+
+ m_pSerializer->endElementNS(XML_v, XML_textbox);
+ }
+
+ if (m_pWrapAttrList)
+ {
+ m_pSerializer->singleElementNS(XML_w10, XML_wrap, m_pWrapAttrList);
+ }
+
+ // end of the shape
+ m_pSerializer->endElementNS( XML_v, nShapeElement );
+}
+
+OString const & VMLExport::AddSdrObject( const SdrObject& rObj,
+ bool const bIsFollowingTextFlow,
+ sal_Int16 eHOri, sal_Int16 eVOri, sal_Int16 eHRel, sal_Int16 eVRel,
+ FastAttributeList* pWrapAttrList,
+ const bool bOOxmlExport )
+{
+ m_pSdrObject = &rObj;
+ m_eHOri = eHOri;
+ m_eVOri = eVOri;
+ m_eHRel = eHRel;
+ m_eVRel = eVRel;
+ m_pWrapAttrList = pWrapAttrList;
+ m_bInline = false;
+ m_IsFollowingTextFlow = bIsFollowingTextFlow;
+ EscherEx::AddSdrObject(rObj, bOOxmlExport);
+ return m_sShapeId;
+}
+
+OString const & VMLExport::AddInlineSdrObject( const SdrObject& rObj, const bool bOOxmlExport )
+{
+ m_pSdrObject = &rObj;
+ m_eHOri = -1;
+ m_eVOri = -1;
+ m_eHRel = -1;
+ m_eVRel = -1;
+ m_pWrapAttrList.clear();
+ m_bInline = true;
+ m_IsFollowingTextFlow = true;
+ EscherEx::AddSdrObject(rObj, bOOxmlExport);
+ return m_sShapeId;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */