diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /oox/source/export | |
parent | Initial commit. (diff) | |
download | libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.tar.xz libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'oox/source/export')
-rw-r--r-- | oox/source/export/ColorPropertySet.cxx | 178 | ||||
-rw-r--r-- | oox/source/export/ColorPropertySet.hxx | 90 | ||||
-rw-r--r-- | oox/source/export/README | 2 | ||||
-rw-r--r-- | oox/source/export/chartexport.cxx | 4202 | ||||
-rw-r--r-- | oox/source/export/drawingml.cxx | 4634 | ||||
-rw-r--r-- | oox/source/export/ooxml-export-notes.txt | 235 | ||||
-rw-r--r-- | oox/source/export/preset-definitions-to-shape-types.pl | 1237 | ||||
-rw-r--r-- | oox/source/export/presetTextWarpDefinitions.xml | 1902 | ||||
-rw-r--r-- | oox/source/export/shapes.cxx | 2221 | ||||
-rw-r--r-- | oox/source/export/vmlexport.cxx | 1523 |
10 files changed, 16224 insertions, 0 deletions
diff --git a/oox/source/export/ColorPropertySet.cxx b/oox/source/export/ColorPropertySet.cxx new file mode 100644 index 000000000..e2734a8e7 --- /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::makeAny(css::drawing::FillStyle_SOLID); + } + else if (aPropertyName == m_aColorPropName) + return uno::makeAny( 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::makeAny( 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..e1734422a --- /dev/null +++ b/oox/source/export/ColorPropertySet.hxx @@ -0,0 +1,90 @@ +/* -*- 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 +{ +namespace 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 chart +} // namespace xmloff + +// XMLOFF_COLORPROPERTYSET_HXX +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 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..6511cfaac --- /dev/null +++ b/oox/source/export/chartexport.cxx @@ -0,0 +1,4202 @@ +/* -*- 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 <iterator> + +#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/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/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/zforlist.hxx> +#include <svl/numuno.hxx> +#include <tools/diagnose_ex.h> +#include <sal/log.hxx> + +#include <set> +#include <unordered_set> + +#include <rtl/math.hxx> +#include <o3tl/temporary.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 ) +{ + 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()) + { + 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()); + Reference< chart2::data::XLabeledDataSequence > xCategories( lcl_getCategories( xDiagram ) ); + 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 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()); + for( sal_Int32 i=0; i<aAnies.getLength(); ++i ) + aAnies[i] >>= aLabels[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(); + Sequence< OUString > aTextData( xTextualDataSequence->getTextualData()); + ::std::copy( aTextData.begin(), aTextData.end(), + ::std::back_inserter( rOutCategories )); + } + 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 ) +{ + double fNan = 0.0; + ::rtl::math::setNan( &fNan ); + ::std::vector< double > aResult; + + Reference< chart2::data::XNumericalDataSequence > xNumSeq( xSeq, uno::UNO_QUERY ); + if( xNumSeq.is()) + { + Sequence< double > aValues( xNumSeq->getNumericalData()); + ::std::copy( aValues.begin(), aValues.end(), + ::std::back_inserter( aResult )); + } + else if( xSeq.is()) + { + Sequence< uno::Any > aAnies( xSeq->getData()); + aResult.resize( aAnies.getLength(), fNan ); + for( sal_Int32 i=0; i<aAnies.getLength(); ++i ) + aAnies[i] >>= aResult[i]; + } + return aResult; +} + +static sal_Int32 lcl_getChartType( const OUString& sChartType ) +{ + chart::TypeId eChartTypeId = chart::TYPEID_UNKNOWN; + if( sChartType == "com.sun.star.chart.BarDiagram" + || sChartType == "com.sun.star.chart2.ColumnChartType" ) + eChartTypeId = chart::TYPEID_BAR; + else if( sChartType == "com.sun.star.chart.AreaDiagram" + || sChartType == "com.sun.star.chart2.AreaChartType" ) + eChartTypeId = chart::TYPEID_AREA; + else if( sChartType == "com.sun.star.chart.LineDiagram" + || sChartType == "com.sun.star.chart2.LineChartType" ) + eChartTypeId = chart::TYPEID_LINE; + else if( sChartType == "com.sun.star.chart.PieDiagram" + || sChartType == "com.sun.star.chart2.PieChartType" ) + eChartTypeId = chart::TYPEID_PIE; + else if( sChartType == "com.sun.star.chart.DonutDiagram" + || sChartType == "com.sun.star.chart2.DonutChartType" ) + eChartTypeId = chart::TYPEID_DOUGHNUT; + else if( sChartType == "com.sun.star.chart.XYDiagram" + || sChartType == "com.sun.star.chart2.ScatterChartType" ) + eChartTypeId = chart::TYPEID_SCATTER; + else if( sChartType == "com.sun.star.chart.NetDiagram" + || sChartType == "com.sun.star.chart2.NetChartType" ) + eChartTypeId = chart::TYPEID_RADARLINE; + else if( sChartType == "com.sun.star.chart.FilledNetDiagram" + || sChartType == "com.sun.star.chart2.FilledNetChartType" ) + eChartTypeId = chart::TYPEID_RADARAREA; + else if( sChartType == "com.sun.star.chart.StockDiagram" + || sChartType == "com.sun.star.chart2.CandleStickChartType" ) + eChartTypeId = chart::TYPEID_STOCK; + else if( sChartType == "com.sun.star.chart.BubbleDiagram" + || sChartType == "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); +} + +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) +{ +} + +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(4); + aArguments[0] = beans::PropertyValue("DataRowSource" + , -1, uno::Any(eRowSource) + , beans::PropertyState_DIRECT_VALUE); + aArguments[1] = beans::PropertyValue("FirstCellAsLabel" + , -1, uno::Any(false) + , beans::PropertyState_DIRECT_VALUE); + aArguments[2] = beans::PropertyValue("HasCategories" + , -1, uno::Any(false) + , beans::PropertyState_DIRECT_VALUE); + aArguments[3] = beans::PropertyValue("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); + for (sal_Int32 i = 0; i < nLevelCount; i++) + { + sal_Int32 nElemLabel = 0; + aFinalSplitSource[nLevelCount - i - 1].realloc(aAnyCategories.getLength()); + for (auto const& elemLabel : aAnyCategories) + { + // make sure elemLabel[i] exists! + if (elemLabel.getLength() > i) + { + aFinalSplitSource[nLevelCount - i - 1][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(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::makeAny(css::sheet::AddressConvention::XL_OOX) ); + } + 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.toUtf8()); + + 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.toUtf8() ); + } + 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(nChartCount) + .append( ".xml" ) + .makeStringAndClear(); + OUString sRelativeStream = OUStringBuffer() + .appendAscii(sRelativePath) + .append(nChartCount) + .append( ".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)).toUtf8(), + FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel)).toUtf8(), + FSNS(XML_r, XML_id), sId.toUtf8() ); + + 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)).toUtf8(), + FSNS( XML_xmlns, XML_a ), pFB->getNamespaceURL(OOX_NS(dml)).toUtf8(), + FSNS( XML_xmlns, XML_r ), pFB->getNamespaceURL(OOX_NS(officeRel)).toUtf8()); + // 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); + + 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.toUtf8()); +} + +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; + + 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) + { + Sequence<sal_Int32> deletedLegendEntriesSeq; + aSeriesProp.getProperty(deletedLegendEntriesSeq, PROP_DeletedLegendEntries); + for (auto& deletedLegendEntry : 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(nRotation*0.01))); + // 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 ) ); +} + +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()); + 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 ) +{ + if ( !GetProperty( xPropSet, "FillStyle" ) ) + return; + FillStyle aFillStyle( FillStyle_NONE ); + xPropSet->getPropertyValue( "FillStyle" ) >>= aFillStyle; + switch( aFillStyle ) + { + case FillStyle_GRADIENT : + exportGradientFill( xPropSet ); + break; + case FillStyle_BITMAP : + exportBitmapFill( xPropSet ); + break; + case FillStyle_HATCH: + exportHatch(xPropSet); + break; + default: + WriteFill( xPropSet ); + } +} + +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; + + awt::Gradient aGradient; + awt::Gradient aTransparenceGradient; + 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 ); + if( rGradientValue >>= aGradient ) + { + 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(); + sal_Int32 nTypeId = XML_areaChart; + if( mbIs3DChart ) + nTypeId = XML_area3DChart; + pFS->startElement(FSNS(XML_c, nTypeId)); + + exportGrouping( ); + bool bPrimaryAxes = true; + exportAllSeries(xChartType, 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(); + 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; + exportAllSeries(xChartType, 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(); + pFS->startElement(FSNS(XML_c, XML_bubbleChart)); + + exportVaryColors(xChartType); + + bool bPrimaryAxes = true; + exportAllSeries(xChartType, 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 { + +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[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::exportLineChart( const Reference< chart2::XChartType >& xChartType ) +{ + FSHelperPtr pFS = GetFS(); + std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType); + for (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, + 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 ) +{ + std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType); + bool bExported = false; + for (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(); + pFS->startElement(FSNS(XML_c, XML_stockChart)); + + bool bPrimaryAxes = true; + Reference< chart2::XDataSeriesContainer > xDSCnt( xChartType, uno::UNO_QUERY ); + if(xDSCnt.is()) + exportCandleStickSeries( xDSCnt->getDataSeries(), 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, + 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 : std::as_const(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 ) + { + OUString aRole; + Reference< chart2::data::XDataSequence > xTempValueSeq( aSeqCnt[nSeqIdx]->getValues() ); + if( nMainSequenceIndex==-1 ) + { + Reference< beans::XPropertySet > xSeqProp( xTempValueSeq, uno::UNO_QUERY ); + 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 ); + + // export data labels + exportDataLabels(rSeries, nSeriesLength, eChartType); + + 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"); + + 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 + pFS->startElement(FSNS(XML_c, 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, XML_strCache)); + 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, XML_strCache)); + pFS->endElement(FSNS(XML_c, 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 ) ); + 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); + } + + OString sNumberFormatString = OUStringToOString(aNumberFormatString, RTL_TEXTENCODING_UTF8); + pFS->singleElement(FSNS(XML_c, XML_numFmt), + XML_formatCode, sNumberFormatString.getStr(), + 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)); + + // 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) + { + OUString aVal; + if(GetProperty( xAxisProp, "BuiltInUnit" )) + { + mAny >>= aVal; + if(!aVal.isEmpty()) + { + pFS->startElement(FSNS(XML_c, XML_dispUnits)); + + pFS->singleElement(FSNS(XML_c, XML_builtInUnit), XML_val, aVal.toUtf8()); + + 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::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"; + + 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 ) +{ + 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; + pFS->startElement(FSNS(XML_a, XML_p)); + + for (auto& rField : rCustomLabelFields) + { + Reference<XPropertySet> xPropertySet(rField, UNO_QUERY); + chart2::DataPointCustomLabelFieldType aType = rField->getFieldType(); + sFieldType.clear(); + bool bNewParagraph = false; + + 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(rField->getString()); + 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().toUtf8(), XML_type, + sFieldType.toUtf8()); + writeRunProperties(pChartExport, xPropertySet); + + pFS->startElement(FSNS(XML_a, XML_t)); + pFS->writeEscaped(rField->getString()); + 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 ) +{ + if (!xPropSet.is()) + return; + + chart2::DataPointLabel aLabel; + Sequence<Reference<chart2::XDataPointCustomLabelField>> aCustomLabelFields; + sal_Int32 nLabelBorderWidth = 0; + sal_Int32 nLabelBorderColor = 0x00FFFFFF; + + xPropSet->getPropertyValue("Label") >>= aLabel; + xPropSet->getPropertyValue("CustomLabelFields") >>= aCustomLabelFields; + xPropSet->getPropertyValue("LabelBorderWidth") >>= nLabelBorderWidth; + xPropSet->getPropertyValue("LabelBorderColor") >>= nLabelBorderColor; + + if (nLabelBorderWidth > 0) + { + pFS->startElement(FSNS(XML_c, XML_spPr)); + 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)); + } + + if (aCustomLabelFields.hasElements()) + writeCustomLabel(pFS, pChartExport, aCustomLabelFields); + + 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(false)); + 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 ) ); + } +} + +} + +void ChartExport::exportDataLabels( + const uno::Reference<chart2::XDataSeries> & xSeries, sal_Int32 nSeriesLength, sal_Int32 eChartType ) +{ + 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); + OString sNumberFormatString = OUStringToOString(aNumberFormatString, RTL_TEXTENCODING_UTF8); + + pFS->singleElement(FSNS(XML_c, XML_numFmt), + XML_formatCode, sNumberFormatString, + 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); + OString sNumberFormatString = OUStringToOString(aNumberFormatString, RTL_TEXTENCODING_UTF8); + + pFS->singleElement(FSNS(XML_c, XML_numFmt), XML_formatCode, sNumberFormatString.getStr(), + XML_sourceLinked, ToPsz10(bLinkedNumFmt)); + } + + // Individual label property that overwrites the baseline. + exportTextProps( xLabelPropSet ); + writeLabelProperties(pFS, this, xLabelPropSet, aParam); + pFS->endElement(FSNS(XML_c, XML_dLbl)); + } + + exportTextProps( xPropSet ); + // Baseline label properties for all labels. + writeLabelProperties(pFS, this, xPropSet, aParam); + + pFS->singleElement(FSNS(XML_c, XML_showLeaderLines), XML_val, "0"); + + // 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)).toUtf8()); + pFS->singleElement(FSNS(XML_c15, XML_showLeaderLines), XML_val, "1"); + 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() ) + { + ::std::set< sal_Int32 > aAttrPointSet; + ::std::copy( pPoints, pPoints + aDataPointSeq.getLength(), + ::std::inserter( aAttrPointSet, aAttrPointSet.begin())); + const ::std::set< sal_Int32 >::const_iterator 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( 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; + + ::std::set< sal_Int32 > aAttrPointSet; + ::std::copy( pPoints, pPoints + aDataPointSeq.getLength(), + ::std::inserter( aAttrPointSet, aAttrPointSet.begin())); + const ::std::set< sal_Int32 >::const_iterator 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::min<sal_Int32>( 72, std::max<sal_Int32>( 2, nSize ) ); + 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(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::getUnoTunnelImplementation<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..de2d34979 --- /dev/null +++ b/oox/source/export/drawingml.cxx @@ -0,0 +1,4634 @@ +/* -*- 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 <numeric> +#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/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/XTextContent.hpp> +#include <com/sun/star/text/XTextField.hpp> +#include <com/sun/star/text/XTextRange.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 <comphelper/random.hxx> +#include <comphelper/seqstream.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/xmltools.hxx> +#include <o3tl/any.hxx> +#include <o3tl/safeint.hxx> +#include <tools/stream.hxx> +#include <unotools/fontdefs.hxx> +#include <vcl/cvtgrf.hxx> +#include <vcl/graph.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 <svx/svdoashp.hxx> +#include <svx/svdomedia.hxx> +#include <svx/unoapi.hxx> +#include <svx/unoshape.hxx> +#include <svx/EnhancedCustomShape2d.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 ::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(bStart ? rGradient.StartColor : rGradient.EndColor).GetRed(); + // drawingML alpha is a percentage on a 0..100000 scale. + return (255 - nRed) * oox::drawingml::MAX_PERCENT / 255; +} +} + +namespace oox::drawingml { + +URLTransformer::~URLTransformer() +{ +} + +OUString URLTransformer::getTransformedString(const OUString& rString) const +{ + return rString; +} + +bool URLTransformer::isExternalURL(const OUString& /*rURL*/) const +{ + return true; +} + +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)); + sax_fastparser::XFastAttributeListRef xAttributeList(pAttributeList.get()); + pFS->singleElementNS(XML_a, XML_fillToRect, xAttributeList); + + pFS->endElementNS(XML_a, XML_path); +} +} + +// not thread safe +int DrawingML::mnImageCounter = 1; +int DrawingML::mnWdpImageCounter = 1; +std::map<OUString, OUString> DrawingML::maWdpCache; + +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::ResetCounters() +{ + mnImageCounter = 1; + mnWdpImageCounter = 1; + maWdpCache.clear(); +} + +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; +} + +void DrawingML::WriteColor( ::Color nColor, sal_Int32 nAlpha ) +{ + // 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(); + } + 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.toUtf8()); + WriteColorTransformations( aTransformations, nAlpha ); + mpFS->endElementNS( XML_a, XML_schemeClr ); + } + else if(nAlpha < MAX_PERCENT) + { + mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName.toUtf8()); + 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.toUtf8()); + } +} + +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)); + } + } + } +} + +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 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 + WriteSolidFill( ::Color(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(nFillColor & 0xffffff), nAlpha ); + } +} + +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(( ( ( 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; + awt::Gradient aTransparenceGradient; + 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 + { + mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0"); + if( GetProperty(rXPropSet, "FillTransparenceGradient") ) + aTransparenceGradient = *o3tl::doAccess<awt::Gradient>(mAny); + 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 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; + 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(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, nLineWidth == 0 || (nLineWidth > 1 && nStyleLineWidth != nLineWidth) ? + OString::number(nEmuLineWidth).getStr() : nullptr ); + + 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( sColorFillScheme, 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 (); + OUString sMediaType; + const char* pExtension = ""; + OUString sRelId; + + 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" ) + .append( static_cast<sal_Int32>(mnImageCounter) ) + .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(); + sRelId = mpFB->addRelation( mpFS->getOutputStream(), + oox::getRelationship(Relationship::IMAGE), + OUStringBuffer() + .appendAscii( sRelationCompPrefix.getStr() ) + .appendAscii( sRelPathToMedia.getStr() ) + .append( static_cast<sal_Int32>(mnImageCounter ++) ) + .appendAscii( pExtension ) + .makeStringAndClear() ); + + return sRelId; +} + +void DrawingML::WriteMediaNonVisualProperties(const css::uno::Reference<css::drawing::XShape>& xShape) +{ + SdrMediaObj* pMediaObj = dynamic_cast<SdrMediaObj*>(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; + } + } + + OUString aVideoFileRelId; + OUString aMediaRelId; + + if (bEmbed) + { + // copy the video stream + Reference<XOutputStream> xOutStream = mpFB->openFragmentStream(OUStringBuffer() + .appendAscii(GetComponentDir()) + .append("/media/media") + .append(static_cast<sal_Int32>(mnImageCounter)) + .append(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") + .append(static_cast<sal_Int32>(mnImageCounter++)) + .append(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); + aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), rURL); + } + + GetFS()->startElementNS(XML_p, XML_nvPr); + + GetFS()->singleElementNS(XML_a, eMediaType == Relationship::VIDEO ? XML_videoFile : XML_audioFile, + FSNS(XML_r, XML_link), aVideoFileRelId.toUtf8()); + + 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.toUtf8()); + + 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, nBright ? OString::number(nBright * 1000).getStr() : nullptr, + XML_contrast, nContrast ? OString::number(nContrast * 1000).getStr() : nullptr); + } + + 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); + if (mpTextExport) + { + BitmapChecksum nChecksum = aGraphic.GetChecksum(); + sRelId = mpTextExport->FindRelId(nChecksum); + } + if (sRelId.isEmpty()) + { + sRelId = WriteImage(aGraphic, bRelPathToMedia); + if (mpTextExport) + { + BitmapChecksum nChecksum = aGraphic.GetChecksum(); + mpTextExport->CacheRelId(nChecksum, sRelId); + } + } + + mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId.toUtf8()); + + 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: + mpFS->singleElementNS(XML_a, XML_tile); + 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)); + + mpFS->startElementNS(XML_a, XML_fgClr); + WriteColor(::Color(rHatch.Color)); + mpFS->endElementNS( XML_a , XML_fgClr ); + + ::Color nColor = COL_WHITE; + sal_Int32 nAlpha = 0; + + if ( GetProperty( rXPropSet, "FillBackground" ) ) + { + bool isBackgroundFilled = false; + mAny >>= isBackgroundFilled; + if( isBackgroundFilled ) + { + nAlpha = MAX_PERCENT; + + if( GetProperty( rXPropSet, "FillColor" ) ) + { + mAny >>= nColor; + } + } + } + + 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; + + 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)); + + css::text::GraphicCrop aGraphicCropStruct; + mAny >>= aGraphicCropStruct; + + 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); +} + +void DrawingML::WriteTransformation(const tools::Rectangle& rRect, + sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, sal_Int32 nRotation, bool bIsGroupShape) +{ + + mpFS->startElementNS( nXmlNamespace, XML_xfrm, + XML_flipH, bFlipH ? "1" : nullptr, + XML_flipV, bFlipV ? "1" : nullptr, + XML_rot, (nRotation % 21600000) ? OString::number(nRotation).getStr() : nullptr ); + + sal_Int32 nLeft = rRect.Left(); + sal_Int32 nTop = rRect.Top(); + if (GetDocumentType() == DOCUMENT_DOCX && !m_xParent.is()) + { + nLeft = 0; + nTop = 0; + } + + 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 (GetDocumentType() != DOCUMENT_DOCX && bIsGroupShape) + { + mpFS->singleElementNS(XML_a, XML_chOff, + XML_x, OString::number(oox::drawingml::convertHmmToEmu(nLeft)), + XML_y, OString::number(oox::drawingml::convertHmmToEmu(nTop))); + 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"); + + sal_Int32 nRotation=0; + 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 = GetSdrObjectFromXShape( rXShape ); + nRotation = pShape ? pShape->GetRotateAngle() : 0; + if ( GetDocumentType() != DOCUMENT_DOCX ) + { + int faccos=bFlipV ? -1 : 1; + int facsin=bFlipH ? -1 : 1; + aPos.X-=(1-faccos*cos(nRotation*F_PI18000))*aSize.Width/2-facsin*sin(nRotation*F_PI18000)*aSize.Height/2; + aPos.Y-=(1-faccos*cos(nRotation*F_PI18000))*aSize.Height/2+facsin*sin(nRotation*F_PI18000)*aSize.Width/2; + } + + // 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")) + xPropertySet->getPropertyValue("RotateAngle") >>= nRotation; + } + + // OOXML flips shapes before rotating them. + if(bFlipH != bFlipV) + nRotation = nRotation * -1 + 36000; + + WriteTransformation(tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), nXmlNamespace, + bFlipHWrite, bFlipVWrite, ExportRotateClockwisify(nRotation), IsGroupShape( rXShape )); +} + +void DrawingML::WriteRunProperties( const Reference< XPropertySet >& rRun, bool bIsField, sal_Int32 nElement, bool bCheckDirect, + bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, sal_Int16 nScriptType ) +{ + 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; + + 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 ) + { + 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 (nCharEscapement + && (GetPropertyAndState(rXPropSet, rXPropState, "CharEscapementHeight", eState) + && eState == beans::PropertyState_DIRECT_VALUE)) + { + sal_uInt32 nCharEscapementHeight = 0; + mAny >>= 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, usLanguage.isEmpty() ? nullptr : usLanguage.toUtf8().getStr(), + XML_sz, OString::number(nSize), + // For Condensed character spacing spc value is negative. + XML_spc, nCharKerning ? OString::number(nCharKerning).getStr() : nullptr, + XML_strike, strikeout, + XML_u, underline, + XML_baseline, nCharEscapement == 0 ? nullptr : OString::number(nCharEscapement*1000).getStr(), + XML_cap, cap ); + + // 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( *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); + } + + // 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. + if( color != COL_AUTO ) + { + color.SetTransparency(0); + // TODO: special handle embossed/engraved + WriteSolidFill(color, nTransparency); + } + } + + // 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(*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(*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.toUtf8(), + 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.toUtf8(), + 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() ) { + OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(), + oox::getRelationship(Relationship::HYPERLINK), + sURL, true ); + + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId.toUtf8()); + } + } + + 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; + switch(static_cast<SvxDateFormat>(nNumFmt)) + { + case SvxDateFormat::StdSmall: + case SvxDateFormat::A: aFieldValue = "datetime"; // 13/02/96 + break; + case SvxDateFormat::B: aFieldValue = "datetime1"; // 13/02/1996 + break; + case SvxDateFormat::StdBig: + case SvxDateFormat::D: aFieldValue = "datetime3"; // 13 February 1996 + break; + default: break; + } + } + else if(aFieldKind == "ExtTime") + { + sal_Int32 nNumFmt = -1; + rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt; + switch(static_cast<SvxTimeFormat>(nNumFmt)) + { + case SvxTimeFormat::Standard: + case SvxTimeFormat::HH24_MM_SS: + aFieldValue = "datetime11"; // 13:49:38 + break; + case SvxTimeFormat::HH24_MM: + aFieldValue = "datetime10"; // 13:49 + break; + case SvxTimeFormat::HH12_MM: + aFieldValue = "datetime12"; // 01:49 PM + break; + case SvxTimeFormat::HH12_MM_SS: + aFieldValue = "datetime13"; // 01:49:38 PM + break; + default: break; + } + } + 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; +} + +void DrawingML::WriteRun( const Reference< XTextRange >& rRun, + bool& rbOverridingCharHeight, sal_Int32& rnCharHeight) +{ + 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; + + 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") + { + 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.toUtf8() ); + } + else + { + mpFS->startElementNS(XML_a, XML_r); + } + + Reference< XPropertySet > xPropSet( rRun, uno::UNO_QUERY ); + + WriteRunProperties( xPropSet, bIsURLField, XML_rPr, true, rbOverridingCharHeight, rnCharHeight, GetScriptType(sText) ); + 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(*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 documemts 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) + { + 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; + long nPaddingX = std::max<long>(0, std::lround((aDestSize.Width() - fBulletSizeRelX * aDestSize.Width()) / 2.f)); + 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, 24), 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.toUtf8()); + mpFS->endElementNS( XML_a, XML_buBlip ); + } + else + { + if(bHasBulletColor) + { + if (nBulletColor == COL_AUTO ) + { + nBulletColor = ::Color(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.toUtf8(), + XML_charset, (aFontDesc.CharSet == awt::CharSet::SYMBOL) ? "2" : nullptr ); + } + + OUString aAutoNumType = GetAutoNumType( nNumberingType, bSDot, bPBehind, bPBoth ); + + if (!aAutoNumType.isEmpty()) + { + mpFS->singleElementNS(XML_a, XML_buAutoNum, + XML_type, aAutoNumType.toUtf8(), + XML_startAt, nStartWith > 1 ? OString::number(nStartWith).getStr() : nullptr); + } + else + { + mpFS->singleElementNS(XML_a, XML_buChar, XML_char, OUString(aBulletChar).toUtf8()); + } + } +} + +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; +} + +bool DrawingML::IsDiagram(const Reference<XShape>& rXShape) +{ + uno::Reference<beans::XPropertySet> xPropSet(rXShape, uno::UNO_QUERY); + if (!xPropSet.is()) + return false; + + // if the shape doesn't have the InteropGrabBag property, it's not a diagram + uno::Reference<beans::XPropertySetInfo> xPropSetInfo = xPropSet->getPropertySetInfo(); + OUString aName = UNO_NAME_MISC_OBJ_INTEROPGRABBAG; + if (!xPropSetInfo->hasPropertyByName(aName)) + return false; + + uno::Sequence<beans::PropertyValue> propList; + xPropSet->getPropertyValue(aName) >>= propList; + return std::any_of(std::cbegin(propList), std::cend(propList), + [](const beans::PropertyValue& rProp) { + // if we find any of the diagram components, it's a diagram + OUString propName = rProp.Name; + return propName == "OOXData" || propName == "OOXLayout" || propName == "OOXStyle" + || propName == "OOXColor" || propName == "OOXDrawing"; + }); +} + +sal_Int32 DrawingML::getBulletMarginIndentation (const Reference< XPropertySet >& rXPropSet,sal_Int16 nLevel, const OUString& 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 ) +{ + if( rSpacing.Mode == LineSpacingMode::PROP ) + { + mpFS->singleElementNS( XML_a, XML_spcPct, + XML_val, OString::number(static_cast<sal_Int32>(rSpacing.Height)*1000)); + } + else + { + mpFS->singleElementNS( XML_a, XML_spcPts, + XML_val, OString::number(std::lround(rSpacing.Height / 25.4 * 72))); + } +} + +void DrawingML::WriteParagraphProperties( const Reference< XTextContent >& rParagraph, float fFirstCharHeight) +{ + Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY ); + Reference< XPropertyState > rXPropState( rParagraph, UNO_QUERY ); + PropertyState eState; + + if( !rXPropSet.is() || !rXPropState.is() ) + return; + + sal_Int16 nLevel = -1; + if (GetProperty(rXPropSet, "NumberingLevel")) + mAny >>= nLevel; + + 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) + && eState == beans::PropertyState_DIRECT_VALUE) + bHasLinespacing = ( mAny >>= aLineSpacing ); + + 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,"LeftMargin"); + sal_Int32 nLineIndentation = getBulletMarginIndentation ( rXPropSet, nLevel,"FirstLineOffset"); + + if( !(nLevel != -1 + || nAlignment != style::ParagraphAdjust_LEFT + || bHasLinespacing) ) + return; + + if (nParaLeftMargin) // For Paragraph + mpFS->startElementNS( XML_a, XML_pPr, + XML_lvl, nLevel > 0 ? OString::number(nLevel).getStr() : nullptr, + XML_marL, nParaLeftMargin > 0 ? OString::number(oox::drawingml::convertHmmToEmu(nParaLeftMargin)).getStr() : nullptr, + XML_indent, nParaFirstLineIndent ? OString::number(oox::drawingml::convertHmmToEmu(nParaFirstLineIndent)).getStr() : nullptr, + XML_algn, GetAlignment( nAlignment ), + XML_rtl, bRtl ? ToPsz10(bRtl) : nullptr ); + else + mpFS->startElementNS( XML_a, XML_pPr, + XML_lvl, nLevel > 0 ? OString::number(nLevel).getStr() : nullptr, + XML_marL, nLeftMargin > 0 ? OString::number(oox::drawingml::convertHmmToEmu(nLeftMargin)).getStr() : nullptr, + XML_indent, nLineIndentation ? OString::number(oox::drawingml::convertHmmToEmu(nLineIndentation)).getStr() : nullptr, + XML_algn, GetAlignment( nAlignment ), + XML_rtl, bRtl ? ToPsz10(bRtl) : nullptr ); + + + if( bHasLinespacing ) + { + mpFS->startElementNS(XML_a, XML_lnSpc); + WriteLinespacing( aLineSpacing ); + 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 ); + } + + WriteParagraphNumbering( rXPropSet, fFirstCharHeight, nLevel ); + + WriteParagraphTabStops( rXPropSet ); + + mpFS->endElementNS( XML_a, XML_pPr ); +} + +void DrawingML::WriteParagraph( const Reference< XTextContent >& rParagraph, + bool& rbOverridingCharHeight, sal_Int32& rnCharHeight ) +{ + 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>(); + WriteParagraphProperties( rParagraph, fFirstCharHeight ); + bPropertiesWritten = true; + } + WriteRun( run, rbOverridingCharHeight, rnCharHeight ); + } + } + Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY ); + WriteRunProperties( rXPropSet, false, XML_endParaRPr, false, rbOverridingCharHeight, rnCharHeight ); + + mpFS->endElementNS( XML_a, XML_p ); +} + +void DrawingML::WriteText( const Reference< XInterface >& rXIface, const OUString& presetWarp, bool bBodyPr, bool bText, sal_Int32 nXmlNamespace ) +{ + Reference< XText > xXText( rXIface, UNO_QUERY ); + Reference< XPropertySet > rXPropSet( rXIface, UNO_QUERY ); + + if( !xXText.is() ) + return; + + sal_Int32 nTextPreRotateAngle = 0; + double nTextRotateAngle = 0; + bool bIsFontworkShape(presetWarp.startsWith("text") && (presetWarp != "textNoShape")); + +#define DEFLRINS 254 +#define DEFTBINS 127 + sal_Int32 nLeft, nRight, nTop, nBottom; + nLeft = nRight = DEFLRINS; + nTop = nBottom = DEFTBINS; + + // 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; + + TextVerticalAdjust eVerticalAlignment( TextVerticalAdjust_TOP ); + const char* sVerticalAlignment = nullptr; + if (GetProperty(rXPropSet, "TextVerticalAdjust")) + mAny >>= eVerticalAlignment; + if( eVerticalAlignment != TextVerticalAdjust_TOP ) + 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; + } + } + + Sequence<drawing::EnhancedCustomShapeAdjustmentValue> aAdjustmentSeq; + uno::Sequence<beans::PropertyValue> aTextPathSeq; + bool bScaleX(false); + + 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; + } + if (!bIsFontworkShape) + break; + } + else if (rProp.Name == "AdjustmentValues") + rProp.Value >>= aAdjustmentSeq; + else if( rProp.Name == "TextRotateAngle" ) + rProp.Value >>= nTextRotateAngle; + else if (rProp.Name == "TextPath") + { + rProp.Value >>= aTextPathSeq; + for (const auto& rTextPath : std::as_const(aTextPathSeq)) + { + if (rTextPath.Name == "ScaleX") + rTextPath.Value >>= bScaleX; + } + } + } + } + } + + bool bFromWordArt = !bScaleX + && ( presetWarp == "textArchDown" || presetWarp == "textArchUp" + || presetWarp == "textButton" || presetWarp == "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 ? "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"; + } + mpFS->startElementNS( (nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr, + XML_wrap, pWrap, + XML_fromWordArt, bFromWordArt ? "1" : nullptr, + XML_lIns, (nLeft != DEFLRINS) ? OString::number(oox::drawingml::convertHmmToEmu(nLeft)).getStr() : nullptr, + XML_rIns, (nRight != DEFLRINS) ? OString::number(oox::drawingml::convertHmmToEmu(nRight)).getStr() : nullptr, + XML_tIns, (nTop != DEFTBINS) ? OString::number(oox::drawingml::convertHmmToEmu(nTop)).getStr() : nullptr, + XML_bIns, (nBottom != DEFTBINS) ? OString::number(oox::drawingml::convertHmmToEmu(nBottom)).getStr() : nullptr, + XML_anchor, sVerticalAlignment, + XML_anchorCtr, bHorizontalCenter ? "1" : nullptr, + XML_vert, sWritingMode, + XML_rot, ((nTextPreRotateAngle + nTextRotateAngle) != 0) ? oox::drawingml::calcRotationValue( (nTextPreRotateAngle + nTextRotateAngle) * 100 ).getStr() : nullptr ); + if (bIsFontworkShape) + { + if (aAdjustmentSeq.hasElements()) + { + mpFS->startElementNS(XML_a, XML_prstTxWarp, XML_prst, presetWarp.toUtf8()); + mpFS->startElementNS(XML_a, XML_avLst); + for (sal_Int32 i = 0, nElems = aAdjustmentSeq.getLength(); i < nElems; ++i ) + { + OString sName = "adj" + (( nElems > 1 ) ? 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 (presetWarp == "textArchDown" || presetWarp == "textArchUp" + || presetWarp == "textButton" || presetWarp == "textCircle" + || ((i == 0) && (presetWarp == "textArchDownPour" || presetWarp == "textArchUpPour" + || presetWarp == "textButtonPour" || presetWarp == "textCirclePour"))) + { + fValue *= 60000.0; + } + else if ((i == 1) && (presetWarp == "textDoubleWave1" || presetWarp == "textWave1" + || presetWarp == "textWave2" || presetWarp == "textWave4")) + { + fValue = fValue / 0.216 - 50000.0; + } + else if ((i == 1) && (presetWarp == "textArchDownPour" || presetWarp == "textArchUpPour" + || presetWarp == "textButtonPour" || presetWarp == "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); + } + mpFS->endElementNS( XML_a, XML_avLst ); + mpFS->endElementNS(XML_a, XML_prstTxWarp); + } + else + { + mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, presetWarp.toUtf8()); + } + } + + if (GetDocumentType() == DOCUMENT_DOCX || GetDocumentType() == DOCUMENT_XLSX) + { + // tdf#112312: only custom shapes obey the TextAutoGrowHeight option + bool bTextAutoGrowHeight = false; + uno::Reference<drawing::XShape> xShape(rXIface, uno::UNO_QUERY); + auto pSdrObjCustomShape = xShape.is() ? dynamic_cast<SdrObjCustomShape*>(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) + { + double fScaleY = pTextObject->GetFontScaleY(); + nFontScale = static_cast<sal_uInt32>(fScaleY * 100) * 1000; + } + } + + mpFS->singleElementNS(XML_a, XML_normAutofit, XML_fontScale, + ( nFontScale < MAX_SCALE_VAL && nFontScale > 0 ) ? OString::number(nFontScale).getStr() : nullptr); + } + 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)); + } + } + + WriteShape3DEffects( rXPropSet ); + + 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; + + uno::Reference<drawing::XShape> xShape(rXIface, uno::UNO_QUERY); + SdrObject* pSdrObject = xShape.is() ? GetSdrObjectFromXShape(xShape) : nullptr; + const SdrTextObj* pTxtObj = dynamic_cast<SdrTextObj*>( pSdrObject ); + if (pTxtObj && mpTextExport) + { + const OutlinerParaObject* pParaObj = nullptr; + bool bOwnParaObj = false; + + /* + #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().release(); + bOwnParaObj = true; + } + else + pParaObj = pTxtObj->GetOutlinerParaObject(); + + if (pParaObj) + { + // this is reached only in case some text is attached to the shape + mpTextExport->WriteOutliner(*pParaObj); + if (bOwnParaObj) + delete pParaObj; + } + return; + } + + bool bOverridingCharHeight = false; + sal_Int32 nCharHeight = -1; + + while( enumeration->hasMoreElements() ) + { + Reference< XTextContent > paragraph; + Any any ( enumeration->nextElement() ); + + if( any >>= paragraph) + WriteParagraph( paragraph, bOverridingCharHeight, nCharHeight ); + } +} + +void DrawingML::WritePresetShape( const char* 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 char* 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"); + OString 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 = aLine.getToken(0, '\t', nIndex); + OString aValue = aLine.copy(nIndex); + aRet[aKey].push_back(aValue); + bNotDone = aStream.ReadLine(aLine); + } + return aRet; +} + +void DrawingML::WritePresetShape( const char* 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(OString(pShape)) != aAdjMap.end()) + aAdjustments = aAdjMap[OString(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 + && OString(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 ); +} + +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 ) + { + for( const beans::PropertyValue& rProp : *pGeometrySeq ) + { + if ( rProp.Name == "Path" ) + { + uno::Sequence<beans::PropertyValue> aPathProp; + rProp.Value >>= aPathProp; + + uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs; + uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments; + uno::Sequence<awt::Size> aPathSize; + 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; + } + + if ( !aPairs.hasElements() ) + return false; + + if ( !aSegments.hasElements() ) + { + aSegments = uno::Sequence<drawing::EnhancedCustomShapeSegment>(4); + aSegments[0].Count = 1; + aSegments[0].Command = drawing::EnhancedCustomShapeSegmentCommand::MOVETO; + aSegments[1].Count = static_cast<sal_Int16>(std::min( aPairs.getLength() - 1, sal_Int32(32767) )); + aSegments[1].Command = drawing::EnhancedCustomShapeSegmentCommand::LINETO; + aSegments[2].Count = 0; + aSegments[2].Command = drawing::EnhancedCustomShapeSegmentCommand::CLOSESUBPATH; + aSegments[3].Count = 0; + aSegments[3].Command = drawing::EnhancedCustomShapeSegmentCommand::ENDSUBPATH; + } + + 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; + } + + 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, "l", XML_t, "t", + XML_r, "r", XML_b, "b"); + mpFS->startElementNS(XML_a, XML_pathLst); + + if ( aPathSize.hasElements() ) + { + mpFS->startElementNS( XML_a, XML_path, + XML_w, OString::number(aPathSize[0].Width), + XML_h, OString::number(aPathSize[0].Height) ); + } + else + { + 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, rSdrObjCustomShape); + sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, rSdrObjCustomShape); + if (nX < nXMin) + nXMin = nX; + if (nY < nYMin) + nYMin = nY; + if (nX > nXMax) + nXMax = nX; + if (nY > nYMax) + nYMax = nY; + } + mpFS->startElementNS( XML_a, XML_path, + XML_w, OString::number(nXMax - nXMin), + XML_h, OString::number(nYMax - nYMin) ); + } + + + int nPairIndex = 0; + bool bOK = true; + for (const auto& rSegment : std::as_const(aSegments)) + { + if ( rSegment.Command == drawing::EnhancedCustomShapeSegmentCommand::CLOSESUBPATH ) + { + mpFS->singleElementNS(XML_a, XML_close); + } + for (int k = 0; k < rSegment.Count && bOK; ++k) + { + switch( rSegment.Command ) + { + case drawing::EnhancedCustomShapeSegmentCommand::MOVETO : + { + if (nPairIndex >= aPairs.getLength()) + bOK = false; + else + { + mpFS->startElementNS(XML_a, XML_moveTo); + WriteCustomGeometryPoint(aPairs[nPairIndex], rSdrObjCustomShape); + mpFS->endElementNS( XML_a, XML_moveTo ); + nPairIndex++; + } + break; + } + case drawing::EnhancedCustomShapeSegmentCommand::LINETO : + { + if (nPairIndex >= aPairs.getLength()) + bOK = false; + else + { + mpFS->startElementNS(XML_a, XML_lnTo); + WriteCustomGeometryPoint(aPairs[nPairIndex], rSdrObjCustomShape); + mpFS->endElementNS( XML_a, XML_lnTo ); + nPairIndex++; + } + break; + } + case drawing::EnhancedCustomShapeSegmentCommand::CURVETO : + { + if (nPairIndex + 2 >= aPairs.getLength()) + bOK = false; + else + { + mpFS->startElementNS(XML_a, XML_cubicBezTo); + for( sal_uInt8 l = 0; l <= 2; ++l ) + { + WriteCustomGeometryPoint(aPairs[nPairIndex+l], rSdrObjCustomShape); + } + mpFS->endElementNS( XML_a, XML_cubicBezTo ); + nPairIndex += 3; + } + break; + } + case drawing::EnhancedCustomShapeSegmentCommand::ANGLEELLIPSETO : + case drawing::EnhancedCustomShapeSegmentCommand::ANGLEELLIPSE : + { + nPairIndex += 3; + break; + } + case drawing::EnhancedCustomShapeSegmentCommand::ARCTO : + case drawing::EnhancedCustomShapeSegmentCommand::ARC : + case drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARCTO : + case drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARC : + { + nPairIndex += 4; + break; + } + case drawing::EnhancedCustomShapeSegmentCommand::ELLIPTICALQUADRANTX : + case drawing::EnhancedCustomShapeSegmentCommand::ELLIPTICALQUADRANTY : + { + nPairIndex++; + break; + } + case drawing::EnhancedCustomShapeSegmentCommand::QUADRATICCURVETO : + { + if (nPairIndex + 1 >= aPairs.getLength()) + bOK = false; + else + { + mpFS->startElementNS(XML_a, XML_quadBezTo); + for( sal_uInt8 l = 0; l < 2; ++l ) + { + WriteCustomGeometryPoint(aPairs[nPairIndex+l], rSdrObjCustomShape); + } + mpFS->endElementNS( XML_a, XML_quadBezTo ); + nPairIndex += 2; + } + break; + } + case drawing::EnhancedCustomShapeSegmentCommand::ARCANGLETO : + { + nPairIndex += 2; + break; + } + default: + // do nothing + break; + } + } + if (!bOK) + break; + } + mpFS->endElementNS( XML_a, XML_path ); + mpFS->endElementNS( XML_a, XML_pathLst ); + mpFS->endElementNS( XML_a, XML_custGeom ); + return bOK; + } + } + } + return false; +} + +void DrawingML::WriteCustomGeometryPoint( + const drawing::EnhancedCustomShapeParameterPair& rParamPair, + const SdrObjCustomShape& rSdrObjCustomShape) +{ + sal_Int32 nX = GetCustomGeometryPointValue(rParamPair.First, rSdrObjCustomShape); + sal_Int32 nY = GetCustomGeometryPointValue(rParamPair.Second, rSdrObjCustomShape); + + mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(nX), XML_y, OString::number(nY)); +} + +sal_Int32 DrawingML::GetCustomGeometryPointValue( + const css::drawing::EnhancedCustomShapeParameter& rParam, + const SdrObjCustomShape& rSdrObjCustomShape) +{ + const EnhancedCustomShape2d aCustoShape2d(const_cast< SdrObjCustomShape& >(rSdrObjCustomShape)); + double fValue = 0.0; + aCustoShape2d.GetParameter(fValue, rParam, false, false); + sal_Int32 nValue(std::lround(fValue)); + + return nValue; +} + +void DrawingML::WritePolyPolygon( const tools::PolyPolygon& rPolyPolygon, const bool bClosed ) +{ + // In case of Writer, the parent element is <wps:spPr>, and there the + // <a:custGeom> element is not optional. + if (rPolyPolygon.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 ); + + const tools::Rectangle aRect( rPolyPolygon.GetBoundRect() ); + + // Put all polygons of rPolyPolygon in the same path element + // to subtract the overlapped areas. + mpFS->startElementNS( XML_a, XML_path, + XML_w, OString::number(aRect.GetWidth()), + XML_h, OString::number(aRect.GetHeight()) ); + + for( sal_uInt16 i = 0; i < rPolyPolygon.Count(); i ++ ) + { + + const tools::Polygon& rPoly = rPolyPolygon[ i ]; + + if( rPoly.GetSize() > 0 ) + { + mpFS->startElementNS(XML_a, XML_moveTo); + + mpFS->singleElementNS( XML_a, XML_pt, + XML_x, OString::number(rPoly[0].X() - aRect.Left()), + XML_y, OString::number(rPoly[0].Y() - aRect.Top()) ); + + mpFS->endElementNS( XML_a, XML_moveTo ); + } + + for( sal_uInt16 j = 1; j < rPoly.GetSize(); j ++ ) + { + PolyFlags flags = rPoly.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 < rPoly.GetSize() && rPoly.GetFlags(j+1) == PolyFlags::Control && rPoly.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(rPoly[j+k].X() - aRect.Left()), + XML_y, OString::number(rPoly[j+k].Y() - aRect.Top())); + + } + 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(rPoly[j].X() - aRect.Left()), + XML_y, OString::number(rPoly[j].Y() - aRect.Top()) ); + 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( EscherConnectorListEntry& rConnectorEntry, sal_Int32 nStartID, sal_Int32 nEndID ) +{ + if( nStartID != -1 ) + { + mpFS->singleElementNS( XML_a, XML_stCxn, + XML_id, OString::number(nStartID), + XML_idx, OString::number(rConnectorEntry.GetConnectorRule(true)) ); + } + if( nEndID != -1 ) + { + mpFS->singleElementNS( XML_a, XML_endCxn, + XML_id, OString::number(nEndID), + XML_idx, OString::number(rConnectorEntry.GetConnectorRule(false)) ); + } +} + +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, + const OUString& 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; + } + + 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: + 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( const OUString& 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 == "outerShdw" ) + { + nEffectToken = FSNS( XML_a, XML_outerShdw ); + bContainsColor = true; + } + else if( sName == "innerShdw" ) + { + nEffectToken = FSNS( XML_a, XML_innerShdw ); + bContainsColor = true; + } + else if( sName == "glow" ) + { + nEffectToken = FSNS( XML_a, XML_glow ); + bContainsColor = true; + } + else if( sName == "softEdge" ) + nEffectToken = FSNS( XML_a, XML_softEdge ); + else if( sName == "reflection" ) + nEffectToken = FSNS( XML_a, XML_reflection ); + else if( sName == "blur" ) + nEffectToken = FSNS( XML_a, XML_blur ); + + OUString sSchemeClr; + ::Color nRgbClr; + sal_Int32 nAlpha = MAX_PERCENT; + Sequence< PropertyValue > aTransformations; + sax_fastparser::FastAttributeList *aOuterShdwAttrList = FastSerializerHelper::createAttrList(); + sax_fastparser::XFastAttributeListRef xOuterShdwAttrList( aOuterShdwAttrList ); + 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, OUStringToOString( sVal, RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if( rOuterShdwProp.Name == "blurRad" ) + { + sal_Int32 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, xOuterShdwAttrList ); + + 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(atan2(dY,dX)) * 60000) + 21600000) % 21600000; +} + +void DrawingML::WriteShapeEffects( const Reference< XPropertySet >& rXPropSet ) +{ + Sequence< PropertyValue > aGrabBag, aEffects, aOuterShdwProps; + if( 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 ) + { + Sequence< PropertyValue > aShadowGrabBag( 3 ); + Sequence< PropertyValue > aShadowAttribsGrabBag( 2 ); + + double dX = +0.0, dY = +0.0; + rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX; + rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY; + + aShadowAttribsGrabBag[0].Name = "dist"; + aShadowAttribsGrabBag[0].Value <<= lcl_CalculateDist(dX, dY); + aShadowAttribsGrabBag[1].Name = "dir"; + aShadowAttribsGrabBag[1].Value <<= lcl_CalculateDir(dX, dY); + + aShadowGrabBag[0].Name = "Attribs"; + aShadowGrabBag[0].Value <<= aShadowAttribsGrabBag; + aShadowGrabBag[1].Name = "RgbClr"; + aShadowGrabBag[1].Value = rXPropSet->getPropertyValue( "ShadowColor" ); + aShadowGrabBag[2].Name = "RgbClrTransparency"; + aShadowGrabBag[2].Value = rXPropSet->getPropertyValue( "ShadowTransparence" ); + + WriteShapeEffect( "outerShdw", aShadowGrabBag ); + } + WriteSoftEdgeEffect(rXPropSet); + mpFS->endElementNS(XML_a, XML_effectLst); + } + } + else + { + for( auto& rOuterShdwProp : aOuterShdwProps ) + { + if( rOuterShdwProp.Name == "Attribs" ) + { + Sequence< PropertyValue > aAttribsProps; + rOuterShdwProp.Value >>= aAttribsProps; + + double dX = +0.0, dY = +0.0; + rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX; + rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY; + + for( auto& rAttribsProp : aAttribsProps ) + { + if( rAttribsProp.Name == "dist" ) + { + rAttribsProp.Value <<= lcl_CalculateDist(dX, dY); + } + else if( rAttribsProp.Name == "dir" ) + { + rAttribsProp.Value <<= lcl_CalculateDir(dX, dY); + } + } + + 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) +{ + sal_Int32 nRad = 0; + rXPropSet->getPropertyValue("GlowEffectRadius") >>= nRad; + if (!nRad) + return; + + Sequence< PropertyValue > aGlowAttribs(1); + aGlowAttribs[0].Name = "rad"; + aGlowAttribs[0].Value <<= oox::drawingml::convertHmmToEmu(nRad); + Sequence< PropertyValue > aGlowProps(3); + aGlowProps[0].Name = "Attribs"; + aGlowProps[0].Value <<= aGlowAttribs; + aGlowProps[1].Name = "RgbClr"; + aGlowProps[1].Value = rXPropSet->getPropertyValue("GlowEffectColor"); + aGlowProps[2].Name = "RgbClrTransparency"; + aGlowProps[2].Value = rXPropSet->getPropertyValue("GlowEffectTransparency"); + // TODO other stuff like saturation or luminance + + WriteShapeEffect("glow", aGlowProps); +} + +void DrawingML::WriteSoftEdgeEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet) +{ + sal_Int32 nRad = 0; + rXPropSet->getPropertyValue("SoftEdgeRadius") >>= nRad; + if (!nRad) + return; + + css::uno::Sequence<css::beans::PropertyValue> aAttribs(1); + aAttribs[0].Name = "rad"; + aAttribs[0].Value <<= oox::drawingml::convertHmmToEmu(nRad); + css::uno::Sequence<css::beans::PropertyValue> aProps(1); + aProps[0].Name = "Attribs"; + aProps[0].Value <<= aAttribs; + + WriteShapeEffect("softEdge", aProps); +} + +void DrawingML::WriteShape3DEffects( 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, aEffectProps, aLightRigProps, aShape3DProps; + mAny >>= aGrabBag; + auto pShapeProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag), + [](const PropertyValue& rProp) { return rProp.Name == "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; + } + } + + auto pTextProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag), + [](const PropertyValue& rProp) { return rProp.Name == "Text3DEffectProperties"; }); + + if (pTextProp != std::cend(aGrabBag)) + { + Sequence< PropertyValue > a3DEffectProps; + pTextProp->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; + sax_fastparser::FastAttributeList *aCameraAttrList = FastSerializerHelper::createAttrList(); + sax_fastparser::XFastAttributeListRef xCameraAttrList( aCameraAttrList ); + sax_fastparser::FastAttributeList *aCameraRotationAttrList = FastSerializerHelper::createAttrList(); + sax_fastparser::XFastAttributeListRef xRotAttrList( aCameraRotationAttrList ); + for( const auto& rEffectProp : std::as_const(aEffectProps) ) + { + if( rEffectProp.Name == "prst" ) + { + OUString sVal; + rEffectProp.Value >>= sVal; + aCameraAttrList->add( XML_prst, OUStringToOString( sVal, RTL_TEXTENCODING_UTF8 ).getStr() ); + } + 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; + sax_fastparser::FastAttributeList *aLightRigAttrList = FastSerializerHelper::createAttrList(); + sax_fastparser::XFastAttributeListRef xLightAttrList( aLightRigAttrList ); + sax_fastparser::FastAttributeList *aLightRigRotationAttrList = FastSerializerHelper::createAttrList(); + sax_fastparser::XFastAttributeListRef xLightRotAttrList( aLightRigRotationAttrList ); + 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, OUStringToOString( sVal, RTL_TEXTENCODING_UTF8 ).getStr() ); + } + 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, xCameraAttrList ); + if( bCameraRotationPresent ) + { + mpFS->singleElementNS( XML_a, XML_rot, xRotAttrList ); + } + 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, xLightAttrList ); + if( bLightRigRotationPresent ) + { + mpFS->singleElementNS( XML_a, XML_rot, xLightRotAttrList ); + } + 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; + sax_fastparser::FastAttributeList *aBevelTAttrList = FastSerializerHelper::createAttrList(); + sax_fastparser::XFastAttributeListRef xBevelTAttrList( aBevelTAttrList ); + sax_fastparser::FastAttributeList *aBevelBAttrList = FastSerializerHelper::createAttrList(); + sax_fastparser::XFastAttributeListRef xBevelBAttrList( aBevelBAttrList ); + 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, OUStringToOString( sVal, RTL_TEXTENCODING_UTF8 ).getStr() ); + } + 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; + + sax_fastparser::FastAttributeList *aBevelAttrList = nullptr; + 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, OUStringToOString( sVal, RTL_TEXTENCODING_UTF8 ).getStr() ); + } + } + + } + } + + sax_fastparser::XFastAttributeListRef xAttrList( aShape3DAttrList ); + mpFS->startElementNS( XML_a, XML_sp3d, xAttrList ); + if( bBevelTPresent ) + { + mpFS->singleElementNS( XML_a, XML_bevelT, xBevelTAttrList ); + } + if( bBevelBPresent ) + { + mpFS->singleElementNS( XML_a, XML_bevelB, xBevelBAttrList ); + } + 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; + 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)).toUtf8() ); + mpFS->startElementNS(XML_a14, XML_imgLayer, FSNS(XML_r, XML_embed), sRelId); + mpFS->startElementNS(XML_a14, XML_imgEffect); + + sax_fastparser::XFastAttributeListRef xAttrList( aAttrList ); + mpFS->singleElementNS( XML_a14, nEffectToken, xAttrList ); + + 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 ) +{ + std::map<OUString, OUString>::iterator aCachedItem = maWdpCache.find( rFileId ); + if( aCachedItem != maWdpCache.end() ) + return OUStringToOString( aCachedItem->second, RTL_TEXTENCODING_UTF8 ); + + OUString sFileName = "media/hdphoto" + OUString::number( mnWdpImageCounter++ ) + ".wdp"; + Reference< XOutputStream > xOutStream = mpFB->openFragmentStream( OUStringBuffer() + .appendAscii( GetComponentDir() ) + .append( "/" ) + .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() ); + + maWdpCache[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 + sax_fastparser::FastAttributeList* pDocPrAttrList + = sax_fastparser::FastSerializerHelper::createAttrList(); + pDocPrAttrList->add(XML_id, OString::number(nDiagramId).getStr()); + OUString sName = "Diagram" + OUString::number(nDiagramId); + pDocPrAttrList->add(XML_name, OUStringToOString(sName, RTL_TEXTENCODING_UTF8).getStr()); + sax_fastparser::XFastAttributeListRef xDocPrAttrListRef(pDocPrAttrList); + + if (GetDocumentType() == DOCUMENT_DOCX) + { + mpFS->singleElementNS(XML_wp, XML_docPr, xDocPrAttrListRef); + mpFS->singleElementNS(XML_wp, XML_cNvGraphicFramePr); + + mpFS->startElementNS( + XML_a, XML_graphic, FSNS(XML_xmlns, XML_a), + mpFB->getNamespaceURL(OOX_NS(dml)).toUtf8()); + } + else + { + mpFS->startElementNS(XML_p, XML_nvGraphicFramePr); + + mpFS->singleElementNS(XML_p, XML_cNvPr, xDocPrAttrListRef); + 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)).toUtf8(), + 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( + 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"; + OString dataRelId = OUStringToOString( + mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDATA), + sRelationCompPrefix + dataFileName), + RTL_TEXTENCODING_UTF8); + + // add layout relation + OUString layoutFileName = "diagrams/layout" + OUString::number(nDiagramId) + ".xml"; + OString layoutRelId + = OUStringToOString(mpFB->addRelation(mpFS->getOutputStream(), + oox::getRelationship(Relationship::DIAGRAMLAYOUT), + sRelationCompPrefix + layoutFileName), + RTL_TEXTENCODING_UTF8); + + // add style relation + OUString styleFileName = "diagrams/quickStyle" + OUString::number(nDiagramId) + ".xml"; + OString styleRelId + = OUStringToOString(mpFB->addRelation(mpFS->getOutputStream(), + oox::getRelationship(Relationship::DIAGRAMQUICKSTYLE), + sRelationCompPrefix + styleFileName), + RTL_TEXTENCODING_UTF8); + + // add color relation + OUString colorFileName = "diagrams/colors" + OUString::number(nDiagramId) + ".xml"; + OString colorRelId + = OUStringToOString(mpFB->addRelation(mpFS->getOutputStream(), + oox::getRelationship(Relationship::DIAGRAMCOLORS), + sRelationCompPrefix + colorFileName), + RTL_TEXTENCODING_UTF8); + + 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), + 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)).toUtf8(), + FSNS(XML_xmlns, XML_r), mpFB->getNamespaceURL(OOX_NS(officeRel)).toUtf8(), + 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, "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, "OOXDiagramDrawingRels", nDiagramId); +} + +void DrawingML::writeDiagramRels(const uno::Sequence<uno::Sequence<uno::Any>>& xRelSeq, + const uno::Reference<io::XOutputStream>& xOutStream, + const OUString& 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 = "image/" + sExtension.copy(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 = "media/" + sGrabBagProperyName + OUString::number(nDiagramId) + "_" + + OUString::number(j) + sExtension; + + PropertySet aProps(xOutStream); + aProps.setAnyProperty(PROP_RelId, uno::makeAny(sRelId.toInt32())); + + mpFB->addRelation(xOutStream, sType, "../" + 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(); + } +} + +} + +/* 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..f6bd260f7 --- /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=\"shapetype___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..e5f0c4f88 --- /dev/null +++ b/oox/source/export/presetTextWarpDefinitions.xml @@ -0,0 +1,1902 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- + * 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 . + --> +<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..6828bc629 --- /dev/null +++ b/oox/source/export/shapes.cxx @@ -0,0 +1,2221 @@ +/* -*- 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 <sal/config.h> + +#include <sal/log.hxx> +#include <filter/msfilter/util.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 <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 <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 <svx/unoapi.hxx> +#include <oox/export/chartexport.hxx> +#include <oox/mathml/export.hxx> +#include <drawingml/presetgeometrynames.hxx> +#include <basegfx/numeric/ftools.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; + +#define IDS(x) OString(#x " " + OString::number(mnShapeIdMax++)).getStr() + +namespace oox { + +static void lcl_ConvertProgID(OUString const& rProgID, + OUString & o_rMediaType, OUString & o_rRelationType, OUString & o_rFileExtension) +{ + if (rProgID == "Excel.Sheet.12") + { + o_rMediaType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "xlsx"; + } + else if (rProgID.startsWith("Excel.SheetBinaryMacroEnabled.12") ) + { + o_rMediaType = "application/vnd.ms-excel.sheet.binary.macroEnabled.12"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "xlsb"; + } + else if (rProgID.startsWith("Excel.SheetMacroEnabled.12")) + { + o_rMediaType = "application/vnd.ms-excel.sheet.macroEnabled.12"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "xlsm"; + } + else if (rProgID.startsWith("Excel.Sheet")) + { + o_rMediaType = "application/vnd.ms-excel"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "xls"; + } + else if (rProgID == "PowerPoint.Show.12") + { + o_rMediaType = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "pptx"; + } + else if (rProgID == "PowerPoint.ShowMacroEnabled.12") + { + o_rMediaType = "application/vnd.ms-powerpoint.presentation.macroEnabled.12"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "pptm"; + } + else if (rProgID.startsWith("PowerPoint.Show")) + { + o_rMediaType = "application/vnd.ms-powerpoint"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "ppt"; + } + else if (rProgID.startsWith("PowerPoint.Slide.12")) + { + o_rMediaType = "application/vnd.openxmlformats-officedocument.presentationml.slide"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "sldx"; + } + else if (rProgID == "PowerPoint.SlideMacroEnabled.12") + { + o_rMediaType = "application/vnd.ms-powerpoint.slide.macroEnabled.12"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "sldm"; + } + else if (rProgID == "Word.DocumentMacroEnabled.12") + { + o_rMediaType = "application/vnd.ms-word.document.macroEnabled.12"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "docm"; + } + else if (rProgID == "Word.Document.12") + { + o_rMediaType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "docx"; + } + else if (rProgID == "Word.Document.8") + { + o_rMediaType = "application/msword"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "doc"; + } + else if (rProgID == "Excel.Chart.8") + { + o_rMediaType = "application/vnd.ms-excel"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "xls"; + } + else if (rProgID == "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, + OUString const& 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 ) + : DrawingML( std::move(pFS), pFB, eDocumentType, pTextExport ) + , m_nEmbeddedObjects(0) + , mnShapeIdMax( 1 ) + , mnPictureIdMax( 1 ) + , 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 ? XML_sp : XML_wsp)); + + tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon( xShape ); + tools::Rectangle aRect( aPolyPolygon.GetBoundRect() ); + +#if OSL_DEBUG_LEVEL > 0 + 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) + { + pFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + pFS->singleElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, IDS( Freeform ) ); + } + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr); + if (GetDocumentType() != DOCUMENT_DOCX) + { + WriteNonVisualProperties( xShape ); + pFS->endElementNS( mnXmlNamespace, XML_nvSpPr ); + } + + // visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_spPr); + WriteTransformation( aRect, XML_a ); + WritePolyPolygon( aPolyPolygon, 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 ? 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) + { + if (!m_xParent.is()) + nGroupShapeToken = XML_wgp; // toplevel + else + mnXmlNamespace = XML_wpg; + } + + pFS->startElementNS(mnXmlNamespace, nGroupShapeToken); + + // non visual properties + if (GetDocumentType() != DOCUMENT_DOCX) + { + pFS->startElementNS(mnXmlNamespace, XML_nvGrpSpPr); + pFS->singleElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, IDS(Group)); + 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) + { + // 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_IsOnBlacklist(OUString const & rShapeType) +{ + static const std::initializer_list<OUStringLiteral> vBlacklist = { + "block-arc", + "rectangle", + "ellipse", + "ring", + "can", + "cube", + "paper", + "frame", + "forbidden", + "smiley", + "sun", + "flower", + "bracket-pair", + "brace-pair", + "col-60da8460", + "col-502ad400", + "quad-bevel", + "round-rectangular-callout", + "rectangular-callout", + "round-callout", + "cloud-callout", + "line-callout-1", + "line-callout-2", + "line-callout-3", + "paper", + "vertical-scroll", + "horizontal-scroll", + "mso-spt34", + "mso-spt75", + "mso-spt164", + "mso-spt180", + "flowchart-process", + "flowchart-alternate-process", + "flowchart-decision", + "flowchart-data", + "flowchart-predefined-process", + "flowchart-internal-storage", + "flowchart-document", + "flowchart-multidocument", + "flowchart-terminator", + "flowchart-preparation", + "flowchart-manual-input", + "flowchart-manual-operation", + "flowchart-connector", + "flowchart-off-page-connector", + "flowchart-card", + "flowchart-punched-tape", + "flowchart-summing-junction", + "flowchart-or", + "flowchart-collate", + "flowchart-sort", + "flowchart-extract", + "flowchart-merge", + "flowchart-stored-data", + "flowchart-delay", + "flowchart-sequential-access", + "flowchart-magnetic-disk", + "flowchart-direct-access-storage", + "flowchart-display" + }; + + return std::find(vBlacklist.begin(), vBlacklist.end(), rShapeType) != vBlacklist.end(); +} + +static bool lcl_IsOnWhitelist(OUString const & rShapeType) +{ + static const std::initializer_list<OUStringLiteral> vWhitelist = { + "heart", + "puzzle" + }; + + return std::find(vWhitelist.begin(), vWhitelist.end(), rShapeType) != vWhitelist.end(); +} + +static bool lcl_GetHandlePosition( sal_Int32 &nValue, const EnhancedCustomShapeParameter &rParam, 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, + Sequence< EnhancedCustomShapeAdjustmentValue > &rSeq) +{ + for ( const Sequence< PropertyValue >& rPropSeq : rHandles ) + { + const OUString sPosition( "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(nInternAngle / 100.0); // intern 1/100 deg to degree to rad + fAngle = atan2(nHeight * sin(fAngle), nWidth * cos(fAngle)); // circle to ellipse + fAngle = basegfx::rad2deg(fAngle) * 60000.0; // rad to degree 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; +} + +ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape ) +{ + // First check, if this is a Fontwork-shape. For DrawingML, such a shape is a + // TextBox shape with body property prstTxWarp. + SAL_INFO("oox.shape", "write custom shape"); + Reference< XPropertySet > rXPropSet( xShape, UNO_QUERY ); + bool bIsFontworkShape(false); + bool bHasGeometrySeq(false); + Sequence< PropertyValue > aGeometrySeq; + OUString sShapeType; + 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 == "TextPath") + { + uno::Sequence<beans::PropertyValue> aTextPathSeq; + rProp.Value >>= aTextPathSeq; + for (const PropertyValue& rTextProp : std::as_const(aTextPathSeq)) + { + if (rTextProp.Name == "TextPath") + { + rTextProp.Value >>= bIsFontworkShape; + } + } + } + else if (rProp.Name == "Type") + rProp.Value >>= sShapeType; + } + } + } + if (bIsFontworkShape) + { + // write the correct type to m_presetWarp, WriteTextShape() needs it + // to set TextWarp. + m_presetWarp = PresetGeometryTypeNames::GetMsoName(sShapeType); + ShapeExport::WriteTextShape(xShape); // qualifier to prevent PowerPointShapeExport + return *this; + } + + + bool bPredefinedHandlesUsed = true; + bool bHasHandles = false; + + ShapeFlag nMirrorFlags = ShapeFlag::NONE; + MSO_SPT eShapeType = EscherPropertyContainer::GetCustomShapeType( xShape, nMirrorFlags, sShapeType ); + OSL_ENSURE(nullptr != dynamic_cast< SdrObjCustomShape* >(GetSdrObjectFromXShape(xShape)), "Not a SdrObjCustomShape (!)"); + SdrObjCustomShape& rSdrObjCustomShape(static_cast< SdrObjCustomShape& >(*GetSdrObjectFromXShape(xShape))); + const bool bIsDefaultObject( + EscherPropertyContainer::IsDefaultObject( + rSdrObjCustomShape, + eShapeType)); + const char* sPresetShape = msfilter::util::GetOOXMLPresetGeometry(sShapeType.toUtf8().getStr()); + 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; + + // Avoid interference of preset type to the next shape + m_presetWarp = ""; + + 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 == "PresetTextWarp" ) + { + rProp.Value >>= m_presetWarp; + } + else if ( rProp.Name == "ViewBox" ) + rProp.Value >>= aViewBox; + } + } + + FSHelperPtr pFS = GetFS(); + pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX ? XML_sp : XML_wsp)); + + // non visual shape properties + if (GetDocumentType() != DOCUMENT_DOCX) + { + bool isVisible = true ; + if( GETA (Visible)) + { + mAny >>= isVisible; + } + pFS->startElementNS( mnXmlNamespace, XML_nvSpPr ); + pFS->startElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, IDS( CustomShape ), + XML_hidden, isVisible ? nullptr : "1" ); + + 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.toUtf8()); + } + } + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr); + WriteNonVisualProperties( xShape ); + pFS->endElementNS( mnXmlNamespace, XML_nvSpPr ); + } + else + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr); + + // visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_spPr); + // moon is flipped in MSO, and mso-spt89 (right up arrow) is mapped to leftUpArrow + if ( sShapeType == "moon" || sShapeType == "mso-spt89" ) + bFlipH = !bFlipH; + + // 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 blacklist + // we use a whitelist for shapes where mapping to MSO preset shape is not optimal + bool bCustGeom = true; + bool bOnBlacklist = false; + if( sShapeType == "ooxml-non-primitive" ) + bCustGeom = true; + else if( sShapeType.startsWith("ooxml") ) + bCustGeom = false; + else if( lcl_IsOnWhitelist(sShapeType) ) + bCustGeom = true; + else if( lcl_IsOnBlacklist(sShapeType) ) + { + bCustGeom = false; + bOnBlacklist = true; + } + else if( bHasHandles ) + bCustGeom = true; + + if (bHasHandles && bCustGeom) + { + WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV, false, true );// do not flip, polypolygon coordinates are flipped already + tools::PolyPolygon aPolyPolygon( rSdrObjCustomShape.GetLineGeometry(true) ); + sal_Int32 nRotation = 0; + // The RotateAngle property's value is independent from any flipping, and that's exactly what we need here. + uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY); + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); + if (xPropertySetInfo->hasPropertyByName("RotateAngle")) + xPropertySet->getPropertyValue("RotateAngle") >>= nRotation; + // Remove rotation + bool bInvertRotation = bFlipH != bFlipV; + if (nRotation != 0) + aPolyPolygon.Rotate(Point(0,0), static_cast<sal_uInt16>(bInvertRotation ? nRotation/10 : 3600-nRotation/10)); + WritePolyPolygon( aPolyPolygon, false ); + } + else if (bCustGeom) + { + WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV ); + bool bSuccess = WriteCustomGeometry(xShape, rSdrObjCustomShape); + if (!bSuccess) + WritePresetShape( sPresetShape ); + } + else if (bOnBlacklist && 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 (!strcmp( 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) + WriteShape3DEffects( rXPropSet ); + } + + 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 ? 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 ? XML_sp : XML_wsp)); + + // TODO: connector ? + + // non visual shape properties + if (GetDocumentType() != DOCUMENT_DOCX) + { + pFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + pFS->singleElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, IDS( Ellipse ) ); + 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 ? 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) + pFS->startElementNS(mnXmlNamespace, XML_pic); + else + pFS->startElementNS(mnXmlNamespace, XML_pic, + FSNS(XML_xmlns, XML_pic), pFB->getNamespaceURL(OOX_NS(dmlPicture)).toUtf8()); + + pFS->startElementNS(mnXmlNamespace, XML_nvPicPr); + + OUString sName, sDescr, sURL; + bool bHaveName, bHaveDesc; + + if ( ( bHaveName= GetProperty( xShapeProps, "Name" ) ) ) + mAny >>= sName; + if ( ( bHaveDesc = GetProperty( xShapeProps, "Description" ) ) ) + mAny >>= sDescr; + if ( GetProperty( xShapeProps, "URL" ) ) + mAny >>= sURL; + + pFS->startElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, bHaveName + ? sName.toUtf8() + : OString("Picture " + OString::number(mnPictureIdMax++)), + XML_descr, bHaveDesc ? sDescr.toUtf8().getStr() : nullptr ); + + // OOXTODO: //cNvPr children: XML_extLst, XML_hlinkHover + if (bHasMediaURL) + pFS->singleElementNS(XML_a, XML_hlinkClick, + FSNS(XML_r, XML_id), "", + XML_action, "ppaction://media"); + 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.toUtf8()); + } + 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, false); + } + else if (bHasMediaURL) + { + Reference<graphic::XGraphic> xFallbackGraphic; + if (xShapeProps->getPropertySetInfo()->hasPropertyByName("FallbackGraphic")) + xShapeProps->getPropertyValue("FallbackGraphic") >>= xFallbackGraphic; + + WriteXGraphicBlip(xShapeProps, xFallbackGraphic, false); + } + + 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); + + 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 ); + WriteShape3DEffects( xShapeProps ); + + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + pFS->endElementNS( mnXmlNamespace, XML_pic ); +} + +ShapeExport& ShapeExport::WriteConnectorShape( const Reference< XShape >& xShape ) +{ + bool bFlipH = false; + bool bFlipV = false; + + SAL_INFO("oox.shape", "write connector shape"); + + FSHelperPtr pFS = GetFS(); + + const char* sGeometry = "line"; + 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; + if( GETAD( EdgeKind ) ) { + mAny >>= eConnectorType; + + switch( eConnectorType ) { + case ConnectorType_CURVE: + sGeometry = "curvedConnector3"; + break; + case ConnectorType_STANDARD: + sGeometry = "bentConnector3"; + break; + default: + case ConnectorType_LINE: + case ConnectorType_LINES: + sGeometry = "straightConnector1"; + break; + } + + if( GETAD( EdgeStartPoint ) ) { + mAny >>= aStartPoint; + if( GETAD( EdgeEndPoint ) ) { + mAny >>= aEndPoint; + } + } + GET( rXShapeA, EdgeStartConnection ); + GET( rXShapeB, EdgeEndConnection ); + } + EscherConnectorListEntry aConnectorEntry( xShape, aStartPoint, rXShapeA, aEndPoint, rXShapeB ); + + tools::Rectangle aRect( Point( aStartPoint.X, aStartPoint.Y ), Point( aEndPoint.X, aEndPoint.Y ) ); + if( aRect.getWidth() < 0 ) { + bFlipH = true; + aRect.setX( aEndPoint.X ); + aRect.setWidth( aStartPoint.X - aEndPoint.X ); + } + + if( aRect.getHeight() < 0 ) { + bFlipV = true; + aRect.setY( aEndPoint.Y ); + aRect.setHeight( aStartPoint.Y - aEndPoint.Y ); + } + + pFS->startElementNS(mnXmlNamespace, XML_cxnSp); + + // non visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_nvCxnSpPr); + pFS->singleElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, IDS( Line ) ); + // non visual connector shape drawing properties + pFS->startElementNS(mnXmlNamespace, XML_cNvCxnSpPr); + WriteConnectorConnections( aConnectorEntry, GetShapeID( rXShapeA ), GetShapeID( rXShapeB ) ); + pFS->endElementNS( mnXmlNamespace, XML_cNvCxnSpPr ); + pFS->singleElementNS(mnXmlNamespace, XML_nvPr); + pFS->endElementNS( mnXmlNamespace, XML_nvCxnSpPr ); + + // visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_spPr); + WriteTransformation( aRect, XML_a, bFlipH, bFlipV ); + // TODO: write adjustments (ppt export doesn't work well there either) + WritePresetShape( sGeometry ); + Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY ); + if( xShapeProps.is() ) + WriteOutline( xShapeProps ); + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + // write text + WriteTextBox( xShape, mnXmlNamespace ); + + pFS->endElementNS( mnXmlNamespace, XML_cxnSp ); + + 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 ? 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) + { + pFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + pFS->singleElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, IDS( Line ) ); + } + pFS->singleElementNS( mnXmlNamespace, XML_cNvSpPr ); + if (GetDocumentType() != DOCUMENT_DOCX) + { + 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 ? 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 ? 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) + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr); + pFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + pFS->singleElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, IDS( Rectangle ) ); + 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 ? 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 + { + { "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.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 ) +{ + 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 ); + } + (this->*(aConverter->second))( xShape ); + + return *this; +} + +ShapeExport& ShapeExport::WriteTextBox( const Reference< XInterface >& xIface, sal_Int32 nXmlNamespace ) +{ + // In case this shape has an associated textbox, then export that, and we're done. + if (GetDocumentType() == DOCUMENT_DOCX && GetTextExport()) + { + uno::Reference<beans::XPropertySet> xPropertySet(xIface, uno::UNO_QUERY); + if (xPropertySet.is()) + { + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); + if (xPropertySetInfo->hasPropertyByName("TextBox") && xPropertySet->getPropertyValue("TextBox").get<bool>()) + { + GetTextExport()->WriteTextBox(uno::Reference<drawing::XShape>(xIface, uno::UNO_QUERY_THROW)); + WriteText( xIface, m_presetWarp, /*bBodyPr=*/true, /*bText=*/false, /*nXmlNamespace=*/nXmlNamespace ); + return *this; + } + } + } + + Reference< XText > xXText( xIface, UNO_QUERY ); + if( NonEmptyText( xIface ) && xXText.is() ) + { + FSHelperPtr pFS = GetFS(); + + pFS->startElementNS(nXmlNamespace, + (GetDocumentType() != DOCUMENT_DOCX ? XML_txBody : XML_txbx)); + WriteText( xIface, m_presetWarp, /*bBodyPr=*/(GetDocumentType() != DOCUMENT_DOCX) ); + pFS->endElementNS( nXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX ? XML_txBody : XML_txbx) ); + if (GetDocumentType() == DOCUMENT_DOCX) + WriteText( xIface, m_presetWarp, /*bBodyPr=*/true, /*bText=*/false, /*nXmlNamespace=*/nXmlNamespace ); + } + else if (GetDocumentType() == DOCUMENT_DOCX) + 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->singleElementNS(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); + + Any aLeftMargin = xCellPropSet->getPropertyValue("TextLeftDistance"); + aLeftMargin >>= nLeftMargin; + + Any aRightMargin = xCellPropSet->getPropertyValue("TextRightDistance"); + aRightMargin >>= nRightMargin; + + mpFS->startElementNS(XML_a, XML_tcPr, + XML_marL, nLeftMargin > 0 ? OString::number(oox::drawingml::convertHmmToEmu(nLeftMargin)).getStr() : nullptr, + XML_marR, nRightMargin > 0 ? OString::number(oox::drawingml::convertHmmToEmu(nRightMargin)).getStr() : nullptr); + + // 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(rBorderLine.Color) ); + 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, IDS(Table) ); + + 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 ) +{ + bool bIsFontworkShape(m_presetWarp.startsWith("text") && m_presetWarp != "textNoShape"); + FSHelperPtr pFS = GetFS(); + Reference<XPropertySet> xShapeProps(xShape, UNO_QUERY); + pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX ? XML_sp : XML_wsp)); + + // non visual shape properties + if (GetDocumentType() != DOCUMENT_DOCX) + { + pFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + pFS->startElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, IDS(TextShape)); + 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.toUtf8()); + } + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + } + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr, XML_txBox, "1"); + if (GetDocumentType() != DOCUMENT_DOCX) + { + 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 (!bIsFontworkShape) // Fontwork needs fill and outline on char instead. + { + WriteBlipOrNormalFill(xPropertySet, "Graphic"); + WriteOutline(xPropertySet); + } + WriteShapeEffects(xPropertySet); + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + WriteTextBox( xShape, mnXmlNamespace ); + + pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX ? 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)).toUtf8(), + 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, IDS(Formula)); + 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 + ChartExport aChartExport( mnXmlNamespace, GetFS(), xChartDoc, GetFB(), GetDocumentType() ); + static sal_Int32 nChartCount = 0; + aChartExport.WriteChartObj( xShape, GetNewShapeID( xShape ), ++nChartCount ); + 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"); + 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, + 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, IDS(Object) ); + + 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.toUtf8(), + XML_spid, "" ); + } + else + { + mpFS->startElementNS( mnXmlNamespace, XML_oleObj, +//? XML_name, "Document", + FSNS(XML_r, XML_id), sRelId.toUtf8(), + // 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( GetSdrObjectFromXShape( xShape ) ); + // The spec doesn't allow <p:pic> here, but PowerPoint requires it. + bool bEcma = mpFB->getVersion() == oox::core::ECMA_DIALECT; + if (dynamic_cast<const SdrOle2Obj*>( pSdrOLE2) && bEcma) + { + const Graphic* pGraphic = static_cast<SdrOle2Obj*>(pSdrOLE2)->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; +} + +} + +/* 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..54eba8b7e --- /dev/null +++ b/oox/source/export/vmlexport.cxx @@ -0,0 +1,1523 @@ +/* -*- 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 <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 <cstdio> + +using namespace sax_fastparser; +using namespace oox::vml; +using namespace com::sun::star; + +static const sal_Int32 Tag_Container = 44444; +static 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_pShapeAttrList( 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 ); + FastAttributeList *pAttrList = FastSerializerHelper::createAttrList(); + + pAttrList->add( XML_id, ShapeIdString( nShapeId ) ); + + if ( rShapeName.getLength() ) + pAttrList->add( XML_alt, OUStringToOString( rShapeName, RTL_TEXTENCODING_UTF8 ) ); + + bool rbAbsolutePos = true; + //editAs + OUString rEditAs = EscherEx::GetEditAs(); + if (!rEditAs.isEmpty()) + { + pAttrList->add(XML_editas, OUStringToOString( rEditAs, RTL_TEXTENCODING_UTF8 )); + 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, + OStringBuffer( 20 ).append( sal_Int32( pRect->Left() ) ) + .append( "," ).append( sal_Int32( pRect->Top() ) ) + .makeStringAndClear() ); + + pAttrList->add( XML_coordsize, + OStringBuffer( 20 ).append( sal_Int32( pRect->Right() ) - sal_Int32( pRect->Left() ) ) + .append( "," ).append( sal_Int32( pRect->Bottom() ) - sal_Int32( pRect->Top() ) ) + .makeStringAndClear() ); + } + + m_pSerializer->startElementNS( XML_v, XML_group, XFastAttributeListRef( 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, OUStringToOString(m_pSdrObject->GetName(), RTL_TEXTENCODING_UTF8) ); + // also ('o:spid') + m_pShapeAttrList->addNS( XML_o, XML_spid, m_sShapeId ); + } +} + +bool VMLExport::IsWaterMarkShape(const OUString& rStr) +{ + if (rStr.isEmpty() ) return false; + + return rStr.match("PowerPlusWaterMarkObject") || rStr.match("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 ) +{ +#if OSL_DEBUG_LEVEL > 0 + if ( nColor & 0xFF000000 ) + fprintf( stderr, "TODO: this is not a RGB value!\n" ); +#endif + + 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 + 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, + OStringBuffer( 20 ).append( sal_Int32( nLeft ) ) + .append( "," ).append( sal_Int32( nTop ) ) + .makeStringAndClear() ); + } + 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(nTop!=0 && nLeft!=0 && nBottom!=0 && nRight!=0 ) + m_pShapeAttrList->add( XML_coordsize, + OStringBuffer( 20 ).append( sal_Int32( nRight ) - sal_Int32( nLeft ) ) + .append( "," ).append( sal_Int32( nBottom ) - sal_Int32( nTop ) ) + .makeStringAndClear() ); + } + 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" ).append( nX ).append( "," ).append( 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" ).append( nX ).append( "," ).append( 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" ).append( nX1 ).append( "," ).append( nY1 ).append( "," ) + .append( nX2 ).append( "," ).append( nY2 ).append( "," ) + .append( nX3 ).append( "," ).append( 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 ( !aPath.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; + 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) + { + sax_fastparser::FastAttributeList* pAttrListSignatureLine + = FastSerializerHelper::createAttrList(); + pAttrListSignatureLine->add(XML_issignatureline, "t"); + if (!pSdrGrafObj->getSignatureLineId().isEmpty()) + { + pAttrListSignatureLine->add( + XML_id, OUStringToOString(pSdrGrafObj->getSignatureLineId(), + RTL_TEXTENCODING_UTF8)); + } + if (!pSdrGrafObj->getSignatureLineSuggestedSignerName().isEmpty()) + { + pAttrListSignatureLine->add( + FSNS(XML_o, XML_suggestedsigner), + OUStringToOString( + pSdrGrafObj->getSignatureLineSuggestedSignerName(), + RTL_TEXTENCODING_UTF8)); + } + if (!pSdrGrafObj->getSignatureLineSuggestedSignerTitle().isEmpty()) + { + pAttrListSignatureLine->add( + FSNS(XML_o, XML_suggestedsigner2), + OUStringToOString( + pSdrGrafObj->getSignatureLineSuggestedSignerTitle(), + RTL_TEXTENCODING_UTF8)); + } + if (!pSdrGrafObj->getSignatureLineSuggestedSignerEmail().isEmpty()) + { + pAttrListSignatureLine->add( + FSNS(XML_o, XML_suggestedsigneremail), + OUStringToOString( + pSdrGrafObj->getSignatureLineSuggestedSignerEmail(), + RTL_TEXTENCODING_UTF8)); + } + if (!pSdrGrafObj->getSignatureLineSigningInstructions().isEmpty()) + { + pAttrListSignatureLine->add(XML_signinginstructionsset, "t"); + pAttrListSignatureLine->add( + FSNS(XML_o, XML_signinginstructions), + OUStringToOString( + pSdrGrafObj->getSignatureLineSigningInstructions(), + RTL_TEXTENCODING_UTF8)); + } + pAttrListSignatureLine->add( + XML_showsigndate, + pSdrGrafObj->isSignatureLineShowSignDate() ? "t" : "f"); + pAttrListSignatureLine->add( + XML_allowcomments, + pSdrGrafObj->isSignatureLineCanAddComment() ? "t" : "f"); + + m_pSerializer->singleElementNS( + XML_o, XML_signatureline, + XFastAttributeListRef(pAttrListSignatureLine)); + + // Get signature line graphic + const uno::Reference<graphic::XGraphic>& xGraphic + = pSdrGrafObj->getSignatureLineUnsignedGraphic(); + Graphic aGraphic(xGraphic); + + BitmapChecksum nChecksum = aGraphic.GetChecksum(); + OUString aImageId = m_pTextExport->FindRelId(nChecksum); + if (aImageId.isEmpty()) + { + aImageId = m_pTextExport->GetDrawingML().WriteImage(aGraphic); + m_pTextExport->CacheRelId(nChecksum, aImageId); + } + pAttrList->add(FSNS(XML_r, XML_id), + OUStringToOString(aImageId, RTL_TEXTENCODING_UTF8)); + 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); + + BitmapChecksum nChecksum = aGraphic.GetChecksum(); + OUString aImageId = m_pTextExport->FindRelId(nChecksum); + if (aImageId.isEmpty()) + { + aImageId = m_pTextExport->GetDrawingML().WriteImage(aGraphic); + m_pTextExport->CacheRelId(nChecksum, aImageId); + } + pAttrList->add(FSNS(XML_r, XML_id), + OUStringToOString(aImageId, RTL_TEXTENCODING_UTF8)); + imageData = true; + } + + if (rProps.GetOpt(ESCHER_Prop_fNoFillHitTest, nValue)) + impl_AddBool(pAttrList, FSNS(XML_o, XML_detectmouseclick), nValue != 0); + + if (imageData) + m_pSerializer->singleElementNS( XML_v, XML_imagedata, XFastAttributeListRef( 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, XML_fillcolor, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_fillBackColor, nValue ) ) + impl_AddColor( pAttrList, 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, XFastAttributeListRef( 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; + sax_fastparser::FastAttributeList *pAttrList = FastSerializerHelper::createAttrList(); + + if ( rProps.GetOpt( ESCHER_Prop_lineColor, nValue ) ) + impl_AddColor( pAttrList, XML_color, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_lineWidth, nValue ) ) + impl_AddInt( pAttrList, 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, XML_startarrow, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_lineEndArrowhead, nValue ) ) + impl_AddArrowHead( pAttrList, XML_endarrow, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_lineStartArrowWidth, nValue ) ) + impl_AddArrowWidth( pAttrList, XML_startarrowwidth, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_lineStartArrowLength, nValue ) ) + impl_AddArrowLength( pAttrList, XML_startarrowlength, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_lineEndArrowWidth, nValue ) ) + impl_AddArrowWidth( pAttrList, XML_endarrowwidth, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_lineEndArrowLength, nValue ) ) + impl_AddArrowLength( pAttrList, 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, XFastAttributeListRef( 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 ) + { + sax_fastparser::FastAttributeList *pAttrList = FastSerializerHelper::createAttrList(); + impl_AddBool( pAttrList, XML_on, bShadow ); + impl_AddBool( pAttrList, XML_obscured, bObscured ); + + if ( rProps.GetOpt( ESCHER_Prop_shadowColor, nValue ) ) + impl_AddColor( pAttrList, XML_color, nValue ); + + m_pSerializer->singleElementNS( XML_v, XML_shadow, XFastAttributeListRef( 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"); + + sax_fastparser::FastAttributeList* pAttrList = FastSerializerHelper::createAttrList(); + pAttrList->add(XML_on, "t"); + pAttrList->add(XML_fitshape, "t"); + pAttrList->add(XML_string, OUStringToOString(aTextPathString, RTL_TEXTENCODING_UTF8)); + 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, OUStringToOString(aStyle, RTL_TEXTENCODING_UTF8)); + m_pSerializer->singleElementNS(XML_v, XML_textpath, XFastAttributeListRef(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:").append(double(opt.nPropValue >> 16)); + bAlreadyWritten[ESCHER_Prop_Rotation] = true; + } + break; + case ESCHER_Prop_fNoLineDrawDash: + { + // See DffPropertyReader::ApplyLineAttributes(). + impl_AddBool( m_pShapeAttrList, 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, OUStringToOString(idStr, RTL_TEXTENCODING_UTF8).getStr()); + + 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, + OStringBuffer( 20 ).append( aLeft ) + .append( "," ).append( aTop ) + .makeStringAndClear() ); + + m_pShapeAttrList->add( XML_to, + OStringBuffer( 20 ).append( aRight ) + .append( "," ).append( aBottom ) + .makeStringAndClear() ); +} + +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:" ).append( double( rRectangle.Right() - rRectangle.Left() ) / 20 ) + .append( "pt;height:" ).append( double( rRectangle.Bottom() - rRectangle.Top() ) / 20 ) + .append( "pt" ); + } + else if ( mnGroupLevel == 1 ) + { + rBuffer.append( "margin-left:" ).append( double( rRectangle.Left() ) / 20 ) + .append( "pt;margin-top:" ).append( double( rRectangle.Top() ) / 20 ) + .append( "pt;width:" ).append( double( rRectangle.Right() - rRectangle.Left() ) / 20 ) + .append( "pt;height:" ).append( double( rRectangle.Bottom() - rRectangle.Top() ) / 20 ) + .append( "pt" ); + } + else + { + rBuffer.append( "left:" ).append( rRectangle.Left() ) + .append( ";top:" ).append( rRectangle.Top() ) + .append( ";width:" ).append( rRectangle.Right() - rRectangle.Left() ) + .append( ";height:" ).append( 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"); + OString aLine; + bool bNotDone = aStream.ReadLine(aLine); + while (bNotDone) + { + // Filter out comments. + if (!aLine.startsWith("/")) + aRet.push_back(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()) + { + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); + return xPropertySetInfo->hasPropertyByName("TextBox") && xPropertySet->getPropertyValue("TextBox").get<bool>(); + } + return false; +} + +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++; +} + +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: + { + // We don't have a shape definition for host control in presetShapeDefinitions.xml + // So use a definition copied from DOCX file created with MSO + bReferToShapeType = true; + nShapeElement = XML_shape; + if ( !m_aShapeTypeWritten[ m_nShapeType ] ) + { + OString sShapeType = + "<v:shapetype id=\"shapetype_" + OString::number(m_nShapeType) + + "\" coordsize=\"21600,21600\" o:spt=\"" + OString::number(m_nShapeType) + + "\" 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>"; + m_pSerializer->write(sShapeType); + m_aShapeTypeWritten[ m_nShapeType ] = true; + } + break; + } + case ESCHER_ShpInst_PictureFrame: + { + // We don't have a shape definition for picture frame in presetShapeDefinitions.xml + // So use a definition copied from DOCX file created with MSO + bReferToShapeType = true; + nShapeElement = XML_shape; + if ( !m_aShapeTypeWritten[ m_nShapeType ] ) + { + OString sShapeType = + "<v:shapetype id=\"shapetype_" + OString::number(m_nShapeType) + + "\" coordsize=\"21600,21600\" o:spt=\"" + OString::number(m_nShapeType) + + "\" 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>"; + m_pSerializer->write(sShapeType); + 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(); + OString aShapeType = aShapeTypes[ m_nShapeType ]; + if ( aShapeType != "NULL" ) + { + bReferToShapeType = true; + if ( !m_aShapeTypeWritten[ m_nShapeType ] ) + { + m_pSerializer->write( aShapeType.getStr() ); + 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; + } + + // 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, OUStringToOString(sAnchorId, RTL_TEXTENCODING_UTF8)); + + if ( nShapeElement >= 0 && !m_pShapeAttrList->hasAttribute( XML_type ) && bReferToShapeType ) + { + OStringBuffer sTypeBuffer( 20 ); + if (m_bUseHashMarkForType) + sTypeBuffer.append("#"); + m_pShapeAttrList->add( XML_type, sTypeBuffer + .append( "shapetype_" ).append( sal_Int32( m_nShapeType ) ) + .makeStringAndClear() ); + } + + // start of the shape + m_pSerializer->startElementNS( XML_v, nShapeElement, XFastAttributeListRef( 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)) + { + const OutlinerParaObject* pParaObj = nullptr; + bool bOwnParaObj = false; + + /* + #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().release(); + bOwnParaObj = true; + } + else + { + pParaObj = pTxtObj->GetOutlinerParaObject(); + } + + if( pParaObj ) + { + sax_fastparser::FastAttributeList* pTextboxAttrList = FastSerializerHelper::createAttrList(); + sax_fastparser::XFastAttributeListRef xTextboxAttrList(pTextboxAttrList); + 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, xTextboxAttrList); + m_pTextExport->WriteOutliner(*pParaObj); + m_pSerializer->endElementNS(XML_v, XML_textbox); + if( bOwnParaObj ) + delete pParaObj; + } + } + + return nShapeElement; +} + +void VMLExport::EndShape( sal_Int32 nShapeElement ) +{ + if ( nShapeElement < 0 ) + return; + + if (m_pTextExport && lcl_isTextBox(m_pSdrObject)) + { + uno::Reference<beans::XPropertySet> xPropertySet(const_cast<SdrObject*>(m_pSdrObject)->getUnoShape(), uno::UNO_QUERY); + comphelper::SequenceAsHashMap aCustomShapeProperties(xPropertySet->getPropertyValue("CustomShapeGeometry")); + sax_fastparser::FastAttributeList* pTextboxAttrList = FastSerializerHelper::createAttrList(); + if (aCustomShapeProperties.find("TextPreRotateAngle") != aCustomShapeProperties.end()) + { + sal_Int32 nTextRotateAngle = aCustomShapeProperties["TextPreRotateAngle"].get<sal_Int32>(); + if (nTextRotateAngle == -270) + pTextboxAttrList->add(XML_style, "mso-layout-flow-alt:bottom-to-top"); + } + sax_fastparser::XFastAttributeListRef xTextboxAttrList(pTextboxAttrList); + pTextboxAttrList = nullptr; + m_pSerializer->startElementNS(XML_v, XML_textbox, xTextboxAttrList); + + m_pTextExport->WriteVMLTextBox(uno::Reference<drawing::XShape>(xPropertySet, uno::UNO_QUERY_THROW)); + + m_pSerializer->endElementNS(XML_v, XML_textbox); + } + + if (m_pWrapAttrList) + { + sax_fastparser::XFastAttributeListRef const pWrapAttrList(m_pWrapAttrList.release()); + m_pSerializer->singleElementNS(XML_w10, XML_wrap, pWrapAttrList); + } + + // end of the shape + m_pSerializer->endElementNS( XML_v, nShapeElement ); +} + +OString const & VMLExport::AddSdrObject( const SdrObject& rObj, sal_Int16 eHOri, sal_Int16 eVOri, sal_Int16 eHRel, sal_Int16 eVRel, + std::unique_ptr<FastAttributeList> pWrapAttrList, + const bool bOOxmlExport ) +{ + m_pSdrObject = &rObj; + m_eHOri = eHOri; + m_eVOri = eVOri; + m_eHRel = eHRel; + m_eVRel = eVRel; + m_pWrapAttrList = std::move(pWrapAttrList); + m_bInline = false; + 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.reset(); + m_bInline = true; + EscherEx::AddSdrObject(rObj, bOOxmlExport); + return m_sShapeId; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |