diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /oox/source/export | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'oox/source/export')
-rw-r--r-- | oox/source/export/ColorExportUtils.cxx | 60 | ||||
-rw-r--r-- | oox/source/export/ColorPropertySet.cxx | 178 | ||||
-rw-r--r-- | oox/source/export/ColorPropertySet.hxx | 87 | ||||
-rw-r--r-- | oox/source/export/DMLPresetShapeExport.cxx | 1656 | ||||
-rw-r--r-- | oox/source/export/README | 2 | ||||
-rw-r--r-- | oox/source/export/ThemeExport.cxx | 902 | ||||
-rw-r--r-- | oox/source/export/chartexport.cxx | 4712 | ||||
-rw-r--r-- | oox/source/export/drawingml.cxx | 6591 | ||||
-rw-r--r-- | oox/source/export/ooxml-export-notes.txt | 234 | ||||
-rw-r--r-- | oox/source/export/preset-definitions-to-shape-types.pl | 1237 | ||||
-rw-r--r-- | oox/source/export/presetTextWarpDefinitions.xml | 1885 | ||||
-rw-r--r-- | oox/source/export/shapes.cxx | 2906 | ||||
-rw-r--r-- | oox/source/export/vmlexport.cxx | 1613 |
13 files changed, 22063 insertions, 0 deletions
diff --git a/oox/source/export/ColorExportUtils.cxx b/oox/source/export/ColorExportUtils.cxx new file mode 100644 index 0000000000..d46ab493b5 --- /dev/null +++ b/oox/source/export/ColorExportUtils.cxx @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> +#include <array> +#include <oox/export/ColorExportUtils.hxx> +#include <docmodel/color/ComplexColor.hxx> + +namespace oox +{ +double convertColorTransformsToTintOrShade(model::ComplexColor const& rComplexColor) +{ + sal_Int16 nLumMod = 10'000; + sal_Int16 nLumOff = 0; + + for (auto const& rTransform : rComplexColor.getTransformations()) + { + if (rTransform.meType == model::TransformationType::LumMod) + nLumMod = rTransform.mnValue; + if (rTransform.meType == model::TransformationType::LumOff) + nLumOff = rTransform.mnValue; + } + + if (nLumMod == 10'000 && nLumOff == 0) + return 0.0; + + double fTint = 0.0; + + if (nLumOff > 0) // tint + fTint = double(nLumOff) / 10'000.0; + else + fTint = -double(10'000 - nLumMod) / 10'000.0; + + return fTint; +} + +sal_Int32 convertThemeColorTypeToExcelThemeNumber(model::ThemeColorType eType) +{ + if (eType == model::ThemeColorType::Unknown) + return -1; + + // Change position of text1 and text2 and background1 and background2 - needed because of an bug in excel, where + // the text and background index positions are switched. + // 0 -> 1, 1 -> 0 + // 2 -> 3, 3 -> 2 + // everything else stays the same + static constexpr std::array<sal_Int32, 12> constThemeColorMapToXmlMap + = { 1, 0, 3, 2, 4, 5, 6, 7, 8, 9, 10, 11 }; + + return constThemeColorMapToXmlMap[sal_Int32(eType)]; +} +} // end oox + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/export/ColorPropertySet.cxx b/oox/source/export/ColorPropertySet.cxx new file mode 100644 index 0000000000..714870d93e --- /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, getXWeak()); +} + +sal_Bool SAL_CALL lcl_ColorPropertySetInfo::hasPropertyByName( const OUString& Name ) +{ + return Name == m_aColorPropName; +} + +} // anonymous namespace + +namespace oox::drawingml +{ + +ColorPropertySet::ColorPropertySet( ::Color nColor, bool bFillColor /* = true */ ) : + // note: length of FillColor and LineColor is 9 + m_aColorPropName( (bFillColor ? "FillColor" : "LineColor"), 9, RTL_TEXTENCODING_ASCII_US ), + m_nColor( nColor ), + m_bIsFillColor( bFillColor ), + m_nDefaultColor( 0x0099ccff ) // blue 8 +{} + +ColorPropertySet::~ColorPropertySet() +{} + +// ____ XPropertySet ____ + +Reference< XPropertySetInfo > SAL_CALL ColorPropertySet::getPropertySetInfo() +{ + if( ! m_xInfo.is()) + m_xInfo.set( new lcl_ColorPropertySetInfo( m_bIsFillColor )); + + return m_xInfo; +} + +void SAL_CALL ColorPropertySet::setPropertyValue( const OUString& rPropertyName, const uno::Any& aValue ) +{ + if (rPropertyName != m_aColorPropName) + { + // trying to catch these cases in the next crash testing + assert(false); + return; + } + + aValue >>= m_nColor; +} + +uno::Any SAL_CALL ColorPropertySet::getPropertyValue( const OUString& aPropertyName ) +{ + if( aPropertyName == "FillStyle" && m_bIsFillColor ) + { + return uno::Any(css::drawing::FillStyle_SOLID); + } + else if (aPropertyName == m_aColorPropName) + return uno::Any( m_nColor ); + + throw UnknownPropertyException(aPropertyName); +} + +void SAL_CALL ColorPropertySet::addPropertyChangeListener( const OUString& /* aPropertyName */, const Reference< XPropertyChangeListener >& /* xListener */ ) +{ + OSL_FAIL( "Not Implemented" ); +} + +void SAL_CALL ColorPropertySet::removePropertyChangeListener( const OUString& /* aPropertyName */, const Reference< XPropertyChangeListener >& /* aListener */ ) +{ + OSL_FAIL( "Not Implemented" ); +} + +void SAL_CALL ColorPropertySet::addVetoableChangeListener( const OUString& /* PropertyName */, const Reference< XVetoableChangeListener >& /* aListener */ ) +{ + OSL_FAIL( "Not Implemented" ); +} + +void SAL_CALL ColorPropertySet::removeVetoableChangeListener( const OUString& /* PropertyName */, const Reference< XVetoableChangeListener >& /* aListener */ ) +{ + OSL_FAIL( "Not Implemented" ); +} + +// ____ XPropertyState ____ + +PropertyState SAL_CALL ColorPropertySet::getPropertyState( const OUString& /* PropertyName */ ) +{ + return PropertyState_DIRECT_VALUE; +} + +Sequence< PropertyState > SAL_CALL ColorPropertySet::getPropertyStates( const Sequence< OUString >& /* aPropertyName */ ) +{ + PropertyState aState = PropertyState_DIRECT_VALUE; + // coverity[overrun-buffer-arg : FALSE] - coverity has difficulty with css::uno::Sequence + return Sequence<PropertyState>(&aState, 1); +} + +void SAL_CALL ColorPropertySet::setPropertyToDefault( const OUString& PropertyName ) +{ + if( PropertyName == m_aColorPropName ) + m_nColor = m_nDefaultColor; +} + +uno::Any SAL_CALL ColorPropertySet::getPropertyDefault( const OUString& aPropertyName ) +{ + if( aPropertyName == m_aColorPropName ) + return uno::Any( m_nDefaultColor ); + + return uno::Any(); +} + +} // namespace oox::drawingml + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/export/ColorPropertySet.hxx b/oox/source/export/ColorPropertySet.hxx new file mode 100644 index 0000000000..70213a731b --- /dev/null +++ b/oox/source/export/ColorPropertySet.hxx @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef XMLOFF_COLORPROPERTYSET_HXX +#define XMLOFF_COLORPROPERTYSET_HXX + +// FIXME? this file is identical to xmloff/source/chart/ColorPropertySet.hxx + +#include <cppuhelper/implbase.hxx> +#include <tools/color.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> + +namespace oox::drawingml +{ + +class ColorPropertySet : public ::cppu::WeakImplHelper< + css::beans::XPropertySet, + css::beans::XPropertyState > +{ +public: + // if bFillColor == false, the color is a LineColor + explicit ColorPropertySet( ::Color nColor, bool bFillColor = true ); + virtual ~ColorPropertySet() override; + +protected: + // ____ XPropertySet ____ + virtual css::uno::Reference< css::beans::XPropertySetInfo > SAL_CALL getPropertySetInfo() override; + virtual void SAL_CALL setPropertyValue( + const OUString& aPropertyName, + const css::uno::Any& aValue ) override; + virtual css::uno::Any SAL_CALL getPropertyValue( + const OUString& PropertyName ) override; + virtual void SAL_CALL addPropertyChangeListener( + const OUString& aPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& xListener ) override; + virtual void SAL_CALL removePropertyChangeListener( + const OUString& aPropertyName, + const css::uno::Reference< css::beans::XPropertyChangeListener >& aListener ) override; + virtual void SAL_CALL addVetoableChangeListener( + const OUString& PropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + virtual void SAL_CALL removeVetoableChangeListener( + const OUString& PropertyName, + const css::uno::Reference< css::beans::XVetoableChangeListener >& aListener ) override; + + // ____ XPropertyState ____ + virtual css::beans::PropertyState SAL_CALL getPropertyState( + const OUString& PropertyName ) override; + virtual css::uno::Sequence< css::beans::PropertyState > SAL_CALL getPropertyStates( + const css::uno::Sequence< OUString >& aPropertyName ) override; + virtual void SAL_CALL setPropertyToDefault( + const OUString& PropertyName ) override; + virtual css::uno::Any SAL_CALL getPropertyDefault( + const OUString& aPropertyName ) override; + +private: + css::uno::Reference< css::beans::XPropertySetInfo > m_xInfo; + OUString m_aColorPropName; + ::Color m_nColor; + bool m_bIsFillColor; + ::Color m_nDefaultColor; +}; + +} // namespace xmloff::chart + +// XMLOFF_COLORPROPERTYSET_HXX +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/export/DMLPresetShapeExport.cxx b/oox/source/export/DMLPresetShapeExport.cxx new file mode 100644 index 0000000000..0ed099ee57 --- /dev/null +++ b/oox/source/export/DMLPresetShapeExport.cxx @@ -0,0 +1,1656 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* +* This file is part of the LibreOffice project. +* +* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this +* file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include <oox/export/DMLPresetShapeExport.hxx> +#include <oox/token/tokens.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/PropertyValue.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp> +#include <com/sun/star/drawing/XShape.hpp> + +#include <osl/diagnose.h> +#include <filter/msfilter/util.hxx> + +#include <string_view> + +using namespace ::css; +using namespace ::css::drawing; + +namespace oox::drawingml +{ +// DMLPresetShapeExporter class + +// ctor +DMLPresetShapeExporter::DMLPresetShapeExporter(DrawingML* pDMLExporter, + css::uno::Reference<css::drawing::XShape> xShape) + : m_pDMLexporter(pDMLExporter) +{ + // This class only work with custom shapes! + OSL_ASSERT(xShape->getShapeType() == "com.sun.star.drawing.CustomShape"); + + m_xShape = xShape; + m_bHasHandleValues = false; + uno::Reference<beans::XPropertySet> xShapeProps(m_xShape, uno::UNO_QUERY); + css::uno::Sequence<css::beans::PropertyValue> aCustomShapeGeometry + = xShapeProps->getPropertyValue("CustomShapeGeometry") + .get<uno::Sequence<beans::PropertyValue>>(); + + for (auto const& rCustomShapeGeometryItem : aCustomShapeGeometry) + { + if (rCustomShapeGeometryItem.Name == "Type") + { + m_sPresetShapeType = rCustomShapeGeometryItem.Value.get<OUString>(); + } + if (rCustomShapeGeometryItem.Name == "Handles") + { + m_bHasHandleValues = true; + m_HandleValues + = rCustomShapeGeometryItem.Value + .get<css::uno::Sequence<css::uno::Sequence<css::beans::PropertyValue>>>(); + } + if (rCustomShapeGeometryItem.Name == "AdjustmentValues") + { + m_AdjustmentValues + = rCustomShapeGeometryItem.Value + .get<css::uno::Sequence<css::drawing::EnhancedCustomShapeAdjustmentValue>>(); + } + if (rCustomShapeGeometryItem.Name == "MirroredX") + { + m_bIsFlipped.first = rCustomShapeGeometryItem.Value.get<bool>(); + } + if (rCustomShapeGeometryItem.Name == "MirroredY") + { + m_bIsFlipped.second = rCustomShapeGeometryItem.Value.get<bool>(); + } + //if (rCustomShapeGeometryItem.Name == "Equations") + //{ + // m_Equations = rCustomShapeGeometryItem.Value.get<css::uno::Sequence<OUString>>(); + //} + //if (rCustomShapeGeometryItem.Name == "Path") + //{ + // m_Path = rCustomShapeGeometryItem + // .Value.get<css::uno::Sequence<css::beans::PropertyValue>>(); + //} + //if (rCustomShapeGeometryItem.Name == "ViewBox") + //{ + // m_ViewBox = rCustomShapeGeometryItem.Value.get<css::awt::Rectangle>(); + //} + } +}; + +// dtor +DMLPresetShapeExporter::~DMLPresetShapeExporter(){ + // Do nothing +}; + +bool DMLPresetShapeExporter::HasHandleValue() const { return m_bHasHandleValues; } + +const OUString& DMLPresetShapeExporter::GetShapeType() const { return m_sPresetShapeType; } + +const css::uno::Sequence<css::uno::Sequence<css::beans::PropertyValue>>& +DMLPresetShapeExporter::GetHandleValues() const +{ + return m_HandleValues; +}; + +const css::uno::Sequence<css::drawing::EnhancedCustomShapeAdjustmentValue>& +DMLPresetShapeExporter::GetAdjustmentValues() const +{ + return m_AdjustmentValues; +}; + +css::uno::Any DMLPresetShapeExporter::GetHandleValueOfModificationPoint(sal_Int32 nPoint, + std::u16string_view sType) +{ + uno::Any aRet; + if (GetHandleValues().getLength() > nPoint) + { + for (sal_Int32 i = 0; i < GetHandleValues()[nPoint].getLength(); i++) + { + if (GetHandleValues()[nPoint][i].Name == sType) + { + aRet = GetHandleValues()[nPoint][i].Value; + break; + } + } + } + return aRet; +}; + +DMLPresetShapeExporter::RadiusAdjustmentValue +DMLPresetShapeExporter::GetAdjustmentPointRadiusValue(sal_Int32 nPoint) +{ + RadiusAdjustmentValue aRet; + try + { + auto aValPos = GetHandleValueOfModificationPoint(nPoint, u"Position") + .get<EnhancedCustomShapeParameterPair>(); + aRet.nMinVal = GetHandleValueOfModificationPoint(nPoint, u"RadiusRangeMinimum") + .get<EnhancedCustomShapeParameter>() + .Value.get<double>(); + aRet.nMaxVal = GetHandleValueOfModificationPoint(nPoint, u"RadiusRangeMaximum") + .get<EnhancedCustomShapeParameter>() + .Value.get<double>(); + aRet.nCurrVal = GetAdjustmentValues()[aValPos.First.Value.get<long>()].Value.get<double>(); + } + catch (...) + { + // Do nothing. + } + return aRet; +}; + +DMLPresetShapeExporter::AngleAdjustmentValue +DMLPresetShapeExporter::GetAdjustmentPointAngleValue(sal_Int32 nPoint) +{ + AngleAdjustmentValue aRet; + try + { + auto aValPos = GetHandleValueOfModificationPoint(nPoint, u"Position") + .get<EnhancedCustomShapeParameterPair>(); + aRet.nMinVal = 0; + aRet.nMaxVal = 360; + aRet.nCurrVal = GetAdjustmentValues()[aValPos.Second.Value.get<long>()].Value.get<double>(); + } + catch (...) + { + // Do nothing. + } + return aRet; +}; + +DMLPresetShapeExporter::XAdjustmentValue +DMLPresetShapeExporter::GetAdjustmentPointXValue(sal_Int32 nPoint) +{ + XAdjustmentValue aRet; + try + { + auto aValPos = GetHandleValueOfModificationPoint(nPoint, u"Position") + .get<EnhancedCustomShapeParameterPair>(); + aRet.nMinVal = GetHandleValueOfModificationPoint(nPoint, u"RangeXMinimum") + .get<EnhancedCustomShapeParameter>() + .Value.get<double>(); + aRet.nMaxVal = GetHandleValueOfModificationPoint(nPoint, u"RangeXMaximum") + .get<EnhancedCustomShapeParameter>() + .Value.get<double>(); + aRet.nCurrVal = GetAdjustmentValues()[aValPos.First.Value.get<long>()].Value.get<double>(); + } + catch (...) + { + // Do nothing. + } + return aRet; +}; + +DMLPresetShapeExporter::YAdjustmentValue +DMLPresetShapeExporter::GetAdjustmentPointYValue(sal_Int32 nPoint) +{ + YAdjustmentValue aRet; + try + { + auto aValPos = GetHandleValueOfModificationPoint(nPoint, u"Position") + .get<EnhancedCustomShapeParameterPair>(); + aRet.nMinVal = GetHandleValueOfModificationPoint(nPoint, u"RangeYMinimum") + .get<EnhancedCustomShapeParameter>() + .Value.get<double>(); + aRet.nMaxVal = GetHandleValueOfModificationPoint(nPoint, u"RangeYMaximum") + .get<EnhancedCustomShapeParameter>() + .Value.get<double>(); + aRet.nCurrVal = GetAdjustmentValues()[aValPos.Second.Value.get<long>()].Value.get<double>(); + } + catch (...) + { + // Do nothing. + } + return aRet; +}; + +bool DMLPresetShapeExporter::WriteShape() +{ + if (m_pDMLexporter && m_xShape) + { + // Case 1: We do not have adjustment points of the shape: just export it as preset + if (!m_bHasHandleValues) + { + OUString sShapeType = GetShapeType(); + const OString& sPresetShape = msfilter::util::GetOOXMLPresetGeometry(sShapeType); + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + m_pDMLexporter->WritePresetShape(sPresetShape); + return true; + } + else // Case2: There are adjustment points what have to be converted and exported. + { + return WriteShapeWithAVlist(); + } + } + return false; +}; + +bool DMLPresetShapeExporter::WriteAV(const OUString& sValName, const OUString& sVal) +{ + try + { + m_pDMLexporter->GetFS()->singleElementNS(XML_a, XML_gd, XML_name, sValName, XML_fmla, sVal); + return true; + } + catch (...) + { + return false; + } +}; + +bool DMLPresetShapeExporter::StartAVListWriting() +{ + try + { + const OString& pShape = msfilter::util::GetOOXMLPresetGeometry(GetShapeType()); + m_pDMLexporter->GetFS()->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape); + m_pDMLexporter->GetFS()->startElementNS(XML_a, XML_avLst); + return true; + } + catch (...) + { + return false; + } +}; +bool DMLPresetShapeExporter::EndAVListWriting() +{ + try + { + m_pDMLexporter->GetFS()->endElementNS(XML_a, XML_avLst); + m_pDMLexporter->GetFS()->endElementNS(XML_a, XML_prstGeom); + return true; + } + catch (...) + { + return false; + } +}; + +bool DMLPresetShapeExporter::WriteShapeWithAVlist() +{ + // Remark: This method is under development. If a shape type is implemented, the corresponding, + // return must be set to true. False means nothing done true, export done. There are many + // types which do not have pairs in LO, they are do not have to be mapped, because import + // filter it does with GrabBag, this method only maps the SDR ones to OOXML shapes. + + OString sShapeType(msfilter::util::GetOOXMLPresetGeometry(GetShapeType())); + + // OOXML uses 60th of degree, so 360 degree is 21 600 000 60thdeg + const tools::Long nConstOfMaxDegreeOf60th = 21600000; + try + { + if (sShapeType == "accentBorderCallout1") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "accentBorderCallout2") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "accentBorderCallout3") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "accentCallout1") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "accentCallout2") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "accentCallout3") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "actionButtonBackPrevious") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "actionButtonBeginning") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "actionButtonBlank") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "actionButtonDocument") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "actionButtonEnd") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "actionButtonForwardNext") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "actionButtonHelp") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "actionButtonHome") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "actionButtonInformation") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "actionButtonMovie") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "actionButtonReturn") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "actionButtonSound") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "arc") + { + // LO does not have handle points for this, so CustGeom is enough. + return false; + } + if (sShapeType == "bentArrow") + { + // LO has only one type, which have to be rotated, without handling points + // So CustGeom enough. + return false; + } + if (sShapeType == "bentConnector2") + { + // CustGeom Enough + return false; + } + if (sShapeType == "bentConnector3") + { + // CustGeom Enough + return false; + } + if (sShapeType == "bentConnector4") + { + // CustGeom Enough + return false; + } + if (sShapeType == "bentConnector5") + { + // CustGeom Enough + return false; + } + if (sShapeType == "bentUpArrow") + { + // CustGeom Enough, no handle points + return false; + } + if (sShapeType == "bevel") + { + auto aPoint1 = GetAdjustmentPointXValue(0); + if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value() + || !aPoint1.nMinVal.has_value()) + return false; + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + + tools::Long nVal1 + = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * 50000); + return StartAVListWriting() + && WriteAV(u"adj"_ustr, OUString(u"val " + OUString::number(nVal1))) + && EndAVListWriting(); + } + if (sShapeType == "blockArc") + { + auto aPointR = GetAdjustmentPointRadiusValue(0); + auto aPointA = GetAdjustmentPointAngleValue(0); + if (!aPointA.nCurrVal.has_value() || !aPointA.nMaxVal.has_value() + || !aPointA.nMinVal.has_value() || !aPointR.nCurrVal.has_value() + || !aPointR.nMaxVal.has_value() || !aPointR.nMinVal.has_value()) + return false; + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nVal1 + = std::lround((*aPointA.nCurrVal < 0 ? 360 + *aPointA.nCurrVal : *aPointA.nCurrVal) + / (*aPointA.nMaxVal - *aPointA.nMinVal) * nConstOfMaxDegreeOf60th); + tools::Long nVal2 = std::lround( + (*aPointA.nCurrVal > 180 ? 360 - *aPointA.nCurrVal : 180 - *aPointA.nCurrVal) + / (*aPointA.nMaxVal - *aPointA.nMinVal) * nConstOfMaxDegreeOf60th); + tools::Long nVal3 = std::lround( + 50000 - (*aPointR.nCurrVal / (*aPointR.nMaxVal - *aPointR.nMinVal) * 50000)); + return StartAVListWriting() + && WriteAV(u"adj1"_ustr, OUString(u"val " + OUString::number(nVal1))) + && WriteAV(u"adj2"_ustr, OUString(u"val " + OUString::number(nVal2))) + && WriteAV(u"adj3"_ustr, OUString(u"val " + OUString::number(nVal3))) + && EndAVListWriting(); + } + if (sShapeType == "borderCallout1") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "borderCallout2") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "borderCallout3") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "bracePair") + { + auto aPoint1 = GetAdjustmentPointYValue(0); + if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value() + || !aPoint1.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nVal1 + = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * 25000); + return StartAVListWriting() + && WriteAV(u"adj"_ustr, OUString(u"val " + OUString::number(nVal1))) + && EndAVListWriting(); + } + if (sShapeType == "bracketPair") + { + auto aPoint1 = GetAdjustmentPointYValue(0); + if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value() + || !aPoint1.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nVal1 + = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * 50000); + return StartAVListWriting() + && WriteAV(u"adj"_ustr, OUString(u"val " + OUString::number(nVal1))) + && EndAVListWriting(); + } + if (sShapeType == "callout1") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "callout2") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "callout3") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "can") + { + return false; + // Do the export as before. + } + if (sShapeType == "chartPlus") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "chartStar") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "chartX") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "chord") + { + // CustGeom, because LO does not have handle points + return false; + } + if (sShapeType == "circularArrow") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "cloud") + { + // CustGeom enough + return false; + } + if (sShapeType == "cloudCallout") + { + return false; + // Works fine without this, so export it like before. + } + if (sShapeType == "cornerTabs") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "cube") + { + // Works fine without this, so export it like before. + return false; + } + if (sShapeType == "curvedConnector2") + { + // Not necessary to be mapped + return false; + } + if (sShapeType == "curvedConnector3") + { + // Not necessary to be mapped + return false; + } + if (sShapeType == "curvedConnector4") + { + // Not necessary to be mapped + return false; + } + if (sShapeType == "curvedConnector5") + { + // Not necessary to be mapped + return false; + } + if (sShapeType == "curvedDownArrow") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "curvedLeftArrow") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "curvedRightArrow") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "curvedUpArrow") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "decagon") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "diagStripe") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "diamond") + { + // It does not have handle points so it do not have to be mapped. + return false; + } + if (sShapeType == "dodecagon") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "donut") + { + // TODO + return false; + } + if (sShapeType == "doubleWave") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "downArrow") + { + auto aPointX = GetAdjustmentPointXValue(0); + auto aPointY = GetAdjustmentPointYValue(0); + if (!aPointX.nCurrVal.has_value() || !aPointX.nMaxVal.has_value() + || !aPointX.nMinVal.has_value() || !aPointY.nCurrVal.has_value() + || !aPointY.nMaxVal.has_value() || !aPointY.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nMaxVal1 = 100000; + tools::Long nMaxVal2 + = 100000 * m_xShape->getSize().Height + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nVal1 = std::lround((*aPointX.nMaxVal - *aPointX.nCurrVal) + / (*aPointX.nMaxVal - *aPointX.nMinVal) * nMaxVal1); + tools::Long nVal2 = std::lround((*aPointY.nMaxVal - *aPointY.nCurrVal) + / (*aPointY.nMaxVal - *aPointY.nMinVal) * nMaxVal2); + return StartAVListWriting() + && WriteAV(u"adj1"_ustr, OUString(u"val " + OUString::number(nVal1))) + && WriteAV(u"adj2"_ustr, OUString(u"val " + OUString::number(nVal2))) + && EndAVListWriting(); + } + if (sShapeType == "downArrowCallout") + { + auto aNeckFromBox = GetAdjustmentPointXValue(1); + auto aHeadFromNeck = GetAdjustmentPointXValue(2); + auto aHeadHeight = GetAdjustmentPointYValue(1); + auto aBoxHeight = GetAdjustmentPointYValue(0); + if (!aNeckFromBox.nCurrVal.has_value() || !aNeckFromBox.nMaxVal.has_value() + || !aNeckFromBox.nMinVal.has_value() || !aHeadFromNeck.nCurrVal.has_value() + || !aHeadFromNeck.nMaxVal.has_value() || !aHeadFromNeck.nMinVal.has_value() + || !aHeadHeight.nCurrVal.has_value() || !aHeadHeight.nMaxVal.has_value() + || !aHeadHeight.nMinVal.has_value() || !aBoxHeight.nCurrVal.has_value() + || !aBoxHeight.nMaxVal.has_value() || !aBoxHeight.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nMaxVal1 + = 100000 * m_xShape->getSize().Width + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nMaxVal2 + = 50000 * m_xShape->getSize().Width + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nMaxVal3 + = 100000 * m_xShape->getSize().Height + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nVal1 + = std::lround((*aNeckFromBox.nMaxVal - *aNeckFromBox.nCurrVal) + / (*aNeckFromBox.nMaxVal - *aNeckFromBox.nMinVal) * nMaxVal1); + tools::Long nVal2 = std::lround((10800 - *aHeadFromNeck.nCurrVal) + / (10800 - *aHeadFromNeck.nMinVal) * nMaxVal2); + tools::Long nVal3 + = std::lround((*aHeadHeight.nMaxVal - *aHeadHeight.nCurrVal) + / (*aHeadHeight.nMaxVal - *aHeadHeight.nMinVal) * nMaxVal3); + tools::Long nVal4 = std::lround((*aBoxHeight.nCurrVal - *aBoxHeight.nMinVal) + / (21600 - *aBoxHeight.nMinVal) * 100000); + return StartAVListWriting() + && WriteAV(u"adj1"_ustr, OUString(u"val " + OUString::number(nVal1))) + && WriteAV(u"adj2"_ustr, OUString(u"val " + OUString::number(nVal2))) + && WriteAV(u"adj3"_ustr, OUString(u"val " + OUString::number(nVal3))) + && WriteAV(u"adj4"_ustr, OUString(u"val " + OUString::number(nVal4))) + && EndAVListWriting(); + } + if (sShapeType == "ellipse") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "ellipseRibbon") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "ellipseRibbon2") + { + // LO does not have this type, so it does not necessary to be mapped. + return false; + } + if (sShapeType == "flowChartAlternateProcess") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartCollate") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartConnector") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartDecision") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartDecision") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartDelay") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartDisplay") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartDocument") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartExtract") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartInputOutput") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartInternalStorage") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartMagneticDisk") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartMagneticDrum") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartMagneticTape") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartManualInput") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartManualOperation") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartMerge") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartMultidocument") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartOfflineStorage") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartOffpageConnector") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartOnlineStorage") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartOr") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartDecision") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartPredefinedProcess") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartPreparation") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartPunchedCard") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartPunchedTape") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartSort") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartSummingJunction") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "flowChartTerminator") + { + // Does not have handle points, so preset enough. + return false; + } + if (sShapeType == "foldedCorner") + { + // TODO + return false; + } + if (sShapeType == "frame") + { + // TODO + return false; + } + if (sShapeType == "funnel") + { + // Not found in word + return false; + } + if (sShapeType == "gear6") + { + // Not found in word + return false; + } + if (sShapeType == "gear9") + { + // Not found in word + return false; + } + if (sShapeType == "halfFrame") + { + // LO does not have this type, not necessary to map + return false; + } + if (sShapeType == "heart") + { + // TODO + return false; + } + if (sShapeType == "heptagon") + { + // LO does not have this type, not necessary to map + return false; + } + if (sShapeType == "hexagon") + { + auto aPoint1 = GetAdjustmentPointXValue(0); + if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value() + || !aPoint1.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nMaxVal = 50000 * m_xShape->getSize().Width + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nVal1 + = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * nMaxVal); + return StartAVListWriting() + && WriteAV(u"adj"_ustr, OUString(u"val " + OUString::number(nVal1))) + && WriteAV(u"vf"_ustr, OUString(u"val " + OUString::number(115470))) + && EndAVListWriting(); + } + if (sShapeType == "homePlate") + { + // Not found in word + return false; + } + if (sShapeType == "horizontalScroll") + { + // TODO + return false; + } + if (sShapeType == "irregularSeal1") + { + // Not found in word + return false; + } + if (sShapeType == "irregularSeal2") + { + // Not found in word + return false; + } + if (sShapeType == "leftArrow") + { + auto aPointX = GetAdjustmentPointXValue(0); + auto aPointY = GetAdjustmentPointYValue(0); + if (!aPointX.nCurrVal.has_value() || !aPointX.nMaxVal.has_value() + || !aPointX.nMinVal.has_value() || !aPointY.nCurrVal.has_value() + || !aPointY.nMaxVal.has_value() || !aPointY.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nMaxVal1 = 100000; + tools::Long nMaxVal2 + = 100000 + * (double(m_xShape->getSize().Width) + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height)); + tools::Long nVal1 = std::lround((*aPointY.nMaxVal - *aPointY.nCurrVal) + / (*aPointY.nMaxVal - *aPointY.nMinVal) * nMaxVal1); + tools::Long nVal2 = std::lround((*aPointX.nCurrVal - *aPointX.nMinVal) + / (*aPointX.nMaxVal - *aPointX.nMinVal) * nMaxVal2); + return StartAVListWriting() + && WriteAV(u"adj1"_ustr, OUString(u"val " + OUString::number(nVal1))) + && WriteAV(u"adj2"_ustr, OUString(u"val " + OUString::number(nVal2))) + && EndAVListWriting(); + } + if (sShapeType == "leftArrowCallout") + { + auto aBoxWidth = GetAdjustmentPointXValue(0); + auto aNeckLength = GetAdjustmentPointXValue(1); + auto aNeckFromBox = GetAdjustmentPointYValue(1); + auto aHeadFromNeck = GetAdjustmentPointYValue(2); + if (!aBoxWidth.nCurrVal.has_value() || !aBoxWidth.nMaxVal.has_value() + || !aBoxWidth.nMinVal.has_value() || !aNeckLength.nCurrVal.has_value() + || !aNeckLength.nMaxVal.has_value() || !aNeckLength.nMinVal.has_value() + || !aNeckFromBox.nCurrVal.has_value() || !aNeckFromBox.nMaxVal.has_value() + || !aNeckFromBox.nMinVal.has_value() || !aHeadFromNeck.nCurrVal.has_value() + || !aHeadFromNeck.nMaxVal.has_value() || !aHeadFromNeck.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nMaxVal1 + = 100000 * m_xShape->getSize().Height + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nMaxVal2 + = 50000 * m_xShape->getSize().Height + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nMaxVal3 + = 100000 * m_xShape->getSize().Width + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nVal1 + = std::lround((*aNeckFromBox.nMaxVal - *aNeckFromBox.nCurrVal) + / (*aNeckFromBox.nMaxVal - *aNeckFromBox.nMinVal) * nMaxVal1); + tools::Long nVal2 = std::lround((10800 - *aHeadFromNeck.nCurrVal) + / (10800 - *aHeadFromNeck.nMinVal) * nMaxVal2); + tools::Long nVal3 = std::lround((*aNeckLength.nCurrVal - *aNeckLength.nMinVal) + / (21600 - *aNeckLength.nMinVal) * nMaxVal3); + tools::Long nVal4 = std::lround((*aBoxWidth.nMaxVal - *aBoxWidth.nCurrVal) + / (*aBoxWidth.nMaxVal - *aBoxWidth.nMinVal) * 100000); + return StartAVListWriting() + && WriteAV(u"adj1"_ustr, OUString(u"val " + OUString::number(nVal1))) + && WriteAV(u"adj2"_ustr, OUString(u"val " + OUString::number(nVal2))) + && WriteAV(u"adj3"_ustr, OUString(u"val " + OUString::number(nVal3))) + && WriteAV(u"adj4"_ustr, OUString(u"val " + OUString::number(nVal4))) + && EndAVListWriting(); + } + if (sShapeType == "leftBrace") + { + // TODO + return false; + } + if (sShapeType == "leftBracket") + { + // TODO + return false; + } + if (sShapeType == "leftCircularArrow") + { + // LO does not have this type, not necessary to map + return false; + } + if (sShapeType == "leftRightArrow") + { + auto aPointX = GetAdjustmentPointXValue(0); + auto aPointY = GetAdjustmentPointYValue(0); + if (!aPointX.nCurrVal.has_value() || !aPointX.nMaxVal.has_value() + || !aPointX.nMinVal.has_value() || !aPointY.nCurrVal.has_value() + || !aPointY.nMaxVal.has_value() || !aPointY.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nMaxVal1 = 100000; + tools::Long nMaxVal2 + = 50000 + * (double(m_xShape->getSize().Width) + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height)); + tools::Long nVal1 = std::lround((*aPointY.nMaxVal - *aPointY.nCurrVal) + / (*aPointY.nMaxVal - *aPointY.nMinVal) * nMaxVal1); + tools::Long nVal2 = std::lround((*aPointX.nCurrVal - *aPointX.nMinVal) + / (*aPointX.nMaxVal - *aPointX.nMinVal) * nMaxVal2); + return StartAVListWriting() + && WriteAV(u"adj1"_ustr, OUString(u"val " + OUString::number(nVal1))) + && WriteAV(u"adj2"_ustr, OUString(u"val " + OUString::number(nVal2))) + && EndAVListWriting(); + } + if (sShapeType == "leftRightArrowCallout") + { + auto aNeckFromBox = GetAdjustmentPointXValue(1); + auto aHeadFromNeck = GetAdjustmentPointXValue(2); + auto aHeadHeight = GetAdjustmentPointYValue(1); + auto aBoxHeight = GetAdjustmentPointYValue(0); + if (!aNeckFromBox.nCurrVal.has_value() || !aNeckFromBox.nMaxVal.has_value() + || !aNeckFromBox.nMinVal.has_value() || !aHeadFromNeck.nCurrVal.has_value() + || !aHeadFromNeck.nMaxVal.has_value() || !aHeadFromNeck.nMinVal.has_value() + || !aHeadHeight.nCurrVal.has_value() || !aHeadHeight.nMaxVal.has_value() + || !aHeadHeight.nMinVal.has_value() || !aBoxHeight.nCurrVal.has_value() + || !aBoxHeight.nMaxVal.has_value() || !aBoxHeight.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nMaxVal1 + = 100000 * m_xShape->getSize().Width + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nMaxVal2 + = 50000 * m_xShape->getSize().Width + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nMaxVal3 + = 100000 * m_xShape->getSize().Height + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nVal1 + = std::lround((*aNeckFromBox.nMaxVal - *aNeckFromBox.nCurrVal) + / (*aNeckFromBox.nMaxVal - *aNeckFromBox.nMinVal) * nMaxVal1); + tools::Long nVal2 = std::lround((10800 - *aHeadFromNeck.nCurrVal) + / (10800 - *aHeadFromNeck.nMinVal) * nMaxVal2); + tools::Long nVal3 = std::lround((*aHeadHeight.nCurrVal - *aHeadHeight.nMinVal) + / (21600 - *aHeadHeight.nMinVal) * nMaxVal3); + tools::Long nVal4 = std::lround((*aBoxHeight.nCurrVal - *aBoxHeight.nMinVal) + / (10800 - *aBoxHeight.nMinVal) * 100000); + return StartAVListWriting() + && WriteAV(u"adj1"_ustr, OUString(u"val " + OUString::number(nVal1))) + && WriteAV(u"adj2"_ustr, OUString(u"val " + OUString::number(nVal2))) + && WriteAV(u"adj3"_ustr, OUString(u"val " + OUString::number(nVal3))) + && WriteAV(u"adj4"_ustr, OUString(u"val " + OUString::number(nVal4))) + && EndAVListWriting(); + } + if (sShapeType == "leftRightCircularArrow") + { + // Not found in word + return false; + } + if (sShapeType == "leftRightRibbon") + { + // LO does not have this type so mapping not necessary + return false; + } + if (sShapeType == "leftRightUpArrow") + { + // TODO? + // MS Word stretches the arrow to fit the bounding box; LO doesn't + return false; + } + if (sShapeType == "leftUpArrow") + { + // MS Word's and LO's interpretations of what a leftUpArrow should look like + // are too different to find a compromise :( + } + if (sShapeType == "lightningBolt") + { + // Difference between the SDR and OOXML variants, custgeom? + return false; + } + if (sShapeType == "line") + { + // Not necessary + return false; + } + if (sShapeType == "lineInv") + { + // Not necessary + return false; + } + if (sShapeType == "mathDivide") + { + // LO does not have this type so mapping not necessary + return false; + } + if (sShapeType == "mathEqual") + { + // LO does not have this type so mapping not necessary + return false; + } + if (sShapeType == "mathMinus") + { + // LO does not have this type so mapping not necessary + return false; + } + if (sShapeType == "mathMultiply") + { + // LO does not have this type so mapping not necessary + return false; + } + if (sShapeType == "mathNotEqual") + { + // LO does not have this type so mapping not necessary + return false; + } + if (sShapeType == "mathPlus") + { + // LO does not have this type so mapping not necessary + return false; + } + if (sShapeType == "nonIsoscelesTrapezoid") + { + // TODO + return false; + } + if (sShapeType == "noSmoking") + { + // TODO + return false; + } + if (sShapeType == "notchedRightArrow") + { + // TODO + return false; + } + if (sShapeType == "octagon") + { + auto aPoint1 = GetAdjustmentPointXValue(0); + if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value() + || !aPoint1.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nVal1 + = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * 50000); + return StartAVListWriting() + && WriteAV(u"adj"_ustr, OUString(u"val " + OUString::number(nVal1))) + && EndAVListWriting(); + } + if (sShapeType == "parallelogram") + { + auto aPoint1 = GetAdjustmentPointXValue(0); + if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value() + || !aPoint1.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nMaxVal = 100000 * m_xShape->getSize().Width + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nVal1 + = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * nMaxVal); + return StartAVListWriting() + && WriteAV(u"adj"_ustr, OUString(u"val " + OUString::number(nVal1))) + && EndAVListWriting(); + } + if (sShapeType == "pentagon") + { + // TODO + return false; + } + if (sShapeType == "pie") + { + // TODO + return false; + } + if (sShapeType == "pieWedge") + { + // Not found in word. + return false; + } + if (sShapeType == "plaque") + { + // TODO + return false; + } + if (sShapeType == "plaqueTabs") + { + // LO does not have this, so not necessary to map. + return false; + } + if (sShapeType == "plus") + { + auto aPoint1 = GetAdjustmentPointXValue(0); + if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value() + || !aPoint1.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nVal1 + = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * 50000); + return StartAVListWriting() + && WriteAV(u"adj"_ustr, OUString(u"val " + OUString::number(nVal1))) + && EndAVListWriting(); + } + if (sShapeType == "quadArrow") + { + // TODO + return false; + } + if (sShapeType == "quadArrowCallout") + { + // TODO + return false; + } + if (sShapeType == "rect") + { + // preset enough without AV points. + return false; + } + if (sShapeType == "ribbon") + { + // LO does not have this, so not necessary to map. + return false; + } + if (sShapeType == "ribbon2") + { + // LO does not have this, so not necessary to map. + return false; + } + if (sShapeType == "rightArrow") + { + auto aPointX = GetAdjustmentPointXValue(0); + auto aPointY = GetAdjustmentPointYValue(0); + if (!aPointX.nCurrVal.has_value() || !aPointX.nMaxVal.has_value() + || !aPointX.nMinVal.has_value() || !aPointY.nCurrVal.has_value() + || !aPointY.nMaxVal.has_value() || !aPointY.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nMaxVal1 = 100000; + tools::Long nMaxVal2 + = 100000 + * (double(m_xShape->getSize().Width) + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height)); + tools::Long nVal1 = std::lround((*aPointY.nMaxVal - *aPointY.nCurrVal) + / (*aPointY.nMaxVal - *aPointY.nMinVal) * nMaxVal1); + tools::Long nVal2 = std::lround((*aPointX.nMaxVal - *aPointX.nCurrVal) + / (*aPointX.nMaxVal - *aPointX.nMinVal) * nMaxVal2); + return StartAVListWriting() + && WriteAV(u"adj1"_ustr, OUString(u"val " + OUString::number(nVal1))) + && WriteAV(u"adj2"_ustr, OUString(u"val " + OUString::number(nVal2))) + && EndAVListWriting(); + } + if (sShapeType == "rightArrowCallout") + { + auto aBoxWidth = GetAdjustmentPointXValue(0); + auto aNeckLength = GetAdjustmentPointXValue(1); + auto aNeckFromBox = GetAdjustmentPointYValue(1); + auto aHeadFromNeck = GetAdjustmentPointYValue(2); + if (!aBoxWidth.nCurrVal.has_value() || !aBoxWidth.nMaxVal.has_value() + || !aBoxWidth.nMinVal.has_value() || !aNeckLength.nCurrVal.has_value() + || !aNeckLength.nMaxVal.has_value() || !aNeckLength.nMinVal.has_value() + || !aNeckFromBox.nCurrVal.has_value() || !aNeckFromBox.nMaxVal.has_value() + || !aNeckFromBox.nMinVal.has_value() || !aHeadFromNeck.nCurrVal.has_value() + || !aHeadFromNeck.nMaxVal.has_value() || !aHeadFromNeck.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nMaxVal1 + = 100000 * m_xShape->getSize().Height + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nMaxVal2 + = 50000 * m_xShape->getSize().Height + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nMaxVal3 + = 100000 * m_xShape->getSize().Width + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nVal1 + = std::lround((*aNeckFromBox.nMaxVal - *aNeckFromBox.nCurrVal) + / (*aNeckFromBox.nMaxVal - *aNeckFromBox.nMinVal) * nMaxVal1); + tools::Long nVal2 = std::lround((10800 - *aHeadFromNeck.nCurrVal) + / (10800 - *aHeadFromNeck.nMinVal) * nMaxVal2); + tools::Long nVal3 + = std::lround((*aNeckLength.nMaxVal - *aNeckLength.nCurrVal) + / (*aNeckLength.nMaxVal - *aNeckLength.nMinVal) * nMaxVal3); + tools::Long nVal4 = std::lround((*aBoxWidth.nCurrVal - *aBoxWidth.nMinVal) + / (21600 - *aBoxWidth.nMinVal) * 100000); + return StartAVListWriting() + && WriteAV(u"adj1"_ustr, OUString(u"val " + OUString::number(nVal1))) + && WriteAV(u"adj2"_ustr, OUString(u"val " + OUString::number(nVal2))) + && WriteAV(u"adj3"_ustr, OUString(u"val " + OUString::number(nVal3))) + && WriteAV(u"adj4"_ustr, OUString(u"val " + OUString::number(nVal4))) + && EndAVListWriting(); + } + if (sShapeType == "rightBrace") + { + // TODO + return false; + } + if (sShapeType == "rightBracket") + { + // TODO + return false; + } + if (sShapeType == "round1Rect") + { + // LO does not have this, so not necessary to map. + return false; + } + if (sShapeType == "round2DiagRect") + { + // LO does not have this, so not necessary to map. + return false; + } + if (sShapeType == "round2SameRect") + { + // LO does not have this, so not necessary to map. + return false; + } + if (sShapeType == "roundRect") + { + tools::Long nVal1 = 0; + if (m_xShape->getSize().Width >= m_xShape->getSize().Height) + { + auto aPointX = GetAdjustmentPointXValue(0); + if (!aPointX.nCurrVal.has_value() || !aPointX.nMaxVal.has_value() + || !aPointX.nMinVal.has_value()) + return false; + nVal1 = std::lround(*aPointX.nCurrVal / (*aPointX.nMaxVal - *aPointX.nMinVal) + * 50000); + } + else + { + auto aPointY = GetAdjustmentPointYValue(0); + if (!aPointY.nCurrVal.has_value() || !aPointY.nMaxVal.has_value() + || !aPointY.nMinVal.has_value()) + return false; + nVal1 = std::lround(*aPointY.nCurrVal / (*aPointY.nMaxVal - *aPointY.nMinVal) + * 50000); + } + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + return StartAVListWriting() + && WriteAV(u"adj"_ustr, OUString(u"val " + OUString::number(nVal1))) + && EndAVListWriting(); + } + if (sShapeType == "rtTriangle") + { + // Does not have AV points not necessary to map + return false; + } + if (sShapeType == "smileyFace") + { + // TODO + return false; + } + if (sShapeType == "snip1Rect") + { + // LO does not have this, so not necessary to map. + return false; + } + if (sShapeType == "snip2DiagRect") + { + // LO does not have this, so not necessary to map. + return false; + } + if (sShapeType == "snip2SameRect") + { + // LO does not have this, so not necessary to map. + return false; + } + if (sShapeType == "snipRoundRect") + { + // LO does not have this, so not necessary to map. + return false; + } + if (sShapeType == "squareTabs") + { + // LO does not have this, so not necessary to map. + return false; + } + if (sShapeType == "star10") + { + // LO does not have this, so not necessary to map. + return false; + } + if (sShapeType == "star12") + { + // TODO + return false; + } + if (sShapeType == "star16") + { + // LO does not have this, so not necessary to map. + return false; + } + if (sShapeType == "star24") + { + // TODO + return false; + } + if (sShapeType == "star32") + { + // LO does not have this, so not necessary to map. + return false; + } + if (sShapeType == "star4") + { + // TODO + return false; + } + if (sShapeType == "star5") + { + // TODO + return false; + } + if (sShapeType == "star6") + { + // TODO + return false; + } + if (sShapeType == "star7") + { + // LO does not have this, so not necessary to map. + return false; + } + if (sShapeType == "star8") + { + // TODO + return false; + } + if (sShapeType == "straightConnector1") + { + // Not necessary to map. + return false; + } + if (sShapeType == "stripedRightArrow") + { + // TODO + return false; + } + if (sShapeType == "sun") + { + // TODO + return false; + } + if (sShapeType == "swooshArrow") + { + // Not found in word. + return false; + } + if (sShapeType == "teardrop") + { + // TODO + return false; + } + if (sShapeType == "trapezoid") + { + // Preset enough. + return false; + } + if (sShapeType == "triangle") + { + auto aPoint1 = GetAdjustmentPointXValue(0); + if (!aPoint1.nCurrVal.has_value() || !aPoint1.nMaxVal.has_value() + || !aPoint1.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nMaxVal = 100000; + tools::Long nVal1 + = std::lround(*aPoint1.nCurrVal / (*aPoint1.nMaxVal - *aPoint1.nMinVal) * nMaxVal); + return StartAVListWriting() + && WriteAV(u"adj"_ustr, OUString(u"val " + OUString::number(nVal1))) + && EndAVListWriting(); + } + if (sShapeType == "upArrowCallout") + { + auto aNeckFromBox = GetAdjustmentPointXValue(1); + auto aHeadFromNeck = GetAdjustmentPointXValue(2); + auto aHeadHeight = GetAdjustmentPointYValue(1); + auto aBoxHeight = GetAdjustmentPointYValue(0); + if (!aNeckFromBox.nCurrVal.has_value() || !aNeckFromBox.nMaxVal.has_value() + || !aNeckFromBox.nMinVal.has_value() || !aHeadFromNeck.nCurrVal.has_value() + || !aHeadFromNeck.nMaxVal.has_value() || !aHeadFromNeck.nMinVal.has_value() + || !aHeadHeight.nCurrVal.has_value() || !aHeadHeight.nMaxVal.has_value() + || !aHeadHeight.nMinVal.has_value() || !aBoxHeight.nCurrVal.has_value() + || !aBoxHeight.nMaxVal.has_value() || !aBoxHeight.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nMaxVal1 + = 100000 * m_xShape->getSize().Width + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nMaxVal2 + = 50000 * m_xShape->getSize().Width + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nMaxVal3 + = 100000 * m_xShape->getSize().Height + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nVal1 + = std::lround((*aNeckFromBox.nMaxVal - *aNeckFromBox.nCurrVal) + / (*aNeckFromBox.nMaxVal - *aNeckFromBox.nMinVal) * nMaxVal1); + tools::Long nVal2 = std::lround((10800 - *aHeadFromNeck.nCurrVal) + / (10800 - *aHeadFromNeck.nMinVal) * nMaxVal2); + tools::Long nVal3 = std::lround((*aHeadHeight.nCurrVal - *aHeadHeight.nMinVal) + / (21600 - *aHeadHeight.nMinVal) * nMaxVal3); + tools::Long nVal4 = std::lround((*aBoxHeight.nCurrVal - *aBoxHeight.nMinVal) + / (10800 - *aBoxHeight.nMinVal) * 100000); + return StartAVListWriting() + && WriteAV(u"adj1"_ustr, OUString(u"val " + OUString::number(nVal1))) + && WriteAV(u"adj2"_ustr, OUString(u"val " + OUString::number(nVal2))) + && WriteAV(u"adj3"_ustr, OUString(u"val " + OUString::number(nVal3))) + && WriteAV(u"adj4"_ustr, OUString(u"val " + OUString::number(nVal4))) + && EndAVListWriting(); + } + if (sShapeType == "upDownArrow") + { + auto aPointX = GetAdjustmentPointXValue(0); + auto aPointY = GetAdjustmentPointYValue(0); + if (!aPointX.nCurrVal.has_value() || !aPointX.nMaxVal.has_value() + || !aPointX.nMinVal.has_value() || !aPointY.nCurrVal.has_value() + || !aPointY.nMaxVal.has_value() || !aPointY.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nMaxVal1 = 100000; + tools::Long nMaxVal2 + = 50000 * m_xShape->getSize().Height + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nVal1 = std::lround((*aPointX.nMaxVal - *aPointX.nCurrVal) + / (*aPointX.nMaxVal - *aPointX.nMinVal) * nMaxVal1); + tools::Long nVal2 = std::lround((*aPointY.nCurrVal - *aPointY.nMinVal) + / (*aPointY.nMaxVal - *aPointY.nMinVal) * nMaxVal2); + return StartAVListWriting() + && WriteAV(u"adj1"_ustr, OUString(u"val " + OUString::number(nVal1))) + && WriteAV(u"adj2"_ustr, OUString(u"val " + OUString::number(nVal2))) + && EndAVListWriting(); + } + if (sShapeType == "upArrow") + { + auto aPointX = GetAdjustmentPointXValue(0); + auto aPointY = GetAdjustmentPointYValue(0); + if (!aPointX.nCurrVal.has_value() || !aPointX.nMaxVal.has_value() + || !aPointX.nMinVal.has_value() || !aPointY.nCurrVal.has_value() + || !aPointY.nMaxVal.has_value() || !aPointY.nMinVal.has_value()) + return false; + + m_pDMLexporter->WriteShapeTransformation(m_xShape, XML_a, IsXFlipped(), IsYFlipped(), + false, false); + tools::Long nMaxVal1 = 100000; + tools::Long nMaxVal2 + = 100000 * m_xShape->getSize().Height + / std::min(m_xShape->getSize().Width, m_xShape->getSize().Height); + tools::Long nVal1 = std::lround((*aPointX.nMaxVal - *aPointX.nCurrVal) + / (*aPointX.nMaxVal - *aPointX.nMinVal) * nMaxVal1); + tools::Long nVal2 = std::lround((*aPointY.nCurrVal - *aPointY.nMinVal) + / (*aPointY.nMaxVal - *aPointY.nMinVal) * nMaxVal2); + return StartAVListWriting() + && WriteAV(u"adj1"_ustr, OUString(u"val " + OUString::number(nVal1))) + && WriteAV(u"adj2"_ustr, OUString(u"val " + OUString::number(nVal2))) + && EndAVListWriting(); + } + if (sShapeType == "upDownArrowCallout") + { + // TODO + return false; + } + if (sShapeType == "uturnArrow") + { + // LO does not have like this. + return false; + } + if (sShapeType == "verticalScroll") + { + // TODO + return false; + } + if (sShapeType == "wave") + { + // LO does not have. + return false; + } + if (sShapeType == "wedgeEllipseCallout") + { + // TODO + return false; + } + if (sShapeType == "wedgeRectCallout") + { + // TODO + return false; + } + if (sShapeType == "wedgeRoundRectCallout") + { + // TODO + return false; + } + } + catch (...) + { + // Problem detected with the writing, aborting and trying to find another way. + return false; + } + + // Default, nothing happened return. + return false; +}; +} diff --git a/oox/source/export/README b/oox/source/export/README new file mode 100644 index 0000000000..56195b9770 --- /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/ThemeExport.cxx b/oox/source/export/ThemeExport.cxx new file mode 100644 index 0000000000..6e0efb341a --- /dev/null +++ b/oox/source/export/ThemeExport.cxx @@ -0,0 +1,902 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <oox/export/ThemeExport.hxx> + +#include <oox/token/namespaces.hxx> +#include <oox/token/properties.hxx> +#include <oox/token/tokens.hxx> +#include <oox/export/utils.hxx> +#include <docmodel/theme/Theme.hxx> +#include <docmodel/theme/FormatScheme.hxx> +#include <sax/fshelper.hxx> +#include <sax/fastattribs.hxx> +#include <unordered_map> +#include <oox/export/drawingml.hxx> +#include <frozen/bits/defines.h> +#include <frozen/bits/elsa_std.h> +#include <frozen/unordered_map.h> + +namespace oox +{ +namespace +{ +void writeRelativeRectangle(sax_fastparser::FSHelperPtr pFS, sal_Int32 nToken, + model::RelativeRectangle const& rRelativeRectangle) +{ + pFS->singleElementNS(XML_a, nToken, XML_l, OString::number(rRelativeRectangle.mnLeft), XML_t, + OString::number(rRelativeRectangle.mnTop), XML_r, + OString::number(rRelativeRectangle.mnRight), XML_b, + OString::number(rRelativeRectangle.mnBottom)); +} +} // end anonymous namespace + +ThemeExport::ThemeExport(oox::core::XmlFilterBase* pFilterBase, + oox::drawingml::DocumentType eDocumentType) + : mpFilterBase(pFilterBase) + , meDocumentType(eDocumentType) +{ +} + +void ThemeExport::write(OUString const& rPath, model::Theme const& rTheme) +{ + mpFS = mpFilterBase->openFragmentStreamWithSerializer( + rPath, "application/vnd.openxmlformats-officedocument.theme+xml"); + + OUString aThemeName = rTheme.GetName(); + + mpFS->startElementNS(XML_a, XML_theme, FSNS(XML_xmlns, XML_a), + mpFilterBase->getNamespaceURL(OOX_NS(dml)), FSNS(XML_xmlns, XML_r), + mpFilterBase->getNamespaceURL(OOX_NS(officeRel)), XML_name, aThemeName); + + mpFS->startElementNS(XML_a, XML_themeElements); + + const auto pColorSet = rTheme.getColorSet(); + + mpFS->startElementNS(XML_a, XML_clrScheme, XML_name, pColorSet->getName()); + writeColorSet(rTheme); + mpFS->endElementNS(XML_a, XML_clrScheme); + + model::FontScheme const& rFontScheme = rTheme.getFontScheme(); + mpFS->startElementNS(XML_a, XML_fontScheme, XML_name, rFontScheme.getName()); + writeFontScheme(rFontScheme); + mpFS->endElementNS(XML_a, XML_fontScheme); + + model::FormatScheme const& rFormatScheme = rTheme.getFormatScheme(); + mpFS->startElementNS(XML_a, XML_fmtScheme); + writeFormatScheme(rFormatScheme); + mpFS->endElementNS(XML_a, XML_fmtScheme); + + mpFS->endElementNS(XML_a, XML_themeElements); + mpFS->endElementNS(XML_a, XML_theme); + + mpFS->endDocument(); +} + +namespace +{ +void fillAttrList(rtl::Reference<sax_fastparser::FastAttributeList> const& pAttrList, + model::ThemeFont const& rThemeFont) +{ + if (rThemeFont.maTypeface.isEmpty()) + { + pAttrList->add(XML_typeface, ""); // 'typeface' attribute is mandatory + return; + } + + pAttrList->add(XML_typeface, rThemeFont.maTypeface); + + if (!rThemeFont.maPanose.isEmpty()) + pAttrList->add(XML_panose, rThemeFont.maPanose); + + pAttrList->add(XML_pitchFamily, OString::number(rThemeFont.getPitchFamily())); + pAttrList->add(XML_charset, OString::number(rThemeFont.maCharset)); +} + +} // end anonymous ns + +bool ThemeExport::writeFontScheme(model::FontScheme const& rFontScheme) +{ + mpFS->startElementNS(XML_a, XML_majorFont); + + { + auto aAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + fillAttrList(aAttrList, rFontScheme.getMajorLatin()); + mpFS->singleElementNS(XML_a, XML_latin, aAttrList); + } + { + auto aAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + fillAttrList(aAttrList, rFontScheme.getMajorAsian()); + mpFS->singleElementNS(XML_a, XML_ea, aAttrList); + } + { + auto aAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + fillAttrList(aAttrList, rFontScheme.getMajorComplex()); + mpFS->singleElementNS(XML_a, XML_cs, aAttrList); + } + + mpFS->endElementNS(XML_a, XML_majorFont); + + mpFS->startElementNS(XML_a, XML_minorFont); + + { + auto aAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + fillAttrList(aAttrList, rFontScheme.getMinorLatin()); + mpFS->singleElementNS(XML_a, XML_latin, aAttrList); + } + { + auto aAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + fillAttrList(aAttrList, rFontScheme.getMinorAsian()); + mpFS->singleElementNS(XML_a, XML_ea, aAttrList); + } + { + auto aAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + fillAttrList(aAttrList, rFontScheme.getMinorComplex()); + mpFS->singleElementNS(XML_a, XML_cs, aAttrList); + } + + mpFS->endElementNS(XML_a, XML_minorFont); + + return true; +} + +namespace +{ +constexpr frozen::unordered_map<model::TransformationType, sal_Int32, 4> constTransformTypeTokenMap{ + { model::TransformationType::Tint, XML_tint }, + { model::TransformationType::Shade, XML_shade }, + { model::TransformationType::LumMod, XML_lumMod }, + { model::TransformationType::LumOff, XML_lumOff }, +}; + +constexpr frozen::unordered_map<model::ThemeColorType, const char*, 12> constThemeColorTypeTokenMap{ + { model::ThemeColorType::Dark1, "dk1" }, + { model::ThemeColorType::Light1, "lt1" }, + { model::ThemeColorType::Dark2, "dk2" }, + { model::ThemeColorType::Light2, "lt2" }, + { model::ThemeColorType::Accent1, "accent1" }, + { model::ThemeColorType::Accent2, "accent2" }, + { model::ThemeColorType::Accent3, "accent3" }, + { model::ThemeColorType::Accent4, "accent4" }, + { model::ThemeColorType::Accent5, "accent5" }, + { model::ThemeColorType::Accent6, "accent6" }, + { model::ThemeColorType::Hyperlink, "hlink" }, + { model::ThemeColorType::FollowedHyperlink, "folHlink" } +}; + +constexpr frozen::unordered_map<model::SystemColorType, const char*, 30> + constSystemColorTypeTokenMap{ + { model::SystemColorType::DarkShadow3D, "3dDkShadow" }, + { model::SystemColorType::Light3D, "3dLight" }, + { model::SystemColorType::ActiveBorder, "activeBorder" }, + { model::SystemColorType::ActiveCaption, "activeCaption" }, + { model::SystemColorType::AppWorkspace, "appWorkspace" }, + { model::SystemColorType::Background, "background" }, + { model::SystemColorType::ButtonFace, "btnFace" }, + { model::SystemColorType::ButtonHighlight, "btnHighlight" }, + { model::SystemColorType::ButtonShadow, "btnShadow" }, + { model::SystemColorType::ButtonText, "btnText" }, + { model::SystemColorType::CaptionText, "captionText" }, + { model::SystemColorType::GradientActiveCaption, "gradientActiveCaption" }, + { model::SystemColorType::GradientInactiveCaption, "gradientInactiveCaption" }, + { model::SystemColorType::GrayText, "grayText" }, + { model::SystemColorType::Highlight, "highlight" }, + { model::SystemColorType::HighlightText, "highlightText" }, + { model::SystemColorType::HotLight, "hotLight" }, + { model::SystemColorType::InactiveBorder, "inactiveBorder" }, + { model::SystemColorType::InactiveCaption, "inactiveCaption" }, + { model::SystemColorType::InactiveCaptionText, "inactiveCaptionText" }, + { model::SystemColorType::InfoBack, "infoBk" }, + { model::SystemColorType::InfoText, "infoText" }, + { model::SystemColorType::Menu, "menu" }, + { model::SystemColorType::MenuBar, "menuBar" }, + { model::SystemColorType::MenuHighlight, "menuHighlight" }, + { model::SystemColorType::MenuText, "menuText" }, + { model::SystemColorType::ScrollBar, "scrollBar" }, + { model::SystemColorType::Window, "window" }, + { model::SystemColorType::WindowFrame, "windowFrame" }, + { model::SystemColorType::WindowText, "windowText" } + }; + +constexpr frozen::unordered_map<sal_Int32, model::ThemeColorType, 12> constTokenMap{ + { XML_dk1, model::ThemeColorType::Dark1 }, + { XML_lt1, model::ThemeColorType::Light1 }, + { XML_dk2, model::ThemeColorType::Dark2 }, + { XML_lt2, model::ThemeColorType::Light2 }, + { XML_accent1, model::ThemeColorType::Accent1 }, + { XML_accent2, model::ThemeColorType::Accent2 }, + { XML_accent3, model::ThemeColorType::Accent3 }, + { XML_accent4, model::ThemeColorType::Accent4 }, + { XML_accent5, model::ThemeColorType::Accent5 }, + { XML_accent6, model::ThemeColorType::Accent6 }, + { XML_hlink, model::ThemeColorType::Hyperlink }, + { XML_folHlink, model::ThemeColorType::FollowedHyperlink } +}; + +} // end anonymous ns + +void ThemeExport::writeColorTransformations( + std::vector<model::Transformation> const& rTransformations) +{ + for (model::Transformation const& rTransformation : rTransformations) + { + auto iterator = constTransformTypeTokenMap.find(rTransformation.meType); + if (iterator != constTransformTypeTokenMap.end()) + { + sal_Int32 nToken = iterator->second; + mpFS->singleElementNS(XML_a, nToken, XML_val, + OString::number(rTransformation.mnValue * 10)); + } + } +} + +void ThemeExport::writeColorRGB(model::ComplexColor const& rComplexColor) +{ + auto aColor = rComplexColor.getRGBColor(); + mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, I32SHEX(sal_Int32(aColor))); + mpFS->endElementNS(XML_a, XML_srgbClr); +} + +void ThemeExport::writeColorCRGB(model::ComplexColor const& rComplexColor) +{ + mpFS->startElementNS(XML_a, XML_scrgbClr, XML_r, + OString::number(sal_Int32(rComplexColor.getRed())), XML_g, + OString::number(sal_Int32(rComplexColor.getGreen())), XML_b, + OString::number(sal_Int32(rComplexColor.getBlue()))); + writeColorTransformations(rComplexColor.getTransformations()); + mpFS->endElementNS(XML_a, XML_scrgbClr); +} + +void ThemeExport::writeColorHSL(model::ComplexColor const& rComplexColor) +{ + mpFS->startElementNS(XML_a, XML_hslClr, XML_hue, + OString::number(sal_Int32(rComplexColor.getRed())), XML_sat, + OString::number(sal_Int32(rComplexColor.getGreen())), XML_lum, + OString::number(sal_Int32(rComplexColor.getBlue()))); + writeColorTransformations(rComplexColor.getTransformations()); + mpFS->endElementNS(XML_a, XML_hslClr); +} + +void ThemeExport::writeColorTheme(model::ComplexColor const& rComplexColor) +{ + auto iterator = constThemeColorTypeTokenMap.find(rComplexColor.getThemeColorType()); + if (iterator != constThemeColorTypeTokenMap.end()) + { + const char* sValue = iterator->second; + mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sValue); + writeColorTransformations(rComplexColor.getTransformations()); + mpFS->endElementNS(XML_a, XML_schemeClr); + } +} + +void ThemeExport::writeColorSystem(model::ComplexColor const& rComplexColor) +{ + auto iterator = constSystemColorTypeTokenMap.find(rComplexColor.getSystemColorType()); + if (iterator != constSystemColorTypeTokenMap.end()) + { + const char* sValue = iterator->second; + mpFS->startElementNS(XML_a, XML_sysClr, XML_val, sValue); + //XML_lastClr + writeColorTransformations(rComplexColor.getTransformations()); + mpFS->endElementNS(XML_a, XML_schemeClr); + } +} + +void ThemeExport::writeColorPlaceholder(model::ComplexColor const& rComplexColor) +{ + mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, "phClr"); + writeColorTransformations(rComplexColor.getTransformations()); + mpFS->endElementNS(XML_a, XML_schemeClr); +} + +void ThemeExport::writeComplexColor(model::ComplexColor const& rComplexColor) +{ + switch (rComplexColor.getType()) + { + case model::ColorType::Unused: + break; + case model::ColorType::RGB: + writeColorRGB(rComplexColor); + break; + case model::ColorType::CRGB: + writeColorCRGB(rComplexColor); + break; + case model::ColorType::HSL: + writeColorHSL(rComplexColor); + break; + case model::ColorType::Theme: + writeColorTheme(rComplexColor); + break; + case model::ColorType::Palette: + break; + case model::ColorType::System: + writeColorSystem(rComplexColor); + break; + case model::ColorType::Placeholder: + writeColorPlaceholder(rComplexColor); + break; + } +} + +void ThemeExport::writeSolidFill(model::SolidFill const& rSolidFill) +{ + mpFS->startElementNS(XML_a, XML_solidFill); + writeComplexColor(rSolidFill.maColor); + mpFS->endElementNS(XML_a, XML_solidFill); +} + +void ThemeExport::writeGradientFill(model::GradientFill const& rGradientFill) +{ + mpFS->startElementNS(XML_a, XML_gradFill); + mpFS->startElementNS(XML_a, XML_gsLst); + for (auto const& rStop : rGradientFill.maGradientStops) + { + mpFS->startElementNS(XML_a, XML_gs, XML_pos, + OString::number(sal_Int32(rStop.mfPosition * 100000.0))); + writeComplexColor(rStop.maColor); + mpFS->endElementNS(XML_a, XML_gs); + } + mpFS->endElementNS(XML_a, XML_gsLst); + + if (rGradientFill.meGradientType == model::GradientType::Linear) + { + mpFS->singleElementNS(XML_a, XML_lin, XML_ang, + OString::number(rGradientFill.maLinearGradient.mnAngle), XML_scaled, + rGradientFill.maLinearGradient.mbScaled ? "1" : "0"); + } + else + { + OString sPathType; + switch (rGradientFill.meGradientType) + { + case model::GradientType::Circle: + sPathType = "circle"_ostr; + break; + case model::GradientType::Rectangle: + sPathType = "rect"_ostr; + break; + case model::GradientType::Shape: + sPathType = "shape"_ostr; + break; + default: + break; + } + + if (!sPathType.isEmpty()) + { + mpFS->startElementNS(XML_a, XML_path, XML_path, sPathType); + writeRelativeRectangle(mpFS, XML_fillToRect, rGradientFill.maFillToRectangle); + mpFS->endElementNS(XML_a, XML_path); + } + } + writeRelativeRectangle(mpFS, XML_tileRect, rGradientFill.maTileRectangle); + mpFS->endElementNS(XML_a, XML_gradFill); +} + +void ThemeExport::writePatternFill(model::PatternFill const& rPatternFill) +{ + OString sPresetType; + switch (rPatternFill.mePatternPreset) + { + case model::PatternPreset::Percent_5: + sPresetType = "pct5"_ostr; + break; + case model::PatternPreset::Percent_10: + sPresetType = "pct10"_ostr; + break; + case model::PatternPreset::Percent_20: + sPresetType = "pct20"_ostr; + break; + case model::PatternPreset::Percent_25: + sPresetType = "pct25"_ostr; + break; + case model::PatternPreset::Percent_30: + sPresetType = "pct30"_ostr; + break; + case model::PatternPreset::Percent_40: + sPresetType = "pct40"_ostr; + break; + case model::PatternPreset::Percent_50: + sPresetType = "pct50"_ostr; + break; + case model::PatternPreset::Percent_60: + sPresetType = "pct60"_ostr; + break; + case model::PatternPreset::Percent_70: + sPresetType = "pct70"_ostr; + break; + case model::PatternPreset::Percent_75: + sPresetType = "pct75"_ostr; + break; + case model::PatternPreset::Percent_80: + sPresetType = "pct80"_ostr; + break; + case model::PatternPreset::Percent_90: + sPresetType = "pct90"_ostr; + break; + case model::PatternPreset::Horizontal: + sPresetType = "horz"_ostr; + break; + case model::PatternPreset::Vertical: + sPresetType = "vert"_ostr; + break; + case model::PatternPreset::LightHorizontal: + sPresetType = "ltHorz"_ostr; + break; + case model::PatternPreset::LightVertical: + sPresetType = "ltVert"_ostr; + break; + case model::PatternPreset::DarkHorizontal: + sPresetType = "dkHorz"_ostr; + break; + case model::PatternPreset::DarkVertical: + sPresetType = "dkVert"_ostr; + break; + case model::PatternPreset::NarrowHorizontal: + sPresetType = "narHorz"_ostr; + break; + case model::PatternPreset::NarrowVertical: + sPresetType = "narVert"_ostr; + break; + case model::PatternPreset::DashedHorizontal: + sPresetType = "dashHorz"_ostr; + break; + case model::PatternPreset::DashedVertical: + sPresetType = "dashVert"_ostr; + break; + case model::PatternPreset::Cross: + sPresetType = "cross"_ostr; + break; + case model::PatternPreset::DownwardDiagonal: + sPresetType = "dnDiag"_ostr; + break; + case model::PatternPreset::UpwardDiagonal: + sPresetType = "upDiag"_ostr; + break; + case model::PatternPreset::LightDownwardDiagonal: + sPresetType = "ltDnDiag"_ostr; + break; + case model::PatternPreset::LightUpwardDiagonal: + sPresetType = "ltUpDiag"_ostr; + break; + case model::PatternPreset::DarkDownwardDiagonal: + sPresetType = "dkDnDiag"_ostr; + break; + case model::PatternPreset::DarkUpwardDiagonal: + sPresetType = "dkUpDiag"_ostr; + break; + case model::PatternPreset::WideDownwardDiagonal: + sPresetType = "wdDnDiag"_ostr; + break; + case model::PatternPreset::WideUpwardDiagonal: + sPresetType = "wdUpDiag"_ostr; + break; + case model::PatternPreset::DashedDownwardDiagonal: + sPresetType = "dashDnDiag"_ostr; + break; + case model::PatternPreset::DashedUpwardDiagonal: + sPresetType = "dashUpDiag"_ostr; + break; + case model::PatternPreset::DiagonalCross: + sPresetType = "diagCross"_ostr; + break; + case model::PatternPreset::SmallCheckerBoard: + sPresetType = "smCheck"_ostr; + break; + case model::PatternPreset::LargeCheckerBoard: + sPresetType = "lgCheck"_ostr; + break; + case model::PatternPreset::SmallGrid: + sPresetType = "smGrid"_ostr; + break; + case model::PatternPreset::LargeGrid: + sPresetType = "lgGrid"_ostr; + break; + case model::PatternPreset::DottedGrid: + sPresetType = "dotGrid"_ostr; + break; + case model::PatternPreset::SmallConfetti: + sPresetType = "smConfetti"_ostr; + break; + case model::PatternPreset::LargeConfetti: + sPresetType = "lgConfetti"_ostr; + break; + case model::PatternPreset::HorizontalBrick: + sPresetType = "horzBrick"_ostr; + break; + case model::PatternPreset::DiagonalBrick: + sPresetType = "diagBrick"_ostr; + break; + case model::PatternPreset::SolidDiamond: + sPresetType = "solidDmnd"_ostr; + break; + case model::PatternPreset::OpenDiamond: + sPresetType = "openDmnd"_ostr; + break; + case model::PatternPreset::DottedDiamond: + sPresetType = "dotDmnd"_ostr; + break; + case model::PatternPreset::Plaid: + sPresetType = "plaid"_ostr; + break; + case model::PatternPreset::Sphere: + sPresetType = "sphere"_ostr; + break; + case model::PatternPreset::Weave: + sPresetType = "weave"_ostr; + break; + case model::PatternPreset::Divot: + sPresetType = "divot"_ostr; + break; + case model::PatternPreset::Shingle: + sPresetType = "shingle"_ostr; + break; + case model::PatternPreset::Wave: + sPresetType = "wave"_ostr; + break; + case model::PatternPreset::Trellis: + sPresetType = "trellis"_ostr; + break; + case model::PatternPreset::ZigZag: + sPresetType = "zigZag"_ostr; + break; + default: + break; + } + + if (!sPresetType.isEmpty()) + { + mpFS->startElementNS(XML_a, XML_pattFill, XML_prst, sPresetType); + + mpFS->startElementNS(XML_a, XML_fgClr); + writeComplexColor(rPatternFill.maForegroundColor); + mpFS->endElementNS(XML_a, XML_fgClr); + + mpFS->startElementNS(XML_a, XML_bgClr); + writeComplexColor(rPatternFill.maBackgroundColor); + mpFS->endElementNS(XML_a, XML_bgClr); + + mpFS->endElementNS(XML_a, XML_pattFill); + } +} + +namespace +{ +OString convertFlipMode(model::FlipMode eFlipMode) +{ + switch (eFlipMode) + { + case model::FlipMode::X: + return "x"_ostr; + case model::FlipMode::Y: + return "y"_ostr; + case model::FlipMode::XY: + return "xy"_ostr; + case model::FlipMode::None: + return "none"_ostr; + } + return "none"_ostr; +} + +OString convertRectangleAlignment(model::RectangleAlignment eFlipMode) +{ + switch (eFlipMode) + { + case model::RectangleAlignment::TopLeft: + return "tl"_ostr; + case model::RectangleAlignment::Top: + return "t"_ostr; + case model::RectangleAlignment::TopRight: + return "tr"_ostr; + case model::RectangleAlignment::Left: + return "l"_ostr; + case model::RectangleAlignment::Center: + return "ctr"_ostr; + case model::RectangleAlignment::Right: + return "r"_ostr; + case model::RectangleAlignment::BottomLeft: + return "bl"_ostr; + case model::RectangleAlignment::Bottom: + return "b"_ostr; + case model::RectangleAlignment::BottomRight: + return "br"_ostr; + case model::RectangleAlignment::Unset: + break; + } + return {}; +} +} // end anonymous ns + +void ThemeExport::writeBlip(model::BlipFill const& rBlipFill) +{ + if (!rBlipFill.mxGraphic.is()) + return; + oox::drawingml::GraphicExport aExporter(mpFS, mpFilterBase, meDocumentType); + Graphic aGraphic(rBlipFill.mxGraphic); + aExporter.writeBlip(aGraphic, rBlipFill.maBlipEffects, false); +} + +void ThemeExport::writeBlipFill(model::BlipFill const& rBlipFill) +{ + mpFS->startElementNS(XML_a, XML_blipFill, XML_rotWithShape, + rBlipFill.mbRotateWithShape ? "1" : "0" + /*XML_dpi*/); + + writeBlip(rBlipFill); + + writeRelativeRectangle(mpFS, XML_srcRect, rBlipFill.maClipRectangle); + + if (rBlipFill.meMode == model::BitmapMode::Tile) + { + OString aFlipMode = convertFlipMode(rBlipFill.meTileFlipMode); + OString aAlignment = convertRectangleAlignment(rBlipFill.meTileAlignment); + + mpFS->startElementNS(XML_a, XML_tile, XML_tx, OString::number(rBlipFill.mnTileOffsetX), + XML_ty, OString::number(rBlipFill.mnTileOffsetY), XML_sx, + OString::number(rBlipFill.mnTileScaleX), XML_sy, + OString::number(rBlipFill.mnTileScaleY), XML_flip, aFlipMode, XML_algn, + aAlignment); + mpFS->endElementNS(XML_a, XML_tile); + } + else if (rBlipFill.meMode == model::BitmapMode::Stretch) + { + mpFS->startElementNS(XML_a, XML_stretch); + writeRelativeRectangle(mpFS, XML_fillRect, rBlipFill.maFillRectangle); + mpFS->endElementNS(XML_a, XML_stretch); + } + + mpFS->endElementNS(XML_a, XML_blipFill); +} + +void ThemeExport::writeFillStyle(model::FillStyle const& rFillStyle) +{ + switch (rFillStyle.mpFill->meType) + { + case model::FillType::None: + case model::FillType::Solid: + { + auto* pSolidFill = static_cast<model::SolidFill*>(rFillStyle.mpFill.get()); + writeSolidFill(*pSolidFill); + } + break; + case model::FillType::Gradient: + { + auto* pGradientFill = static_cast<model::GradientFill*>(rFillStyle.mpFill.get()); + writeGradientFill(*pGradientFill); + } + break; + case model::FillType::Pattern: + { + auto* pPatternFill = static_cast<model::PatternFill*>(rFillStyle.mpFill.get()); + writePatternFill(*pPatternFill); + } + break; + case model::FillType::Blip: + { + auto* pBlipFill = static_cast<model::BlipFill*>(rFillStyle.mpFill.get()); + writeBlipFill(*pBlipFill); + } + break; + } +} + +void ThemeExport::writeBackgroundFillStyle(model::FillStyle const& rFillStyle) +{ + writeFillStyle(rFillStyle); +} + +void ThemeExport::writeLineStyle(model::LineStyle const& rLineStyle) +{ + OString sCap; + switch (rLineStyle.meCapType) + { + case model::CapType::Flat: + sCap = "flat"_ostr; + break; + case model::CapType::Round: + sCap = "rnd"_ostr; + break; + case model::CapType::Square: + sCap = "sq"_ostr; + break; + case model::CapType::Unset: + break; + } + + OString sPenAlign; + switch (rLineStyle.mePenAlignment) + { + case model::PenAlignmentType::Center: + sPenAlign = "ctr"_ostr; + break; + case model::PenAlignmentType::Inset: + sPenAlign = "in"_ostr; + break; + case model::PenAlignmentType::Unset: + break; + } + + OString sCompoundLine; + switch (rLineStyle.meCompoundLineType) + { + case model::CompoundLineType::Single: + sCompoundLine = "sng"_ostr; + break; + case model::CompoundLineType::Double: + sCompoundLine = "dbl"_ostr; + break; + case model::CompoundLineType::ThickThin_Double: + sCompoundLine = "thickThin"_ostr; + break; + case model::CompoundLineType::ThinThick_Double: + sCompoundLine = "thinThick"_ostr; + break; + case model::CompoundLineType::Triple: + sCompoundLine = "tri"_ostr; + break; + case model::CompoundLineType::Unset: + break; + } + + mpFS->startElementNS(XML_a, XML_ln, XML_w, OString::number(rLineStyle.mnWidth), XML_cap, + sax_fastparser::UseIf(sCap, !sCap.isEmpty()), XML_cmpd, + sax_fastparser::UseIf(sCompoundLine, !sCompoundLine.isEmpty()), XML_algn, + sax_fastparser::UseIf(sPenAlign, !sPenAlign.isEmpty())); + + if (rLineStyle.maLineDash.mePresetType != model::PresetDashType::Unset) + { + OString sPresetType; + switch (rLineStyle.maLineDash.mePresetType) + { + case model::PresetDashType::Dot: + sPresetType = "dot"_ostr; + break; + case model::PresetDashType::Dash: + sPresetType = "dash"_ostr; + break; + case model::PresetDashType::LargeDash: + sPresetType = "lgDash"_ostr; + break; + case model::PresetDashType::DashDot: + sPresetType = "dashDot"_ostr; + break; + case model::PresetDashType::LargeDashDot: + sPresetType = "lgDashDot"_ostr; + break; + case model::PresetDashType::LargeDashDotDot: + sPresetType = "lgDashDotDot"_ostr; + break; + case model::PresetDashType::Solid: + sPresetType = "solid"_ostr; + break; + case model::PresetDashType::SystemDash: + sPresetType = "sysDash"_ostr; + break; + case model::PresetDashType::SystemDot: + sPresetType = "sysDot"_ostr; + break; + case model::PresetDashType::SystemDashDot: + sPresetType = "sysDashDot"_ostr; + break; + case model::PresetDashType::SystemDashDotDot: + sPresetType = "sysDashDotDot"_ostr; + break; + case model::PresetDashType::Unset: + break; + } + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, sPresetType); + } + + if (rLineStyle.maLineJoin.meType != model::LineJoinType::Unset) + { + switch (rLineStyle.maLineJoin.meType) + { + case model::LineJoinType::Round: + mpFS->singleElementNS(XML_a, XML_round); + break; + case model::LineJoinType::Bevel: + mpFS->singleElementNS(XML_a, XML_bevel); + break; + case model::LineJoinType::Miter: + { + sal_Int32 nMiterLimit = rLineStyle.maLineJoin.mnMiterLimit; + mpFS->singleElementNS( + XML_a, XML_miter, XML_lim, + sax_fastparser::UseIf(OString::number(nMiterLimit), nMiterLimit > 0)); + } + break; + case model::LineJoinType::Unset: + break; + } + } + + mpFS->endElementNS(XML_a, XML_ln); +} + +void ThemeExport::writeEffectStyle(model::EffectStyle const& /*rEffectStyle*/) +{ + mpFS->startElementNS(XML_a, XML_effectStyle); + mpFS->singleElementNS(XML_a, XML_effectLst); + mpFS->endElementNS(XML_a, XML_effectStyle); +} + +bool ThemeExport::writeFormatScheme(model::FormatScheme const& rFormatScheme) +{ + // Format Scheme: 3 or more per list but only 3 will be used currently + + // Fill Style List + rFormatScheme.ensureFillStyleList(); + mpFS->startElementNS(XML_a, XML_fillStyleLst); + for (auto const& rFillStyle : rFormatScheme.getFillStyleList()) + { + writeFillStyle(rFillStyle); + } + mpFS->endElementNS(XML_a, XML_fillStyleLst); + + // Line Style List + rFormatScheme.ensureLineStyleList(); + mpFS->startElementNS(XML_a, XML_lnStyleLst); + for (auto const& rLineStyle : rFormatScheme.getLineStyleList()) + { + writeLineStyle(rLineStyle); + } + mpFS->endElementNS(XML_a, XML_lnStyleLst); + + // Effect Style List + rFormatScheme.ensureEffectStyleList(); + mpFS->startElementNS(XML_a, XML_effectStyleLst); + { + for (auto const& rEffectStyle : rFormatScheme.getEffectStyleList()) + { + writeEffectStyle(rEffectStyle); + } + } + mpFS->endElementNS(XML_a, XML_effectStyleLst); + + // Background Fill Style List + rFormatScheme.ensureBackgroundFillStyleList(); + mpFS->startElementNS(XML_a, XML_bgFillStyleLst); + for (auto const& rFillStyle : rFormatScheme.getBackgroundFillStyleList()) + { + writeBackgroundFillStyle(rFillStyle); + } + mpFS->endElementNS(XML_a, XML_bgFillStyleLst); + + return true; +} + +bool ThemeExport::writeColorSet(model::Theme const& rTheme) +{ + static const constexpr std::array<sal_Int32, 12> constTokenArray + = { XML_dk1, XML_lt1, XML_dk2, XML_lt2, XML_accent1, XML_accent2, + XML_accent3, XML_accent4, XML_accent5, XML_accent6, XML_hlink, XML_folHlink }; + + const auto pColorSet = rTheme.getColorSet(); + if (!pColorSet) + return false; + + for (auto nToken : constTokenArray) + { + auto iterator = constTokenMap.find(nToken); + if (iterator != constTokenMap.end()) + { + model::ThemeColorType eColorType = iterator->second; + Color aColor = pColorSet->getColor(eColorType); + mpFS->startElementNS(XML_a, nToken); + mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, I32SHEX(sal_Int32(aColor))); + mpFS->endElementNS(XML_a, nToken); + } + } + + return true; +} + +} // end namespace oox + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/export/chartexport.cxx b/oox/source/export/chartexport.cxx new file mode 100644 index 0000000000..c80e8c1ba6 --- /dev/null +++ b/oox/source/export/chartexport.cxx @@ -0,0 +1,4712 @@ +/* -*- 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 <basegfx/utils/gradienttools.hxx> +#include <docmodel/uno/UnoGradientTools.hxx> + +#include <cstdio> +#include <limits> + +#include <com/sun/star/awt/Gradient2.hpp> +#include <com/sun/star/chart/XChartDocument.hpp> +#include <com/sun/star/chart/ChartLegendPosition.hpp> +#include <com/sun/star/chart/XTwoAxisXSupplier.hpp> +#include <com/sun/star/chart/XTwoAxisYSupplier.hpp> +#include <com/sun/star/chart/XAxisZSupplier.hpp> +#include <com/sun/star/chart/ChartDataRowSource.hpp> +#include <com/sun/star/chart/X3DDisplay.hpp> +#include <com/sun/star/chart/XStatisticDisplay.hpp> +#include <com/sun/star/chart/XSecondAxisTitleSupplier.hpp> +#include <com/sun/star/chart/ChartSymbolType.hpp> +#include <com/sun/star/chart/ChartAxisMarks.hpp> +#include <com/sun/star/chart/ChartAxisLabelPosition.hpp> +#include <com/sun/star/chart/ChartAxisPosition.hpp> +#include <com/sun/star/chart/ChartSolidType.hpp> +#include <com/sun/star/chart/DataLabelPlacement.hpp> +#include <com/sun/star/chart/ErrorBarStyle.hpp> +#include <com/sun/star/chart/MissingValueTreatment.hpp> +#include <com/sun/star/chart/XDiagramPositioning.hpp> +#include <com/sun/star/chart/TimeIncrement.hpp> +#include <com/sun/star/chart/TimeInterval.hpp> +#include <com/sun/star/chart/TimeUnit.hpp> + +#include <com/sun/star/chart2/RelativePosition.hpp> +#include <com/sun/star/chart2/RelativeSize.hpp> +#include <com/sun/star/chart2/XChartDocument.hpp> +#include <com/sun/star/chart2/XDiagram.hpp> +#include <com/sun/star/chart2/XCoordinateSystemContainer.hpp> +#include <com/sun/star/chart2/XRegressionCurveContainer.hpp> +#include <com/sun/star/chart2/XChartTypeContainer.hpp> +#include <com/sun/star/chart2/XDataSeriesContainer.hpp> +#include <com/sun/star/chart2/DataPointLabel.hpp> +#include <com/sun/star/chart2/XDataPointCustomLabelField.hpp> +#include <com/sun/star/chart2/DataPointCustomLabelFieldType.hpp> +#include <com/sun/star/chart2/Symbol.hpp> +#include <com/sun/star/chart2/data/XDataSource.hpp> +#include <com/sun/star/chart2/data/XDataProvider.hpp> +#include <com/sun/star/chart2/data/XTextualDataSequence.hpp> +#include <com/sun/star/chart2/data/XNumericalDataSequence.hpp> +#include <com/sun/star/chart2/data/XLabeledDataSequence.hpp> +#include <com/sun/star/chart2/XAnyDescriptionAccess.hpp> +#include <com/sun/star/chart2/AxisType.hpp> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XServiceName.hpp> + +#include <com/sun/star/table/CellAddress.hpp> +#include <com/sun/star/sheet/XFormulaParser.hpp> +#include <com/sun/star/sheet/FormulaToken.hpp> +#include <com/sun/star/sheet/AddressConvention.hpp> + +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/embed/XVisualObject.hpp> +#include <com/sun/star/embed/Aspects.hpp> + +#include <comphelper/processfactory.hxx> +#include <comphelper/random.hxx> +#include <utility> +#include <xmloff/SchXMLSeriesHelper.hxx> +#include "ColorPropertySet.hxx" + +#include <svl/numformat.hxx> +#include <svl/numuno.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <sal/log.hxx> + +#include <set> +#include <unordered_set> + +#include <frozen/bits/defines.h> +#include <frozen/bits/elsa_std.h> +#include <frozen/unordered_map.h> + +#include <o3tl/temporary.hxx> +#include <o3tl/sorted_vector.hxx> + +using namespace css; +using namespace css::uno; +using namespace css::drawing; +using namespace ::oox::core; +using css::beans::PropertyValue; +using css::beans::XPropertySet; +using css::container::XNamed; +using css::table::CellAddress; +using css::sheet::XFormulaParser; +using ::oox::core::XmlFilterBase; +using ::sax_fastparser::FSHelperPtr; + +namespace cssc = css::chart; + +namespace oox::drawingml { + +namespace { + +bool isPrimaryAxes(sal_Int32 nIndex) +{ + assert(nIndex == 0 || nIndex == 1); + return nIndex != 1; +} + +class lcl_MatchesRole +{ +public: + explicit lcl_MatchesRole( OUString aRole ) : + m_aRole(std::move( aRole )) + {} + + bool operator () ( const Reference< chart2::data::XLabeledDataSequence > & xSeq ) const + { + if( !xSeq.is() ) + return false; + Reference< beans::XPropertySet > xProp( xSeq->getValues(), uno::UNO_QUERY ); + OUString aRole; + + return ( xProp.is() && + (xProp->getPropertyValue( "Role" ) >>= aRole ) && + m_aRole == aRole ); + } + +private: + OUString m_aRole; +}; + +} + +static Reference< chart2::data::XLabeledDataSequence > lcl_getCategories( const Reference< chart2::XDiagram > & xDiagram, bool& bHasDateCategories ) +{ + bHasDateCategories = false; + Reference< chart2::data::XLabeledDataSequence > xResult; + try + { + Reference< chart2::XCoordinateSystemContainer > xCooSysCnt( + xDiagram, uno::UNO_QUERY_THROW ); + const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq( + xCooSysCnt->getCoordinateSystems()); + for( const auto& xCooSys : aCooSysSeq ) + { + OSL_ASSERT( xCooSys.is()); + for( sal_Int32 nN = xCooSys->getDimension(); nN--; ) + { + const sal_Int32 nMaxAxisIndex = xCooSys->getMaximumAxisIndexByDimension(nN); + for(sal_Int32 nI=0; nI<=nMaxAxisIndex; ++nI) + { + Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension( nN, nI ); + OSL_ASSERT( xAxis.is()); + if( xAxis.is()) + { + chart2::ScaleData aScaleData = xAxis->getScaleData(); + if( aScaleData.Categories.is()) + { + bHasDateCategories = aScaleData.AxisType == chart2::AxisType::DATE; + xResult.set( aScaleData.Categories ); + break; + } + } + } + } + } + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("oox"); + } + + return xResult; +} + +static Reference< chart2::data::XLabeledDataSequence > + lcl_getDataSequenceByRole( + const Sequence< Reference< chart2::data::XLabeledDataSequence > > & aLabeledSeq, + const OUString & rRole ) +{ + Reference< chart2::data::XLabeledDataSequence > aNoResult; + + const Reference< chart2::data::XLabeledDataSequence > * pBegin = aLabeledSeq.getConstArray(); + const Reference< chart2::data::XLabeledDataSequence > * pEnd = pBegin + aLabeledSeq.getLength(); + const Reference< chart2::data::XLabeledDataSequence > * pMatch = + ::std::find_if( pBegin, pEnd, lcl_MatchesRole( rRole )); + + if( pMatch != pEnd ) + return *pMatch; + + return aNoResult; +} + +static bool lcl_hasCategoryLabels( const Reference< chart2::XChartDocument >& xChartDoc ) +{ + //categories are always the first sequence + Reference< chart2::XDiagram > xDiagram( xChartDoc->getFirstDiagram()); + bool bDateCategories; + Reference< chart2::data::XLabeledDataSequence > xCategories( lcl_getCategories( xDiagram, bDateCategories ) ); + return xCategories.is(); +} + +static bool lcl_isCategoryAxisShifted( const Reference< chart2::XDiagram >& xDiagram ) +{ + bool bCategoryPositionShifted = false; + try + { + Reference< chart2::XCoordinateSystemContainer > xCooSysCnt( + xDiagram, uno::UNO_QUERY_THROW); + const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq( + xCooSysCnt->getCoordinateSystems()); + for (const auto& xCooSys : aCooSysSeq) + { + OSL_ASSERT(xCooSys.is()); + if( 0 < xCooSys->getDimension() && 0 <= xCooSys->getMaximumAxisIndexByDimension(0) ) + { + Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(0, 0); + OSL_ASSERT(xAxis.is()); + if (xAxis.is()) + { + chart2::ScaleData aScaleData = xAxis->getScaleData(); + bCategoryPositionShifted = aScaleData.ShiftedCategoryPosition; + break; + } + } + } + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("oox"); + } + + return bCategoryPositionShifted; +} + +static sal_Int32 lcl_getCategoryAxisType( const Reference< chart2::XDiagram >& xDiagram, sal_Int32 nDimensionIndex, sal_Int32 nAxisIndex ) +{ + sal_Int32 nAxisType = -1; + try + { + Reference< chart2::XCoordinateSystemContainer > xCooSysCnt( + xDiagram, uno::UNO_QUERY_THROW); + const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq( + xCooSysCnt->getCoordinateSystems()); + for( const auto& xCooSys : aCooSysSeq ) + { + OSL_ASSERT(xCooSys.is()); + if( nDimensionIndex < xCooSys->getDimension() && nAxisIndex <= xCooSys->getMaximumAxisIndexByDimension(nDimensionIndex) ) + { + Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(nDimensionIndex, nAxisIndex); + OSL_ASSERT(xAxis.is()); + if( xAxis.is() ) + { + chart2::ScaleData aScaleData = xAxis->getScaleData(); + nAxisType = aScaleData.AxisType; + break; + } + } + } + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("oox"); + } + + return nAxisType; +} + +static OUString lclGetTimeUnitToken( sal_Int32 nTimeUnit ) +{ + switch( nTimeUnit ) + { + case cssc::TimeUnit::DAY: return "days"; + case cssc::TimeUnit::MONTH: return "months"; + case cssc::TimeUnit::YEAR: return "years"; + default: OSL_ENSURE(false, "lclGetTimeUnitToken - unexpected time unit"); + } + return "days"; +} + +static cssc::TimeIncrement lcl_getDateTimeIncrement( const Reference< chart2::XDiagram >& xDiagram, sal_Int32 nAxisIndex ) +{ + cssc::TimeIncrement aTimeIncrement; + try + { + Reference< chart2::XCoordinateSystemContainer > xCooSysCnt( + xDiagram, uno::UNO_QUERY_THROW); + const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq( + xCooSysCnt->getCoordinateSystems()); + for( const auto& xCooSys : aCooSysSeq ) + { + OSL_ASSERT(xCooSys.is()); + if( 0 < xCooSys->getDimension() && nAxisIndex <= xCooSys->getMaximumAxisIndexByDimension(0) ) + { + Reference< chart2::XAxis > xAxis = xCooSys->getAxisByDimension(0, nAxisIndex); + OSL_ASSERT(xAxis.is()); + if( xAxis.is() ) + { + chart2::ScaleData aScaleData = xAxis->getScaleData(); + aTimeIncrement = aScaleData.TimeIncrement; + break; + } + } + } + } + catch (const uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("oox"); + } + + return aTimeIncrement; +} + +static bool lcl_isSeriesAttachedToFirstAxis( + const Reference< chart2::XDataSeries > & xDataSeries ) +{ + bool bResult=true; + + try + { + sal_Int32 nAxisIndex = 0; + Reference< beans::XPropertySet > xProp( xDataSeries, uno::UNO_QUERY_THROW ); + xProp->getPropertyValue("AttachedAxisIndex") >>= nAxisIndex; + bResult = (0==nAxisIndex); + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("oox"); + } + + return bResult; +} + +static OUString lcl_flattenStringSequence( const Sequence< OUString > & rSequence ) +{ + OUStringBuffer aResult; + bool bPrecedeWithSpace = false; + for( const auto& rString : rSequence ) + { + if( !rString.isEmpty()) + { + if( bPrecedeWithSpace ) + aResult.append( ' ' ); + aResult.append( rString ); + bPrecedeWithSpace = true; + } + } + return aResult.makeStringAndClear(); +} + +static Sequence< OUString > lcl_getLabelSequence( const Reference< chart2::data::XDataSequence > & xLabelSeq ) +{ + Sequence< OUString > aLabels; + + uno::Reference< chart2::data::XTextualDataSequence > xTextualDataSequence( xLabelSeq, uno::UNO_QUERY ); + if( xTextualDataSequence.is()) + { + aLabels = xTextualDataSequence->getTextualData(); + } + else if( xLabelSeq.is()) + { + const Sequence< uno::Any > aAnies( xLabelSeq->getData()); + aLabels.realloc( aAnies.getLength()); + auto pLabels = aLabels.getArray(); + for( sal_Int32 i=0; i<aAnies.getLength(); ++i ) + aAnies[i] >>= pLabels[i]; + } + + return aLabels; +} + +static void lcl_fillCategoriesIntoStringVector( + const Reference< chart2::data::XDataSequence > & xCategories, + ::std::vector< OUString > & rOutCategories ) +{ + OSL_ASSERT( xCategories.is()); + if( !xCategories.is()) + return; + Reference< chart2::data::XTextualDataSequence > xTextualDataSequence( xCategories, uno::UNO_QUERY ); + if( xTextualDataSequence.is()) + { + rOutCategories.clear(); + const Sequence< OUString > aTextData( xTextualDataSequence->getTextualData()); + rOutCategories.insert( rOutCategories.end(), aTextData.begin(), aTextData.end() ); + } + else + { + Sequence< uno::Any > aAnies( xCategories->getData()); + rOutCategories.resize( aAnies.getLength()); + for( sal_Int32 i=0; i<aAnies.getLength(); ++i ) + aAnies[i] >>= rOutCategories[i]; + } +} + +static ::std::vector< double > lcl_getAllValuesFromSequence( const Reference< chart2::data::XDataSequence > & xSeq ) +{ + ::std::vector< double > aResult; + + Reference< chart2::data::XNumericalDataSequence > xNumSeq( xSeq, uno::UNO_QUERY ); + if( xNumSeq.is()) + { + const Sequence< double > aValues( xNumSeq->getNumericalData()); + aResult.insert( aResult.end(), aValues.begin(), aValues.end() ); + } + else if( xSeq.is()) + { + Sequence< uno::Any > aAnies( xSeq->getData()); + aResult.resize( aAnies.getLength(), std::numeric_limits<double>::quiet_NaN() ); + for( sal_Int32 i=0; i<aAnies.getLength(); ++i ) + aAnies[i] >>= aResult[i]; + } + return aResult; +} + +namespace +{ + +constexpr auto constChartTypeMap = frozen::make_unordered_map<std::u16string_view, chart::TypeId>( +{ + { u"com.sun.star.chart.BarDiagram", chart::TYPEID_BAR }, + { u"com.sun.star.chart2.ColumnChartType", chart::TYPEID_BAR }, + { u"com.sun.star.chart.AreaDiagram", chart::TYPEID_AREA }, + { u"com.sun.star.chart2.AreaChartType", chart::TYPEID_AREA }, + { u"com.sun.star.chart.LineDiagram", chart::TYPEID_LINE }, + { u"com.sun.star.chart2.LineChartType", chart::TYPEID_LINE }, + { u"com.sun.star.chart.PieDiagram", chart::TYPEID_PIE }, + { u"com.sun.star.chart2.PieChartType", chart::TYPEID_PIE }, + { u"com.sun.star.chart.DonutDiagram", chart::TYPEID_DOUGHNUT }, + { u"com.sun.star.chart2.DonutChartType", chart::TYPEID_DOUGHNUT }, + { u"com.sun.star.chart.XYDiagram", chart::TYPEID_SCATTER }, + { u"com.sun.star.chart2.ScatterChartType", chart::TYPEID_SCATTER }, + { u"com.sun.star.chart.NetDiagram", chart::TYPEID_RADARLINE }, + { u"com.sun.star.chart2.NetChartType", chart::TYPEID_RADARLINE }, + { u"com.sun.star.chart.FilledNetDiagram", chart::TYPEID_RADARAREA }, + { u"com.sun.star.chart2.FilledNetChartType", chart::TYPEID_RADARAREA }, + { u"com.sun.star.chart.StockDiagram", chart::TYPEID_STOCK }, + { u"com.sun.star.chart2.CandleStickChartType", chart::TYPEID_STOCK }, + { u"com.sun.star.chart.BubbleDiagram", chart::TYPEID_BUBBLE }, + { u"com.sun.star.chart2.BubbleChartType", chart::TYPEID_BUBBLE }, +}); + +} // end anonymous namespace + +static sal_Int32 lcl_getChartType(std::u16string_view sChartType) +{ + auto aIterator = constChartTypeMap.find(sChartType); + if (aIterator == constChartTypeMap.end()) + return chart::TYPEID_UNKNOWN; + return aIterator->second; +} + +static sal_Int32 lcl_generateRandomValue() +{ + return comphelper::rng::uniform_int_distribution(0, 100000000-1); +} + +bool DataLabelsRange::empty() const +{ + return maLabels.empty(); +} + +size_t DataLabelsRange::count() const +{ + return maLabels.size(); +} + +bool DataLabelsRange::hasLabel(sal_Int32 nIndex) const +{ + return maLabels.find(nIndex) != maLabels.end(); +} + +const OUString & DataLabelsRange::getRange() const +{ + return maRange; +} + +void DataLabelsRange::setRange(const OUString& rRange) +{ + maRange = rRange; +} + +void DataLabelsRange::setLabel(sal_Int32 nIndex, const OUString& rText) +{ + maLabels.emplace(nIndex, rText); +} + +DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::begin() const +{ + return maLabels.begin(); +} + +DataLabelsRange::LabelsRangeMap::const_iterator DataLabelsRange::end() const +{ + return maLabels.end(); +} + +ChartExport::ChartExport( sal_Int32 nXmlNamespace, FSHelperPtr pFS, Reference< frame::XModel > const & xModel, XmlFilterBase* pFB, DocumentType eDocumentType ) + : DrawingML( std::move(pFS), pFB, eDocumentType ) + , mnXmlNamespace( nXmlNamespace ) + , mnSeriesCount(0) + , mxChartModel( xModel ) + , mpURLTransformer(std::make_shared<URLTransformer>()) + , mbHasCategoryLabels( false ) + , mbHasZAxis( false ) + , mbIs3DChart( false ) + , mbStacked(false) + , mbPercent(false) + , mbHasDateCategories(false) +{ +} + +void ChartExport::SetURLTranslator(const std::shared_ptr<URLTransformer>& pTransformer) +{ + mpURLTransformer = pTransformer; +} + +sal_Int32 ChartExport::getChartType( ) +{ + OUString sChartType = mxDiagram->getDiagramType(); + return lcl_getChartType( sChartType ); +} + +namespace { + +uno::Sequence< beans::PropertyValue > createArguments( + const OUString & rRangeRepresentation, bool bUseColumns) +{ + css::chart::ChartDataRowSource eRowSource = css::chart::ChartDataRowSource_ROWS; + if (bUseColumns) + eRowSource = css::chart::ChartDataRowSource_COLUMNS; + + uno::Sequence<beans::PropertyValue> aArguments{ + { "DataRowSource", -1, uno::Any(eRowSource), beans::PropertyState_DIRECT_VALUE }, + { "FirstCellAsLabel", -1, uno::Any(false), beans::PropertyState_DIRECT_VALUE }, + { "HasCategories", -1, uno::Any(false), beans::PropertyState_DIRECT_VALUE }, + { "CellRangeRepresentation", -1, uno::Any(rRangeRepresentation), + beans::PropertyState_DIRECT_VALUE } + }; + + return aArguments; +} + +Reference<chart2::XDataSeries> getPrimaryDataSeries(const Reference<chart2::XChartType>& xChartType) +{ + Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY_THROW); + + // export dataseries for current chart-type + const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries()); + for (const auto& rSeries : aSeriesSeq) + { + Reference<chart2::XDataSeries> xSource(rSeries, uno::UNO_QUERY); + if (xSource.is()) + return xSource; + } + + return Reference<chart2::XDataSeries>(); +} + +} + +Sequence< Sequence< OUString > > ChartExport::getSplitCategoriesList( const OUString& rRange ) +{ + Reference< chart2::XChartDocument > xChartDoc(getModel(), uno::UNO_QUERY); + OSL_ASSERT(xChartDoc.is()); + if (xChartDoc.is()) + { + Reference< chart2::data::XDataProvider > xDataProvider(xChartDoc->getDataProvider()); + OSL_ENSURE(xDataProvider.is(), "No DataProvider"); + if (xDataProvider.is()) + { + //detect whether the first series is a row or a column + bool bSeriesUsesColumns = true; + Reference< chart2::XDiagram > xDiagram(xChartDoc->getFirstDiagram()); + try + { + Reference< chart2::XCoordinateSystemContainer > xCooSysCnt(xDiagram, uno::UNO_QUERY_THROW); + const Sequence< Reference< chart2::XCoordinateSystem > > aCooSysSeq(xCooSysCnt->getCoordinateSystems()); + for (const auto& rCooSys : aCooSysSeq) + { + const Reference< chart2::XChartTypeContainer > xCTCnt(rCooSys, uno::UNO_QUERY_THROW); + const Sequence< Reference< chart2::XChartType > > aChartTypeSeq(xCTCnt->getChartTypes()); + for (const auto& rChartType : aChartTypeSeq) + { + Reference< chart2::XDataSeries > xDataSeries = getPrimaryDataSeries(rChartType); + if (xDataSeries.is()) + { + uno::Reference< chart2::data::XDataSource > xSeriesSource(xDataSeries, uno::UNO_QUERY); + const uno::Sequence< beans::PropertyValue > rArguments = xDataProvider->detectArguments(xSeriesSource); + for (const beans::PropertyValue& rProperty : rArguments) + { + if (rProperty.Name == "DataRowSource") + { + css::chart::ChartDataRowSource eRowSource; + if (rProperty.Value >>= eRowSource) + { + bSeriesUsesColumns = (eRowSource == css::chart::ChartDataRowSource_COLUMNS); + break; + } + } + } + } + } + } + } + catch (const uno::Exception &) + { + DBG_UNHANDLED_EXCEPTION("chart2"); + } + // detect we have an inner data table or not + if (xChartDoc->hasInternalDataProvider() && rRange == "categories") + { + try + { + css::uno::Reference< css::chart2::XAnyDescriptionAccess > xDataAccess(xChartDoc->getDataProvider(), uno::UNO_QUERY); + const Sequence< Sequence< uno::Any > >aAnyCategories(bSeriesUsesColumns ? xDataAccess->getAnyRowDescriptions() : xDataAccess->getAnyColumnDescriptions()); + auto pMax = std::max_element(aAnyCategories.begin(), aAnyCategories.end(), + [](const Sequence<uno::Any>& a, const Sequence<uno::Any>& b) { + return a.getLength() < b.getLength(); }); + + //minimum is 1! + if (pMax != aAnyCategories.end() && pMax->getLength() > 1) + { + sal_Int32 nLevelCount = pMax->getLength(); + //we have complex categories + //sort the categories name + Sequence<Sequence<OUString>>aFinalSplitSource(nLevelCount); + auto pFinalSplitSource = aFinalSplitSource.getArray(); + for (sal_Int32 i = 0; i < nLevelCount; i++) + { + sal_Int32 nElemLabel = 0; + pFinalSplitSource[nLevelCount - i - 1].realloc(aAnyCategories.getLength()); + auto pSeq = pFinalSplitSource[nLevelCount - i - 1].getArray(); + for (auto const& elemLabel : aAnyCategories) + { + // make sure elemLabel[i] exists! + if (elemLabel.getLength() > i) + { + pSeq[nElemLabel] = elemLabel[i].get<OUString>(); + nElemLabel++; + } + } + } + return aFinalSplitSource; + } + } + catch (const uno::Exception &) + { + DBG_UNHANDLED_EXCEPTION("oox"); + } + } + else + { + try + { + uno::Reference< chart2::data::XDataSource > xCategoriesSource(xDataProvider->createDataSource( + createArguments(rRange, bSeriesUsesColumns))); + + if (xCategoriesSource.is()) + { + const Sequence< Reference< chart2::data::XLabeledDataSequence >> aCategories = xCategoriesSource->getDataSequences(); + if (aCategories.getLength() > 1) + { + //we have complex categories + //sort the categories name + Sequence<Sequence<OUString>> aFinalSplitSource(aCategories.getLength()); + std::transform(aCategories.begin(), aCategories.end(), + std::reverse_iterator(asNonConstRange(aFinalSplitSource).end()), + [](const Reference<chart2::data::XLabeledDataSequence>& xCat) { + return lcl_getLabelSequence(xCat->getValues()); }); + return aFinalSplitSource; + } + } + } + catch (const uno::Exception &) + { + DBG_UNHANDLED_EXCEPTION("oox"); + } + } + } + } + + return Sequence< Sequence< OUString>>(0); +} + +OUString ChartExport::parseFormula( const OUString& rRange ) +{ + OUString aResult; + Reference< XFormulaParser > xParser; + uno::Reference< lang::XMultiServiceFactory > xSF = GetFB()->getModelFactory(); + if( xSF.is() ) + { + try + { + xParser.set( xSF->createInstance("com.sun.star.sheet.FormulaParser"), UNO_QUERY ); + } + catch( Exception& ) + { + } + } + + SAL_WARN_IF(!xParser.is(), "oox", "creating formula parser failed"); + + if( xParser.is() ) + { + Reference< XPropertySet > xParserProps( xParser, uno::UNO_QUERY ); + // rRange is the result of a + // css::chart2::data::XDataSequence::getSourceRangeRepresentation() + // call that returns the range in the document's current UI notation. + // Creating a FormulaParser defaults to the same notation, for + // parseFormula() do not attempt to override the FormulaConvention + // property with css::sheet::AddressConvention::OOO or some such. + /* TODO: it would be much better to introduce a + * getSourceRangeRepresentation(css::sheet::AddressConvention) to + * return the ranges in a specific convention than converting them with + * the overhead of creating an XFormulaParser for each... */ + uno::Sequence<sheet::FormulaToken> aTokens = xParser->parseFormula( rRange, CellAddress( 0, 0, 0 ) ); + if( xParserProps.is() ) + { + xParserProps->setPropertyValue("FormulaConvention", uno::Any(css::sheet::AddressConvention::XL_OOX) ); + // For referencing named ranges correctly with special excel chart syntax. + xParserProps->setPropertyValue("RefConventionChartOOXML", uno::Any(true) ); + } + aResult = xParser->printFormula( aTokens, CellAddress( 0, 0, 0 ) ); + } + else + { + //FIXME: currently just using simple converter, e.g $Sheet1.$A$1:$C$1 -> Sheet1!$A$1:$C$1 + OUString aRange( rRange ); + if( aRange.startsWith("$") ) + aRange = aRange.copy(1); + aRange = aRange.replaceAll(".$", "!$" ); + aResult = aRange; + } + + return aResult; +} + +void ChartExport::WriteChartObj( const Reference< XShape >& xShape, sal_Int32 nID, sal_Int32 nChartCount ) +{ + FSHelperPtr pFS = GetFS(); + + Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY ); + + pFS->startElementNS(mnXmlNamespace, XML_graphicFrame); + + pFS->startElementNS(mnXmlNamespace, XML_nvGraphicFramePr); + + // TODO: get the correct chart name chart id + OUString sName = "Object 1"; + Reference< XNamed > xNamed( xShape, UNO_QUERY ); + if (xNamed.is()) + sName = xNamed->getName(); + + pFS->startElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(nID), + XML_name, sName); + + OUString sURL; + if ( GetProperty( xShapeProps, "URL" ) ) + mAny >>= sURL; + if( !sURL.isEmpty() ) + { + OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(), + oox::getRelationship(Relationship::HYPERLINK), + mpURLTransformer->getTransformedString(sURL), + mpURLTransformer->isExternalURL(sURL)); + + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); + } + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + + pFS->singleElementNS(mnXmlNamespace, XML_cNvGraphicFramePr); + + if( GetDocumentType() == DOCUMENT_PPTX ) + pFS->singleElementNS(mnXmlNamespace, XML_nvPr); + pFS->endElementNS( mnXmlNamespace, XML_nvGraphicFramePr ); + + // visual chart properties + WriteShapeTransformation( xShape, mnXmlNamespace ); + + // writer chart object + pFS->startElement(FSNS(XML_a, XML_graphic)); + pFS->startElement( FSNS( XML_a, XML_graphicData ), + XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/chart" ); + OUString sId; + const char* sFullPath = nullptr; + const char* sRelativePath = nullptr; + switch( GetDocumentType() ) + { + case DOCUMENT_DOCX: + { + sFullPath = "word/charts/chart"; + sRelativePath = "charts/chart"; + break; + } + case DOCUMENT_PPTX: + { + sFullPath = "ppt/charts/chart"; + sRelativePath = "../charts/chart"; + break; + } + case DOCUMENT_XLSX: + { + sFullPath = "xl/charts/chart"; + sRelativePath = "../charts/chart"; + break; + } + default: + { + sFullPath = "charts/chart"; + sRelativePath = "charts/chart"; + break; + } + } + OUString sFullStream = OUStringBuffer() + .appendAscii(sFullPath) + .append(OUString::number(nChartCount) + ".xml") + .makeStringAndClear(); + OUString sRelativeStream = OUStringBuffer() + .appendAscii(sRelativePath) + .append(OUString::number(nChartCount) + ".xml" ) + .makeStringAndClear(); + FSHelperPtr pChart = CreateOutputStream( + sFullStream, + sRelativeStream, + pFS->getOutputStream(), + "application/vnd.openxmlformats-officedocument.drawingml.chart+xml", + oox::getRelationship(Relationship::CHART), + &sId ); + + XmlFilterBase* pFB = GetFB(); + pFS->singleElement( FSNS( XML_c, XML_chart ), + FSNS(XML_xmlns, XML_c), pFB->getNamespaceURL(OOX_NS(dmlChart)), + FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel)), + FSNS(XML_r, XML_id), sId ); + + pFS->endElement( FSNS( XML_a, XML_graphicData ) ); + pFS->endElement( FSNS( XML_a, XML_graphic ) ); + pFS->endElementNS( mnXmlNamespace, XML_graphicFrame ); + + SetFS( pChart ); + ExportContent(); + SetFS( pFS ); + pChart->endDocument(); +} + +void ChartExport::InitRangeSegmentationProperties( const Reference< chart2::XChartDocument > & xChartDoc ) +{ + if( !xChartDoc.is()) + return; + + try + { + Reference< chart2::data::XDataProvider > xDataProvider( xChartDoc->getDataProvider() ); + OSL_ENSURE( xDataProvider.is(), "No DataProvider" ); + if( xDataProvider.is()) + { + mbHasCategoryLabels = lcl_hasCategoryLabels( xChartDoc ); + } + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION("oox"); + } +} + +void ChartExport::ExportContent() +{ + Reference< chart2::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY ); + OSL_ASSERT( xChartDoc.is() ); + if( !xChartDoc.is() ) + return; + InitRangeSegmentationProperties( xChartDoc ); + // TODO: export chart + ExportContent_( ); +} + +void ChartExport::ExportContent_() +{ + Reference< css::chart::XChartDocument > xChartDoc( getModel(), uno::UNO_QUERY ); + if( xChartDoc.is()) + { + // determine if data comes from the outside + bool bIncludeTable = true; + + Reference< chart2::XChartDocument > xNewDoc( xChartDoc, uno::UNO_QUERY ); + if( xNewDoc.is()) + { + // check if we have own data. If so we must not export the complete + // range string, as this is our only indicator for having own or + // external data. @todo: fix this in the file format! + Reference< lang::XServiceInfo > xDPServiceInfo( xNewDoc->getDataProvider(), uno::UNO_QUERY ); + if( ! (xDPServiceInfo.is() && xDPServiceInfo->getImplementationName() == "com.sun.star.comp.chart.InternalDataProvider" )) + { + bIncludeTable = false; + } + } + exportChartSpace( xChartDoc, bIncludeTable ); + } + else + { + OSL_FAIL( "Couldn't export chart due to wrong XModel" ); + } +} + +void ChartExport::exportChartSpace( const Reference< css::chart::XChartDocument >& xChartDoc, + bool bIncludeTable ) +{ + FSHelperPtr pFS = GetFS(); + XmlFilterBase* pFB = GetFB(); + pFS->startElement( FSNS( XML_c, XML_chartSpace ), + FSNS( XML_xmlns, XML_c ), pFB->getNamespaceURL(OOX_NS(dmlChart)), + FSNS( XML_xmlns, XML_a ), pFB->getNamespaceURL(OOX_NS(dml)), + FSNS( XML_xmlns, XML_r ), pFB->getNamespaceURL(OOX_NS(officeRel))); + // TODO: get the correct editing language + pFS->singleElement(FSNS(XML_c, XML_lang), XML_val, "en-US"); + + pFS->singleElement(FSNS(XML_c, XML_roundedCorners), XML_val, "0"); + + if( !bIncludeTable ) + { + // TODO:external data + } + //XML_chart + exportChart(xChartDoc); + + // TODO: printSettings + // TODO: style + // TODO: text properties + // TODO: shape properties + Reference< XPropertySet > xPropSet = xChartDoc->getArea(); + if( xPropSet.is() ) + exportShapeProps( xPropSet ); + + //XML_externalData + exportExternalData(xChartDoc); + + // export additional shapes in chart + exportAdditionalShapes(xChartDoc); + + pFS->endElement( FSNS( XML_c, XML_chartSpace ) ); +} + +void ChartExport::exportExternalData( const Reference< css::chart::XChartDocument >& xChartDoc ) +{ + // Embedded external data is grab bagged for docx file hence adding export part of + // external data for docx files only. + if(GetDocumentType() != DOCUMENT_DOCX) + return; + + OUString externalDataPath; + Reference< beans::XPropertySet > xDocPropSet( xChartDoc->getDiagram(), uno::UNO_QUERY ); + if( xDocPropSet.is()) + { + try + { + Any aAny( xDocPropSet->getPropertyValue( "ExternalData" )); + aAny >>= externalDataPath; + } + catch( beans::UnknownPropertyException & ) + { + SAL_WARN("oox", "Required property not found in ChartDocument"); + } + } + if(externalDataPath.isEmpty()) + return; + + // Here adding external data entry to relationship. + OUString relationPath = externalDataPath; + // Converting absolute path to relative path. + if( externalDataPath[ 0 ] != '.' && externalDataPath[ 1 ] != '.') + { + sal_Int32 nSepPos = externalDataPath.indexOf( '/', 0 ); + if( nSepPos > 0) + { + relationPath = relationPath.copy( nSepPos, ::std::max< sal_Int32 >( externalDataPath.getLength(), 0 ) - nSepPos ); + relationPath = ".." + relationPath; + } + } + FSHelperPtr pFS = GetFS(); + OUString type = oox::getRelationship(Relationship::PACKAGE); + if (relationPath.endsWith(".bin")) + type = oox::getRelationship(Relationship::OLEOBJECT); + + OUString sRelId = GetFB()->addRelation(pFS->getOutputStream(), + type, + relationPath); + pFS->singleElementNS(XML_c, XML_externalData, FSNS(XML_r, XML_id), sRelId); +} + +void ChartExport::exportAdditionalShapes( const Reference< css::chart::XChartDocument >& xChartDoc ) +{ + Reference< beans::XPropertySet > xDocPropSet(xChartDoc, uno::UNO_QUERY); + if (!xDocPropSet.is()) + return; + + css::uno::Reference< css::drawing::XShapes > mxAdditionalShapes; + // get a sequence of non-chart shapes + try + { + Any aShapesAny = xDocPropSet->getPropertyValue("AdditionalShapes"); + if( (aShapesAny >>= mxAdditionalShapes) && mxAdditionalShapes.is() ) + { + OUString sId; + const char* sFullPath = nullptr; + const char* sRelativePath = nullptr; + sal_Int32 nDrawing = getNewDrawingUniqueId(); + + switch (GetDocumentType()) + { + case DOCUMENT_DOCX: + { + sFullPath = "word/drawings/drawing"; + sRelativePath = "../drawings/drawing"; + break; + } + case DOCUMENT_PPTX: + { + sFullPath = "ppt/drawings/drawing"; + sRelativePath = "../drawings/drawing"; + break; + } + case DOCUMENT_XLSX: + { + sFullPath = "xl/drawings/drawing"; + sRelativePath = "../drawings/drawing"; + break; + } + default: + { + sFullPath = "drawings/drawing"; + sRelativePath = "drawings/drawing"; + break; + } + } + OUString sFullStream = OUStringBuffer() + .appendAscii(sFullPath) + .append(OUString::number(nDrawing) + ".xml") + .makeStringAndClear(); + OUString sRelativeStream = OUStringBuffer() + .appendAscii(sRelativePath) + .append(OUString::number(nDrawing) + ".xml") + .makeStringAndClear(); + + sax_fastparser::FSHelperPtr pDrawing = CreateOutputStream( + sFullStream, + sRelativeStream, + GetFS()->getOutputStream(), + "application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml", + oox::getRelationship(Relationship::CHARTUSERSHAPES), + &sId); + + GetFS()->singleElementNS(XML_c, XML_userShapes, FSNS(XML_r, XML_id), sId); + + XmlFilterBase* pFB = GetFB(); + pDrawing->startElement(FSNS(XML_c, XML_userShapes), + FSNS(XML_xmlns, XML_cdr), pFB->getNamespaceURL(OOX_NS(dmlChartDr)), + FSNS(XML_xmlns, XML_a), pFB->getNamespaceURL(OOX_NS(dml)), + FSNS(XML_xmlns, XML_c), pFB->getNamespaceURL(OOX_NS(dmlChart)), + FSNS(XML_xmlns, XML_r), pFB->getNamespaceURL(OOX_NS(officeRel))); + + const sal_Int32 nShapeCount(mxAdditionalShapes->getCount()); + for (sal_Int32 nShapeId = 0; nShapeId < nShapeCount; nShapeId++) + { + Reference< drawing::XShape > xShape; + mxAdditionalShapes->getByIndex(nShapeId) >>= xShape; + SAL_WARN_IF(!xShape.is(), "xmloff.chart", "Shape without an XShape?"); + if (!xShape.is()) + continue; + + // TODO: absSizeAnchor: we import both (absSizeAnchor and relSizeAnchor), but there is no essential difference between them. + pDrawing->startElement(FSNS(XML_cdr, XML_relSizeAnchor)); + uno::Reference< beans::XPropertySet > xShapeProperties(xShape, uno::UNO_QUERY); + if( xShapeProperties.is() ) + { + Reference<embed::XVisualObject> xVisObject(mxChartModel, uno::UNO_QUERY); + awt::Size aPageSize = xVisObject->getVisualAreaSize(embed::Aspects::MSOLE_CONTENT); + WriteFromTo( xShape, aPageSize, pDrawing ); + + ShapeExport aExport(XML_cdr, pDrawing, nullptr, GetFB(), GetDocumentType(), nullptr, true); + aExport.WriteShape(xShape); + } + pDrawing->endElement(FSNS(XML_cdr, XML_relSizeAnchor)); + } + pDrawing->endElement(FSNS(XML_c, XML_userShapes)); + pDrawing->endDocument(); + } + } + catch (const uno::Exception&) + { + TOOLS_INFO_EXCEPTION("xmloff.chart", "AdditionalShapes not found"); + } +} + +void ChartExport::exportChart( const Reference< css::chart::XChartDocument >& xChartDoc ) +{ + Reference< chart2::XChartDocument > xNewDoc( xChartDoc, uno::UNO_QUERY ); + mxDiagram.set( xChartDoc->getDiagram() ); + if( xNewDoc.is()) + mxNewDiagram.set( xNewDoc->getFirstDiagram()); + + // get Properties of ChartDocument + bool bHasMainTitle = false; + OUString aSubTitle; + bool bHasLegend = false; + Reference< beans::XPropertySet > xDocPropSet( xChartDoc, uno::UNO_QUERY ); + if( xDocPropSet.is()) + { + try + { + Any aAny( xDocPropSet->getPropertyValue("HasMainTitle")); + aAny >>= bHasMainTitle; + aAny = xDocPropSet->getPropertyValue("HasLegend"); + aAny >>= bHasLegend; + } + catch( beans::UnknownPropertyException & ) + { + SAL_WARN("oox", "Required property not found in ChartDocument"); + } + } // if( xDocPropSet.is()) + + Reference< beans::XPropertySet > xPropSubTitle( xChartDoc->getSubTitle(), UNO_QUERY ); + if( xPropSubTitle.is()) + { + try + { + xPropSubTitle->getPropertyValue("String") >>= aSubTitle; + } + catch( beans::UnknownPropertyException & ) + { + } + } + + // chart element + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_chart)); + + // titles + if( bHasMainTitle ) + { + exportTitle( xChartDoc->getTitle(), !aSubTitle.isEmpty() ? &aSubTitle : nullptr ); + pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "0"); + } + else if( !aSubTitle.isEmpty() ) + { + exportTitle( xChartDoc->getSubTitle(), nullptr ); + pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "0"); + } + else + { + pFS->singleElement(FSNS(XML_c, XML_autoTitleDeleted), XML_val, "1"); + } + + InitPlotArea( ); + if( mbIs3DChart ) + { + exportView3D(); + + // floor + Reference< beans::XPropertySet > xFloor = mxNewDiagram->getFloor(); + if( xFloor.is() ) + { + pFS->startElement(FSNS(XML_c, XML_floor)); + exportShapeProps( xFloor ); + pFS->endElement( FSNS( XML_c, XML_floor ) ); + } + + // LibreOffice doesn't distinguish between sideWall and backWall (both are using the same color). + // It is controlled by the same Wall property. + Reference< beans::XPropertySet > xWall = mxNewDiagram->getWall(); + if( xWall.is() ) + { + // sideWall + pFS->startElement(FSNS(XML_c, XML_sideWall)); + exportShapeProps( xWall ); + pFS->endElement( FSNS( XML_c, XML_sideWall ) ); + + // backWall + pFS->startElement(FSNS(XML_c, XML_backWall)); + exportShapeProps( xWall ); + pFS->endElement( FSNS( XML_c, XML_backWall ) ); + } + + } + // plot area + exportPlotArea( xChartDoc ); + // legend + if( bHasLegend ) + exportLegend( xChartDoc ); + + uno::Reference<beans::XPropertySet> xDiagramPropSet(xChartDoc->getDiagram(), uno::UNO_QUERY); + uno::Any aPlotVisOnly = xDiagramPropSet->getPropertyValue("IncludeHiddenCells"); + bool bIncludeHiddenCells = false; + aPlotVisOnly >>= bIncludeHiddenCells; + pFS->singleElement(FSNS(XML_c, XML_plotVisOnly), XML_val, ToPsz10(!bIncludeHiddenCells)); + + exportMissingValueTreatment(Reference<beans::XPropertySet>(mxDiagram, uno::UNO_QUERY)); + + pFS->endElement( FSNS( XML_c, XML_chart ) ); +} + +void ChartExport::exportMissingValueTreatment(const uno::Reference<beans::XPropertySet>& xPropSet) +{ + if (!xPropSet.is()) + return; + + sal_Int32 nVal = 0; + uno::Any aAny = xPropSet->getPropertyValue("MissingValueTreatment"); + if (!(aAny >>= nVal)) + return; + + const char* pVal = nullptr; + switch (nVal) + { + case cssc::MissingValueTreatment::LEAVE_GAP: + pVal = "gap"; + break; + case cssc::MissingValueTreatment::USE_ZERO: + pVal = "zero"; + break; + case cssc::MissingValueTreatment::CONTINUE: + pVal = "span"; + break; + default: + SAL_WARN("oox", "unknown MissingValueTreatment value"); + break; + } + + FSHelperPtr pFS = GetFS(); + pFS->singleElement(FSNS(XML_c, XML_dispBlanksAs), XML_val, pVal); +} + +void ChartExport::exportLegend( const Reference< css::chart::XChartDocument >& xChartDoc ) +{ + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_legend)); + + Reference< beans::XPropertySet > xProp( xChartDoc->getLegend(), uno::UNO_QUERY ); + if( xProp.is() ) + { + // position + css::chart::ChartLegendPosition aLegendPos = css::chart::ChartLegendPosition_NONE; + try + { + Any aAny( xProp->getPropertyValue( "Alignment" )); + aAny >>= aLegendPos; + } + catch( beans::UnknownPropertyException & ) + { + SAL_WARN("oox", "Property Align not found in ChartLegend"); + } + + const char* strPos = nullptr; + switch( aLegendPos ) + { + case css::chart::ChartLegendPosition_LEFT: + strPos = "l"; + break; + case css::chart::ChartLegendPosition_RIGHT: + strPos = "r"; + break; + case css::chart::ChartLegendPosition_TOP: + strPos = "t"; + break; + case css::chart::ChartLegendPosition_BOTTOM: + strPos = "b"; + break; + case css::chart::ChartLegendPosition_NONE: + case css::chart::ChartLegendPosition::ChartLegendPosition_MAKE_FIXED_SIZE: + // nothing + break; + } + + if( strPos != nullptr ) + { + pFS->singleElement(FSNS(XML_c, XML_legendPos), XML_val, strPos); + } + + // legendEntry + Reference<chart2::XCoordinateSystemContainer> xCooSysContainer(mxNewDiagram, UNO_QUERY_THROW); + const Sequence<Reference<chart2::XCoordinateSystem>> xCooSysSequence(xCooSysContainer->getCoordinateSystems()); + + sal_Int32 nIndex = 0; + bool bShowLegendEntry; + for (const auto& rCooSys : xCooSysSequence) + { + PropertySet aCooSysProp(rCooSys); + bool bSwapXAndY = aCooSysProp.getBoolProperty(PROP_SwapXAndYAxis); + + Reference<chart2::XChartTypeContainer> xChartTypeContainer(rCooSys, UNO_QUERY_THROW); + const Sequence<Reference<chart2::XChartType>> xChartTypeSequence(xChartTypeContainer->getChartTypes()); + if (!xChartTypeSequence.hasElements()) + continue; + + for (const auto& rCT : xChartTypeSequence) + { + Reference<chart2::XDataSeriesContainer> xDSCont(rCT, UNO_QUERY); + if (!xDSCont.is()) + continue; + + OUString aChartType(rCT->getChartType()); + bool bIsPie = lcl_getChartType(aChartType) == chart::TYPEID_PIE; + if (bIsPie) + { + PropertySet xChartTypeProp(rCT); + bIsPie = !xChartTypeProp.getBoolProperty(PROP_UseRings); + } + const Sequence<Reference<chart2::XDataSeries>> aDataSeriesSeq = xDSCont->getDataSeries(); + if (bSwapXAndY) + nIndex += aDataSeriesSeq.getLength() - 1; + for (const auto& rDataSeries : aDataSeriesSeq) + { + PropertySet aSeriesProp(rDataSeries); + bool bVaryColorsByPoint = aSeriesProp.getBoolProperty(PROP_VaryColorsByPoint); + if (bVaryColorsByPoint || bIsPie) + { + Sequence<sal_Int32> deletedLegendEntriesSeq; + aSeriesProp.getProperty(deletedLegendEntriesSeq, PROP_DeletedLegendEntries); + for (const auto& deletedLegendEntry : std::as_const(deletedLegendEntriesSeq)) + { + pFS->startElement(FSNS(XML_c, XML_legendEntry)); + pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, + OString::number(nIndex + deletedLegendEntry)); + pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, "1"); + pFS->endElement(FSNS(XML_c, XML_legendEntry)); + } + Reference<chart2::data::XDataSource> xDSrc(rDataSeries, UNO_QUERY); + if (!xDSrc.is()) + continue; + + const Sequence<Reference<chart2::data::XLabeledDataSequence>> aDataSeqs = xDSrc->getDataSequences(); + for (const auto& rDataSeq : aDataSeqs) + { + Reference<chart2::data::XDataSequence> xValues = rDataSeq->getValues(); + if (!xValues.is()) + continue; + + sal_Int32 nDataSeqSize = xValues->getData().getLength(); + nIndex += nDataSeqSize; + } + } + else + { + bShowLegendEntry = aSeriesProp.getBoolProperty(PROP_ShowLegendEntry); + if (!bShowLegendEntry) + { + pFS->startElement(FSNS(XML_c, XML_legendEntry)); + pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, + OString::number(nIndex)); + pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, "1"); + pFS->endElement(FSNS(XML_c, XML_legendEntry)); + } + bSwapXAndY ? nIndex-- : nIndex++; + } + } + if (bSwapXAndY) + nIndex += aDataSeriesSeq.getLength() + 1; + } + } + + uno::Any aRelativePos = xProp->getPropertyValue("RelativePosition"); + if (aRelativePos.hasValue()) + { + pFS->startElement(FSNS(XML_c, XML_layout)); + pFS->startElement(FSNS(XML_c, XML_manualLayout)); + + pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge"); + pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge"); + chart2::RelativePosition aPos = aRelativePos.get<chart2::RelativePosition>(); + + const double x = aPos.Primary; + const double y = aPos.Secondary; + + pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x)); + pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y)); + + uno::Any aRelativeSize = xProp->getPropertyValue("RelativeSize"); + if (aRelativeSize.hasValue()) + { + chart2::RelativeSize aSize = aRelativeSize.get<chart2::RelativeSize>(); + + const double w = aSize.Primary; + const double h = aSize.Secondary; + + pFS->singleElement(FSNS(XML_c, XML_w), XML_val, OString::number(w)); + + pFS->singleElement(FSNS(XML_c, XML_h), XML_val, OString::number(h)); + } + + SAL_WARN_IF(aPos.Anchor != css::drawing::Alignment_TOP_LEFT, "oox", "unsupported anchor position"); + + pFS->endElement(FSNS(XML_c, XML_manualLayout)); + pFS->endElement(FSNS(XML_c, XML_layout)); + } + + if (strPos != nullptr) + { + uno::Any aOverlay = xProp->getPropertyValue("Overlay"); + if(aOverlay.get<bool>()) + pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, "1"); + else + pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, "0"); + } + + // shape properties + exportShapeProps( xProp ); + + // draw-chart:txPr text properties + exportTextProps( xProp ); + } + + pFS->endElement( FSNS( XML_c, XML_legend ) ); +} + +void ChartExport::exportTitle( const Reference< XShape >& xShape, const OUString* pSubText) +{ + OUString sText; + Reference< beans::XPropertySet > xPropSet( xShape, uno::UNO_QUERY ); + if( xPropSet.is()) + { + xPropSet->getPropertyValue("String") >>= sText; + } + + // tdf#101322: add subtitle to title + if( pSubText ) + sText = sText.isEmpty() ? *pSubText : sText + "\n" + *pSubText; + + if( sText.isEmpty() ) + return; + + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_title)); + + pFS->startElement(FSNS(XML_c, XML_tx)); + pFS->startElement(FSNS(XML_c, XML_rich)); + + // TODO: bodyPr + const char* sWritingMode = nullptr; + bool bVertical = false; + xPropSet->getPropertyValue("StackedText") >>= bVertical; + if( bVertical ) + sWritingMode = "wordArtVert"; + + sal_Int32 nRotation = 0; + xPropSet->getPropertyValue("TextRotation") >>= nRotation; + + pFS->singleElement( FSNS( XML_a, XML_bodyPr ), + XML_vert, sWritingMode, + XML_rot, oox::drawingml::calcRotationValue(nRotation) ); + // TODO: lstStyle + pFS->singleElement(FSNS(XML_a, XML_lstStyle)); + // FIXME: handle multiple paragraphs to parse aText + pFS->startElement(FSNS(XML_a, XML_p)); + + pFS->startElement(FSNS(XML_a, XML_pPr)); + + bool bDummy = false; + sal_Int32 nDummy; + WriteRunProperties(xPropSet, false, XML_defRPr, true, bDummy, nDummy ); + + pFS->endElement( FSNS( XML_a, XML_pPr ) ); + + pFS->startElement(FSNS(XML_a, XML_r)); + bDummy = false; + WriteRunProperties( xPropSet, false, XML_rPr, true, bDummy, nDummy ); + pFS->startElement(FSNS(XML_a, XML_t)); + pFS->writeEscaped( sText ); + pFS->endElement( FSNS( XML_a, XML_t ) ); + pFS->endElement( FSNS( XML_a, XML_r ) ); + + pFS->endElement( FSNS( XML_a, XML_p ) ); + + pFS->endElement( FSNS( XML_c, XML_rich ) ); + pFS->endElement( FSNS( XML_c, XML_tx ) ); + + uno::Any aManualLayout = xPropSet->getPropertyValue("RelativePosition"); + if (aManualLayout.hasValue()) + { + pFS->startElement(FSNS(XML_c, XML_layout)); + pFS->startElement(FSNS(XML_c, XML_manualLayout)); + pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge"); + pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge"); + + Reference<embed::XVisualObject> xVisObject(mxChartModel, uno::UNO_QUERY); + awt::Size aPageSize = xVisObject->getVisualAreaSize(embed::Aspects::MSOLE_CONTENT); + + awt::Size aSize = xShape->getSize(); + awt::Point aPos2 = xShape->getPosition(); + // rotated shapes need special handling... + double fSin = fabs(sin(basegfx::deg2rad<100>(nRotation))); + // remove part of height from X direction, if title is rotated down + if( nRotation*0.01 > 180.0 ) + aPos2.X -= static_cast<sal_Int32>(fSin * aSize.Height + 0.5); + // remove part of width from Y direction, if title is rotated up + else if( nRotation*0.01 > 0.0 ) + aPos2.Y -= static_cast<sal_Int32>(fSin * aSize.Width + 0.5); + + double x = static_cast<double>(aPos2.X) / static_cast<double>(aPageSize.Width); + double y = static_cast<double>(aPos2.Y) / static_cast<double>(aPageSize.Height); + /* + pFS->singleElement(FSNS(XML_c, XML_wMode), XML_val, "edge"); + pFS->singleElement(FSNS(XML_c, XML_hMode), XML_val, "edge"); + */ + pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x)); + pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y)); + /* + pFS->singleElement(FSNS(XML_c, XML_w), XML_val, ""); + pFS->singleElement(FSNS(XML_c, XML_h), XML_val, ""); + */ + pFS->endElement(FSNS(XML_c, XML_manualLayout)); + pFS->endElement(FSNS(XML_c, XML_layout)); + } + + pFS->singleElement(FSNS(XML_c, XML_overlay), XML_val, "0"); + + // shape properties + if( xPropSet.is() ) + { + exportShapeProps( xPropSet ); + } + + pFS->endElement( FSNS( XML_c, XML_title ) ); +} + +namespace { + + std::vector<Sequence<Reference<chart2::XDataSeries> > > splitDataSeriesByAxis(const Reference< chart2::XChartType >& xChartType) + { + std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitSeries; + std::map<sal_Int32, size_t> aMapAxisToIndex; + + Reference< chart2::XDataSeriesContainer > xDSCnt(xChartType, uno::UNO_QUERY); + if (xDSCnt.is()) + { + sal_Int32 nAxisIndexOfFirstSeries = -1; + const Sequence< Reference< chart2::XDataSeries > > aSeriesSeq(xDSCnt->getDataSeries()); + for (const uno::Reference<chart2::XDataSeries>& xSeries : aSeriesSeq) + { + Reference<beans::XPropertySet> xPropSet(xSeries, uno::UNO_QUERY); + if (!xPropSet.is()) + continue; + + sal_Int32 nAxisIndex = -1; + uno::Any aAny = xPropSet->getPropertyValue("AttachedAxisIndex"); + aAny >>= nAxisIndex; + size_t nVectorPos = 0; + if (nAxisIndexOfFirstSeries == -1) + { + nAxisIndexOfFirstSeries = nAxisIndex; + } + + auto it = aMapAxisToIndex.find(nAxisIndex); + if (it == aMapAxisToIndex.end()) + { + aSplitSeries.emplace_back(); + nVectorPos = aSplitSeries.size() - 1; + aMapAxisToIndex.insert(std::pair<sal_Int32, size_t>(nAxisIndex, nVectorPos)); + } + else + { + nVectorPos = it->second; + } + + uno::Sequence<Reference<chart2::XDataSeries> >& rAxisSeriesSeq = aSplitSeries[nVectorPos]; + sal_Int32 nLength = rAxisSeriesSeq.getLength(); + rAxisSeriesSeq.realloc(nLength + 1); + rAxisSeriesSeq.getArray()[nLength] = xSeries; + } + // if the first series attached to secondary axis, then export those series first, which are attached to primary axis + // also the MS Office export every time in this order + if (aSplitSeries.size() > 1 && nAxisIndexOfFirstSeries == 1) + { + std::swap(aSplitSeries[0], aSplitSeries[1]); + } + } + + return aSplitSeries; + } + +} + +void ChartExport::exportPlotArea(const Reference< css::chart::XChartDocument >& xChartDoc) +{ + Reference< chart2::XCoordinateSystemContainer > xBCooSysCnt( mxNewDiagram, uno::UNO_QUERY ); + if( ! xBCooSysCnt.is()) + return; + + // plot-area element + + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_plotArea)); + + Reference<beans::XPropertySet> xWall(mxNewDiagram, uno::UNO_QUERY); + if( xWall.is() ) + { + uno::Any aAny = xWall->getPropertyValue("RelativePosition"); + if (aAny.hasValue()) + { + chart2::RelativePosition aPos = aAny.get<chart2::RelativePosition>(); + aAny = xWall->getPropertyValue("RelativeSize"); + chart2::RelativeSize aSize = aAny.get<chart2::RelativeSize>(); + uno::Reference< css::chart::XDiagramPositioning > xDiagramPositioning( xChartDoc->getDiagram(), uno::UNO_QUERY ); + exportManualLayout(aPos, aSize, xDiagramPositioning->isExcludingDiagramPositioning() ); + } + } + + // chart type + const Sequence< Reference< chart2::XCoordinateSystem > > + aCooSysSeq( xBCooSysCnt->getCoordinateSystems()); + + // tdf#123647 Save empty chart as empty bar chart. + if (!aCooSysSeq.hasElements()) + { + pFS->startElement(FSNS(XML_c, XML_barChart)); + pFS->singleElement(FSNS(XML_c, XML_barDir), XML_val, "col"); + pFS->singleElement(FSNS(XML_c, XML_grouping), XML_val, "clustered"); + pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, "0"); + exportAxesId(true); + pFS->endElement(FSNS(XML_c, XML_barChart)); + } + + for( const auto& rCS : aCooSysSeq ) + { + Reference< chart2::XChartTypeContainer > xCTCnt( rCS, uno::UNO_QUERY ); + if( ! xCTCnt.is()) + continue; + mnSeriesCount=0; + const Sequence< Reference< chart2::XChartType > > aCTSeq( xCTCnt->getChartTypes()); + for( const auto& rCT : aCTSeq ) + { + Reference< chart2::XDataSeriesContainer > xDSCnt( rCT, uno::UNO_QUERY ); + if( ! xDSCnt.is()) + return; + Reference< chart2::XChartType > xChartType( rCT, uno::UNO_QUERY ); + if( ! xChartType.is()) + continue; + // note: if xDSCnt.is() then also aCTSeq[nCTIdx] + OUString aChartType( xChartType->getChartType()); + sal_Int32 eChartType = lcl_getChartType( aChartType ); + switch( eChartType ) + { + case chart::TYPEID_BAR: + { + exportBarChart( xChartType ); + break; + } + case chart::TYPEID_AREA: + { + exportAreaChart( xChartType ); + break; + } + case chart::TYPEID_LINE: + { + exportLineChart( xChartType ); + break; + } + case chart::TYPEID_BUBBLE: + { + exportBubbleChart( xChartType ); + break; + } + case chart::TYPEID_OFPIE: + { + break; + } + case chart::TYPEID_DOUGHNUT: + case chart::TYPEID_PIE: + { + exportPieChart( xChartType ); + break; + } + case chart::TYPEID_RADARLINE: + case chart::TYPEID_RADARAREA: + { + exportRadarChart( xChartType ); + break; + } + case chart::TYPEID_SCATTER: + { + exportScatterChart( xChartType ); + break; + } + case chart::TYPEID_STOCK: + { + exportStockChart( xChartType ); + break; + } + case chart::TYPEID_SURFACE: + { + exportSurfaceChart( xChartType ); + break; + } + default: + { + SAL_WARN("oox", "ChartExport::exportPlotArea -- not support chart type"); + break; + } + } + + } + } + //Axis Data + exportAxes( ); + + // Data Table + exportDataTable(); + + // shape properties + /* + * Export the Plot area Shape Properties + * eg: Fill and Outline + */ + Reference< css::chart::X3DDisplay > xWallFloorSupplier( mxDiagram, uno::UNO_QUERY ); + // tdf#114139 For 2D charts Plot Area equivalent is Chart Wall. + // Unfortunately LibreOffice doesn't have Plot Area equivalent for 3D charts. + // It means that Plot Area couldn't be displayed and changed for 3D chars in LibreOffice. + // We cannot write Wall attributes into Plot Area for 3D charts, because Wall us used as background wall. + if( !mbIs3DChart && xWallFloorSupplier.is() ) + { + Reference< beans::XPropertySet > xWallPropSet = xWallFloorSupplier->getWall(); + if( xWallPropSet.is() ) + { + uno::Any aAny = xWallPropSet->getPropertyValue("LineStyle"); + sal_Int32 eChartType = getChartType( ); + // Export LineStyle_NONE instead of default linestyle of PlotArea border, because LibreOffice + // make invisible the Wall shape properties, in case of these charts. Or in the future set + // the default LineStyle of these charts to LineStyle_NONE. + bool noSupportWallProp = ( (eChartType == chart::TYPEID_PIE) || (eChartType == chart::TYPEID_RADARLINE) || (eChartType == chart::TYPEID_RADARAREA) ); + if ( noSupportWallProp && (aAny != drawing::LineStyle_NONE) ) + { + xWallPropSet->setPropertyValue( "LineStyle", uno::Any(drawing::LineStyle_NONE) ); + } + exportShapeProps( xWallPropSet ); + } + } + + pFS->endElement( FSNS( XML_c, XML_plotArea ) ); + +} + +void ChartExport::exportManualLayout(const css::chart2::RelativePosition& rPos, + const css::chart2::RelativeSize& rSize, + const bool bIsExcludingDiagramPositioning) +{ + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_layout)); + pFS->startElement(FSNS(XML_c, XML_manualLayout)); + + // By default layoutTarget is set to "outer" and we shouldn't save it in that case + if ( bIsExcludingDiagramPositioning ) + { + pFS->singleElement(FSNS(XML_c, XML_layoutTarget), XML_val, "inner"); + } + pFS->singleElement(FSNS(XML_c, XML_xMode), XML_val, "edge"); + pFS->singleElement(FSNS(XML_c, XML_yMode), XML_val, "edge"); + + double x = rPos.Primary; + double y = rPos.Secondary; + const double w = rSize.Primary; + const double h = rSize.Secondary; + switch (rPos.Anchor) + { + case drawing::Alignment_LEFT: + y -= (h/2); + break; + case drawing::Alignment_TOP_LEFT: + break; + case drawing::Alignment_BOTTOM_LEFT: + y -= h; + break; + case drawing::Alignment_TOP: + x -= (w/2); + break; + case drawing::Alignment_CENTER: + x -= (w/2); + y -= (h/2); + break; + case drawing::Alignment_BOTTOM: + x -= (w/2); + y -= h; + break; + case drawing::Alignment_TOP_RIGHT: + x -= w; + break; + case drawing::Alignment_BOTTOM_RIGHT: + x -= w; + y -= h; + break; + case drawing::Alignment_RIGHT: + y -= (h/2); + x -= w; + break; + default: + SAL_WARN("oox", "unhandled alignment case for manual layout export " << static_cast<sal_uInt16>(rPos.Anchor)); + } + + pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(x)); + + pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(y)); + + pFS->singleElement(FSNS(XML_c, XML_w), XML_val, OString::number(w)); + + pFS->singleElement(FSNS(XML_c, XML_h), XML_val, OString::number(h)); + + pFS->endElement(FSNS(XML_c, XML_manualLayout)); + pFS->endElement(FSNS(XML_c, XML_layout)); +} + +void ChartExport::exportFill( const Reference< XPropertySet >& xPropSet ) +{ + // Similar to DrawingML::WriteFill, but gradient access via name + if (!GetProperty( xPropSet, "FillStyle" )) + return; + FillStyle aFillStyle(FillStyle_NONE); + mAny >>= aFillStyle; + + // map full transparent background to no fill + if (aFillStyle == FillStyle_SOLID && GetProperty( xPropSet, "FillTransparence" )) + { + sal_Int16 nVal = 0; + mAny >>= nVal; + if ( nVal == 100 ) + aFillStyle = FillStyle_NONE; + } + OUString sFillTransparenceGradientName; + if (aFillStyle == FillStyle_SOLID + && GetProperty(xPropSet, "FillTransparenceGradientName") && (mAny >>= sFillTransparenceGradientName) + && !sFillTransparenceGradientName.isEmpty()) + { + awt::Gradient aTransparenceGradient; + uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY ); + uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance("com.sun.star.drawing.TransparencyGradientTable"), uno::UNO_QUERY); + uno::Any rTransparenceValue = xTransparenceGradient->getByName(sFillTransparenceGradientName); + rTransparenceValue >>= aTransparenceGradient; + if (aTransparenceGradient.StartColor == 0xffffff && aTransparenceGradient.EndColor == 0xffffff) + aFillStyle = FillStyle_NONE; + } + switch( aFillStyle ) + { + case FillStyle_SOLID: + exportSolidFill(xPropSet); + break; + case FillStyle_GRADIENT : + exportGradientFill( xPropSet ); + break; + case FillStyle_BITMAP : + exportBitmapFill( xPropSet ); + break; + case FillStyle_HATCH: + exportHatch(xPropSet); + break; + case FillStyle_NONE: + mpFS->singleElementNS(XML_a, XML_noFill); + break; + default: + ; + } +} + +void ChartExport::exportSolidFill(const Reference< XPropertySet >& xPropSet) +{ + // Similar to DrawingML::WriteSolidFill, but gradient access via name + // and currently no InteropGrabBag + // get fill color + sal_uInt32 nFillColor = 0; + if (!GetProperty(xPropSet, "FillColor") || !(mAny >>= nFillColor)) + return; + + sal_Int32 nAlpha = MAX_PERCENT; + if (GetProperty( xPropSet, "FillTransparence" )) + { + sal_Int32 nTransparency = 0; + mAny >>= nTransparency; + // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency()) + nAlpha = (MAX_PERCENT - ( PER_PERCENT * nTransparency ) ); + } + // OOXML has no separate transparence gradient but uses transparency in the gradient stops. + // So we merge transparency and color and use gradient fill in such case. + basegfx::BGradient aTransparenceGradient; + bool bNeedGradientFill(false); + OUString sFillTransparenceGradientName; + + if (GetProperty(xPropSet, "FillTransparenceGradientName") + && (mAny >>= sFillTransparenceGradientName) + && !sFillTransparenceGradientName.isEmpty()) + { + uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY ); + uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance("com.sun.star.drawing.TransparencyGradientTable"), uno::UNO_QUERY); + const uno::Any rTransparenceAny = xTransparenceGradient->getByName(sFillTransparenceGradientName); + + aTransparenceGradient = model::gradient::getFromAny(rTransparenceAny); + basegfx::BColor aSingleColor; + bNeedGradientFill = !aTransparenceGradient.GetColorStops().isSingleColor(aSingleColor); + + if (!bNeedGradientFill) + { + // Our alpha is a single gray color value. + const sal_uInt8 nRed(aSingleColor.getRed() * 255.0); + + // drawingML alpha is a percentage on a 0..100000 scale. + nAlpha = (255 - nRed) * oox::drawingml::MAX_PERCENT / 255; + } + } + // write XML + if (bNeedGradientFill) + { + // no longer create copy/PseudoColorGradient, use new API of + // WriteGradientFill to express fix fill color + mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0"); + WriteGradientFill(nullptr, nFillColor, &aTransparenceGradient); + mpFS->endElementNS(XML_a, XML_gradFill); + } + else + WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha); +} + +void ChartExport::exportHatch( const Reference< XPropertySet >& xPropSet ) +{ + if (!xPropSet.is()) + return; + + if (GetProperty(xPropSet, "FillHatchName")) + { + OUString aHatchName; + mAny >>= aHatchName; + uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY ); + uno::Reference< container::XNameAccess > xHatchTable( xFact->createInstance("com.sun.star.drawing.HatchTable"), uno::UNO_QUERY ); + uno::Any rValue = xHatchTable->getByName(aHatchName); + css::drawing::Hatch aHatch; + rValue >>= aHatch; + WritePattFill(xPropSet, aHatch); + } + +} + +void ChartExport::exportBitmapFill( const Reference< XPropertySet >& xPropSet ) +{ + if( !xPropSet.is() ) + return; + + OUString sFillBitmapName; + xPropSet->getPropertyValue("FillBitmapName") >>= sFillBitmapName; + + uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY ); + try + { + uno::Reference< container::XNameAccess > xBitmapTable( xFact->createInstance("com.sun.star.drawing.BitmapTable"), uno::UNO_QUERY ); + uno::Any rValue = xBitmapTable->getByName( sFillBitmapName ); + if (rValue.has<uno::Reference<awt::XBitmap>>()) + { + uno::Reference<awt::XBitmap> xBitmap = rValue.get<uno::Reference<awt::XBitmap>>(); + uno::Reference<graphic::XGraphic> xGraphic(xBitmap, uno::UNO_QUERY); + if (xGraphic.is()) + { + WriteXGraphicBlipFill(xPropSet, xGraphic, XML_a, true, true); + } + } + } + catch (const uno::Exception &) + { + TOOLS_WARN_EXCEPTION("oox", "ChartExport::exportBitmapFill"); + } +} + +void ChartExport::exportGradientFill( const Reference< XPropertySet >& xPropSet ) +{ + if( !xPropSet.is() ) + return; + + OUString sFillGradientName; + xPropSet->getPropertyValue("FillGradientName") >>= sFillGradientName; + + uno::Reference< lang::XMultiServiceFactory > xFact( getModel(), uno::UNO_QUERY ); + try + { + uno::Reference< container::XNameAccess > xGradient( xFact->createInstance("com.sun.star.drawing.GradientTable"), uno::UNO_QUERY ); + const uno::Any rGradientAny(xGradient->getByName( sFillGradientName )); + const basegfx::BGradient aGradient = model::gradient::getFromAny(rGradientAny); + basegfx::BColor aSingleColor; + + if (!aGradient.GetColorStops().isSingleColor(aSingleColor)) + { + basegfx::BGradient aTransparenceGradient; + mpFS->startElementNS(XML_a, XML_gradFill); + OUString sFillTransparenceGradientName; + + if( (xPropSet->getPropertyValue("FillTransparenceGradientName") >>= sFillTransparenceGradientName) && !sFillTransparenceGradientName.isEmpty()) + { + uno::Reference< container::XNameAccess > xTransparenceGradient(xFact->createInstance("com.sun.star.drawing.TransparencyGradientTable"), uno::UNO_QUERY); + const uno::Any rTransparenceAny(xTransparenceGradient->getByName(sFillTransparenceGradientName)); + + aTransparenceGradient = model::gradient::getFromAny(rTransparenceAny); + + WriteGradientFill(&aGradient, 0, &aTransparenceGradient); + } + else if (GetProperty(xPropSet, "FillTransparence") ) + { + // no longer create PseudoTransparencyGradient, use new API of + // WriteGradientFill to express fix transparency + sal_Int32 nTransparency = 0; + mAny >>= nTransparency; + // nTransparency is [0..100]% + WriteGradientFill(&aGradient, 0, nullptr, nTransparency * 0.01); + } + else + { + WriteGradientFill(&aGradient, 0, nullptr); + } + + mpFS->endElementNS(XML_a, XML_gradFill); + } + } + catch (const uno::Exception &) + { + TOOLS_INFO_EXCEPTION("oox", "ChartExport::exportGradientFill"); + } +} + +void ChartExport::exportDataTable( ) +{ + auto xDataTable = mxNewDiagram->getDataTable(); + if (!xDataTable.is()) + return; + + FSHelperPtr pFS = GetFS(); + uno::Reference<beans::XPropertySet> aPropSet(xDataTable, uno::UNO_QUERY); + + bool bShowVBorder = false; + bool bShowHBorder = false; + bool bShowOutline = false; + bool bShowKeys = false; + + if (GetProperty(aPropSet, "HBorder")) + mAny >>= bShowHBorder; + if (GetProperty(aPropSet, "VBorder")) + mAny >>= bShowVBorder; + if (GetProperty(aPropSet, "Outline")) + mAny >>= bShowOutline; + if (GetProperty(aPropSet, "Keys")) + mAny >>= bShowKeys; + + 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"); + if (bShowKeys) + pFS->singleElement(FSNS(XML_c, XML_showKeys), XML_val, "1"); + + exportShapeProps(aPropSet); + exportTextProps(aPropSet); + + pFS->endElement(FSNS(XML_c, XML_dTable)); +} + +void ChartExport::exportAreaChart( const Reference< chart2::XChartType >& xChartType ) +{ + FSHelperPtr pFS = GetFS(); + const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType); + for (const auto& splitDataSeries : aSplitDataSeries) + { + if (!splitDataSeries.hasElements()) + continue; + + sal_Int32 nTypeId = XML_areaChart; + if (mbIs3DChart) + nTypeId = XML_area3DChart; + pFS->startElement(FSNS(XML_c, nTypeId)); + + exportGrouping(); + bool bPrimaryAxes = true; + exportSeries(xChartType, splitDataSeries, bPrimaryAxes); + exportAxesId(bPrimaryAxes); + + pFS->endElement(FSNS(XML_c, nTypeId)); + } +} + +void ChartExport::exportBarChart(const Reference< chart2::XChartType >& xChartType) +{ + sal_Int32 nTypeId = XML_barChart; + if (mbIs3DChart) + nTypeId = XML_bar3DChart; + FSHelperPtr pFS = GetFS(); + + const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType); + for (const auto& splitDataSeries : aSplitDataSeries) + { + if (!splitDataSeries.hasElements()) + continue; + + pFS->startElement(FSNS(XML_c, nTypeId)); + // bar direction + bool bVertical = false; + Reference< XPropertySet > xPropSet(mxDiagram, uno::UNO_QUERY); + if (GetProperty(xPropSet, "Vertical")) + mAny >>= bVertical; + + const char* bardir = bVertical ? "bar" : "col"; + pFS->singleElement(FSNS(XML_c, XML_barDir), XML_val, bardir); + + exportGrouping(true); + + exportVaryColors(xChartType); + + bool bPrimaryAxes = true; + exportSeries(xChartType, splitDataSeries, bPrimaryAxes); + + Reference< XPropertySet > xTypeProp(xChartType, uno::UNO_QUERY); + + if (xTypeProp.is() && GetProperty(xTypeProp, "GapwidthSequence")) + { + uno::Sequence< sal_Int32 > aBarPositionSequence; + mAny >>= aBarPositionSequence; + if (aBarPositionSequence.hasElements()) + { + sal_Int32 nGapWidth = aBarPositionSequence[0]; + pFS->singleElement(FSNS(XML_c, XML_gapWidth), XML_val, OString::number(nGapWidth)); + } + } + + if (mbIs3DChart) + { + // Shape + namespace cssc = css::chart; + sal_Int32 nGeom3d = cssc::ChartSolidType::RECTANGULAR_SOLID; + if (xPropSet.is() && GetProperty(xPropSet, "SolidType")) + mAny >>= nGeom3d; + const char* sShapeType = nullptr; + switch (nGeom3d) + { + case cssc::ChartSolidType::RECTANGULAR_SOLID: + sShapeType = "box"; + break; + case cssc::ChartSolidType::CONE: + sShapeType = "cone"; + break; + case cssc::ChartSolidType::CYLINDER: + sShapeType = "cylinder"; + break; + case cssc::ChartSolidType::PYRAMID: + sShapeType = "pyramid"; + break; + } + pFS->singleElement(FSNS(XML_c, XML_shape), XML_val, sShapeType); + } + + //overlap + if (!mbIs3DChart && xTypeProp.is() && GetProperty(xTypeProp, "OverlapSequence")) + { + uno::Sequence< sal_Int32 > aBarPositionSequence; + mAny >>= aBarPositionSequence; + if (aBarPositionSequence.hasElements()) + { + sal_Int32 nOverlap = aBarPositionSequence[0]; + // Stacked/Percent Bar/Column chart Overlap-workaround + // Export the Overlap value with 100% for stacked charts, + // because the default overlap value of the Bar/Column chart is 0% and + // LibreOffice do nothing with the overlap value in Stacked charts case, + // unlike the MS Office, which is interpreted differently. + if ((mbStacked || mbPercent) && nOverlap != 100) + { + nOverlap = 100; + pFS->singleElement(FSNS(XML_c, XML_overlap), XML_val, OString::number(nOverlap)); + } + else // Normal bar chart + { + pFS->singleElement(FSNS(XML_c, XML_overlap), XML_val, OString::number(nOverlap)); + } + } + } + + exportAxesId(bPrimaryAxes); + + pFS->endElement(FSNS(XML_c, nTypeId)); + } +} + +void ChartExport::exportBubbleChart( const Reference< chart2::XChartType >& xChartType ) +{ + FSHelperPtr pFS = GetFS(); + const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType); + for (const auto& splitDataSeries : aSplitDataSeries) + { + if (!splitDataSeries.hasElements()) + continue; + + pFS->startElement(FSNS(XML_c, XML_bubbleChart)); + + exportVaryColors(xChartType); + + bool bPrimaryAxes = true; + exportSeries(xChartType, splitDataSeries, bPrimaryAxes); + + exportAxesId(bPrimaryAxes); + + pFS->endElement(FSNS(XML_c, XML_bubbleChart)); + } +} + +void ChartExport::exportDoughnutChart( const Reference< chart2::XChartType >& xChartType ) +{ + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_doughnutChart)); + + exportVaryColors(xChartType); + + bool bPrimaryAxes = true; + exportAllSeries(xChartType, bPrimaryAxes); + // firstSliceAng + exportFirstSliceAng( ); + //FIXME: holeSize + pFS->singleElement(FSNS(XML_c, XML_holeSize), XML_val, OString::number(50)); + + pFS->endElement( FSNS( XML_c, XML_doughnutChart ) ); +} + +namespace { + +void writeDataLabelsRange(const FSHelperPtr& pFS, const XmlFilterBase* pFB, DataLabelsRange& rDLblsRange) +{ + if (rDLblsRange.empty()) + return; + + pFS->startElement(FSNS(XML_c, XML_extLst)); + pFS->startElement(FSNS(XML_c, XML_ext), XML_uri, "{02D57815-91ED-43cb-92C2-25804820EDAC}", FSNS(XML_xmlns, XML_c15), pFB->getNamespaceURL(OOX_NS(c15))); + pFS->startElement(FSNS(XML_c15, XML_datalabelsRange)); + + // Write cell range. + pFS->startElement(FSNS(XML_c15, XML_f)); + pFS->writeEscaped(rDLblsRange.getRange()); + pFS->endElement(FSNS(XML_c15, XML_f)); + + // Write all labels. + pFS->startElement(FSNS(XML_c15, XML_dlblRangeCache)); + pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(rDLblsRange.count())); + for (const auto& rLabelKV: rDLblsRange) + { + pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(rLabelKV.first)); + pFS->startElement(FSNS(XML_c, XML_v)); + pFS->writeEscaped(rLabelKV.second); + pFS->endElement(FSNS( XML_c, XML_v )); + pFS->endElement(FSNS(XML_c, XML_pt)); + } + + pFS->endElement(FSNS(XML_c15, XML_dlblRangeCache)); + + pFS->endElement(FSNS(XML_c15, XML_datalabelsRange)); + pFS->endElement(FSNS(XML_c, XML_ext)); + pFS->endElement(FSNS(XML_c, XML_extLst)); +} + +} + +void ChartExport::exportLineChart( const Reference< chart2::XChartType >& xChartType ) +{ + FSHelperPtr pFS = GetFS(); + const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType); + for (const auto& splitDataSeries : aSplitDataSeries) + { + if (!splitDataSeries.hasElements()) + continue; + + sal_Int32 nTypeId = XML_lineChart; + if( mbIs3DChart ) + nTypeId = XML_line3DChart; + pFS->startElement(FSNS(XML_c, nTypeId)); + + exportGrouping( ); + + exportVaryColors(xChartType); + // TODO: show marker symbol in series? + bool bPrimaryAxes = true; + exportSeries(xChartType, splitDataSeries, bPrimaryAxes); + + // show marker? + sal_Int32 nSymbolType = css::chart::ChartSymbolType::NONE; + Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY); + if( GetProperty( xPropSet, "SymbolType" ) ) + mAny >>= nSymbolType; + + if( !mbIs3DChart ) + { + exportHiLowLines(); + exportUpDownBars(xChartType); + const char* marker = nSymbolType == css::chart::ChartSymbolType::NONE? "0":"1"; + pFS->singleElement(FSNS(XML_c, XML_marker), XML_val, marker); + } + + exportAxesId(bPrimaryAxes, true); + + pFS->endElement( FSNS( XML_c, nTypeId ) ); + } +} + +void ChartExport::exportPieChart( const Reference< chart2::XChartType >& xChartType ) +{ + sal_Int32 eChartType = getChartType( ); + if(eChartType == chart::TYPEID_DOUGHNUT) + { + exportDoughnutChart( xChartType ); + return; + } + FSHelperPtr pFS = GetFS(); + sal_Int32 nTypeId = XML_pieChart; + if( mbIs3DChart ) + nTypeId = XML_pie3DChart; + pFS->startElement(FSNS(XML_c, nTypeId)); + + exportVaryColors(xChartType); + + bool bPrimaryAxes = true; + exportAllSeries(xChartType, bPrimaryAxes); + + if( !mbIs3DChart ) + { + // firstSliceAng + exportFirstSliceAng( ); + } + + pFS->endElement( FSNS( XML_c, nTypeId ) ); +} + +void ChartExport::exportRadarChart( const Reference< chart2::XChartType >& xChartType) +{ + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_radarChart)); + + // radarStyle + sal_Int32 eChartType = getChartType( ); + const char* radarStyle = nullptr; + if( eChartType == chart::TYPEID_RADARAREA ) + radarStyle = "filled"; + else + radarStyle = "marker"; + pFS->singleElement(FSNS(XML_c, XML_radarStyle), XML_val, radarStyle); + + exportVaryColors(xChartType); + bool bPrimaryAxes = true; + exportAllSeries(xChartType, bPrimaryAxes); + exportAxesId(bPrimaryAxes); + + pFS->endElement( FSNS( XML_c, XML_radarChart ) ); +} + +void ChartExport::exportScatterChartSeries( const Reference< chart2::XChartType >& xChartType, + const css::uno::Sequence<css::uno::Reference<chart2::XDataSeries>>* pSeries) +{ + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_scatterChart)); + // TODO:scatterStyle + + sal_Int32 nSymbolType = css::chart::ChartSymbolType::NONE; + Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY); + if( GetProperty( xPropSet, "SymbolType" ) ) + mAny >>= nSymbolType; + + const char* scatterStyle = "lineMarker"; + if (nSymbolType == css::chart::ChartSymbolType::NONE) + { + scatterStyle = "line"; + } + + pFS->singleElement(FSNS(XML_c, XML_scatterStyle), XML_val, scatterStyle); + + exportVaryColors(xChartType); + // FIXME: should export xVal and yVal + bool bPrimaryAxes = true; + if (pSeries) + exportSeries(xChartType, *pSeries, bPrimaryAxes); + exportAxesId(bPrimaryAxes); + + pFS->endElement( FSNS( XML_c, XML_scatterChart ) ); +} + +void ChartExport::exportScatterChart( const Reference< chart2::XChartType >& xChartType ) +{ + const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType); + bool bExported = false; + for (const auto& splitDataSeries : aSplitDataSeries) + { + if (!splitDataSeries.hasElements()) + continue; + + bExported = true; + exportScatterChartSeries(xChartType, &splitDataSeries); + } + if (!bExported) + exportScatterChartSeries(xChartType, nullptr); +} + +void ChartExport::exportStockChart( const Reference< chart2::XChartType >& xChartType ) +{ + FSHelperPtr pFS = GetFS(); + const std::vector<Sequence<Reference<chart2::XDataSeries> > > aSplitDataSeries = splitDataSeriesByAxis(xChartType); + for (const auto& splitDataSeries : aSplitDataSeries) + { + if (!splitDataSeries.hasElements()) + continue; + + pFS->startElement(FSNS(XML_c, XML_stockChart)); + + bool bPrimaryAxes = true; + exportCandleStickSeries(splitDataSeries, bPrimaryAxes); + + // export stock properties + Reference< css::chart::XStatisticDisplay > xStockPropProvider(mxDiagram, uno::UNO_QUERY); + if (xStockPropProvider.is()) + { + exportHiLowLines(); + exportUpDownBars(xChartType); + } + + exportAxesId(bPrimaryAxes); + + pFS->endElement(FSNS(XML_c, XML_stockChart)); + } +} + +void ChartExport::exportHiLowLines() +{ + FSHelperPtr pFS = GetFS(); + // export the chart property + Reference< css::chart::XStatisticDisplay > xChartPropProvider( mxDiagram, uno::UNO_QUERY ); + + if (!xChartPropProvider.is()) + return; + + Reference< beans::XPropertySet > xStockPropSet = xChartPropProvider->getMinMaxLine(); + if( !xStockPropSet.is() ) + return; + + pFS->startElement(FSNS(XML_c, XML_hiLowLines)); + exportShapeProps( xStockPropSet ); + pFS->endElement( FSNS( XML_c, XML_hiLowLines ) ); +} + +void ChartExport::exportUpDownBars( const Reference< chart2::XChartType >& xChartType) +{ + if(xChartType->getChartType() != "com.sun.star.chart2.CandleStickChartType") + return; + + FSHelperPtr pFS = GetFS(); + // export the chart property + Reference< css::chart::XStatisticDisplay > xChartPropProvider( mxDiagram, uno::UNO_QUERY ); + if(!xChartPropProvider.is()) + return; + + // updownbar + pFS->startElement(FSNS(XML_c, XML_upDownBars)); + // TODO: gapWidth + pFS->singleElement(FSNS(XML_c, XML_gapWidth), XML_val, OString::number(150)); + + Reference< beans::XPropertySet > xChartPropSet = xChartPropProvider->getUpBar(); + if( xChartPropSet.is() ) + { + pFS->startElement(FSNS(XML_c, XML_upBars)); + // For Linechart with UpDownBars, spPr is not getting imported + // so no need to call the exportShapeProps() for LineChart + if(xChartType->getChartType() == "com.sun.star.chart2.CandleStickChartType") + { + exportShapeProps(xChartPropSet); + } + pFS->endElement( FSNS( XML_c, XML_upBars ) ); + } + xChartPropSet = xChartPropProvider->getDownBar(); + if( xChartPropSet.is() ) + { + pFS->startElement(FSNS(XML_c, XML_downBars)); + if(xChartType->getChartType() == "com.sun.star.chart2.CandleStickChartType") + { + exportShapeProps(xChartPropSet); + } + pFS->endElement( FSNS( XML_c, XML_downBars ) ); + } + pFS->endElement( FSNS( XML_c, XML_upDownBars ) ); +} + +void ChartExport::exportSurfaceChart( const Reference< chart2::XChartType >& xChartType ) +{ + FSHelperPtr pFS = GetFS(); + sal_Int32 nTypeId = XML_surfaceChart; + if( mbIs3DChart ) + nTypeId = XML_surface3DChart; + pFS->startElement(FSNS(XML_c, nTypeId)); + exportVaryColors(xChartType); + bool bPrimaryAxes = true; + exportAllSeries(xChartType, bPrimaryAxes); + exportAxesId(bPrimaryAxes); + + pFS->endElement( FSNS( XML_c, nTypeId ) ); +} + +void ChartExport::exportAllSeries(const Reference<chart2::XChartType>& xChartType, bool& rPrimaryAxes) +{ + Reference< chart2::XDataSeriesContainer > xDSCnt( xChartType, uno::UNO_QUERY ); + if( ! xDSCnt.is()) + return; + + // export dataseries for current chart-type + Sequence< Reference< chart2::XDataSeries > > aSeriesSeq( xDSCnt->getDataSeries()); + exportSeries(xChartType, aSeriesSeq, rPrimaryAxes); +} + +void ChartExport::exportVaryColors(const Reference<chart2::XChartType>& xChartType) +{ + FSHelperPtr pFS = GetFS(); + try + { + Reference<chart2::XDataSeries> xDataSeries = getPrimaryDataSeries(xChartType); + Reference<beans::XPropertySet> xDataSeriesProps(xDataSeries, uno::UNO_QUERY_THROW); + Any aAnyVaryColors = xDataSeriesProps->getPropertyValue("VaryColorsByPoint"); + bool bVaryColors = false; + aAnyVaryColors >>= bVaryColors; + pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, ToPsz10(bVaryColors)); + } + catch (...) + { + pFS->singleElement(FSNS(XML_c, XML_varyColors), XML_val, "0"); + } +} + +void ChartExport::exportSeries( const Reference<chart2::XChartType>& xChartType, + const Sequence<Reference<chart2::XDataSeries> >& rSeriesSeq, bool& rPrimaryAxes ) +{ + OUString aLabelRole = xChartType->getRoleOfSequenceForSeriesLabel(); + OUString aChartType( xChartType->getChartType()); + sal_Int32 eChartType = lcl_getChartType( aChartType ); + + for( const auto& rSeries : rSeriesSeq ) + { + // export series + Reference< chart2::data::XDataSource > xSource( rSeries, uno::UNO_QUERY ); + if( xSource.is()) + { + Reference< chart2::XDataSeries > xDataSeries( xSource, uno::UNO_QUERY ); + Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt( + xSource->getDataSequences()); + // search for main sequence and create a series element + { + sal_Int32 nMainSequenceIndex = -1; + sal_Int32 nSeriesLength = 0; + Reference< chart2::data::XDataSequence > xValuesSeq; + Reference< chart2::data::XDataSequence > xLabelSeq; + sal_Int32 nSeqIdx=0; + for( ; nSeqIdx<aSeqCnt.getLength(); ++nSeqIdx ) + { + Reference< chart2::data::XDataSequence > xTempValueSeq( aSeqCnt[nSeqIdx]->getValues() ); + if( nMainSequenceIndex==-1 ) + { + Reference< beans::XPropertySet > xSeqProp( xTempValueSeq, uno::UNO_QUERY ); + OUString aRole; + if( xSeqProp.is()) + xSeqProp->getPropertyValue("Role") >>= aRole; + // "main" sequence + if( aRole == aLabelRole ) + { + xValuesSeq.set( xTempValueSeq ); + xLabelSeq.set( aSeqCnt[nSeqIdx]->getLabel()); + nMainSequenceIndex = nSeqIdx; + } + } + sal_Int32 nSequenceLength = (xTempValueSeq.is()? xTempValueSeq->getData().getLength() : sal_Int32(0)); + if( nSeriesLength < nSequenceLength ) + nSeriesLength = nSequenceLength; + } + + // have found the main sequence, then xValuesSeq and + // xLabelSeq contain those. Otherwise both are empty + { + FSHelperPtr pFS = GetFS(); + + pFS->startElement(FSNS(XML_c, XML_ser)); + + // TODO: idx and order + pFS->singleElement( FSNS( XML_c, XML_idx ), + XML_val, OString::number(mnSeriesCount) ); + pFS->singleElement( FSNS( XML_c, XML_order ), + XML_val, OString::number(mnSeriesCount++) ); + + // export label + if( xLabelSeq.is() ) + exportSeriesText( xLabelSeq ); + + Reference<XPropertySet> xPropSet(xDataSeries, UNO_QUERY_THROW); + if( GetProperty( xPropSet, "AttachedAxisIndex") ) + { + sal_Int32 nLocalAttachedAxis = 0; + mAny >>= nLocalAttachedAxis; + rPrimaryAxes = isPrimaryAxes(nLocalAttachedAxis); + } + + // export shape properties + Reference< XPropertySet > xOldPropSet = SchXMLSeriesHelper::createOldAPISeriesPropertySet( + rSeries, getModel() ); + if( xOldPropSet.is() ) + { + exportShapeProps( xOldPropSet ); + } + + switch( eChartType ) + { + case chart::TYPEID_BUBBLE: + case chart::TYPEID_HORBAR: + case chart::TYPEID_BAR: + { + pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0"); + } + break; + case chart::TYPEID_LINE: + { + exportMarker(xOldPropSet); + break; + } + case chart::TYPEID_PIE: + case chart::TYPEID_DOUGHNUT: + { + if( xOldPropSet.is() && GetProperty( xOldPropSet, "SegmentOffset") ) + { + sal_Int32 nOffset = 0; + mAny >>= nOffset; + pFS->singleElement( FSNS( XML_c, XML_explosion ), + XML_val, OString::number( nOffset ) ); + } + break; + } + case chart::TYPEID_SCATTER: + { + exportMarker(xOldPropSet); + break; + } + case chart::TYPEID_RADARLINE: + { + exportMarker(xOldPropSet); + break; + } + } + + // export data points + exportDataPoints( uno::Reference< beans::XPropertySet >( rSeries, uno::UNO_QUERY ), nSeriesLength, eChartType ); + + DataLabelsRange aDLblsRange; + // export data labels + exportDataLabels(rSeries, nSeriesLength, eChartType, aDLblsRange); + + exportTrendlines( rSeries ); + + if( eChartType != chart::TYPEID_PIE && + eChartType != chart::TYPEID_RADARLINE ) + { + //export error bars here + Reference< XPropertySet > xSeriesPropSet( xSource, uno::UNO_QUERY ); + Reference< XPropertySet > xErrorBarYProps; + xSeriesPropSet->getPropertyValue("ErrorBarY") >>= xErrorBarYProps; + if(xErrorBarYProps.is()) + exportErrorBar(xErrorBarYProps, true); + if (eChartType != chart::TYPEID_BAR && + eChartType != chart::TYPEID_HORBAR) + { + Reference< XPropertySet > xErrorBarXProps; + xSeriesPropSet->getPropertyValue("ErrorBarX") >>= xErrorBarXProps; + if(xErrorBarXProps.is()) + exportErrorBar(xErrorBarXProps, false); + } + } + + // export categories + if( eChartType != chart::TYPEID_SCATTER && eChartType != chart::TYPEID_BUBBLE && mxCategoriesValues.is() ) + exportSeriesCategory( mxCategoriesValues ); + + if( (eChartType == chart::TYPEID_SCATTER) + || (eChartType == chart::TYPEID_BUBBLE) ) + { + // export xVal + Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, "values-x" ) ); + if( xSequence.is() ) + { + Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() ); + if( xValues.is() ) + exportSeriesValues( xValues, XML_xVal ); + } + else if( mxCategoriesValues.is() ) + exportSeriesCategory( mxCategoriesValues, XML_xVal ); + } + + if( eChartType == chart::TYPEID_BUBBLE ) + { + // export yVal + Reference< chart2::data::XLabeledDataSequence > xSequence( lcl_getDataSequenceByRole( aSeqCnt, "values-y" ) ); + if( xSequence.is() ) + { + Reference< chart2::data::XDataSequence > xValues( xSequence->getValues() ); + if( xValues.is() ) + exportSeriesValues( xValues, XML_yVal ); + } + } + + // export values + if( xValuesSeq.is() ) + { + sal_Int32 nYValueType = XML_val; + if( eChartType == chart::TYPEID_SCATTER ) + nYValueType = XML_yVal; + else if( eChartType == chart::TYPEID_BUBBLE ) + nYValueType = XML_bubbleSize; + exportSeriesValues( xValuesSeq, nYValueType ); + } + + if( eChartType == chart::TYPEID_SCATTER + || eChartType == chart::TYPEID_LINE ) + exportSmooth(); + + // tdf103988: "corrupted" files with Bubble chart opening in MSO + if( eChartType == chart::TYPEID_BUBBLE ) + pFS->singleElement(FSNS(XML_c, XML_bubble3D), XML_val, "0"); + + if (!aDLblsRange.empty()) + writeDataLabelsRange(pFS, GetFB(), aDLblsRange); + + pFS->endElement( FSNS( XML_c, XML_ser ) ); + } + } + } + } +} + +void ChartExport::exportCandleStickSeries( + const Sequence< Reference< chart2::XDataSeries > > & aSeriesSeq, + bool& rPrimaryAxes) +{ + for( const Reference< chart2::XDataSeries >& xSeries : aSeriesSeq ) + { + rPrimaryAxes = lcl_isSeriesAttachedToFirstAxis(xSeries); + + Reference< chart2::data::XDataSource > xSource( xSeries, uno::UNO_QUERY ); + if( xSource.is()) + { + // export series in correct order (as we don't store roles) + // with japanese candlesticks: open, low, high, close + // otherwise: low, high, close + Sequence< Reference< chart2::data::XLabeledDataSequence > > aSeqCnt( + xSource->getDataSequences()); + + const char* sSeries[] = {"values-first","values-max","values-min","values-last",nullptr}; + + for( sal_Int32 idx = 0; sSeries[idx] != nullptr ; idx++ ) + { + Reference< chart2::data::XLabeledDataSequence > xLabeledSeq( lcl_getDataSequenceByRole( aSeqCnt, OUString::createFromAscii(sSeries[idx]) ) ); + if( xLabeledSeq.is()) + { + Reference< chart2::data::XDataSequence > xLabelSeq( xLabeledSeq->getLabel()); + Reference< chart2::data::XDataSequence > xValueSeq( xLabeledSeq->getValues()); + { + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_ser)); + + // TODO: idx and order + // idx attribute should start from 1 and not from 0. + pFS->singleElement( FSNS( XML_c, XML_idx ), + XML_val, OString::number(idx+1) ); + pFS->singleElement( FSNS( XML_c, XML_order ), + XML_val, OString::number(idx+1) ); + + // export label + if( xLabelSeq.is() ) + exportSeriesText( xLabelSeq ); + + // TODO:export shape properties + + // export categories + if( mxCategoriesValues.is() ) + exportSeriesCategory( mxCategoriesValues ); + + // export values + if( xValueSeq.is() ) + exportSeriesValues( xValueSeq ); + + pFS->endElement( FSNS( XML_c, XML_ser ) ); + } + } + } + } + } +} + +void ChartExport::exportSeriesText( const Reference< chart2::data::XDataSequence > & xValueSeq ) +{ + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_tx)); + + OUString aCellRange = xValueSeq->getSourceRangeRepresentation(); + aCellRange = parseFormula( aCellRange ); + pFS->startElement(FSNS(XML_c, XML_strRef)); + + pFS->startElement(FSNS(XML_c, XML_f)); + pFS->writeEscaped( aCellRange ); + pFS->endElement( FSNS( XML_c, XML_f ) ); + + OUString aLabelString = lcl_flattenStringSequence(lcl_getLabelSequence(xValueSeq)); + pFS->startElement(FSNS(XML_c, XML_strCache)); + pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, "1"); + pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, "0"); + pFS->startElement(FSNS(XML_c, XML_v)); + pFS->writeEscaped( aLabelString ); + pFS->endElement( FSNS( XML_c, XML_v ) ); + pFS->endElement( FSNS( XML_c, XML_pt ) ); + pFS->endElement( FSNS( XML_c, XML_strCache ) ); + pFS->endElement( FSNS( XML_c, XML_strRef ) ); + pFS->endElement( FSNS( XML_c, XML_tx ) ); +} + +void ChartExport::exportSeriesCategory( const Reference< chart2::data::XDataSequence > & xValueSeq, sal_Int32 nValueType ) +{ + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, nValueType)); + + OUString aCellRange = xValueSeq.is() ? xValueSeq->getSourceRangeRepresentation() : OUString(); + const Sequence< Sequence< OUString >> aFinalSplitSource = (nValueType == XML_cat) ? getSplitCategoriesList(aCellRange) : Sequence< Sequence< OUString>>(0); + aCellRange = parseFormula( aCellRange ); + + if(aFinalSplitSource.getLength() > 1) + { + // export multi level category axis labels + pFS->startElement(FSNS(XML_c, XML_multiLvlStrRef)); + + pFS->startElement(FSNS(XML_c, XML_f)); + pFS->writeEscaped(aCellRange); + pFS->endElement(FSNS(XML_c, XML_f)); + + pFS->startElement(FSNS(XML_c, XML_multiLvlStrCache)); + pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(aFinalSplitSource[0].getLength())); + for(const auto& rSeq : aFinalSplitSource) + { + pFS->startElement(FSNS(XML_c, XML_lvl)); + for(sal_Int32 j = 0; j < rSeq.getLength(); j++) + { + if(!rSeq[j].isEmpty()) + { + pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(j)); + pFS->startElement(FSNS(XML_c, XML_v)); + pFS->writeEscaped(rSeq[j]); + pFS->endElement(FSNS(XML_c, XML_v)); + pFS->endElement(FSNS(XML_c, XML_pt)); + } + } + pFS->endElement(FSNS(XML_c, XML_lvl)); + } + + pFS->endElement(FSNS(XML_c, XML_multiLvlStrCache)); + pFS->endElement(FSNS(XML_c, XML_multiLvlStrRef)); + } + else + { + // export single category axis labels + bool bWriteDateCategories = mbHasDateCategories && (nValueType == XML_cat); + OUString aNumberFormatString; + if (bWriteDateCategories) + { + Reference< css::chart::XAxisXSupplier > xAxisXSupp( mxDiagram, uno::UNO_QUERY ); + if( xAxisXSupp.is()) + { + Reference< XPropertySet > xAxisProp = xAxisXSupp->getXAxis(); + if (GetProperty(xAxisProp, "NumberFormat")) + { + sal_Int32 nKey = 0; + mAny >>= nKey; + aNumberFormatString = getNumberFormatCode(nKey); + } + } + if (aNumberFormatString.isEmpty()) + bWriteDateCategories = false; + } + + pFS->startElement(FSNS(XML_c, bWriteDateCategories ? XML_numRef : XML_strRef)); + + pFS->startElement(FSNS(XML_c, XML_f)); + pFS->writeEscaped(aCellRange); + pFS->endElement(FSNS(XML_c, XML_f)); + + ::std::vector< OUString > aCategories; + lcl_fillCategoriesIntoStringVector(xValueSeq, aCategories); + sal_Int32 ptCount = aCategories.size(); + pFS->startElement(FSNS(XML_c, bWriteDateCategories ? XML_numCache : XML_strCache)); + if (bWriteDateCategories) + { + pFS->startElement(FSNS(XML_c, XML_formatCode)); + pFS->writeEscaped(aNumberFormatString); + pFS->endElement(FSNS(XML_c, XML_formatCode)); + } + + pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(ptCount)); + for (sal_Int32 i = 0; i < ptCount; i++) + { + pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(i)); + pFS->startElement(FSNS(XML_c, XML_v)); + pFS->writeEscaped(aCategories[i]); + pFS->endElement(FSNS(XML_c, XML_v)); + pFS->endElement(FSNS(XML_c, XML_pt)); + } + + pFS->endElement(FSNS(XML_c, bWriteDateCategories ? XML_numCache : XML_strCache)); + pFS->endElement(FSNS(XML_c, bWriteDateCategories ? XML_numRef : XML_strRef)); + } + + pFS->endElement( FSNS( XML_c, nValueType ) ); +} + +void ChartExport::exportSeriesValues( const Reference< chart2::data::XDataSequence > & xValueSeq, sal_Int32 nValueType ) +{ + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, nValueType)); + + OUString aCellRange = xValueSeq.is() ? xValueSeq->getSourceRangeRepresentation() : OUString(); + aCellRange = parseFormula( aCellRange ); + // TODO: need to handle XML_multiLvlStrRef according to aCellRange + pFS->startElement(FSNS(XML_c, XML_numRef)); + + pFS->startElement(FSNS(XML_c, XML_f)); + pFS->writeEscaped( aCellRange ); + pFS->endElement( FSNS( XML_c, XML_f ) ); + + ::std::vector< double > aValues = lcl_getAllValuesFromSequence( xValueSeq ); + sal_Int32 ptCount = aValues.size(); + pFS->startElement(FSNS(XML_c, XML_numCache)); + pFS->startElement(FSNS(XML_c, XML_formatCode)); + OUString sNumberFormatString("General"); + const sal_Int32 nKey = xValueSeq.is() ? xValueSeq->getNumberFormatKeyByIndex(-1) : 0; + if (nKey > 0) + sNumberFormatString = getNumberFormatCode(nKey); + pFS->writeEscaped(sNumberFormatString); + pFS->endElement( FSNS( XML_c, XML_formatCode ) ); + pFS->singleElement(FSNS(XML_c, XML_ptCount), XML_val, OString::number(ptCount)); + + for( sal_Int32 i = 0; i < ptCount; i++ ) + { + if (!std::isnan(aValues[i])) + { + pFS->startElement(FSNS(XML_c, XML_pt), XML_idx, OString::number(i)); + pFS->startElement(FSNS(XML_c, XML_v)); + pFS->write(aValues[i]); + pFS->endElement(FSNS(XML_c, XML_v)); + pFS->endElement(FSNS(XML_c, XML_pt)); + } + } + + pFS->endElement( FSNS( XML_c, XML_numCache ) ); + pFS->endElement( FSNS( XML_c, XML_numRef ) ); + pFS->endElement( FSNS( XML_c, nValueType ) ); +} + +void ChartExport::exportShapeProps( const Reference< XPropertySet >& xPropSet ) +{ + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_spPr)); + + exportFill( xPropSet ); + WriteOutline( xPropSet, getModel() ); + + pFS->endElement( FSNS( XML_c, XML_spPr ) ); +} + +void ChartExport::exportTextProps(const Reference<XPropertySet>& xPropSet) +{ + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_txPr)); + + sal_Int32 nRotation = 0; + const char* textWordWrap = nullptr; + + if (auto xServiceInfo = uno::Reference<lang::XServiceInfo>(xPropSet, uno::UNO_QUERY)) + { + double fMultiplier = 0.0; + // We have at least two possible units of returned value: degrees (e.g., for data labels), + // and 100ths of degree (e.g., for axes labels). The latter is returned as an Any wrapping + // a sal_Int32 value (see WrappedTextRotationProperty::convertInnerToOuterValue), while + // the former is double. So we could test the contained type to decide which multiplier to + // use. But testing the service info should be more robust. + if (xServiceInfo->supportsService("com.sun.star.chart.ChartAxis")) + fMultiplier = -600.0; + else if (xServiceInfo->supportsService("com.sun.star.chart2.DataSeries") || xServiceInfo->supportsService("com.sun.star.chart2.DataPointProperties")) + { + fMultiplier = -60000.0; + bool bTextWordWrap = false; + if ((xPropSet->getPropertyValue("TextWordWrap") >>= bTextWordWrap) && bTextWordWrap) + textWordWrap = "square"; + else + textWordWrap = "none"; + } + + if (fMultiplier) + { + double fTextRotation = 0.0; + uno::Any aAny = xPropSet->getPropertyValue("TextRotation"); + if (aAny.hasValue() && (aAny >>= fTextRotation)) + { + fTextRotation *= fMultiplier; + // The MS Office UI allows values only in range of [-90,90]. + if (fTextRotation < -5400000.0 && fTextRotation > -16200000.0) + { + // Reflect the angle if the value is between 90° and 270° + fTextRotation += 10800000.0; + } + else if (fTextRotation <= -16200000.0) + { + fTextRotation += 21600000.0; + } + nRotation = std::round(fTextRotation); + } + } + } + + if (nRotation) + pFS->singleElement(FSNS(XML_a, XML_bodyPr), XML_rot, OString::number(nRotation), XML_wrap, textWordWrap); + else + pFS->singleElement(FSNS(XML_a, XML_bodyPr), XML_wrap, textWordWrap); + + pFS->singleElement(FSNS(XML_a, XML_lstStyle)); + + pFS->startElement(FSNS(XML_a, XML_p)); + pFS->startElement(FSNS(XML_a, XML_pPr)); + + WriteRunProperties(xPropSet, false, XML_defRPr, true, o3tl::temporary(false), + o3tl::temporary(sal_Int32())); + + pFS->endElement(FSNS(XML_a, XML_pPr)); + pFS->endElement(FSNS(XML_a, XML_p)); + pFS->endElement(FSNS(XML_c, XML_txPr)); +} + +void ChartExport::InitPlotArea( ) +{ + Reference< XPropertySet > xDiagramProperties (mxDiagram, uno::UNO_QUERY); + + // Check for supported services and then the properties provided by this service. + Reference<lang::XServiceInfo> xServiceInfo (mxDiagram, uno::UNO_QUERY); + if (xServiceInfo.is()) + { + if (xServiceInfo->supportsService("com.sun.star.chart.ChartAxisZSupplier")) + { + xDiagramProperties->getPropertyValue("HasZAxis") >>= mbHasZAxis; + } + } + + xDiagramProperties->getPropertyValue("Dim3D") >>= mbIs3DChart; + + if( mbHasCategoryLabels && mxNewDiagram.is()) + { + Reference< chart2::data::XLabeledDataSequence > xCategories( lcl_getCategories( mxNewDiagram, mbHasDateCategories ) ); + if( xCategories.is() ) + { + mxCategoriesValues.set( xCategories->getValues() ); + } + } +} + +void ChartExport::exportAxes( ) +{ + sal_Int32 nSize = maAxes.size(); + // let's export the axis types in the right order + for ( sal_Int32 nSortIdx = AXIS_PRIMARY_X; nSortIdx <= AXIS_SECONDARY_Y; nSortIdx++ ) + { + for ( sal_Int32 nIdx = 0; nIdx < nSize; nIdx++ ) + { + if (nSortIdx == maAxes[nIdx].nAxisType) + exportAxis( maAxes[nIdx] ); + } + } +} + +namespace { + +sal_Int32 getXAxisTypeByChartType(sal_Int32 eChartType) +{ + if( (eChartType == chart::TYPEID_SCATTER) + || (eChartType == chart::TYPEID_BUBBLE) ) + return XML_valAx; + else if( eChartType == chart::TYPEID_STOCK ) + return XML_dateAx; + + return XML_catAx; +} + +sal_Int32 getRealXAxisType(sal_Int32 nAxisType) +{ + if( nAxisType == chart2::AxisType::CATEGORY ) + return XML_catAx; + else if( nAxisType == chart2::AxisType::DATE ) + return XML_dateAx; + else if( nAxisType == chart2::AxisType::SERIES ) + return XML_serAx; + + return XML_valAx; +} + +} + +void ChartExport::exportAxis(const AxisIdPair& rAxisIdPair) +{ + // get some properties from document first + bool bHasXAxisTitle = false, + bHasYAxisTitle = false, + bHasZAxisTitle = false, + bHasSecondaryXAxisTitle = false, + bHasSecondaryYAxisTitle = false; + bool bHasXAxisMajorGrid = false, + bHasXAxisMinorGrid = false, + bHasYAxisMajorGrid = false, + bHasYAxisMinorGrid = false, + bHasZAxisMajorGrid = false, + bHasZAxisMinorGrid = false; + + Reference< XPropertySet > xDiagramProperties (mxDiagram, uno::UNO_QUERY); + + xDiagramProperties->getPropertyValue("HasXAxisTitle") >>= bHasXAxisTitle; + xDiagramProperties->getPropertyValue("HasYAxisTitle") >>= bHasYAxisTitle; + xDiagramProperties->getPropertyValue("HasZAxisTitle") >>= bHasZAxisTitle; + xDiagramProperties->getPropertyValue("HasSecondaryXAxisTitle") >>= bHasSecondaryXAxisTitle; + xDiagramProperties->getPropertyValue("HasSecondaryYAxisTitle") >>= bHasSecondaryYAxisTitle; + + xDiagramProperties->getPropertyValue("HasXAxisGrid") >>= bHasXAxisMajorGrid; + xDiagramProperties->getPropertyValue("HasYAxisGrid") >>= bHasYAxisMajorGrid; + xDiagramProperties->getPropertyValue("HasZAxisGrid") >>= bHasZAxisMajorGrid; + + xDiagramProperties->getPropertyValue("HasXAxisHelpGrid") >>= bHasXAxisMinorGrid; + xDiagramProperties->getPropertyValue("HasYAxisHelpGrid") >>= bHasYAxisMinorGrid; + xDiagramProperties->getPropertyValue("HasZAxisHelpGrid") >>= bHasZAxisMinorGrid; + + Reference< XPropertySet > xAxisProp; + Reference< drawing::XShape > xAxisTitle; + Reference< beans::XPropertySet > xMajorGrid; + Reference< beans::XPropertySet > xMinorGrid; + sal_Int32 nAxisType = XML_catAx; + const char* sAxPos = nullptr; + + switch( rAxisIdPair.nAxisType ) + { + case AXIS_PRIMARY_X: + { + Reference< css::chart::XAxisXSupplier > xAxisXSupp( mxDiagram, uno::UNO_QUERY ); + if( xAxisXSupp.is()) + xAxisProp = xAxisXSupp->getXAxis(); + if( bHasXAxisTitle ) + xAxisTitle = xAxisXSupp->getXAxisTitle(); + if( bHasXAxisMajorGrid ) + xMajorGrid = xAxisXSupp->getXMainGrid(); + if( bHasXAxisMinorGrid ) + xMinorGrid = xAxisXSupp->getXHelpGrid(); + + nAxisType = lcl_getCategoryAxisType(mxNewDiagram, 0, 0); + if( nAxisType != -1 ) + nAxisType = getRealXAxisType(nAxisType); + else + nAxisType = getXAxisTypeByChartType( getChartType() ); + // FIXME: axPos, need to check axis direction + sAxPos = "b"; + break; + } + case AXIS_PRIMARY_Y: + { + Reference< css::chart::XAxisYSupplier > xAxisYSupp( mxDiagram, uno::UNO_QUERY ); + if( xAxisYSupp.is()) + xAxisProp = xAxisYSupp->getYAxis(); + if( bHasYAxisTitle ) + xAxisTitle = xAxisYSupp->getYAxisTitle(); + if( bHasYAxisMajorGrid ) + xMajorGrid = xAxisYSupp->getYMainGrid(); + if( bHasYAxisMinorGrid ) + xMinorGrid = xAxisYSupp->getYHelpGrid(); + + nAxisType = XML_valAx; + // FIXME: axPos, need to check axis direction + sAxPos = "l"; + break; + } + case AXIS_PRIMARY_Z: + { + Reference< css::chart::XAxisZSupplier > xAxisZSupp( mxDiagram, uno::UNO_QUERY ); + if( xAxisZSupp.is()) + xAxisProp = xAxisZSupp->getZAxis(); + if( bHasZAxisTitle ) + xAxisTitle = xAxisZSupp->getZAxisTitle(); + if( bHasZAxisMajorGrid ) + xMajorGrid = xAxisZSupp->getZMainGrid(); + if( bHasZAxisMinorGrid ) + xMinorGrid = xAxisZSupp->getZHelpGrid(); + + sal_Int32 eChartType = getChartType( ); + if( (eChartType == chart::TYPEID_SCATTER) + || (eChartType == chart::TYPEID_BUBBLE) ) + nAxisType = XML_valAx; + else if( eChartType == chart::TYPEID_STOCK ) + nAxisType = XML_dateAx; + else if( eChartType == chart::TYPEID_BAR || eChartType == chart::TYPEID_AREA ) + nAxisType = XML_serAx; + // FIXME: axPos, need to check axis direction + sAxPos = "b"; + break; + } + case AXIS_SECONDARY_X: + { + Reference< css::chart::XTwoAxisXSupplier > xAxisTwoXSupp( mxDiagram, uno::UNO_QUERY ); + if( xAxisTwoXSupp.is()) + xAxisProp = xAxisTwoXSupp->getSecondaryXAxis(); + if( bHasSecondaryXAxisTitle ) + { + Reference< css::chart::XSecondAxisTitleSupplier > xAxisSupp( mxDiagram, uno::UNO_QUERY ); + xAxisTitle = xAxisSupp->getSecondXAxisTitle(); + } + + nAxisType = lcl_getCategoryAxisType(mxNewDiagram, 0, 1); + if( nAxisType != -1 ) + nAxisType = getRealXAxisType(nAxisType); + else + nAxisType = getXAxisTypeByChartType( getChartType() ); + // FIXME: axPos, need to check axis direction + sAxPos = "t"; + break; + } + case AXIS_SECONDARY_Y: + { + Reference< css::chart::XTwoAxisYSupplier > xAxisTwoYSupp( mxDiagram, uno::UNO_QUERY ); + if( xAxisTwoYSupp.is()) + xAxisProp = xAxisTwoYSupp->getSecondaryYAxis(); + if( bHasSecondaryYAxisTitle ) + { + Reference< css::chart::XSecondAxisTitleSupplier > xAxisSupp( mxDiagram, uno::UNO_QUERY ); + xAxisTitle = xAxisSupp->getSecondYAxisTitle(); + } + + nAxisType = XML_valAx; + // FIXME: axPos, need to check axis direction + sAxPos = "r"; + break; + } + } + + _exportAxis(xAxisProp, xAxisTitle, xMajorGrid, xMinorGrid, nAxisType, sAxPos, rAxisIdPair); +} + +void ChartExport::_exportAxis( + const Reference< XPropertySet >& xAxisProp, + const Reference< drawing::XShape >& xAxisTitle, + const Reference< XPropertySet >& xMajorGrid, + const Reference< XPropertySet >& xMinorGrid, + sal_Int32 nAxisType, + const char* sAxisPos, + const AxisIdPair& rAxisIdPair ) +{ + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, nAxisType)); + pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(rAxisIdPair.nAxisId)); + + pFS->startElement(FSNS(XML_c, XML_scaling)); + + // logBase, min, max + if(GetProperty( xAxisProp, "Logarithmic" ) ) + { + bool bLogarithmic = false; + mAny >>= bLogarithmic; + if( bLogarithmic ) + { + // default value is 10? + pFS->singleElement(FSNS(XML_c, XML_logBase), XML_val, OString::number(10)); + } + } + + // orientation: minMax, maxMin + bool bReverseDirection = false; + if(GetProperty( xAxisProp, "ReverseDirection" ) ) + mAny >>= bReverseDirection; + + const char* orientation = bReverseDirection ? "maxMin":"minMax"; + pFS->singleElement(FSNS(XML_c, XML_orientation), XML_val, orientation); + + bool bAutoMax = false; + if(GetProperty( xAxisProp, "AutoMax" ) ) + mAny >>= bAutoMax; + + if( !bAutoMax && (GetProperty( xAxisProp, "Max" ) ) ) + { + double dMax = 0; + mAny >>= dMax; + pFS->singleElement(FSNS(XML_c, XML_max), XML_val, OString::number(dMax)); + } + + bool bAutoMin = false; + if(GetProperty( xAxisProp, "AutoMin" ) ) + mAny >>= bAutoMin; + + if( !bAutoMin && (GetProperty( xAxisProp, "Min" ) ) ) + { + double dMin = 0; + mAny >>= dMin; + pFS->singleElement(FSNS(XML_c, XML_min), XML_val, OString::number(dMin)); + } + + pFS->endElement( FSNS( XML_c, XML_scaling ) ); + + bool bVisible = true; + if( xAxisProp.is() ) + { + xAxisProp->getPropertyValue("Visible") >>= bVisible; + } + + // only export each axis only once non-deleted + auto aItInsertedPair = maExportedAxis.insert(rAxisIdPair.nAxisType); + bool bDeleted = !aItInsertedPair.second; + + pFS->singleElement(FSNS(XML_c, XML_delete), XML_val, !bDeleted && bVisible ? "0" : "1"); + + // FIXME: axPos, need to check the property "ReverseDirection" + pFS->singleElement(FSNS(XML_c, XML_axPos), XML_val, sAxisPos); + // major grid line + if( xMajorGrid.is()) + { + pFS->startElement(FSNS(XML_c, XML_majorGridlines)); + exportShapeProps( xMajorGrid ); + pFS->endElement( FSNS( XML_c, XML_majorGridlines ) ); + } + + // minor grid line + if( xMinorGrid.is()) + { + pFS->startElement(FSNS(XML_c, XML_minorGridlines)); + exportShapeProps( xMinorGrid ); + pFS->endElement( FSNS( XML_c, XML_minorGridlines ) ); + } + + // title + if( xAxisTitle.is() ) + exportTitle( xAxisTitle ); + + bool bLinkedNumFmt = true; + if (GetProperty(xAxisProp, "LinkNumberFormatToSource")) + mAny >>= bLinkedNumFmt; + + OUString aNumberFormatString("General"); + if (GetProperty(xAxisProp, "NumberFormat")) + { + sal_Int32 nKey = 0; + mAny >>= nKey; + aNumberFormatString = getNumberFormatCode(nKey); + } + + pFS->singleElement(FSNS(XML_c, XML_numFmt), + XML_formatCode, aNumberFormatString, + XML_sourceLinked, bLinkedNumFmt ? "1" : "0"); + + // majorTickMark + sal_Int32 nValue = 0; + if(GetProperty( xAxisProp, "Marks" ) ) + { + mAny >>= nValue; + bool bInner = nValue & css::chart::ChartAxisMarks::INNER; + bool bOuter = nValue & css::chart::ChartAxisMarks::OUTER; + const char* majorTickMark = nullptr; + if( bInner && bOuter ) + majorTickMark = "cross"; + else if( bInner ) + majorTickMark = "in"; + else if( bOuter ) + majorTickMark = "out"; + else + majorTickMark = "none"; + pFS->singleElement(FSNS(XML_c, XML_majorTickMark), XML_val, majorTickMark); + } + // minorTickMark + if(GetProperty( xAxisProp, "HelpMarks" ) ) + { + mAny >>= nValue; + bool bInner = nValue & css::chart::ChartAxisMarks::INNER; + bool bOuter = nValue & css::chart::ChartAxisMarks::OUTER; + const char* minorTickMark = nullptr; + if( bInner && bOuter ) + minorTickMark = "cross"; + else if( bInner ) + minorTickMark = "in"; + else if( bOuter ) + minorTickMark = "out"; + else + minorTickMark = "none"; + pFS->singleElement(FSNS(XML_c, XML_minorTickMark), XML_val, minorTickMark); + } + // tickLblPos + const char* sTickLblPos = nullptr; + bool bDisplayLabel = true; + if(GetProperty( xAxisProp, "DisplayLabels" ) ) + mAny >>= bDisplayLabel; + if( bDisplayLabel && (GetProperty( xAxisProp, "LabelPosition" ) ) ) + { + css::chart::ChartAxisLabelPosition eLabelPosition = css::chart::ChartAxisLabelPosition_NEAR_AXIS; + mAny >>= eLabelPosition; + switch( eLabelPosition ) + { + case css::chart::ChartAxisLabelPosition_NEAR_AXIS: + case css::chart::ChartAxisLabelPosition_NEAR_AXIS_OTHER_SIDE: + sTickLblPos = "nextTo"; + break; + case css::chart::ChartAxisLabelPosition_OUTSIDE_START: + sTickLblPos = "low"; + break; + case css::chart::ChartAxisLabelPosition_OUTSIDE_END: + sTickLblPos = "high"; + break; + default: + sTickLblPos = "nextTo"; + break; + } + } + else + { + sTickLblPos = "none"; + } + pFS->singleElement(FSNS(XML_c, XML_tickLblPos), XML_val, sTickLblPos); + + // shape properties + exportShapeProps( xAxisProp ); + + exportTextProps(xAxisProp); + + pFS->singleElement(FSNS(XML_c, XML_crossAx), XML_val, OString::number(rAxisIdPair.nCrossAx)); + + // crosses & crossesAt + bool bCrossesValue = false; + const char* sCrosses = nullptr; + // do not export the CrossoverPosition/CrossoverValue, if the axis is deleted and not visible + if( GetProperty( xAxisProp, "CrossoverPosition" ) && !bDeleted && bVisible ) + { + css::chart::ChartAxisPosition ePosition( css::chart::ChartAxisPosition_ZERO ); + mAny >>= ePosition; + switch( ePosition ) + { + case css::chart::ChartAxisPosition_START: + sCrosses = "min"; + break; + case css::chart::ChartAxisPosition_END: + sCrosses = "max"; + break; + case css::chart::ChartAxisPosition_ZERO: + sCrosses = "autoZero"; + break; + default: + bCrossesValue = true; + break; + } + } + + if( bCrossesValue && GetProperty( xAxisProp, "CrossoverValue" ) ) + { + double dValue = 0; + mAny >>= dValue; + pFS->singleElement(FSNS(XML_c, XML_crossesAt), XML_val, OString::number(dValue)); + } + else + { + if(sCrosses) + { + pFS->singleElement(FSNS(XML_c, XML_crosses), XML_val, sCrosses); + } + } + + if( ( nAxisType == XML_catAx ) + || ( nAxisType == XML_dateAx ) ) + { + // FIXME: seems not support? use default value, + const char* const isAuto = "1"; + pFS->singleElement(FSNS(XML_c, XML_auto), XML_val, isAuto); + + if( nAxisType == XML_catAx ) + { + // FIXME: seems not support? lblAlgn + const char* const sLblAlgn = "ctr"; + pFS->singleElement(FSNS(XML_c, XML_lblAlgn), XML_val, sLblAlgn); + } + + // FIXME: seems not support? lblOffset + pFS->singleElement(FSNS(XML_c, XML_lblOffset), XML_val, OString::number(100)); + + // export baseTimeUnit, majorTimeUnit, minorTimeUnit of Date axis + if( nAxisType == XML_dateAx ) + { + sal_Int32 nAxisIndex = -1; + if( rAxisIdPair.nAxisType == AXIS_PRIMARY_X ) + nAxisIndex = 0; + else if( rAxisIdPair.nAxisType == AXIS_SECONDARY_X ) + nAxisIndex = 1; + + cssc::TimeIncrement aTimeIncrement = lcl_getDateTimeIncrement( mxNewDiagram, nAxisIndex ); + sal_Int32 nTimeResolution = css::chart::TimeUnit::DAY; + if( aTimeIncrement.TimeResolution >>= nTimeResolution ) + pFS->singleElement(FSNS(XML_c, XML_baseTimeUnit), XML_val, lclGetTimeUnitToken(nTimeResolution)); + + cssc::TimeInterval aInterval; + if( aTimeIncrement.MajorTimeInterval >>= aInterval ) + { + pFS->singleElement(FSNS(XML_c, XML_majorUnit), XML_val, OString::number(aInterval.Number)); + pFS->singleElement(FSNS(XML_c, XML_majorTimeUnit), XML_val, lclGetTimeUnitToken(aInterval.TimeUnit)); + } + if( aTimeIncrement.MinorTimeInterval >>= aInterval ) + { + pFS->singleElement(FSNS(XML_c, XML_minorUnit), XML_val, OString::number(aInterval.Number)); + pFS->singleElement(FSNS(XML_c, XML_minorTimeUnit), XML_val, lclGetTimeUnitToken(aInterval.TimeUnit)); + } + } + + // FIXME: seems not support? noMultiLvlLbl + pFS->singleElement(FSNS(XML_c, XML_noMultiLvlLbl), XML_val, OString::number(0)); + } + + // crossBetween + if( nAxisType == XML_valAx ) + { + if( lcl_isCategoryAxisShifted( mxNewDiagram )) + pFS->singleElement(FSNS(XML_c, XML_crossBetween), XML_val, "between"); + else + pFS->singleElement(FSNS(XML_c, XML_crossBetween), XML_val, "midCat"); + } + + // majorUnit + bool bAutoStepMain = false; + if(GetProperty( xAxisProp, "AutoStepMain" ) ) + mAny >>= bAutoStepMain; + + if( !bAutoStepMain && (GetProperty( xAxisProp, "StepMain" ) ) ) + { + double dMajorUnit = 0; + mAny >>= dMajorUnit; + pFS->singleElement(FSNS(XML_c, XML_majorUnit), XML_val, OString::number(dMajorUnit)); + } + // minorUnit + bool bAutoStepHelp = false; + if(GetProperty( xAxisProp, "AutoStepHelp" ) ) + mAny >>= bAutoStepHelp; + + if( !bAutoStepHelp && (GetProperty( xAxisProp, "StepHelp" ) ) ) + { + double dMinorUnit = 0; + mAny >>= dMinorUnit; + if( GetProperty( xAxisProp, "StepHelpCount" ) ) + { + sal_Int32 dMinorUnitCount = 0; + mAny >>= dMinorUnitCount; + // tdf#114168 Don't save minor unit if number of step help count is 5 (which is default for MS Excel), + // to allow proper .xlsx import. If minorUnit is set and majorUnit not, then it is impossible + // to calculate StepHelpCount. + if( dMinorUnitCount != 5 ) + { + pFS->singleElement( FSNS( XML_c, XML_minorUnit ), + XML_val, OString::number( dMinorUnit ) ); + } + } + } + + if( nAxisType == XML_valAx && GetProperty( xAxisProp, "DisplayUnits" ) ) + { + bool bDisplayUnits = false; + mAny >>= bDisplayUnits; + if(bDisplayUnits) + { + if(GetProperty( xAxisProp, "BuiltInUnit" )) + { + OUString aVal; + mAny >>= aVal; + if(!aVal.isEmpty()) + { + pFS->startElement(FSNS(XML_c, XML_dispUnits)); + + pFS->singleElement(FSNS(XML_c, XML_builtInUnit), XML_val, aVal); + + pFS->singleElement(FSNS( XML_c, XML_dispUnitsLbl )); + pFS->endElement( FSNS( XML_c, XML_dispUnits ) ); + } + } + } + } + + pFS->endElement( FSNS( XML_c, nAxisType ) ); +} + +namespace { + +struct LabelPlacementParam +{ + bool mbExport; + sal_Int32 meDefault; + + std::unordered_set<sal_Int32> maAllowedValues; + + LabelPlacementParam(bool bExport, sal_Int32 nDefault) : + mbExport(bExport), + meDefault(nDefault), + maAllowedValues( + { + css::chart::DataLabelPlacement::OUTSIDE, + css::chart::DataLabelPlacement::INSIDE, + css::chart::DataLabelPlacement::CENTER, + css::chart::DataLabelPlacement::NEAR_ORIGIN, + css::chart::DataLabelPlacement::TOP, + css::chart::DataLabelPlacement::BOTTOM, + css::chart::DataLabelPlacement::LEFT, + css::chart::DataLabelPlacement::RIGHT, + css::chart::DataLabelPlacement::AVOID_OVERLAP + } + ) + {} +}; + +const char* toOOXMLPlacement( sal_Int32 nPlacement ) +{ + switch (nPlacement) + { + case css::chart::DataLabelPlacement::OUTSIDE: return "outEnd"; + case css::chart::DataLabelPlacement::INSIDE: return "inEnd"; + case css::chart::DataLabelPlacement::CENTER: return "ctr"; + case css::chart::DataLabelPlacement::NEAR_ORIGIN: return "inBase"; + case css::chart::DataLabelPlacement::TOP: return "t"; + case css::chart::DataLabelPlacement::BOTTOM: return "b"; + case css::chart::DataLabelPlacement::LEFT: return "l"; + case css::chart::DataLabelPlacement::RIGHT: return "r"; + case css::chart::DataLabelPlacement::CUSTOM: + case css::chart::DataLabelPlacement::AVOID_OVERLAP: return "bestFit"; + default: + ; + } + + return "outEnd"; +} + +OUString getFieldTypeString( const chart2::DataPointCustomLabelFieldType aType ) +{ + switch (aType) + { + case chart2::DataPointCustomLabelFieldType_CATEGORYNAME: + return "CATEGORYNAME"; + + case chart2::DataPointCustomLabelFieldType_SERIESNAME: + return "SERIESNAME"; + + case chart2::DataPointCustomLabelFieldType_VALUE: + return "VALUE"; + + case chart2::DataPointCustomLabelFieldType_CELLREF: + return "CELLREF"; + + case chart2::DataPointCustomLabelFieldType_CELLRANGE: + return "CELLRANGE"; + + default: + break; + } + return OUString(); +} + +void writeRunProperties( ChartExport* pChartExport, Reference<XPropertySet> const & xPropertySet ) +{ + bool bDummy = false; + sal_Int32 nDummy; + pChartExport->WriteRunProperties(xPropertySet, false, XML_rPr, true, bDummy, nDummy); +} + +void writeCustomLabel( const FSHelperPtr& pFS, ChartExport* pChartExport, + const Sequence<Reference<chart2::XDataPointCustomLabelField>>& rCustomLabelFields, + sal_Int32 nLabelIndex, DataLabelsRange& rDLblsRange ) +{ + pFS->startElement(FSNS(XML_c, XML_tx)); + pFS->startElement(FSNS(XML_c, XML_rich)); + + // TODO: body properties? + pFS->singleElement(FSNS(XML_a, XML_bodyPr)); + + OUString sFieldType; + OUString sContent; + pFS->startElement(FSNS(XML_a, XML_p)); + + for (auto& rField : rCustomLabelFields) + { + Reference<XPropertySet> xPropertySet(rField, UNO_QUERY); + chart2::DataPointCustomLabelFieldType aType = rField->getFieldType(); + sFieldType.clear(); + sContent.clear(); + bool bNewParagraph = false; + + if (aType == chart2::DataPointCustomLabelFieldType_CELLRANGE && + rField->getDataLabelsRange()) + { + if (rDLblsRange.getRange().isEmpty()) + rDLblsRange.setRange(rField->getCellRange()); + + if (!rDLblsRange.hasLabel(nLabelIndex)) + rDLblsRange.setLabel(nLabelIndex, rField->getString()); + + sContent = "[CELLRANGE]"; + } + else + { + sContent = rField->getString(); + } + + if (aType == chart2::DataPointCustomLabelFieldType_NEWLINE) + bNewParagraph = true; + else if (aType != chart2::DataPointCustomLabelFieldType_TEXT) + sFieldType = getFieldTypeString(aType); + + if (bNewParagraph) + { + pFS->endElement(FSNS(XML_a, XML_p)); + pFS->startElement(FSNS(XML_a, XML_p)); + continue; + } + + if (sFieldType.isEmpty()) + { + // Normal text run + pFS->startElement(FSNS(XML_a, XML_r)); + writeRunProperties(pChartExport, xPropertySet); + + pFS->startElement(FSNS(XML_a, XML_t)); + pFS->writeEscaped(sContent); + pFS->endElement(FSNS(XML_a, XML_t)); + + pFS->endElement(FSNS(XML_a, XML_r)); + } + else + { + // Field + pFS->startElement(FSNS(XML_a, XML_fld), XML_id, rField->getGuid(), XML_type, + sFieldType); + writeRunProperties(pChartExport, xPropertySet); + + pFS->startElement(FSNS(XML_a, XML_t)); + pFS->writeEscaped(sContent); + pFS->endElement(FSNS(XML_a, XML_t)); + + pFS->endElement(FSNS(XML_a, XML_fld)); + } + } + + pFS->endElement(FSNS(XML_a, XML_p)); + pFS->endElement(FSNS(XML_c, XML_rich)); + pFS->endElement(FSNS(XML_c, XML_tx)); +} + +void writeLabelProperties( const FSHelperPtr& pFS, ChartExport* pChartExport, + const uno::Reference<beans::XPropertySet>& xPropSet, const LabelPlacementParam& rLabelParam, + sal_Int32 nLabelIndex, DataLabelsRange& rDLblsRange ) +{ + if (!xPropSet.is()) + return; + + chart2::DataPointLabel aLabel; + Sequence<Reference<chart2::XDataPointCustomLabelField>> aCustomLabelFields; + sal_Int32 nLabelBorderWidth = 0; + sal_Int32 nLabelBorderColor = 0x00FFFFFF; + sal_Int32 nLabelFillColor = -1; + + xPropSet->getPropertyValue("Label") >>= aLabel; + xPropSet->getPropertyValue("CustomLabelFields") >>= aCustomLabelFields; + xPropSet->getPropertyValue("LabelBorderWidth") >>= nLabelBorderWidth; + xPropSet->getPropertyValue("LabelBorderColor") >>= nLabelBorderColor; + xPropSet->getPropertyValue("LabelFillColor") >>= nLabelFillColor; + + if (nLabelBorderWidth > 0 || nLabelFillColor != -1) + { + pFS->startElement(FSNS(XML_c, XML_spPr)); + + if (nLabelFillColor != -1) + { + pFS->startElement(FSNS(XML_a, XML_solidFill)); + + OString aStr = OString::number(nLabelFillColor, 16).toAsciiUpperCase(); + pFS->singleElement(FSNS(XML_a, XML_srgbClr), XML_val, aStr); + + pFS->endElement(FSNS(XML_a, XML_solidFill)); + } + + if (nLabelBorderWidth > 0) + { + pFS->startElement(FSNS(XML_a, XML_ln), XML_w, + OString::number(convertHmmToEmu(nLabelBorderWidth))); + + if (nLabelBorderColor != -1) + { + pFS->startElement(FSNS(XML_a, XML_solidFill)); + + OString aStr = OString::number(nLabelBorderColor, 16).toAsciiUpperCase(); + pFS->singleElement(FSNS(XML_a, XML_srgbClr), XML_val, aStr); + + pFS->endElement(FSNS(XML_a, XML_solidFill)); + } + + pFS->endElement(FSNS(XML_a, XML_ln)); + } + + pFS->endElement(FSNS(XML_c, XML_spPr)); + } + + pChartExport->exportTextProps(xPropSet); + + if (aCustomLabelFields.hasElements()) + writeCustomLabel(pFS, pChartExport, aCustomLabelFields, nLabelIndex, rDLblsRange); + + if (rLabelParam.mbExport) + { + sal_Int32 nLabelPlacement = rLabelParam.meDefault; + if (xPropSet->getPropertyValue("LabelPlacement") >>= nLabelPlacement) + { + if (!rLabelParam.maAllowedValues.count(nLabelPlacement)) + nLabelPlacement = rLabelParam.meDefault; + pFS->singleElement(FSNS(XML_c, XML_dLblPos), XML_val, toOOXMLPlacement(nLabelPlacement)); + } + } + + pFS->singleElement(FSNS(XML_c, XML_showLegendKey), XML_val, ToPsz10(aLabel.ShowLegendSymbol)); + pFS->singleElement(FSNS(XML_c, XML_showVal), XML_val, ToPsz10(aLabel.ShowNumber)); + pFS->singleElement(FSNS(XML_c, XML_showCatName), XML_val, ToPsz10(aLabel.ShowCategoryName)); + pFS->singleElement(FSNS(XML_c, XML_showSerName), XML_val, ToPsz10(aLabel.ShowSeriesName)); + pFS->singleElement(FSNS(XML_c, XML_showPercent), XML_val, ToPsz10(aLabel.ShowNumberInPercent)); + + // Export the text "separator" if exists + uno::Any aAny = xPropSet->getPropertyValue("LabelSeparator"); + if( aAny.hasValue() ) + { + OUString nLabelSeparator; + aAny >>= nLabelSeparator; + pFS->startElement(FSNS(XML_c, XML_separator)); + pFS->writeEscaped( nLabelSeparator ); + pFS->endElement( FSNS( XML_c, XML_separator ) ); + } + + if (rDLblsRange.hasLabel(nLabelIndex)) + { + pFS->startElement(FSNS(XML_c, XML_extLst)); + pFS->startElement(FSNS(XML_c, XML_ext), XML_uri, + "{CE6537A1-D6FC-4f65-9D91-7224C49458BB}", FSNS(XML_xmlns, XML_c15), + pChartExport->GetFB()->getNamespaceURL(OOX_NS(c15))); + + pFS->singleElement(FSNS(XML_c15, XML_showDataLabelsRange), XML_val, "1"); + + pFS->endElement(FSNS(XML_c, XML_ext)); + pFS->endElement(FSNS(XML_c, XML_extLst)); + } +} + +} + +void ChartExport::exportDataLabels( + const uno::Reference<chart2::XDataSeries> & xSeries, sal_Int32 nSeriesLength, sal_Int32 eChartType, + DataLabelsRange& rDLblsRange) +{ + if (!xSeries.is() || nSeriesLength <= 0) + return; + + uno::Reference<beans::XPropertySet> xPropSet(xSeries, uno::UNO_QUERY); + if (!xPropSet.is()) + return; + + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_dLbls)); + + bool bLinkedNumFmt = true; + if (GetProperty(xPropSet, "LinkNumberFormatToSource")) + mAny >>= bLinkedNumFmt; + + chart2::DataPointLabel aLabel; + bool bLabelIsNumberFormat = true; + if( xPropSet->getPropertyValue("Label") >>= aLabel ) + bLabelIsNumberFormat = aLabel.ShowNumber; + + if (GetProperty(xPropSet, bLabelIsNumberFormat ? OUString("NumberFormat") : OUString("PercentageNumberFormat"))) + { + sal_Int32 nKey = 0; + mAny >>= nKey; + + OUString aNumberFormatString = getNumberFormatCode(nKey); + + pFS->singleElement(FSNS(XML_c, XML_numFmt), + XML_formatCode, aNumberFormatString, + XML_sourceLinked, ToPsz10(bLinkedNumFmt)); + } + + uno::Sequence<sal_Int32> aAttrLabelIndices; + xPropSet->getPropertyValue("AttributedDataPoints") >>= aAttrLabelIndices; + + // We must not export label placement property when the chart type doesn't + // support this option in MS Office, else MS Office would think the file + // is corrupt & refuse to open it. + + const chart::TypeGroupInfo& rInfo = chart::GetTypeGroupInfo(static_cast<chart::TypeId>(eChartType)); + LabelPlacementParam aParam(!mbIs3DChart, rInfo.mnDefLabelPos); + switch (eChartType) // diagram chart type + { + case chart::TYPEID_PIE: + if(getChartType() == chart::TYPEID_DOUGHNUT) + aParam.mbExport = false; + else + // All pie charts support label placement. + aParam.mbExport = true; + break; + case chart::TYPEID_AREA: + case chart::TYPEID_RADARLINE: + case chart::TYPEID_RADARAREA: + // These chart types don't support label placement. + aParam.mbExport = false; + break; + case chart::TYPEID_BAR: + if (mbStacked || mbPercent) + { + aParam.maAllowedValues.clear(); + aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::CENTER); + aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::INSIDE); + aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::NEAR_ORIGIN); + aParam.meDefault = css::chart::DataLabelPlacement::CENTER; + } + else // Clustered bar chart + { + aParam.maAllowedValues.clear(); + aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::CENTER); + aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::INSIDE); + aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::OUTSIDE); + aParam.maAllowedValues.insert(css::chart::DataLabelPlacement::NEAR_ORIGIN); + aParam.meDefault = css::chart::DataLabelPlacement::OUTSIDE; + } + break; + default: + ; + } + + for (const sal_Int32 nIdx : std::as_const(aAttrLabelIndices)) + { + uno::Reference<beans::XPropertySet> xLabelPropSet = xSeries->getDataPointByIndex(nIdx); + + if (!xLabelPropSet.is()) + continue; + + pFS->startElement(FSNS(XML_c, XML_dLbl)); + pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nIdx)); + + // export custom position of data label + if( eChartType != chart::TYPEID_PIE ) + { + chart2::RelativePosition aCustomLabelPosition; + if( xLabelPropSet->getPropertyValue("CustomLabelPosition") >>= aCustomLabelPosition ) + { + pFS->startElement(FSNS(XML_c, XML_layout)); + pFS->startElement(FSNS(XML_c, XML_manualLayout)); + + pFS->singleElement(FSNS(XML_c, XML_x), XML_val, OString::number(aCustomLabelPosition.Primary)); + pFS->singleElement(FSNS(XML_c, XML_y), XML_val, OString::number(aCustomLabelPosition.Secondary)); + + SAL_WARN_IF(aCustomLabelPosition.Anchor != css::drawing::Alignment_TOP_LEFT, "oox", "unsupported anchor position"); + + pFS->endElement(FSNS(XML_c, XML_manualLayout)); + pFS->endElement(FSNS(XML_c, XML_layout)); + } + } + + if( GetProperty(xLabelPropSet, "LinkNumberFormatToSource") ) + mAny >>= bLinkedNumFmt; + + if( xLabelPropSet->getPropertyValue("Label") >>= aLabel ) + bLabelIsNumberFormat = aLabel.ShowNumber; + else + bLabelIsNumberFormat = true; + + if (GetProperty(xLabelPropSet, bLabelIsNumberFormat ? OUString("NumberFormat") : OUString("PercentageNumberFormat"))) + { + sal_Int32 nKey = 0; + mAny >>= nKey; + + OUString aNumberFormatString = getNumberFormatCode(nKey); + + pFS->singleElement(FSNS(XML_c, XML_numFmt), XML_formatCode, aNumberFormatString, + XML_sourceLinked, ToPsz10(bLinkedNumFmt)); + } + + // Individual label property that overwrites the baseline. + writeLabelProperties(pFS, this, xLabelPropSet, aParam, nIdx, rDLblsRange); + pFS->endElement(FSNS(XML_c, XML_dLbl)); + } + + // Baseline label properties for all labels. + writeLabelProperties(pFS, this, xPropSet, aParam, -1, rDLblsRange); + + bool bShowLeaderLines = false; + xPropSet->getPropertyValue("ShowCustomLeaderLines") >>= bShowLeaderLines; + pFS->singleElement(FSNS(XML_c, XML_showLeaderLines), XML_val, ToPsz10(bShowLeaderLines)); + + // Export leader line + if( eChartType != chart::TYPEID_PIE ) + { + pFS->startElement(FSNS(XML_c, XML_extLst)); + pFS->startElement(FSNS(XML_c, XML_ext), XML_uri, "{CE6537A1-D6FC-4f65-9D91-7224C49458BB}", FSNS(XML_xmlns, XML_c15), GetFB()->getNamespaceURL(OOX_NS(c15))); + pFS->singleElement(FSNS(XML_c15, XML_showLeaderLines), XML_val, ToPsz10(bShowLeaderLines)); + pFS->endElement(FSNS(XML_c, XML_ext)); + pFS->endElement(FSNS(XML_c, XML_extLst)); + } + pFS->endElement(FSNS(XML_c, XML_dLbls)); +} + +void ChartExport::exportDataPoints( + const uno::Reference< beans::XPropertySet > & xSeriesProperties, + sal_Int32 nSeriesLength, sal_Int32 eChartType ) +{ + uno::Reference< chart2::XDataSeries > xSeries( xSeriesProperties, uno::UNO_QUERY ); + bool bVaryColorsByPoint = false; + Sequence< sal_Int32 > aDataPointSeq; + if( xSeriesProperties.is()) + { + Any aAny = xSeriesProperties->getPropertyValue( "AttributedDataPoints" ); + aAny >>= aDataPointSeq; + xSeriesProperties->getPropertyValue( "VaryColorsByPoint" ) >>= bVaryColorsByPoint; + } + + const sal_Int32 * pPoints = aDataPointSeq.getConstArray(); + sal_Int32 nElement; + Reference< chart2::XColorScheme > xColorScheme; + if( mxNewDiagram.is()) + xColorScheme.set( mxNewDiagram->getDefaultColorScheme()); + + if( bVaryColorsByPoint && xColorScheme.is() ) + { + o3tl::sorted_vector< sal_Int32 > aAttrPointSet; + aAttrPointSet.reserve(aDataPointSeq.getLength()); + for (auto p = pPoints; p < pPoints + aDataPointSeq.getLength(); ++p) + aAttrPointSet.insert(*p); + const auto aEndIt = aAttrPointSet.end(); + for( nElement = 0; nElement < nSeriesLength; ++nElement ) + { + uno::Reference< beans::XPropertySet > xPropSet; + if( aAttrPointSet.find( nElement ) != aEndIt ) + { + try + { + xPropSet = SchXMLSeriesHelper::createOldAPIDataPointPropertySet( + xSeries, nElement, getModel() ); + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION( "oox", "Exception caught during Export of data point" ); + } + } + else + { + // property set only containing the color + xPropSet.set( new ColorPropertySet( ColorTransparency, xColorScheme->getColorByIndex( nElement ))); + } + + if( xPropSet.is() ) + { + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_dPt)); + pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nElement)); + + switch (eChartType) + { + case chart::TYPEID_PIE: + case chart::TYPEID_DOUGHNUT: + { + if( xPropSet.is() && GetProperty( xPropSet, "SegmentOffset") ) + { + sal_Int32 nOffset = 0; + mAny >>= nOffset; + if (nOffset) + pFS->singleElement( FSNS( XML_c, XML_explosion ), + XML_val, OString::number( nOffset ) ); + } + break; + } + default: + break; + } + exportShapeProps( xPropSet ); + + pFS->endElement( FSNS( XML_c, XML_dPt ) ); + } + } + } + + // Export Data Point Property in Charts even if the VaryColors is false + if( bVaryColorsByPoint ) + return; + + o3tl::sorted_vector< sal_Int32 > aAttrPointSet; + aAttrPointSet.reserve(aDataPointSeq.getLength()); + for (auto p = pPoints; p < pPoints + aDataPointSeq.getLength(); ++p) + aAttrPointSet.insert(*p); + const auto aEndIt = aAttrPointSet.end(); + for( nElement = 0; nElement < nSeriesLength; ++nElement ) + { + uno::Reference< beans::XPropertySet > xPropSet; + if( aAttrPointSet.find( nElement ) != aEndIt ) + { + try + { + xPropSet = SchXMLSeriesHelper::createOldAPIDataPointPropertySet( + xSeries, nElement, getModel() ); + } + catch( const uno::Exception & ) + { + DBG_UNHANDLED_EXCEPTION( "oox", "Exception caught during Export of data point" ); + } + } + + if( xPropSet.is() ) + { + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_dPt)); + pFS->singleElement(FSNS(XML_c, XML_idx), XML_val, OString::number(nElement)); + + switch( eChartType ) + { + case chart::TYPEID_BUBBLE: + case chart::TYPEID_HORBAR: + case chart::TYPEID_BAR: + pFS->singleElement(FSNS(XML_c, XML_invertIfNegative), XML_val, "0"); + exportShapeProps(xPropSet); + break; + + case chart::TYPEID_LINE: + case chart::TYPEID_SCATTER: + case chart::TYPEID_RADARLINE: + exportMarker(xPropSet); + break; + + default: + exportShapeProps(xPropSet); + break; + } + + pFS->endElement( FSNS( XML_c, XML_dPt ) ); + } + } +} + +void ChartExport::exportAxesId(bool bPrimaryAxes, bool bCheckCombinedAxes) +{ + sal_Int32 nAxisIdx, nAxisIdy; + bool bPrimaryAxisExists = false; + bool bSecondaryAxisExists = false; + // let's check which axis already exists and which axis is attached to the actual dataseries + if (maAxes.size() >= 2) + { + bPrimaryAxisExists = bPrimaryAxes && maAxes[1].nAxisType == AXIS_PRIMARY_Y; + bSecondaryAxisExists = !bPrimaryAxes && maAxes[1].nAxisType == AXIS_SECONDARY_Y; + } + // tdf#114181 keep axes of combined charts + if ( bCheckCombinedAxes && ( bPrimaryAxisExists || bSecondaryAxisExists ) ) + { + nAxisIdx = maAxes[0].nAxisId; + nAxisIdy = maAxes[1].nAxisId; + } + else + { + nAxisIdx = lcl_generateRandomValue(); + nAxisIdy = lcl_generateRandomValue(); + AxesType eXAxis = bPrimaryAxes ? AXIS_PRIMARY_X : AXIS_SECONDARY_X; + AxesType eYAxis = bPrimaryAxes ? AXIS_PRIMARY_Y : AXIS_SECONDARY_Y; + maAxes.emplace_back( eXAxis, nAxisIdx, nAxisIdy ); + maAxes.emplace_back( eYAxis, nAxisIdy, nAxisIdx ); + } + FSHelperPtr pFS = GetFS(); + pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdx)); + pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdy)); + if (mbHasZAxis) + { + sal_Int32 nAxisIdz = 0; + if( isDeep3dChart() ) + { + nAxisIdz = lcl_generateRandomValue(); + maAxes.emplace_back( AXIS_PRIMARY_Z, nAxisIdz, nAxisIdy ); + } + pFS->singleElement(FSNS(XML_c, XML_axId), XML_val, OString::number(nAxisIdz)); + } +} + +void ChartExport::exportGrouping( bool isBar ) +{ + FSHelperPtr pFS = GetFS(); + Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY); + // grouping + if( GetProperty( xPropSet, "Stacked" ) ) + mAny >>= mbStacked; + if( GetProperty( xPropSet, "Percent" ) ) + mAny >>= mbPercent; + + const char* grouping = nullptr; + if (mbStacked) + grouping = "stacked"; + else if (mbPercent) + grouping = "percentStacked"; + else + { + if( isBar && !isDeep3dChart() ) + { + grouping = "clustered"; + } + else + grouping = "standard"; + } + pFS->singleElement(FSNS(XML_c, XML_grouping), XML_val, grouping); +} + +void ChartExport::exportTrendlines( const Reference< chart2::XDataSeries >& xSeries ) +{ + FSHelperPtr pFS = GetFS(); + Reference< chart2::XRegressionCurveContainer > xRegressionCurveContainer( xSeries, UNO_QUERY ); + if( !xRegressionCurveContainer.is() ) + return; + + const Sequence< Reference< chart2::XRegressionCurve > > aRegCurveSeq = xRegressionCurveContainer->getRegressionCurves(); + for( const Reference< chart2::XRegressionCurve >& xRegCurve : aRegCurveSeq ) + { + if (!xRegCurve.is()) + continue; + + Reference< XPropertySet > xProperties( xRegCurve , uno::UNO_QUERY ); + + OUString aService; + Reference< lang::XServiceName > xServiceName( xProperties, UNO_QUERY ); + if( !xServiceName.is() ) + continue; + + aService = xServiceName->getServiceName(); + + if(aService != "com.sun.star.chart2.LinearRegressionCurve" && + aService != "com.sun.star.chart2.ExponentialRegressionCurve" && + aService != "com.sun.star.chart2.LogarithmicRegressionCurve" && + aService != "com.sun.star.chart2.PotentialRegressionCurve" && + aService != "com.sun.star.chart2.PolynomialRegressionCurve" && + aService != "com.sun.star.chart2.MovingAverageRegressionCurve") + continue; + + pFS->startElement(FSNS(XML_c, XML_trendline)); + + OUString aName; + xProperties->getPropertyValue("CurveName") >>= aName; + if(!aName.isEmpty()) + { + pFS->startElement(FSNS(XML_c, XML_name)); + pFS->writeEscaped(aName); + pFS->endElement( FSNS( XML_c, XML_name) ); + } + + exportShapeProps( xProperties ); + + if( aService == "com.sun.star.chart2.LinearRegressionCurve" ) + { + pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "linear"); + } + else if( aService == "com.sun.star.chart2.ExponentialRegressionCurve" ) + { + pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "exp"); + } + else if( aService == "com.sun.star.chart2.LogarithmicRegressionCurve" ) + { + pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "log"); + } + else if( aService == "com.sun.star.chart2.PotentialRegressionCurve" ) + { + pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "power"); + } + else if( aService == "com.sun.star.chart2.PolynomialRegressionCurve" ) + { + pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "poly"); + + sal_Int32 aDegree = 2; + xProperties->getPropertyValue( "PolynomialDegree") >>= aDegree; + pFS->singleElement(FSNS(XML_c, XML_order), XML_val, OString::number(aDegree)); + } + else if( aService == "com.sun.star.chart2.MovingAverageRegressionCurve" ) + { + pFS->singleElement(FSNS(XML_c, XML_trendlineType), XML_val, "movingAvg"); + + sal_Int32 aPeriod = 2; + xProperties->getPropertyValue( "MovingAveragePeriod") >>= aPeriod; + + pFS->singleElement(FSNS(XML_c, XML_period), XML_val, OString::number(aPeriod)); + } + else + { + // should never happen + // This would produce invalid OOXML files so we check earlier for the type + assert(false); + } + + double fExtrapolateForward = 0.0; + double fExtrapolateBackward = 0.0; + + xProperties->getPropertyValue("ExtrapolateForward") >>= fExtrapolateForward; + xProperties->getPropertyValue("ExtrapolateBackward") >>= fExtrapolateBackward; + + pFS->singleElement( FSNS( XML_c, XML_forward ), + XML_val, OString::number(fExtrapolateForward) ); + + pFS->singleElement( FSNS( XML_c, XML_backward ), + XML_val, OString::number(fExtrapolateBackward) ); + + bool bForceIntercept = false; + xProperties->getPropertyValue("ForceIntercept") >>= bForceIntercept; + + if (bForceIntercept) + { + double fInterceptValue = 0.0; + xProperties->getPropertyValue("InterceptValue") >>= fInterceptValue; + + pFS->singleElement( FSNS( XML_c, XML_intercept ), + XML_val, OString::number(fInterceptValue) ); + } + + // Equation properties + Reference< XPropertySet > xEquationProperties( xRegCurve->getEquationProperties() ); + + // Show Equation + bool bShowEquation = false; + xEquationProperties->getPropertyValue("ShowEquation") >>= bShowEquation; + + // Show R^2 + bool bShowCorrelationCoefficient = false; + xEquationProperties->getPropertyValue("ShowCorrelationCoefficient") >>= bShowCorrelationCoefficient; + + pFS->singleElement( FSNS( XML_c, XML_dispRSqr ), + XML_val, ToPsz10(bShowCorrelationCoefficient) ); + + pFS->singleElement(FSNS(XML_c, XML_dispEq), XML_val, ToPsz10(bShowEquation)); + + pFS->endElement( FSNS( XML_c, XML_trendline ) ); + } +} + +void ChartExport::exportMarker(const Reference< XPropertySet >& xPropSet) +{ + chart2::Symbol aSymbol; + if( GetProperty( xPropSet, "Symbol" ) ) + mAny >>= aSymbol; + + if(aSymbol.Style != chart2::SymbolStyle_STANDARD && aSymbol.Style != chart2::SymbolStyle_NONE) + return; + + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_marker)); + + sal_Int32 nSymbol = aSymbol.StandardSymbol; + // TODO: more properties support for marker + const char* pSymbolType; // no initialization here, to let compiler warn if we have a code path + // where it stays uninitialized + switch( nSymbol ) + { + case 0: + pSymbolType = "square"; + break; + case 1: + pSymbolType = "diamond"; + break; + case 2: + case 3: + case 4: + case 5: + pSymbolType = "triangle"; + break; + case 8: + pSymbolType = "circle"; + break; + case 9: + pSymbolType = "star"; + break; + case 10: + pSymbolType = "x"; // in MS office 2010 built in symbol marker 'X' is represented as 'x' + break; + case 11: + pSymbolType = "plus"; + break; + case 13: + pSymbolType = "dash"; + break; + default: + pSymbolType = "square"; + break; + } + + bool bSkipFormatting = false; + if (aSymbol.Style == chart2::SymbolStyle_NONE) + { + bSkipFormatting = true; + pSymbolType = "none"; + } + + pFS->singleElement(FSNS(XML_c, XML_symbol), XML_val, pSymbolType); + + if (!bSkipFormatting) + { + awt::Size aSymbolSize = aSymbol.Size; + sal_Int32 nSize = std::max( aSymbolSize.Width, aSymbolSize.Height ); + + nSize = nSize/250.0*7.0 + 1; // just guessed based on some test cases, + //the value is always 1 less than the actual value. + nSize = std::clamp( int(nSize), 2, 72 ); + pFS->singleElement(FSNS(XML_c, XML_size), XML_val, OString::number(nSize)); + + pFS->startElement(FSNS(XML_c, XML_spPr)); + + util::Color aColor = aSymbol.FillColor; + if (GetProperty(xPropSet, "Color")) + mAny >>= aColor; + + if (aColor == -1) + { + pFS->singleElement(FSNS(XML_a, XML_noFill)); + } + else + WriteSolidFill(::Color(ColorTransparency, aColor)); + + pFS->endElement( FSNS( XML_c, XML_spPr ) ); + } + + pFS->endElement( FSNS( XML_c, XML_marker ) ); +} + +void ChartExport::exportSmooth() +{ + FSHelperPtr pFS = GetFS(); + Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY ); + sal_Int32 nSplineType = 0; + if( GetProperty( xPropSet, "SplineType" ) ) + mAny >>= nSplineType; + const char* pVal = nSplineType != 0 ? "1" : "0"; + pFS->singleElement(FSNS(XML_c, XML_smooth), XML_val, pVal); +} + +void ChartExport::exportFirstSliceAng( ) +{ + FSHelperPtr pFS = GetFS(); + sal_Int32 nStartingAngle = 0; + Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY); + if( GetProperty( xPropSet, "StartingAngle" ) ) + mAny >>= nStartingAngle; + + // convert to ooxml angle + nStartingAngle = (450 - nStartingAngle ) % 360; + pFS->singleElement(FSNS(XML_c, XML_firstSliceAng), XML_val, OString::number(nStartingAngle)); +} + +namespace { + +const char* getErrorBarStyle(sal_Int32 nErrorBarStyle) +{ + switch(nErrorBarStyle) + { + case cssc::ErrorBarStyle::NONE: + return nullptr; + case cssc::ErrorBarStyle::VARIANCE: + break; + case cssc::ErrorBarStyle::STANDARD_DEVIATION: + return "stdDev"; + case cssc::ErrorBarStyle::ABSOLUTE: + return "fixedVal"; + case cssc::ErrorBarStyle::RELATIVE: + return "percentage"; + case cssc::ErrorBarStyle::ERROR_MARGIN: + break; + case cssc::ErrorBarStyle::STANDARD_ERROR: + return "stdErr"; + case cssc::ErrorBarStyle::FROM_DATA: + return "cust"; + default: + assert(false && "can't happen"); + } + return nullptr; +} + +Reference< chart2::data::XDataSequence> getLabeledSequence( + const uno::Sequence< uno::Reference< chart2::data::XLabeledDataSequence > >& aSequences, + bool bPositive ) +{ + OUString aDirection; + if(bPositive) + aDirection = "positive"; + else + aDirection = "negative"; + + for( const auto& rSequence : aSequences ) + { + if( rSequence.is()) + { + uno::Reference< chart2::data::XDataSequence > xSequence( rSequence->getValues()); + uno::Reference< beans::XPropertySet > xSeqProp( xSequence, uno::UNO_QUERY_THROW ); + OUString aRole; + if( ( xSeqProp->getPropertyValue( "Role" ) >>= aRole ) && + aRole.match( "error-bars" ) && aRole.indexOf(aDirection) >= 0 ) + { + return xSequence; + } + } + } + + return Reference< chart2::data::XDataSequence > (); +} + +} + +void ChartExport::exportErrorBar(const Reference< XPropertySet>& xErrorBarProps, bool bYError) +{ + sal_Int32 nErrorBarStyle = cssc::ErrorBarStyle::NONE; + xErrorBarProps->getPropertyValue("ErrorBarStyle") >>= nErrorBarStyle; + const char* pErrorBarStyle = getErrorBarStyle(nErrorBarStyle); + if(!pErrorBarStyle) + return; + + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_errBars)); + pFS->singleElement(FSNS(XML_c, XML_errDir), XML_val, bYError ? "y" : "x"); + bool bPositive = false, bNegative = false; + xErrorBarProps->getPropertyValue("ShowPositiveError") >>= bPositive; + xErrorBarProps->getPropertyValue("ShowNegativeError") >>= bNegative; + const char* pErrBarType; + if(bPositive && bNegative) + pErrBarType = "both"; + else if(bPositive) + pErrBarType = "plus"; + else if(bNegative) + pErrBarType = "minus"; + else + { + // what the hell should we do now? + // at least this makes the file valid + pErrBarType = "both"; + } + pFS->singleElement(FSNS(XML_c, XML_errBarType), XML_val, pErrBarType); + pFS->singleElement(FSNS(XML_c, XML_errValType), XML_val, pErrorBarStyle); + pFS->singleElement(FSNS(XML_c, XML_noEndCap), XML_val, "0"); + if(nErrorBarStyle == cssc::ErrorBarStyle::FROM_DATA) + { + uno::Reference< chart2::data::XDataSource > xDataSource(xErrorBarProps, uno::UNO_QUERY); + Sequence< Reference < chart2::data::XLabeledDataSequence > > aSequences = + xDataSource->getDataSequences(); + + if(bPositive) + { + exportSeriesValues(getLabeledSequence(aSequences, true), XML_plus); + } + + if(bNegative) + { + exportSeriesValues(getLabeledSequence(aSequences, false), XML_minus); + } + } + else + { + double nVal = 0.0; + if(nErrorBarStyle == cssc::ErrorBarStyle::STANDARD_DEVIATION) + { + xErrorBarProps->getPropertyValue("Weight") >>= nVal; + } + else + { + if(bPositive) + xErrorBarProps->getPropertyValue("PositiveError") >>= nVal; + else + xErrorBarProps->getPropertyValue("NegativeError") >>= nVal; + } + + pFS->singleElement(FSNS(XML_c, XML_val), XML_val, OString::number(nVal)); + } + + exportShapeProps( xErrorBarProps ); + + pFS->endElement( FSNS( XML_c, XML_errBars) ); +} + +void ChartExport::exportView3D() +{ + Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY); + if( !xPropSet.is() ) + return; + FSHelperPtr pFS = GetFS(); + pFS->startElement(FSNS(XML_c, XML_view3D)); + sal_Int32 eChartType = getChartType( ); + // rotX + if( GetProperty( xPropSet, "RotationHorizontal" ) ) + { + sal_Int32 nRotationX = 0; + mAny >>= nRotationX; + if( nRotationX < 0 ) + { + if(eChartType == chart::TYPEID_PIE) + { + /* In OOXML we get value in 0..90 range for pie chart X rotation , whereas we expect it to be in -90..90 range, + so we convert that during import. It is modified in View3DConverter::convertFromModel() + here we convert it back to 0..90 as we received in import */ + nRotationX += 90; // X rotation (map Chart2 [-179,180] to OOXML [0..90]) + } + else + nRotationX += 360; // X rotation (map Chart2 [-179,180] to OOXML [-90..90]) + } + pFS->singleElement(FSNS(XML_c, XML_rotX), XML_val, OString::number(nRotationX)); + } + // rotY + if( GetProperty( xPropSet, "RotationVertical" ) ) + { + // Y rotation (map Chart2 [-179,180] to OOXML [0..359]) + if( eChartType == chart::TYPEID_PIE && GetProperty( xPropSet, "StartingAngle" ) ) + { + // Y rotation used as 'first pie slice angle' in 3D pie charts + sal_Int32 nStartingAngle=0; + mAny >>= nStartingAngle; + // convert to ooxml angle + nStartingAngle = (450 - nStartingAngle ) % 360; + pFS->singleElement(FSNS(XML_c, XML_rotY), XML_val, OString::number(nStartingAngle)); + } + else + { + sal_Int32 nRotationY = 0; + mAny >>= nRotationY; + // Y rotation (map Chart2 [-179,180] to OOXML [0..359]) + if( nRotationY < 0 ) + nRotationY += 360; + pFS->singleElement(FSNS(XML_c, XML_rotY), XML_val, OString::number(nRotationY)); + } + } + // rAngAx + if( GetProperty( xPropSet, "RightAngledAxes" ) ) + { + bool bRightAngled = false; + mAny >>= bRightAngled; + const char* sRightAngled = bRightAngled ? "1":"0"; + pFS->singleElement(FSNS(XML_c, XML_rAngAx), XML_val, sRightAngled); + } + // perspective + if( GetProperty( xPropSet, "Perspective" ) ) + { + sal_Int32 nPerspective = 0; + mAny >>= nPerspective; + // map Chart2 [0,100] to OOXML [0..200] + nPerspective *= 2; + pFS->singleElement(FSNS(XML_c, XML_perspective), XML_val, OString::number(nPerspective)); + } + pFS->endElement( FSNS( XML_c, XML_view3D ) ); +} + +bool ChartExport::isDeep3dChart() +{ + bool isDeep = false; + if( mbIs3DChart ) + { + Reference< XPropertySet > xPropSet( mxDiagram , uno::UNO_QUERY); + if( GetProperty( xPropSet, "Deep" ) ) + mAny >>= isDeep; + } + return isDeep; +} + +OUString ChartExport::getNumberFormatCode(sal_Int32 nKey) const +{ + /* XXX if this was called more than one or two times per export the two + * SvNumberFormatter instances and NfKeywordTable should be member + * variables and initialized only once. */ + + OUString aCode("General"); // init with fallback + uno::Reference<util::XNumberFormatsSupplier> xNumberFormatsSupplier(mxChartModel, uno::UNO_QUERY_THROW); + SvNumberFormatsSupplierObj* pSupplierObj = comphelper::getFromUnoTunnel<SvNumberFormatsSupplierObj>( xNumberFormatsSupplier); + if (!pSupplierObj) + return aCode; + + SvNumberFormatter* pNumberFormatter = pSupplierObj->GetNumberFormatter(); + if (!pNumberFormatter) + return aCode; + + SvNumberFormatter aTempFormatter( comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US); + NfKeywordTable aKeywords; + aTempFormatter.FillKeywordTableForExcel( aKeywords); + aCode = pNumberFormatter->GetFormatStringForExcel( nKey, aKeywords, aTempFormatter); + + return aCode; +} + +}// oox + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx new file mode 100644 index 0000000000..05c96c9ad7 --- /dev/null +++ b/oox/source/export/drawingml.cxx @@ -0,0 +1,6591 @@ +/* -*- 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/fontworkhelpers.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 <comphelper/diagnose_ex.hxx> +#include <comphelper/processfactory.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/utils/gradienttools.hxx> + +#include <numeric> +#include <string_view> + +#include <com/sun/star/awt/CharSet.hpp> +#include <com/sun/star/awt/FontDescriptor.hpp> +#include <com/sun/star/awt/FontSlant.hpp> +#include <com/sun/star/awt/FontStrikeout.hpp> +#include <com/sun/star/awt/FontWeight.hpp> +#include <com/sun/star/awt/FontUnderline.hpp> +#include <com/sun/star/awt/Gradient.hpp> +#include <com/sun/star/awt/Gradient2.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/drawing/BitmapMode.hpp> +#include <com/sun/star/drawing/ColorMode.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp> +#include <com/sun/star/drawing/FillStyle.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/frame/XModel.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/style/LineSpacing.hpp> +#include <com/sun/star/style/LineSpacingMode.hpp> +#include <com/sun/star/text/WritingMode.hpp> +#include <com/sun/star/text/WritingMode2.hpp> +#include <com/sun/star/text/GraphicCrop.hpp> +#include <com/sun/star/text/XText.hpp> +#include <com/sun/star/text/XTextColumns.hpp> +#include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/text/XTextField.hpp> +#include <com/sun/star/text/XTextRange.hpp> +#include <com/sun/star/text/XTextFrame.hpp> +#include <com/sun/star/style/CaseMap.hpp> +#include <com/sun/star/xml/dom/XNodeList.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/xml/sax/XSAXSerializable.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/drawing/XDrawPages.hpp> +#include <com/sun/star/drawing/XDrawPagesSupplier.hpp> +#include <com/sun/star/drawing/RectanglePoint.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <comphelper/random.hxx> +#include <comphelper/seqstream.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/xmltools.hxx> +#include <o3tl/any.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> +#include <tools/stream.hxx> +#include <tools/UnitConversion.hxx> +#include <unotools/fontdefs.hxx> +#include <vcl/cvtgrf.hxx> +#include <vcl/svapp.hxx> +#include <rtl/strbuf.hxx> +#include <filter/msfilter/escherex.hxx> +#include <filter/msfilter/util.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/svxenum.hxx> +#include <editeng/unonames.hxx> +#include <editeng/unoprnms.hxx> +#include <editeng/flditem.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/unonrule.hxx> +#include <docmodel/uno/UnoComplexColor.hxx> +#include <svx/svdoashp.hxx> +#include <svx/svdomedia.hxx> +#include <svx/svdtrans.hxx> +#include <svx/unoshape.hxx> +#include <svx/EnhancedCustomShape2d.hxx> +#include <drawingml/presetgeometrynames.hxx> +#include <docmodel/uno/UnoGradientTools.hxx> + +using namespace ::css; +using namespace ::css::beans; +using namespace ::css::drawing; +using namespace ::css::i18n; +using namespace ::css::style; +using namespace ::css::text; +using namespace ::css::uno; +using namespace ::css::container; +using namespace ::com::sun::star::drawing::EnhancedCustomShapeSegmentCommand; + +using ::css::io::XOutputStream; +using ::sax_fastparser::FSHelperPtr; +using ::sax_fastparser::FastSerializerHelper; + +namespace +{ +const char* g_aPredefinedClrNames[] = { + "dk1", + "lt1", + "dk2", + "lt2", + "accent1", + "accent2", + "accent3", + "accent4", + "accent5", + "accent6", + "hlink", + "folHlink", +}; +} + +namespace oox::drawingml { + +URLTransformer::~URLTransformer() +{ +} + +OUString URLTransformer::getTransformedString(const OUString& rString) const +{ + return rString; +} + +bool URLTransformer::isExternalURL(const OUString& rURL) const +{ + bool bExternal = true; + if (rURL.startsWith("#")) + bExternal = false; + return bExternal; +} + +GraphicExportCache& GraphicExportCache::get() +{ + static GraphicExportCache staticGraphicExportCache; + return staticGraphicExportCache; +} + +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 basegfx::BGradient& rBGradient, 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 = rBGradient.GetXOffset(); + pAttributeList->add(XML_l, OString::number(nLeftPercent * PER_PERCENT)); + sal_Int32 nTopPercent = rBGradient.GetYOffset(); + pAttributeList->add(XML_t, OString::number(nTopPercent * PER_PERCENT)); + sal_Int32 nRightPercent = 100 - rBGradient.GetXOffset(); + pAttributeList->add(XML_r, OString::number(nRightPercent * PER_PERCENT)); + sal_Int32 nBottomPercent = 100 - rBGradient.GetYOffset(); + pAttributeList->add(XML_b, OString::number(nBottomPercent * PER_PERCENT)); + pFS->singleElementNS(XML_a, XML_fillToRect, pAttributeList); + + pFS->endElementNS(XML_a, XML_path); +} +} + +// not thread safe +sal_Int32 DrawingML::mnDrawingMLCount = 0; +sal_Int32 DrawingML::mnVmlCount = 0; +sal_Int32 DrawingML::mnChartCount = 0; + +sal_Int16 DrawingML::GetScriptType(const OUString& rStr) +{ + if (rStr.getLength() > 0) + { + static Reference<css::i18n::XBreakIterator> xBreakIterator = + css::i18n::BreakIterator::create(comphelper::getProcessComponentContext()); + + sal_Int16 nScriptType = xBreakIterator->getScriptType(rStr, 0); + + if (nScriptType == css::i18n::ScriptType::WEAK) + { + sal_Int32 nPos = xBreakIterator->nextScript(rStr, 0, nScriptType); + if (nPos < rStr.getLength()) + nScriptType = xBreakIterator->getScriptType(rStr, nPos); + + } + + if (nScriptType != css::i18n::ScriptType::WEAK) + return nScriptType; + } + + return css::i18n::ScriptType::LATIN; +} + +void DrawingML::ResetMlCounters() +{ + mnDrawingMLCount = 0; + mnVmlCount = 0; + mnChartCount = 0; +} + +bool DrawingML::GetProperty( const Reference< XPropertySet >& rXPropertySet, const OUString& aName ) +{ + try + { + mAny = rXPropertySet->getPropertyValue(aName); + if (mAny.hasValue()) + return true; + } + catch( const Exception& ) + { + /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */ + } + return false; +} + +bool DrawingML::GetPropertyAndState( const Reference< XPropertySet >& rXPropertySet, const Reference< XPropertyState >& rXPropertyState, const OUString& aName, PropertyState& eState ) +{ + try + { + mAny = rXPropertySet->getPropertyValue(aName); + if (mAny.hasValue()) + { + eState = rXPropertyState->getPropertyState(aName); + return true; + } + } + catch( const Exception& ) + { + /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */ + } + return false; +} + +namespace +{ +/// Gets hexa value of color on string format. +OString getColorStr(const ::Color nColor) +{ + // Transparency is a separate element. + OString sColor = OString::number(sal_uInt32(nColor) & 0x00FFFFFF, 16); + if (sColor.getLength() < 6) + { + OStringBuffer sBuf("0"); + int remains = 5 - sColor.getLength(); + + while (remains > 0) + { + sBuf.append("0"); + remains--; + } + + sBuf.append(sColor); + + sColor = sBuf.toString(); + } + return sColor; +} +} + +void DrawingML::WriteColor( ::Color nColor, sal_Int32 nAlpha ) +{ + const auto sColor = getColorStr(nColor); + if( nAlpha < MAX_PERCENT ) + { + mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor); + mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha)); + mpFS->endElementNS( XML_a, XML_srgbClr ); + + } + else + { + mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor); + } +} + +void DrawingML::WriteColor( const OUString& sColorSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) +{ + // prevent writing a tag with empty val attribute + if( sColorSchemeName.isEmpty() ) + return; + + if( aTransformations.hasElements() ) + { + mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName); + WriteColorTransformations( aTransformations, nAlpha ); + mpFS->endElementNS( XML_a, XML_schemeClr ); + } + else if(nAlpha < MAX_PERCENT) + { + mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName); + mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha)); + mpFS->endElementNS( XML_a, XML_schemeClr ); + } + else + { + mpFS->singleElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName); + } +} + +void DrawingML::WriteColor( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) +{ + const auto sColor = getColorStr(nColor); + if( aTransformations.hasElements() ) + { + mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor); + WriteColorTransformations(aTransformations, nAlpha); + mpFS->endElementNS(XML_a, XML_srgbClr); + } + else if(nAlpha < MAX_PERCENT) + { + mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor); + mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha)); + mpFS->endElementNS(XML_a, XML_srgbClr); + } + else + { + mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor); + } +} + +void DrawingML::WriteColorTransformations( const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) +{ + for( const auto& rTransformation : aTransformations ) + { + sal_Int32 nToken = Color::getColorTransformationToken( rTransformation.Name ); + if( nToken != XML_TOKEN_INVALID && rTransformation.Value.hasValue() ) + { + if(nToken == XML_alpha && nAlpha < MAX_PERCENT) + { + mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nAlpha)); + } + else + { + sal_Int32 nValue = rTransformation.Value.get<sal_Int32>(); + mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nValue)); + } + } + } +} + +void DrawingML::WriteSolidFill( ::Color nColor, sal_Int32 nAlpha ) +{ + mpFS->startElementNS(XML_a, XML_solidFill); + WriteColor( nColor, nAlpha ); + mpFS->endElementNS( XML_a, XML_solidFill ); +} + +void DrawingML::WriteSolidFill( const OUString& sSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) +{ + mpFS->startElementNS(XML_a, XML_solidFill); + WriteColor( sSchemeName, aTransformations, nAlpha ); + mpFS->endElementNS( XML_a, XML_solidFill ); +} + +void DrawingML::WriteSolidFill( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) +{ + mpFS->startElementNS(XML_a, XML_solidFill); + WriteColor(nColor, aTransformations, nAlpha); + mpFS->endElementNS(XML_a, XML_solidFill); +} + +void DrawingML::WriteSolidFill( const Reference< XPropertySet >& rXPropSet ) +{ + // get fill color + if ( !GetProperty( rXPropSet, "FillColor" ) ) + return; + sal_uInt32 nFillColor = mAny.get<sal_uInt32>(); + + // get InteropGrabBag and search the relevant attributes + OUString sColorFillScheme; + sal_uInt32 nOriginalColor = 0; + Sequence< PropertyValue > aStyleProperties, aTransformations; + if ( GetProperty( rXPropSet, "InteropGrabBag" ) ) + { + Sequence< PropertyValue > aGrabBag; + mAny >>= aGrabBag; + for( const auto& rProp : std::as_const(aGrabBag) ) + { + if( rProp.Name == "SpPrSolidFillSchemeClr" ) + rProp.Value >>= sColorFillScheme; + else if( rProp.Name == "OriginalSolidFillClr" ) + rProp.Value >>= nOriginalColor; + else if( rProp.Name == "StyleFillRef" ) + rProp.Value >>= aStyleProperties; + else if( rProp.Name == "SpPrSolidFillSchemeClrTransformations" ) + rProp.Value >>= aTransformations; + } + } + + sal_Int32 nAlpha = MAX_PERCENT; + if( GetProperty( rXPropSet, "FillTransparence" ) ) + { + sal_Int32 nTransparency = 0; + mAny >>= nTransparency; + // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency()) + nAlpha = (MAX_PERCENT - ( PER_PERCENT * nTransparency ) ); + } + + // OOXML has no separate transparence gradient but uses transparency in the gradient stops. + // So we merge transparency and color and use gradient fill in such case. + basegfx::BGradient aTransparenceGradient; + OUString sFillTransparenceGradientName; + bool bNeedGradientFill(false); + + if (GetProperty(rXPropSet, "FillTransparenceGradientName") + && (mAny >>= sFillTransparenceGradientName) + && !sFillTransparenceGradientName.isEmpty() + && GetProperty(rXPropSet, "FillTransparenceGradient")) + { + aTransparenceGradient = model::gradient::getFromAny(mAny); + basegfx::BColor aSingleColor; + bNeedGradientFill = !aTransparenceGradient.GetColorStops().isSingleColor(aSingleColor); + + // we no longer need to 'guess' if FillTransparenceGradient is used by + // comparing it's 1st color to COL_BLACK after having tested that the + // FillTransparenceGradientName is set + if (!bNeedGradientFill) + { + // Our alpha is a gray color value. + const sal_uInt8 nRed(aSingleColor.getRed() * 255.0); + + // drawingML alpha is a percentage on a 0..100000 scale. + nAlpha = (255 - nRed) * oox::drawingml::MAX_PERCENT / 255; + } + } + + // write XML + if (bNeedGradientFill) + { + // no longer create copy/PseudoColorGradient, use new API of + // WriteGradientFill to express fix fill color + mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0"); + WriteGradientFill(nullptr, nFillColor, &aTransparenceGradient); + mpFS->endElementNS( XML_a, XML_gradFill ); + } + else if ( nFillColor != nOriginalColor ) + { + // the user has set a different color for the shape + if (!WriteSchemeColor(u"FillComplexColor"_ustr, rXPropSet)) + { + WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha); + } + } + // tdf#91332 LO doesn't export the actual theme.xml in XLSX. + else if ( !sColorFillScheme.isEmpty() && GetDocumentType() != DOCUMENT_XLSX ) + { + // the shape had a scheme color and the user didn't change it + WriteSolidFill( sColorFillScheme, aTransformations, nAlpha ); + } + else + { + // the shape had a custom color and the user didn't change it + // tdf#124013 + WriteSolidFill( ::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha ); + } +} + +bool DrawingML::WriteSchemeColor(OUString const& rPropertyName, const uno::Reference<beans::XPropertySet>& xPropertySet) +{ + if (!xPropertySet->getPropertySetInfo()->hasPropertyByName(rPropertyName)) + return false; + + uno::Reference<util::XComplexColor> xComplexColor; + xPropertySet->getPropertyValue(rPropertyName) >>= xComplexColor; + if (!xComplexColor.is()) + return false; + + auto aComplexColor = model::color::getFromXComplexColor(xComplexColor); + if (aComplexColor.getThemeColorType() == model::ThemeColorType::Unknown) + return false; + const char* pColorName = g_aPredefinedClrNames[sal_Int16(aComplexColor.getThemeColorType())]; + mpFS->startElementNS(XML_a, XML_solidFill); + mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, pColorName); + for (auto const& rTransform : aComplexColor.getTransformations()) + { + switch (rTransform.meType) + { + case model::TransformationType::LumMod: + mpFS->singleElementNS(XML_a, XML_lumMod, XML_val, OString::number(rTransform.mnValue * 10)); + break; + case model::TransformationType::LumOff: + mpFS->singleElementNS(XML_a, XML_lumOff, XML_val, OString::number(rTransform.mnValue * 10)); + break; + case model::TransformationType::Tint: + mpFS->singleElementNS(XML_a, XML_tint, XML_val, OString::number(rTransform.mnValue * 10)); + break; + case model::TransformationType::Shade: + mpFS->singleElementNS(XML_a, XML_shade, XML_val, OString::number(rTransform.mnValue * 10)); + break; + default: + break; + } + } + // Alpha is actually not contained in maTransformations although possible (as of Mar 2023). + sal_Int16 nAPITransparency(0); + if ((rPropertyName == u"FillComplexColor" && GetProperty(xPropertySet, "FillTransparence")) + || (rPropertyName == u"LineComplexColor" && GetProperty(xPropertySet, "LineTransparence")) + || (rPropertyName == u"CharComplexColor" && GetProperty(xPropertySet, "CharTransparence"))) + { + mAny >>= nAPITransparency; + } + if (nAPITransparency != 0) + mpFS->singleElementNS(XML_a, XML_alpha, XML_val, + OString::number(MAX_PERCENT - (PER_PERCENT * nAPITransparency))); + + mpFS->endElementNS(XML_a, XML_schemeClr); + mpFS->endElementNS(XML_a, XML_solidFill); + + return true; +} + +void DrawingML::WriteGradientStop(double fOffset, const basegfx::BColor& rColor, const basegfx::BColor& rAlpha) +{ + mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(basegfx::fround(fOffset * 100000))); + WriteColor( + ::Color(rColor), + basegfx::fround((1.0 - rAlpha.luminance()) * oox::drawingml::MAX_PERCENT)); + mpFS->endElementNS( XML_a, XML_gs ); +} + +::Color DrawingML::ColorWithIntensity( sal_uInt32 nColor, sal_uInt32 nIntensity ) +{ + return ::Color(ColorTransparency, ( ( ( nColor & 0xff ) * nIntensity ) / 100 ) + | ( ( ( ( ( nColor & 0xff00 ) >> 8 ) * nIntensity ) / 100 ) << 8 ) + | ( ( ( ( ( nColor & 0xff0000 ) >> 8 ) * nIntensity ) / 100 ) << 8 )); +} + +void DrawingML::WriteGradientFill( const Reference< XPropertySet >& rXPropSet ) +{ + if (!GetProperty(rXPropSet, "FillGradient")) + return; + + // use BGradient constructor directly, it will take care of Gradient/Gradient2 + basegfx::BGradient aGradient = model::gradient::getFromAny(mAny); + + // get InteropGrabBag and search the relevant attributes + basegfx::BGradient 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" ) + aOriginalGradient = model::gradient::getFromAny(rProp.Value); + } + + // 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 (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"); + + basegfx::BGradient aTransparenceGradient; + basegfx::BGradient* pTransparenceGradient(nullptr); + double fTransparency(0.0); + OUString sFillTransparenceGradientName; + + if (GetProperty(rXPropSet, "FillTransparenceGradientName") + && (mAny >>= sFillTransparenceGradientName) + && !sFillTransparenceGradientName.isEmpty() + && GetProperty(rXPropSet, "FillTransparenceGradient")) + { + // TransparenceGradient is only used when name is not empty + aTransparenceGradient = model::gradient::getFromAny(mAny); + pTransparenceGradient = &aTransparenceGradient; + } + else if (GetProperty(rXPropSet, "FillTransparence")) + { + // no longer create PseudoTransparencyGradient, use new API of + // WriteGradientFill to express fix transparency + sal_Int32 nTransparency(0); + mAny >>= nTransparency; + // nTransparency is [0..100]% + fTransparency = nTransparency * 0.01; + } + + // tdf#155852 The gradient might wrongly have StepCount==0, as the draw:gradient-step-count + // attribute in ODF does not belong to the gradient definition but is an attribute in + // the graphic style of the shape. + if (GetProperty(rXPropSet, "FillGradientStepCount")) + { + sal_Int16 nStepCount = 0; + mAny >>= nStepCount; + aGradient.SetSteps(nStepCount); + } + + WriteGradientFill(&aGradient, 0, pTransparenceGradient, fTransparency); + + mpFS->endElementNS(XML_a, XML_gradFill); + } +} + +void DrawingML::WriteGrabBagGradientFill( const Sequence< PropertyValue >& aGradientStops, const basegfx::BGradient& rBGradient ) +{ + // 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)); + 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 (rBGradient.GetGradientStyle()) + { + default: + { + const sal_Int16 nAngle(rBGradient.GetAngle()); + mpFS->singleElementNS( + XML_a, XML_lin, XML_ang, + OString::number(((3600 - static_cast<sal_Int32>(nAngle) + 900) * 6000) % 21600000)); + break; + } + case awt::GradientStyle_RADIAL: + { + WriteGradientPath(rBGradient, mpFS, true); + break; + } + } +} + +void DrawingML::WriteGradientFill( + const basegfx::BGradient* pColorGradient, sal_Int32 nFixColor, + const basegfx::BGradient* pTransparenceGradient, double fFixTransparence) +{ + basegfx::BColorStops aColorStops; + basegfx::BColorStops aAlphaStops; + basegfx::BColor aSingleColor(::Color(ColorTransparency, nFixColor).getBColor()); + basegfx::BColor aSingleAlpha(fFixTransparence); + const basegfx::BGradient* pGradient(pColorGradient); + + if (nullptr != pColorGradient) + { + // extract and correct/process ColorStops + basegfx::utils::prepareColorStops(*pColorGradient, aColorStops, aSingleColor); + + // tdf#155827 Convert 'axial' to 'linear' before synchronize and for each gradient separate. + if (aColorStops.size() > 0 && awt::GradientStyle_AXIAL == pColorGradient->GetGradientStyle()) + aColorStops.doApplyAxial(); + } + if (nullptr != pTransparenceGradient) + { + // remember basic Gradient definition to use + // So we can get the gradient geometry in any case from pGradient. + if (nullptr == pGradient) + { + pGradient = pTransparenceGradient; + } + + // extract and correct/process AlphaStops + basegfx::utils::prepareColorStops(*pTransparenceGradient, aAlphaStops, aSingleAlpha); + if (aAlphaStops.size() > 0 + && awt::GradientStyle_AXIAL == pTransparenceGradient->GetGradientStyle()) + { + aAlphaStops.doApplyAxial(); + } + } + + if (nullptr == pGradient) + { + // an error - see comment in header - is to give neither pColorGradient + // nor pTransparenceGradient + assert(false && "pColorGradient or pTransparenceGradient should be set"); + return; + } + + // Apply steps if used. That increases the number of stops and thus needs to be done before + // synchronize. + if (pGradient->GetSteps()) + { + aColorStops.doApplySteps(pGradient->GetSteps()); + // transparency gradients are always automatic, so do not have steps. + } + + // synchronize ColorStops and AlphaStops as preparation to export + // so also gradients 'coupled' indirectly using the 'FillTransparenceGradient' + // method (at import time) will be exported again. + basegfx::utils::synchronizeColorStops(aColorStops, aAlphaStops, aSingleColor, aSingleAlpha); + + if (aColorStops.size() != aAlphaStops.size()) + { + // this is an error - synchronizeColorStops above *has* to create that + // state, see description there (!) + assert(false && "oox::WriteGradientFill: non-synchronized gradients (!)"); + return; + } + + bool bLinearOrAxial(awt::GradientStyle_LINEAR == pGradient->GetGradientStyle() + || awt::GradientStyle_AXIAL == pGradient->GetGradientStyle()); + if (!bLinearOrAxial) + { + // case awt::GradientStyle_RADIAL: + // case awt::GradientStyle_ELLIPTICAL: + // case awt::GradientStyle_RECT: + // case awt::GradientStyle_SQUARE: + // all these types need the gradients to be mirrored + aColorStops.reverseColorStops(); + aAlphaStops.reverseColorStops(); + } + + // If there were one stop, prepareColorStops() method would have cleared aColorStops, same for + // aAlphaStops. In case of empty stops vectors synchronizeColorStops() method creates two stops + // for each. So at this point we have at least two stops and can fulfill the requirement of + // <gsLst> element to have at least two child elements. + + // export GradientStops (with alpha) + mpFS->startElementNS(XML_a, XML_gsLst); + + basegfx::BColorStops::const_iterator aCurrColor(aColorStops.begin()); + basegfx::BColorStops::const_iterator aCurrAlpha(aAlphaStops.begin()); + + while (aCurrColor != aColorStops.end() && aCurrAlpha != aAlphaStops.end()) + { + WriteGradientStop( + aCurrColor->getStopOffset(), + aCurrColor->getStopColor(), + aCurrAlpha->getStopColor()); + aCurrColor++; + aCurrAlpha++; + } + + mpFS->endElementNS( XML_a, XML_gsLst ); + + if (bLinearOrAxial) + { + // CT_LinearShadeProperties, cases where gradient rotation has to be exported + // 'scaled' does not exist in LO, so only 'ang'. + const sal_Int16 nAngle(pGradient->GetAngle()); + mpFS->singleElementNS( + XML_a, XML_lin, XML_ang, + OString::number(((3600 - static_cast<sal_Int32>(nAngle) + 900) * 6000) % 21600000)); + } + else + { + // CT_PathShadeProperties, cases where gradient path has to be exported + // Concentric fill is not yet implemented, therefore no type 'shape', only 'circle' or 'rect' + const bool bCircle(pGradient->GetGradientStyle() == awt::GradientStyle_RADIAL || + pGradient->GetGradientStyle() == awt::GradientStyle_ELLIPTICAL); + WriteGradientPath(*pGradient, mpFS, bCircle); + } +} + +void DrawingML::WriteLineArrow( const Reference< XPropertySet >& rXPropSet, bool bLineStart ) +{ + ESCHER_LineEnd eLineEnd; + sal_Int32 nArrowLength; + sal_Int32 nArrowWidth; + + if ( !EscherPropertyContainer::GetLineArrow( bLineStart, rXPropSet, eLineEnd, nArrowLength, nArrowWidth ) ) + return; + + const char* len; + const char* type; + const char* width; + + switch( nArrowLength ) + { + case ESCHER_LineShortArrow: + len = "sm"; + break; + default: + case ESCHER_LineMediumLenArrow: + len = "med"; + break; + case ESCHER_LineLongArrow: + len = "lg"; + break; + } + + switch( eLineEnd ) + { + default: + case ESCHER_LineNoEnd: + type = "none"; + break; + case ESCHER_LineArrowEnd: + type = "triangle"; + break; + case ESCHER_LineArrowStealthEnd: + type = "stealth"; + break; + case ESCHER_LineArrowDiamondEnd: + type = "diamond"; + break; + case ESCHER_LineArrowOvalEnd: + type = "oval"; + break; + case ESCHER_LineArrowOpenEnd: + type = "arrow"; + break; + } + + switch( nArrowWidth ) + { + case ESCHER_LineNarrowArrow: + width = "sm"; + break; + default: + case ESCHER_LineMediumWidthArrow: + width = "med"; + break; + case ESCHER_LineWideArrow: + width = "lg"; + break; + } + + mpFS->singleElementNS( XML_a, bLineStart ? XML_headEnd : XML_tailEnd, + XML_len, len, + XML_type, type, + XML_w, width ); +} + +void DrawingML::WriteOutline( const Reference<XPropertySet>& rXPropSet, Reference< frame::XModel > const & xModel ) +{ + drawing::LineStyle aLineStyle( drawing::LineStyle_NONE ); + if (GetProperty(rXPropSet, "LineStyle")) + mAny >>= aLineStyle; + + const LineCap aLineCap = GetProperty(rXPropSet, "LineCap") ? mAny.get<drawing::LineCap>() : LineCap_BUTT; + + sal_uInt32 nLineWidth = 0; + sal_uInt32 nEmuLineWidth = 0; + ::Color nColor; + sal_Int32 nColorAlpha = MAX_PERCENT; + bool bColorSet = false; + const char* cap = nullptr; + drawing::LineDash aLineDash; + bool bDashSet = false; + bool bNoFill = false; + + + // get InteropGrabBag and search the relevant attributes + OUString sColorFillScheme; + ::Color aResolvedColorFillScheme; + + ::Color nOriginalColor; + ::Color nStyleColor; + sal_uInt32 nStyleLineWidth = 0; + + Sequence<PropertyValue> aStyleProperties; + Sequence<PropertyValue> aTransformations; + + drawing::LineStyle aStyleLineStyle(drawing::LineStyle_NONE); + drawing::LineJoint aStyleLineJoint(drawing::LineJoint_NONE); + + if (GetProperty(rXPropSet, "InteropGrabBag")) + { + Sequence<PropertyValue> aGrabBag; + mAny >>= aGrabBag; + + for (const auto& rProp : std::as_const(aGrabBag)) + { + if( rProp.Name == "SpPrLnSolidFillSchemeClr" ) + rProp.Value >>= sColorFillScheme; + if( rProp.Name == "SpPrLnSolidFillResolvedSchemeClr" ) + rProp.Value >>= aResolvedColorFillScheme; + else if( rProp.Name == "OriginalLnSolidFillClr" ) + rProp.Value >>= nOriginalColor; + else if( rProp.Name == "StyleLnRef" ) + rProp.Value >>= aStyleProperties; + else if( rProp.Name == "SpPrLnSolidFillSchemeClrTransformations" ) + rProp.Value >>= aTransformations; + else if( rProp.Name == "EmuLineWidth" ) + rProp.Value >>= nEmuLineWidth; + } + for (const auto& rStyleProp : std::as_const(aStyleProperties)) + { + if( rStyleProp.Name == "Color" ) + rStyleProp.Value >>= nStyleColor; + else if( rStyleProp.Name == "LineStyle" ) + rStyleProp.Value >>= aStyleLineStyle; + else if( rStyleProp.Name == "LineJoint" ) + rStyleProp.Value >>= aStyleLineJoint; + else if( rStyleProp.Name == "LineWidth" ) + rStyleProp.Value >>= nStyleLineWidth; + } + } + + if (GetProperty(rXPropSet, "LineWidth")) + mAny >>= nLineWidth; + + switch (aLineStyle) + { + case drawing::LineStyle_NONE: + bNoFill = true; + break; + case drawing::LineStyle_DASH: + if (GetProperty(rXPropSet, "LineDash")) + { + aLineDash = mAny.get<drawing::LineDash>(); + //this query is good for shapes, but in the case of charts it returns 0 values + if (aLineDash.Dots == 0 && aLineDash.DotLen == 0 && aLineDash.Dashes == 0 && aLineDash.DashLen == 0 && aLineDash.Distance == 0) { + OUString aLineDashName; + if (GetProperty(rXPropSet, "LineDashName")) + mAny >>= aLineDashName; + if (!aLineDashName.isEmpty() && xModel) { + css::uno::Any aAny = getLineDash(xModel, aLineDashName); + aAny >>= aLineDash; + } + } + } + else + { + //export the linestyle of chart wall (plot area) and chart page + OUString aLineDashName; + if (GetProperty(rXPropSet, "LineDashName")) + mAny >>= aLineDashName; + if (!aLineDashName.isEmpty() && xModel) { + css::uno::Any aAny = getLineDash(xModel, aLineDashName); + aAny >>= aLineDash; + } + } + bDashSet = true; + if (aLineDash.Style == DashStyle_ROUND || aLineDash.Style == DashStyle_ROUNDRELATIVE) + { + cap = "rnd"; + } + + SAL_INFO("oox.shape", "dash dots: " << aLineDash.Dots << " dashes: " << aLineDash.Dashes + << " dotlen: " << aLineDash.DotLen << " dashlen: " << aLineDash.DashLen << " distance: " << aLineDash.Distance); + + [[fallthrough]]; + case drawing::LineStyle_SOLID: + default: + if (GetProperty(rXPropSet, "LineColor")) + { + nColor = ::Color(ColorTransparency, mAny.get<sal_uInt32>() & 0xffffff); + bColorSet = true; + } + if (GetProperty(rXPropSet, "LineTransparence")) + { + nColorAlpha = MAX_PERCENT - (mAny.get<sal_Int16>() * PER_PERCENT); + } + if (aLineCap == LineCap_ROUND) + cap = "rnd"; + else if (aLineCap == LineCap_SQUARE) + cap = "sq"; + break; + } + + // if the line-width was not modified after importing then the original EMU value will be exported to avoid unexpected conversion (rounding) error + if (nEmuLineWidth == 0 || static_cast<sal_uInt32>(oox::drawingml::convertEmuToHmm(nEmuLineWidth)) != nLineWidth) + nEmuLineWidth = oox::drawingml::convertHmmToEmu(nLineWidth); + mpFS->startElementNS( XML_a, XML_ln, + XML_cap, cap, + XML_w, sax_fastparser::UseIf(OString::number(nEmuLineWidth), + nLineWidth == 0 || GetDocumentType() == DOCUMENT_XLSX // tdf#119565 LO doesn't export the actual theme.xml in XLSX. + || (nLineWidth > 1 && nStyleLineWidth != nLineWidth))); + + if( bColorSet ) + { + if( nColor != nOriginalColor ) + { + // the user has set a different color for the line + if (!WriteSchemeColor(u"LineComplexColor"_ustr, rXPropSet)) + WriteSolidFill(nColor, nColorAlpha); + } + else if( !sColorFillScheme.isEmpty() ) + { + // the line had a scheme color and the user didn't change it + WriteSolidFill( aResolvedColorFillScheme, aTransformations ); + } + else + { + WriteSolidFill( nColor, nColorAlpha ); + } + } + + if( bDashSet && aStyleLineStyle != drawing::LineStyle_DASH ) + { + // Try to detect if it might come from ms preset line style import. + // MS Office styles are always relative, both binary and OOXML. + // "dot" is always the first dash and "dash" the second one. All OOXML presets linestyles + // start with the longer one. Definitions are in OOXML part 1, 20.1.10.49 + // The tests are strict, for to not catch styles from standard.sod (as of Aug 2019). + bool bIsConverted = false; + + bool bIsRelative(aLineDash.Style == DashStyle_RECTRELATIVE || aLineDash.Style == DashStyle_ROUNDRELATIVE); + if ( bIsRelative && aLineDash.Dots == 1) + { // The length were tweaked on import in case of prstDash. Revert it here. + sal_uInt32 nDotLen = aLineDash.DotLen; + sal_uInt32 nDashLen = aLineDash.DashLen; + sal_uInt32 nDistance = aLineDash.Distance; + if (aLineCap != LineCap_BUTT && nDistance >= 99) + { + nDistance -= 99; + nDotLen += 99; + if (nDashLen > 0) + nDashLen += 99; + } + // LO uses length 0 for 100%, if the attribute is missing in ODF. + // Other applications might write 100%. Make is unique for the conditions. + if (nDotLen == 0) + nDotLen = 100; + if (nDashLen == 0 && aLineDash.Dashes > 0) + nDashLen = 100; + bIsConverted = true; + if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dot"); + } + else if (nDotLen == 400 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dash"); + } + else if (nDotLen == 400 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dashDot"); + } + else if (nDotLen == 800 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDash"); + } + else if (nDotLen == 800 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDot"); + } + else if (nDotLen == 800 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 300) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDotDot"); + } + else if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDot"); + } + else if (nDotLen == 300 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDash"); + } + else if (nDotLen == 300 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 100) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDot"); + } + else if (nDotLen == 300 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 100) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDotDot"); + } + else + bIsConverted = false; + } + // Do not map our own line styles to OOXML prstDash values, because custDash gives better results. + if (!bIsConverted) + { + mpFS->startElementNS(XML_a, XML_custDash); + // In case of hairline we would need the current pixel size. Instead use a reasonable + // ersatz for it. The value is the same as SMALLEST_DASH_WIDTH in xattr.cxx. + // (And it makes sure fLineWidth is not zero in below division.) + double fLineWidth = nLineWidth > 0 ? nLineWidth : 26.95; + int i; + double fSp = bIsRelative ? aLineDash.Distance : aLineDash.Distance * 100.0 / fLineWidth; + // LO uses line width, in case Distance is zero. MS Office would use a space of zero length. + // So set 100% explicitly. + if (aLineDash.Distance <= 0) + fSp = 100.0; + // In case of custDash, round caps are included in dash length in MS Office. Square caps are added + // to dash length, same as in ODF. Change the length values accordingly. + if (aLineCap == LineCap_ROUND && fSp > 99.0) + fSp -= 99.0; + + if (aLineDash.Dots > 0) + { + double fD = bIsRelative ? aLineDash.DotLen : aLineDash.DotLen * 100.0 / fLineWidth; + // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended. + if (aLineDash.DotLen == 0) + fD = 100.0; + // Tweak dash length, see above. + if (aLineCap == LineCap_ROUND && fSp > 99.0) + fD += 99.0; + + for( i = 0; i < aLineDash.Dots; i ++ ) + { + mpFS->singleElementNS( XML_a, XML_ds, + XML_d , write1000thOfAPercent(fD), + XML_sp, write1000thOfAPercent(fSp) ); + } + } + if ( aLineDash.Dashes > 0 ) + { + double fD = bIsRelative ? aLineDash.DashLen : aLineDash.DashLen * 100.0 / fLineWidth; + // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended. + if (aLineDash.DashLen == 0) + fD = 100.0; + // Tweak dash length, see above. + if (aLineCap == LineCap_ROUND && fSp > 99.0) + fD += 99.0; + + for( i = 0; i < aLineDash.Dashes; i ++ ) + { + mpFS->singleElementNS( XML_a , XML_ds, + XML_d , write1000thOfAPercent(fD), + XML_sp, write1000thOfAPercent(fSp) ); + } + } + + SAL_WARN_IF(nLineWidth <= 0, + "oox.shape", "while writing outline - custom dash - line width was < 0 : " << nLineWidth); + SAL_WARN_IF(aLineDash.Dashes < 0, + "oox.shape", "while writing outline - custom dash - number of dashes was < 0 : " << aLineDash.Dashes); + SAL_WARN_IF(aLineDash.Dashes > 0 && aLineDash.DashLen <= 0, + "oox.shape", "while writing outline - custom dash - dash length was < 0 : " << aLineDash.DashLen); + SAL_WARN_IF(aLineDash.Dots < 0, + "oox.shape", "while writing outline - custom dash - number of dots was < 0 : " << aLineDash.Dots); + SAL_WARN_IF(aLineDash.Dots > 0 && aLineDash.DotLen <= 0, + "oox.shape", "while writing outline - custom dash - dot length was < 0 : " << aLineDash.DotLen); + SAL_WARN_IF(aLineDash.Distance <= 0, + "oox.shape", "while writing outline - custom dash - distance was < 0 : " << aLineDash.Distance); + + mpFS->endElementNS( XML_a, XML_custDash ); + } + } + + if (!bNoFill && nLineWidth > 1 && GetProperty(rXPropSet, "LineJoint")) + { + LineJoint eLineJoint = mAny.get<LineJoint>(); + + // tdf#119565 LO doesn't export the actual theme.xml in XLSX. + if (aStyleLineJoint == LineJoint_NONE || GetDocumentType() == DOCUMENT_XLSX + || 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 ); +} + +OUString DrawingML::GetComponentDir() const +{ + return OUString(getComponentDir(meDocumentType)); +} + +OUString DrawingML::GetRelationCompPrefix() const +{ + return OUString(getRelationCompPrefix(meDocumentType)); +} + +void GraphicExport::writeSvgExtension(OUString const& rSvgRelId) +{ + if (rSvgRelId.isEmpty()) + return; + + mpFS->startElementNS(XML_a, XML_extLst); + mpFS->startElementNS(XML_a, XML_ext, XML_uri, "{96DAC541-7B7A-43D3-8B79-37D633B846F1}"); + mpFS->singleElementNS(XML_asvg, XML_svgBlip, + FSNS(XML_xmlns, XML_asvg), mpFilterBase->getNamespaceURL(OOX_NS(asvg)), + FSNS(XML_r, XML_embed), rSvgRelId); + mpFS->endElementNS(XML_a, XML_ext); + mpFS->endElementNS( XML_a, XML_extLst); +} + +void GraphicExport::writeBlip(Graphic const& rGraphic, std::vector<model::BlipEffect> const& rEffects, bool bRelPathToMedia) +{ + OUString sRelId = writeToStorage(rGraphic, bRelPathToMedia); + + mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId); + + auto const& rVectorGraphicDataPtr = rGraphic.getVectorGraphicData(); + + if (rVectorGraphicDataPtr && rVectorGraphicDataPtr->getType() == VectorGraphicDataType::Svg) + { + OUString sSvgRelId = writeToStorage(rGraphic, bRelPathToMedia, TypeHint::SVG); + writeSvgExtension(sSvgRelId); + } + + for (auto const& rEffect : rEffects) + { + switch (rEffect.meType) + { + case model::BlipEffectType::AlphaBiLevel: + { + mpFS->singleElementNS(XML_a, XML_alphaBiLevel, XML_thresh, OString::number(rEffect.mnThreshold)); + } + break; + case model::BlipEffectType::AlphaCeiling: + { + mpFS->singleElementNS(XML_a, XML_alphaCeiling); + } + break; + case model::BlipEffectType::AlphaFloor: + { + mpFS->singleElementNS(XML_a, XML_alphaFloor); + } + break; + case model::BlipEffectType::AlphaInverse: + { + mpFS->singleElementNS(XML_a, XML_alphaInv); + // TODO: export rEffect.maColor1 + } + break; + case model::BlipEffectType::AlphaModulate: + { + mpFS->singleElementNS(XML_a, XML_alphaMod); + // TODO + } + break; + case model::BlipEffectType::AlphaModulateFixed: + { + mpFS->singleElementNS(XML_a, XML_alphaModFix, XML_amt, OString::number(rEffect.mnAmount)); + } + break; + case model::BlipEffectType::AlphaReplace: + { + mpFS->singleElementNS(XML_a, XML_alphaRepl, XML_a, OString::number(rEffect.mnAlpha)); + } + break; + case model::BlipEffectType::BiLevel: + { + mpFS->singleElementNS(XML_a, XML_biLevel, XML_thresh, OString::number(rEffect.mnThreshold)); + } + break; + case model::BlipEffectType::Blur: + { + mpFS->singleElementNS(XML_a, XML_blur, + XML_rad, OString::number(rEffect.mnRadius), + XML_grow, rEffect.mbGrow ? "1" : "0"); + } + break; + case model::BlipEffectType::ColorChange: + { + mpFS->startElementNS(XML_a, XML_clrChange, XML_useA, rEffect.mbUseAlpha ? "1" : "0"); + mpFS->endElementNS(XML_a, XML_clrChange); + } + break; + case model::BlipEffectType::ColorReplace: + { + mpFS->startElementNS(XML_a, XML_clrRepl); + mpFS->endElementNS(XML_a, XML_clrRepl); + } + break; + case model::BlipEffectType::DuoTone: + { + mpFS->startElementNS(XML_a, XML_duotone); + mpFS->endElementNS(XML_a, XML_duotone); + } + break; + case model::BlipEffectType::FillOverlay: + { + mpFS->singleElementNS(XML_a, XML_fillOverlay); + } + break; + case model::BlipEffectType::Grayscale: + { + mpFS->singleElementNS(XML_a, XML_grayscl); + } + break; + case model::BlipEffectType::HSL: + { + mpFS->singleElementNS(XML_a, XML_hsl, + XML_hue, OString::number(rEffect.mnHue), + XML_sat, OString::number(rEffect.mnSaturation), + XML_lum, OString::number(rEffect.mnLuminance)); + } + break; + case model::BlipEffectType::Luminance: + { + mpFS->singleElementNS(XML_a, XML_lum, + XML_bright, OString::number(rEffect.mnBrightness), + XML_contrast, OString::number(rEffect.mnContrast)); + } + break; + case model::BlipEffectType::Tint: + { + mpFS->singleElementNS(XML_a, XML_tint, + XML_hue, OString::number(rEffect.mnHue), + XML_amt, OString::number(rEffect.mnAmount)); + } + break; + + default: + break; + } + } + + mpFS->endElementNS(XML_a, XML_blip); +} + +OUString GraphicExport::writeNewEntryToStorage(const Graphic& rGraphic, bool bRelPathToMedia) +{ + GfxLink const& rLink = rGraphic.GetGfxLink(); + + OUString sMediaType; + OUString aExtension; + + SvMemoryStream aStream; + const void* aData = rLink.GetData(); + std::size_t nDataSize = rLink.GetDataSize(); + + switch (rLink.GetType()) + { + case GfxLinkType::NativeGif: + sMediaType = u"image/gif"_ustr; + aExtension = u"gif"_ustr; + break; + + // #i15508# added BMP type for better exports + // export not yet active, so adding for reference (not checked) + case GfxLinkType::NativeBmp: + sMediaType = u"image/bmp"_ustr; + aExtension = u"bmp"_ustr; + break; + + case GfxLinkType::NativeJpg: + sMediaType = u"image/jpeg"_ustr; + aExtension = u"jpeg"_ustr; + break; + case GfxLinkType::NativePng: + sMediaType = u"image/png"_ustr; + aExtension = u"png"_ustr; + break; + case GfxLinkType::NativeTif: + sMediaType = u"image/tiff"_ustr; + aExtension = u"tif"_ustr; + break; + case GfxLinkType::NativeWmf: + sMediaType = u"image/x-wmf"_ustr; + aExtension = u"wmf"_ustr; + break; + case GfxLinkType::NativeMet: + sMediaType = u"image/x-met"_ustr; + aExtension = u"met"_ustr; + break; + case GfxLinkType::NativePct: + sMediaType = u"image/x-pict"_ustr; + aExtension = u"pct"_ustr; + break; + case GfxLinkType::NativeMov: + sMediaType = u"application/movie"_ustr; + aExtension = u"MOV"_ustr; + 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 = u"image/png"_ustr; + aExtension = u"png"_ustr; + } + else + { + (void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::EMF); + sMediaType = u"image/x-emf"_ustr; + aExtension = u"emf"_ustr; + } + } + 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 OUString(); + } + + aData = aStream.GetData(); + nDataSize = aStream.GetEndOfData(); + } + break; + } + + GraphicExportCache& rGraphicExportCache = GraphicExportCache::get(); + auto sImageCountString = OUString::number(rGraphicExportCache.nextImageCount()); + + OUString sComponentDir(getComponentDir(meDocumentType)); + + OUString sImagePath = sComponentDir + u"/media/image"_ustr + sImageCountString + u"."_ustr + aExtension; + + Reference<XOutputStream> xOutStream = mpFilterBase->openFragmentStream(sImagePath, sMediaType); + xOutStream->writeBytes(Sequence<sal_Int8>(static_cast<const sal_Int8*>(aData), nDataSize)); + xOutStream->closeOutput(); + + OUString sRelationCompPrefix; + if (bRelPathToMedia) + sRelationCompPrefix = u"../"_ustr; + else + sRelationCompPrefix = getRelationCompPrefix(meDocumentType); + + OUString sPath = sRelationCompPrefix + u"media/image"_ustr + sImageCountString + u"."_ustr + aExtension; + + rGraphicExportCache.addExportGraphics(rGraphic.GetChecksum(), sPath); + + return sPath; +} + +namespace +{ +BitmapChecksum makeChecksumUniqueForSVG(BitmapChecksum const& rChecksum) +{ + // need to modify the checksum so we know it's for SVG - just invert it + return ~rChecksum; +} + +} // end anonymous namespace + +OUString GraphicExport::writeNewSvgEntryToStorage(const Graphic& rGraphic, bool bRelPathToMedia) +{ + OUString sMediaType = u"image/svg"_ustr; + OUString aExtension = u"svg"_ustr; + + GfxLink const& rLink = rGraphic.GetGfxLink(); + if (rLink.GetType() != GfxLinkType::NativeSvg) + return OUString(); + + const void* aData = rLink.GetData(); + std::size_t nDataSize = rLink.GetDataSize(); + + GraphicExportCache& rGraphicExportCache = GraphicExportCache::get(); + auto sImageCountString = OUString::number(rGraphicExportCache.nextImageCount()); + + OUString sComponentDir(getComponentDir(meDocumentType)); + + OUString sImagePath = sComponentDir + u"/media/image"_ustr + sImageCountString + u"."_ustr + aExtension; + + Reference<XOutputStream> xOutStream = mpFilterBase->openFragmentStream(sImagePath, sMediaType); + xOutStream->writeBytes(Sequence<sal_Int8>(static_cast<const sal_Int8*>(aData), nDataSize)); + xOutStream->closeOutput(); + + OUString sRelationCompPrefix; + if (bRelPathToMedia) + sRelationCompPrefix = u"../"_ustr; + else + sRelationCompPrefix = getRelationCompPrefix(meDocumentType); + + OUString sPath = sRelationCompPrefix + u"media/image"_ustr + sImageCountString + u"."_ustr + aExtension; + + rGraphicExportCache.addExportGraphics(makeChecksumUniqueForSVG(rGraphic.GetChecksum()), sPath); + + return sPath; +} + +OUString GraphicExport::writeToStorage(const Graphic& rGraphic, bool bRelPathToMedia, TypeHint eHint) +{ + OUString sPath; + + auto aChecksum = rGraphic.GetChecksum(); + if (eHint == TypeHint::SVG) + aChecksum = makeChecksumUniqueForSVG(aChecksum); + + GraphicExportCache& rGraphicExportCache = GraphicExportCache::get(); + sPath = rGraphicExportCache.findExportGraphics(aChecksum); + + if (sPath.isEmpty()) + { + if (eHint == TypeHint::SVG) + sPath = writeNewSvgEntryToStorage(rGraphic, bRelPathToMedia); + else + sPath = writeNewEntryToStorage(rGraphic, bRelPathToMedia); + + if (sPath.isEmpty()) + return OUString(); // couldn't store + } + + OUString sRelId = mpFilterBase->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::IMAGE), sPath); + + return sRelId; +} + +std::shared_ptr<GraphicExport> DrawingML::createGraphicExport() +{ + return std::make_shared<GraphicExport>(mpFS, mpFB, meDocumentType); +} + +OUString DrawingML::writeGraphicToStorage(const Graphic& rGraphic , bool bRelPathToMedia, GraphicExport::TypeHint eHint) +{ + GraphicExport aExporter(mpFS, mpFB, meDocumentType); + return aExporter.writeToStorage(rGraphic, bRelPathToMedia, eHint); +} + +void DrawingML::WriteMediaNonVisualProperties(const css::uno::Reference<css::drawing::XShape>& xShape) +{ + SdrMediaObj* pMediaObj = dynamic_cast<SdrMediaObj*>(SdrObject::getSdrObjectFromXShape(xShape)); + if (!pMediaObj) + return; + + // extension + OUString aExtension; + const OUString& rURL(pMediaObj->getURL()); + int nLastDot = rURL.lastIndexOf('.'); + if (nLastDot >= 0) + aExtension = rURL.copy(nLastDot); + + bool bEmbed = rURL.startsWith("vnd.sun.star.Package:"); + Relationship eMediaType = Relationship::VIDEO; + + // mime type +#if HAVE_FEATURE_AVMEDIA + OUString aMimeType(pMediaObj->getMediaProperties().getMimeType()); +#else + OUString aMimeType("none"); +#endif + if (aMimeType.startsWith("audio/")) + { + eMediaType = Relationship::AUDIO; + } + else + if (aMimeType == "application/vnd.sun.star.media") + { + // try to set something better + // TODO fix the importer to actually set the mimetype on import + if (aExtension.equalsIgnoreAsciiCase(".avi")) + aMimeType = "video/x-msvideo"; + else if (aExtension.equalsIgnoreAsciiCase(".flv")) + aMimeType = "video/x-flv"; + else if (aExtension.equalsIgnoreAsciiCase(".mp4")) + aMimeType = "video/mp4"; + else if (aExtension.equalsIgnoreAsciiCase(".mov")) + aMimeType = "video/quicktime"; + else if (aExtension.equalsIgnoreAsciiCase(".ogv")) + aMimeType = "video/ogg"; + else if (aExtension.equalsIgnoreAsciiCase(".wmv")) + aMimeType = "video/x-ms-wmv"; + else if (aExtension.equalsIgnoreAsciiCase(".wav")) + { + aMimeType = "audio/x-wav"; + eMediaType = Relationship::AUDIO; + } + else if (aExtension.equalsIgnoreAsciiCase(".m4a")) + { + aMimeType = "audio/mp4"; + eMediaType = Relationship::AUDIO; + } + else if (aExtension.equalsIgnoreAsciiCase(".mp3")) + { + aMimeType = "audio/mp3"; + eMediaType = Relationship::AUDIO; + } + } + + OUString aVideoFileRelId; + OUString aMediaRelId; + + if (bEmbed) + { + sal_Int32 nImageCount = GraphicExportCache::get().nextImageCount(); + + OUString sFileName = GetComponentDir() + u"/media/media"_ustr + OUString::number(nImageCount) + aExtension; + + // copy the video stream + Reference<XOutputStream> xOutStream = mpFB->openFragmentStream(sFileName, aMimeType); + + uno::Reference<io::XInputStream> xInputStream(pMediaObj->GetInputStream()); + comphelper::OStorageHelper::CopyInputToOutput(xInputStream, xOutStream); + + xOutStream->closeOutput(); + + // create the relation + OUString aPath = GetRelationCompPrefix() + u"media/media"_ustr + OUString::number(nImageCount) + aExtension; + + aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), aPath); + aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), aPath); + } + else + { + aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), rURL, true); + aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), rURL, true); + } + + GetFS()->startElementNS(XML_p, XML_nvPr); + + GetFS()->singleElementNS(XML_a, eMediaType == Relationship::VIDEO ? XML_videoFile : XML_audioFile, + FSNS(XML_r, XML_link), aVideoFileRelId); + + GetFS()->startElementNS(XML_p, XML_extLst); + // media extensions; google this ID for details + GetFS()->startElementNS(XML_p, XML_ext, XML_uri, "{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}"); + + GetFS()->singleElementNS(XML_p14, XML_media, + bEmbed? FSNS(XML_r, XML_embed): FSNS(XML_r, XML_link), aMediaRelId); + + GetFS()->endElementNS(XML_p, XML_ext); + GetFS()->endElementNS(XML_p, XML_extLst); + + GetFS()->endElementNS(XML_p, XML_nvPr); +} + +void DrawingML::WriteImageBrightnessContrastTransparence(uno::Reference<beans::XPropertySet> const & rXPropSet) +{ + sal_Int16 nBright = 0; + sal_Int32 nContrast = 0; + sal_Int32 nTransparence = 0; + + if (GetProperty(rXPropSet, "AdjustLuminance")) + nBright = mAny.get<sal_Int16>(); + if (GetProperty(rXPropSet, "AdjustContrast")) + nContrast = mAny.get<sal_Int32>(); + // Used for shapes with picture fill + if (GetProperty(rXPropSet, "FillTransparence")) + nTransparence = mAny.get<sal_Int32>(); + // Used for pictures + if (nTransparence == 0 && GetProperty(rXPropSet, "Transparency")) + nTransparence = static_cast<sal_Int32>(mAny.get<sal_Int16>()); + + if (GetProperty(rXPropSet, "GraphicColorMode")) + { + drawing::ColorMode aColorMode; + mAny >>= aColorMode; + if (aColorMode == drawing::ColorMode_GREYS) + mpFS->singleElementNS(XML_a, XML_grayscl); + else if (aColorMode == drawing::ColorMode_MONO) + //black/white has a 0,5 threshold in LibreOffice + mpFS->singleElementNS(XML_a, XML_biLevel, XML_thresh, OString::number(50000)); + else if (aColorMode == drawing::ColorMode_WATERMARK) + { + //map watermark with mso washout + nBright = 70; + nContrast = -70; + } + } + + + if (nBright || nContrast) + { + mpFS->singleElementNS(XML_a, XML_lum, + XML_bright, sax_fastparser::UseIf(OString::number(nBright * 1000), nBright != 0), + XML_contrast, sax_fastparser::UseIf(OString::number(nContrast * 1000), nContrast != 0)); + } + + if (nTransparence) + { + sal_Int32 nAlphaMod = (100 - nTransparence ) * PER_PERCENT; + mpFS->singleElementNS(XML_a, XML_alphaModFix, XML_amt, OString::number(nAlphaMod)); + } +} + +void DrawingML::WriteXGraphicBlip(uno::Reference<beans::XPropertySet> const & rXPropSet, + uno::Reference<graphic::XGraphic> const & rxGraphic, + bool bRelPathToMedia) +{ + OUString sRelId; + + if (!rxGraphic.is()) + return; + + Graphic aGraphic(rxGraphic); + + sRelId = writeGraphicToStorage(aGraphic, bRelPathToMedia); + + mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId); + + auto pVectorGraphicDataPtr = aGraphic.getVectorGraphicData(); + + if (pVectorGraphicDataPtr && pVectorGraphicDataPtr->getType() == VectorGraphicDataType::Svg) + { + GraphicExport aExporter(mpFS, mpFB, meDocumentType); + OUString sSvgRelId = aExporter.writeToStorage(aGraphic, bRelPathToMedia, GraphicExport::TypeHint::SVG); + if (!sSvgRelId.isEmpty()) + aExporter.writeSvgExtension(sSvgRelId); + } + + WriteImageBrightnessContrastTransparence(rXPropSet); + + WriteArtisticEffect(rXPropSet); + + mpFS->endElementNS(XML_a, XML_blip); +} + +void DrawingML::WriteXGraphicBlipMode(uno::Reference<beans::XPropertySet> const & rXPropSet, + uno::Reference<graphic::XGraphic> const & rxGraphic, + css::awt::Size const& rSize) +{ + BitmapMode eBitmapMode(BitmapMode_NO_REPEAT); + if (GetProperty(rXPropSet, "FillBitmapMode")) + mAny >>= eBitmapMode; + + SAL_INFO("oox.shape", "fill bitmap mode: " << int(eBitmapMode)); + + switch (eBitmapMode) + { + case BitmapMode_REPEAT: + WriteXGraphicTile(rXPropSet, rxGraphic, rSize); + break; + case BitmapMode_STRETCH: + WriteXGraphicStretch(rXPropSet, rxGraphic); + break; + case BitmapMode_NO_REPEAT: + WriteXGraphicCustomPosition(rXPropSet, rxGraphic, rSize); + break; + default: + break; + } +} + +void DrawingML::WriteBlipOrNormalFill(const Reference<XPropertySet>& xPropSet, + const OUString& rURLPropName, const awt::Size& rSize) +{ + // 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, rSize); +} + +void DrawingML::WriteBlipFill(const Reference<XPropertySet>& rXPropSet, + const OUString& sURLPropName, const awt::Size& rSize) +{ + WriteBlipFill( rXPropSet, rSize, sURLPropName, XML_a ); +} + +void DrawingML::WriteBlipFill(const Reference<XPropertySet>& rXPropSet, const awt::Size& rSize, + 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, false, rSize); + } +} + +void DrawingML::WriteXGraphicBlipFill(uno::Reference<beans::XPropertySet> const & rXPropSet, + uno::Reference<graphic::XGraphic> const & rxGraphic, + sal_Int32 nXmlNamespace, bool bWriteMode, + bool bRelPathToMedia, css::awt::Size const& rSize) +{ + 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, rSize); + } + else if(GetProperty(rXPropSet, "FillBitmapStretch")) + { + bool bStretch = mAny.get<bool>(); + + if (bStretch) + { + WriteXGraphicStretch(rXPropSet, rxGraphic); + } + } + mpFS->endElementNS(nXmlNamespace, XML_blipFill); +} + +void DrawingML::WritePattFill( const Reference< XPropertySet >& rXPropSet ) +{ + if ( GetProperty( rXPropSet, "FillHatch" ) ) + { + drawing::Hatch aHatch; + mAny >>= aHatch; + WritePattFill(rXPropSet, aHatch); + } +} + +void DrawingML::WritePattFill(const Reference<XPropertySet>& rXPropSet, const css::drawing::Hatch& rHatch) +{ + mpFS->startElementNS(XML_a, XML_pattFill, XML_prst, GetHatchPattern(rHatch)); + + sal_Int32 nAlpha = MAX_PERCENT; + if (GetProperty(rXPropSet, "FillTransparence")) + { + sal_Int32 nTransparency = 0; + mAny >>= nTransparency; + nAlpha = (MAX_PERCENT - (PER_PERCENT * nTransparency)); + } + + mpFS->startElementNS(XML_a, XML_fgClr); + WriteColor(::Color(ColorTransparency, rHatch.Color), nAlpha); + mpFS->endElementNS( XML_a , XML_fgClr ); + + ::Color nColor = COL_WHITE; + + if ( GetProperty( rXPropSet, "FillBackground" ) ) + { + bool isBackgroundFilled = false; + mAny >>= isBackgroundFilled; + if( isBackgroundFilled ) + { + if( GetProperty( rXPropSet, "FillColor" ) ) + { + mAny >>= nColor; + } + } + else + nAlpha = 0; + } + + mpFS->startElementNS(XML_a, XML_bgClr); + WriteColor(nColor, nAlpha); + mpFS->endElementNS( XML_a , XML_bgClr ); + + mpFS->endElementNS( XML_a , XML_pattFill ); +} + +void DrawingML::WriteGraphicCropProperties(uno::Reference<beans::XPropertySet> const & rXPropSet, + Size const & rOriginalSize, + MapMode const & rMapMode) +{ + if (!GetProperty(rXPropSet, "GraphicCrop")) + return; + + css::text::GraphicCrop aGraphicCropStruct; + mAny >>= aGraphicCropStruct; + + if(GetProperty(rXPropSet, "CustomShapeGeometry")) + { + // tdf#134210 GraphicCrop property is handled in import filter because of LibreOffice has not core + // feature. We cropped the bitmap physically and MSO shouldn't crop bitmap one more time. When we + // have core feature for graphic cropping in custom shapes, we should uncomment the code anymore. + + mpFS->singleElementNS( XML_a, XML_srcRect); + } + else + { + Size aOriginalSize(rOriginalSize); + + // GraphicCrop is in mm100, so in case the original size is in pixels, convert it over. + if (rMapMode.GetMapUnit() == MapUnit::MapPixel) + aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, MapMode(MapUnit::Map100thMM)); + + if ( (0 != aGraphicCropStruct.Left) || (0 != aGraphicCropStruct.Top) || (0 != aGraphicCropStruct.Right) || (0 != aGraphicCropStruct.Bottom) ) + { + mpFS->singleElementNS( XML_a, XML_srcRect, + XML_l, OString::number(rtl::math::round(aGraphicCropStruct.Left * 100000.0 / aOriginalSize.Width())), + XML_t, OString::number(rtl::math::round(aGraphicCropStruct.Top * 100000.0 / aOriginalSize.Height())), + XML_r, OString::number(rtl::math::round(aGraphicCropStruct.Right * 100000.0 / aOriginalSize.Width())), + XML_b, OString::number(rtl::math::round(aGraphicCropStruct.Bottom * 100000.0 / aOriginalSize.Height())) ); + } + } +} + +void DrawingML::WriteSrcRectXGraphic(uno::Reference<beans::XPropertySet> const & rxPropertySet, + uno::Reference<graphic::XGraphic> const & rxGraphic) +{ + Graphic aGraphic(rxGraphic); + Size aOriginalSize = aGraphic.GetPrefSize(); + const MapMode& rMapMode = aGraphic.GetPrefMapMode(); + WriteGraphicCropProperties(rxPropertySet, aOriginalSize, rMapMode); +} + +void DrawingML::WriteXGraphicStretch(uno::Reference<beans::XPropertySet> const & rXPropSet, + uno::Reference<graphic::XGraphic> const & rxGraphic) +{ + if (GetDocumentType() != DOCUMENT_DOCX) + { + // Limiting the area used for stretching is not supported in Impress. + mpFS->singleElementNS(XML_a, XML_stretch); + return; + } + + mpFS->startElementNS(XML_a, XML_stretch); + + bool bCrop = false; + if (GetProperty(rXPropSet, "GraphicCrop")) + { + css::text::GraphicCrop aGraphicCropStruct; + mAny >>= aGraphicCropStruct; + + if ((0 != aGraphicCropStruct.Left) + || (0 != aGraphicCropStruct.Top) + || (0 != aGraphicCropStruct.Right) + || (0 != aGraphicCropStruct.Bottom)) + { + Graphic aGraphic(rxGraphic); + Size aOriginalSize(aGraphic.GetPrefSize()); + mpFS->singleElementNS(XML_a, XML_fillRect, + XML_l, OString::number(((aGraphicCropStruct.Left) * 100000) / aOriginalSize.Width()), + XML_t, OString::number(((aGraphicCropStruct.Top) * 100000) / aOriginalSize.Height()), + XML_r, OString::number(((aGraphicCropStruct.Right) * 100000) / aOriginalSize.Width()), + XML_b, OString::number(((aGraphicCropStruct.Bottom) * 100000) / aOriginalSize.Height())); + bCrop = true; + } + } + + if (!bCrop) + { + mpFS->singleElementNS(XML_a, XML_fillRect); + } + + mpFS->endElementNS(XML_a, XML_stretch); +} + +static OUString lclConvertRectanglePointToToken(RectanglePoint eRectanglePoint) +{ + OUString sAlignment; + switch (eRectanglePoint) + { + case RectanglePoint_LEFT_TOP: + sAlignment = "tl"; + break; + case RectanglePoint_MIDDLE_TOP: + sAlignment = "t"; + break; + case RectanglePoint_RIGHT_TOP: + sAlignment = "tr"; + break; + case RectanglePoint_LEFT_MIDDLE: + sAlignment = "l"; + break; + case RectanglePoint_MIDDLE_MIDDLE: + sAlignment = "ctr"; + break; + case RectanglePoint_RIGHT_MIDDLE: + sAlignment = "r"; + break; + case RectanglePoint_LEFT_BOTTOM: + sAlignment = "bl"; + break; + case RectanglePoint_MIDDLE_BOTTOM: + sAlignment = "b"; + break; + case RectanglePoint_RIGHT_BOTTOM: + sAlignment = "br"; + break; + default: + break; + } + return sAlignment; +} + +void DrawingML::WriteXGraphicTile(uno::Reference<beans::XPropertySet> const& rXPropSet, + uno::Reference<graphic::XGraphic> const& rxGraphic, + css::awt::Size const& rSize) +{ + Graphic aGraphic(rxGraphic); + Size aOriginalSize(aGraphic.GetPrefSize()); + const MapMode& rMapMode = aGraphic.GetPrefMapMode(); + // if the original size is in pixel, convert it to mm100 + if (rMapMode.GetMapUnit() == MapUnit::MapPixel) + aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, + MapMode(MapUnit::Map100thMM)); + sal_Int32 nSizeX = 0; + sal_Int32 nOffsetX = 0; + if (GetProperty(rXPropSet, "FillBitmapSizeX")) + { + mAny >>= nSizeX; + if (GetProperty(rXPropSet, "FillBitmapPositionOffsetX")) + { + sal_Int32 nX = (nSizeX != 0) ? nSizeX : aOriginalSize.Width(); + if (nX < 0 && rSize.Width > 0) + nX = rSize.Width * std::abs(nX) / 100; + nOffsetX = (*o3tl::doAccess<sal_Int32>(mAny)) * nX * 3.6; + } + + // convert the X size of bitmap to a percentage + if (nSizeX > 0) + nSizeX = double(nSizeX) / aOriginalSize.Width() * 100000; + else if (nSizeX < 0) + nSizeX *= 1000; + else + nSizeX = 100000; + } + + sal_Int32 nSizeY = 0; + sal_Int32 nOffsetY = 0; + if (GetProperty(rXPropSet, "FillBitmapSizeY")) + { + mAny >>= nSizeY; + if (GetProperty(rXPropSet, "FillBitmapPositionOffsetY")) + { + sal_Int32 nY = (nSizeY != 0) ? nSizeY : aOriginalSize.Height(); + if (nY < 0 && rSize.Height > 0) + nY = rSize.Height * std::abs(nY) / 100; + nOffsetY = (*o3tl::doAccess<sal_Int32>(mAny)) * nY * 3.6; + } + + // convert the Y size of bitmap to a percentage + if (nSizeY > 0) + nSizeY = double(nSizeY) / aOriginalSize.Height() * 100000; + else if (nSizeY < 0) + nSizeY *= 1000; + else + nSizeY = 100000; + } + + // if the "Scale" setting is checked in the images settings dialog. + if (nSizeX < 0 && nSizeY < 0) + { + if (rSize.Width != 0 && rSize.Height != 0) + { + nSizeX = rSize.Width / double(aOriginalSize.Width()) * std::abs(nSizeX); + nSizeY = rSize.Height / double(aOriginalSize.Height()) * std::abs(nSizeY); + } + else + { + nSizeX = std::abs(nSizeX); + nSizeY = std::abs(nSizeY); + } + } + + OUString sRectanglePoint; + if (GetProperty(rXPropSet, "FillBitmapRectanglePoint")) + sRectanglePoint = lclConvertRectanglePointToToken(*o3tl::doAccess<RectanglePoint>(mAny)); + + mpFS->singleElementNS(XML_a, XML_tile, XML_tx, OUString::number(nOffsetX), XML_ty, + OUString::number(nOffsetY), XML_sx, OUString::number(nSizeX), XML_sy, + OUString::number(nSizeY), XML_algn, sRectanglePoint); +} + +void DrawingML::WriteXGraphicCustomPosition(uno::Reference<beans::XPropertySet> const& rXPropSet, + uno::Reference<graphic::XGraphic> const& rxGraphic, + css::awt::Size const& rSize) +{ + Graphic aGraphic(rxGraphic); + Size aOriginalSize(aGraphic.GetPrefSize()); + const MapMode& rMapMode = aGraphic.GetPrefMapMode(); + // if the original size is in pixel, convert it to mm100 + if (rMapMode.GetMapUnit() == MapUnit::MapPixel) + aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, + MapMode(MapUnit::Map100thMM)); + double nSizeX = 0; + if (GetProperty(rXPropSet, "FillBitmapSizeX")) + { + mAny >>= nSizeX; + if (nSizeX <= 0) + { + if (nSizeX == 0) + nSizeX = aOriginalSize.Width(); + else + nSizeX /= 100; // percentage + } + } + + double nSizeY = 0; + if (GetProperty(rXPropSet, "FillBitmapSizeY")) + { + mAny >>= nSizeY; + if (nSizeY <= 0) + { + if (nSizeY == 0) + nSizeY = aOriginalSize.Height(); + else + nSizeY /= 100; // percentage + } + } + + if (nSizeX < 0 && nSizeY < 0 && rSize.Width != 0 && rSize.Height != 0) + { + nSizeX = rSize.Width * std::abs(nSizeX); + nSizeY = rSize.Height * std::abs(nSizeY); + } + + sal_Int32 nL = 0, nT = 0, nR = 0, nB = 0; + if (GetProperty(rXPropSet, "FillBitmapRectanglePoint") && rSize.Width != 0 && rSize.Height != 0) + { + sal_Int32 nWidth = (1 - (nSizeX / rSize.Width)) * 100000; + sal_Int32 nHeight = (1 - (nSizeY / rSize.Height)) * 100000; + + switch (*o3tl::doAccess<RectanglePoint>(mAny)) + { + case RectanglePoint_LEFT_TOP: nR = nWidth; nB = nHeight; break; + case RectanglePoint_RIGHT_TOP: nL = nWidth; nB = nHeight; break; + case RectanglePoint_LEFT_BOTTOM: nR = nWidth; nT = nHeight; break; + case RectanglePoint_RIGHT_BOTTOM: nL = nWidth; nT = nHeight; break; + case RectanglePoint_LEFT_MIDDLE: nR = nWidth; nT = nB = nHeight / 2; break; + case RectanglePoint_RIGHT_MIDDLE: nL = nWidth; nT = nB = nHeight / 2; break; + case RectanglePoint_MIDDLE_TOP: nB = nHeight; nL = nR = nWidth / 2; break; + case RectanglePoint_MIDDLE_BOTTOM: nT = nHeight; nL = nR = nWidth / 2; break; + case RectanglePoint_MIDDLE_MIDDLE: nL = nR = nWidth / 2; nT = nB = nHeight / 2; break; + default: break; + } + } + + mpFS->startElementNS(XML_a, XML_stretch); + + mpFS->singleElementNS(XML_a, XML_fillRect, XML_l, + sax_fastparser::UseIf(OString::number(nL), nL != 0), XML_t, + sax_fastparser::UseIf(OString::number(nT), nT != 0), XML_r, + sax_fastparser::UseIf(OString::number(nR), nR != 0), XML_b, + sax_fastparser::UseIf(OString::number(nB), nB != 0)); + + mpFS->endElementNS(XML_a, XML_stretch); +} + +namespace +{ +bool IsTopGroupObj(const uno::Reference<drawing::XShape>& xShape) +{ + SdrObject* pObject = SdrObject::getSdrObjectFromXShape(xShape); + if (!pObject) + return false; + + if (pObject->getParentSdrObjectFromSdrObject()) + return false; + + return pObject->IsGroupObject(); +} +} + +void DrawingML::WriteTransformation(const Reference< XShape >& xShape, const tools::Rectangle& rRect, + sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, sal_Int32 nRotation, bool bIsGroupShape) +{ + + mpFS->startElementNS( nXmlNamespace, XML_xfrm, + XML_flipH, sax_fastparser::UseIf("1", bFlipH), + XML_flipV, sax_fastparser::UseIf("1", bFlipV), + XML_rot, sax_fastparser::UseIf(OString::number(nRotation), nRotation % 21600000 != 0)); + + sal_Int32 nLeft = rRect.Left(); + sal_Int32 nTop = rRect.Top(); + if (GetDocumentType() == DOCUMENT_DOCX && !m_xParent.is()) + { + nLeft = 0; + nTop = 0; + } + sal_Int32 nChildLeft = nLeft; + sal_Int32 nChildTop = nTop; + + mpFS->singleElementNS(XML_a, XML_off, + XML_x, OString::number(oox::drawingml::convertHmmToEmu(nLeft)), + XML_y, OString::number(oox::drawingml::convertHmmToEmu(nTop))); + mpFS->singleElementNS(XML_a, XML_ext, + XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())), + XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight()))); + + if (bIsGroupShape && (GetDocumentType() != DOCUMENT_DOCX || IsTopGroupObj(xShape))) + { + mpFS->singleElementNS(XML_a, XML_chOff, + XML_x, OString::number(oox::drawingml::convertHmmToEmu(nChildLeft)), + XML_y, OString::number(oox::drawingml::convertHmmToEmu(nChildTop))); + mpFS->singleElementNS(XML_a, XML_chExt, + XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())), + XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight()))); + } + + mpFS->endElementNS( nXmlNamespace, XML_xfrm ); +} + +void DrawingML::WriteShapeTransformation( const Reference< XShape >& rXShape, sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, bool bSuppressRotation, bool bSuppressFlipping, bool bFlippedBeforeRotation ) +{ + SAL_INFO("oox.shape", "write shape transformation"); + + Degree100 nRotation; + Degree100 nCameraRotation; + awt::Point aPos = rXShape->getPosition(); + awt::Size aSize = rXShape->getSize(); + + bool bFlipHWrite = bFlipH && !bSuppressFlipping; + bool bFlipVWrite = bFlipV && !bSuppressFlipping; + bFlipH = bFlipH && !bFlippedBeforeRotation; + bFlipV = bFlipV && !bFlippedBeforeRotation; + + if (GetDocumentType() == DOCUMENT_DOCX && m_xParent.is()) + { + awt::Point aParentPos = m_xParent->getPosition(); + aPos.X -= aParentPos.X; + aPos.Y -= aParentPos.Y; + } + + if ( aSize.Width < 0 ) + aSize.Width = 1000; + if ( aSize.Height < 0 ) + aSize.Height = 1000; + if (!bSuppressRotation) + { + SdrObject* pShape = SdrObject::getSdrObjectFromXShape(rXShape); + nRotation = pShape ? pShape->GetRotateAngle() : 0_deg100; + if ( GetDocumentType() != DOCUMENT_DOCX ) + { + int faccos=bFlipV ? -1 : 1; + int facsin=bFlipH ? -1 : 1; + aPos.X-=(1-faccos*cos(toRadians(nRotation)))*aSize.Width/2-facsin*sin(toRadians(nRotation))*aSize.Height/2; + aPos.Y-=(1-faccos*cos(toRadians(nRotation)))*aSize.Height/2+facsin*sin(toRadians(nRotation))*aSize.Width/2; + } + else if (m_xParent.is() && nRotation != 0_deg100) + { + // Position for rotated shapes inside group is not set by DocxSdrExport. + basegfx::B2DRange aRect(-aSize.Width / 2.0, -aSize.Height / 2.0, aSize.Width / 2.0, + aSize.Height / 2.0); + basegfx::B2DHomMatrix aRotateMatrix = + basegfx::utils::createRotateB2DHomMatrix(toRadians(nRotation)); + aRect.transform(aRotateMatrix); + aPos.X += -aSize.Width / 2.0 - aRect.getMinX(); + aPos.Y += -aSize.Height / 2.0 - aRect.getMinY(); + } + + // The RotateAngle property's value is independent from any flipping, and that's exactly what we need here. + uno::Reference<beans::XPropertySet> xPropertySet(rXShape, uno::UNO_QUERY); + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); + if (xPropertySetInfo->hasPropertyByName("RotateAngle")) + { + sal_Int32 nTmp; + if (xPropertySet->getPropertyValue("RotateAngle") >>= nTmp) + nRotation = Degree100(nTmp); + } + // tdf#133037: restore original rotate angle before output + if (nRotation && xPropertySetInfo->hasPropertyByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG)) + { + uno::Sequence<beans::PropertyValue> aGrabBagProps; + xPropertySet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= aGrabBagProps; + auto p3DEffectProps = std::find_if(std::cbegin(aGrabBagProps), std::cend(aGrabBagProps), + [](const PropertyValue& rProp) { return rProp.Name == "3DEffectProperties"; }); + if (p3DEffectProps != std::cend(aGrabBagProps)) + { + uno::Sequence<beans::PropertyValue> a3DEffectProps; + p3DEffectProps->Value >>= a3DEffectProps; + auto pCameraProps = std::find_if(std::cbegin(a3DEffectProps), std::cend(a3DEffectProps), + [](const PropertyValue& rProp) { return rProp.Name == "Camera"; }); + if (pCameraProps != std::cend(a3DEffectProps)) + { + uno::Sequence<beans::PropertyValue> aCameraProps; + pCameraProps->Value >>= aCameraProps; + auto pZRotationProp = std::find_if(std::cbegin(aCameraProps), std::cend(aCameraProps), + [](const PropertyValue& rProp) { return rProp.Name == "rotRev"; }); + if (pZRotationProp != std::cend(aCameraProps)) + { + sal_Int32 nTmp = 0; + pZRotationProp->Value >>= nTmp; + nCameraRotation = NormAngle36000(Degree100(nTmp / -600)); + } + } + } + } + } + + // OOXML flips shapes before rotating them. + if(bFlipH != bFlipV) + nRotation = 36000_deg100 - nRotation; + + WriteTransformation(rXShape, tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), nXmlNamespace, + bFlipHWrite, bFlipVWrite, ExportRotateClockwisify(nRotation + nCameraRotation), IsGroupShape( rXShape )); +} + +static OUString lcl_GetTarget(const css::uno::Reference<css::frame::XModel>& xModel, OUString& rURL) +{ + Reference<drawing::XDrawPagesSupplier> xDPS(xModel, uno::UNO_QUERY_THROW); + Reference<drawing::XDrawPages> xDrawPages(xDPS->getDrawPages(), uno::UNO_SET_THROW); + sal_uInt32 nPageCount = xDrawPages->getCount(); + OUString sTarget; + + for (sal_uInt32 i = 0; i < nPageCount; ++i) + { + Reference<XDrawPage> xDrawPage; + xDrawPages->getByIndex(i) >>= xDrawPage; + Reference<container::XNamed> xNamed(xDrawPage, UNO_QUERY); + if (!xNamed) + continue; + OUString sSlideName = "#" + xNamed->getName(); + if (rURL == sSlideName) + { + sTarget = "slide" + OUString::number(i + 1) + ".xml"; + break; + } + } + if (sTarget.isEmpty()) + { + sal_Int32 nSplit = rURL.lastIndexOf(' '); + if (nSplit > -1) + sTarget = OUString::Concat("slide") + rURL.subView(nSplit + 1) + ".xml"; + } + + return sTarget; +} + +void DrawingML::WriteRunProperties( const Reference< XPropertySet >& rRun, bool bIsField, sal_Int32 nElement, + bool bCheckDirect,bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, + sal_Int16 nScriptType, const Reference< XPropertySet >& rXShapePropSet) +{ + Reference< XPropertySet > rXPropSet = rRun; + Reference< XPropertyState > rXPropState( rRun, UNO_QUERY ); + OUString usLanguage; + PropertyState eState; + bool bComplex = ( nScriptType == css::i18n::ScriptType::COMPLEX ); + const char* bold = "0"; + const char* italic = nullptr; + const char* underline = nullptr; + const char* strikeout = nullptr; + const char* cap = nullptr; + sal_Int32 nSize = 1800; + sal_Int32 nCharEscapement = 0; + sal_Int32 nCharKerning = 0; + sal_Int32 nCharEscapementHeight = 0; + + if ( nElement == XML_endParaRPr && rbOverridingCharHeight ) + { + nSize = rnCharHeight; + } + else if (GetProperty(rXPropSet, "CharHeight")) + { + nSize = static_cast<sal_Int32>(100*(*o3tl::doAccess<float>(mAny))); + if ( nElement == XML_rPr || nElement == XML_defRPr ) + { + rbOverridingCharHeight = true; + rnCharHeight = nSize; + } + } + + if (GetProperty(rXPropSet, "CharKerning")) + nCharKerning = static_cast<sal_Int32>(*o3tl::doAccess<sal_Int16>(mAny)); + /** While setting values in propertymap, + * CharKerning converted using GetTextSpacingPoint + * i.e set @ https://opengrok.libreoffice.org/xref/core/oox/source/drawingml/textcharacterproperties.cxx#129 + * therefore to get original value CharKerning need to be convert. + * https://opengrok.libreoffice.org/xref/core/oox/source/drawingml/drawingmltypes.cxx#95 + **/ + nCharKerning = ((nCharKerning * 720)-360) / 254; + + if ((bComplex && GetProperty(rXPropSet, "CharWeightComplex")) + || GetProperty(rXPropSet, "CharWeight")) + { + if ( *o3tl::doAccess<float>(mAny) >= awt::FontWeight::SEMIBOLD ) + bold = "1"; + } + + if ((bComplex && GetProperty(rXPropSet, "CharPostureComplex")) + || GetProperty(rXPropSet, "CharPosture")) + switch ( *o3tl::doAccess<awt::FontSlant>(mAny) ) + { + case awt::FontSlant_OBLIQUE : + case awt::FontSlant_ITALIC : + italic = "1"; + break; + default: + break; + } + + if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharUnderline", eState) + && eState == beans::PropertyState_DIRECT_VALUE) + || GetProperty(rXPropSet, "CharUnderline")) + { + switch ( *o3tl::doAccess<sal_Int16>(mAny) ) + { + case awt::FontUnderline::SINGLE : + underline = "sng"; + break; + case awt::FontUnderline::DOUBLE : + underline = "dbl"; + break; + case awt::FontUnderline::DOTTED : + underline = "dotted"; + break; + case awt::FontUnderline::DASH : + underline = "dash"; + break; + case awt::FontUnderline::LONGDASH : + underline = "dashLong"; + break; + case awt::FontUnderline::DASHDOT : + underline = "dotDash"; + break; + case awt::FontUnderline::DASHDOTDOT : + underline = "dotDotDash"; + break; + case awt::FontUnderline::WAVE : + underline = "wavy"; + break; + case awt::FontUnderline::DOUBLEWAVE : + underline = "wavyDbl"; + break; + case awt::FontUnderline::BOLD : + underline = "heavy"; + break; + case awt::FontUnderline::BOLDDOTTED : + underline = "dottedHeavy"; + break; + case awt::FontUnderline::BOLDDASH : + underline = "dashHeavy"; + break; + case awt::FontUnderline::BOLDLONGDASH : + underline = "dashLongHeavy"; + break; + case awt::FontUnderline::BOLDDASHDOT : + underline = "dotDashHeavy"; + break; + case awt::FontUnderline::BOLDDASHDOTDOT : + underline = "dotDotDashHeavy"; + break; + case awt::FontUnderline::BOLDWAVE : + underline = "wavyHeavy"; + break; + } + } + + if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharStrikeout", eState) + && eState == beans::PropertyState_DIRECT_VALUE) + || GetProperty(rXPropSet, "CharStrikeout")) + { + switch ( *o3tl::doAccess<sal_Int16>(mAny) ) + { + case awt::FontStrikeout::NONE : + strikeout = "noStrike"; + break; + case awt::FontStrikeout::SINGLE : + // LibO supports further values of character + // strikeout, OOXML standard (20.1.10.78, + // ST_TextStrikeType) however specifies only + // 3 - single, double and none. Approximate + // the remaining ones by single strike (better + // some strike than none at all). + // TODO: figure out how to do this better + case awt::FontStrikeout::BOLD : + case awt::FontStrikeout::SLASH : + case awt::FontStrikeout::X : + strikeout = "sngStrike"; + break; + case awt::FontStrikeout::DOUBLE : + strikeout = "dblStrike"; + break; + } + } + + bool bLang = false; + switch(nScriptType) + { + case css::i18n::ScriptType::ASIAN: + bLang = GetProperty(rXPropSet, "CharLocaleAsian"); break; + case css::i18n::ScriptType::COMPLEX: + bLang = GetProperty(rXPropSet, "CharLocaleComplex"); break; + default: + bLang = GetProperty(rXPropSet, "CharLocale"); break; + } + + if (bLang) + { + css::lang::Locale aLocale; + mAny >>= aLocale; + LanguageTag aLanguageTag( aLocale); + if (!aLanguageTag.isSystemLocale()) + usLanguage = aLanguageTag.getBcp47MS(); + } + + if (GetPropertyAndState(rXPropSet, rXPropState, "CharEscapement", eState) + && eState == beans::PropertyState_DIRECT_VALUE) + mAny >>= nCharEscapement; + + if (GetPropertyAndState(rXPropSet, rXPropState, "CharEscapementHeight", eState) + && eState == beans::PropertyState_DIRECT_VALUE) + mAny >>= nCharEscapementHeight; + + if (DFLT_ESC_AUTO_SUPER == nCharEscapement) + { + // Raised by the differences between the ascenders (ascent = baseline to top of highest letter). + // The ascent is generally about 80% of the total font height. + // That is why DFLT_ESC_PROP (58) leads to 33% (DFLT_ESC_SUPER) + nCharEscapement = .8 * (100 - nCharEscapementHeight); + } + else if (DFLT_ESC_AUTO_SUB == nCharEscapement) + { + // Lowered by the differences between the descenders (descent = baseline to bottom of lowest letter). + // The descent is generally about 20% of the total font height. + // That is why DFLT_ESC_PROP (58) leads to 8% (DFLT_ESC_SUB) + nCharEscapement = .2 * -(100 - nCharEscapementHeight); + } + + if (nCharEscapement && nCharEscapementHeight) + { + nSize = (nSize * nCharEscapementHeight) / 100; + // MSO uses default ~58% size + nSize = (nSize / 0.58); + } + + if (GetProperty(rXPropSet, "CharCaseMap")) + { + switch ( *o3tl::doAccess<sal_Int16>(mAny) ) + { + case CaseMap::UPPERCASE : + cap = "all"; + break; + case CaseMap::SMALLCAPS : + cap = "small"; + break; + } + } + + mpFS->startElementNS( XML_a, nElement, + XML_b, bold, + XML_i, italic, + XML_lang, sax_fastparser::UseIf(usLanguage, !usLanguage.isEmpty()), + XML_sz, OString::number(nSize), + // For Condensed character spacing spc value is negative. + XML_spc, sax_fastparser::UseIf(OString::number(nCharKerning), nCharKerning != 0), + XML_strike, strikeout, + XML_u, underline, + XML_baseline, sax_fastparser::UseIf(OString::number(nCharEscapement*1000), nCharEscapement != 0), + XML_cap, cap ); + + // Fontwork-shapes in LO have text outline and fill from shape stroke and shape fill + // PowerPoint has this as run properties + if (IsFontworkShape(rXShapePropSet)) + { + WriteOutline(rXShapePropSet); + WriteBlipOrNormalFill(rXShapePropSet, "Graphic"); + WriteShapeEffects(rXShapePropSet); + } + else + { + // mso doesn't like text color to be placed after typeface + if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharColor", eState) + && eState == beans::PropertyState_DIRECT_VALUE) + || GetProperty(rXPropSet, "CharColor")) + { + ::Color color( ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny) ); + SAL_INFO("oox.shape", "run color: " << sal_uInt32(color) << " auto: " << sal_uInt32(COL_AUTO)); + + // WriteSolidFill() handles MAX_PERCENT as "no transparency". + sal_Int32 nTransparency = MAX_PERCENT; + if (rXPropSet->getPropertySetInfo()->hasPropertyByName("CharTransparence")) + { + rXPropSet->getPropertyValue("CharTransparence") >>= nTransparency; + // UNO scale is 0..100, OOXML scale is 0..100000; also UNO tracks transparency, OOXML + // tracks opacity. + nTransparency = MAX_PERCENT - (nTransparency * PER_PERCENT); + } + + bool bContoured = false; + if (GetProperty(rXPropSet, "CharContoured")) + bContoured = *o3tl::doAccess<bool>(mAny); + + // tdf#127696 If the CharContoured is true, then the text color is white and the outline color is the CharColor. + if (bContoured) + { + mpFS->startElementNS(XML_a, XML_ln); + if (color == COL_AUTO) + { + mbIsBackgroundDark ? WriteSolidFill(COL_WHITE) : WriteSolidFill(COL_BLACK); + } + else + { + color.SetAlpha(255); + if (!WriteSchemeColor(u"CharComplexColor"_ustr, rXPropSet)) + WriteSolidFill(color, nTransparency); + } + mpFS->endElementNS(XML_a, XML_ln); + + WriteSolidFill(COL_WHITE); + } + // tdf#104219 In LibreOffice and MS Office, there are two types of colors: + // Automatic and Fixed. OOXML is setting automatic color, by not providing color. + else if( color != COL_AUTO ) + { + color.SetAlpha(255); + // TODO: special handle embossed/engraved + if (!WriteSchemeColor(u"CharComplexColor"_ustr, rXPropSet)) + { + WriteSolidFill(color, nTransparency); + } + } + else if (GetDocumentType() == DOCUMENT_PPTX) + { + // Resolve COL_AUTO for PPTX since MS Powerpoint doesn't have automatic colors. + bool bIsTextBackgroundDark = mbIsBackgroundDark; + if (rXShapePropSet.is() && GetProperty(rXShapePropSet, "FillStyle") + && mAny.get<FillStyle>() != FillStyle_NONE + && GetProperty(rXShapePropSet, "FillColor")) + { + ::Color aShapeFillColor(ColorTransparency, mAny.get<sal_uInt32>()); + bIsTextBackgroundDark = aShapeFillColor.IsDark(); + } + + if (bIsTextBackgroundDark) + WriteSolidFill(COL_WHITE); + else + WriteSolidFill(COL_BLACK); + } + } + } + + // tdf#128096, exporting XML_highlight to docx already works fine, + // so make sure this code is only run when exporting to pptx, just in case + if (GetDocumentType() == DOCUMENT_PPTX) + { + if (GetProperty(rXPropSet, "CharBackColor")) + { + ::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny)); + if( color != COL_AUTO ) + { + mpFS->startElementNS(XML_a, XML_highlight); + WriteColor( color ); + mpFS->endElementNS( XML_a, XML_highlight ); + } + } + } + + if (underline + && ((bCheckDirect + && GetPropertyAndState(rXPropSet, rXPropState, "CharUnderlineColor", eState) + && eState == beans::PropertyState_DIRECT_VALUE) + || GetProperty(rXPropSet, "CharUnderlineColor"))) + { + ::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny)); + // if color is automatic, then we shouldn't write information about color but to take color from character + if( color != COL_AUTO ) + { + mpFS->startElementNS(XML_a, XML_uFill); + WriteSolidFill( color ); + mpFS->endElementNS( XML_a, XML_uFill ); + } + else + { + mpFS->singleElementNS(XML_a, XML_uFillTx); + } + } + + if (GetProperty(rXPropSet, "CharFontName")) + { + const char* const pitch = nullptr; + const char* const charset = nullptr; + OUString usTypeface; + + mAny >>= usTypeface; + OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) ); + if (!aSubstName.isEmpty()) + usTypeface = aSubstName; + + mpFS->singleElementNS( XML_a, XML_latin, + XML_typeface, usTypeface, + XML_pitchFamily, pitch, + XML_charset, charset ); + } + + if ((bComplex + && (GetPropertyAndState(rXPropSet, rXPropState, "CharFontNameComplex", eState) + && eState == beans::PropertyState_DIRECT_VALUE)) + || (!bComplex + && (GetPropertyAndState(rXPropSet, rXPropState, "CharFontNameAsian", eState) + && eState == beans::PropertyState_DIRECT_VALUE))) + { + const char* const pitch = nullptr; + const char* const charset = nullptr; + OUString usTypeface; + + mAny >>= usTypeface; + OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) ); + if (!aSubstName.isEmpty()) + usTypeface = aSubstName; + + mpFS->singleElementNS( XML_a, bComplex ? XML_cs : XML_ea, + XML_typeface, usTypeface, + XML_pitchFamily, pitch, + XML_charset, charset ); + } + + if( bIsField ) + { + Reference< XTextField > rXTextField; + if (GetProperty(rXPropSet, "TextField")) + mAny >>= rXTextField; + if( rXTextField.is() ) + rXPropSet.set( rXTextField, UNO_QUERY ); + } + + // field properties starts here + if (GetProperty(rXPropSet, "URL")) + { + OUString sURL; + + mAny >>= sURL; + if (!sURL.isEmpty()) + { + if (!sURL.match("#action?jump=")) + { + bool bExtURL = URLTransformer().isExternalURL(sURL); + sURL = bExtURL ? sURL : lcl_GetTarget(GetFB()->getModel(), sURL); + + OUString sRelId + = mpFB->addRelation(mpFS->getOutputStream(), + bExtURL ? oox::getRelationship(Relationship::HYPERLINK) + : oox::getRelationship(Relationship::SLIDE), + sURL, bExtURL); + + if (bExtURL) + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); + else + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId, + XML_action, "ppaction://hlinksldjump"); + } + else + { + sal_Int32 nIndex = sURL.indexOf('='); + std::u16string_view aDestination(sURL.subView(nIndex + 1)); + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action, + OUString::Concat("ppaction://hlinkshowjump?jump=") + aDestination); + } + } + } + mpFS->endElementNS( XML_a, nElement ); +} + +OUString DrawingML::GetFieldValue( const css::uno::Reference< css::text::XTextRange >& rRun, bool& bIsURLField ) +{ + Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY ); + OUString aFieldType, aFieldValue; + + if (GetProperty(rXPropSet, "TextPortionType")) + { + aFieldType = *o3tl::doAccess<OUString>(mAny); + SAL_INFO("oox.shape", "field type: " << aFieldType); + } + + if( aFieldType == "TextField" ) + { + Reference< XTextField > rXTextField; + if (GetProperty(rXPropSet, "TextField")) + mAny >>= rXTextField; + if( rXTextField.is() ) + { + rXPropSet.set( rXTextField, UNO_QUERY ); + if( rXPropSet.is() ) + { + OUString aFieldKind( rXTextField->getPresentation( true ) ); + SAL_INFO("oox.shape", "field kind: " << aFieldKind); + if( aFieldKind == "Page" ) + { + aFieldValue = "slidenum"; + } + else if( aFieldKind == "Pages" ) + { + aFieldValue = "slidecount"; + } + else if( aFieldKind == "PageName" ) + { + aFieldValue = "slidename"; + } + else if( aFieldKind == "URL" ) + { + bIsURLField = true; + if (GetProperty(rXPropSet, "Representation")) + mAny >>= aFieldValue; + + } + else if(aFieldKind == "Date") + { + sal_Int32 nNumFmt = -1; + rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt; + aFieldValue = GetDatetimeTypeFromDate(static_cast<SvxDateFormat>(nNumFmt)); + } + else if(aFieldKind == "ExtTime") + { + sal_Int32 nNumFmt = -1; + rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt; + aFieldValue = GetDatetimeTypeFromTime(static_cast<SvxTimeFormat>(nNumFmt)); + } + else if(aFieldKind == "ExtFile") + { + sal_Int32 nNumFmt = -1; + rXPropSet->getPropertyValue(UNO_TC_PROP_FILE_FORMAT) >>= nNumFmt; + switch(nNumFmt) + { + case 0: aFieldValue = "file"; // Path/File name + break; + case 1: aFieldValue = "file1"; // Path + break; + case 2: aFieldValue = "file2"; // File name without extension + break; + case 3: aFieldValue = "file3"; // File name with extension + } + } + else if(aFieldKind == "Author") + { + aFieldValue = "author"; + } + } + } + } + return aFieldValue; +} + +OUString DrawingML::GetDatetimeTypeFromDate(SvxDateFormat eDate) +{ + return GetDatetimeTypeFromDateTime(eDate, SvxTimeFormat::AppDefault); +} + +OUString DrawingML::GetDatetimeTypeFromTime(SvxTimeFormat eTime) +{ + return GetDatetimeTypeFromDateTime(SvxDateFormat::AppDefault, eTime); +} + +OUString DrawingML::GetDatetimeTypeFromDateTime(SvxDateFormat eDate, SvxTimeFormat eTime) +{ + OUString aDateField; + switch (eDate) + { + case SvxDateFormat::StdSmall: + case SvxDateFormat::A: + aDateField = "datetime"; + break; + case SvxDateFormat::B: + aDateField = "datetime1"; // 13/02/1996 + break; + case SvxDateFormat::C: + aDateField = "datetime5"; + break; + case SvxDateFormat::D: + aDateField = "datetime3"; // 13 February 1996 + break; + case SvxDateFormat::StdBig: + case SvxDateFormat::E: + case SvxDateFormat::F: + aDateField = "datetime2"; + break; + default: + break; + } + + OUString aTimeField; + switch (eTime) + { + case SvxTimeFormat::Standard: + case SvxTimeFormat::HH24_MM_SS: + case SvxTimeFormat::HH24_MM_SS_00: + aTimeField = "datetime11"; // 13:49:38 + break; + case SvxTimeFormat::HH24_MM: + aTimeField = "datetime10"; // 13:49 + break; + case SvxTimeFormat::HH12_MM: + case SvxTimeFormat::HH12_MM_AMPM: + aTimeField = "datetime12"; // 01:49 PM + break; + case SvxTimeFormat::HH12_MM_SS: + case SvxTimeFormat::HH12_MM_SS_AMPM: + case SvxTimeFormat::HH12_MM_SS_00: + case SvxTimeFormat::HH12_MM_SS_00_AMPM: + aTimeField = "datetime13"; // 01:49:38 PM + break; + default: + break; + } + + if (!aDateField.isEmpty() && aTimeField.isEmpty()) + return aDateField; + else if (!aTimeField.isEmpty() && aDateField.isEmpty()) + return aTimeField; + else if (!aDateField.isEmpty() && !aTimeField.isEmpty()) + { + if (aTimeField == "datetime11" || aTimeField == "datetime13") + // only datetime format that has Date and HH:MM:SS + return "datetime9"; // dd/mm/yyyy H:MM:SS + else + // only datetime format that has Date and HH:MM + return "datetime8"; // dd/mm/yyyy H:MM + } + else + return ""; +} + +void DrawingML::WriteRun( const Reference< XTextRange >& rRun, + bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, + const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet) +{ + Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY ); + sal_Int16 nLevel = -1; + if (GetProperty(rXPropSet, "NumberingLevel")) + mAny >>= nLevel; + + bool bNumberingIsNumber = true; + if (GetProperty(rXPropSet, "NumberingIsNumber")) + mAny >>= bNumberingIsNumber; + + float nFontSize = -1; + if (GetProperty(rXPropSet, "CharHeight")) + mAny >>= nFontSize; + + bool bIsURLField = false; + OUString sFieldValue = GetFieldValue( rRun, bIsURLField ); + bool bWriteField = !( sFieldValue.isEmpty() || bIsURLField ); + + OUString sText = rRun->getString(); + + //if there is no text following the bullet, add a space after the bullet + if (nLevel !=-1 && bNumberingIsNumber && sText.isEmpty() ) + sText=" "; + + if ( bIsURLField ) + sText = sFieldValue; + + if( sText.isEmpty()) + { + Reference< XPropertySet > xPropSet( rRun, UNO_QUERY ); + + try + { + if( !xPropSet.is() || !( xPropSet->getPropertyValue( "PlaceholderText" ) >>= sText ) ) + return; + if( sText.isEmpty() ) + return; + } + catch (const Exception &) + { + return; + } + } + + if (sText == "\n") + { + // Empty run? Do not forget to write the font size in case of pptx: + if ((GetDocumentType() == DOCUMENT_PPTX) && (nFontSize != -1)) + { + mpFS->startElementNS(XML_a, XML_br); + mpFS->singleElementNS(XML_a, XML_rPr, XML_sz, + OString::number(nFontSize * 100)); + mpFS->endElementNS(XML_a, XML_br); + } + else + mpFS->singleElementNS(XML_a, XML_br); + } + else + { + if( bWriteField ) + { + OString sUUID(comphelper::xml::generateGUIDString()); + mpFS->startElementNS( XML_a, XML_fld, + XML_id, sUUID.getStr(), + XML_type, sFieldValue ); + } + else + { + mpFS->startElementNS(XML_a, XML_r); + } + + Reference< XPropertySet > xPropSet( rRun, uno::UNO_QUERY ); + + WriteRunProperties( xPropSet, bIsURLField, XML_rPr, true, rbOverridingCharHeight, rnCharHeight, GetScriptType(sText), rXShapePropSet); + mpFS->startElementNS(XML_a, XML_t); + mpFS->writeEscaped( sText ); + mpFS->endElementNS( XML_a, XML_t ); + + if( bWriteField ) + mpFS->endElementNS( XML_a, XML_fld ); + else + mpFS->endElementNS( XML_a, XML_r ); + } +} + +static OUString GetAutoNumType(SvxNumType nNumberingType, bool bSDot, bool bPBehind, bool bPBoth) +{ + OUString sPrefixSuffix; + + if (bPBoth) + sPrefixSuffix = "ParenBoth"; + else if (bPBehind) + sPrefixSuffix = "ParenR"; + else if (bSDot) + sPrefixSuffix = "Period"; + + switch( nNumberingType ) + { + case SVX_NUM_CHARS_UPPER_LETTER_N : + case SVX_NUM_CHARS_UPPER_LETTER : + return "alphaUc" + sPrefixSuffix; + + case SVX_NUM_CHARS_LOWER_LETTER_N : + case SVX_NUM_CHARS_LOWER_LETTER : + return "alphaLc" + sPrefixSuffix; + + case SVX_NUM_ROMAN_UPPER : + return "romanUc" + sPrefixSuffix; + + case SVX_NUM_ROMAN_LOWER : + return "romanLc" + sPrefixSuffix; + + case SVX_NUM_ARABIC : + { + if (sPrefixSuffix.isEmpty()) + return "arabicPlain"; + else + return "arabic" + sPrefixSuffix; + } + default: + break; + } + + return OUString(); +} + +void DrawingML::WriteParagraphNumbering(const Reference< XPropertySet >& rXPropSet, float fFirstCharHeight, sal_Int16 nLevel ) +{ + if (nLevel < 0 || !GetProperty(rXPropSet, "NumberingRules")) + return; + + Reference< XIndexAccess > rXIndexAccess; + + if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount()) + return; + + SAL_INFO("oox.shape", "numbering rules"); + + Sequence<PropertyValue> aPropertySequence; + rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence; + + if (!aPropertySequence.hasElements()) + return; + + SvxNumType nNumberingType = SVX_NUM_NUMBER_NONE; + bool bSDot = false; + bool bPBehind = false; + bool bPBoth = false; + sal_Unicode aBulletChar = 0x2022; // a bullet + awt::FontDescriptor aFontDesc; + bool bHasFontDesc = false; + uno::Reference<graphic::XGraphic> xGraphic; + sal_Int16 nBulletRelSize = 0; + sal_Int16 nStartWith = 1; + ::Color nBulletColor; + bool bHasBulletColor = false; + awt::Size aGraphicSize; + + for ( const PropertyValue& rPropValue : std::as_const(aPropertySequence) ) + { + OUString aPropName( rPropValue.Name ); + SAL_INFO("oox.shape", "pro name: " << aPropName); + if ( aPropName == "NumberingType" ) + { + nNumberingType = static_cast<SvxNumType>(*o3tl::doAccess<sal_Int16>(rPropValue.Value)); + } + else if ( aPropName == "Prefix" ) + { + if( *o3tl::doAccess<OUString>(rPropValue.Value) == ")") + bPBoth = true; + } + else if ( aPropName == "Suffix" ) + { + auto s = o3tl::doAccess<OUString>(rPropValue.Value); + if( *s == ".") + bSDot = true; + else if( *s == ")") + bPBehind = true; + } + else if(aPropName == "BulletColor") + { + nBulletColor = ::Color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(rPropValue.Value)); + bHasBulletColor = true; + } + else if ( aPropName == "BulletChar" ) + { + aBulletChar = (*o3tl::doAccess<OUString>(rPropValue.Value))[ 0 ]; + } + else if ( aPropName == "BulletFont" ) + { + aFontDesc = *o3tl::doAccess<awt::FontDescriptor>(rPropValue.Value); + bHasFontDesc = true; + + // Our numbullet dialog has set the wrong textencoding for our "StarSymbol" font, + // instead of a Unicode encoding the encoding RTL_TEXTENCODING_SYMBOL was used. + // Because there might exist a lot of damaged documents I added this two lines + // which fixes the bullet problem for the export. + if ( aFontDesc.Name.equalsIgnoreAsciiCase("StarSymbol") ) + aFontDesc.CharSet = RTL_TEXTENCODING_MS_1252; + + } + else if ( aPropName == "BulletRelSize" ) + { + nBulletRelSize = *o3tl::doAccess<sal_Int16>(rPropValue.Value); + } + else if ( aPropName == "StartWith" ) + { + nStartWith = *o3tl::doAccess<sal_Int16>(rPropValue.Value); + } + else if (aPropName == "GraphicBitmap") + { + auto xBitmap = rPropValue.Value.get<uno::Reference<awt::XBitmap>>(); + xGraphic.set(xBitmap, uno::UNO_QUERY); + } + else if ( aPropName == "GraphicSize" ) + { + aGraphicSize = *o3tl::doAccess<awt::Size>(rPropValue.Value); + SAL_INFO("oox.shape", "graphic size: " << aGraphicSize.Width << "x" << aGraphicSize.Height); + } + } + + if (nNumberingType == SVX_NUM_NUMBER_NONE) + return; + + Graphic aGraphic(xGraphic); + if (xGraphic.is() && aGraphic.GetType() != GraphicType::NONE) + { + tools::Long nFirstCharHeightMm = TransformMetric(fFirstCharHeight * 100.f, FieldUnit::POINT, FieldUnit::MM); + float fBulletSizeRel = aGraphicSize.Height / static_cast<float>(nFirstCharHeightMm) / OOX_BULLET_LIST_SCALE_FACTOR; + + OUString sRelationId; + + if (fBulletSizeRel < 1.0f) + { + // Add padding to get the bullet point centered in PPT + Size aDestSize(64, 64); + float fBulletSizeRelX = fBulletSizeRel / aGraphicSize.Height * aGraphicSize.Width; + tools::Long nPaddingX = std::max<tools::Long>(0, std::lround((aDestSize.Width() - fBulletSizeRelX * aDestSize.Width()) / 2.f)); + tools::Long nPaddingY = std::lround((aDestSize.Height() - fBulletSizeRel * aDestSize.Height()) / 2.f); + tools::Rectangle aDestRect(nPaddingX, nPaddingY, aDestSize.Width() - nPaddingX, aDestSize.Height() - nPaddingY); + + AlphaMask aMask(aDestSize); + aMask.Erase(255); + BitmapEx aSourceBitmap(aGraphic.GetBitmapEx()); + aSourceBitmap.Scale(aDestRect.GetSize()); + tools::Rectangle aSourceRect(Point(0, 0), aDestRect.GetSize()); + BitmapEx aDestBitmap(Bitmap(aDestSize, vcl::PixelFormat::N24_BPP), aMask); + aDestBitmap.CopyPixel(aDestRect, aSourceRect, aSourceBitmap); + Graphic aDestGraphic(aDestBitmap); + sRelationId = writeGraphicToStorage(aDestGraphic); + fBulletSizeRel = 1.0f; + } + else + { + sRelationId = writeGraphicToStorage(aGraphic); + } + + mpFS->singleElementNS( XML_a, XML_buSzPct, + XML_val, OString::number(std::min<sal_Int32>(std::lround(100000.f * fBulletSizeRel), 400000))); + mpFS->startElementNS(XML_a, XML_buBlip); + mpFS->singleElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelationId); + mpFS->endElementNS( XML_a, XML_buBlip ); + } + else + { + if(bHasBulletColor) + { + if (nBulletColor == COL_AUTO ) + { + nBulletColor = ::Color(ColorTransparency, mbIsBackgroundDark ? 0xffffff : 0x000000); + } + mpFS->startElementNS(XML_a, XML_buClr); + WriteColor( nBulletColor ); + mpFS->endElementNS( XML_a, XML_buClr ); + } + + if( nBulletRelSize && nBulletRelSize != 100 ) + mpFS->singleElementNS( XML_a, XML_buSzPct, + XML_val, OString::number(std::clamp<sal_Int32>(1000*nBulletRelSize, 25000, 400000))); + if( bHasFontDesc ) + { + if ( SVX_NUM_CHAR_SPECIAL == nNumberingType ) + aBulletChar = SubstituteBullet( aBulletChar, aFontDesc ); + mpFS->singleElementNS( XML_a, XML_buFont, + XML_typeface, aFontDesc.Name, + XML_charset, sax_fastparser::UseIf("2", aFontDesc.CharSet == awt::CharSet::SYMBOL)); + } + + OUString aAutoNumType = GetAutoNumType( nNumberingType, bSDot, bPBehind, bPBoth ); + + if (!aAutoNumType.isEmpty()) + { + mpFS->singleElementNS(XML_a, XML_buAutoNum, + XML_type, aAutoNumType, + XML_startAt, sax_fastparser::UseIf(OString::number(nStartWith), nStartWith > 1)); + } + else + { + mpFS->singleElementNS(XML_a, XML_buChar, XML_char, OUString(aBulletChar)); + } + } +} + +void DrawingML::WriteParagraphTabStops(const Reference<XPropertySet>& rXPropSet) +{ + css::uno::Sequence<css::style::TabStop> aTabStops; + if (GetProperty(rXPropSet, "ParaTabStops")) + aTabStops = *o3tl::doAccess<css::uno::Sequence<css::style::TabStop>>(mAny); + + if (aTabStops.getLength() > 0) + mpFS->startElementNS(XML_a, XML_tabLst); + + for (const css::style::TabStop& rTabStop : std::as_const(aTabStops)) + { + OString sPosition = OString::number(GetPointFromCoordinate(rTabStop.Position)); + OString sAlignment; + switch (rTabStop.Alignment) + { + case css::style::TabAlign_DECIMAL: + sAlignment = "dec"_ostr; + break; + case css::style::TabAlign_RIGHT: + sAlignment = "r"_ostr; + break; + case css::style::TabAlign_CENTER: + sAlignment = "ctr"_ostr; + break; + case css::style::TabAlign_LEFT: + default: + sAlignment = "l"_ostr; + } + mpFS->singleElementNS(XML_a, XML_tab, XML_algn, sAlignment, XML_pos, sPosition); + } + if (aTabStops.getLength() > 0) + mpFS->endElementNS(XML_a, XML_tabLst); +} + +bool DrawingML::IsGroupShape( const Reference< XShape >& rXShape ) +{ + bool bRet = false; + if ( rXShape.is() ) + { + uno::Reference<lang::XServiceInfo> xServiceInfo(rXShape, uno::UNO_QUERY_THROW); + bRet = xServiceInfo->supportsService("com.sun.star.drawing.GroupShape"); + } + return bRet; +} + +sal_Int32 DrawingML::getBulletMarginIndentation (const Reference< XPropertySet >& rXPropSet,sal_Int16 nLevel, std::u16string_view propName) +{ + if (nLevel < 0 || !GetProperty(rXPropSet, "NumberingRules")) + return 0; + + Reference< XIndexAccess > rXIndexAccess; + + if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount()) + return 0; + + SAL_INFO("oox.shape", "numbering rules"); + + Sequence<PropertyValue> aPropertySequence; + rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence; + + if (!aPropertySequence.hasElements()) + return 0; + + for ( const PropertyValue& rPropValue : std::as_const(aPropertySequence) ) + { + OUString aPropName( rPropValue.Name ); + SAL_INFO("oox.shape", "pro name: " << aPropName); + if ( aPropName == propName ) + return *o3tl::doAccess<sal_Int32>(rPropValue.Value); + } + + return 0; +} + +const char* DrawingML::GetAlignment( style::ParagraphAdjust nAlignment ) +{ + const char* sAlignment = nullptr; + + switch( nAlignment ) + { + case style::ParagraphAdjust_CENTER: + sAlignment = "ctr"; + break; + case style::ParagraphAdjust_RIGHT: + sAlignment = "r"; + break; + case style::ParagraphAdjust_BLOCK: + sAlignment = "just"; + break; + default: + ; + } + + return sAlignment; +} + +void DrawingML::WriteLinespacing(const LineSpacing& rSpacing, float fFirstCharHeight) +{ + if( rSpacing.Mode == LineSpacingMode::PROP ) + { + mpFS->singleElementNS( XML_a, XML_spcPct, + XML_val, OString::number(static_cast<sal_Int32>(rSpacing.Height)*1000)); + } + else if (rSpacing.Mode == LineSpacingMode::MINIMUM + && fFirstCharHeight > static_cast<float>(rSpacing.Height) * 0.001 * 72.0 / 2.54) + { + // 100% proportional line spacing = single line spacing + mpFS->singleElementNS(XML_a, XML_spcPct, XML_val, + OString::number(static_cast<sal_Int32>(100000))); + } + else + { + mpFS->singleElementNS( XML_a, XML_spcPts, + XML_val, OString::number(std::lround(rSpacing.Height / 25.4 * 72))); + } +} + +bool DrawingML::WriteParagraphProperties(const Reference<XTextContent>& rParagraph, float fFirstCharHeight, sal_Int32 nElement) +{ + Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY ); + Reference< XPropertyState > rXPropState( rParagraph, UNO_QUERY ); + PropertyState eState; + + if( !rXPropSet.is() || !rXPropState.is() ) + return false; + + sal_Int16 nLevel = -1; + if (GetProperty(rXPropSet, "NumberingLevel")) + mAny >>= nLevel; + + bool bWriteNumbering = true; + bool bForceZeroIndent = false; + if (mbPlaceholder) + { + Reference< text::XTextRange > xParaText(rParagraph, UNO_QUERY); + if (xParaText) + { + bool bNumberingOnThisLevel = false; + if (nLevel > -1) + { + Reference< XIndexAccess > xNumberingRules(rXPropSet->getPropertyValue("NumberingRules"), UNO_QUERY); + const PropertyValues& rNumRuleOfLevel = xNumberingRules->getByIndex(nLevel).get<PropertyValues>(); + for (const PropertyValue& rRule : rNumRuleOfLevel) + if (rRule.Name == "NumberingType" && rRule.Value.hasValue()) + bNumberingOnThisLevel = rRule.Value.get<sal_uInt16>() != style::NumberingType::NUMBER_NONE; + } + + const bool bIsNumberingVisible = rXPropSet->getPropertyValue("NumberingIsNumber").get<bool>(); + const bool bIsLineEmpty = !xParaText->getString().getLength(); + + bWriteNumbering = !bIsLineEmpty && bIsNumberingVisible && (nLevel != -1); + bForceZeroIndent = (!bIsNumberingVisible || bIsLineEmpty || !bNumberingOnThisLevel); + } + + } + + sal_Int16 nTmp = sal_Int16(style::ParagraphAdjust_LEFT); + if (GetProperty(rXPropSet, "ParaAdjust")) + mAny >>= nTmp; + style::ParagraphAdjust nAlignment = static_cast<style::ParagraphAdjust>(nTmp); + + bool bHasLinespacing = false; + LineSpacing aLineSpacing; + if (GetPropertyAndState(rXPropSet, rXPropState, "ParaLineSpacing", eState) + && (mAny >>= aLineSpacing) + && (eState == beans::PropertyState_DIRECT_VALUE || + // only export if it differs from the default 100% line spacing + aLineSpacing.Mode != LineSpacingMode::PROP || aLineSpacing.Height != 100)) + bHasLinespacing = true; + + bool bRtl = false; + if (GetProperty(rXPropSet, "WritingMode")) + { + sal_Int16 nWritingMode; + if( ( mAny >>= nWritingMode ) && nWritingMode == text::WritingMode2::RL_TB ) + { + bRtl = true; + } + } + + sal_Int32 nParaLeftMargin = 0; + sal_Int32 nParaFirstLineIndent = 0; + + if (GetProperty(rXPropSet, "ParaLeftMargin")) + mAny >>= nParaLeftMargin; + if (GetProperty(rXPropSet, "ParaFirstLineIndent")) + mAny >>= nParaFirstLineIndent; + + sal_Int32 nParaTopMargin = 0; + sal_Int32 nParaBottomMargin = 0; + + if (GetProperty(rXPropSet, "ParaTopMargin")) + mAny >>= nParaTopMargin; + if (GetProperty(rXPropSet, "ParaBottomMargin")) + mAny >>= nParaBottomMargin; + + sal_Int32 nLeftMargin = getBulletMarginIndentation ( rXPropSet, nLevel,u"LeftMargin"); + sal_Int32 nLineIndentation = getBulletMarginIndentation ( rXPropSet, nLevel,u"FirstLineOffset"); + + if (bWriteNumbering && !bForceZeroIndent) + { + if (!(nLevel != -1 + || nAlignment != style::ParagraphAdjust_LEFT + || bHasLinespacing)) + return false; + } + + sal_Int32 nParaDefaultTabSize = 0; + if (GetProperty(rXPropSet, "ParaTabStopDefaultDistance")) + mAny >>= nParaDefaultTabSize; + + if (nParaLeftMargin) // For Paragraph + mpFS->startElementNS( XML_a, nElement, + XML_lvl, sax_fastparser::UseIf(OString::number(nLevel), nLevel > 0), + XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaLeftMargin)), nParaLeftMargin > 0), + XML_indent, sax_fastparser::UseIf(OString::number((bForceZeroIndent && nParaFirstLineIndent == 0) ? 0 : oox::drawingml::convertHmmToEmu(nParaFirstLineIndent)), (bForceZeroIndent || nParaFirstLineIndent != 0)), + XML_algn, GetAlignment( nAlignment ), + XML_defTabSz, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaDefaultTabSize)), nParaDefaultTabSize > 0), + XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl)); + else + mpFS->startElementNS( XML_a, nElement, + XML_lvl, sax_fastparser::UseIf(OString::number(nLevel), nLevel > 0), + XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeftMargin)), nLeftMargin > 0), + XML_indent, sax_fastparser::UseIf(OString::number(!bForceZeroIndent ? oox::drawingml::convertHmmToEmu(nLineIndentation) : 0), (bForceZeroIndent || ( nLineIndentation != 0))), + XML_algn, GetAlignment( nAlignment ), + XML_defTabSz, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaDefaultTabSize)), nParaDefaultTabSize > 0), + XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl)); + + + if( bHasLinespacing ) + { + mpFS->startElementNS(XML_a, XML_lnSpc); + WriteLinespacing(aLineSpacing, fFirstCharHeight); + mpFS->endElementNS( XML_a, XML_lnSpc ); + } + + if( nParaTopMargin != 0 ) + { + mpFS->startElementNS(XML_a, XML_spcBef); + { + mpFS->singleElementNS( XML_a, XML_spcPts, + XML_val, OString::number(std::lround(nParaTopMargin / 25.4 * 72))); + } + mpFS->endElementNS( XML_a, XML_spcBef ); + } + + if( nParaBottomMargin != 0 ) + { + mpFS->startElementNS(XML_a, XML_spcAft); + { + mpFS->singleElementNS( XML_a, XML_spcPts, + XML_val, OString::number(std::lround(nParaBottomMargin / 25.4 * 72))); + } + mpFS->endElementNS( XML_a, XML_spcAft ); + } + + if (!bWriteNumbering) + mpFS->singleElementNS(XML_a, XML_buNone); + else + WriteParagraphNumbering( rXPropSet, fFirstCharHeight, nLevel ); + + WriteParagraphTabStops( rXPropSet ); + + // do not end element for lstStyles since, defRPr should be stacked inside it + if( nElement != XML_lvl1pPr ) + mpFS->endElementNS( XML_a, nElement ); + + return true; +} + +void DrawingML::WriteLstStyles(const css::uno::Reference<css::text::XTextContent>& rParagraph, + bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, + const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet) +{ + Reference<XEnumerationAccess> xAccess(rParagraph, UNO_QUERY); + if (!xAccess.is()) + return; + + Reference<XEnumeration> xEnumeration(xAccess->createEnumeration()); + if (!xEnumeration.is()) + return; + + + Reference<XTextRange> rRun; + + if (!xEnumeration->hasMoreElements()) + return; + + Any aAny(xEnumeration->nextElement()); + if (aAny >>= rRun) + { + float fFirstCharHeight = rnCharHeight / 1000.; + Reference<XPropertySet> xFirstRunPropSet(rRun, UNO_QUERY); + Reference<XPropertySetInfo> xFirstRunPropSetInfo + = xFirstRunPropSet->getPropertySetInfo(); + + if (xFirstRunPropSetInfo->hasPropertyByName("CharHeight")) + fFirstCharHeight = xFirstRunPropSet->getPropertyValue("CharHeight").get<float>(); + + mpFS->startElementNS(XML_a, XML_lstStyle); + if( !WriteParagraphProperties(rParagraph, fFirstCharHeight, XML_lvl1pPr) ) + mpFS->startElementNS(XML_a, XML_lvl1pPr); + WriteRunProperties(xFirstRunPropSet, false, XML_defRPr, true, rbOverridingCharHeight, + rnCharHeight, GetScriptType(rRun->getString()), rXShapePropSet); + mpFS->endElementNS(XML_a, XML_lvl1pPr); + mpFS->endElementNS(XML_a, XML_lstStyle); + } +} + +void DrawingML::WriteParagraph( const Reference< XTextContent >& rParagraph, + bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, + const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet) +{ + Reference< XEnumerationAccess > access( rParagraph, UNO_QUERY ); + if( !access.is() ) + return; + + Reference< XEnumeration > enumeration( access->createEnumeration() ); + if( !enumeration.is() ) + return; + + mpFS->startElementNS(XML_a, XML_p); + + bool bPropertiesWritten = false; + while( enumeration->hasMoreElements() ) + { + Reference< XTextRange > run; + Any any ( enumeration->nextElement() ); + + if (any >>= run) + { + if( !bPropertiesWritten ) + { + float fFirstCharHeight = rnCharHeight / 1000.; + Reference< XPropertySet > xFirstRunPropSet (run, UNO_QUERY); + Reference< XPropertySetInfo > xFirstRunPropSetInfo = xFirstRunPropSet->getPropertySetInfo(); + if( xFirstRunPropSetInfo->hasPropertyByName("CharHeight") ) + { + fFirstCharHeight = xFirstRunPropSet->getPropertyValue("CharHeight").get<float>(); + rnCharHeight = 100 * fFirstCharHeight; + rbOverridingCharHeight = true; + } + WriteParagraphProperties(rParagraph, fFirstCharHeight, XML_pPr); + bPropertiesWritten = true; + } + WriteRun( run, rbOverridingCharHeight, rnCharHeight, rXShapePropSet); + } + } + Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY ); + sal_Int16 nDummy = -1; + WriteRunProperties(rXPropSet, false, XML_endParaRPr, false, rbOverridingCharHeight, + rnCharHeight, nDummy, rXShapePropSet); + + mpFS->endElementNS( XML_a, XML_p ); +} + +bool DrawingML::IsFontworkShape(const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet) +{ + bool bResult(false); + if (rXShapePropSet.is()) + { + Sequence<PropertyValue> aCustomShapeGeometryProps; + if (GetProperty(rXShapePropSet, "CustomShapeGeometry")) + { + mAny >>= aCustomShapeGeometryProps; + uno::Sequence<beans::PropertyValue> aTextPathSeq; + for (const auto& rProp : std::as_const(aCustomShapeGeometryProps)) + { + if (rProp.Name == "TextPath") + { + rProp.Value >>= aTextPathSeq; + for (const auto& rTextPathItem : std::as_const(aTextPathSeq)) + { + if (rTextPathItem.Name == "TextPath") + { + rTextPathItem.Value >>= bResult; + break; + } + } + break; + } + } + } + } + return bResult; +} + +void DrawingML::WriteText(const Reference<XInterface>& rXIface, bool bBodyPr, bool bText, + sal_Int32 nXmlNamespace, bool bWritePropertiesAsLstStyles) +{ + // ToDo: Fontwork in DOCX + uno::Reference<XText> xXText(rXIface, UNO_QUERY); + if( !xXText.is() ) + return; + + uno::Reference<drawing::XShape> xShape(rXIface, UNO_QUERY); + uno::Reference<XPropertySet> rXPropSet(rXIface, UNO_QUERY); + + constexpr const sal_Int32 constDefaultLeftRightInset = 254; + constexpr const sal_Int32 constDefaultTopBottomInset = 127; + sal_Int32 nLeft = constDefaultLeftRightInset; + sal_Int32 nRight = constDefaultLeftRightInset; + sal_Int32 nTop = constDefaultTopBottomInset; + sal_Int32 nBottom = constDefaultTopBottomInset; + + // top inset looks a bit different compared to ppt export + // check if something related doesn't work as expected + if (GetProperty(rXPropSet, "TextLeftDistance")) + mAny >>= nLeft; + if (GetProperty(rXPropSet, "TextRightDistance")) + mAny >>= nRight; + if (GetProperty(rXPropSet, "TextUpperDistance")) + mAny >>= nTop; + if (GetProperty(rXPropSet, "TextLowerDistance")) + mAny >>= nBottom; + + // Transform the text distance values so they are compatible with OOXML insets + if (xShape.is()) + { + sal_Int32 nTextHeight = xShape->getSize().Height; // Hmm, default + + // CustomShape can have text area different from shape rectangle + auto* pCustomShape + = dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape)); + if (pCustomShape) + { + const EnhancedCustomShape2d aCustomShape2d(*pCustomShape); + nTextHeight = aCustomShape2d.GetTextRect().getOpenHeight(); + if (DOCUMENT_DOCX == meDocumentType) + nTextHeight = convertTwipToMm100(nTextHeight); + } + + if (nTop + nBottom >= nTextHeight) + { + // Effective bottom would be above effective top of text area. LO normalizes the + // effective text area in such case implicitly for rendering. MS needs indents so that + // the result is the normalized effective text area. + std::swap(nTop, nBottom); + nTop = nTextHeight - nTop; + nBottom = nTextHeight - nBottom; + } + } + + std::optional<OString> sWritingMode; + if (GetProperty(rXPropSet, "TextWritingMode")) + { + WritingMode eMode; + if( ( mAny >>= eMode ) && eMode == WritingMode_TB_RL ) + sWritingMode = "eaVert"; + } + if (GetProperty(rXPropSet, "WritingMode")) + { + sal_Int16 nWritingMode; + if (mAny >>= nWritingMode) + { + if (nWritingMode == text::WritingMode2::TB_RL) + sWritingMode = "eaVert"; + else if (nWritingMode == text::WritingMode2::BT_LR) + sWritingMode = "vert270"; + else if (nWritingMode == text::WritingMode2::TB_RL90) + sWritingMode = "vert"; + else if (nWritingMode == text::WritingMode2::TB_LR) + sWritingMode = "mongolianVert"; + } + } + + // read values from CustomShapeGeometry + Sequence<drawing::EnhancedCustomShapeAdjustmentValue> aAdjustmentSeq; + uno::Sequence<beans::PropertyValue> aTextPathSeq; + bool bScaleX(false); + OUString sShapeType("non-primitive"); + OUString sMSWordPresetTextWarp; + sal_Int32 nTextPreRotateAngle = 0; // degree + std::optional<Degree100> nTextRotateAngleDeg100; // text area rotation + + 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; + else if (rProp.Name == "AdjustmentValues") + rProp.Value >>= aAdjustmentSeq; + else if (rProp.Name == "TextRotateAngle") + { + double fTextRotateAngle = 0; // degree + rProp.Value >>= fTextRotateAngle; + nTextRotateAngleDeg100 = Degree100(std::lround(fTextRotateAngle * 100.0)); + } + else if (rProp.Name == "Type") + rProp.Value >>= sShapeType; + else if (rProp.Name == "TextPath") + { + rProp.Value >>= aTextPathSeq; + for (const auto& rTextPathItem : std::as_const(aTextPathSeq)) + { + if (rTextPathItem.Name == "ScaleX") + rTextPathItem.Value >>= bScaleX; + } + } + else if (rProp.Name == "PresetTextWarp") + rProp.Value >>= sMSWordPresetTextWarp; + } + } + } + else + { + if (mpTextExport) + { + if (xShape) + { + auto xTextFrame = mpTextExport->GetUnoTextFrame(xShape); + if (xTextFrame) + { + uno::Reference<beans::XPropertySet> xPropSet(xTextFrame, uno::UNO_QUERY); + auto aAny = xPropSet->getPropertyValue("WritingMode"); + sal_Int16 nWritingMode; + if (aAny >>= nWritingMode) + { + switch (nWritingMode) + { + case WritingMode2::TB_RL: + sWritingMode = "eaVert"; + break; + case WritingMode2::BT_LR: + sWritingMode = "vert270"; + break; + case WritingMode2::TB_RL90: + sWritingMode = "vert"; + break; + case WritingMode2::TB_LR: + sWritingMode = "mongolianVert"; + break; + default: + break; + } + } + } + } + } + } + + // read InteropGrabBag if any + std::optional<OUString> sHorzOverflow; + std::optional<OUString> sVertOverflow; + bool bUpright = false; + std::optional<OString> isUpright; + if (rXPropSet->getPropertySetInfo()->hasPropertyByName("InteropGrabBag")) + { + uno::Sequence<beans::PropertyValue> aGrabBag; + rXPropSet->getPropertyValue("InteropGrabBag") >>= aGrabBag; + for (const auto& aProp : std::as_const(aGrabBag)) + { + if (aProp.Name == "Upright") + { + aProp.Value >>= bUpright; + isUpright = OString(bUpright ? "1" : "0"); + } + else if (aProp.Name == "horzOverflow") + { + OUString sValue; + aProp.Value >>= sValue; + sHorzOverflow = sValue; + } + else if (aProp.Name == "vertOverflow") + { + OUString sValue; + aProp.Value >>= sValue; + sVertOverflow = sValue; + } + } + } + + bool bIsFontworkShape(IsFontworkShape(rXPropSet)); + OUString sPresetWarp(PresetGeometryTypeNames::GetMsoName(sShapeType)); + // ODF may have user defined TextPath, use "textPlain" as ersatz. + if (sPresetWarp.isEmpty()) + sPresetWarp = bIsFontworkShape ? std::u16string_view(u"textPlain") : std::u16string_view(u"textNoShape"); + + bool bFromWordArt = !bScaleX + && ( sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp" + || sPresetWarp == "textButton" || sPresetWarp == "textCircle"); + + // Fontwork shapes in LO ignore insets in rendering, Word interprets them. + if (GetDocumentType() == DOCUMENT_DOCX && bIsFontworkShape) + { + nLeft = 0; + nRight = 0; + nTop = 0; + nBottom = 0; + } + + if (bUpright) + { + Degree100 nShapeRotateAngleDeg100(0_deg100); + if (GetProperty(rXPropSet, "RotateAngle")) + nShapeRotateAngleDeg100 = Degree100(mAny.get<sal_Int32>()); + // Depending on shape rotation, the import has made 90deg changes to properties + // "TextPreRotateAngle" and "TextRotateAngle". Revert it. + bool bWasAngleChanged + = (nShapeRotateAngleDeg100 > 4500_deg100 && nShapeRotateAngleDeg100 <= 13500_deg100) + || (nShapeRotateAngleDeg100 > 22500_deg100 + && nShapeRotateAngleDeg100 <= 31500_deg100); + if (bWasAngleChanged) + { + nTextRotateAngleDeg100 = nTextRotateAngleDeg100.value_or(0_deg100) + 9000_deg100; + nTextPreRotateAngle -= 90; + } + // If text is no longer upright, user has changed something. Do not write 'upright' then. + // This try to detect the case assumes, that the text area rotation was 0 in the original + // MS Office document. That is likely because MS Office has no UI to set it and the + // predefined SmartArt shapes, which use it, do not use 'upright'. + Degree100 nAngleSum = nShapeRotateAngleDeg100 + nTextRotateAngleDeg100.value_or(0_deg100); + if (abs(NormAngle18000(nAngleSum)) < 100_deg100) // consider inaccuracy from rounding + { + nTextRotateAngleDeg100.reset(); // 'upright' does not overrule text area rotation. + } + else + { + // User changes. Keep current angles. + isUpright.reset(); + if (bWasAngleChanged) + { + nTextPreRotateAngle += 90; + nTextRotateAngleDeg100 = nTextRotateAngleDeg100.value_or(0_deg100) - 9000_deg100; + } + } + } + + // ToDo: Unsure about this. Need to investigate shapes from diagram import, especially diagrams + // with vertical text directions. + if (nTextPreRotateAngle != 0 && !sWritingMode) + { + if (nTextPreRotateAngle == -90 || nTextPreRotateAngle == 270) + sWritingMode = "vert"; + else if (nTextPreRotateAngle == -270 || nTextPreRotateAngle == 90) + sWritingMode = "vert270"; + else if (nTextPreRotateAngle == -180 || nTextPreRotateAngle == 180) + { +#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + nTextRotateAngleDeg100 + = NormAngle18000(nTextRotateAngleDeg100.value_or(0_deg100) + 18000_deg100); +#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 +#pragma GCC diagnostic pop +#endif + // ToDo: Examine insets. They might need rotation too. Check diagrams (SmartArt). + } + else + SAL_WARN("oox", "unsuitable value for TextPreRotateAngle:" << nTextPreRotateAngle); + } + else if (nTextPreRotateAngle != 0 && sWritingMode && sWritingMode.value() == "eaVert") + { + // ToDo: eaVert plus 270deg clockwise rotation has to be written with vert="horz" + // plus attribute 'normalEastAsianFlow="1"' on the <wps:wsp> element. + } + // else nothing to do + + // Our WritingMode introduces text pre rotation which includes padding, MSO vert does not include + // padding. Therefore set padding so, that is looks the same in MSO as in LO. + if (sWritingMode) + { + if (sWritingMode.value() == "vert" || sWritingMode.value() == "eaVert") + { + sal_Int32 nHelp = nLeft; + nLeft = nBottom; + nBottom = nRight; + nRight = nTop; + nTop = nHelp; + } + else if (sWritingMode.value() == "vert270") + { + sal_Int32 nHelp = nLeft; + nLeft = nTop; + nTop = nRight; + nRight = nBottom; + nBottom = nHelp; + } + else if (sWritingMode.value() == "mongolianVert") + { + // ToDo: Examine padding + } + } + + + std::optional<OString> sTextRotateAngleMSUnit; + if (nTextRotateAngleDeg100.has_value()) +#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + sTextRotateAngleMSUnit + = oox::drawingml::calcRotationValue(nTextRotateAngleDeg100.value().get()); +#if defined __GNUC__ && !defined __clang__ && __GNUC__ == 12 +#pragma GCC diagnostic pop +#endif + + // Prepare attributes 'anchor' and 'anchorCtr' + // LibreOffice has 12 value sets, MS Office only 6. We map them so, that it reverses the + // 6 mappings from import, and we assign the others approximately. + TextVerticalAdjust eVerticalAlignment(TextVerticalAdjust_TOP); + if (GetProperty(rXPropSet, "TextVerticalAdjust")) + mAny >>= eVerticalAlignment; + TextHorizontalAdjust eHorizontalAlignment(TextHorizontalAdjust_CENTER); + if (GetProperty(rXPropSet, "TextHorizontalAdjust")) + mAny >>= eHorizontalAlignment; + + const char* sAnchor = nullptr; + bool bAnchorCtr = false; + if (sWritingMode.has_value() + && (sWritingMode.value() == "eaVert" || sWritingMode.value() == "mongolianVert")) + { + bAnchorCtr = eVerticalAlignment == TextVerticalAdjust_CENTER + || eVerticalAlignment == TextVerticalAdjust_BOTTOM + || eVerticalAlignment == TextVerticalAdjust_BLOCK; + switch (eHorizontalAlignment) + { + case TextHorizontalAdjust_CENTER: + sAnchor = "ctr"; + break; + case TextHorizontalAdjust_LEFT: + sAnchor = sWritingMode.value() == "eaVert" ? "b" : "t"; + break; + case TextHorizontalAdjust_RIGHT: + default: // TextHorizontalAdjust_BLOCK, should not happen + sAnchor = sWritingMode.value() == "eaVert" ? "t" : "b"; + break; + } + } + else + { + bAnchorCtr = eHorizontalAlignment == TextHorizontalAdjust_CENTER + || eHorizontalAlignment == TextHorizontalAdjust_RIGHT; + sAnchor = GetTextVerticalAdjust(eVerticalAlignment); + } + + bool bHasWrap = false; + bool bWrap = false; + // Only custom shapes obey the TextWordWrap option, normal text always wraps. + if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, "TextWordWrap")) + { + mAny >>= bWrap; + bHasWrap = true; + } + + if (bBodyPr) + { + const char* pWrap = (bHasWrap && !bWrap) || bIsFontworkShape ? "none" : nullptr; + if (GetDocumentType() == DOCUMENT_DOCX) + { + // In case of DOCX, if we want to have the same effect as + // TextShape's automatic word wrapping, then we need to set + // wrapping to square. + uno::Reference<lang::XServiceInfo> xServiceInfo(rXIface, uno::UNO_QUERY); + if ((xServiceInfo.is() && xServiceInfo->supportsService("com.sun.star.drawing.TextShape")) + || bIsFontworkShape) + pWrap = "square"; + } + + sal_Int16 nCols = 0; + sal_Int32 nColSpacing = -1; + if (GetProperty(rXPropSet, "TextColumns")) + { + if (css::uno::Reference<css::text::XTextColumns> xCols{ mAny, css::uno::UNO_QUERY }) + { + nCols = xCols->getColumnCount(); + if (css::uno::Reference<css::beans::XPropertySet> xProps{ mAny, + css::uno::UNO_QUERY }) + { + if (GetProperty(xProps, "AutomaticDistance")) + mAny >>= nColSpacing; + } + } + } + + if (!sVertOverflow && GetProperty(rXPropSet, "TextClipVerticalOverflow") && mAny.get<bool>()) + { + sVertOverflow = "clip"; + } + + // tdf#151134 When writing placeholder shapes, inset must be explicitly specified + bool bRequireInset = GetProperty(rXPropSet, "IsPresentationObject") && rXPropSet->getPropertyValue("IsPresentationObject").get<bool>(); + + mpFS->startElementNS( (nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr, + XML_numCol, sax_fastparser::UseIf(OString::number(nCols), nCols > 0), + XML_spcCol, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nColSpacing)), nCols > 0 && nColSpacing >= 0), + XML_wrap, pWrap, + XML_horzOverflow, sHorzOverflow, + XML_vertOverflow, sVertOverflow, + XML_fromWordArt, sax_fastparser::UseIf("1", bFromWordArt), + XML_lIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeft)), + bRequireInset || nLeft != constDefaultLeftRightInset), + XML_rIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nRight)), + bRequireInset || nRight != constDefaultLeftRightInset), + XML_tIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nTop)), + bRequireInset || nTop != constDefaultTopBottomInset), + XML_bIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nBottom)), + bRequireInset || nBottom != constDefaultTopBottomInset), + XML_anchor, sAnchor, + XML_anchorCtr, sax_fastparser::UseIf("1", bAnchorCtr), + XML_vert, sWritingMode, + XML_upright, isUpright, + XML_rot, sTextRotateAngleMSUnit); + + if (bIsFontworkShape) + { + if (aAdjustmentSeq.hasElements()) + { + mpFS->startElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp); + mpFS->startElementNS(XML_a, XML_avLst); + bool bHasTwoHandles( + sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour" + || sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour" + || sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1" + || sPresetWarp == "textWave2" || sPresetWarp == "textWave4"); + for (sal_Int32 i = 0, nElems = aAdjustmentSeq.getLength(); i < nElems; ++i ) + { + OString sName = "adj" + (bHasTwoHandles ? OString::number(i + 1) : OString()); + double fValue(0.0); + if (aAdjustmentSeq[i].Value.getValueTypeClass() == TypeClass_DOUBLE) + aAdjustmentSeq[i].Value >>= fValue; + else + { + sal_Int32 nNumber(0); + aAdjustmentSeq[i].Value >>= nNumber; + fValue = static_cast<double>(nNumber); + } + // Convert from binary coordinate system with viewBox "0 0 21600 21600" and simple degree + // to DrawingML with coordinate range 0..100000 and angle in 1/60000 degree. + // Reverse to conversion in lcl_createPresetShape in drawingml/shape.cxx on import. + if (sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp" + || sPresetWarp == "textButton" || sPresetWarp == "textCircle" + || ((i == 0) + && (sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour" + || sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour"))) + { + fValue *= 60000.0; + if (fValue < 0) + fValue += 21600000; + } + else if ((i == 1) + && (sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1" + || sPresetWarp == "textWave2" || sPresetWarp == "textWave4")) + { + fValue = fValue / 0.216 - 50000.0; + } + else if ((i == 1) + && (sPresetWarp == "textArchDownPour" + || sPresetWarp == "textArchUpPour" + || sPresetWarp == "textButtonPour" + || sPresetWarp == "textCirclePour")) + { + fValue /= 0.108; + } + else + { + fValue /= 0.216; + } + OString sFmla = "val " + OString::number(std::lround(fValue)); + mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla); + // There exists faulty Favorite shapes with one handle but two adjustment values. + if (!bHasTwoHandles) + break; + } + mpFS->endElementNS(XML_a, XML_avLst); + mpFS->endElementNS(XML_a, XML_prstTxWarp); + } + else + { + mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp); + } + } + else if (GetDocumentType() == DOCUMENT_DOCX) + { + // interim solution for fdo#80897, roundtrip DOCX > LO > DOCX + if (!sMSWordPresetTextWarp.isEmpty()) + mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sMSWordPresetTextWarp); + } + + if (GetDocumentType() == DOCUMENT_DOCX || GetDocumentType() == DOCUMENT_XLSX) + { + // tdf#112312: only custom shapes obey the TextAutoGrowHeight option + bool bTextAutoGrowHeight = false; + auto pSdrObjCustomShape = xShape.is() ? dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape)) : nullptr; + if (pSdrObjCustomShape && GetProperty(rXPropSet, "TextAutoGrowHeight")) + { + mAny >>= bTextAutoGrowHeight; + } + mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit)); + } + if (GetDocumentType() == DOCUMENT_PPTX) + { + TextFitToSizeType eFit = TextFitToSizeType_NONE; + if (GetProperty(rXPropSet, "TextFitToSize")) + mAny >>= eFit; + + if (eFit == TextFitToSizeType_AUTOFIT) + { + const sal_Int32 MAX_SCALE_VAL = 100000; + sal_Int32 nFontScale = MAX_SCALE_VAL; + sal_Int32 nSpacingReduction = 0; + SvxShapeText* pTextShape = dynamic_cast<SvxShapeText*>(rXIface.get()); + if (pTextShape) + { + SdrTextObj* pTextObject = DynCastSdrTextObj(pTextShape->GetSdrObject()); + if (pTextObject) + { + nFontScale = sal_Int32(pTextObject->GetFontScale() * 1000.0); + nSpacingReduction = sal_Int32((100.0 - pTextObject->GetSpacingScale()) * 1000.0); + } + } + + bool bExportFontScale = false; + if (nFontScale < MAX_SCALE_VAL && nFontScale > 0) + bExportFontScale = true; + + bool bExportSpaceReduction = false; + if (nSpacingReduction < MAX_SCALE_VAL && nSpacingReduction > 0) + bExportSpaceReduction = true; + + mpFS->singleElementNS(XML_a, XML_normAutofit, + XML_fontScale, sax_fastparser::UseIf(OString::number(nFontScale), bExportFontScale), + XML_lnSpcReduction, sax_fastparser::UseIf(OString::number(nSpacingReduction), bExportSpaceReduction)); + } + else + { + // tdf#127030: Only custom shapes obey the TextAutoGrowHeight option. + bool bTextAutoGrowHeight = false; + if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, "TextAutoGrowHeight")) + mAny >>= bTextAutoGrowHeight; + mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit)); + } + } + + Write3DEffects( rXPropSet, /*bIsText=*/true ); + + mpFS->endElementNS((nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr); + } + + Reference< XEnumerationAccess > access( xXText, UNO_QUERY ); + if( !access.is() || !bText ) + return; + + Reference< XEnumeration > enumeration( access->createEnumeration() ); + if( !enumeration.is() ) + return; + + SdrObject* pSdrObject = xShape.is() ? SdrObject::getSdrObjectFromXShape(xShape) : nullptr; + const SdrTextObj* pTxtObj = DynCastSdrTextObj( pSdrObject ); + if (pTxtObj && mpTextExport) + { + std::vector<beans::PropertyValue> aOldCharFillPropVec; + if (bIsFontworkShape) + { + // Users may have set the character fill properties for more convenient editing. + // Save the properties before changing them for Fontwork export. + FontworkHelpers::collectCharColorProps(xXText, aOldCharFillPropVec); + // Word has properties for abc-transform in the run properties of the text of the shape. + // Writer has the Fontwork properties as shape properties. Create the character fill + // properties needed for export from the shape fill properties + // and apply them to all runs. + std::vector<beans::PropertyValue> aExportCharFillPropVec; + FontworkHelpers::createCharFillPropsFromShape(rXPropSet, aExportCharFillPropVec); + FontworkHelpers::applyPropsToRuns(aExportCharFillPropVec, xXText); + // Import has converted some items from CharInteropGrabBag to fill and line + // properties of the shape. For export we convert them back because users might have + // changed them. And we create them in case we come from an odt document. + std::vector<beans::PropertyValue> aUpdatePropVec; + FontworkHelpers::createCharInteropGrabBagUpdatesFromShapeProps(rXPropSet, aUpdatePropVec); + FontworkHelpers::applyUpdatesToCharInteropGrabBag(aUpdatePropVec, xXText); + } + + std::optional<OutlinerParaObject> pParaObj; + + /* + #i13885# + When the object is actively being edited, that text is not set into + the objects normal text object, but lives in a separate object. + */ + if (pTxtObj->IsTextEditActive()) + { + pParaObj = pTxtObj->CreateEditOutlinerParaObject(); + } + else if (pTxtObj->GetOutlinerParaObject()) + pParaObj = *pTxtObj->GetOutlinerParaObject(); + + if (pParaObj) + { + // this is reached only in case some text is attached to the shape + mpTextExport->WriteOutliner(*pParaObj); + } + + if (bIsFontworkShape) + FontworkHelpers::applyPropsToRuns(aOldCharFillPropVec, xXText); + return; + } + + bool bOverridingCharHeight = false; + sal_Int32 nCharHeight = -1; + bool bFirstParagraph = true; + + // tdf#144092 For shapes without text: Export run properties (into + // endParaRPr) from the shape's propset instead of the paragraph's. + if(xXText->getString().isEmpty() && enumeration->hasMoreElements()) + { + Any aAny (enumeration->nextElement()); + Reference<XTextContent> xParagraph; + if( aAny >>= xParagraph ) + { + mpFS->startElementNS(XML_a, XML_p); + WriteParagraphProperties(xParagraph, nCharHeight, XML_pPr); + sal_Int16 nDummy = -1; + WriteRunProperties(rXPropSet, false, XML_endParaRPr, false, + bOverridingCharHeight, nCharHeight, nDummy, rXPropSet); + mpFS->endElementNS(XML_a, XML_p); + } + return; + } + + while( enumeration->hasMoreElements() ) + { + Reference< XTextContent > paragraph; + Any any ( enumeration->nextElement() ); + + if( any >>= paragraph) + { + if (bFirstParagraph && bWritePropertiesAsLstStyles) + WriteLstStyles(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet); + + WriteParagraph(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet); + bFirstParagraph = false; + } + } +} + +void DrawingML::WritePresetShape( const OString& pShape , std::vector< std::pair<sal_Int32,sal_Int32>> & rAvList ) +{ + mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape); + if ( !rAvList.empty() ) + { + + mpFS->startElementNS(XML_a, XML_avLst); + for (auto const& elem : rAvList) + { + OString sName = "adj" + ( ( elem.first > 0 ) ? OString::number(elem.first) : OString() ); + OString sFmla = "val " + OString::number( elem.second ); + + mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla); + } + mpFS->endElementNS( XML_a, XML_avLst ); + } + else + mpFS->singleElementNS(XML_a, XML_avLst); + + mpFS->endElementNS( XML_a, XML_prstGeom ); +} + +void DrawingML::WritePresetShape( const OString& pShape ) +{ + mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape); + mpFS->singleElementNS(XML_a, XML_avLst); + mpFS->endElementNS( XML_a, XML_prstGeom ); +} + +static std::map< OString, std::vector<OString> > lcl_getAdjNames() +{ + std::map< OString, std::vector<OString> > aRet; + + OUString aPath("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/filter/oox-drawingml-adj-names"); + rtl::Bootstrap::expandMacros(aPath); + SvFileStream aStream(aPath, StreamMode::READ); + if (aStream.GetError() != ERRCODE_NONE) + SAL_WARN("oox.shape", "failed to open oox-drawingml-adj-names"); + OStringBuffer aLine; + bool bNotDone = aStream.ReadLine(aLine); + while (bNotDone) + { + sal_Int32 nIndex = 0; + // Each line is in a "key\tvalue" format: read the key, the rest is the value. + OString aKey( o3tl::getToken(aLine, 0, '\t', nIndex) ); + OString aValue( std::string_view(aLine).substr(nIndex) ); + aRet[aKey].push_back(aValue); + bNotDone = aStream.ReadLine(aLine); + } + return aRet; +} + +void DrawingML::WritePresetShape( const OString& pShape, MSO_SPT eShapeType, bool bPredefinedHandlesUsed, const PropertyValue& rProp ) +{ + static std::map< OString, std::vector<OString> > aAdjMap = lcl_getAdjNames(); + // If there are predefined adj names for this shape type, look them up now. + std::vector<OString> aAdjustments; + auto it = aAdjMap.find(pShape); + if (it != aAdjMap.end()) + aAdjustments = it->second; + + mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape); + mpFS->startElementNS(XML_a, XML_avLst); + + Sequence< drawing::EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq; + if ( ( rProp.Value >>= aAdjustmentSeq ) + && eShapeType != mso_sptActionButtonForwardNext // we have adjustments values for these type of shape, but MSO doesn't like them + && eShapeType != mso_sptActionButtonBackPrevious // so they are now disabled + && pShape != "rect" //some shape types are commented out in pCustomShapeTypeTranslationTable[] & are being defaulted to rect & rect does not have adjustment values/name. + ) + { + SAL_INFO("oox.shape", "adj seq len: " << aAdjustmentSeq.getLength()); + sal_Int32 nAdjustmentsWhichNeedsToBeConverted = 0; + if ( bPredefinedHandlesUsed ) + EscherPropertyContainer::LookForPolarHandles( eShapeType, nAdjustmentsWhichNeedsToBeConverted ); + + sal_Int32 nValue, nLength = aAdjustmentSeq.getLength(); + // aAdjustments will give info about the number of adj values for a particular geometry. For example for hexagon aAdjustments.size() will be 2 and for circular arrow it will be 5 as per lcl_getAdjNames. + // Sometimes there are more values than needed, so we ignore the excessive ones. + if (aAdjustments.size() <= o3tl::make_unsigned(nLength)) + { + for (sal_Int32 i = 0; i < static_cast<sal_Int32>(aAdjustments.size()); i++) + { + if( EscherPropertyContainer::GetAdjustmentValue( aAdjustmentSeq[ i ], i, nAdjustmentsWhichNeedsToBeConverted, nValue ) ) + { + // If the document model doesn't have an adjustment name (e.g. shape was created from VML), then take it from the predefined list. + OString aAdjName = aAdjustmentSeq[i].Name.isEmpty() + ? aAdjustments[i] + : aAdjustmentSeq[i].Name.toUtf8(); + + mpFS->singleElementNS( XML_a, XML_gd, + XML_name, aAdjName, + XML_fmla, "val " + OString::number(nValue)); + } + } + } + } + + mpFS->endElementNS( XML_a, XML_avLst ); + mpFS->endElementNS( XML_a, XML_prstGeom ); +} + +namespace // helpers for DrawingML::WriteCustomGeometry +{ +sal_Int32 +FindNextCommandEndSubpath(const sal_Int32 nStart, + const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments) +{ + sal_Int32 i = nStart < 0 ? 0 : nStart; + while (i < rSegments.getLength() && rSegments[i].Command != ENDSUBPATH) + i++; + return i; +} + +bool HasCommandInSubPath(const sal_Int16 nCommand, const sal_Int32 nFirst, const sal_Int32 nLast, + const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments) +{ + for (sal_Int32 i = nFirst < 0 ? 0 : nFirst; i <= nLast && i < rSegments.getLength(); i++) + { + if (rSegments[i].Command == nCommand) + return true; + } + return false; +} + +// Ellipse is given by radii fwR and fhR and center (fCx|fCy). The ray from center through point RayP +// intersects the ellipse in point S and this point S has angle fAngleDeg in degrees. +void getEllipsePointAndAngleFromRayPoint(double& rfAngleDeg, double& rfSx, double& rfSy, + const double fWR, const double fHR, const double fCx, + const double fCy, const double fRayPx, const double fRayPy) +{ + if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR)) + { + rfSx = fCx; // needed for getting new 'current point' + rfSy = fCy; + } + else + { + // center ellipse at origin, stretch in y-direction to circle, flip to Math orientation + // and get angle + double fCircleMathAngle = atan2(-fWR / fHR * (fRayPy - fCy), fRayPx - fCx); + // use angle for intersection point on circle and stretch back to ellipse + double fPointMathEllipse_x = fWR * cos(fCircleMathAngle); + double fPointMathEllipse_y = fHR * sin(fCircleMathAngle); + // get angle of intersection point on ellipse + double fEllipseMathAngle = atan2(fPointMathEllipse_y, fPointMathEllipse_x); + // convert from Math to View orientation and shift ellipse back from origin + rfAngleDeg = -basegfx::rad2deg(fEllipseMathAngle); + rfSx = fPointMathEllipse_x + fCx; + rfSy = -fPointMathEllipse_y + fCy; + } +} + +void getEllipsePointFromViewAngle(double& rfSx, double& rfSy, const double fWR, const double fHR, + const double fCx, const double fCy, const double fViewAngleDeg) +{ + if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR)) + { + rfSx = fCx; // needed for getting new 'current point' + rfSy = fCy; + } + else + { + double fX = cos(basegfx::deg2rad(fViewAngleDeg)) / fWR; + double fY = sin(basegfx::deg2rad(fViewAngleDeg)) / fHR; + double fRadius = 1.0 / std::hypot(fX, fY); + rfSx = fCx + fRadius * cos(basegfx::deg2rad(fViewAngleDeg)); + rfSy = fCy + fRadius * sin(basegfx::deg2rad(fViewAngleDeg)); + } +} + +sal_Int32 GetCustomGeometryPointValue(const css::drawing::EnhancedCustomShapeParameter& rParam, + const EnhancedCustomShape2d& rCustomShape2d, + const bool bReplaceGeoWidth, const bool bReplaceGeoHeight) +{ + double fValue = 0.0; + rCustomShape2d.GetParameter(fValue, rParam, bReplaceGeoWidth, bReplaceGeoHeight); + sal_Int32 nValue(std::lround(fValue)); + + return nValue; +} + +struct TextAreaRect +{ + OString left; + OString top; + OString right; + OString bottom; +}; + +struct Guide +{ + OString sName; + OString sFormula; +}; + +void prepareTextArea(const EnhancedCustomShape2d& rEnhancedCustomShape2d, + std::vector<Guide>& rGuideList, TextAreaRect& rTextAreaRect) +{ + tools::Rectangle aTextAreaLO(rEnhancedCustomShape2d.GetTextRect()); + tools::Rectangle aLogicRectLO(rEnhancedCustomShape2d.GetLogicRect()); + if (aTextAreaLO == aLogicRectLO) + { + rTextAreaRect.left = "l"_ostr; + rTextAreaRect.top = "t"_ostr; + rTextAreaRect.right = "r"_ostr; + rTextAreaRect.bottom = "b"_ostr; + return; + } + // Flip aTextAreaLO if shape is flipped + if (rEnhancedCustomShape2d.IsFlipHorz()) + aTextAreaLO.Move((aLogicRectLO.Center().X() - aTextAreaLO.Center().X()) * 2, 0); + if (rEnhancedCustomShape2d.IsFlipVert()) + aTextAreaLO.Move(0, (aLogicRectLO.Center().Y() - aTextAreaLO.Center().Y()) * 2); + + Guide aGuide; + // horizontal + const sal_Int32 nWidth = aLogicRectLO.Right() - aLogicRectLO.Left(); + const OString sWidth = OString::number(oox::drawingml::convertHmmToEmu(nWidth)); + + // left + aGuide.sName = "textAreaLeft"_ostr; + sal_Int32 nHelp = aTextAreaLO.Left() - aLogicRectLO.Left(); + const OString sLeft = OString::number(oox::drawingml::convertHmmToEmu(nHelp)); + aGuide.sFormula = "*/ " + sLeft + " w " + sWidth; + rTextAreaRect.left = aGuide.sName; + rGuideList.push_back(aGuide); + + // right + aGuide.sName = "textAreaRight"_ostr; + nHelp = aTextAreaLO.Right() - aLogicRectLO.Left(); + const OString sRight = OString::number(oox::drawingml::convertHmmToEmu(nHelp)); + aGuide.sFormula = "*/ " + sRight + " w " + sWidth; + rTextAreaRect.right = aGuide.sName; + rGuideList.push_back(aGuide); + + // vertical + const sal_Int32 nHeight = aLogicRectLO.Bottom() - aLogicRectLO.Top(); + const OString sHeight = OString::number(oox::drawingml::convertHmmToEmu(nHeight)); + + // top + aGuide.sName = "textAreaTop"_ostr; + nHelp = aTextAreaLO.Top() - aLogicRectLO.Top(); + const OString sTop = OString::number(oox::drawingml::convertHmmToEmu(nHelp)); + aGuide.sFormula = "*/ " + sTop + " h " + sHeight; + rTextAreaRect.top = aGuide.sName; + rGuideList.push_back(aGuide); + + // bottom + aGuide.sName = "textAreaBottom"_ostr; + nHelp = aTextAreaLO.Bottom() - aLogicRectLO.Top(); + const OString sBottom = OString::number(oox::drawingml::convertHmmToEmu(nHelp)); + aGuide.sFormula = "*/ " + sBottom + " h " + sHeight; + rTextAreaRect.bottom = aGuide.sName; + rGuideList.push_back(aGuide); + + return; +} +} + +bool DrawingML::WriteCustomGeometry( + const Reference< XShape >& rXShape, + const SdrObjCustomShape& rSdrObjCustomShape) +{ + uno::Reference< beans::XPropertySet > aXPropSet; + uno::Any aAny( rXShape->queryInterface(cppu::UnoType<beans::XPropertySet>::get())); + + if ( ! (aAny >>= aXPropSet) ) + return false; + + try + { + aAny = aXPropSet->getPropertyValue( "CustomShapeGeometry" ); + if ( !aAny.hasValue() ) + return false; + } + catch( const ::uno::Exception& ) + { + return false; + } + + auto pGeometrySeq = o3tl::tryAccess<uno::Sequence<beans::PropertyValue>>(aAny); + if (!pGeometrySeq) + return false; + + auto pPathProp = std::find_if(std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq), + [](const PropertyValue& rProp) { return rProp.Name == "Path"; }); + if (pPathProp == std::cend(*pGeometrySeq)) + return false; + + uno::Sequence<beans::PropertyValue> aPathProp; + pPathProp->Value >>= aPathProp; + + uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs; + uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments; + uno::Sequence<awt::Size> aPathSize; + bool bReplaceGeoWidth = false; + bool bReplaceGeoHeight = false; + for (const beans::PropertyValue& rPathProp : std::as_const(aPathProp)) + { + if (rPathProp.Name == "Coordinates") + rPathProp.Value >>= aPairs; + else if (rPathProp.Name == "Segments") + rPathProp.Value >>= aSegments; + else if (rPathProp.Name == "SubViewSize") + rPathProp.Value >>= aPathSize; + else if (rPathProp.Name == "StretchX") + bReplaceGeoWidth = true; + else if (rPathProp.Name == "StretchY") + bReplaceGeoHeight = true; + } + + if ( !aPairs.hasElements() ) + return false; + + if ( !aSegments.hasElements() ) + { + aSegments = uno::Sequence<drawing::EnhancedCustomShapeSegment> + { + { MOVETO, 1 }, + { LINETO, + static_cast<sal_Int16>(std::min( aPairs.getLength() - 1, sal_Int32(32767) )) }, + { CLOSESUBPATH, 0 }, + { ENDSUBPATH, 0 } + }; + }; + + int nExpectedPairCount = std::accumulate(std::cbegin(aSegments), std::cend(aSegments), 0, + [](const int nSum, const drawing::EnhancedCustomShapeSegment& rSegment) { return nSum + rSegment.Count; }); + + if ( nExpectedPairCount > aPairs.getLength() ) + { + SAL_WARN("oox.shape", "Segments need " << nExpectedPairCount << " coordinates, but Coordinates have only " << aPairs.getLength() << " pairs."); + return false; + } + + // A EnhancedCustomShape2d caches the equation results. Therefore we use only one of it for the + // entire method. + const EnhancedCustomShape2d aCustomShape2d(const_cast<SdrObjCustomShape&>(rSdrObjCustomShape)); + + TextAreaRect aTextAreaRect; + std::vector<Guide> aGuideList; // for now only for <a:rect> + prepareTextArea(aCustomShape2d, aGuideList, aTextAreaRect); + mpFS->startElementNS(XML_a, XML_custGeom); + mpFS->singleElementNS(XML_a, XML_avLst); + if (aGuideList.empty()) + { + mpFS->singleElementNS(XML_a, XML_gdLst); + } + else + { + mpFS->startElementNS(XML_a, XML_gdLst); + for (auto const& elem : aGuideList) + { + mpFS->singleElementNS(XML_a, XML_gd, XML_name, elem.sName, XML_fmla, elem.sFormula); + } + mpFS->endElementNS(XML_a, XML_gdLst); + } + mpFS->singleElementNS(XML_a, XML_ahLst); + mpFS->singleElementNS(XML_a, XML_rect, XML_l, aTextAreaRect.left, XML_t, aTextAreaRect.top, + XML_r, aTextAreaRect.right, XML_b, aTextAreaRect.bottom); + mpFS->startElementNS(XML_a, XML_pathLst); + + // Prepare width and height for <a:path> + bool bUseGlobalViewBox(false); + + // nViewBoxWidth must be integer otherwise ReplaceGeoWidth in aCustomShape2d.GetParameter() is not + // triggered; same for height. + sal_Int32 nViewBoxWidth(0); + sal_Int32 nViewBoxHeight(0); + if (!aPathSize.hasElements()) + { + bUseGlobalViewBox = true; + // If draw:viewBox is missing in draw:enhancedGeometry, then import sets + // viewBox="0 0 21600 21600". Missing ViewBox can only occur, if user has manipulated + // current file via macro. Author of macro has to fix it. + auto pProp = std::find_if( + std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq), + [](const beans::PropertyValue& rGeomProp) { return rGeomProp.Name == "ViewBox"; }); + if (pProp != std::cend(*pGeometrySeq)) + { + css::awt::Rectangle aViewBox; + if (pProp->Value >>= aViewBox) + { + nViewBoxWidth = aViewBox.Width; + nViewBoxHeight = aViewBox.Height; + css::drawing::EnhancedCustomShapeParameter aECSP; + aECSP.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL; + aECSP.Value <<= nViewBoxWidth; + double fRetValue; + aCustomShape2d.GetParameter(fRetValue, aECSP, true, false); + nViewBoxWidth = basegfx::fround(fRetValue); + aECSP.Value <<= nViewBoxHeight; + aCustomShape2d.GetParameter(fRetValue, aECSP, false, true); + nViewBoxHeight = basegfx::fround(fRetValue); + } + } + // Import from oox or documents, which are imported from oox and saved to strict ODF, might + // have no subViewSize but viewBox="0 0 0 0". We need to generate width and height in those + // cases. Even if that is fixed, we need the substitute for old documents. + if ((nViewBoxWidth == 0 && nViewBoxHeight == 0) || pProp == std::cend(*pGeometrySeq)) + { + // Generate a substitute based on point coordinates + sal_Int32 nXMin(0); + aPairs[0].First.Value >>= nXMin; + sal_Int32 nXMax = nXMin; + sal_Int32 nYMin(0); + aPairs[0].Second.Value >>= nYMin; + sal_Int32 nYMax = nYMin; + + for (const auto& rPair : std::as_const(aPairs)) + { + sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, aCustomShape2d, + bReplaceGeoWidth, false); + sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, aCustomShape2d, false, + bReplaceGeoHeight); + if (nX < nXMin) + nXMin = nX; + if (nY < nYMin) + nYMin = nY; + if (nX > nXMax) + nXMax = nX; + if (nY > nYMax) + nYMax = nY; + } + nViewBoxWidth = std::max(nXMax, nXMax - nXMin); + nViewBoxHeight = std::max(nYMax, nYMax - nYMin); + } + // ToDo: Other values of left,top than 0,0 are not considered yet. Such would require a + // shift of the resulting path coordinates. + } + + // Iterate over subpaths + sal_Int32 nPairIndex = 0; // index over "Coordinates" + sal_Int32 nPathSizeIndex = 0; // index over "SubViewSize" + sal_Int32 nSubpathStartIndex(0); // index over "Segments" + sal_Int32 nSubPathIndex(0); // serial number of current subpath + do + { + bool bOK(true); // catch faulty paths were commands do not correspond to points + // get index of next command ENDSUBPATH; if such doesn't exist use index behind last segment + sal_Int32 nNextNcommandIndex = FindNextCommandEndSubpath(nSubpathStartIndex, aSegments); + + // Prepare attributes for a:path start element + // NOFILL or one of the LIGHTEN commands + std::optional<OString> sFill; + if (HasCommandInSubPath(NOFILL, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments)) + sFill = "none"; + else if (HasCommandInSubPath(DARKEN, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments)) + sFill = "darken"; + else if (HasCommandInSubPath(DARKENLESS, nSubpathStartIndex, nNextNcommandIndex - 1, + aSegments)) + sFill = "darkenLess"; + else if (HasCommandInSubPath(LIGHTEN, nSubpathStartIndex, nNextNcommandIndex - 1, + aSegments)) + sFill = "lighten"; + else if (HasCommandInSubPath(LIGHTENLESS, nSubpathStartIndex, nNextNcommandIndex - 1, + aSegments)) + sFill = "lightenLess"; + else + { + // shading info might be in object type, e.g. "Octagon Bevel". + sal_Int32 nLuminanceChange(aCustomShape2d.GetLuminanceChange(nSubPathIndex)); + if (nLuminanceChange <= -40) + sFill = "darken"; + else if (nLuminanceChange <= -10) + sFill = "darkenLess"; + else if (nLuminanceChange >= 40) + sFill = "lighten"; + else if (nLuminanceChange >= 10) + sFill = "lightenLess"; + } + // NOSTROKE + std::optional<OString> sStroke; + if (HasCommandInSubPath(NOSTROKE, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments)) + sStroke = "0"; + + // Write a:path start element + mpFS->startElementNS( + XML_a, XML_path, XML_fill, sFill, XML_stroke, sStroke, XML_w, + OString::number(bUseGlobalViewBox ? nViewBoxWidth : aPathSize[nPathSizeIndex].Width), + XML_h, + OString::number(bUseGlobalViewBox ? nViewBoxHeight : aPathSize[nPathSizeIndex].Height)); + + // Arcs drawn by commands ELLIPTICALQUADRANTX and ELLIPTICALQUADRANTY depend on the position + // of the target point in regard to the current point. Therefore we need to track the + // current point. A current point is not defined in the beginning. + double fCurrentX(0.0); + double fCurrentY(0.0); + bool bCurrentValid(false); + // Actually write the subpath + for (sal_Int32 nSegmentIndex = nSubpathStartIndex; nSegmentIndex < nNextNcommandIndex; + ++nSegmentIndex) + { + const auto& rSegment(aSegments[nSegmentIndex]); + if (rSegment.Command == CLOSESUBPATH) + { + mpFS->singleElementNS(XML_a, XML_close); // command Z has no parameter + // ODF 1.4 specifies, that the start of the subpath becomes the current point. + // But that is not implemented yet. Currently LO keeps the last current point. + } + for (sal_Int32 k = 0; k < rSegment.Count && bOK; ++k) + { + bOK = WriteCustomGeometrySegment(rSegment.Command, k, aPairs, nPairIndex, fCurrentX, + fCurrentY, bCurrentValid, aCustomShape2d, + bReplaceGeoWidth, bReplaceGeoHeight); + } + } // end loop over all commands of subpath + // finish this subpath in any case + mpFS->endElementNS(XML_a, XML_path); + + if (!bOK) + break; // exit loop if not enough values in aPairs + + // step forward to next subpath + nSubpathStartIndex = nNextNcommandIndex + 1; + nPathSizeIndex++; + nSubPathIndex++; + } while (nSubpathStartIndex < aSegments.getLength()); + + mpFS->endElementNS(XML_a, XML_pathLst); + mpFS->endElementNS(XML_a, XML_custGeom); + return true; // We have written custGeom even if path is poorly structured. +} + +bool DrawingML::WriteCustomGeometrySegment( + const sal_Int16 eCommand, const sal_Int32 nCount, + const uno::Sequence<css::drawing::EnhancedCustomShapeParameterPair>& rPairs, + sal_Int32& rnPairIndex, double& rfCurrentX, double& rfCurrentY, bool& rbCurrentValid, + const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth, + const bool bReplaceGeoHeight) +{ + switch (eCommand) + { + case MOVETO: + { + if (rnPairIndex >= rPairs.getLength()) + return false; + + mpFS->startElementNS(XML_a, XML_moveTo); + WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, + bReplaceGeoHeight); + mpFS->endElementNS(XML_a, XML_moveTo); + rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth, + false); + rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false, + bReplaceGeoHeight); + rbCurrentValid = true; + rnPairIndex++; + break; + } + case LINETO: + { + if (rnPairIndex >= rPairs.getLength()) + return false; + // LINETO without valid current point is a faulty path. LO is tolerant and makes a + // moveTo instead. Do the same on export. MS OFFICE requires a current point for lnTo, + // otherwise it shows nothing of the shape. + if (rbCurrentValid) + { + mpFS->startElementNS(XML_a, XML_lnTo); + WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, + bReplaceGeoHeight); + mpFS->endElementNS(XML_a, XML_lnTo); + } + else + { + mpFS->startElementNS(XML_a, XML_moveTo); + WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, + bReplaceGeoHeight); + mpFS->endElementNS(XML_a, XML_moveTo); + } + rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth, + false); + rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false, + bReplaceGeoHeight); + rbCurrentValid = true; + rnPairIndex++; + break; + } + case CURVETO: + { + if (rnPairIndex + 2 >= rPairs.getLength()) + return false; + + mpFS->startElementNS(XML_a, XML_cubicBezTo); + for (sal_uInt8 i = 0; i <= 2; ++i) + { + WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth, + bReplaceGeoHeight); + } + mpFS->endElementNS(XML_a, XML_cubicBezTo); + rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth, + false); + rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 2].Second, false, + bReplaceGeoHeight); + rbCurrentValid = true; + rnPairIndex += 3; + break; + } + case ANGLEELLIPSETO: + case ANGLEELLIPSE: + { + if (rnPairIndex + 2 >= rPairs.getLength()) + return false; + + // Read parameters + double fCx = 0.0; + rCustomShape2d.GetParameter(fCx, rPairs[rnPairIndex].First, bReplaceGeoWidth, false); + double fCy = 0.0; + rCustomShape2d.GetParameter(fCy, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight); + double fWR = 0.0; + rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex + 1].First, false, false); + double fHR = 0.0; + rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex + 1].Second, false, false); + double fStartAngle = 0.0; + rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 2].First, false, false); + double fEndAngle = 0.0; + rCustomShape2d.GetParameter(fEndAngle, rPairs[rnPairIndex + 2].Second, false, false); + + // Prepare start and swing angle + sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); + sal_Int32 nSwingAng = 0; + if (basegfx::fTools::equalZero(fStartAngle) + && basegfx::fTools::equalZero(fEndAngle - 360.0)) + nSwingAng = 360 * 60000; // special case full circle + else + { + nSwingAng = std::lround((fEndAngle - fStartAngle) * 60000); + if (nSwingAng < 0) + nSwingAng += 360 * 60000; + } + + // calculate start point on ellipse + double fSx = 0.0; + double fSy = 0.0; + getEllipsePointFromViewAngle(fSx, fSy, fWR, fHR, fCx, fCy, fStartAngle); + + // write markup for going to start point + // lnTo requires a valid current point + if (eCommand == ANGLEELLIPSETO && rbCurrentValid) + { + mpFS->startElementNS(XML_a, XML_lnTo); + mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)), + XML_y, OString::number(std::lround(fSy))); + mpFS->endElementNS(XML_a, XML_lnTo); + } + else + { + mpFS->startElementNS(XML_a, XML_moveTo); + mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)), + XML_y, OString::number(std::lround(fSy))); + mpFS->endElementNS(XML_a, XML_moveTo); + } + // write markup for arcTo + if (!basegfx::fTools::equalZero(fWR) && !basegfx::fTools::equalZero(fHR)) + mpFS->singleElement( + FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR, + OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng), + XML_swAng, OString::number(nSwingAng)); + + getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy, fEndAngle); + rbCurrentValid = true; + rnPairIndex += 3; + break; + } + case ARCTO: + case ARC: + case CLOCKWISEARCTO: + case CLOCKWISEARC: + { + if (rnPairIndex + 3 >= rPairs.getLength()) + return false; + + // read parameters + double fX1 = 0.0; + rCustomShape2d.GetParameter(fX1, rPairs[rnPairIndex].First, bReplaceGeoWidth, false); + double fY1 = 0.0; + rCustomShape2d.GetParameter(fY1, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight); + double fX2 = 0.0; + rCustomShape2d.GetParameter(fX2, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth, + false); + double fY2 = 0.0; + rCustomShape2d.GetParameter(fY2, rPairs[rnPairIndex + 1].Second, false, + bReplaceGeoHeight); + double fX3 = 0.0; + rCustomShape2d.GetParameter(fX3, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth, + false); + double fY3 = 0.0; + rCustomShape2d.GetParameter(fY3, rPairs[rnPairIndex + 2].Second, false, + bReplaceGeoHeight); + double fX4 = 0.0; + rCustomShape2d.GetParameter(fX4, rPairs[rnPairIndex + 3].First, bReplaceGeoWidth, + false); + double fY4 = 0.0; + rCustomShape2d.GetParameter(fY4, rPairs[rnPairIndex + 3].Second, false, + bReplaceGeoHeight); + // calculate ellipse parameter + const double fWR = (std::max(fX1, fX2) - std::min(fX1, fX2)) / 2.0; + const double fHR = (std::max(fY1, fY2) - std::min(fY1, fY2)) / 2.0; + const double fCx = (fX1 + fX2) / 2.0; + const double fCy = (fY1 + fY2) / 2.0; + // calculate start angle + double fStartAngle = 0.0; + double fPx = 0.0; + double fPy = 0.0; + getEllipsePointAndAngleFromRayPoint(fStartAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX3, + fY3); + // markup for going to start point + // lnTo requires a valid current point. + if ((eCommand == ARCTO || eCommand == CLOCKWISEARCTO) && rbCurrentValid) + { + mpFS->startElementNS(XML_a, XML_lnTo); + mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)), + XML_y, OString::number(std::lround(fPy))); + mpFS->endElementNS(XML_a, XML_lnTo); + } + else + { + mpFS->startElementNS(XML_a, XML_moveTo); + mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)), + XML_y, OString::number(std::lround(fPy))); + mpFS->endElementNS(XML_a, XML_moveTo); + } + // calculate swing angle + double fEndAngle = 0.0; + getEllipsePointAndAngleFromRayPoint(fEndAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX4, fY4); + double fSwingAngle(fEndAngle - fStartAngle); + const bool bIsClockwise(eCommand == CLOCKWISEARCTO || eCommand == CLOCKWISEARC); + if (bIsClockwise && fSwingAngle < 0) + fSwingAngle += 360.0; + else if (!bIsClockwise && fSwingAngle > 0) + fSwingAngle -= 360.0; + // markup for arcTo + // ToDo: write markup for case zero width or height of ellipse + const sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); + const sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000)); + mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), + XML_hR, OString::number(std::lround(fHR)), XML_stAng, + OString::number(nStartAng), XML_swAng, OString::number(nSwingAng)); + rfCurrentX = fPx; + rfCurrentY = fPy; + rbCurrentValid = true; + rnPairIndex += 4; + break; + } + case ELLIPTICALQUADRANTX: + case ELLIPTICALQUADRANTY: + { + if (rnPairIndex >= rPairs.getLength()) + return false; + + // read parameters + double fX = 0.0; + rCustomShape2d.GetParameter(fX, rPairs[rnPairIndex].First, bReplaceGeoWidth, false); + double fY = 0.0; + rCustomShape2d.GetParameter(fY, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight); + + // Prepare parameters for arcTo + if (rbCurrentValid) + { + double fWR = std::abs(rfCurrentX - fX); + double fHR = std::abs(rfCurrentY - fY); + double fStartAngle(0.0); + double fSwingAngle(0.0); + // The starting direction of the arc toggles between X and Y + if ((eCommand == ELLIPTICALQUADRANTX && !(nCount % 2)) + || (eCommand == ELLIPTICALQUADRANTY && (nCount % 2))) + { + // arc starts horizontal + fStartAngle = fY < rfCurrentY ? 90.0 : 270.0; + const bool bClockwise = (fX < rfCurrentX && fY < rfCurrentY) + || (fX > rfCurrentX && fY > rfCurrentY); + fSwingAngle = bClockwise ? 90.0 : -90.0; + } + else + { + // arc starts vertical + fStartAngle = fX < rfCurrentX ? 0.0 : 180.0; + const bool bClockwise = (fX < rfCurrentX && fY > rfCurrentY) + || (fX > rfCurrentX && fY < rfCurrentY); + fSwingAngle = bClockwise ? 90.0 : -90.0; + } + sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); + sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000)); + mpFS->singleElement( + FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR, + OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng), + XML_swAng, OString::number(nSwingAng)); + } + else + { + // faulty path, but we continue with the target point + mpFS->startElementNS(XML_a, XML_moveTo); + WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth, + bReplaceGeoHeight); + mpFS->endElementNS(XML_a, XML_moveTo); + } + rfCurrentX = fX; + rfCurrentY = fY; + rbCurrentValid = true; + rnPairIndex++; + break; + } + case QUADRATICCURVETO: + { + if (rnPairIndex + 1 >= rPairs.getLength()) + return false; + + mpFS->startElementNS(XML_a, XML_quadBezTo); + for (sal_uInt8 i = 0; i < 2; ++i) + { + WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth, + bReplaceGeoHeight); + } + mpFS->endElementNS(XML_a, XML_quadBezTo); + rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth, + false); + rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 1].Second, false, + bReplaceGeoHeight); + rbCurrentValid = true; + rnPairIndex += 2; + break; + } + case ARCANGLETO: + { + if (rnPairIndex + 1 >= rPairs.getLength()) + return false; + + double fWR = 0.0; + rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex].First, false, false); + double fHR = 0.0; + rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex].Second, false, false); + double fStartAngle = 0.0; + rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 1].First, false, false); + sal_Int32 nStartAng(std::lround(fStartAngle * 60000)); + double fSwingAng = 0.0; + rCustomShape2d.GetParameter(fSwingAng, rPairs[rnPairIndex + 1].Second, false, false); + sal_Int32 nSwingAng(std::lround(fSwingAng * 60000)); + mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(fWR), XML_hR, + OString::number(fHR), XML_stAng, OString::number(nStartAng), + XML_swAng, OString::number(nSwingAng)); + double fPx = 0.0; + double fPy = 0.0; + getEllipsePointFromViewAngle(fPx, fPy, fWR, fHR, 0.0, 0.0, fStartAngle); + double fCx = rfCurrentX - fPx; + double fCy = rfCurrentY - fPy; + getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy, + fStartAngle + fSwingAng); + rbCurrentValid = true; + rnPairIndex += 2; + break; + } + default: + // do nothing + break; + } + return true; +} + +void DrawingML::WriteCustomGeometryPoint( + const drawing::EnhancedCustomShapeParameterPair& rParamPair, + const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth, + const bool bReplaceGeoHeight) +{ + sal_Int32 nX + = GetCustomGeometryPointValue(rParamPair.First, rCustomShape2d, bReplaceGeoWidth, false); + sal_Int32 nY + = GetCustomGeometryPointValue(rParamPair.Second, rCustomShape2d, false, bReplaceGeoHeight); + + mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(nX), XML_y, OString::number(nY)); +} + +void DrawingML::WriteEmptyCustomGeometry() +{ + // This method is used for export to docx in case WriteCustomGeometry fails. + mpFS->startElementNS(XML_a, XML_custGeom); + mpFS->singleElementNS(XML_a, XML_avLst); + mpFS->singleElementNS(XML_a, XML_gdLst); + mpFS->singleElementNS(XML_a, XML_ahLst); + mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b"); + mpFS->singleElementNS(XML_a, XML_pathLst); + mpFS->endElementNS(XML_a, XML_custGeom); +} + +// version for SdrPathObj +void DrawingML::WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape, + const bool bClosed) +{ + tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(rXShape); + // In case of Writer, the parent element is <wps:spPr>, and there the + // <a:custGeom> element is not optional. + if (aPolyPolygon.Count() < 1 && GetDocumentType() != DOCUMENT_DOCX) + return; + + mpFS->startElementNS(XML_a, XML_custGeom); + mpFS->singleElementNS(XML_a, XML_avLst); + mpFS->singleElementNS(XML_a, XML_gdLst); + mpFS->singleElementNS(XML_a, XML_ahLst); + mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b"); + + mpFS->startElementNS(XML_a, XML_pathLst); + + awt::Size aSize = rXShape->getSize(); + awt::Point aPos = rXShape->getPosition(); + Reference<XPropertySet> xPropertySet(rXShape, UNO_QUERY); + uno::Reference<XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); + if (xPropertySetInfo->hasPropertyByName("AnchorPosition")) + { + awt::Point aAnchorPosition; + xPropertySet->getPropertyValue("AnchorPosition") >>= aAnchorPosition; + aPos.X += aAnchorPosition.X; + aPos.Y += aAnchorPosition.Y; + } + + // Only closed SdrPathObj can be filled + std::optional<OString> sFill; + if (!bClosed) + sFill = "none"; // for possible values see ST_PathFillMode in OOXML standard + + // Put all polygons of rPolyPolygon in the same path element + // to subtract the overlapped areas. + mpFS->startElementNS(XML_a, XML_path, XML_fill, sFill, XML_w, OString::number(aSize.Width), + XML_h, OString::number(aSize.Height)); + + for (sal_uInt16 i = 0; i < aPolyPolygon.Count(); i++) + { + const tools::Polygon& aPoly = aPolyPolygon[i]; + + if (aPoly.GetSize() > 0) + { + mpFS->startElementNS(XML_a, XML_moveTo); + + mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[0].X() - aPos.X), + XML_y, OString::number(aPoly[0].Y() - aPos.Y)); + + mpFS->endElementNS(XML_a, XML_moveTo); + } + + for (sal_uInt16 j = 1; j < aPoly.GetSize(); j++) + { + PolyFlags flags = aPoly.GetFlags(j); + if (flags == PolyFlags::Control) + { + // a:cubicBezTo can only contain 3 a:pt elements, so we need to make sure of this + if (j + 2 < aPoly.GetSize() && aPoly.GetFlags(j + 1) == PolyFlags::Control + && aPoly.GetFlags(j + 2) != PolyFlags::Control) + { + mpFS->startElementNS(XML_a, XML_cubicBezTo); + for (sal_uInt8 k = 0; k <= 2; ++k) + { + mpFS->singleElementNS(XML_a, XML_pt, XML_x, + OString::number(aPoly[j + k].X() - aPos.X), XML_y, + OString::number(aPoly[j + k].Y() - aPos.Y)); + } + mpFS->endElementNS(XML_a, XML_cubicBezTo); + j += 2; + } + } + else if (flags == PolyFlags::Normal) + { + mpFS->startElementNS(XML_a, XML_lnTo); + mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[j].X() - aPos.X), + XML_y, OString::number(aPoly[j].Y() - aPos.Y)); + mpFS->endElementNS(XML_a, XML_lnTo); + } + } + } + if (bClosed) + mpFS->singleElementNS(XML_a, XML_close); + mpFS->endElementNS(XML_a, XML_path); + + mpFS->endElementNS(XML_a, XML_pathLst); + + mpFS->endElementNS(XML_a, XML_custGeom); +} + +void DrawingML::WriteConnectorConnections( sal_Int32 nStartGlueId, sal_Int32 nEndGlueId, sal_Int32 nStartID, sal_Int32 nEndID ) +{ + if( nStartID != -1 ) + { + mpFS->singleElementNS( XML_a, XML_stCxn, + XML_id, OString::number(nStartID), + XML_idx, OString::number(nStartGlueId) ); + } + if( nEndID != -1 ) + { + mpFS->singleElementNS( XML_a, XML_endCxn, + XML_id, OString::number(nEndID), + XML_idx, OString::number(nEndGlueId) ); + } +} + +sal_Unicode DrawingML::SubstituteBullet( sal_Unicode cBulletId, css::awt::FontDescriptor& rFontDesc ) +{ + if ( IsOpenSymbol(rFontDesc.Name) ) + { + rtl_TextEncoding eCharSet = rFontDesc.CharSet; + cBulletId = msfilter::util::bestFitOpenSymbolToMSFont(cBulletId, eCharSet, rFontDesc.Name); + rFontDesc.CharSet = eCharSet; + } + + return cBulletId; +} + +sax_fastparser::FSHelperPtr DrawingML::CreateOutputStream ( + const OUString& sFullStream, + std::u16string_view sRelativeStream, + const Reference< XOutputStream >& xParentRelation, + const OUString& sContentType, + const OUString& sRelationshipType, + OUString* pRelationshipId ) +{ + OUString sRelationshipId; + if (xParentRelation.is()) + sRelationshipId = GetFB()->addRelation( xParentRelation, sRelationshipType, sRelativeStream ); + else + sRelationshipId = GetFB()->addRelation( sRelationshipType, sRelativeStream ); + + if( pRelationshipId ) + *pRelationshipId = sRelationshipId; + + sax_fastparser::FSHelperPtr p = GetFB()->openFragmentStreamWithSerializer( sFullStream, sContentType ); + + return p; +} + +void DrawingML::WriteFill(const Reference<XPropertySet>& xPropSet, const awt::Size& rSize) +{ + if ( !GetProperty( xPropSet, "FillStyle" ) ) + return; + FillStyle aFillStyle( FillStyle_NONE ); + xPropSet->getPropertyValue( "FillStyle" ) >>= aFillStyle; + + // map full transparent background to no fill + if (aFillStyle == FillStyle_SOLID) + { + OUString sFillTransparenceGradientName; + + if (GetProperty(xPropSet, "FillTransparenceGradientName") + && (mAny >>= sFillTransparenceGradientName) + && !sFillTransparenceGradientName.isEmpty() + && GetProperty(xPropSet, "FillTransparenceGradient")) + { + // check if a fully transparent TransparenceGradient is used + // use BGradient constructor & tooling here now + const basegfx::BGradient aTransparenceGradient = model::gradient::getFromAny(mAny); + basegfx::BColor aSingleColor; + const bool bSingleColor(aTransparenceGradient.GetColorStops().isSingleColor(aSingleColor)); + const bool bCompletelyTransparent(bSingleColor && basegfx::fTools::equal(aSingleColor.luminance(), 1.0)); + + if (bCompletelyTransparent) + { + aFillStyle = FillStyle_NONE; + } + } + else if ( GetProperty( xPropSet, "FillTransparence" ) ) + { + // check if a fully transparent FillTransparence is used + sal_Int16 nVal = 0; + xPropSet->getPropertyValue( "FillTransparence" ) >>= nVal; + if ( nVal == 100 ) + aFillStyle = FillStyle_NONE; + } + } + + bool bUseBackground(false); + if (GetProperty(xPropSet, "FillUseSlideBackground")) + xPropSet->getPropertyValue("FillUseSlideBackground") >>= bUseBackground; + + switch( aFillStyle ) + { + case FillStyle_SOLID : + WriteSolidFill( xPropSet ); + break; + case FillStyle_GRADIENT : + WriteGradientFill( xPropSet ); + break; + case FillStyle_BITMAP : + WriteBlipFill( xPropSet, "FillBitmap", rSize ); + break; + case FillStyle_HATCH : + WritePattFill( xPropSet ); + break; + case FillStyle_NONE: + if (!bUseBackground) // attribute `useBgFill` will be written at parent p:sp shape + mpFS->singleElementNS(XML_a, XML_noFill); + break; + default: + ; + } +} + +void DrawingML::WriteStyleProperties( sal_Int32 nTokenId, const Sequence< PropertyValue >& aProperties ) +{ + if( aProperties.hasElements() ) + { + OUString sSchemeClr; + sal_uInt32 nIdx = 0; + Sequence< PropertyValue > aTransformations; + for( const auto& rProp : aProperties) + { + if( rProp.Name == "SchemeClr" ) + rProp.Value >>= sSchemeClr; + else if( rProp.Name == "Idx" ) + rProp.Value >>= nIdx; + else if( rProp.Name == "Transformations" ) + rProp.Value >>= aTransformations; + } + mpFS->startElementNS(XML_a, nTokenId, XML_idx, OString::number(nIdx)); + WriteColor(sSchemeClr, aTransformations); + mpFS->endElementNS( XML_a, nTokenId ); + } + else + { + // write mock <a:*Ref> tag + mpFS->singleElementNS(XML_a, nTokenId, XML_idx, OString::number(0)); + } +} + +void DrawingML::WriteShapeStyle( const Reference< XPropertySet >& xPropSet ) +{ + // check existence of the grab bag + if ( !GetProperty( xPropSet, "InteropGrabBag" ) ) + return; + + // extract the relevant properties from the grab bag + Sequence< PropertyValue > aGrabBag; + Sequence< PropertyValue > aFillRefProperties, aLnRefProperties, aEffectRefProperties; + mAny >>= aGrabBag; + for( const auto& rProp : std::as_const(aGrabBag)) + { + if( rProp.Name == "StyleFillRef" ) + rProp.Value >>= aFillRefProperties; + else if( rProp.Name == "StyleLnRef" ) + rProp.Value >>= aLnRefProperties; + else if( rProp.Name == "StyleEffectRef" ) + rProp.Value >>= aEffectRefProperties; + } + + WriteStyleProperties( XML_lnRef, aLnRefProperties ); + WriteStyleProperties( XML_fillRef, aFillRefProperties ); + WriteStyleProperties( XML_effectRef, aEffectRefProperties ); + + // write mock <a:fontRef> + mpFS->singleElementNS(XML_a, XML_fontRef, XML_idx, "minor"); +} + +void DrawingML::WriteShapeEffect( std::u16string_view sName, const Sequence< PropertyValue >& aEffectProps ) +{ + if( !aEffectProps.hasElements() ) + return; + + // assign the proper tag and enable bContainsColor if necessary + sal_Int32 nEffectToken = 0; + bool bContainsColor = false; + if( sName == u"outerShdw" ) + { + nEffectToken = FSNS( XML_a, XML_outerShdw ); + bContainsColor = true; + } + else if( sName == u"innerShdw" ) + { + nEffectToken = FSNS( XML_a, XML_innerShdw ); + bContainsColor = true; + } + else if( sName == u"glow" ) + { + nEffectToken = FSNS( XML_a, XML_glow ); + bContainsColor = true; + } + else if( sName == u"softEdge" ) + nEffectToken = FSNS( XML_a, XML_softEdge ); + else if( sName == u"reflection" ) + nEffectToken = FSNS( XML_a, XML_reflection ); + else if( sName == u"blur" ) + nEffectToken = FSNS( XML_a, XML_blur ); + + OUString sSchemeClr; + ::Color nRgbClr; + sal_Int32 nAlpha = MAX_PERCENT; + Sequence< PropertyValue > aTransformations; + rtl::Reference<sax_fastparser::FastAttributeList> aOuterShdwAttrList = FastSerializerHelper::createAttrList(); + for( const auto& rEffectProp : aEffectProps ) + { + if( rEffectProp.Name == "Attribs" ) + { + // read tag attributes + uno::Sequence< beans::PropertyValue > aOuterShdwProps; + rEffectProp.Value >>= aOuterShdwProps; + for( const auto& rOuterShdwProp : std::as_const(aOuterShdwProps) ) + { + if( rOuterShdwProp.Name == "algn" ) + { + OUString sVal; + rOuterShdwProp.Value >>= sVal; + aOuterShdwAttrList->add( XML_algn, sVal ); + } + else if( rOuterShdwProp.Name == "blurRad" ) + { + sal_Int64 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_blurRad, OString::number( nVal ) ); + } + else if( rOuterShdwProp.Name == "dir" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_dir, OString::number( nVal ) ); + } + else if( rOuterShdwProp.Name == "dist" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_dist, OString::number( nVal ) ); + } + else if( rOuterShdwProp.Name == "kx" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_kx, OString::number( nVal ) ); + } + else if( rOuterShdwProp.Name == "ky" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_ky, OString::number( nVal ) ); + } + else if( rOuterShdwProp.Name == "rotWithShape" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_rotWithShape, OString::number( nVal ) ); + } + else if( rOuterShdwProp.Name == "sx" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_sx, OString::number( nVal ) ); + } + else if( rOuterShdwProp.Name == "sy" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_sy, OString::number( nVal ) ); + } + else if( rOuterShdwProp.Name == "rad" ) + { + sal_Int64 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_rad, OString::number( nVal ) ); + } + else if( rOuterShdwProp.Name == "endA" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_endA, OString::number( nVal ) ); + } + else if( rOuterShdwProp.Name == "endPos" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_endPos, OString::number( nVal ) ); + } + else if( rOuterShdwProp.Name == "fadeDir" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_fadeDir, OString::number( nVal ) ); + } + else if( rOuterShdwProp.Name == "stA" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_stA, OString::number( nVal ) ); + } + else if( rOuterShdwProp.Name == "stPos" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_stPos, OString::number( nVal ) ); + } + else if( rOuterShdwProp.Name == "grow" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_grow, OString::number( nVal ) ); + } + } + } + else if(rEffectProp.Name == "RgbClr") + { + rEffectProp.Value >>= nRgbClr; + } + else if(rEffectProp.Name == "RgbClrTransparency") + { + sal_Int32 nTransparency; + if (rEffectProp.Value >>= nTransparency) + // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency()) + nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency ); + } + else if(rEffectProp.Name == "SchemeClr") + { + rEffectProp.Value >>= sSchemeClr; + } + else if(rEffectProp.Name == "SchemeClrTransformations") + { + rEffectProp.Value >>= aTransformations; + } + } + + if( nEffectToken <= 0 ) + return; + + mpFS->startElement( nEffectToken, aOuterShdwAttrList ); + + if( bContainsColor ) + { + if( sSchemeClr.isEmpty() ) + WriteColor( nRgbClr, nAlpha ); + else + WriteColor( sSchemeClr, aTransformations ); + } + + mpFS->endElement( nEffectToken ); +} + +static sal_Int32 lcl_CalculateDist(const double dX, const double dY) +{ + return static_cast< sal_Int32 >(std::hypot(dX, dY) * 360); +} + +static sal_Int32 lcl_CalculateDir(const double dX, const double dY) +{ + return (static_cast< sal_Int32 >(basegfx::rad2deg<60000>(atan2(dY,dX))) + 21600000) % 21600000; +} + +void DrawingML::WriteShapeEffects( const Reference< XPropertySet >& rXPropSet ) +{ + Sequence< PropertyValue > aGrabBag, aEffects, aOuterShdwProps; + bool bHasInteropGrabBag = rXPropSet->getPropertySetInfo()->hasPropertyByName("InteropGrabBag"); + if (bHasInteropGrabBag && GetProperty(rXPropSet, "InteropGrabBag")) + { + mAny >>= aGrabBag; + auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag), + [](const PropertyValue& rProp) { return rProp.Name == "EffectProperties"; }); + if (pProp != std::cend(aGrabBag)) + { + pProp->Value >>= aEffects; + auto pEffect = std::find_if(std::cbegin(aEffects), std::cend(aEffects), + [](const PropertyValue& rEffect) { return rEffect.Name == "outerShdw"; }); + if (pEffect != std::cend(aEffects)) + pEffect->Value >>= aOuterShdwProps; + } + } + + // tdf#132201: the order of effects is important. Effects order (CT_EffectList in ECMA-376): + // blur -> fillOverlay -> glow -> innerShdw -> outerShdw -> prstShdw -> reflection -> softEdge + + if( !aEffects.hasElements() ) + { + bool bHasShadow = false; + if( GetProperty( rXPropSet, "Shadow" ) ) + mAny >>= bHasShadow; + bool bHasEffects = bHasShadow; + if (!bHasEffects && GetProperty(rXPropSet, "GlowEffectRadius")) + { + sal_Int32 rad = 0; + mAny >>= rad; + bHasEffects = rad > 0; + } + if (!bHasEffects && GetProperty(rXPropSet, "SoftEdgeRadius")) + { + sal_Int32 rad = 0; + mAny >>= rad; + bHasEffects = rad > 0; + } + + if (bHasEffects) + { + mpFS->startElementNS(XML_a, XML_effectLst); + WriteGlowEffect(rXPropSet); + if( bHasShadow ) + { + double dX = +0.0, dY = +0.0; + sal_Int32 nBlur =0; + rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX; + rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY; + rXPropSet->getPropertyValue( "ShadowBlur" ) >>= nBlur; + + Sequence< PropertyValue > aShadowAttribsGrabBag{ + comphelper::makePropertyValue("dist", lcl_CalculateDist(dX, dY)), + comphelper::makePropertyValue("dir", lcl_CalculateDir(dX, dY)), + comphelper::makePropertyValue("blurRad", oox::drawingml::convertHmmToEmu(nBlur)), + comphelper::makePropertyValue("rotWithShape", false) //ooxml default is 'true', so must write it + }; + + Sequence< PropertyValue > aShadowGrabBag{ + comphelper::makePropertyValue("Attribs", aShadowAttribsGrabBag), + comphelper::makePropertyValue("RgbClr", rXPropSet->getPropertyValue( "ShadowColor" )), + comphelper::makePropertyValue("RgbClrTransparency", rXPropSet->getPropertyValue( "ShadowTransparence" )) + }; + + WriteShapeEffect( u"outerShdw", aShadowGrabBag ); + } + WriteSoftEdgeEffect(rXPropSet); + mpFS->endElementNS(XML_a, XML_effectLst); + } + } + else + { + for( auto& rOuterShdwProp : asNonConstRange(aOuterShdwProps) ) + { + if( rOuterShdwProp.Name == "Attribs" ) + { + Sequence< PropertyValue > aAttribsProps; + rOuterShdwProp.Value >>= aAttribsProps; + + double dX = +0.0, dY = +0.0; + sal_Int32 nBlur =0; + rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX; + rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY; + rXPropSet->getPropertyValue( "ShadowBlur" ) >>= nBlur; + + + for( auto& rAttribsProp : asNonConstRange(aAttribsProps) ) + { + if( rAttribsProp.Name == "dist" ) + { + rAttribsProp.Value <<= lcl_CalculateDist(dX, dY); + } + else if( rAttribsProp.Name == "dir" ) + { + rAttribsProp.Value <<= lcl_CalculateDir(dX, dY); + } + else if( rAttribsProp.Name == "blurRad" ) + { + rAttribsProp.Value <<= oox::drawingml::convertHmmToEmu(nBlur); + } + } + + rOuterShdwProp.Value <<= aAttribsProps; + } + else if( rOuterShdwProp.Name == "RgbClr" ) + { + rOuterShdwProp.Value = rXPropSet->getPropertyValue( "ShadowColor" ); + } + else if( rOuterShdwProp.Name == "RgbClrTransparency" ) + { + rOuterShdwProp.Value = rXPropSet->getPropertyValue( "ShadowTransparence" ); + } + } + + mpFS->startElementNS(XML_a, XML_effectLst); + bool bGlowWritten = false; + for( const auto& rEffect : std::as_const(aEffects) ) + { + if (!bGlowWritten + && (rEffect.Name == "innerShdw" || rEffect.Name == "outerShdw" + || rEffect.Name == "prstShdw" || rEffect.Name == "reflection" + || rEffect.Name == "softEdge")) + { + WriteGlowEffect(rXPropSet); + bGlowWritten = true; + } + + if( rEffect.Name == "outerShdw" ) + { + WriteShapeEffect( rEffect.Name, aOuterShdwProps ); + } + else + { + Sequence< PropertyValue > aEffectProps; + rEffect.Value >>= aEffectProps; + WriteShapeEffect( rEffect.Name, aEffectProps ); + } + } + if (!bGlowWritten) + WriteGlowEffect(rXPropSet); + WriteSoftEdgeEffect(rXPropSet); // the last + + mpFS->endElementNS(XML_a, XML_effectLst); + } +} + +void DrawingML::WriteGlowEffect(const Reference< XPropertySet >& rXPropSet) +{ + if (!rXPropSet->getPropertySetInfo()->hasPropertyByName("GlowEffectRadius")) + { + return; + } + + sal_Int32 nRad = 0; + rXPropSet->getPropertyValue("GlowEffectRadius") >>= nRad; + if (!nRad) + return; + + Sequence< PropertyValue > aGlowAttribs{ comphelper::makePropertyValue( + "rad", oox::drawingml::convertHmmToEmu(nRad)) }; + Sequence< PropertyValue > aGlowProps{ + comphelper::makePropertyValue("Attribs", aGlowAttribs), + comphelper::makePropertyValue("RgbClr", rXPropSet->getPropertyValue("GlowEffectColor")), + comphelper::makePropertyValue("RgbClrTransparency", rXPropSet->getPropertyValue("GlowEffectTransparency")) + }; + // TODO other stuff like saturation or luminance + + WriteShapeEffect(u"glow", aGlowProps); +} + +void DrawingML::WriteSoftEdgeEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet) +{ + if (!rXPropSet->getPropertySetInfo()->hasPropertyByName("SoftEdgeRadius")) + { + return; + } + + sal_Int32 nRad = 0; + rXPropSet->getPropertyValue("SoftEdgeRadius") >>= nRad; + if (!nRad) + return; + + css::uno::Sequence<css::beans::PropertyValue> aAttribs{ comphelper::makePropertyValue( + "rad", oox::drawingml::convertHmmToEmu(nRad)) }; + css::uno::Sequence<css::beans::PropertyValue> aProps{ comphelper::makePropertyValue("Attribs", + aAttribs) }; + + WriteShapeEffect(u"softEdge", aProps); +} + +void DrawingML::Write3DEffects( const Reference< XPropertySet >& xPropSet, bool bIsText ) +{ + // check existence of the grab bag + if( !GetProperty( xPropSet, "InteropGrabBag" ) ) + return; + + // extract the relevant properties from the grab bag + Sequence< PropertyValue > aGrabBag, aEffectProps, aLightRigProps, aShape3DProps; + mAny >>= aGrabBag; + + auto pShapeProp = std::find_if( std::cbegin(aGrabBag), std::cend(aGrabBag), + [bIsText](const PropertyValue& rProp) + { return rProp.Name == (bIsText ? u"Text3DEffectProperties" : u"3DEffectProperties"); }); + if (pShapeProp != std::cend(aGrabBag)) + { + Sequence< PropertyValue > a3DEffectProps; + pShapeProp->Value >>= a3DEffectProps; + for( const auto& r3DEffectProp : std::as_const(a3DEffectProps) ) + { + if( r3DEffectProp.Name == "Camera" ) + r3DEffectProp.Value >>= aEffectProps; + else if( r3DEffectProp.Name == "LightRig" ) + r3DEffectProp.Value >>= aLightRigProps; + else if( r3DEffectProp.Name == "Shape3D" ) + r3DEffectProp.Value >>= aShape3DProps; + } + } + + if( !aEffectProps.hasElements() && !aLightRigProps.hasElements() && !aShape3DProps.hasElements() ) + return; + + bool bCameraRotationPresent = false; + rtl::Reference<sax_fastparser::FastAttributeList> aCameraAttrList = FastSerializerHelper::createAttrList(); + rtl::Reference<sax_fastparser::FastAttributeList> aCameraRotationAttrList = FastSerializerHelper::createAttrList(); + for( const auto& rEffectProp : std::as_const(aEffectProps) ) + { + if( rEffectProp.Name == "prst" ) + { + OUString sVal; + rEffectProp.Value >>= sVal; + aCameraAttrList->add(XML_prst, sVal); + } + else if( rEffectProp.Name == "fov" ) + { + float fVal = 0; + rEffectProp.Value >>= fVal; + aCameraAttrList->add( XML_fov, OString::number( fVal * 60000 ) ); + } + else if( rEffectProp.Name == "zoom" ) + { + float fVal = 1; + rEffectProp.Value >>= fVal; + aCameraAttrList->add( XML_zoom, OString::number( fVal * 100000 ) ); + } + 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 ) ); + bCameraRotationPresent = true; + } + } + + bool bLightRigRotationPresent = false; + rtl::Reference<sax_fastparser::FastAttributeList> aLightRigAttrList = FastSerializerHelper::createAttrList(); + rtl::Reference<sax_fastparser::FastAttributeList> aLightRigRotationAttrList = FastSerializerHelper::createAttrList(); + for( const auto& rLightRigProp : std::as_const(aLightRigProps) ) + { + if( rLightRigProp.Name == "rig" || rLightRigProp.Name == "dir" ) + { + OUString sVal; + sal_Int32 nToken = XML_none; + rLightRigProp.Value >>= sVal; + if( rLightRigProp.Name == "rig" ) + nToken = XML_rig; + else if( rLightRigProp.Name == "dir" ) + nToken = XML_dir; + aLightRigAttrList->add(nToken, sVal); + } + else if( rLightRigProp.Name == "rotLat" || + rLightRigProp.Name == "rotLon" || + rLightRigProp.Name == "rotRev" ) + { + sal_Int32 nVal = 0, nToken = XML_none; + rLightRigProp.Value >>= nVal; + if( rLightRigProp.Name == "rotLat" ) + nToken = XML_lat; + else if( rLightRigProp.Name == "rotLon" ) + nToken = XML_lon; + else if( rLightRigProp.Name == "rotRev" ) + nToken = XML_rev; + aLightRigRotationAttrList->add( nToken, OString::number( nVal ) ); + bLightRigRotationPresent = true; + } + } + + mpFS->startElementNS(XML_a, XML_scene3d); + + if( aEffectProps.hasElements() ) + { + mpFS->startElementNS( XML_a, XML_camera, aCameraAttrList ); + if( bCameraRotationPresent ) + { + mpFS->singleElementNS( XML_a, XML_rot, aCameraRotationAttrList ); + } + mpFS->endElementNS( XML_a, XML_camera ); + } + else + { + // a:camera with Word default values - Word won't open the document if this is not present + mpFS->singleElementNS(XML_a, XML_camera, XML_prst, "orthographicFront"); + } + + if( aEffectProps.hasElements() ) + { + mpFS->startElementNS( XML_a, XML_lightRig, aLightRigAttrList ); + if( bLightRigRotationPresent ) + { + mpFS->singleElementNS( XML_a, XML_rot, aLightRigRotationAttrList ); + } + mpFS->endElementNS( XML_a, XML_lightRig ); + } + else + { + // a:lightRig with Word default values - Word won't open the document if this is not present + mpFS->singleElementNS(XML_a, XML_lightRig, XML_rig, "threePt", XML_dir, "t"); + } + + mpFS->endElementNS( XML_a, XML_scene3d ); + + if( !aShape3DProps.hasElements() ) + return; + + bool bBevelTPresent = false, bBevelBPresent = false; + Sequence< PropertyValue > aExtrusionColorProps, aContourColorProps; + rtl::Reference<sax_fastparser::FastAttributeList> aBevelTAttrList = FastSerializerHelper::createAttrList(); + rtl::Reference<sax_fastparser::FastAttributeList> aBevelBAttrList = FastSerializerHelper::createAttrList(); + rtl::Reference<sax_fastparser::FastAttributeList> aShape3DAttrList = FastSerializerHelper::createAttrList(); + for( const auto& rShape3DProp : std::as_const(aShape3DProps) ) + { + if( rShape3DProp.Name == "extrusionH" || rShape3DProp.Name == "contourW" || rShape3DProp.Name == "z" ) + { + sal_Int32 nVal = 0, nToken = XML_none; + rShape3DProp.Value >>= nVal; + if( rShape3DProp.Name == "extrusionH" ) + nToken = XML_extrusionH; + else if( rShape3DProp.Name == "contourW" ) + nToken = XML_contourW; + else if( rShape3DProp.Name == "z" ) + nToken = XML_z; + aShape3DAttrList->add( nToken, OString::number( nVal ) ); + } + else if( rShape3DProp.Name == "prstMaterial" ) + { + OUString sVal; + rShape3DProp.Value >>= sVal; + aShape3DAttrList->add(XML_prstMaterial, sVal); + } + else if( rShape3DProp.Name == "extrusionClr" ) + { + rShape3DProp.Value >>= aExtrusionColorProps; + } + else if( rShape3DProp.Name == "contourClr" ) + { + rShape3DProp.Value >>= aContourColorProps; + } + else if( rShape3DProp.Name == "bevelT" || rShape3DProp.Name == "bevelB" ) + { + Sequence< PropertyValue > aBevelProps; + rShape3DProp.Value >>= aBevelProps; + if ( !aBevelProps.hasElements() ) + continue; + + rtl::Reference<sax_fastparser::FastAttributeList> aBevelAttrList; + if( rShape3DProp.Name == "bevelT" ) + { + bBevelTPresent = true; + aBevelAttrList = aBevelTAttrList; + } + else + { + bBevelBPresent = true; + aBevelAttrList = aBevelBAttrList; + } + for( const auto& rBevelProp : std::as_const(aBevelProps) ) + { + if( rBevelProp.Name == "w" || rBevelProp.Name == "h" ) + { + sal_Int32 nVal = 0, nToken = XML_none; + rBevelProp.Value >>= nVal; + if( rBevelProp.Name == "w" ) + nToken = XML_w; + else if( rBevelProp.Name == "h" ) + nToken = XML_h; + aBevelAttrList->add( nToken, OString::number( nVal ) ); + } + else if( rBevelProp.Name == "prst" ) + { + OUString sVal; + rBevelProp.Value >>= sVal; + aBevelAttrList->add(XML_prst, sVal); + } + } + + } + } + + mpFS->startElementNS( XML_a, XML_sp3d, aShape3DAttrList ); + if( bBevelTPresent ) + { + mpFS->singleElementNS( XML_a, XML_bevelT, aBevelTAttrList ); + } + if( bBevelBPresent ) + { + mpFS->singleElementNS( XML_a, XML_bevelB, aBevelBAttrList ); + } + if( aExtrusionColorProps.hasElements() ) + { + OUString sSchemeClr; + ::Color nColor; + sal_Int32 nTransparency(0); + Sequence< PropertyValue > aColorTransformations; + for( const auto& rExtrusionColorProp : std::as_const(aExtrusionColorProps) ) + { + if( rExtrusionColorProp.Name == "schemeClr" ) + rExtrusionColorProp.Value >>= sSchemeClr; + else if( rExtrusionColorProp.Name == "schemeClrTransformations" ) + rExtrusionColorProp.Value >>= aColorTransformations; + else if( rExtrusionColorProp.Name == "rgbClr" ) + rExtrusionColorProp.Value >>= nColor; + else if( rExtrusionColorProp.Name == "rgbClrTransparency" ) + rExtrusionColorProp.Value >>= nTransparency; + } + mpFS->startElementNS(XML_a, XML_extrusionClr); + + if( sSchemeClr.isEmpty() ) + WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) ); + else + WriteColor( sSchemeClr, aColorTransformations ); + + mpFS->endElementNS( XML_a, XML_extrusionClr ); + } + if( aContourColorProps.hasElements() ) + { + OUString sSchemeClr; + ::Color nColor; + sal_Int32 nTransparency(0); + Sequence< PropertyValue > aColorTransformations; + for( const auto& rContourColorProp : std::as_const(aContourColorProps) ) + { + if( rContourColorProp.Name == "schemeClr" ) + rContourColorProp.Value >>= sSchemeClr; + else if( rContourColorProp.Name == "schemeClrTransformations" ) + rContourColorProp.Value >>= aColorTransformations; + else if( rContourColorProp.Name == "rgbClr" ) + rContourColorProp.Value >>= nColor; + else if( rContourColorProp.Name == "rgbClrTransparency" ) + rContourColorProp.Value >>= nTransparency; + } + mpFS->startElementNS(XML_a, XML_contourClr); + + if( sSchemeClr.isEmpty() ) + WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) ); + else + WriteColor( sSchemeClr, aContourColorProps ); + + mpFS->endElementNS( XML_a, XML_contourClr ); + } + mpFS->endElementNS( XML_a, XML_sp3d ); +} + +void DrawingML::WriteArtisticEffect( const Reference< XPropertySet >& rXPropSet ) +{ + if( !GetProperty( rXPropSet, "InteropGrabBag" ) ) + return; + + PropertyValue aEffect; + Sequence< PropertyValue > aGrabBag; + mAny >>= aGrabBag; + auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag), + [](const PropertyValue& rProp) { return rProp.Name == "ArtisticEffectProperties"; }); + if (pProp != std::cend(aGrabBag)) + pProp->Value >>= aEffect; + sal_Int32 nEffectToken = ArtisticEffectProperties::getEffectToken( aEffect.Name ); + if( nEffectToken == XML_none ) + return; + + Sequence< PropertyValue > aAttrs; + aEffect.Value >>= aAttrs; + rtl::Reference<sax_fastparser::FastAttributeList> aAttrList = FastSerializerHelper::createAttrList(); + OString sRelId; + for( const auto& rAttr : std::as_const(aAttrs) ) + { + sal_Int32 nToken = ArtisticEffectProperties::getEffectToken( rAttr.Name ); + if( nToken != XML_none ) + { + sal_Int32 nVal = 0; + rAttr.Value >>= nVal; + aAttrList->add( nToken, OString::number( nVal ) ); + } + else if( rAttr.Name == "OriginalGraphic" ) + { + Sequence< PropertyValue > aGraphic; + rAttr.Value >>= aGraphic; + Sequence< sal_Int8 > aGraphicData; + OUString sGraphicId; + for( const auto& rProp : std::as_const(aGraphic) ) + { + if( rProp.Name == "Id" ) + rProp.Value >>= sGraphicId; + else if( rProp.Name == "Data" ) + rProp.Value >>= aGraphicData; + } + sRelId = WriteWdpPicture( sGraphicId, aGraphicData ); + } + } + + mpFS->startElementNS(XML_a, XML_extLst); + mpFS->startElementNS(XML_a, XML_ext, XML_uri, "{BEBA8EAE-BF5A-486C-A8C5-ECC9F3942E4B}"); + mpFS->startElementNS( XML_a14, XML_imgProps, + FSNS(XML_xmlns, XML_a14), mpFB->getNamespaceURL(OOX_NS(a14)) ); + mpFS->startElementNS(XML_a14, XML_imgLayer, FSNS(XML_r, XML_embed), sRelId); + mpFS->startElementNS(XML_a14, XML_imgEffect); + + mpFS->singleElementNS( XML_a14, nEffectToken, aAttrList ); + + mpFS->endElementNS( XML_a14, XML_imgEffect ); + mpFS->endElementNS( XML_a14, XML_imgLayer ); + mpFS->endElementNS( XML_a14, XML_imgProps ); + mpFS->endElementNS( XML_a, XML_ext ); + mpFS->endElementNS( XML_a, XML_extLst ); +} + +OString DrawingML::WriteWdpPicture( const OUString& rFileId, const Sequence< sal_Int8 >& rPictureData ) +{ + auto& rGraphicExportCache = GraphicExportCache::get(); + + OUString aId = rGraphicExportCache.findWdpID(rFileId); + if (!aId.isEmpty()) + return OUStringToOString(aId, RTL_TEXTENCODING_UTF8); + + sal_Int32 nWdpImageCount = rGraphicExportCache.nextWdpImageCount(); + OUString sFileName = u"media/hdphoto"_ustr + OUString::number(nWdpImageCount) + u".wdp"_ustr; + OUString sFragment = GetComponentDir() + u"/"_ustr + sFileName; + Reference< XOutputStream > xOutStream = mpFB->openFragmentStream(sFragment, "image/vnd.ms-photo"); + xOutStream->writeBytes( rPictureData ); + xOutStream->closeOutput(); + + aId = mpFB->addRelation(mpFS->getOutputStream(), + oox::getRelationship(Relationship::HDPHOTO), + Concat2View(GetRelationCompPrefix() + sFileName)); + + rGraphicExportCache.addToWdpCache(rFileId, aId); + + return OUStringToOString(aId, RTL_TEXTENCODING_UTF8); +} + +void DrawingML::WriteDiagram(const css::uno::Reference<css::drawing::XShape>& rXShape, int nDiagramId) +{ + uno::Reference<beans::XPropertySet> xPropSet(rXShape, uno::UNO_QUERY); + + uno::Reference<xml::dom::XDocument> dataDom; + uno::Reference<xml::dom::XDocument> layoutDom; + uno::Reference<xml::dom::XDocument> styleDom; + uno::Reference<xml::dom::XDocument> colorDom; + uno::Reference<xml::dom::XDocument> drawingDom; + uno::Sequence<uno::Sequence<uno::Any>> xDataRelSeq; + uno::Sequence<uno::Any> diagramDrawing; + + // retrieve the doms from the GrabBag + uno::Sequence<beans::PropertyValue> propList; + xPropSet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= propList; + for (const auto& rProp : std::as_const(propList)) + { + OUString propName = rProp.Name; + if (propName == "OOXData") + rProp.Value >>= dataDom; + else if (propName == "OOXLayout") + rProp.Value >>= layoutDom; + else if (propName == "OOXStyle") + rProp.Value >>= styleDom; + else if (propName == "OOXColor") + rProp.Value >>= colorDom; + else if (propName == "OOXDrawing") + { + rProp.Value >>= diagramDrawing; + diagramDrawing[0] + >>= drawingDom; // if there is OOXDrawing property then set drawingDom here only. + } + else if (propName == "OOXDiagramDataRels") + rProp.Value >>= xDataRelSeq; + } + + // check that we have the 4 mandatory XDocuments + // if not, there was an error importing and we won't output anything + if (!dataDom.is() || !layoutDom.is() || !styleDom.is() || !colorDom.is()) + return; + + // generate a unique id + rtl::Reference<sax_fastparser::FastAttributeList> pDocPrAttrList + = sax_fastparser::FastSerializerHelper::createAttrList(); + pDocPrAttrList->add(XML_id, OString::number(nDiagramId)); + OString sName = "Diagram" + OString::number(nDiagramId); + pDocPrAttrList->add(XML_name, sName); + + if (GetDocumentType() == DOCUMENT_DOCX) + { + mpFS->singleElementNS(XML_wp, XML_docPr, pDocPrAttrList); + mpFS->singleElementNS(XML_wp, XML_cNvGraphicFramePr); + + mpFS->startElementNS(XML_a, XML_graphic, FSNS(XML_xmlns, XML_a), + mpFB->getNamespaceURL(OOX_NS(dml))); + } + else + { + mpFS->startElementNS(XML_p, XML_nvGraphicFramePr); + + mpFS->singleElementNS(XML_p, XML_cNvPr, pDocPrAttrList); + mpFS->singleElementNS(XML_p, XML_cNvGraphicFramePr); + + mpFS->startElementNS(XML_p, XML_nvPr); + mpFS->startElementNS(XML_p, XML_extLst); + // change tracking extension - required in PPTX + mpFS->startElementNS(XML_p, XML_ext, XML_uri, "{D42A27DB-BD31-4B8C-83A1-F6EECF244321}"); + mpFS->singleElementNS(XML_p14, XML_modId, + FSNS(XML_xmlns, XML_p14), mpFB->getNamespaceURL(OOX_NS(p14)), + XML_val, + OString::number(comphelper::rng::uniform_uint_distribution(1, SAL_MAX_UINT32))); + mpFS->endElementNS(XML_p, XML_ext); + mpFS->endElementNS(XML_p, XML_extLst); + mpFS->endElementNS(XML_p, XML_nvPr); + + mpFS->endElementNS(XML_p, XML_nvGraphicFramePr); + + // store size and position of background shape instead of group shape + // as some shapes may be outside + css::uno::Reference<css::drawing::XShapes> xShapes(rXShape, uno::UNO_QUERY); + if (xShapes.is() && xShapes->hasElements()) + { + css::uno::Reference<css::drawing::XShape> xShapeBg(xShapes->getByIndex(0), + uno::UNO_QUERY); + awt::Point aPos = xShapeBg->getPosition(); + awt::Size aSize = xShapeBg->getSize(); + WriteTransformation( + xShapeBg, tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), + XML_p, false, false, 0, false); + } + + mpFS->startElementNS(XML_a, XML_graphic); + } + + mpFS->startElementNS(XML_a, XML_graphicData, XML_uri, + "http://schemas.openxmlformats.org/drawingml/2006/diagram"); + + OUString sRelationCompPrefix = GetRelationCompPrefix(); + + // add data relation + OUString dataFileName = "diagrams/data" + OUString::number(nDiagramId) + ".xml"; + OUString dataRelId = + mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDATA), + Concat2View(sRelationCompPrefix + dataFileName)); + + // add layout relation + OUString layoutFileName = "diagrams/layout" + OUString::number(nDiagramId) + ".xml"; + OUString layoutRelId = mpFB->addRelation(mpFS->getOutputStream(), + oox::getRelationship(Relationship::DIAGRAMLAYOUT), + Concat2View(sRelationCompPrefix + layoutFileName)); + + // add style relation + OUString styleFileName = "diagrams/quickStyle" + OUString::number(nDiagramId) + ".xml"; + OUString styleRelId = mpFB->addRelation(mpFS->getOutputStream(), + oox::getRelationship(Relationship::DIAGRAMQUICKSTYLE), + Concat2View(sRelationCompPrefix + styleFileName)); + + // add color relation + OUString colorFileName = "diagrams/colors" + OUString::number(nDiagramId) + ".xml"; + OUString colorRelId = mpFB->addRelation(mpFS->getOutputStream(), + oox::getRelationship(Relationship::DIAGRAMCOLORS), + Concat2View(sRelationCompPrefix + colorFileName)); + + OUString drawingFileName; + if (drawingDom.is()) + { + // add drawing relation + drawingFileName = "diagrams/drawing" + OUString::number(nDiagramId) + ".xml"; + OUString drawingRelId = mpFB->addRelation( + mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDRAWING), + Concat2View(sRelationCompPrefix + drawingFileName)); + + // the data dom contains a reference to the drawing relation. We need to update it with the new generated + // relation value before writing the dom to a file + + // Get the dsp:damaModelExt node from the dom + uno::Reference<xml::dom::XNodeList> nodeList = dataDom->getElementsByTagNameNS( + "http://schemas.microsoft.com/office/drawing/2008/diagram", "dataModelExt"); + + // There must be one element only so get it + uno::Reference<xml::dom::XNode> node = nodeList->item(0); + + // Get the list of attributes of the node + uno::Reference<xml::dom::XNamedNodeMap> nodeMap = node->getAttributes(); + + // Get the node with the relId attribute and set its new value + uno::Reference<xml::dom::XNode> relIdNode = nodeMap->getNamedItem("relId"); + relIdNode->setNodeValue(drawingRelId); + } + + mpFS->singleElementNS(XML_dgm, XML_relIds, + FSNS(XML_xmlns, XML_dgm), mpFB->getNamespaceURL(OOX_NS(dmlDiagram)), + FSNS(XML_xmlns, XML_r), mpFB->getNamespaceURL(OOX_NS(officeRel)), + FSNS(XML_r, XML_dm), dataRelId, FSNS(XML_r, XML_lo), layoutRelId, + FSNS(XML_r, XML_qs), styleRelId, FSNS(XML_r, XML_cs), colorRelId); + + mpFS->endElementNS(XML_a, XML_graphicData); + mpFS->endElementNS(XML_a, XML_graphic); + + uno::Reference<xml::sax::XSAXSerializable> serializer; + uno::Reference<xml::sax::XWriter> writer + = xml::sax::Writer::create(comphelper::getProcessComponentContext()); + + OUString sDir = GetComponentDir(); + + // write data file + serializer.set(dataDom, uno::UNO_QUERY); + uno::Reference<io::XOutputStream> xDataOutputStream = mpFB->openFragmentStream( + sDir + "/" + dataFileName, + "application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml"); + writer->setOutputStream(xDataOutputStream); + serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), + uno::Sequence<beans::StringPair>()); + + // write the associated Images and rels for data file + writeDiagramRels(xDataRelSeq, xDataOutputStream, u"OOXDiagramDataRels", nDiagramId); + + // write layout file + serializer.set(layoutDom, uno::UNO_QUERY); + writer->setOutputStream(mpFB->openFragmentStream( + sDir + "/" + layoutFileName, + "application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml")); + serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), + uno::Sequence<beans::StringPair>()); + + // write style file + serializer.set(styleDom, uno::UNO_QUERY); + writer->setOutputStream(mpFB->openFragmentStream( + sDir + "/" + styleFileName, + "application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml")); + serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), + uno::Sequence<beans::StringPair>()); + + // write color file + serializer.set(colorDom, uno::UNO_QUERY); + writer->setOutputStream(mpFB->openFragmentStream( + sDir + "/" + colorFileName, + "application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml")); + serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), + uno::Sequence<beans::StringPair>()); + + // write drawing file + if (!drawingDom.is()) + return; + + serializer.set(drawingDom, uno::UNO_QUERY); + uno::Reference<io::XOutputStream> xDrawingOutputStream = mpFB->openFragmentStream( + sDir + "/" + drawingFileName, "application/vnd.ms-office.drawingml.diagramDrawing+xml"); + writer->setOutputStream(xDrawingOutputStream); + serializer->serialize( + uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), + uno::Sequence<beans::StringPair>()); + + // write the associated Images and rels for drawing file + uno::Sequence<uno::Sequence<uno::Any>> xDrawingRelSeq; + diagramDrawing[1] >>= xDrawingRelSeq; + writeDiagramRels(xDrawingRelSeq, xDrawingOutputStream, u"OOXDiagramDrawingRels", nDiagramId); +} + +void DrawingML::writeDiagramRels(const uno::Sequence<uno::Sequence<uno::Any>>& xRelSeq, + const uno::Reference<io::XOutputStream>& xOutStream, + std::u16string_view sGrabBagProperyName, int nDiagramId) +{ + // add image relationships of OOXData, OOXDiagram + OUString sType(oox::getRelationship(Relationship::IMAGE)); + uno::Reference<xml::sax::XWriter> xWriter + = xml::sax::Writer::create(comphelper::getProcessComponentContext()); + xWriter->setOutputStream(xOutStream); + + // retrieve the relationships from Sequence + for (sal_Int32 j = 0; j < xRelSeq.getLength(); j++) + { + // diagramDataRelTuple[0] => RID, + // diagramDataRelTuple[1] => xInputStream + // diagramDataRelTuple[2] => extension + uno::Sequence<uno::Any> diagramDataRelTuple = xRelSeq[j]; + + OUString sRelId; + OUString sExtension; + diagramDataRelTuple[0] >>= sRelId; + diagramDataRelTuple[2] >>= sExtension; + OUString sContentType; + if (sExtension.equalsIgnoreAsciiCase(".WMF")) + sContentType = "image/x-wmf"; + else + sContentType = OUString::Concat("image/") + sExtension.subView(1); + sRelId = sRelId.copy(3); + + StreamDataSequence dataSeq; + diagramDataRelTuple[1] >>= dataSeq; + uno::Reference<io::XInputStream> dataImagebin( + new ::comphelper::SequenceInputStream(dataSeq)); + + //nDiagramId is used to make the name unique irrespective of the number of smart arts. + OUString sFragment = OUString::Concat("media/") + sGrabBagProperyName + + OUString::number(nDiagramId) + "_" + + OUString::number(j) + sExtension; + + PropertySet aProps(xOutStream); + aProps.setAnyProperty(PROP_RelId, uno::Any(sRelId.toInt32())); + + mpFB->addRelation(xOutStream, sType, Concat2View("../" + sFragment)); + + OUString sDir = GetComponentDir(); + uno::Reference<io::XOutputStream> xBinOutStream + = mpFB->openFragmentStream(sDir + "/" + sFragment, sContentType); + + try + { + comphelper::OStorageHelper::CopyInputToOutput(dataImagebin, xBinOutStream); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("oox.drawingml", "DrawingML::writeDiagramRels Failed to copy grabbaged Image"); + } + dataImagebin->closeInput(); + } +} + +void DrawingML::WriteFromTo(const uno::Reference<css::drawing::XShape>& rXShape, const awt::Size& aPageSize, + const FSHelperPtr& pDrawing) +{ + awt::Point aTopLeft = rXShape->getPosition(); + awt::Size aSize = rXShape->getSize(); + + SdrObject* pObj = SdrObject::getSdrObjectFromXShape(rXShape); + if (pObj) + { + Degree100 nRotation = pObj->GetRotateAngle(); + if (nRotation) + { + sal_Int16 nHalfWidth = aSize.Width / 2; + sal_Int16 nHalfHeight = aSize.Height / 2; + // aTopLeft needs correction for rotated customshapes + if (pObj->GetObjIdentifier() == SdrObjKind::CustomShape) + { + // Center of bounding box of the rotated shape + const auto aSnapRectCenter(pObj->GetSnapRect().Center()); + aTopLeft.X = aSnapRectCenter.X() - nHalfWidth; + aTopLeft.Y = aSnapRectCenter.Y() - nHalfHeight; + } + + // MSO changes the anchor positions at these angles and that does an extra 90 degrees + // rotation on our shapes, so we output it in such position that MSO + // can draw this shape correctly. + if ((nRotation >= 4500_deg100 && nRotation < 13500_deg100) || (nRotation >= 22500_deg100 && nRotation < 31500_deg100)) + { + aTopLeft.X = aTopLeft.X - nHalfHeight + nHalfWidth; + aTopLeft.Y = aTopLeft.Y - nHalfWidth + nHalfHeight; + + std::swap(aSize.Width, aSize.Height); + } + } + } + + tools::Rectangle aLocation(aTopLeft.X, aTopLeft.Y, aTopLeft.X + aSize.Width, aTopLeft.Y + aSize.Height); + double nXpos = static_cast<double>(aLocation.TopLeft().getX()) / static_cast<double>(aPageSize.Width); + double nYpos = static_cast<double>(aLocation.TopLeft().getY()) / static_cast<double>(aPageSize.Height); + + pDrawing->startElement(FSNS(XML_cdr, XML_from)); + pDrawing->startElement(FSNS(XML_cdr, XML_x)); + pDrawing->write(nXpos); + pDrawing->endElement(FSNS(XML_cdr, XML_x)); + pDrawing->startElement(FSNS(XML_cdr, XML_y)); + pDrawing->write(nYpos); + pDrawing->endElement(FSNS(XML_cdr, XML_y)); + pDrawing->endElement(FSNS(XML_cdr, XML_from)); + + nXpos = static_cast<double>(aLocation.BottomRight().getX()) / static_cast<double>(aPageSize.Width); + nYpos = static_cast<double>(aLocation.BottomRight().getY()) / static_cast<double>(aPageSize.Height); + + pDrawing->startElement(FSNS(XML_cdr, XML_to)); + pDrawing->startElement(FSNS(XML_cdr, XML_x)); + pDrawing->write(nXpos); + pDrawing->endElement(FSNS(XML_cdr, XML_x)); + pDrawing->startElement(FSNS(XML_cdr, XML_y)); + pDrawing->write(nYpos); + pDrawing->endElement(FSNS(XML_cdr, XML_y)); + pDrawing->endElement(FSNS(XML_cdr, XML_to)); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/export/ooxml-export-notes.txt b/oox/source/export/ooxml-export-notes.txt new file mode 100644 index 0000000000..1da7ffd2f1 --- /dev/null +++ b/oox/source/export/ooxml-export-notes.txt @@ -0,0 +1,234 @@ +# +# 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 + +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 0000000000..5760a86090 --- /dev/null +++ b/oox/source/export/preset-definitions-to-shape-types.pl @@ -0,0 +1,1237 @@ +#!/usr/bin/env perl -w +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +use strict; +use warnings; + +sub usage() { + print STDERR <<EOF; +Usage: preset-definitions-to-shape-types.pl [ --drawingml-adj-names-data | --vml-shape-types-data ] <shapes> <text> + +Converts presetShapeDefinitions.xml and presetTextWarpDefinitions.xml to a +.cxx that contains VML with the definitions of the shapes. The result is +written to stdout. + +<shapes> presetShapeDefinitions.xml (including the path to it) +<text> presetTextWarpDefinitions.xml (including the path to it) +EOF + exit 1; +} + +sub show_call_stack +{ + my ( $path, $line, $subr ); + my $max_depth = 30; + my $i = 1; + print STDERR "--- Begin stack trace ---\n"; + while ( (my @call_details = (caller($i++))) && ($i<$max_depth) ) { + print STDERR "$call_details[1] line $call_details[2] in function $call_details[3]\n"; + } + print STDERR "--- End stack trace ---\n"; +} + +my $drawingml_adj_names_data = 0; +my $vml_shape_types_data = 0; +my $src_shapes = shift; +if ($src_shapes eq "--drawingml-adj-names-data") { + $drawingml_adj_names_data = 1; + $src_shapes = shift; +} elsif ($src_shapes eq "--vml-shape-types-data") { + $vml_shape_types_data = 1; + $src_shapes = shift; +} +my $src_text = shift; + +usage() if ( !defined( $src_shapes ) || !defined( $src_text ) || + $src_shapes eq "-h" || $src_shapes eq "--help" || + !-f $src_shapes || !-f $src_text ); + +# Global variables +my @levels = (); +my $shape_name = ""; +my $state = ""; +my $path = ""; +my $adjust = ""; +my %adj_names; +my $max_adj_no = 0; +my @formulas = (); +my %variables = (); +my $ignore_this_shape = 0; +my $handles = ""; +my $textboxrect = ""; +my $last_pos_x = ""; +my $last_pos_y = ""; +my $no_stroke = 0; +my $no_fill = 0; +my $path_w = 1; +my $path_h = 1; +my @quadratic_bezier = (); + +my %result_shapes = (); + +my %shapes_ids = ( + 0 => 'notPrimitive', + 1 => 'rectangle', + 2 => 'roundRectangle', + 3 => 'ellipse', + 4 => 'diamond', + 5 => 'triangle', + 6 => 'rtTriangle', + 7 => 'parallelogram', + 8 => 'trapezoid', + 9 => 'hexagon', + 10 => 'octagon', + 11 => 'plus', + 12 => 'star5', + 13 => 'rightArrow', + 14 => 'thickArrow', # should not be used + 15 => 'homePlate', + 16 => 'cube', + 17 => 'wedgeRoundRectCallout', # balloon + 18 => 'star16', # seal + 19 => 'arc', + 20 => 'line', + 21 => 'plaque', + 22 => 'can', + 23 => 'donut', + 24 => 'textPlain', # textSimple - FIXME MS Office 2007 converts these to textboxes with unstyled text, so is it actually correct to map it to a real style? + 25 => 'textStop', # textOctagon FIXME see 24 + 26 => 'textTriangle', # textHexagon FIXMME see 24 + 27 => 'textCanDown', # textCurve FIXMME see 24 + 28 => 'textWave1', # textWave FIXMME see 24 + 29 => 'textArchUpPour', # textRing FIXMME see 24 + 30 => 'textCanDown', # textOnCurve FIXMME see 24 + 31 => 'textArchUp', # textOnRing FIXMME see 24 + 32 => 'straightConnector1', + 33 => 'bentConnector2', + 34 => 'bentConnector3', + 35 => 'bentConnector4', + 36 => 'bentConnector5', + 37 => 'curvedConnector2', + 38 => 'curvedConnector3', + 39 => 'curvedConnector4', + 40 => 'curvedConnector5', + 41 => 'callout1', + 42 => 'callout2', + 43 => 'callout3', + 44 => 'accentCallout1', + 45 => 'accentCallout2', + 46 => 'accentCallout3', + 47 => 'borderCallout1', + 48 => 'borderCallout2', + 49 => 'borderCallout3', + 50 => 'accentBorderCallout1', + 51 => 'accentBorderCallout2', + 52 => 'accentBorderCallout3', + 53 => 'ribbon', + 54 => 'ribbon2', + 55 => 'chevron', + 56 => 'pentagon', + 57 => 'noSmoking', + 58 => 'star8', # seal8 + 59 => 'star16', # seal16 + 60 => 'star32', # seal32 + 61 => 'wedgeRectCallout', + 62 => 'wedgeRoundRectCallout', # wedgeRRectCallout + 63 => 'wedgeEllipseCallout', + 64 => 'wave', + 65 => 'foldedCorner', + 66 => 'leftArrow', + 67 => 'downArrow', + 68 => 'upArrow', + 69 => 'leftRightArrow', + 70 => 'upDownArrow', + 71 => 'irregularSeal1', + 72 => 'irregularSeal2', + 73 => 'lightningBolt', + 74 => 'heart', + 75 => 'pictureFrame', + 76 => 'quadArrow', + 77 => 'leftArrowCallout', + 78 => 'rightArrowCallout', + 79 => 'upArrowCallout', + 80 => 'downArrowCallout', + 81 => 'leftRightArrowCallout', + 82 => 'upDownArrowCallout', + 83 => 'quadArrowCallout', + 84 => 'bevel', + 85 => 'leftBracket', + 86 => 'rightBracket', + 87 => 'leftBrace', + 88 => 'rightBrace', + 89 => 'leftUpArrow', + 90 => 'bentUpArrow', + 91 => 'bentArrow', + 92 => 'star24', # seal24 + 93 => 'stripedRightArrow', + 94 => 'notchedRightArrow', + 95 => 'blockArc', + 96 => 'smileyFace', + 97 => 'verticalScroll', + 98 => 'horizontalScroll', + 99 => 'circularArrow', + 100 => 'notchedCircularArrow', # should not be used + 101 => 'uturnArrow', + 102 => 'curvedRightArrow', + 103 => 'curvedLeftArrow', + 104 => 'curvedUpArrow', + 105 => 'curvedDownArrow', + 106 => 'cloudCallout', + 107 => 'ellipseRibbon', + 108 => 'ellipseRibbon2', + 109 => 'flowChartProcess', + 110 => 'flowChartDecision', + 111 => 'flowChartInputOutput', + 112 => 'flowChartPredefinedProcess', + 113 => 'flowChartInternalStorage', + 114 => 'flowChartDocument', + 115 => 'flowChartMultidocument', + 116 => 'flowChartTerminator', + 117 => 'flowChartPreparation', + 118 => 'flowChartManualInput', + 119 => 'flowChartManualOperation', + 120 => 'flowChartConnector', + 121 => 'flowChartPunchedCard', + 122 => 'flowChartPunchedTape', + 123 => 'flowChartSummingJunction', + 124 => 'flowChartOr', + 125 => 'flowChartCollate', + 126 => 'flowChartSort', + 127 => 'flowChartExtract', + 128 => 'flowChartMerge', + 129 => 'flowChartOfflineStorage', + 130 => 'flowChartOnlineStorage', + 131 => 'flowChartMagneticTape', + 132 => 'flowChartMagneticDisk', + 133 => 'flowChartMagneticDrum', + 134 => 'flowChartDisplay', + 135 => 'flowChartDelay', + 136 => 'textPlain', # textPlainText + 137 => 'textStop', + 138 => 'textTriangle', + 139 => 'textTriangleInverted', + 140 => 'textChevron', + 141 => 'textChevronInverted', + 142 => 'textRingInside', + 143 => 'textRingOutside', + 144 => 'textArchUp', # textArchUpCurve + 145 => 'textArchDown', # textArchDownCurve + 146 => 'textCircle', # textCircleCurve + 147 => 'textButton', # textButtonCurve + 148 => 'textArchUpPour', + 149 => 'textArchDownPour', + 150 => 'textCirclePour', + 151 => 'textButtonPour', + 152 => 'textCurveUp', + 153 => 'textCurveDown', + 154 => 'textCascadeUp', + 155 => 'textCascadeDown', + 156 => 'textWave1', + 157 => 'textWave2', + 158 => 'textWave3', + 159 => 'textWave4', + 160 => 'textInflate', + 161 => 'textDeflate', + 162 => 'textInflateBottom', + 163 => 'textDeflateBottom', + 164 => 'textInflateTop', + 165 => 'textDeflateTop', + 166 => 'textDeflateInflate', + 167 => 'textDeflateInflateDeflate', + 168 => 'textFadeRight', + 169 => 'textFadeLeft', + 170 => 'textFadeUp', + 171 => 'textFadeDown', + 172 => 'textSlantUp', + 173 => 'textSlantDown', + 174 => 'textCanUp', + 175 => 'textCanDown', + 176 => 'flowChartAlternateProcess', + 177 => 'flowChartOffpageConnector', + 178 => 'callout1', # callout90 + 179 => 'accentCallout1', # accentCallout90 + 180 => 'borderCallout1', # borderCallout90 + 181 => 'accentBorderCallout1', # accentBorderCallout90 + 182 => 'leftRightUpArrow', + 183 => 'sun', + 184 => 'moon', + 185 => 'bracketPair', + 186 => 'bracePair', + 187 => 'star4', # seal4 + 188 => 'doubleWave', + 189 => 'actionButtonBlank', + 190 => 'actionButtonHome', + 191 => 'actionButtonHelp', + 192 => 'actionButtonInformation', + 193 => 'actionButtonForwardNext', + 194 => 'actionButtonBackPrevious', + 195 => 'actionButtonEnd', + 196 => 'actionButtonBeginning', + 197 => 'actionButtonReturn', + 198 => 'actionButtonDocument', + 199 => 'actionButtonSound', + 200 => 'actionButtonMovie', + 201 => 'hostControl', + 202 => 'textBox' +); +# An error occurred, we have to ignore this shape +sub error( $ ) +{ + my ( $msg ) = @_; + + $ignore_this_shape = 1; + print STDERR "ERROR (in $shape_name ): $msg\n"; +} + +# Setup the %variables map with predefined values +sub setup_variables() +{ + %variables = ( + 'l' => 0, + 't' => 0, + 'r' => 21600, + 'b' => 21600, + + 'w' => 21600, + 'h' => 21600, + 'ss' => 21600, + 'ls' => 21600, + + 'ssd2' => 10800, # 1/2 + 'ssd4' => 5400, # 1/4 + 'ssd6' => 3600, # 1/6 + 'ssd8' => 2700, # 1/8 + 'ssd16' => 1350, # 1/16 + 'ssd32' => 675, # 1/32 + + 'hc' => 10800, # horizontal center + 'vc' => 10800, # vertical center + + 'wd2' => 10800, # 1/2 + 'wd3' => 7200, # 1/3 + 'wd4' => 5400, # 1/4 + 'wd5' => 4320, # 1/5 + 'wd6' => 3600, # 1/6 + 'wd8' => 2700, # 1/8 + 'wd10' => 2160, # 1/10 + 'wd12' => 1800, # 1/12 + 'wd32' => 675, # 1/32 + + 'hd2' => 10800, # 1/2 + 'hd3' => 7200, # 1/3 + 'hd4' => 5400, # 1/4 + 'hd5' => 4320, # 1/5 + 'hd6' => 3600, # 1/6 + 'hd8' => 2700, # 1/8 + 'hd10' => 2160, # 1/10 + 'hd12' => 1800, # 1/12 + 'hd32' => 675, # 1/32 + + '25000' => 5400, + '12500' => 2700, + + 'cd4' => 90, # 1/4 of a circle + 'cd2' => 180, # 1/2 of a circle + '3cd4' => 270, # 3/4 of a circle + + 'cd8' => 45, # 1/8 of a circle + '3cd8' => 135, # 3/8 of a circle + '5cd8' => 225, # 5/8 of a circle + '7cd8' => 315, # 7/8 of a circle + + '-5400000' => -90, + '-10800000'=> -180, + '-16200000'=> -270, + '-21600000'=> -360, + '-21599999'=> -360, + + '5400000' => 90, + '10800000' => 180, + '16200000' => 270, + '21600000' => 360, + '21599999' => 360 +# +# '21600000' => 360, # angle conversions +# '27000000' => 450, +# '32400000' => 540, +# '37800000' => 630 + ); +} + +# Convert the (predefined) value to a number +sub value( $ ) +{ + my ( $val ) = @_; + + my $result = $variables{$val}; + return $result if ( defined( $result ) ); + + return $val if ( $val =~ /^[0-9-]+$/ ); + + error( "Unknown variable '$val'." ); + + show_call_stack(); + return $val; +} + +# Convert the DrawingML formula to a VML one +my %command_variables = ( + 'w' => 'width', + 'h' => 'height', + 'r' => 'width', + 'b' => 'height' +); + +# The same as value(), but some of the hardcoded values can have a name +sub command_value( $ ) +{ + my ( $value ) = @_; + + return "" if ( $value eq "" ); + + return $value if ( $value =~ /^@/ ); + + my $command_val = $command_variables{$value}; + if ( defined( $command_val ) ) { + return $command_val; + } + + return value( $value ); +} + +# Insert the new formula to the list of formulas +# Creates the name if it's empty... +sub insert_formula( $$ ) +{ + my ( $name, $fmla ) = @_; + + my $i = 0; + foreach my $f ( @formulas ) { + if ( $f eq $fmla ) { + if ( $name ne "" ) { + $variables{$name} = "@" . $i; + } + return "@" . $i; + } + ++$i; + } + + if ( $name eq "" ) { + $name = "@" . ( $#formulas + 1 ); + } + + $variables{$name} = "@" . ( $#formulas + 1 ); + push @formulas, $fmla; + + if ( $#formulas > 127 ) { + error( "Reached the maximum amount of formulas, have to ignore the shape '$shape_name'" ); + } + + return $variables{$name}; +} + +# The same as insert_formula(), but converts the params +sub insert_formula_params( $$$$$ ) +{ + my ( $name, $command, $p1, $p2, $p3 ) = @_; + + my $result = $command; + if ( $p1 ne "" ) { + $result .= " " . command_value( $p1 ); + if ( $p2 ne "" ) { + $result .= " " . command_value( $p2 ); + if ( $p3 ne "" ) { + $result .= " " . command_value( $p3 ); + } + } + } + + return insert_formula( $name, $result ); +} + +# Convert the formula from DrawingML to VML +sub convert_formula( $$ ) +{ + my ( $name, $fmla ) = @_; + + if ( $fmla =~ /^([^ ]+)/ ) { + my $command = $1; + + # parse the parameters + ( my $values = $fmla ) =~ s/^([^ ]+) *//; + my $p1 = ""; + my $p2 = ""; + my $p3 = ""; + if ( $values =~ /^([^ ]+)/ ) { + $p1 = $1; + $values =~ s/^([^ ]+) *//; + if ( $values =~ /^([^ ]+)/ ) { + $p2 = $1; + $values =~ s/^([^ ]+) *//; + if ( $values =~ /^([^ ]+)/ ) { + $p3 = $1; + } + } + } + + # now convert the formula + if ( $command eq "+-" ) { + if ( $p1 eq "100000" ) { + $p1 = value( 'w' ); + } + insert_formula_params( $name, "sum", $p1, $p2, $p3 ); + return; + } + elsif ( $command eq "*/" ) { + if ( ( $p2 =~ /^(w|h|ss|hd2|wd2|vc)$/ ) && defined( $variables{$p1} ) ) { + # switch it ;-) - presetTextWarpDefinitions.xml has it in other order + my $tmp = $p1; + $p1 = $p2; + $p2 = $tmp; + } + + if ( ( $p1 =~ /^(w|h|ss|hd2|wd2|vc)$/ ) && defined( $variables{$p2} ) ) { + my $val3 = $p3; + if ( $val3 =~ /^[0-9-]+$/ ) { + $val3 *= ( value( 'w' ) / value( $p1 ) ); + + # Oh yes, I'm too lazy to implement the full GCD here ;-) + if ( ( $val3 % 100000 ) == 0 ) { + $p1 = 1; + $p3 = sprintf( "%.0f", ( $val3 / 100000 ) ); + } + elsif ( $val3 < 100000 ) { + $p3 = 1; + while ( ( ( $p3 * 100000 ) % $val3 ) != 0 ) { + ++$p3 + } + $p1 = ( $p3 * 100000 ) / $val3; + } + else { + error( "Need to count the greatest common divisor." ); + } + } + } + elsif ( $p3 eq "100000" && $p2 =~ /^[0-9-]+$/ ) { + # prevent overflows in some shapes + $p2 = sprintf( "%.0f", ( $p2 / 10 ) ); + $p3 /= 10; + } + elsif ( $p3 eq "32768" && $p2 =~ /^[0-9-]+$/ ) { + # prevent overflows in some shapes + $p2 = sprintf( "%.0f", ( $p2 / 8 ) ); + $p3 /= 8; + } + elsif ( $p3 eq "50000" ) { + $p3 = 10800; + } + elsif ( $name =~ /^maxAdj/ ) { + my $val = value( $p1 ); + if ( $val =~ /^[0-9-]+$/ ) { + $p1 = sprintf( "%.0f", ( value( 'w' ) * $val / 100000 ) ); + } + } + + if ( ( value( $p1 ) eq value( $p3 ) ) || ( value( $p2 ) eq value( $p3 ) ) ) { + my $val = value( ( value( $p1 ) eq value( $p3 ) )? $p2: $p1 ); + if ( $val =~ /^@([0-9]+)$/ ) { + insert_formula( $name, $formulas[$1] ); + } + else { + insert_formula( $name, "val $val" ); + } + } + else { + insert_formula_params( $name, "prod", $p1, $p2, $p3 ); + } + return; + } + elsif ( $command eq "+/" ) { + # we have to split this into 2 formulas - 'sum' and 'prod' + my $constructed = insert_formula_params( "", "sum", $p1, $p2, "0" ); + insert_formula_params( $name, "prod", 1, $constructed, $p3); # references the 'sum' formula + return; + } + elsif ( $command eq "?:" ) { + insert_formula_params( $name, "if", $p1, $p2, $p3 ); + return; + } + elsif ( $command eq "sin" || $command eq "cos" ) { + if ( $p2 =~ /^[0-9-]+$/ && ( ( $p2 % 60000 ) == 0 ) ) { + $p2 /= 60000; + } + else { + $p2 = insert_formula_params( "", "prod", "1", $p2, "60000" ); + } + # we have to use 'sumangle' even for the case when $p2 is const + # and theoretically could be written as such; but Word does not + # accept it :-( + my $conv = insert_formula_params( "", "sumangle", "0", $p2, "0" ); + + $p2 = $conv; + + insert_formula_params( $name, $command, $p1, $p2, "" ); + return; + } + elsif ( $command eq "abs" ) { + insert_formula_params( $name, $command, $p1, "", "" ); + return; + } + elsif ( $command eq "max" || $command eq "min" ) { + insert_formula_params( $name, $command, $p1, $p2, "" ); + return; + } + elsif ( $command eq "at2" ) { + insert_formula_params( $name, "atan2", $p1, $p2, "" ); + return; + } + elsif ( $command eq "cat2" ) { + insert_formula_params( $name, "cosatan2", $p1, $p2, $p3 ); + return; + } + elsif ( $command eq "sat2" ) { + insert_formula_params( $name, "sinatan2", $p1, $p2, $p3 ); + return; + } + elsif ( $command eq "sqrt" ) { + insert_formula_params( $name, "sqrt", $p1, "", "" ); + return; + } + elsif ( $command eq "mod" ) { + insert_formula_params( $name, "mod", $p1, $p2, $p3 ); + return; + } + elsif ( $command eq "val" ) { + insert_formula_params( $name, "val", value( $p1 ), "", "" ); + return; + } + else { + error( "Unknown formula '$name', '$fmla'." ); + } + } + else { + error( "Cannot convert formula's command '$name', '$fmla'." ); + } +} + +# There's no exact equivalent of 'arcTo' in VML, we have to do some special casing... +my %convert_arcTo = ( + '0' => { + '90' => { + 'path' => 'qy', + 'op' => [ 'sum 0 __last_x__ __wR__', 'sum __hR__ __last_y__ 0' ], + }, + '-90' => { + 'path' => 'qy', + 'op' => [ 'sum 0 __last_x__ __wR__', 'sum 0 __last_y__ __hR__' ], + }, + }, + '90' => { + '90' => { + 'path' => 'qx', + 'op' => [ 'sum 0 __last_x__ __wR__', 'sum 0 __last_y__ __hR__' ], + }, + '-90' => { + 'path' => 'qx', + 'op' => [ 'sum __wR__ __last_x__ 0', 'sum 0 __last_y__ __hR__' ], + }, + }, + '180' => { + '90' => { + 'path' => 'qy', + 'op' => [ 'sum __wR__ __last_x__ 0', 'sum 0 __last_y__ __hR__' ], + }, + '-90' => { + 'path' => 'qy', + 'op' => [ 'sum __wR__ __last_x__ 0', 'sum __hR__ __last_y__ 0' ], + }, + }, + '270' => { + '90' => { + 'path' => 'qx', + 'op' => [ 'sum __wR__ __last_x__ 0', 'sum __hR__ __last_y__ 0' ], + }, + '-90' => { + 'path' => 'qx', + 'op' => [ 'sum 0 __last_x__ __wR__', 'sum __hR__ __last_y__ 0' ], + }, + }, +); + +# Elliptic quadrant +# FIXME optimize so that we compute the const values when possible +sub elliptic_quadrant( $$$$ ) +{ + my ( $wR, $hR, $stAng, $swAng ) = @_; + + if ( defined( $convert_arcTo{$stAng} ) && defined( $convert_arcTo{$stAng}{$swAng} ) ) { + my $conv_path = $convert_arcTo{$stAng}{$swAng}{'path'}; + my $conv_op_ref = $convert_arcTo{$stAng}{$swAng}{'op'}; + + $path .= "$conv_path"; + + my $pos_x = $last_pos_x; + my $pos_y = $last_pos_y; + for ( my $i = 0; $i <= $#{$conv_op_ref}; ++$i ) { + my $op = $conv_op_ref->[$i]; + + $op =~ s/__last_x__/$last_pos_x/g; + $op =~ s/__last_y__/$last_pos_y/g; + $op =~ s/__wR__/$wR/g; + $op =~ s/__hR__/$hR/g; + + my $fmla = insert_formula( "", $op ); + + $path .= $fmla; + + # so far it's sufficient just to rotate the positions + # FIXME if not ;-) + $pos_x = $pos_y; + $pos_y = $fmla; + } + $last_pos_x = $pos_x; + $last_pos_y = $pos_y; + } + else { + error( "Unhandled elliptic_quadrant(), input is ($wR, $hR, $stAng, $swAng)." ); + } +} + +# Convert the quadratic bezier to cubic (exact) +# No idea why, but the 'qb' did not work for me :-( +sub quadratic_to_cubic_bezier( $ ) +{ + my ( $axis ) = @_; + + my $a0 = $quadratic_bezier[0]->{$axis}; + my $a1 = $quadratic_bezier[1]->{$axis}; + my $a2 = $quadratic_bezier[2]->{$axis}; + + my $b0 = $a0; + + # $b1 = $a0 + 2/3 * ( $a1 - $a0 ), but in VML + # FIXME optimize for constants - compute directly + my $b1_1 = insert_formula_params( "", "sum", "0", $a1, $a0 ); + my $b1_2 = insert_formula_params( "", "prod", "2", $b1_1, "3" ); + my $b1 = insert_formula_params( "", "sum", $a0, $b1_2, "0" ); + + # $b2 = $b1 + 1/3 * ( $a2 - $a0 ); + # FIXME optimize for constants - compute directly + my $b2_1 = insert_formula_params( "", "sum", "0", $a2, $a0 ); + my $b2_2 = insert_formula_params( "", "prod", "1", $b2_1, "3" ); + my $b2 = insert_formula_params( "", "sum", $b1, $b2_2, "0" ); + + my $b3 = $a2; + + return ( $b0, $b1, $b2, $b3 ); +} + +# Extend $path by one more point +sub add_point_to_path( $$ ) +{ + my ( $x, $y ) = @_; + + if ( $path =~ /[0-9]$/ && $x =~ /^[0-9-]/ ) { + $path .= ","; + } + $path .= $x; + + if ( $path =~ /[0-9]$/ && $y =~ /^[0-9-]/ ) { + $path .= ","; + } + $path .= $y; +} + +# Start of an element +sub start_element( $% ) +{ + my ( $element, %attr ) = @_; + + push @levels, $element; + + #print "element: $element\n"; + + if ( @levels > 1 && ( $levels[-2] eq "presetShapeDefinitons" || + $levels[-2] eq "presetTextWarpDefinitions" ) ) { + $shape_name = $element; + + $state = ""; + $ignore_this_shape = 0; + $path = ""; + $adjust = ""; + $max_adj_no = 0; + @formulas = (); + $handles = ""; + $textboxrect = ""; + $last_pos_x = ""; + $last_pos_y = ""; + $no_stroke = 0; + $no_fill = 0; + @quadratic_bezier = (); + + setup_variables(); + + if ( $shape_name eq "sun" ) { + # hack for this shape + $variables{'100000'} = "21600"; + $variables{'50000'} = "10800"; + $variables{'25000'} = "5400"; + $variables{'12500'} = "2700"; + $variables{'3662'} = "791"; + } + + my $found = 0; + foreach my $name ( values( %shapes_ids ) ) { + if ( $name eq $shape_name ) { + $found = 1; + last; + } + } + if ( !$found ) { + error( "Unknown shape '$shape_name'." ); + } + } + elsif ( $element eq "pathLst" ) { + $state = "path"; + } + elsif ( $element eq "avLst" ) { + $state = "adjust"; + } + elsif ( $element eq "gdLst" ) { + $state = "formulas"; + } + elsif ( $element eq "ahLst" ) { + $state = "handles"; + } + elsif ( $element eq "rect" ) { + $textboxrect = value( $attr{'l'} ) . "," . value( $attr{'t'} ) . "," . + value( $attr{'r'} ) . "," . value( $attr{'b'} ); + } + elsif ( $state eq "path" ) { + if ( $element eq "path" ) { + $no_stroke = ( defined( $attr{'stroke'} ) && $attr{'stroke'} eq 'false' ); + $no_fill = ( defined( $attr{'fill'} ) && $attr{'fill'} eq 'none' ); + $path_w = $attr{'w'}; + $path_h = $attr{'h'}; + } + elsif ( $element eq "moveTo" ) { + $path .= "m"; + } + elsif ( $element eq "lnTo" ) { + $path .= "l"; + } + elsif ( $element eq "cubicBezTo" ) { + $path .= "c"; + } + elsif ( $element eq "quadBezTo" ) { + my %points = ( 'x' => $last_pos_x, 'y' => $last_pos_y ); + @quadratic_bezier = ( \%points ); + } + elsif ( $element eq "close" ) { + $path .= "x"; + } + elsif ( $element eq "pt" ) { + # remember the last position for the arcTo + $last_pos_x = value( $attr{'x'} ); + $last_pos_y = value( $attr{'y'} ); + + $last_pos_x *= ( value( 'w' ) / $path_w ) if ( defined( $path_w ) ); + $last_pos_y *= ( value( 'h' ) / $path_h ) if ( defined( $path_h ) ); + + if ( $#quadratic_bezier >= 0 ) { + my %points = ( 'x' => $last_pos_x, 'y' => $last_pos_y ); + push( @quadratic_bezier, \%points ); + } + else { + add_point_to_path( $last_pos_x, $last_pos_y ); + } + } + elsif ( ( $element eq "arcTo" ) && ( $last_pos_x ne "" ) && ( $last_pos_y ne "" ) ) { + # there's no exact equivalent of arcTo in VML, so we have to + # compute here a bit... + my $stAng = value( $attr{'stAng'} ); + my $swAng = value( $attr{'swAng'} ); + my $wR = value( $attr{'wR'} ); + my $hR = value( $attr{'hR'} ); + + $wR *= ( value( 'w' ) / $path_w ) if ( defined( $path_w ) ); + $hR *= ( value( 'h' ) / $path_h ) if ( defined( $path_h ) ); + + if ( ( $stAng =~ /^[0-9-]+$/ ) && ( $swAng =~ /^[0-9-]+$/ ) ) { + if ( ( ( $stAng % 90 ) == 0 ) && ( ( $swAng % 90 ) == 0 ) && ( $swAng != 0 ) ) { + my $end = $stAng + $swAng; + my $step = ( $swAng > 0 )? 90: -90; + + for ( my $cur = $stAng; $cur != $end; $cur += $step ) { + elliptic_quadrant( $wR, $hR, ( $cur % 360 ), $step ); + } + } + else { + error( "Unsupported numeric 'arcTo' ($attr{'wR'}, $attr{'hR'}, $stAng, $swAng)." ); + } + } + else { + error( "Unsupported 'arcTo' conversion ($attr{'wR'}, $attr{'hR'}, $stAng, $swAng)." ); + } + } + else { + error( "Unhandled path element '$element'." ); + } + } + elsif ( $state eq "adjust" ) { + if ( $element eq "gd" ) { + my $adj_no = $attr{'name'}; + + # Save this adj number for this type for later use. + push(@{$adj_names{$shape_name}}, $adj_no); + + my $is_const = 0; + + $adj_no =~ s/^adj//; + if ( $adj_no eq "" ) { + $max_adj_no = 0; + } + elsif ( !( $adj_no =~ /^[0-9]*$/ ) ) { + ++$max_adj_no; + $is_const = 1; + } + elsif ( $adj_no != $max_adj_no + 1 ) { + error( "Wrong order of adj values." ); + ++$max_adj_no; + } + else { + $max_adj_no = $adj_no; + } + + if ( $attr{'fmla'} =~ /^val ([0-9-]*)$/ ) { + my $val = sprintf( "%.0f", ( 21600 * $1 ) / 100000 ); + if ( $is_const ) { + $variables{$adj_no} = $val; + } + elsif ( $adjust eq "" ) { + $adjust = $val; + } + else { + $adjust = "$val,$adjust"; + } + } + else { + error( "Wrong fmla '$attr{'fmla'}'." ); + } + } + else { + error( "Unhandled adjust element '$element'." ); + } + } + elsif ( $state eq "formulas" ) { + if ( $element eq "gd" ) { + if ( $attr{'fmla'} =~ /^\*\/ (h|w|ss) adj([0-9]+) 100000$/ ) { + insert_formula( $attr{'name'}, "val #" . ( $max_adj_no - $2 ) ); + } + elsif ( $attr{'fmla'} =~ /^pin [^ ]+ ([^ ]+) / ) { + print STDERR "TODO Map 'pin' to VML as xrange for handles.\n"; + my $pin_val = $1; + if ( $pin_val eq "adj" ) { + insert_formula( $attr{'name'}, "val #0" ); + } + elsif ( $pin_val =~ /^adj([0-9]+)/ ) { + insert_formula( $attr{'name'}, "val #" . ( $max_adj_no - $1 ) ); + } + else { + insert_formula( $attr{'name'}, "val " . value( $pin_val ) ); + } + } + elsif ( $attr{'fmla'} =~ /adj/ ) { + error( "Non-standard usage of adj in '$attr{'fmla'}'." ); + } + else { + convert_formula( $attr{'name'}, $attr{'fmla'} ); + } + } + } + elsif ( $state eq "handles" ) { + if ( $element eq "pos" ) { + $handles .= "<v:h position=\"" . value( $attr{'x'} ) . "," . value( $attr{'y'} ) . "\"/>\n"; + } + } +} + +# End of an element +sub end_element( $ ) +{ + my ( $element ) = @_; + + pop @levels; + + if ( $element eq $shape_name ) { + if ( !$ignore_this_shape ) { + # we have all the info, generate the shape now + $state = ""; + + # shape path + my $out = "<v:shapetype id=\"_x0000_t__ID__\" coordsize=\"21600,21600\" o:spt=\"__ID__\" "; + if ( $adjust ne "" ) { + $out .= "adj=\"$adjust\" "; + } + + # optimize it [yes, we need this twice ;-)] + $path =~ s/([^0-9-@])0([^0-9-@])/$1$2/g; + $path =~ s/([^0-9-@])0([^0-9-@])/$1$2/g; + + $out .= "path=\"$path\">\n"; + + # stroke + $out .= "<v:stroke joinstyle=\"miter\"/>\n"; + + # formulas + if ( $#formulas >= 0 ) + { + $out .= "<v:formulas>\n"; + foreach my $fmla ( @formulas ) { + $out .= "<v:f eqn=\"$fmla\"/>\n" + } + $out .= "</v:formulas>\n"; + } + + # path + if ( $textboxrect ne "" ) { # TODO connectlocs, connectangles + $out .= "<v:path gradientshapeok=\"t\" o:connecttype=\"rect\" textboxrect=\"$textboxrect\"/>\n"; + } + + # handles + if ( $handles ne "" ) { + $out .= "<v:handles>\n$handles</v:handles>\n"; + } + + $out .="</v:shapetype>"; + + # hooray! :-) + $result_shapes{$shape_name} = $out; + } + else { + print STDERR "Shape '$shape_name' ignored; see the above error(s) for the reason.\n"; + } + $shape_name = ""; + } + elsif ( $state eq "path" ) { + if ( $element eq "path" ) { + $path .= "ns" if ( $no_stroke ); + $path .= "nf" if ( $no_fill ); + $path .= "e"; + } + elsif ( $element eq "quadBezTo" ) { + # we have to convert the quadratic bezier to cubic + if ( $#quadratic_bezier == 2 ) { + my @points_x = quadratic_to_cubic_bezier( 'x' ); + my @points_y = quadratic_to_cubic_bezier( 'y' ); + + $path .= "c"; + # ignore the starting point + for ( my $i = 1; $i < 4; ++$i ) { + add_point_to_path( $points_x[$i], $points_y[$i] ); + } + } + else { + error( "Wrong number of points of the quadratic bezier." ); + } + @quadratic_bezier = (); + } + } + elsif ( $element eq "avLst" ) { + $state = ""; + } + elsif ( $element eq "gdLst" ) { + $state = ""; + } + elsif ( $element eq "ahLst" ) { + $state = ""; + } +} + +# Text inside an element +sub characters( $ ) +{ + #my ( $text ) = @_; +} + +#################### A trivial XML parser #################### + +# Parse the attributes +sub parse_start_element( $ ) +{ + # split the string containing both the elements and attributes + my ( $element_tmp ) = @_; + + $element_tmp =~ s/\s*$//; + $element_tmp =~ s/^\s*//; + + ( my $element = $element_tmp ) =~ s/\s.*$//; + if ( $element_tmp =~ /\s/ ) { + $element_tmp =~ s/^[^\s]*\s//; + } + else { + $element_tmp = ""; + } + + # we have the element, now the attributes + my %attr; + my $is_key = 1; + my $key = ""; + foreach my $tmp ( split( /"/, $element_tmp ) ) { + if ( $is_key ) { + $key = $tmp; + $key =~ s/^\s*//; + $key =~ s/\s*=\s*$//; + } + else { + $attr{$key} = $tmp; + } + $is_key = !$is_key; + } + + if ( $element ne "" ) { + start_element( $element, %attr ); + } +} + +# Parse the file +sub parse( $ ) +{ + my ( $file ) = @_; + + my $in_comment = 0; + my $line = ""; + while (<$file>) { + # ignore comments + s/<\?[^>]*\?>//g; + s/<!--[^>]*-->//g; + if ( /<!--/ ) { + $in_comment = 1; + s/<!--.*//; + } + elsif ( /-->/ && $in_comment ) { + $in_comment = 0; + s/.*-->//; + } + elsif ( $in_comment ) { + next; + } + # ignore empty lines + chomp; + s/^\s*//; + s/\s*$//; + next if ( $_ eq "" ); + + # take care of lines where element continues + if ( $line ne "" ) { + $line .= " " . $_; + } + else { + $line = $_; + } + next if ( !/>$/ ); + + # the actual parsing + my @starts = split( /</, $line ); + $line = ""; + foreach my $start ( @starts ) { + next if ( $start eq "" ); + + my @ends = split( />/, $start ); + my $element = $ends[0]; + my $data = $ends[1]; + + # start or end element + if ( $element =~ /^\/(.*)/ ) { + end_element( $1 ); + } + elsif ( $element =~ /^(.*)\/$/ ) { + parse_start_element( $1 ); + ( my $end = $1 ) =~ s/\s.*$//; + end_element( $end ); + } + else { + parse_start_element( $element ); + } + + # the data + characters( $data ) if ( defined( $data ) && $data ne "" ); + } + } +} + +# Do the real work +my $file; +open( $file, "<$src_shapes" ) || die "Cannot open $src_shapes: $!"; +parse( $file ); +close( $file ); + +open( $file, "<$src_text" ) || die "Cannot open $src_text: $!"; +parse( $file ); +close( $file ); + +if ( !defined( $result_shapes{'textBox'} ) ) { + # tdf#114842 shapetype id of the textbox, must be the same as defined + $result_shapes{'textBox'} = + "<v:shapetype id=\"_x0000_t__ID__\" coordsize=\"21600,21600\" " . + "o:spt=\"__ID__\" path=\"m,l,21600l21600,21600l21600,xe\">\n" . + "<v:stroke joinstyle=\"miter\"/>\n" . + "<v:path gradientshapeok=\"t\" o:connecttype=\"rect\"/>\n" . + "</v:shapetype>"; +} + +# Generate the data +if ($drawingml_adj_names_data eq 1) { + foreach my $adj_name (sort(keys %adj_names)) + { + foreach my $adj (@{$adj_names{$adj_name}}) + { + print "$adj_name\t$adj\n"; + } + } + exit 0; +} elsif ($vml_shape_types_data eq 1) { + for ( my $i = 0; $i < 203; ++$i ) { + if ( $i < 4 ) { + print "/* $i - $shapes_ids{$i} - handled separately */\nNULL\n"; + } + else { + print "/* $i - $shapes_ids{$i} */\n"; + my $out = $result_shapes{$shapes_ids{$i}}; + if ( defined( $out ) ) { + # set the id + $out =~ s/__ID__/$i/g; + + # output as string + $out =~ s/\n//g; + + print "$out\n"; + } + else { + print "NULL\n"; + } + } + } + exit 0; +} + +# should not happen +exit 1; + +# vim:set ft=perl shiftwidth=4 softtabstop=4 expandtab: # diff --git a/oox/source/export/presetTextWarpDefinitions.xml b/oox/source/export/presetTextWarpDefinitions.xml new file mode 100644 index 0000000000..c701c8f82b --- /dev/null +++ b/oox/source/export/presetTextWarpDefinitions.xml @@ -0,0 +1,1885 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<presetTextWarpDefinitions> + <textArchDown> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 0"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adval" fmla="pin 0 adj 21599999"/> + <gd name="v1" fmla="+- 10800000 0 adval"/> + <gd name="v2" fmla="+- 32400000 0 adval"/> + <gd name="nv1" fmla="+- 0 0 v1"/> + <gd name="stAng" fmla="?: nv1 v2 v1"/> + <gd name="w1" fmla="+- 5400000 0 adval"/> + <gd name="w2" fmla="+- 16200000 0 adval"/> + <gd name="d1" fmla="+- adval 0 stAng"/> + <gd name="d2" fmla="+- d1 0 21600000"/> + <gd name="v3" fmla="+- 0 0 10800000"/> + <gd name="c2" fmla="?: w2 d1 d2"/> + <gd name="c1" fmla="?: v1 d2 c2"/> + <gd name="c0" fmla="?: w1 d1 c1"/> + <gd name="swAng" fmla="?: stAng c0 v3"/> + <gd name="wt1" fmla="sin wd2 adj"/> + <gd name="ht1" fmla="cos hd2 adj"/> + <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/> + <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/> + <gd name="x1" fmla="+- hc dx1 0"/> + <gd name="y1" fmla="+- vc dy1 0"/> + <gd name="wt2" fmla="sin wd2 stAng"/> + <gd name="ht2" fmla="cos hd2 stAng"/> + <gd name="dx2" fmla="cat2 wd2 ht2 wt2"/> + <gd name="dy2" fmla="sat2 hd2 ht2 wt2"/> + <gd name="x2" fmla="+- hc dx2 0"/> + <gd name="y2" fmla="+- vc dy2 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahPolar gdRefAng="adj" minAng="0" maxAng="21599999"> + <pos x="x1" y="y1"/> + </ahPolar> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="x2" y="y2"/> + </moveTo> + <arcTo wR="wd2" hR="hd2" stAng="stAng" swAng="swAng"/> + </path> + </pathLst> + </textArchDown> + <textArchDownPour> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj1" fmla="val 0"/> + <gd name="adj2" fmla="val 25000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adval" fmla="pin 0 adj1 21599999"/> + <gd name="v1" fmla="+- 10800000 0 adval"/> + <gd name="v2" fmla="+- 32400000 0 adval"/> + <gd name="nv1" fmla="+- 0 0 v1"/> + <gd name="stAng" fmla="?: nv1 v2 v1"/> + <gd name="w1" fmla="+- 5400000 0 adval"/> + <gd name="w2" fmla="+- 16200000 0 adval"/> + <gd name="d1" fmla="+- adval 0 stAng"/> + <gd name="d2" fmla="+- d1 0 21600000"/> + <gd name="v3" fmla="+- 0 0 10800000"/> + <gd name="c2" fmla="?: w2 d1 d2"/> + <gd name="c1" fmla="?: v1 d2 c2"/> + <gd name="c0" fmla="?: w1 d1 c1"/> + <gd name="swAng" fmla="?: stAng c0 v3"/> + <gd name="wt1" fmla="sin wd2 stAng"/> + <gd name="ht1" fmla="cos hd2 stAng"/> + <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/> + <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/> + <gd name="x1" fmla="+- hc dx1 0"/> + <gd name="y1" fmla="+- vc dy1 0"/> + <gd name="adval2" fmla="pin 0 adj2 99000"/> + <gd name="ratio" fmla="*/ adval2 1 100000"/> + <gd name="iwd2" fmla="*/ wd2 ratio 1"/> + <gd name="ihd2" fmla="*/ hd2 ratio 1"/> + <gd name="wt2" fmla="sin iwd2 adval"/> + <gd name="ht2" fmla="cos ihd2 adval"/> + <gd name="dx2" fmla="cat2 iwd2 ht2 wt2"/> + <gd name="dy2" fmla="sat2 ihd2 ht2 wt2"/> + <gd name="x2" fmla="+- hc dx2 0"/> + <gd name="y2" fmla="+- vc dy2 0"/> + <gd name="wt3" fmla="sin iwd2 stAng"/> + <gd name="ht3" fmla="cos ihd2 stAng"/> + <gd name="dx3" fmla="cat2 iwd2 ht3 wt3"/> + <gd name="dy3" fmla="sat2 ihd2 ht3 wt3"/> + <gd name="x3" fmla="+- hc dx3 0"/> + <gd name="y3" fmla="+- vc dy3 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahPolar gdRefR="adj2" minR="0" maxR="100000" gdRefAng="adj1" minAng="0" maxAng="21599999"> + <pos x="x2" y="y2"/> + </ahPolar> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="x3" y="y3"/> + </moveTo> + <arcTo wR="iwd2" hR="ihd2" stAng="stAng" swAng="swAng"/> + </path> + <path> + <moveTo> + <pt x="x1" y="y1"/> + </moveTo> + <arcTo wR="wd2" hR="hd2" stAng="stAng" swAng="swAng"/> + </path> + </pathLst> + </textArchDownPour> + <textArchUp> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val cd2"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adval" fmla="pin 0 adj 21599999"/> + <gd name="v1" fmla="+- 10800000 0 adval"/> + <gd name="v2" fmla="+- 32400000 0 adval"/> + <gd name="end" fmla="?: v1 v1 v2"/> + <gd name="w1" fmla="+- 5400000 0 adval"/> + <gd name="w2" fmla="+- 16200000 0 adval"/> + <gd name="d1" fmla="+- end 0 adval"/> + <gd name="d2" fmla="+- 21600000 d1 0"/> + <gd name="c2" fmla="?: w2 d1 d2"/> + <gd name="c1" fmla="?: v1 d2 c2"/> + <gd name="swAng" fmla="?: w1 d1 c1"/> + <gd name="wt1" fmla="sin wd2 adj"/> + <gd name="ht1" fmla="cos hd2 adj"/> + <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/> + <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/> + <gd name="x1" fmla="+- hc dx1 0"/> + <gd name="y1" fmla="+- vc dy1 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahPolar gdRefAng="adj" minAng="0" maxAng="21599999"> + <pos x="x1" y="y1"/> + </ahPolar> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="x1" y="y1"/> + </moveTo> + <arcTo wR="wd2" hR="hd2" stAng="adval" swAng="swAng"/> + </path> + </pathLst> + </textArchUp> + <textArchUpPour> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj1" fmla="val cd2"/> + <gd name="adj2" fmla="val 50000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adval" fmla="pin 0 adj1 21599999"/> + <gd name="v1" fmla="+- 10800000 0 adval"/> + <gd name="v2" fmla="+- 32400000 0 adval"/> + <gd name="end" fmla="?: v1 v1 v2"/> + <gd name="w1" fmla="+- 5400000 0 adval"/> + <gd name="w2" fmla="+- 16200000 0 adval"/> + <gd name="d1" fmla="+- end 0 adval"/> + <gd name="d2" fmla="+- 21600000 d1 0"/> + <gd name="c2" fmla="?: w2 d1 d2"/> + <gd name="c1" fmla="?: v1 d2 c2"/> + <gd name="swAng" fmla="?: w1 d1 c1"/> + <gd name="wt1" fmla="sin wd2 adval"/> + <gd name="ht1" fmla="cos hd2 adval"/> + <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/> + <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/> + <gd name="x1" fmla="+- hc dx1 0"/> + <gd name="y1" fmla="+- vc dy1 0"/> + <gd name="adval2" fmla="pin 0 adj2 99000"/> + <gd name="ratio" fmla="*/ adval2 1 100000"/> + <gd name="iwd2" fmla="*/ wd2 ratio 1"/> + <gd name="ihd2" fmla="*/ hd2 ratio 1"/> + <gd name="wt2" fmla="sin iwd2 adval"/> + <gd name="ht2" fmla="cos ihd2 adval"/> + <gd name="dx2" fmla="cat2 iwd2 ht2 wt2"/> + <gd name="dy2" fmla="sat2 ihd2 ht2 wt2"/> + <gd name="x2" fmla="+- hc dx2 0"/> + <gd name="y2" fmla="+- vc dy2 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahPolar gdRefR="adj2" minR="0" maxR="100000" gdRefAng="adj1" minAng="0" maxAng="21599999"> + <pos x="x2" y="y2"/> + </ahPolar> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="x1" y="y1"/> + </moveTo> + <arcTo wR="wd2" hR="hd2" stAng="adval" swAng="swAng"/> + </path> + <path> + <moveTo> + <pt x="x2" y="y2"/> + </moveTo> + <arcTo wR="iwd2" hR="ihd2" stAng="adval" swAng="swAng"/> + </path> + </pathLst> + </textArchUpPour> + <textButton> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 10800000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adval" fmla="pin 0 adj 21599999"/> + <gd name="bot" fmla="+- 5400000 0 adval"/> + <gd name="lef" fmla="+- 10800000 0 adval"/> + <gd name="top" fmla="+- 16200000 0 adval"/> + <gd name="rig" fmla="+- 21600000 0 adval"/> + <gd name="c3" fmla="?: top adval 0"/> + <gd name="c2" fmla="?: lef 10800000 c3"/> + <gd name="c1" fmla="?: bot rig c2"/> + <gd name="stAng" fmla="?: adval c1 0"/> + <gd name="w1" fmla="+- 21600000 0 stAng"/> + <gd name="stAngB" fmla="?: stAng w1 0"/> + <gd name="td1" fmla="*/ bot 2 1"/> + <gd name="td2" fmla="*/ top 2 1"/> + <gd name="ntd2" fmla="+- 0 0 td2"/> + <gd name="w2" fmla="+- 0 0 10800000"/> + <gd name="c6" fmla="?: top ntd2 w2"/> + <gd name="c5" fmla="?: lef 10800000 c6"/> + <gd name="c4" fmla="?: bot td1 c5"/> + <gd name="v1" fmla="?: adval c4 10800000"/> + <gd name="swAngT" fmla="+- 0 0 v1"/> + <gd name="stT" fmla="?: lef stAngB stAng"/> + <gd name="stB" fmla="?: lef stAng stAngB"/> + <gd name="swT" fmla="?: lef v1 swAngT"/> + <gd name="swB" fmla="?: lef swAngT v1"/> + <gd name="wt1" fmla="sin wd2 stT"/> + <gd name="ht1" fmla="cos hd2 stT"/> + <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/> + <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/> + <gd name="x1" fmla="+- hc dx1 0"/> + <gd name="y1" fmla="+- vc dy1 0"/> + <gd name="wt2" fmla="sin wd2 stB"/> + <gd name="ht2" fmla="cos hd2 stB"/> + <gd name="dx2" fmla="cat2 wd2 ht2 wt2"/> + <gd name="dy2" fmla="sat2 hd2 ht2 wt2"/> + <gd name="x2" fmla="+- hc dx2 0"/> + <gd name="y2" fmla="+- vc dy2 0"/> + <gd name="wt3" fmla="sin wd2 adj"/> + <gd name="ht3" fmla="cos hd2 adj"/> + <gd name="dx3" fmla="cat2 wd2 ht3 wt3"/> + <gd name="dy3" fmla="sat2 hd2 ht3 wt3"/> + <gd name="x3" fmla="+- hc dx3 0"/> + <gd name="y3" fmla="+- vc dy3 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahPolar gdRefAng="adj" minAng="0" maxAng="21599999"> + <pos x="x3" y="y3"/> + </ahPolar> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="x1" y="y1"/> + </moveTo> + <arcTo wR="wd2" hR="hd2" stAng="stT" swAng="swT"/> + </path> + <path> + <moveTo> + <pt x="l" y="vc"/> + </moveTo> + <lnTo> + <pt x="r" y="vc"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="x2" y="y2"/> + </moveTo> + <arcTo wR="wd2" hR="hd2" stAng="stB" swAng="swB"/> + </path> + </pathLst> + </textButton> + <textButtonPour> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj1" fmla="val cd2"/> + <gd name="adj2" fmla="val 50000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adval" fmla="pin 0 adj1 21599999"/> + <gd name="bot" fmla="+- 5400000 0 adval"/> + <gd name="lef" fmla="+- 10800000 0 adval"/> + <gd name="top" fmla="+- 16200000 0 adval"/> + <gd name="rig" fmla="+- 21600000 0 adval"/> + <gd name="c3" fmla="?: top adval 0"/> + <gd name="c2" fmla="?: lef 10800000 c3"/> + <gd name="c1" fmla="?: bot rig c2"/> + <gd name="stAng" fmla="?: adval c1 0"/> + <gd name="w1" fmla="+- 21600000 0 stAng"/> + <gd name="stAngB" fmla="?: stAng w1 0"/> + <gd name="td1" fmla="*/ bot 2 1"/> + <gd name="td2" fmla="*/ top 2 1"/> + <gd name="ntd2" fmla="+- 0 0 td2"/> + <gd name="w2" fmla="+- 0 0 10800000"/> + <gd name="c6" fmla="?: top ntd2 w2"/> + <gd name="c5" fmla="?: lef 10800000 c6"/> + <gd name="c4" fmla="?: bot td1 c5"/> + <gd name="v1" fmla="?: adval c4 10800000"/> + <gd name="swAngT" fmla="+- 0 0 v1"/> + <gd name="stT" fmla="?: lef stAngB stAng"/> + <gd name="stB" fmla="?: lef stAng stAngB"/> + <gd name="swT" fmla="?: lef v1 swAngT"/> + <gd name="swB" fmla="?: lef swAngT v1"/> + <gd name="wt1" fmla="sin wd2 stT"/> + <gd name="ht1" fmla="cos hd2 stT"/> + <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/> + <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/> + <gd name="x1" fmla="+- hc dx1 0"/> + <gd name="y1" fmla="+- vc dy1 0"/> + <gd name="wt6" fmla="sin wd2 stB"/> + <gd name="ht6" fmla="cos hd2 stB"/> + <gd name="dx6" fmla="cat2 wd2 ht6 wt6"/> + <gd name="dy6" fmla="sat2 hd2 ht6 wt6"/> + <gd name="x6" fmla="+- hc dx6 0"/> + <gd name="y6" fmla="+- vc dy6 0"/> + <gd name="adval2" fmla="pin 40000 adj2 99000"/> + <gd name="ratio" fmla="*/ adval2 1 100000"/> + <gd name="iwd2" fmla="*/ wd2 ratio 1"/> + <gd name="ihd2" fmla="*/ hd2 ratio 1"/> + <gd name="wt2" fmla="sin iwd2 stT"/> + <gd name="ht2" fmla="cos ihd2 stT"/> + <gd name="dx2" fmla="cat2 iwd2 ht2 wt2"/> + <gd name="dy2" fmla="sat2 ihd2 ht2 wt2"/> + <gd name="x2" fmla="+- hc dx2 0"/> + <gd name="y2" fmla="+- vc dy2 0"/> + <gd name="wt5" fmla="sin iwd2 stB"/> + <gd name="ht5" fmla="cos ihd2 stB"/> + <gd name="dx5" fmla="cat2 iwd2 ht5 wt5"/> + <gd name="dy5" fmla="sat2 ihd2 ht5 wt5"/> + <gd name="x5" fmla="+- hc dx5 0"/> + <gd name="y5" fmla="+- vc dy5 0"/> + <gd name="d1" fmla="+- hd2 0 ihd2"/> + <gd name="d12" fmla="*/ d1 1 2"/> + <gd name="yu" fmla="+- vc 0 d12"/> + <gd name="yd" fmla="+- vc d12 0"/> + <gd name="v1" fmla="*/ d12 d12 1"/> + <gd name="v2" fmla="*/ ihd2 ihd2 1"/> + <gd name="v3" fmla="*/ v1 1 v2"/> + <gd name="v4" fmla="+- 1 0 v3"/> + <gd name="v5" fmla="*/ iwd2 iwd2 1"/> + <gd name="v6" fmla="*/ v4 v5 1"/> + <gd name="v7" fmla="sqrt v6"/> + <gd name="xl" fmla="+- hc 0 v7"/> + <gd name="xr" fmla="+- hc v7 0"/> + <gd name="wtadj" fmla="sin iwd2 adj1"/> + <gd name="htadj" fmla="cos ihd2 adj1"/> + <gd name="dxadj" fmla="cat2 iwd2 htadj wtadj"/> + <gd name="dyadj" fmla="sat2 ihd2 htadj wtadj"/> + <gd name="xadj" fmla="+- hc dxadj 0"/> + <gd name="yadj" fmla="+- vc dyadj 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahPolar gdRefR="adj2" minR="0" maxR="100000" gdRefAng="adj1" minAng="0" maxAng="21599999"> + <pos x="xadj" y="yadj"/> + </ahPolar> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="x1" y="y1"/> + </moveTo> + <arcTo wR="wd2" hR="hd2" stAng="stT" swAng="swT"/> + </path> + <path> + <moveTo> + <pt x="x2" y="y2"/> + </moveTo> + <arcTo wR="iwd2" hR="ihd2" stAng="stT" swAng="swT"/> + </path> + <path> + <moveTo> + <pt x="xl" y="yu"/> + </moveTo> + <lnTo> + <pt x="xr" y="yu"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="xl" y="yd"/> + </moveTo> + <lnTo> + <pt x="xr" y="yd"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="x5" y="y5"/> + </moveTo> + <arcTo wR="iwd2" hR="ihd2" stAng="stB" swAng="swB"/> + </path> + <path> + <moveTo> + <pt x="x6" y="y6"/> + </moveTo> + <arcTo wR="wd2" hR="hd2" stAng="stB" swAng="swB"/> + </path> + </pathLst> + </textButtonPour> + <textCanDown> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 14286"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 0 adj 33333"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="y0" fmla="+- t dy 0"/> + <gd name="y1" fmla="+- b 0 dy"/> + <gd name="ncd2" fmla="*/ cd2 -1 1"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="0" maxY="33333"> + <pos x="hc" y="y0"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="t"/> + </moveTo> + <arcTo wR="wd2" hR="dy" stAng="cd2" swAng="ncd2"/> + </path> + <path> + <moveTo> + <pt x="l" y="y1"/> + </moveTo> + <arcTo wR="wd2" hR="dy" stAng="cd2" swAng="ncd2"/> + </path> + </pathLst> + </textCanDown> + <textCanUp> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 85714"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 66667 adj 100000"/> + <gd name="dy1" fmla="*/ a h 100000"/> + <gd name="dy" fmla="+- h 0 dy1"/> + <gd name="y0" fmla="+- t dy1 0"/> + <gd name="y1" fmla="+- t dy 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="66667" maxY="100000"> + <pos x="hc" y="y0"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="y1"/> + </moveTo> + <arcTo wR="wd2" hR="dy" stAng="cd2" swAng="cd2"/> + </path> + <path> + <moveTo> + <pt x="l" y="b"/> + </moveTo> + <arcTo wR="wd2" hR="dy" stAng="cd2" swAng="cd2"/> + </path> + </pathLst> + </textCanUp> + <textCascadeDown> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 44444"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 28570 adj 100000"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="y1" fmla="+- t dy 0"/> + <gd name="dy2" fmla="+- h 0 dy"/> + <gd name="dy3" fmla="*/ dy2 1 4"/> + <gd name="y2" fmla="+- t dy3 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="28570" maxY="100000"> + <pos x="l" y="y1"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="t"/> + </moveTo> + <lnTo> + <pt x="r" y="y2"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="y1"/> + </moveTo> + <lnTo> + <pt x="r" y="b"/> + </lnTo> + </path> + </pathLst> + </textCascadeDown> + <textCascadeUp> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 44444"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 28570 adj 100000"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="y1" fmla="+- t dy 0"/> + <gd name="dy2" fmla="+- h 0 dy"/> + <gd name="dy3" fmla="*/ dy2 1 4"/> + <gd name="y2" fmla="+- t dy3 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="28570" maxY="100000"> + <pos x="r" y="y1"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="y2"/> + </moveTo> + <lnTo> + <pt x="r" y="t"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="b"/> + </moveTo> + <lnTo> + <pt x="r" y="y1"/> + </lnTo> + </path> + </pathLst> + </textCascadeUp> + <textChevron> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 25000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 0 adj 50000"/> + <gd name="y" fmla="*/ a h 100000"/> + <gd name="y1" fmla="+- t b y"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="0" maxY="50000"> + <pos x="l" y="y"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="y"/> + </moveTo> + <lnTo> + <pt x="hc" y="t"/> + </lnTo> + <lnTo> + <pt x="r" y="y"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="b"/> + </moveTo> + <lnTo> + <pt x="hc" y="y1"/> + </lnTo> + <lnTo> + <pt x="r" y="b"/> + </lnTo> + </path> + </pathLst> + </textChevron> + <textChevronInverted> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 75000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 50000 adj 100000"/> + <gd name="y" fmla="*/ a h 100000"/> + <gd name="y1" fmla="+- b 0 y"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="50000" maxY="100000"> + <pos x="l" y="y"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="t"/> + </moveTo> + <lnTo> + <pt x="hc" y="y1"/> + </lnTo> + <lnTo> + <pt x="r" y="t"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="y"/> + </moveTo> + <lnTo> + <pt x="hc" y="b"/> + </lnTo> + <lnTo> + <pt x="r" y="y"/> + </lnTo> + </path> + </pathLst> + </textChevronInverted> + <textCircle> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 10800000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adval" fmla="pin 0 adj 21599999"/> + <gd name="d0" fmla="+- adval 0 10800000"/> + <gd name="d1" fmla="+- 10800000 0 adval"/> + <gd name="d2" fmla="+- 21600000 0 adval"/> + <gd name="d3" fmla="?: d1 d1 10799999"/> + <gd name="d4" fmla="?: d0 d2 d3"/> + <gd name="swAng" fmla="*/ d4 2 1"/> + <gd name="wt1" fmla="sin wd2 adj"/> + <gd name="ht1" fmla="cos hd2 adj"/> + <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/> + <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/> + <gd name="x1" fmla="+- hc dx1 0"/> + <gd name="y1" fmla="+- vc dy1 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahPolar gdRefAng="adj" minAng="0" maxAng="21599999"> + <pos x="x1" y="y1"/> + </ahPolar> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="x1" y="y1"/> + </moveTo> + <arcTo wR="wd2" hR="hd2" stAng="adval" swAng="swAng"/> + </path> + </pathLst> + </textCircle> + <textCirclePour> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj1" fmla="val cd2"/> + <gd name="adj2" fmla="val 50000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adval" fmla="pin 0 adj1 21599999"/> + <gd name="d0" fmla="+- adval 0 10800000"/> + <gd name="d1" fmla="+- 10800000 0 adval"/> + <gd name="d2" fmla="+- 21600000 0 adval"/> + <gd name="d3" fmla="?: d1 d1 10799999"/> + <gd name="d4" fmla="?: d0 d2 d3"/> + <gd name="swAng" fmla="*/ d4 2 1"/> + <gd name="wt1" fmla="sin wd2 adval"/> + <gd name="ht1" fmla="cos hd2 adval"/> + <gd name="dx1" fmla="cat2 wd2 ht1 wt1"/> + <gd name="dy1" fmla="sat2 hd2 ht1 wt1"/> + <gd name="x1" fmla="+- hc dx1 0"/> + <gd name="y1" fmla="+- vc dy1 0"/> + <gd name="adval2" fmla="pin 0 adj2 99000"/> + <gd name="ratio" fmla="*/ adval2 1 100000"/> + <gd name="iwd2" fmla="*/ wd2 ratio 1"/> + <gd name="ihd2" fmla="*/ hd2 ratio 1"/> + <gd name="wt2" fmla="sin iwd2 adval"/> + <gd name="ht2" fmla="cos ihd2 adval"/> + <gd name="dx2" fmla="cat2 iwd2 ht2 wt2"/> + <gd name="dy2" fmla="sat2 ihd2 ht2 wt2"/> + <gd name="x2" fmla="+- hc dx2 0"/> + <gd name="y2" fmla="+- vc dy2 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahPolar gdRefR="adj2" minR="0" maxR="100000" gdRefAng="adj1" minAng="0" maxAng="21599999"> + <pos x="x2" y="y2"/> + </ahPolar> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="x1" y="y1"/> + </moveTo> + <arcTo wR="wd2" hR="hd2" stAng="adval" swAng="swAng"/> + </path> + <path> + <moveTo> + <pt x="x2" y="y2"/> + </moveTo> + <arcTo wR="iwd2" hR="ihd2" stAng="adval" swAng="swAng"/> + </path> + </pathLst> + </textCirclePour> + <textCurveDown> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 45977"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 0 adj 56338"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="gd1" fmla="*/ dy 3 4"/> + <gd name="gd2" fmla="*/ dy 5 4"/> + <gd name="gd3" fmla="*/ dy 3 8"/> + <gd name="gd4" fmla="*/ dy 1 8"/> + <gd name="gd5" fmla="+- h 0 gd3"/> + <gd name="gd6" fmla="+- gd4 h 0"/> + <gd name="y0" fmla="+- t dy 0"/> + <gd name="y1" fmla="+- t gd1 0"/> + <gd name="y2" fmla="+- t gd2 0"/> + <gd name="y3" fmla="+- t gd3 0"/> + <gd name="y4" fmla="+- t gd4 0"/> + <gd name="y5" fmla="+- t gd5 0"/> + <gd name="y6" fmla="+- t gd6 0"/> + <gd name="x1" fmla="+- l wd3 0"/> + <gd name="x2" fmla="+- r 0 wd3"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="0" maxY="56338"> + <pos x="r" y="y0"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="t"/> + </moveTo> + <cubicBezTo> + <pt x="x1" y="y1"/> + <pt x="x2" y="y2"/> + <pt x="r" y="y0"/> + </cubicBezTo> + </path> + <path> + <moveTo> + <pt x="l" y="y5"/> + </moveTo> + <cubicBezTo> + <pt x="x1" y="y6"/> + <pt x="x2" y="y6"/> + <pt x="r" y="y5"/> + </cubicBezTo> + </path> + </pathLst> + </textCurveDown> + <textCurveUp> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 45977"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 0 adj 56338"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="gd1" fmla="*/ dy 3 4"/> + <gd name="gd2" fmla="*/ dy 5 4"/> + <gd name="gd3" fmla="*/ dy 3 8"/> + <gd name="gd4" fmla="*/ dy 1 8"/> + <gd name="gd5" fmla="+- h 0 gd3"/> + <gd name="gd6" fmla="+- gd4 h 0"/> + <gd name="y0" fmla="+- t dy 0"/> + <gd name="y1" fmla="+- t gd1 0"/> + <gd name="y2" fmla="+- t gd2 0"/> + <gd name="y3" fmla="+- t gd3 0"/> + <gd name="y4" fmla="+- t gd4 0"/> + <gd name="y5" fmla="+- t gd5 0"/> + <gd name="y6" fmla="+- t gd6 0"/> + <gd name="x1" fmla="+- l wd3 0"/> + <gd name="x2" fmla="+- r 0 wd3"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="0" maxY="56338"> + <pos x="l" y="y0"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="y0"/> + </moveTo> + <cubicBezTo> + <pt x="x1" y="y2"/> + <pt x="x2" y="y1"/> + <pt x="r" y="t"/> + </cubicBezTo> + </path> + <path> + <moveTo> + <pt x="l" y="y5"/> + </moveTo> + <cubicBezTo> + <pt x="x1" y="y6"/> + <pt x="x2" y="y6"/> + <pt x="r" y="y5"/> + </cubicBezTo> + </path> + </pathLst> + </textCurveUp> + <textDeflate> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 18750"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 0 adj 37500"/> + <gd name="dy" fmla="*/ a ss 100000"/> + <gd name="gd0" fmla="*/ dy 4 3"/> + <gd name="gd1" fmla="+- h 0 gd0"/> + <gd name="adjY" fmla="+- t dy 0"/> + <gd name="y0" fmla="+- t gd0 0"/> + <gd name="y1" fmla="+- t gd1 0"/> + <gd name="x0" fmla="+- l wd3 0"/> + <gd name="x1" fmla="+- r 0 wd3"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="0" maxY="37500"> + <pos x="hc" y="adjY"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="t"/> + </moveTo> + <cubicBezTo> + <pt x="x0" y="y0"/> + <pt x="x1" y="y0"/> + <pt x="r" y="t"/> + </cubicBezTo> + </path> + <path> + <moveTo> + <pt x="l" y="b"/> + </moveTo> + <cubicBezTo> + <pt x="x0" y="y1"/> + <pt x="x1" y="y1"/> + <pt x="r" y="b"/> + </cubicBezTo> + </path> + </pathLst> + </textDeflate> + <textDeflateBottom> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 50000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 6250 adj 100000"/> + <gd name="dy" fmla="*/ a ss 100000"/> + <gd name="dy2" fmla="+- h 0 dy"/> + <gd name="y1" fmla="+- t dy 0"/> + <gd name="cp" fmla="+- y1 0 dy2"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="6250" maxY="100000"> + <pos x="hc" y="y1"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="t"/> + </moveTo> + <lnTo> + <pt x="r" y="t"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="b"/> + </moveTo> + <quadBezTo> + <pt x="hc" y="cp"/> + <pt x="r" y="b"/> + </quadBezTo> + </path> + </pathLst> + </textDeflateBottom> + <textDeflateInflate> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 35000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 5000 adj 95000"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="del" fmla="*/ h 5 100"/> + <gd name="dh1" fmla="*/ h 45 100"/> + <gd name="dh2" fmla="*/ h 55 100"/> + <gd name="yh" fmla="+- dy 0 del"/> + <gd name="yl" fmla="+- dy del 0"/> + <gd name="y3" fmla="+- yh yh dh1"/> + <gd name="y4" fmla="+- yl yl dh2"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="5000" maxY="95000"> + <pos x="hc" y="dy"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="t"/> + </moveTo> + <lnTo> + <pt x="r" y="t"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="dh1"/> + </moveTo> + <quadBezTo> + <pt x="hc" y="y3"/> + <pt x="r" y="dh1"/> + </quadBezTo> + </path> + <path> + <moveTo> + <pt x="l" y="dh2"/> + </moveTo> + <quadBezTo> + <pt x="hc" y="y4"/> + <pt x="r" y="dh2"/> + </quadBezTo> + </path> + <path> + <moveTo> + <pt x="l" y="b"/> + </moveTo> + <lnTo> + <pt x="r" y="b"/> + </lnTo> + </path> + </pathLst> + </textDeflateInflate> + <textDeflateInflateDeflate> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 25000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 3000 adj 47000"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="del" fmla="*/ h 3 100"/> + <gd name="ey1" fmla="*/ h 30 100"/> + <gd name="ey2" fmla="*/ h 36 100"/> + <gd name="ey3" fmla="*/ h 63 100"/> + <gd name="ey4" fmla="*/ h 70 100"/> + <gd name="by" fmla="+- b 0 dy"/> + <gd name="yh1" fmla="+- dy 0 del"/> + <gd name="yl1" fmla="+- dy del 0"/> + <gd name="yh2" fmla="+- by 0 del"/> + <gd name="yl2" fmla="+- by del 0"/> + <gd name="y1" fmla="+- yh1 yh1 ey1"/> + <gd name="y2" fmla="+- yl1 yl1 ey2"/> + <gd name="y3" fmla="+- yh2 yh2 ey3"/> + <gd name="y4" fmla="+- yl2 yl2 ey4"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="3000" maxY="47000"> + <pos x="hc" y="dy"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="t"/> + </moveTo> + <lnTo> + <pt x="r" y="t"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="ey1"/> + </moveTo> + <quadBezTo> + <pt x="hc" y="y1"/> + <pt x="r" y="ey1"/> + </quadBezTo> + </path> + <path> + <moveTo> + <pt x="l" y="ey2"/> + </moveTo> + <quadBezTo> + <pt x="hc" y="y2"/> + <pt x="r" y="ey2"/> + </quadBezTo> + </path> + <path> + <moveTo> + <pt x="l" y="ey3"/> + </moveTo> + <quadBezTo> + <pt x="hc" y="y3"/> + <pt x="r" y="ey3"/> + </quadBezTo> + </path> + <path> + <moveTo> + <pt x="l" y="ey4"/> + </moveTo> + <quadBezTo> + <pt x="hc" y="y4"/> + <pt x="r" y="ey4"/> + </quadBezTo> + </path> + <path> + <moveTo> + <pt x="l" y="b"/> + </moveTo> + <lnTo> + <pt x="r" y="b"/> + </lnTo> + </path> + </pathLst> + </textDeflateInflateDeflate> + <textDeflateTop> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 50000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 0 adj 93750"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="y1" fmla="+- t dy 0"/> + <gd name="cp" fmla="+- y1 dy 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="0" maxY="93750"> + <pos x="hc" y="y1"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="t"/> + </moveTo> + <quadBezTo> + <pt x="hc" y="cp"/> + <pt x="r" y="t"/> + </quadBezTo> + </path> + <path> + <moveTo> + <pt x="l" y="b"/> + </moveTo> + <lnTo> + <pt x="r" y="b"/> + </lnTo> + </path> + </pathLst> + </textDeflateTop> + <textDoubleWave1> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj1" fmla="val 6250"/> + <gd name="adj2" fmla="val 0"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a1" fmla="pin 0 adj1 12500"/> + <gd name="a2" fmla="pin -10000 adj2 10000"/> + <gd name="y1" fmla="*/ h a1 100000"/> + <gd name="dy2" fmla="*/ y1 10 3"/> + <gd name="y2" fmla="+- y1 0 dy2"/> + <gd name="y3" fmla="+- y1 dy2 0"/> + <gd name="y4" fmla="+- b 0 y1"/> + <gd name="y5" fmla="+- y4 0 dy2"/> + <gd name="y6" fmla="+- y4 dy2 0"/> + <gd name="of" fmla="*/ w a2 100000"/> + <gd name="of2" fmla="*/ w a2 50000"/> + <gd name="x1" fmla="abs of"/> + <gd name="dx2" fmla="?: of2 0 of2"/> + <gd name="x2" fmla="+- l 0 dx2"/> + <gd name="dx8" fmla="?: of2 of2 0"/> + <gd name="x8" fmla="+- r 0 dx8"/> + <gd name="dx3" fmla="+/ dx2 x8 6"/> + <gd name="x3" fmla="+- x2 dx3 0"/> + <gd name="dx4" fmla="+/ dx2 x8 3"/> + <gd name="x4" fmla="+- x2 dx4 0"/> + <gd name="x5" fmla="+/ x2 x8 2"/> + <gd name="x6" fmla="+- x5 dx3 0"/> + <gd name="x7" fmla="+/ x6 x8 2"/> + <gd name="x9" fmla="+- l dx8 0"/> + <gd name="x15" fmla="+- r dx2 0"/> + <gd name="x10" fmla="+- x9 dx3 0"/> + <gd name="x11" fmla="+- x9 dx4 0"/> + <gd name="x12" fmla="+/ x9 x15 2"/> + <gd name="x13" fmla="+- x12 dx3 0"/> + <gd name="x14" fmla="+/ x13 x15 2"/> + <gd name="x16" fmla="+- r 0 x1"/> + <gd name="xAdj" fmla="+- hc of 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj1" minY="0" maxY="12500"> + <pos x="l" y="y1"/> + </ahXY> + <ahXY gdRefX="adj2" minX="-10000" maxX="10000"> + <pos x="xAdj" y="b"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="x2" y="y1"/> + </moveTo> + <cubicBezTo> + <pt x="x3" y="y2"/> + <pt x="x4" y="y3"/> + <pt x="x5" y="y1"/> + </cubicBezTo> + <cubicBezTo> + <pt x="x6" y="y2"/> + <pt x="x7" y="y3"/> + <pt x="x8" y="y1"/> + </cubicBezTo> + </path> + <path> + <moveTo> + <pt x="x9" y="y4"/> + </moveTo> + <cubicBezTo> + <pt x="x10" y="y5"/> + <pt x="x11" y="y6"/> + <pt x="x12" y="y4"/> + </cubicBezTo> + <cubicBezTo> + <pt x="x13" y="y5"/> + <pt x="x14" y="y6"/> + <pt x="x15" y="y4"/> + </cubicBezTo> + </path> + </pathLst> + </textDoubleWave1> + <textFadeDown> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 33333"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 0 adj 49999"/> + <gd name="dx" fmla="*/ a w 100000"/> + <gd name="x1" fmla="+- l dx 0"/> + <gd name="x2" fmla="+- r 0 dx"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefX="adj" minX="0" maxX="49999"> + <pos x="x1" y="b"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="t"/> + </moveTo> + <lnTo> + <pt x="r" y="t"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="x1" y="b"/> + </moveTo> + <lnTo> + <pt x="x2" y="b"/> + </lnTo> + </path> + </pathLst> + </textFadeDown> + <textFadeLeft> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 33333"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 0 adj 49999"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="y1" fmla="+- t dy 0"/> + <gd name="y2" fmla="+- b 0 dy"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="0" maxY="49999"> + <pos x="l" y="y1"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="y1"/> + </moveTo> + <lnTo> + <pt x="r" y="t"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="y2"/> + </moveTo> + <lnTo> + <pt x="r" y="b"/> + </lnTo> + </path> + </pathLst> + </textFadeLeft> + <textFadeRight> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 33333"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 0 adj 49999"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="y1" fmla="+- t dy 0"/> + <gd name="y2" fmla="+- b 0 dy"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="0" maxY="49999"> + <pos x="r" y="y1"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="t"/> + </moveTo> + <lnTo> + <pt x="r" y="y1"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="b"/> + </moveTo> + <lnTo> + <pt x="r" y="y2"/> + </lnTo> + </path> + </pathLst> + </textFadeRight> + <textFadeUp> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 33333"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 0 adj 49999"/> + <gd name="dx" fmla="*/ a w 100000"/> + <gd name="x1" fmla="+- l dx 0"/> + <gd name="x2" fmla="+- r 0 dx"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefX="adj" minX="0" maxX="49999"> + <pos x="x1" y="t"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="x1" y="t"/> + </moveTo> + <lnTo> + <pt x="x2" y="t"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="b"/> + </moveTo> + <lnTo> + <pt x="r" y="b"/> + </lnTo> + </path> + </pathLst> + </textFadeUp> + <textInflate> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 18750"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 0 adj 20000"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="gd" fmla="*/ dy 1 3"/> + <gd name="gd0" fmla="+- 0 0 gd"/> + <gd name="gd1" fmla="+- h 0 gd0"/> + <gd name="ty" fmla="+- t dy 0"/> + <gd name="by" fmla="+- b 0 dy"/> + <gd name="y0" fmla="+- t gd0 0"/> + <gd name="y1" fmla="+- t gd1 0"/> + <gd name="x0" fmla="+- l wd3 0"/> + <gd name="x1" fmla="+- r 0 wd3"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="0" maxY="20000"> + <pos x="l" y="ty"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="ty"/> + </moveTo> + <cubicBezTo> + <pt x="x0" y="y0"/> + <pt x="x1" y="y0"/> + <pt x="r" y="ty"/> + </cubicBezTo> + </path> + <path> + <moveTo> + <pt x="l" y="by"/> + </moveTo> + <cubicBezTo> + <pt x="x0" y="y1"/> + <pt x="x1" y="y1"/> + <pt x="r" y="by"/> + </cubicBezTo> + </path> + </pathLst> + </textInflate> + <textInflateBottom> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 60000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 60000 adj 100000"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="ty" fmla="+- t dy 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="60000" maxY="100000"> + <pos x="l" y="ty"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="t"/> + </moveTo> + <lnTo> + <pt x="r" y="t"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="ty"/> + </moveTo> + <quadBezTo> + <pt x="hc" y="b"/> + <pt x="r" y="ty"/> + </quadBezTo> + </path> + </pathLst> + </textInflateBottom> + <textInflateTop> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 40000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 0 adj 50000"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="ty" fmla="+- t dy 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="0" maxY="50000"> + <pos x="l" y="ty"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="ty"/> + </moveTo> + <quadBezTo> + <pt x="hc" y="t"/> + <pt x="r" y="ty"/> + </quadBezTo> + </path> + <path> + <moveTo> + <pt x="l" y="b"/> + </moveTo> + <lnTo> + <pt x="r" y="b"/> + </lnTo> + </path> + </pathLst> + </textInflateTop> + <textPlain> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 50000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 30000 adj 70000"/> + <gd name="mid" fmla="*/ a w 100000"/> + <gd name="midDir" fmla="+- mid 0 hc"/> + <gd name="dl" fmla="+- mid 0 l"/> + <gd name="dr" fmla="+- r 0 mid"/> + <gd name="dl2" fmla="*/ dl 2 1"/> + <gd name="dr2" fmla="*/ dr 2 1"/> + <gd name="dx" fmla="?: midDir dr2 dl2"/> + <gd name="xr" fmla="+- l dx 0"/> + <gd name="xl" fmla="+- r 0 dx"/> + <gd name="tlx" fmla="?: midDir l xl"/> + <gd name="trx" fmla="?: midDir xr r"/> + <gd name="blx" fmla="?: midDir xl l"/> + <gd name="brx" fmla="?: midDir r xr"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefX="adj" minX="30000" maxX="70000"> + <pos x="mid" y="b"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="tlx" y="t"/> + </moveTo> + <lnTo> + <pt x="trx" y="t"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="blx" y="b"/> + </moveTo> + <lnTo> + <pt x="brx" y="b"/> + </lnTo> + </path> + </pathLst> + </textPlain> + <textRingInside> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 60000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 50000 adj 99000"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="y" fmla="+- t dy 0"/> + <gd name="r" fmla="*/ dy 1 2"/> + <gd name="y1" fmla="+- t r 0"/> + <gd name="y2" fmla="+- b 0 r"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="50000" maxY="99000"> + <pos x="hc" y="y"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="y1"/> + </moveTo> + <arcTo wR="wd2" hR="r" stAng="10800000" swAng="21599999"/> + </path> + <path> + <moveTo> + <pt x="l" y="y2"/> + </moveTo> + <arcTo wR="wd2" hR="r" stAng="10800000" swAng="21599999"/> + </path> + </pathLst> + </textRingInside> + <textRingOutside> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 60000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 50000 adj 99000"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="y" fmla="+- t dy 0"/> + <gd name="r" fmla="*/ dy 1 2"/> + <gd name="y1" fmla="+- t r 0"/> + <gd name="y2" fmla="+- b 0 r"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="50000" maxY="99000"> + <pos x="hc" y="y"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="y1"/> + </moveTo> + <arcTo wR="wd2" hR="r" stAng="10800000" swAng="-21599999"/> + </path> + <path> + <moveTo> + <pt x="l" y="y2"/> + </moveTo> + <arcTo wR="wd2" hR="r" stAng="10800000" swAng="-21599999"/> + </path> + </pathLst> + </textRingOutside> + <textSlantDown> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 44445"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 28569 adj 100000"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="y1" fmla="+- t dy 0"/> + <gd name="y2" fmla="+- b 0 dy"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="28569" maxY="100000"> + <pos x="l" y="y1"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="t"/> + </moveTo> + <lnTo> + <pt x="r" y="y2"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="y1"/> + </moveTo> + <lnTo> + <pt x="r" y="b"/> + </lnTo> + </path> + </pathLst> + </textSlantDown> + <textSlantUp> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 55555"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 0 adj 71431"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="y1" fmla="+- t dy 0"/> + <gd name="y2" fmla="+- b 0 dy"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="0" maxY="71431"> + <pos x="l" y="y1"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="y1"/> + </moveTo> + <lnTo> + <pt x="r" y="t"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="b"/> + </moveTo> + <lnTo> + <pt x="r" y="y2"/> + </lnTo> + </path> + </pathLst> + </textSlantUp> + <textStop> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 25000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 14286 adj 50000"/> + <gd name="dx" fmla="*/ w 1 3"/> + <gd name="dy" fmla="*/ a h 100000"/> + <gd name="x1" fmla="+- l dx 0"/> + <gd name="x2" fmla="+- r 0 dx"/> + <gd name="y1" fmla="+- t dy 0"/> + <gd name="y2" fmla="+- b 0 dy"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="14286" maxY="50000"> + <pos x="l" y="dy"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="y1"/> + </moveTo> + <lnTo> + <pt x="x1" y="t"/> + </lnTo> + <lnTo> + <pt x="x2" y="t"/> + </lnTo> + <lnTo> + <pt x="r" y="y1"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="y2"/> + </moveTo> + <lnTo> + <pt x="x1" y="b"/> + </lnTo> + <lnTo> + <pt x="x2" y="b"/> + </lnTo> + <lnTo> + <pt x="r" y="y2"/> + </lnTo> + </path> + </pathLst> + </textStop> + <textTriangle> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 50000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 0 adj 100000"/> + <gd name="y" fmla="*/ a h 100000"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="0" maxY="100000"> + <pos x="l" y="y"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="y"/> + </moveTo> + <lnTo> + <pt x="hc" y="t"/> + </lnTo> + <lnTo> + <pt x="r" y="y"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="b"/> + </moveTo> + <lnTo> + <pt x="r" y="b"/> + </lnTo> + </path> + </pathLst> + </textTriangle> + <textTriangleInverted> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj" fmla="val 50000"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a" fmla="pin 0 adj 100000"/> + <gd name="y" fmla="*/ a h 100000"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj" minY="0" maxY="100000"> + <pos x="l" y="y"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="l" y="t"/> + </moveTo> + <lnTo> + <pt x="r" y="t"/> + </lnTo> + </path> + <path> + <moveTo> + <pt x="l" y="y"/> + </moveTo> + <lnTo> + <pt x="hc" y="b"/> + </lnTo> + <lnTo> + <pt x="r" y="y"/> + </lnTo> + </path> + </pathLst> + </textTriangleInverted> + <textWave1> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj1" fmla="val 12500"/> + <gd name="adj2" fmla="val 0"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a1" fmla="pin 0 adj1 20000"/> + <gd name="a2" fmla="pin -10000 adj2 10000"/> + <gd name="y1" fmla="*/ h a1 100000"/> + <gd name="dy2" fmla="*/ y1 10 3"/> + <gd name="y2" fmla="+- y1 0 dy2"/> + <gd name="y3" fmla="+- y1 dy2 0"/> + <gd name="y4" fmla="+- b 0 y1"/> + <gd name="y5" fmla="+- y4 0 dy2"/> + <gd name="y6" fmla="+- y4 dy2 0"/> + <gd name="of" fmla="*/ w a2 100000"/> + <gd name="of2" fmla="*/ w a2 50000"/> + <gd name="x1" fmla="abs of"/> + <gd name="dx2" fmla="?: of2 0 of2"/> + <gd name="x2" fmla="+- l 0 dx2"/> + <gd name="dx5" fmla="?: of2 of2 0"/> + <gd name="x5" fmla="+- r 0 dx5"/> + <gd name="dx3" fmla="+/ dx2 x5 3"/> + <gd name="x3" fmla="+- x2 dx3 0"/> + <gd name="x4" fmla="+/ x3 x5 2"/> + <gd name="x6" fmla="+- l dx5 0"/> + <gd name="x10" fmla="+- r dx2 0"/> + <gd name="x7" fmla="+- x6 dx3 0"/> + <gd name="x8" fmla="+/ x7 x10 2"/> + <gd name="x9" fmla="+- r 0 x1"/> + <gd name="xAdj" fmla="+- hc of 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj1" minY="0" maxY="20000"> + <pos x="l" y="y1"/> + </ahXY> + <ahXY gdRefX="adj2" minX="-10000" maxX="10000"> + <pos x="xAdj" y="b"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="x2" y="y1"/> + </moveTo> + <cubicBezTo> + <pt x="x3" y="y2"/> + <pt x="x4" y="y3"/> + <pt x="x5" y="y1"/> + </cubicBezTo> + </path> + <path> + <moveTo> + <pt x="x6" y="y4"/> + </moveTo> + <cubicBezTo> + <pt x="x7" y="y5"/> + <pt x="x8" y="y6"/> + <pt x="x10" y="y4"/> + </cubicBezTo> + </path> + </pathLst> + </textWave1> + <textWave2> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj1" fmla="val 12500"/> + <gd name="adj2" fmla="val 0"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a1" fmla="pin 0 adj1 20000"/> + <gd name="a2" fmla="pin -10000 adj2 10000"/> + <gd name="y1" fmla="*/ h a1 100000"/> + <gd name="dy2" fmla="*/ y1 10 3"/> + <gd name="y2" fmla="+- y1 0 dy2"/> + <gd name="y3" fmla="+- y1 dy2 0"/> + <gd name="y4" fmla="+- b 0 y1"/> + <gd name="y5" fmla="+- y4 0 dy2"/> + <gd name="y6" fmla="+- y4 dy2 0"/> + <gd name="of" fmla="*/ w a2 100000"/> + <gd name="of2" fmla="*/ w a2 50000"/> + <gd name="x1" fmla="abs of"/> + <gd name="dx2" fmla="?: of2 0 of2"/> + <gd name="x2" fmla="+- l 0 dx2"/> + <gd name="dx5" fmla="?: of2 of2 0"/> + <gd name="x5" fmla="+- r 0 dx5"/> + <gd name="dx3" fmla="+/ dx2 x5 3"/> + <gd name="x3" fmla="+- x2 dx3 0"/> + <gd name="x4" fmla="+/ x3 x5 2"/> + <gd name="x6" fmla="+- l dx5 0"/> + <gd name="x10" fmla="+- r dx2 0"/> + <gd name="x7" fmla="+- x6 dx3 0"/> + <gd name="x8" fmla="+/ x7 x10 2"/> + <gd name="x9" fmla="+- r 0 x1"/> + <gd name="xAdj" fmla="+- hc of 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj1" minY="0" maxY="20000"> + <pos x="l" y="y1"/> + </ahXY> + <ahXY gdRefX="adj2" minX="-10000" maxX="10000"> + <pos x="xAdj" y="b"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="x2" y="y1"/> + </moveTo> + <cubicBezTo> + <pt x="x3" y="y3"/> + <pt x="x4" y="y2"/> + <pt x="x5" y="y1"/> + </cubicBezTo> + </path> + <path> + <moveTo> + <pt x="x6" y="y4"/> + </moveTo> + <cubicBezTo> + <pt x="x7" y="y6"/> + <pt x="x8" y="y5"/> + <pt x="x10" y="y4"/> + </cubicBezTo> + </path> + </pathLst> + </textWave2> + <textWave4> + <avLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="adj1" fmla="val 6250"/> + <gd name="adj2" fmla="val 0"/> + </avLst> + <gdLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <gd name="a1" fmla="pin 0 adj1 12500"/> + <gd name="a2" fmla="pin -10000 adj2 10000"/> + <gd name="y1" fmla="*/ h a1 100000"/> + <gd name="dy2" fmla="*/ y1 10 3"/> + <gd name="y2" fmla="+- y1 0 dy2"/> + <gd name="y3" fmla="+- y1 dy2 0"/> + <gd name="y4" fmla="+- b 0 y1"/> + <gd name="y5" fmla="+- y4 0 dy2"/> + <gd name="y6" fmla="+- y4 dy2 0"/> + <gd name="of" fmla="*/ w a2 100000"/> + <gd name="of2" fmla="*/ w a2 50000"/> + <gd name="x1" fmla="abs of"/> + <gd name="dx2" fmla="?: of2 0 of2"/> + <gd name="x2" fmla="+- l 0 dx2"/> + <gd name="dx8" fmla="?: of2 of2 0"/> + <gd name="x8" fmla="+- r 0 dx8"/> + <gd name="dx3" fmla="+/ dx2 x8 6"/> + <gd name="x3" fmla="+- x2 dx3 0"/> + <gd name="dx4" fmla="+/ dx2 x8 3"/> + <gd name="x4" fmla="+- x2 dx4 0"/> + <gd name="x5" fmla="+/ x2 x8 2"/> + <gd name="x6" fmla="+- x5 dx3 0"/> + <gd name="x7" fmla="+/ x6 x8 2"/> + <gd name="x9" fmla="+- l dx8 0"/> + <gd name="x15" fmla="+- r dx2 0"/> + <gd name="x10" fmla="+- x9 dx3 0"/> + <gd name="x11" fmla="+- x9 dx4 0"/> + <gd name="x12" fmla="+/ x9 x15 2"/> + <gd name="x13" fmla="+- x12 dx3 0"/> + <gd name="x14" fmla="+/ x13 x15 2"/> + <gd name="x16" fmla="+- r 0 x1"/> + <gd name="xAdj" fmla="+- hc of 0"/> + </gdLst> + <ahLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <ahXY gdRefY="adj1" minY="0" maxY="12500"> + <pos x="l" y="y1"/> + </ahXY> + <ahXY gdRefX="adj2" minX="-10000" maxX="10000"> + <pos x="xAdj" y="b"/> + </ahXY> + </ahLst> + <pathLst xmlns="http://schemas.openxmlformats.org/drawingml/2006/main"> + <path> + <moveTo> + <pt x="x2" y="y1"/> + </moveTo> + <cubicBezTo> + <pt x="x3" y="y3"/> + <pt x="x4" y="y2"/> + <pt x="x5" y="y1"/> + </cubicBezTo> + <cubicBezTo> + <pt x="x6" y="y3"/> + <pt x="x7" y="y2"/> + <pt x="x8" y="y1"/> + </cubicBezTo> + </path> + <path> + <moveTo> + <pt x="x9" y="y4"/> + </moveTo> + <cubicBezTo> + <pt x="x10" y="y6"/> + <pt x="x11" y="y5"/> + <pt x="x12" y="y4"/> + </cubicBezTo> + <cubicBezTo> + <pt x="x13" y="y6"/> + <pt x="x14" y="y5"/> + <pt x="x15" y="y4"/> + </cubicBezTo> + </path> + </pathLst> + </textWave4> +</presetTextWarpDefinitions> diff --git a/oox/source/export/shapes.cxx b/oox/source/export/shapes.cxx new file mode 100644 index 0000000000..5997728e31 --- /dev/null +++ b/oox/source/export/shapes.cxx @@ -0,0 +1,2906 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_wasm_strip.h> + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <filter/msfilter/util.hxx> +#include <o3tl/string_view.hxx> +#include <o3tl/any.hxx> +#include <oox/core/xmlfilterbase.hxx> +#include <oox/export/shapes.hxx> +#include <oox/export/utils.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/token/relationship.hxx> +#include <oox/token/tokens.hxx> + +#include <initializer_list> +#include <string_view> + +#include <com/sun/star/beans/PropertyValues.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/container/XChild.hpp> +#include <com/sun/star/document/XExporter.hpp> +#include <com/sun/star/document/XStorageBasedDocument.hpp> +#include <com/sun/star/drawing/CircleKind.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/ConnectorType.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp> +#include <com/sun/star/embed/Aspects.hpp> +#include <com/sun/star/embed/EmbedStates.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/embed/XEmbedPersist.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/text/XSimpleText.hpp> +#include <com/sun/star/text/XText.hpp> +#include <com/sun/star/table/XTable.hpp> +#include <com/sun/star/table/XMergeableCell.hpp> +#include <com/sun/star/chart2/XChartDocument.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/drawing/XDrawPages.hpp> +#include <com/sun/star/drawing/XDrawPagesSupplier.hpp> +#include <com/sun/star/presentation/ClickAction.hpp> +#include <com/sun/star/drawing/XGluePointsSupplier.hpp> +#include <com/sun/star/container/XIdentifierAccess.hpp> +#include <com/sun/star/table/BorderLineStyle.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 <comphelper/diagnose_ex.hxx> +#include <oox/export/chartexport.hxx> +#include <oox/mathml/imexport.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <oox/export/DMLPresetShapeExport.hxx> + +#include <frozen/bits/defines.h> +#include <frozen/bits/elsa_std.h> +#include <frozen/set.h> +#include <frozen/unordered_map.h> + + +using namespace ::css; +using namespace ::css::beans; +using namespace ::css::uno; +using namespace ::css::drawing; +using namespace ::css::i18n; +using namespace ::css::table; +using namespace ::css::container; +using namespace ::css::document; +using namespace ::css::text; + +using ::css::io::XOutputStream; +using ::css::chart2::XChartDocument; +using ::css::frame::XModel; + +using ::oox::core::XmlFilterBase; +using ::sax_fastparser::FSHelperPtr; + + +namespace oox { + +static void lcl_ConvertProgID(std::u16string_view rProgID, + OUString & o_rMediaType, OUString & o_rRelationType, OUString & o_rFileExtension) +{ + if (rProgID == u"Excel.Sheet.12") + { + o_rMediaType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "xlsx"; + } + else if (o3tl::starts_with(rProgID, u"Excel.SheetBinaryMacroEnabled.12") ) + { + o_rMediaType = "application/vnd.ms-excel.sheet.binary.macroEnabled.12"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "xlsb"; + } + else if (o3tl::starts_with(rProgID, u"Excel.SheetMacroEnabled.12")) + { + o_rMediaType = "application/vnd.ms-excel.sheet.macroEnabled.12"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "xlsm"; + } + else if (o3tl::starts_with(rProgID, u"Excel.Sheet")) + { + o_rMediaType = "application/vnd.ms-excel"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "xls"; + } + else if (rProgID == u"PowerPoint.Show.12") + { + o_rMediaType = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "pptx"; + } + else if (rProgID == u"PowerPoint.ShowMacroEnabled.12") + { + o_rMediaType = "application/vnd.ms-powerpoint.presentation.macroEnabled.12"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "pptm"; + } + else if (o3tl::starts_with(rProgID, u"PowerPoint.Show")) + { + o_rMediaType = "application/vnd.ms-powerpoint"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "ppt"; + } + else if (o3tl::starts_with(rProgID, u"PowerPoint.Slide.12")) + { + o_rMediaType = "application/vnd.openxmlformats-officedocument.presentationml.slide"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "sldx"; + } + else if (rProgID == u"PowerPoint.SlideMacroEnabled.12") + { + o_rMediaType = "application/vnd.ms-powerpoint.slide.macroEnabled.12"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "sldm"; + } + else if (rProgID == u"Word.DocumentMacroEnabled.12") + { + o_rMediaType = "application/vnd.ms-word.document.macroEnabled.12"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "docm"; + } + else if (rProgID == u"Word.Document.12") + { + o_rMediaType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "docx"; + } + else if (rProgID == u"Word.Document.8") + { + o_rMediaType = "application/msword"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "doc"; + } + else if (rProgID == u"Excel.Chart.8") + { + o_rMediaType = "application/vnd.ms-excel"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "xls"; + } + else if (rProgID == u"AcroExch.Document.11") + { + o_rMediaType = "application/pdf"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "pdf"; + } + else + { + o_rMediaType = "application/vnd.openxmlformats-officedocument.oleObject"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "bin"; + } +} + +static uno::Reference<io::XInputStream> lcl_StoreOwnAsOOXML( + uno::Reference<uno::XComponentContext> const& xContext, + uno::Reference<embed::XEmbeddedObject> const& xObj, + char const*& o_rpProgID, + OUString & o_rMediaType, OUString & o_rRelationType, OUString & o_rSuffix) +{ + static struct { + struct { + sal_uInt32 n1; + sal_uInt16 n2, n3; + sal_uInt8 b8, b9, b10, b11, b12, b13, b14, b15; + } ClassId; + char const* pFilterName; + char const* pMediaType; + char const* pProgID; + char const* pSuffix; + } const s_Mapping[] = { + { {SO3_SW_CLASSID_60}, "MS Word 2007 XML", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "Word.Document.12", "docx" }, + { {SO3_SC_CLASSID_60}, "Calc MS Excel 2007 XML", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "Excel.Sheet.12", "xlsx" }, + { {SO3_SIMPRESS_CLASSID_60}, "Impress MS PowerPoint 2007 XML", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "PowerPoint.Show.12", "pptx" }, + // FIXME: Draw does not appear to have a MSO format export filter? +// { {SO3_SDRAW_CLASSID}, "", "", "", "" }, + { {SO3_SCH_CLASSID_60}, "unused", "", "", "" }, + { {SO3_SM_CLASSID_60}, "unused", "", "", "" }, + }; + + const char * pFilterName(nullptr); + SvGlobalName const classId(xObj->getClassID()); + for (auto & i : s_Mapping) + { + auto const& rId(i.ClassId); + SvGlobalName const temp(rId.n1, rId.n2, rId.n3, rId.b8, rId.b9, rId.b10, rId.b11, rId.b12, rId.b13, rId.b14, rId.b15); + if (temp == classId) + { + assert(SvGlobalName(SO3_SCH_CLASSID_60) != classId); // chart should be written elsewhere! + assert(SvGlobalName(SO3_SM_CLASSID_60) != classId); // formula should be written elsewhere! + pFilterName = i.pFilterName; + o_rMediaType = OUString::createFromAscii(i.pMediaType); + o_rpProgID = i.pProgID; + o_rSuffix = OUString::createFromAscii(i.pSuffix); + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + break; + } + } + + if (!pFilterName) + { + SAL_WARN("oox.shape", "oox::GetOLEObjectStream: unknown ClassId " << classId.GetHexName()); + return nullptr; + } + + if (embed::EmbedStates::LOADED == xObj->getCurrentState()) + { + xObj->changeState(embed::EmbedStates::RUNNING); + } + // use a temp stream - while it would work to store directly to a + // fragment stream, an error during export means we'd have to delete it + uno::Reference<io::XStream> const xTempStream( + xContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.comp.MemoryStream", xContext), + uno::UNO_QUERY_THROW); + uno::Sequence<beans::PropertyValue> args( comphelper::InitPropertySequence({ + { "OutputStream", Any(xTempStream->getOutputStream()) }, + { "FilterName", Any(OUString::createFromAscii(pFilterName)) } + })); + uno::Reference<frame::XStorable> xStorable(xObj->getComponent(), uno::UNO_QUERY); + try + { + xStorable->storeToURL("private:stream", args); + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("oox.shape", "oox::GetOLEObjectStream"); + return nullptr; + } + xTempStream->getOutputStream()->closeOutput(); + return xTempStream->getInputStream(); +} + +uno::Reference<io::XInputStream> GetOLEObjectStream( + uno::Reference<uno::XComponentContext> const& xContext, + uno::Reference<embed::XEmbeddedObject> const& xObj, + std::u16string_view i_rProgID, + OUString & o_rMediaType, + OUString & o_rRelationType, + OUString & o_rSuffix, + const char *& o_rpProgID) +{ + uno::Reference<io::XInputStream> xInStream; + try + { + uno::Reference<document::XStorageBasedDocument> const xParent( + uno::Reference<container::XChild>(xObj, uno::UNO_QUERY_THROW)->getParent(), + uno::UNO_QUERY_THROW); + uno::Reference<embed::XStorage> const xParentStorage(xParent->getDocumentStorage()); + OUString const entryName( + uno::Reference<embed::XEmbedPersist>(xObj, uno::UNO_QUERY_THROW)->getEntryName()); + + if (xParentStorage->isStreamElement(entryName)) + { + lcl_ConvertProgID(i_rProgID, o_rMediaType, o_rRelationType, o_rSuffix); + xInStream = xParentStorage->cloneStreamElement(entryName)->getInputStream(); + // TODO: make it possible to take the sMediaType from the stream + } + else // the object is ODF - either the whole document is + { // ODF, or the OLE was edited so it was converted to ODF + xInStream = lcl_StoreOwnAsOOXML(xContext, xObj, + o_rpProgID, o_rMediaType, o_rRelationType, o_rSuffix); + } + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("oox.shape", "oox::GetOLEObjectStream"); + } + return xInStream; +} + +} // namespace oox + +namespace oox::drawingml { + +ShapeExport::ShapeExport( sal_Int32 nXmlNamespace, FSHelperPtr pFS, ShapeHashMap* pShapeMap, XmlFilterBase* pFB, DocumentType eDocumentType, DMLTextExport* pTextExport, bool bUserShapes ) + : DrawingML( std::move(pFS), pFB, eDocumentType, pTextExport ) + , m_nEmbeddedObjects(0) + , mnShapeIdMax( 1 ) + , mbUserShapes( bUserShapes ) + , mnXmlNamespace( nXmlNamespace ) + , maMapModeSrc( MapUnit::Map100thMM ) + , maMapModeDest( MapUnit::MapInch, Point(), Fraction( 1, 576 ), Fraction( 1, 576 ) ) + , mpShapeMap( pShapeMap ? pShapeMap : &maShapeMap ) +{ + mpURLTransformer = std::make_shared<URLTransformer>(); +} + +void ShapeExport::SetURLTranslator(const std::shared_ptr<URLTransformer>& pTransformer) +{ + mpURLTransformer = pTransformer; +} + +awt::Size ShapeExport::MapSize( const awt::Size& rSize ) const +{ + Size aRetSize( OutputDevice::LogicToLogic( Size( rSize.Width, rSize.Height ), maMapModeSrc, maMapModeDest ) ); + + if ( !aRetSize.Width() ) + aRetSize.AdjustWidth( 1 ); + if ( !aRetSize.Height() ) + aRetSize.AdjustHeight( 1 ); + return awt::Size( aRetSize.Width(), aRetSize.Height() ); +} + +static bool IsNonEmptySimpleText(const Reference<XInterface>& xIface) +{ + if (Reference<XSimpleText> xText{ xIface, UNO_QUERY }) + return xText->getString().getLength(); + + return false; +} + +bool ShapeExport::NonEmptyText( const Reference< XInterface >& xIface ) +{ + Reference< XPropertySet > xPropSet( xIface, UNO_QUERY ); + + if( xPropSet.is() ) + { + Reference< XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo(); + if ( xPropSetInfo.is() ) + { + if ( xPropSetInfo->hasPropertyByName( "IsEmptyPresentationObject" ) ) + { + bool bIsEmptyPresObj = false; + if ( xPropSet->getPropertyValue( "IsEmptyPresentationObject" ) >>= bIsEmptyPresObj ) + { + SAL_INFO("oox.shape", "empty presentation object " << bIsEmptyPresObj << " , props:"); + if( bIsEmptyPresObj ) + return true; + } + } + + if ( xPropSetInfo->hasPropertyByName( "IsPresentationObject" ) ) + { + bool bIsPresObj = false; + if ( xPropSet->getPropertyValue( "IsPresentationObject" ) >>= bIsPresObj ) + { + SAL_INFO("oox.shape", "presentation object " << bIsPresObj << ", props:"); + if( bIsPresObj ) + return true; + } + } + } + } + + return IsNonEmptySimpleText(xIface); +} + +static void AddExtLst(FSHelperPtr const& pFS, Reference<XPropertySet> const& xShape) +{ + if (xShape->getPropertySetInfo()->hasPropertyByName("Decorative") + && xShape->getPropertyValue("Decorative").get<bool>()) + { + pFS->startElementNS(XML_a, XML_extLst); +// FSNS(XML_xmlns, XML_a), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml))); + pFS->startElementNS(XML_a, XML_ext, + // MSO uses this "URI" which is obviously not a URI + XML_uri, "{C183D7F6-B498-43B3-948B-1728B52AA6E4}"); + pFS->singleElementNS(XML_adec, XML_decorative, + FSNS(XML_xmlns, XML_adec), "http://schemas.microsoft.com/office/drawing/2017/decorative", + XML_val, "1"); + pFS->endElementNS(XML_a, XML_ext); + pFS->endElementNS(XML_a, XML_extLst); + } +} + +ShapeExport& ShapeExport::WritePolyPolygonShape( const Reference< XShape >& xShape, const bool bClosed ) +{ + SAL_INFO("oox.shape", "write polypolygon shape"); + + FSHelperPtr pFS = GetFS(); + pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp)); + + awt::Point aPos = xShape->getPosition(); + // Position is relative to group for child elements in Word, but absolute in API. + if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes && m_xParent.is()) + { + awt::Point aParentPos = m_xParent->getPosition(); + aPos.X -= aParentPos.X; + aPos.Y -= aParentPos.Y; + } + awt::Size aSize = xShape->getSize(); + tools::Rectangle aRect(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)); + +#if OSL_DEBUG_LEVEL > 0 + tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(xShape); + awt::Size size = MapSize( awt::Size( aRect.GetWidth(), aRect.GetHeight() ) ); + SAL_INFO("oox.shape", "poly count " << aPolyPolygon.Count()); + SAL_INFO("oox.shape", "size: " << size.Width << " x " << size.Height); +#endif + + Reference<XPropertySet> const xProps(xShape, UNO_QUERY); + // non visual shape properties + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + pFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + pFS->startElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + AddExtLst(pFS, xProps); + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + } + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr); + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + WriteNonVisualProperties( xShape ); + pFS->endElementNS( mnXmlNamespace, XML_nvSpPr ); + } + + // visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_spPr); + WriteTransformation( xShape, aRect, XML_a ); + WritePolyPolygon(xShape, bClosed); + if( xProps.is() ) { + if( bClosed ) + WriteFill(xProps, aSize); + WriteOutline( xProps ); + } + + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + // write text + WriteTextBox( xShape, mnXmlNamespace ); + + pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) ); + + return *this; +} + +ShapeExport& ShapeExport::WriteClosedPolyPolygonShape( const Reference< XShape >& xShape ) +{ + return WritePolyPolygonShape( xShape, true ); +} + +ShapeExport& ShapeExport::WriteOpenPolyPolygonShape( const Reference< XShape >& xShape ) +{ + return WritePolyPolygonShape( xShape, false ); +} + +ShapeExport& ShapeExport::WriteGroupShape(const uno::Reference<drawing::XShape>& xShape) +{ + FSHelperPtr pFS = GetFS(); + + sal_Int32 nGroupShapeToken = XML_grpSp; + if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes) + { + if (!m_xParent.is()) + nGroupShapeToken = XML_wgp; // toplevel + else + mnXmlNamespace = XML_wpg; + } + + pFS->startElementNS(mnXmlNamespace, nGroupShapeToken); + + // non visual properties + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + pFS->startElementNS(mnXmlNamespace, XML_nvGrpSpPr); + pFS->startElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + uno::Reference<beans::XPropertySet> const xShapeProps(xShape, uno::UNO_QUERY_THROW); + AddExtLst(pFS, xShapeProps); + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + pFS->singleElementNS(mnXmlNamespace, XML_cNvGrpSpPr); + WriteNonVisualProperties(xShape ); + pFS->endElementNS(mnXmlNamespace, XML_nvGrpSpPr); + } + else + pFS->singleElementNS(mnXmlNamespace, XML_cNvGrpSpPr); + + // visual properties + pFS->startElementNS(mnXmlNamespace, XML_grpSpPr); + WriteShapeTransformation(xShape, XML_a, false, false, true); + pFS->endElementNS(mnXmlNamespace, XML_grpSpPr); + + uno::Reference<drawing::XShapes> xGroupShape(xShape, uno::UNO_QUERY_THROW); + uno::Reference<drawing::XShape> xParent = m_xParent; + m_xParent = xShape; + for (sal_Int32 i = 0; i < xGroupShape->getCount(); ++i) + { + uno::Reference<drawing::XShape> xChild(xGroupShape->getByIndex(i), uno::UNO_QUERY_THROW); + sal_Int32 nSavedNamespace = mnXmlNamespace; + + uno::Reference<lang::XServiceInfo> xServiceInfo(xChild, uno::UNO_QUERY_THROW); + if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes) + { + // tdf#128820: WriteGraphicObjectShapePart calls WriteTextShape for non-empty simple + // text objects, which needs writing into wps::wsp element, so make sure to use wps + // namespace for those objects + if (xServiceInfo->supportsService("com.sun.star.drawing.GraphicObjectShape") + && !IsNonEmptySimpleText(xChild)) + mnXmlNamespace = XML_pic; + else + mnXmlNamespace = XML_wps; + } + WriteShape(xChild); + + mnXmlNamespace = nSavedNamespace; + } + m_xParent = xParent; + + pFS->endElementNS(mnXmlNamespace, nGroupShapeToken); + return *this; +} +namespace +{ + +constexpr frozen::set<std::u16string_view, 57> constDenySet( +{ + u"block-arc", + u"rectangle", + u"ellipse", + u"ring", + u"can", + u"cube", + u"paper", + u"frame", + u"forbidden", + u"smiley", + u"sun", + u"flower", + u"bracket-pair", + u"brace-pair", + u"quad-bevel", + u"round-rectangular-callout", + u"rectangular-callout", + u"round-callout", + u"cloud-callout", + u"line-callout-1", + u"line-callout-2", + u"line-callout-3", + u"paper", + u"vertical-scroll", + u"horizontal-scroll", + u"mso-spt34", + u"mso-spt75", + u"mso-spt164", + u"mso-spt180", + u"flowchart-process", + u"flowchart-alternate-process", + u"flowchart-decision", + u"flowchart-data", + u"flowchart-predefined-process", + u"flowchart-internal-storage", + u"flowchart-document", + u"flowchart-multidocument", + u"flowchart-terminator", + u"flowchart-preparation", + u"flowchart-manual-input", + u"flowchart-manual-operation", + u"flowchart-connector", + u"flowchart-off-page-connector", + u"flowchart-card", + u"flowchart-punched-tape", + u"flowchart-summing-junction", + u"flowchart-or", + u"flowchart-collate", + u"flowchart-sort", + u"flowchart-extract", + u"flowchart-merge", + u"flowchart-stored-data", + u"flowchart-delay", + u"flowchart-sequential-access", + u"flowchart-magnetic-disk", + u"flowchart-direct-access-storage", + u"flowchart-display" +}); + +constexpr frozen::set<std::u16string_view, 4> constAllowSet( +{ + u"heart", + u"puzzle", + u"col-60da8460", + u"col-502ad400" +}); + +} // end anonymous namespace + +static bool lcl_IsOnDenylist(OUString const & rShapeType) +{ + return constDenySet.find(rShapeType) != constDenySet.end(); +} + +static bool lcl_IsOnAllowlist(OUString const & rShapeType) +{ + return constAllowSet.find(rShapeType) != constAllowSet.end(); +} + +static bool lcl_GetHandlePosition( sal_Int32 &nValue, const EnhancedCustomShapeParameter &rParam, const Sequence< EnhancedCustomShapeAdjustmentValue > &rSeq) +{ + bool bAdj = false; + if ( rParam.Value.getValueTypeClass() == TypeClass_DOUBLE ) + { + double fValue(0.0); + if ( rParam.Value >>= fValue ) + nValue = static_cast<sal_Int32>(fValue); + } + else + rParam.Value >>= nValue; + + if ( rParam.Type == EnhancedCustomShapeParameterType::ADJUSTMENT) + { + bAdj = true; + sal_Int32 nIdx = nValue; + if ( nIdx < rSeq.getLength() ) + { + if ( rSeq[ nIdx ] .Value.getValueTypeClass() == TypeClass_DOUBLE ) + { + double fValue(0.0); + rSeq[ nIdx ].Value >>= fValue; + nValue = fValue; + + } + else + { + rSeq[ nIdx ].Value >>= nValue; + } + } + } + return bAdj; +} + +static void lcl_AnalyzeHandles( const uno::Sequence<beans::PropertyValues> & rHandles, + std::vector< std::pair< sal_Int32, sal_Int32> > &rHandlePositionList, + const Sequence< EnhancedCustomShapeAdjustmentValue > &rSeq) +{ + for ( const Sequence< PropertyValue >& rPropSeq : rHandles ) + { + static constexpr OUStringLiteral sPosition( u"Position" ); + bool bPosition = false; + EnhancedCustomShapeParameterPair aPosition; + for ( const PropertyValue& rPropVal: rPropSeq ) + { + if ( rPropVal.Name == sPosition ) + { + if ( rPropVal.Value >>= aPosition ) + bPosition = true; + } + } + if ( bPosition ) + { + sal_Int32 nXPosition = 0; + sal_Int32 nYPosition = 0; + // For polar handles, nXPosition is radius and nYPosition is angle + lcl_GetHandlePosition( nXPosition, aPosition.First , rSeq ); + lcl_GetHandlePosition( nYPosition, aPosition.Second, rSeq ); + rHandlePositionList.emplace_back( nXPosition, nYPosition ); + } + } +} + +static void lcl_AppendAdjustmentValue( std::vector< std::pair< sal_Int32, sal_Int32> > &rAvList, sal_Int32 nAdjIdx, sal_Int32 nValue ) +{ + rAvList.emplace_back( nAdjIdx , nValue ); +} + +static sal_Int32 lcl_NormalizeAngle( sal_Int32 nAngle ) +{ + nAngle = nAngle % 360; + return nAngle < 0 ? ( nAngle + 360 ) : nAngle ; +} + +static sal_Int32 lcl_CircleAngle2CustomShapeEllipseAngleOOX(const sal_Int32 nInternAngle, const sal_Int32 nWidth, const sal_Int32 nHeight) +{ + if (nWidth != 0 || nHeight != 0) + { + double fAngle = basegfx::deg2rad<100>(nInternAngle); // intern 1/100 deg to rad + fAngle = atan2(nHeight * sin(fAngle), nWidth * cos(fAngle)); // circle to ellipse + fAngle = basegfx::rad2deg<60000>(fAngle); // rad to OOXML angle unit + sal_Int32 nAngle = basegfx::fround(fAngle); // normalize + nAngle = nAngle % 21600000; + return nAngle < 0 ? (nAngle + 21600000) : nAngle; + } + else // should be handled by caller, dummy value + return 0; +} + +static OUString lcl_GetTarget(const css::uno::Reference<css::frame::XModel>& xModel, + std::u16string_view rURL) +{ + Reference<drawing::XDrawPagesSupplier> xDPS(xModel, uno::UNO_QUERY_THROW); + Reference<drawing::XDrawPages> xDrawPages(xDPS->getDrawPages(), uno::UNO_SET_THROW); + sal_uInt32 nPageCount = xDrawPages->getCount(); + OUString sTarget; + + for (sal_uInt32 i = 0; i < nPageCount; ++i) + { + Reference<XDrawPage> xDrawPage; + xDrawPages->getByIndex(i) >>= xDrawPage; + Reference<container::XNamed> xNamed(xDrawPage, UNO_QUERY); + if (!xNamed) + continue; + OUString sSlideName = "#" + xNamed->getName(); + if (rURL == sSlideName) + { + sTarget = "slide" + OUString::number(i + 1) + ".xml"; + break; + } + } + + return sTarget; +} + +ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape ) +{ + SAL_INFO("oox.shape", "write custom shape"); + Reference< XPropertySet > rXPropSet( xShape, UNO_QUERY ); + // First check, if this is a Fontwork-shape. For DrawingML, such a shape is a + // TextBox shape with body property prstTxWarp. + if (IsFontworkShape(rXPropSet)) + { + ShapeExport::WriteTextShape(xShape); // qualifier to prevent PowerPointShapeExport + return *this; + } + + bool bHasGeometrySeq(false); + Sequence< PropertyValue > aGeometrySeq; + OUString sShapeType("non-primitive"); // default in ODF + if (GetProperty(rXPropSet, "CustomShapeGeometry")) + { + SAL_INFO("oox.shape", "got custom shape geometry"); + if (mAny >>= aGeometrySeq) + { + bHasGeometrySeq = true; + SAL_INFO("oox.shape", "got custom shape geometry sequence"); + for (const PropertyValue& rProp : std::as_const(aGeometrySeq)) + { + SAL_INFO("oox.shape", "geometry property: " << rProp.Name); + if (rProp.Name == "Type") + rProp.Value >>= sShapeType; + } + } + } + + bool bPredefinedHandlesUsed = true; + bool bHasHandles = false; + + ShapeFlag nMirrorFlags = ShapeFlag::NONE; + MSO_SPT eShapeType = EscherPropertyContainer::GetCustomShapeType( xShape, nMirrorFlags, sShapeType ); + assert(dynamic_cast< SdrObjCustomShape* >(SdrObject::getSdrObjectFromXShape(xShape)) && "Not a SdrObjCustomShape (!)"); + SdrObjCustomShape& rSdrObjCustomShape(static_cast< SdrObjCustomShape& >(*SdrObject::getSdrObjectFromXShape(xShape))); + const bool bIsDefaultObject( + EscherPropertyContainer::IsDefaultObject( + rSdrObjCustomShape, + eShapeType)); + OString sPresetShape = msfilter::util::GetOOXMLPresetGeometry(sShapeType); + SAL_INFO("oox.shape", "custom shape type: " << sShapeType << " ==> " << sPresetShape); + + sal_Int32 nAdjustmentValuesIndex = -1; + awt::Rectangle aViewBox; + uno::Sequence<beans::PropertyValues> aHandles; + + bool bFlipH = false; + bool bFlipV = false; + + if (bHasGeometrySeq) + { + for (int i = 0; i < aGeometrySeq.getLength(); i++) + { + const PropertyValue& rProp = aGeometrySeq[ i ]; + SAL_INFO("oox.shape", "geometry property: " << rProp.Name); + + if ( rProp.Name == "MirroredX" ) + rProp.Value >>= bFlipH; + + if ( rProp.Name == "MirroredY" ) + rProp.Value >>= bFlipV; + if ( rProp.Name == "AdjustmentValues" ) + nAdjustmentValuesIndex = i; + else if ( rProp.Name == "Handles" ) + { + rProp.Value >>= aHandles; + if ( aHandles.hasElements() ) + bHasHandles = true; + if( !bIsDefaultObject ) + bPredefinedHandlesUsed = false; + // TODO: update nAdjustmentsWhichNeedsToBeConverted here + } + else if ( rProp.Name == "ViewBox" ) + rProp.Value >>= aViewBox; + } + } + + FSHelperPtr pFS = GetFS(); + // non visual shape properties + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + bool bUseBackground = false; + if (GetProperty(rXPropSet, "FillUseSlideBackground")) + mAny >>= bUseBackground; + if (bUseBackground) + mpFS->startElementNS(mnXmlNamespace, XML_sp, XML_useBgFill, "1"); + else + mpFS->startElementNS(mnXmlNamespace, XML_sp); + + bool isVisible = true ; + if( GetProperty(rXPropSet, "Visible")) + { + mAny >>= isVisible; + } + pFS->startElementNS( mnXmlNamespace, XML_nvSpPr ); + pFS->startElementNS( + mnXmlNamespace, XML_cNvPr, XML_id, + OString::number(GetShapeID(xShape) == -1 ? GetNewShapeID(xShape) : GetShapeID(xShape)), + XML_name, GetShapeName(xShape), XML_hidden, sax_fastparser::UseIf("1", !isVisible)); + + if( GetProperty(rXPropSet, "URL") ) + { + OUString sURL; + mAny >>= sURL; + if( !sURL.isEmpty() ) + { + OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(), + oox::getRelationship(Relationship::HYPERLINK), + mpURLTransformer->getTransformedString(sURL), + mpURLTransformer->isExternalURL(sURL)); + + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); + } + } + + OUString sBookmark; + if (GetProperty(rXPropSet, "Bookmark")) + mAny >>= sBookmark; + + if (GetProperty(rXPropSet, "OnClick")) + { + OUString sPPAction; + presentation::ClickAction eClickAction = presentation::ClickAction_NONE; + mAny >>= eClickAction; + if (eClickAction != presentation::ClickAction_NONE) + { + switch (eClickAction) + { + case presentation::ClickAction_STOPPRESENTATION: + sPPAction = "ppaction://hlinkshowjump?jump=endshow"; + break; + case presentation::ClickAction_NEXTPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=nextslide"; + break; + case presentation::ClickAction_LASTPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=lastslide"; + break; + case presentation::ClickAction_PREVPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=previousslide"; + break; + case presentation::ClickAction_FIRSTPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=firstslide"; + break; + case presentation::ClickAction_BOOKMARK: + sBookmark = "#" + sBookmark; + break; + default: + break; + } + } + if (!sPPAction.isEmpty()) + pFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action, + sPPAction); + } + if (!sBookmark.isEmpty()) + { + bool bExtURL = URLTransformer().isExternalURL(sBookmark); + sBookmark = bExtURL ? sBookmark : lcl_GetTarget(GetFB()->getModel(), sBookmark); + + OUString sRelId + = mpFB->addRelation(mpFS->getOutputStream(), + bExtURL ? oox::getRelationship(Relationship::HYPERLINK) + : oox::getRelationship(Relationship::SLIDE), + sBookmark, bExtURL); + if (bExtURL) + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); + else + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId, + XML_action, "ppaction://hlinksldjump"); + } + AddExtLst(pFS, rXPropSet); + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr); + WriteNonVisualProperties( xShape ); + pFS->endElementNS( mnXmlNamespace, XML_nvSpPr ); + } + else + { + pFS->startElementNS(mnXmlNamespace, XML_wsp); + if (m_xParent.is()) + { + pFS->startElementNS(mnXmlNamespace, XML_cNvPr, XML_id, + OString::number(GetShapeID(xShape) == -1 ? GetNewShapeID(xShape) + : GetShapeID(xShape)), + XML_name, GetShapeName(xShape)); + + if (GetProperty(rXPropSet, "Hyperlink")) + { + 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); + } + } + AddExtLst(pFS, rXPropSet); + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + } + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr); + } + + // visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_spPr); + + // we export non-primitive shapes to custom geometry + // we also export non-ooxml shapes which have handles/equations to custom geometry, because + // we cannot convert ODF equations to DrawingML equations. TODO: see what binary DOC export filter does. + // but our WritePolyPolygon()/WriteCustomGeometry() functions are incomplete, therefore we use a denylist + // we use a allowlist for shapes where mapping to MSO preset shape is not optimal + bool bCustGeom = true; + bool bOnDenylist = false; + if( sShapeType == "ooxml-non-primitive" ) + bCustGeom = true; + else if( sShapeType.startsWith("ooxml") ) + bCustGeom = false; + else if( lcl_IsOnAllowlist(sShapeType) ) + bCustGeom = true; + else if( lcl_IsOnDenylist(sShapeType) ) + { + bCustGeom = false; + bOnDenylist = true; + } + + bool bPresetWriteSuccessful = false; + // Let the custom shapes what has name and preset information in OOXML, to be written + // as preset ones with parameters. Try that with this converter class. + if (!sShapeType.startsWith("ooxml") && sShapeType != "non-primitive" && !mbUserShapes + && xShape->getShapeType() == "com.sun.star.drawing.CustomShape" + && !lcl_IsOnAllowlist(sShapeType)) + { + DMLPresetShapeExporter aCustomShapeConverter(this, xShape); + bPresetWriteSuccessful = aCustomShapeConverter.WriteShape(); + } + // If preset writing has problems try to write the shape as it done before + if (bPresetWriteSuccessful) + ;// Already written do nothing. + else if (bCustGeom) + { + WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV ); + bool bSuccess = WriteCustomGeometry(xShape, rSdrObjCustomShape); + // In case of Writer, the parent element is <wps:spPr>, and there the <a:custGeom> element + // is not optional. + if (!bSuccess && GetDocumentType() == DOCUMENT_DOCX) + { + WriteEmptyCustomGeometry(); + } + } + else if (bOnDenylist && bHasHandles && nAdjustmentValuesIndex !=-1 && !sShapeType.startsWith("mso-spt")) + { + WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV ); + Sequence< EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq; + std::vector< std::pair< sal_Int32, sal_Int32> > aHandlePositionList; + std::vector< std::pair< sal_Int32, sal_Int32> > aAvList; + aGeometrySeq[ nAdjustmentValuesIndex ].Value >>= aAdjustmentSeq ; + + lcl_AnalyzeHandles( aHandles, aHandlePositionList, aAdjustmentSeq ); + + sal_Int32 nXPosition = 0; + sal_Int32 nYPosition = 0; + if ( !aHandlePositionList.empty() ) + { + nXPosition = aHandlePositionList[0].first ; + nYPosition = aHandlePositionList[0].second ; + } + switch( eShapeType ) + { + case mso_sptBorderCallout1: + { + sal_Int32 adj3 = double(nYPosition)/aViewBox.Height *100000; + sal_Int32 adj4 = double(nXPosition)/aViewBox.Width *100000; + lcl_AppendAdjustmentValue( aAvList, 1, 18750 ); + lcl_AppendAdjustmentValue( aAvList, 2, -8333 ); + lcl_AppendAdjustmentValue( aAvList, 3, adj3 ); + lcl_AppendAdjustmentValue( aAvList, 4, adj4 ); + break; + } + case mso_sptBorderCallout2: + { + sal_Int32 adj5 = double(nYPosition)/aViewBox.Height *100000; + sal_Int32 adj6 = double(nXPosition)/aViewBox.Width *100000; + sal_Int32 adj3 = 18750; + sal_Int32 adj4 = -16667; + lcl_AppendAdjustmentValue( aAvList, 1, 18750 ); + lcl_AppendAdjustmentValue( aAvList, 2, -8333 ); + if ( aHandlePositionList.size() > 1 ) + { + nXPosition = aHandlePositionList[1].first ; + nYPosition = aHandlePositionList[1].second ; + adj3 = double(nYPosition)/aViewBox.Height *100000; + adj4 = double(nXPosition)/aViewBox.Width *100000; + } + lcl_AppendAdjustmentValue( aAvList, 3, adj3 ); + lcl_AppendAdjustmentValue( aAvList, 4, adj4 ); + lcl_AppendAdjustmentValue( aAvList, 5, adj5 ); + lcl_AppendAdjustmentValue( aAvList, 6, adj6 ); + break; + } + case mso_sptWedgeRectCallout: + case mso_sptWedgeRRectCallout: + case mso_sptWedgeEllipseCallout: + case mso_sptCloudCallout: + { + sal_Int32 adj1 = (double(nXPosition)/aViewBox.Width -0.5) *100000; + sal_Int32 adj2 = (double(nYPosition)/aViewBox.Height -0.5) *100000; + lcl_AppendAdjustmentValue( aAvList, 1, adj1 ); + lcl_AppendAdjustmentValue( aAvList, 2, adj2 ); + if ( eShapeType == mso_sptWedgeRRectCallout) + { + lcl_AppendAdjustmentValue( aAvList, 3, 16667); + } + + break; + } + case mso_sptFoldedCorner: + { + sal_Int32 adj = double( aViewBox.Width - nXPosition) / std::min( aViewBox.Width,aViewBox.Height ) * 100000; + lcl_AppendAdjustmentValue( aAvList, 0, adj ); + break; + } + case mso_sptDonut: + case mso_sptSun: + case mso_sptMoon: + case mso_sptNoSmoking: + case mso_sptHorizontalScroll: + case mso_sptBevel: + case mso_sptBracketPair: + { + sal_Int32 adj = double( nXPosition )/aViewBox.Width*100000 ; + lcl_AppendAdjustmentValue( aAvList, 0, adj ); + break; + } + case mso_sptCan: + case mso_sptCube: + case mso_sptBracePair: + case mso_sptVerticalScroll: + { + sal_Int32 adj = double( nYPosition )/aViewBox.Height *100000 ; + lcl_AppendAdjustmentValue( aAvList, 0, adj ); + break; + } + case mso_sptSmileyFace: + { + sal_Int32 adj = double( nYPosition )/aViewBox.Height *100000 - 76458.0; + lcl_AppendAdjustmentValue( aAvList, 0, adj ); + break; + } + case mso_sptBlockArc: + { + sal_Int32 nRadius = 50000 * ( 1 - double(nXPosition) / 10800); + sal_Int32 nAngleStart = lcl_NormalizeAngle( nYPosition ); + sal_Int32 nAngleEnd = lcl_NormalizeAngle( 180 - nAngleStart ); + lcl_AppendAdjustmentValue( aAvList, 1, 21600000 / 360 * nAngleStart ); + lcl_AppendAdjustmentValue( aAvList, 2, 21600000 / 360 * nAngleEnd ); + lcl_AppendAdjustmentValue( aAvList, 3, nRadius ); + break; + } + // case mso_sptNil: + // case mso_sptBentConnector3: + // case mso_sptBorderCallout3: + default: + { + if ( sPresetShape == "frame" ) + { + sal_Int32 adj1 = double( nYPosition )/aViewBox.Height *100000 ; + lcl_AppendAdjustmentValue( aAvList, 1, adj1 ); + } + break; + } + } + WritePresetShape( sPresetShape , aAvList ); + } + else // preset geometry + { + WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV ); + if( nAdjustmentValuesIndex != -1 ) + { + WritePresetShape( sPresetShape, eShapeType, bPredefinedHandlesUsed, + aGeometrySeq[ nAdjustmentValuesIndex ] ); + } + else + WritePresetShape( sPresetShape ); + } + if( rXPropSet.is() ) + { + WriteFill(rXPropSet, xShape->getSize()); + WriteOutline( rXPropSet ); + WriteShapeEffects( rXPropSet ); + + bool bHas3DEffectinShape = false; + uno::Sequence<beans::PropertyValue> grabBag; + rXPropSet->getPropertyValue("InteropGrabBag") >>= grabBag; + + for (auto const& it : std::as_const(grabBag)) + if (it.Name == "3DEffectProperties") + bHas3DEffectinShape = true; + + if( bHas3DEffectinShape) + Write3DEffects( rXPropSet, /*bIsText=*/false ); + } + + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + pFS->startElementNS(mnXmlNamespace, XML_style); + WriteShapeStyle( rXPropSet ); + pFS->endElementNS( mnXmlNamespace, XML_style ); + + // write text + WriteTextBox( xShape, mnXmlNamespace ); + + pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) ); + + return *this; +} + +ShapeExport& ShapeExport::WriteEllipseShape( const Reference< XShape >& xShape ) +{ + SAL_INFO("oox.shape", "write ellipse shape"); + + FSHelperPtr pFS = GetFS(); + + pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp)); + + // TODO: connector ? + + Reference<XPropertySet> const xProps(xShape, UNO_QUERY); + // non visual shape properties + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + pFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + pFS->startElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + AddExtLst(pFS, xProps); + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + pFS->singleElementNS( mnXmlNamespace, XML_cNvSpPr ); + WriteNonVisualProperties( xShape ); + pFS->endElementNS( mnXmlNamespace, XML_nvSpPr ); + } + else + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr); + + 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"_ostr); + 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"_ostr, aAvList); + break; + case CircleKind_SECTION : + WritePresetShape("pie"_ostr, aAvList); + break; + case CircleKind_CUT : + WritePresetShape("chord"_ostr, aAvList); + break; + default : + WritePresetShape("ellipse"_ostr); + } + } + if( xProps.is() ) + { + if (CircleKind_ARC == eCircleKind) + { + // An arc in ODF is never filled, even if a fill style other than + // "none" is set. OOXML arc can be filled, so set fill explicit to + // NONE, otherwise some hidden or inherited filling is shown. + FillStyle eFillStyle(FillStyle_NONE); + uno::Any aNewValue; + aNewValue <<= eFillStyle; + xProps->setPropertyValue("FillStyle", aNewValue); + } + WriteFill( xProps ); + WriteOutline( xProps ); + } + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + // write text + WriteTextBox( xShape, mnXmlNamespace ); + + pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) ); + + return *this; +} + +ShapeExport& ShapeExport::WriteGraphicObjectShape( const Reference< XShape >& xShape ) +{ + WriteGraphicObjectShapePart( xShape ); + + return *this; +} + +void ShapeExport::WriteGraphicObjectShapePart( const Reference< XShape >& xShape, const Graphic* pGraphic ) +{ + SAL_INFO("oox.shape", "write graphic object shape"); + + if (IsNonEmptySimpleText(xShape)) + { + SAL_INFO("oox.shape", "graphicObject: wrote only text"); + + WriteTextShape(xShape); + + return; + } + + SAL_INFO("oox.shape", "graphicObject without text"); + + uno::Reference<graphic::XGraphic> xGraphic; + OUString sMediaURL; + + Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY ); + + if (pGraphic) + { + xGraphic.set(pGraphic->GetXGraphic()); + } + else if (xShapeProps.is() && xShapeProps->getPropertySetInfo()->hasPropertyByName("Graphic")) + { + xShapeProps->getPropertyValue("Graphic") >>= xGraphic; + } + + // tdf#155903 Only for PPTX, Microsoft does not support this feature in Word and Excel. + bool bHasMediaURL = GetDocumentType() == DOCUMENT_PPTX && xShapeProps.is() + && xShapeProps->getPropertySetInfo()->hasPropertyByName("MediaURL") + && (xShapeProps->getPropertyValue("MediaURL") >>= sMediaURL); + + if (!xGraphic.is() && !bHasMediaURL) + { + SAL_INFO("oox.shape", "no graphic or media URL found"); + return; + } + + FSHelperPtr pFS = GetFS(); + XmlFilterBase* pFB = GetFB(); + + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + pFS->startElementNS(mnXmlNamespace, XML_pic); + else + pFS->startElementNS(mnXmlNamespace, XML_pic, + FSNS(XML_xmlns, XML_pic), pFB->getNamespaceURL(OOX_NS(dmlPicture))); + + pFS->startElementNS(mnXmlNamespace, XML_nvPicPr); + + presentation::ClickAction eClickAction = presentation::ClickAction_NONE; + OUString sDescr, sURL, sBookmark, sPPAction; + bool bHaveDesc; + + if ( ( bHaveDesc = GetProperty( xShapeProps, "Description" ) ) ) + mAny >>= sDescr; + if ( GetProperty( xShapeProps, "URL" ) ) + mAny >>= sURL; + if (GetProperty(xShapeProps, "Bookmark")) + mAny >>= sBookmark; + if (GetProperty(xShapeProps, "OnClick")) + mAny >>= eClickAction; + + pFS->startElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape), + XML_descr, sax_fastparser::UseIf(sDescr, bHaveDesc)); + + if (eClickAction != presentation::ClickAction_NONE) + { + switch (eClickAction) + { + case presentation::ClickAction_STOPPRESENTATION: + sPPAction = "ppaction://hlinkshowjump?jump=endshow"; + break; + case presentation::ClickAction_NEXTPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=nextslide"; + break; + case presentation::ClickAction_LASTPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=lastslide"; + break; + case presentation::ClickAction_PREVPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=previousslide"; + break; + case presentation::ClickAction_FIRSTPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=firstslide"; + break; + case presentation::ClickAction_BOOKMARK: + sBookmark = "#" + sBookmark; + break; + default: + break; + } + } + + // OOXTODO: //cNvPr children: XML_extLst, XML_hlinkHover + if (bHasMediaURL || !sPPAction.isEmpty()) + pFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action, + bHasMediaURL ? "ppaction://media" : sPPAction); + if( !sURL.isEmpty() ) + { + OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(), + oox::getRelationship(Relationship::HYPERLINK), + mpURLTransformer->getTransformedString(sURL), + mpURLTransformer->isExternalURL(sURL)); + + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); + } + + if (!sBookmark.isEmpty()) + { + bool bExtURL = URLTransformer().isExternalURL(sBookmark); + sBookmark = bExtURL ? sBookmark : lcl_GetTarget(GetFB()->getModel(), sBookmark); + + OUString sRelId = mpFB->addRelation(mpFS->getOutputStream(), + bExtURL ? oox::getRelationship(Relationship::HYPERLINK) + : oox::getRelationship(Relationship::SLIDE), + sBookmark, bExtURL); + + if (bExtURL) + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); + else + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId, XML_action, + "ppaction://hlinksldjump"); + } + AddExtLst(pFS, xShapeProps); + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + + pFS->singleElementNS(mnXmlNamespace, XML_cNvPicPr + // OOXTODO: XML_preferRelativeSize + ); + if (bHasMediaURL) + WriteMediaNonVisualProperties(xShape); + else + WriteNonVisualProperties(xShape); + + pFS->endElementNS( mnXmlNamespace, XML_nvPicPr ); + + pFS->startElementNS(mnXmlNamespace, XML_blipFill); + + if (xGraphic.is()) + { + WriteXGraphicBlip(xShapeProps, xGraphic, mbUserShapes); + } + else if (bHasMediaURL) + { + Reference<graphic::XGraphic> xFallbackGraphic; + if (xShapeProps->getPropertySetInfo()->hasPropertyByName("FallbackGraphic")) + xShapeProps->getPropertyValue("FallbackGraphic") >>= xFallbackGraphic; + + WriteXGraphicBlip(xShapeProps, xFallbackGraphic, mbUserShapes); + } + + if (xGraphic.is()) + { + WriteSrcRectXGraphic(xShapeProps, xGraphic); + } + + // now we stretch always when we get pGraphic (when changing that + // behavior, test n#780830 for regression, where the OLE sheet might get tiled + bool bStretch = false; + if( !pGraphic && GetProperty( xShapeProps, "FillBitmapStretch" ) ) + mAny >>= bStretch; + + if ( pGraphic || bStretch ) + pFS->singleElementNS(XML_a, XML_stretch); + + if (bHasMediaURL) + { + // Graphic of media shapes is always stretched. + pFS->startElementNS(XML_a, XML_stretch); + pFS->singleElementNS(XML_a, XML_fillRect); + pFS->endElementNS(XML_a, XML_stretch); + } + + pFS->endElementNS( mnXmlNamespace, XML_blipFill ); + + // visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_spPr); + bool bFlipH = false; + if( xShapeProps->getPropertySetInfo()->hasPropertyByName("IsMirrored") ) + { + xShapeProps->getPropertyValue("IsMirrored") >>= bFlipH; + } + WriteShapeTransformation( xShape, XML_a, bFlipH, false, false, false, true ); + WritePresetShape( "rect"_ostr ); + // graphic object can come with the frame (bnc#654525) + WriteOutline( xShapeProps ); + + WriteShapeEffects( xShapeProps ); + Write3DEffects( xShapeProps, /*bIsText=*/false ); + + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + pFS->endElementNS( mnXmlNamespace, XML_pic ); +} + +static void lcl_Rotate(sal_Int32 nAngle, Point center, awt::Point& pt) +{ + sal_Int16 nCos, nSin; + switch (nAngle) + { + case 90: + nCos = 0; + nSin = 1; + break; + case 180: + nCos = -1; + nSin = 0; + break; + case 270: + nCos = 0; + nSin = -1; + break; + default: + return; + } + sal_Int32 x = pt.X - center.X(); + sal_Int32 y = pt.Y - center.Y(); + pt.X = center.X() + x * nCos - y * nSin; + pt.Y = center.Y() + y * nCos + x * nSin; +} + +static void lcl_FlipHFlipV(tools::Polygon aPoly, sal_Int32 nAngle, bool& rFlipH, bool& rFlipV) +{ + Point aStart = aPoly[0]; + Point aEnd = aPoly[aPoly.GetSize() - 1]; + + if (aStart.X() > aEnd.X() && aStart.Y() > aEnd.Y()) + { + if (nAngle) + { + if (nAngle == 90) + rFlipH = true; + if (nAngle == 270) + rFlipV = true; + } + else // 0° + { + rFlipH = true; + rFlipV = true; + } + } + + if (aStart.X() < aEnd.X() && aStart.Y() < aEnd.Y()) + { + if (nAngle) + { + if (nAngle != 270) + { + rFlipH = true; + rFlipV = true; + } + else + rFlipH = true; + } + } + + if (aStart.Y() < aEnd.Y() && aStart.X() > aEnd.X()) + { + if (nAngle) + { + if (nAngle == 180) + rFlipV = true; + if (nAngle == 270) + { + rFlipV = true; + rFlipH = true; + } + } + else // 0° + { + rFlipH = true; + } + } + + if (aStart.Y() > aEnd.Y() && aStart.X() < aEnd.X()) + { + if (nAngle) + { + if (nAngle == 90) + { + rFlipH = true; + rFlipV = true; + } + if (nAngle == 180) + rFlipH = true; + } + else // 0° + rFlipV = true; + } +} + +static sal_Int32 lcl_GetAngle(tools::Polygon aPoly) +{ + sal_Int32 nAngle; + Point aStartPoint = aPoly[0]; + Point aEndPoint = aPoly[aPoly.GetSize() - 1]; + if (aStartPoint.X() == aPoly[1].X()) + { + if ((aStartPoint.X() < aEndPoint.X() && aStartPoint.Y() > aEndPoint.Y()) + || (aStartPoint.X() > aEndPoint.X() && aStartPoint.Y() < aEndPoint.Y())) + { + nAngle = 90; + } + else + nAngle = 270; + } + else + { + if (aStartPoint.X() > aPoly[1].X()) + nAngle = 180; + else + nAngle = 0; + } + + return nAngle; +} + +// Adjust value decide the position, where the connector should turn. +static void lcl_GetConnectorAdjustValue(const Reference<XShape>& xShape, tools::Polygon aPoly, + ConnectorType eConnectorType, + std::vector<std::pair<sal_Int32, sal_Int32>>& rAvList) +{ + Reference<XPropertySet> xShapeProps(xShape, UNO_QUERY); + bool bIsOOXMLCurve(false); + xShapeProps->getPropertyValue("EdgeOOXMLCurve") >>= bIsOOXMLCurve; + sal_Int32 nAdjCount = 0; + if (eConnectorType == ConnectorType_CURVE) + { + if (bIsOOXMLCurve) + { + nAdjCount = (aPoly.GetSize() - 4) / 3; + } + else if (aPoly.GetSize() == 4) + { + if ((aPoly[0].X() == aPoly[1].X() && aPoly[2].X() == aPoly[3].X()) + || (aPoly[0].Y() == aPoly[1].Y() && aPoly[2].Y() == aPoly[3].Y())) + { + nAdjCount = 1; // curvedConnector3, control vectors parallel + } + else + nAdjCount = 0; // curvedConnector2, control vectors orthogonal + } + else if (aPoly.GetSize() > 4) + { + if ((aPoly[2].X() == aPoly[3].X() && aPoly[3].X() == aPoly[4].X()) + || (aPoly[2].Y() == aPoly[3].Y() && aPoly[3].Y() == aPoly[4].Y())) + { + nAdjCount = 3; // curvedConnector5 + } + else + nAdjCount = 2; // curvedConnector4 + } + } + else + { + switch (aPoly.GetSize()) + { + case 3: + nAdjCount = 0; // bentConnector2 + break; + case 4: + nAdjCount = 1; // bentConnector3 + break; + case 5: + nAdjCount = 2; // bentConnector4 + break; + case 6: + nAdjCount = 3; // bentConnector5 + break; + } + } + + if (nAdjCount) + { + sal_Int32 nAdjustValue; + Point aStart = aPoly[0]; + Point aEnd = aPoly[aPoly.GetSize() - 1]; + + for (sal_Int32 i = 1; i <= nAdjCount; ++i) + { + Point aPt = aPoly[i]; + + if (aEnd.Y() == aStart.Y()) + aEnd.setY(aStart.Y() + 1); + if (aEnd.X() == aStart.X()) + aEnd.setX(aStart.X() + 1); + + bool bVertical = aPoly[1].X() - aStart.X() != 0 ? true : false; + // vertical and horizon alternate + if (i % 2 == 1) + bVertical = !bVertical; + + if (eConnectorType == ConnectorType_CURVE) + { + if (bIsOOXMLCurve) + { + aPt = aPoly[3 * i]; + } + else + { + awt::Size aSize = xShape->getSize(); + awt::Point aShapePosition = xShape->getPosition(); + tools::Rectangle aBoundRect = aPoly.GetBoundRect(); + + if (bVertical) + { + if ((aBoundRect.GetSize().Height() - aSize.Height) == 1) + aPt.setY(aPoly[i + 1].Y()); + else if (aStart.Y() > aPt.Y()) + aPt.setY(aShapePosition.Y); + else + aPt.setY(aShapePosition.Y + aSize.Height); + } + else + { + if ((aBoundRect.GetSize().Width() - aSize.Width) == 1) + aPt.setX(aPoly[i + 1].X()); + else if (aStart.X() > aPt.X()) + aPt.setX(aShapePosition.X); + else + aPt.setX(aShapePosition.X + aSize.Width); + } + } + } + + if (bVertical) + nAdjustValue = ((aPt.Y() - aStart.Y()) * 100000) / (aEnd.Y() - aStart.Y()); + else + nAdjustValue = ((aPt.X() - aStart.X()) * 100000) / (aEnd.X() - aStart.X()); + + rAvList.emplace_back(i, nAdjustValue); + } + } +} + +static sal_Int32 lcl_GetGluePointId(const Reference<XShape>& xShape, sal_Int32 nGluePointId) +{ + if (nGluePointId > 3) + return nGluePointId - 4; + else + { + bool bFlipH = false; + bool bFlipV = false; + Reference<XPropertySet> xShapeProps(xShape, UNO_QUERY); + if (xShapeProps.is() && xShapeProps->getPropertySetInfo() + && xShapeProps->getPropertySetInfo()->hasPropertyByName("CustomShapeGeometry")) + { + Sequence<PropertyValue> aGeometrySeq; + xShapeProps->getPropertyValue("CustomShapeGeometry") >>= aGeometrySeq; + for (int i = 0; i < aGeometrySeq.getLength(); i++) + { + const PropertyValue& rProp = aGeometrySeq[i]; + if (rProp.Name == "MirroredX") + rProp.Value >>= bFlipH; + + if (rProp.Name == "MirroredY") + rProp.Value >>= bFlipV; + } + } + + if ((!bFlipH && !bFlipV) || (bFlipH && bFlipV)) + { + // change id of the bounding box (1 <-> 3) + if (nGluePointId == 1) + nGluePointId = 3; // Right + else if (nGluePointId == 3) + nGluePointId = 1; // Left + } + } + + return nGluePointId; +} + +ShapeExport& ShapeExport::WriteConnectorShape( const Reference< XShape >& xShape ) +{ + bool bFlipH = false; + bool bFlipV = false; + sal_Int32 nAngle = 0; + sal_Int32 nStartGlueId = 0; + sal_Int32 nEndGlueId = 0; + + SAL_INFO("oox.shape", "write connector shape"); + + FSHelperPtr pFS = GetFS(); + + OUString sGeometry; + std::vector<std::pair<sal_Int32, sal_Int32>> aAdjustValueList; + Reference< XPropertySet > rXPropSet( xShape, UNO_QUERY ); + Reference< XPropertyState > rXPropState( xShape, UNO_QUERY ); + awt::Point aStartPoint, aEndPoint; + Reference< XShape > rXShapeA; + Reference< XShape > rXShapeB; + PropertyState eState; + ConnectorType eConnectorType = ConnectorType_STANDARD; + if (GetProperty(rXPropSet, "EdgeKind")) + mAny >>= eConnectorType; + + switch( eConnectorType ) { + case ConnectorType_CURVE: + sGeometry = "curvedConnector"; + break; + case ConnectorType_LINES: + case ConnectorType_STANDARD: + sGeometry = "bentConnector"; + break; + default: + case ConnectorType_LINE: + sGeometry = "straightConnector1"; + break; + } + + if (GetPropertyAndState( rXPropSet, rXPropState, "EdgeStartPoint", eState ) && eState == beans::PropertyState_DIRECT_VALUE ) + { + mAny >>= aStartPoint; + if (GetPropertyAndState( rXPropSet, rXPropState, "EdgeEndPoint", eState ) && eState == beans::PropertyState_DIRECT_VALUE ) + mAny >>= aEndPoint; + } + if (GetProperty(rXPropSet, "EdgeStartConnection")) + mAny >>= rXShapeA; + if (GetProperty(rXPropSet, "EdgeEndConnection")) + mAny >>= rXShapeB; + + if (GetProperty(rXPropSet, "StartGluePointIndex")) + mAny >>= nStartGlueId; + if (nStartGlueId != -1) + nStartGlueId = lcl_GetGluePointId(rXShapeA, nStartGlueId); + + if (GetProperty(rXPropSet, "EndGluePointIndex")) + mAny >>= nEndGlueId; + if (nEndGlueId != -1) + nEndGlueId = lcl_GetGluePointId(rXShapeB, nEndGlueId); + + // Position is relative to group in Word, but relative to anchor of group in API. + if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes && m_xParent.is()) + { + awt::Point aParentPos = m_xParent->getPosition(); + aStartPoint.X -= aParentPos.X; + aStartPoint.Y -= aParentPos.Y; + aEndPoint.X -= aParentPos.X; + aEndPoint.Y -= aParentPos.Y; + } + EscherConnectorListEntry aConnectorEntry( xShape, aStartPoint, rXShapeA, aEndPoint, rXShapeB ); + + if (eConnectorType != ConnectorType_LINE) + { + tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(xShape); + if (aPolyPolygon.Count() > 0) + { + tools::Polygon aPoly = aPolyPolygon.GetObject(0); + lcl_GetConnectorAdjustValue(xShape, aPoly, eConnectorType, aAdjustValueList); + nAngle = lcl_GetAngle(aPoly); + lcl_FlipHFlipV(aPoly, nAngle, bFlipH, bFlipV); + if (nAngle) + { + Point center((aEndPoint.X + aStartPoint.X) / 2, (aEndPoint.Y + aStartPoint.Y) / 2); + lcl_Rotate(nAngle, center, aStartPoint); + lcl_Rotate(nAngle, center, aEndPoint); + nAngle *= 60000; + } + sGeometry = sGeometry + OUString::number(aAdjustValueList.size() + 2); + } + } + + tools::Rectangle aRect( Point( aStartPoint.X, aStartPoint.Y ), Point( aEndPoint.X, aEndPoint.Y ) ); + if( aRect.getOpenWidth() < 0 ) { + aRect.SetLeft(aEndPoint.X); + aRect.setWidth( aStartPoint.X - aEndPoint.X ); + if (eConnectorType == ConnectorType_LINE) + bFlipH = true; + } + + if( aRect.getOpenHeight() < 0 ) { + aRect.SetTop(aEndPoint.Y); + aRect.setHeight( aStartPoint.Y - aEndPoint.Y ); + if (eConnectorType == ConnectorType_LINE) + bFlipV = true; + } + + // tdf#99810 connector shape (cxnSp) is not valid with namespace 'wps' + const auto nShapeNode = (mnXmlNamespace == XML_wps ? XML_wsp : XML_cxnSp); + pFS->startElementNS(mnXmlNamespace, nShapeNode); + + if (mnXmlNamespace == XML_wps) + { + // non visual connector shape drawing properties + pFS->singleElementNS(mnXmlNamespace, XML_cNvCnPr); + } + else + { + // non visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_nvCxnSpPr); + pFS->startElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + AddExtLst(pFS, rXPropSet); + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + // non visual connector shape drawing properties + pFS->startElementNS(mnXmlNamespace, XML_cNvCxnSpPr); + + if (GetShapeID(rXShapeA) == -1) + GetNewShapeID(rXShapeA); + if (GetShapeID(rXShapeB) == -1) + GetNewShapeID(rXShapeB); + WriteConnectorConnections(nStartGlueId, nEndGlueId, GetShapeID(rXShapeA), GetShapeID(rXShapeB)); + pFS->endElementNS(mnXmlNamespace, XML_cNvCxnSpPr); + if (GetDocumentType() == DOCUMENT_PPTX) + pFS->singleElementNS(mnXmlNamespace, XML_nvPr); + pFS->endElementNS(mnXmlNamespace, XML_nvCxnSpPr); + } + + // visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_spPr); + WriteTransformation( xShape, aRect, XML_a, bFlipH, bFlipV, nAngle ); + // TODO: write adjustments (ppt export doesn't work well there either) + WritePresetShape( sGeometry.toUtf8(), aAdjustValueList); + Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY ); + if( xShapeProps.is() ) + WriteOutline( xShapeProps ); + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + // connector shape (cxnSp) cannot contain text (txBody) (according to schema) + if( nShapeNode != XML_cxnSp ) + { + // write text + WriteTextBox( xShape, mnXmlNamespace ); + } + + pFS->endElementNS(mnXmlNamespace, nShapeNode); + + return *this; +} + +ShapeExport& ShapeExport::WriteLineShape( const Reference< XShape >& xShape ) +{ + bool bFlipH = false; + bool bFlipV = false; + + SAL_INFO("oox.shape", "write line shape"); + + FSHelperPtr pFS = GetFS(); + + pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp)); + + tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon( xShape ); + if( aPolyPolygon.Count() == 1 && aPolyPolygon[ 0 ].GetSize() == 2) + { + const tools::Polygon& rPoly = aPolyPolygon[ 0 ]; + + bFlipH = ( rPoly[ 0 ].X() > rPoly[ 1 ].X() ); + bFlipV = ( rPoly[ 0 ].Y() > rPoly[ 1 ].Y() ); + } + + Reference<XPropertySet> const xShapeProps(xShape, UNO_QUERY); + // non visual shape properties + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + pFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + pFS->startElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + AddExtLst(pFS, xShapeProps); + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + } + pFS->singleElementNS( mnXmlNamespace, XML_cNvSpPr ); + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + WriteNonVisualProperties( xShape ); + pFS->endElementNS( mnXmlNamespace, XML_nvSpPr ); + } + + // visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_spPr); + WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV, true); + WritePresetShape( "line"_ostr ); + if( xShapeProps.is() ) + WriteOutline( xShapeProps ); + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + //write style + pFS->startElementNS(mnXmlNamespace, XML_style); + WriteShapeStyle( xShapeProps ); + pFS->endElementNS( mnXmlNamespace, XML_style ); + + // write text + WriteTextBox( xShape, mnXmlNamespace ); + + pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) ); + + return *this; +} + +ShapeExport& ShapeExport::WriteNonVisualDrawingProperties( const Reference< XShape >& xShape, const char* pName ) +{ + FSHelperPtr pFS = GetFS(); + + Reference<XPropertySet> const xShapeProps(xShape, UNO_QUERY); + pFS->startElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, pName ); + AddExtLst(pFS, xShapeProps); + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + + return *this; +} + +ShapeExport& ShapeExport::WriteNonVisualProperties( const Reference< XShape >& ) +{ + // Override to generate //nvPr elements. + return *this; +} + +ShapeExport& ShapeExport::WriteRectangleShape( const Reference< XShape >& xShape ) +{ + SAL_INFO("oox.shape", "write rectangle shape"); + + FSHelperPtr pFS = GetFS(); + + pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp)); + + sal_Int32 nRadius = 0; + + Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY ); + if( xShapeProps.is() ) + { + xShapeProps->getPropertyValue( "CornerRadius" ) >>= nRadius; + } + + if( nRadius ) + { + nRadius = MapSize( awt::Size( nRadius, 0 ) ).Width; + } + //TODO: use nRadius value more precisely than just deciding whether to use + // "rect" or "roundRect" preset shape below + + // non visual shape properties + if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes) + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr); + pFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + pFS->startElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + AddExtLst(pFS, xShapeProps); + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr); + WriteNonVisualProperties( xShape ); + pFS->endElementNS( mnXmlNamespace, XML_nvSpPr ); + + // visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_spPr); + WriteShapeTransformation( xShape, XML_a ); + WritePresetShape( nRadius == 0 ? "rect" : "roundRect" ); + Reference< XPropertySet > xProps( xShape, UNO_QUERY ); + if( xProps.is() ) + { + WriteFill( xProps ); + WriteOutline( xProps ); + } + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + // write text + WriteTextBox( xShape, mnXmlNamespace ); + + pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) ); + + return *this; +} + + +typedef ShapeExport& (ShapeExport::*ShapeConverter)( const Reference< XShape >& ); +typedef std::unordered_map< const char*, ShapeConverter, rtl::CStringHash, rtl::CStringEqual> NameToConvertMapType; + +namespace +{ + +constexpr auto constMap = frozen::make_unordered_map<std::u16string_view, ShapeConverter>( +{ + { u"com.sun.star.drawing.CaptionShape", &ShapeExport::WriteTextShape }, + { u"com.sun.star.drawing.ClosedBezierShape", &ShapeExport::WriteClosedPolyPolygonShape }, + { u"com.sun.star.drawing.ConnectorShape", &ShapeExport::WriteConnectorShape }, + { u"com.sun.star.drawing.CustomShape", &ShapeExport::WriteCustomShape }, + { u"com.sun.star.drawing.EllipseShape", &ShapeExport::WriteEllipseShape }, + { u"com.sun.star.drawing.GraphicObjectShape", &ShapeExport::WriteGraphicObjectShape }, + { u"com.sun.star.drawing.LineShape", &ShapeExport::WriteLineShape }, + { u"com.sun.star.drawing.MediaShape", &ShapeExport::WriteGraphicObjectShape }, + { u"com.sun.star.drawing.OpenBezierShape", &ShapeExport::WriteOpenPolyPolygonShape }, + { u"com.sun.star.drawing.PolyPolygonShape", &ShapeExport::WriteClosedPolyPolygonShape }, + { u"com.sun.star.drawing.PolyLineShape", &ShapeExport::WriteOpenPolyPolygonShape }, + { u"com.sun.star.drawing.RectangleShape", &ShapeExport::WriteRectangleShape }, + { u"com.sun.star.drawing.OLE2Shape", &ShapeExport::WriteOLE2Shape }, + { u"com.sun.star.drawing.TableShape", &ShapeExport::WriteTableShape }, + { u"com.sun.star.drawing.TextShape", &ShapeExport::WriteTextShape }, + { u"com.sun.star.drawing.GroupShape", &ShapeExport::WriteGroupShape }, + { u"com.sun.star.presentation.GraphicObjectShape", &ShapeExport::WriteGraphicObjectShape }, + { u"com.sun.star.presentation.MediaShape", &ShapeExport::WriteGraphicObjectShape }, + { u"com.sun.star.presentation.ChartShape", &ShapeExport::WriteOLE2Shape }, + { u"com.sun.star.presentation.OLE2Shape", &ShapeExport::WriteOLE2Shape }, + { u"com.sun.star.presentation.TableShape", &ShapeExport::WriteTableShape }, + { u"com.sun.star.presentation.TextShape", &ShapeExport::WriteTextShape }, + { u"com.sun.star.presentation.DateTimeShape", &ShapeExport::WriteTextShape }, + { u"com.sun.star.presentation.FooterShape", &ShapeExport::WriteTextShape }, + { u"com.sun.star.presentation.HeaderShape", &ShapeExport::WriteTextShape }, + { u"com.sun.star.presentation.NotesShape", &ShapeExport::WriteTextShape }, + { u"com.sun.star.presentation.OutlinerShape", &ShapeExport::WriteTextShape }, + { u"com.sun.star.presentation.SlideNumberShape", &ShapeExport::WriteTextShape }, + { u"com.sun.star.presentation.TitleTextShape", &ShapeExport::WriteTextShape }, +}); + +} // end anonymous namespace + +ShapeExport& ShapeExport::WriteShape( const Reference< XShape >& xShape ) +{ + if (!xShape) + throw lang::IllegalArgumentException(); + + OUString sShapeType = xShape->getShapeType(); + SAL_INFO("oox.shape", "write shape: " << sShapeType); + auto aConverterIterator = constMap.find(sShapeType); + if (aConverterIterator == constMap.end()) + { + SAL_INFO("oox.shape", "unknown shape"); + return WriteUnknownShape( xShape ); + } + + if (GetDocumentType() == DOCUMENT_PPTX) + { + Reference< XPropertySet > xShapeProperties(xShape, UNO_QUERY); + if (xShapeProperties && xShapeProperties->getPropertySetInfo() + && xShapeProperties->getPropertySetInfo()->hasPropertyByName("IsPresentationObject") + && xShapeProperties->getPropertyValue("IsPresentationObject").hasValue()) + mbPlaceholder = xShapeProperties->getPropertyValue("IsPresentationObject").get<bool>(); + } + + (this->*(aConverterIterator->second))(xShape); + + return *this; +} + +static bool lcl_isTextBox(const Reference<XInterface>& xIface) +{ + uno::Reference<beans::XPropertySet> xPropertySet(xIface, uno::UNO_QUERY); + if (!xPropertySet.is()) + return false; + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); + if (!xPropertySetInfo->hasPropertyByName("TextBox")) + return false; + css::uno::Any aTextBox(xPropertySet->getPropertyValue("TextBox")); + if (!aTextBox.hasValue()) + return false; + return aTextBox.get<bool>(); +} + +ShapeExport& ShapeExport::WriteTextBox( const Reference< XInterface >& xIface, sal_Int32 nXmlNamespace, bool bWritePropertiesAsLstStyles ) +{ + // In case this shape has an associated textbox, then export that, and we're done. + if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes && GetTextExport()) + { + if (lcl_isTextBox(xIface)) + { + GetTextExport()->WriteTextBox(uno::Reference<drawing::XShape>(xIface, uno::UNO_QUERY_THROW)); + WriteText( xIface, /*bBodyPr=*/true, /*bText=*/false, /*nXmlNamespace=*/nXmlNamespace ); + return *this; + } + } + + Reference< XText > xXText( xIface, UNO_QUERY ); + if( (NonEmptyText( xIface ) || GetDocumentType() == DOCUMENT_PPTX) + && xXText.is() ) + { + FSHelperPtr pFS = GetFS(); + + pFS->startElementNS(nXmlNamespace, + (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_txBody : XML_txbx)); + WriteText(xIface, /*bBodyPr=*/(GetDocumentType() != DOCUMENT_DOCX || mbUserShapes), /*bText=*/true, + /*nXmlNamespace=*/0, /*bWritePropertiesAsLstStyles=*/bWritePropertiesAsLstStyles); + pFS->endElementNS( nXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_txBody : XML_txbx) ); + if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes) + WriteText( xIface, /*bBodyPr=*/true, /*bText=*/false, /*nXmlNamespace=*/nXmlNamespace ); + } + else if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes) + mpFS->singleElementNS(nXmlNamespace, XML_bodyPr); + + return *this; +} + +void ShapeExport::WriteTable( const Reference< XShape >& rXShape ) +{ + Reference< XTable > xTable; + Reference< XPropertySet > xPropSet( rXShape, UNO_QUERY ); + + mpFS->startElementNS(XML_a, XML_graphic); + mpFS->startElementNS(XML_a, XML_graphicData, + XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/table"); + + if ( xPropSet.is() && ( xPropSet->getPropertyValue( "Model" ) >>= xTable ) ) + { + mpFS->startElementNS(XML_a, XML_tbl); + mpFS->startElementNS(XML_a, XML_tblPr); + WriteShapeEffects(xPropSet); + mpFS->endElementNS(XML_a, XML_tblPr); + + Reference< container::XIndexAccess > xColumns( xTable->getColumns(), UNO_QUERY_THROW ); + Reference< container::XIndexAccess > xRows( xTable->getRows(), UNO_QUERY_THROW ); + sal_uInt16 nRowCount = static_cast< sal_uInt16 >( xRows->getCount() ); + sal_uInt16 nColumnCount = static_cast< sal_uInt16 >( xColumns->getCount() ); + + mpFS->startElementNS(XML_a, XML_tblGrid); + + for ( sal_Int32 x = 0; x < nColumnCount; x++ ) + { + Reference< XPropertySet > xColPropSet( xColumns->getByIndex( x ), UNO_QUERY_THROW ); + sal_Int32 nWidth(0); + xColPropSet->getPropertyValue( "Width" ) >>= nWidth; + + mpFS->singleElementNS(XML_a, XML_gridCol, + XML_w, OString::number(oox::drawingml::convertHmmToEmu(nWidth))); + } + + mpFS->endElementNS( XML_a, XML_tblGrid ); + + // map for holding the transpose index of the merged cells and pair<parentTransposeIndex, parentCell> + typedef std::unordered_map<sal_Int32, std::pair<sal_Int32, Reference< XMergeableCell> > > transposeTableMap; + transposeTableMap mergedCellMap; + + for( sal_Int32 nRow = 0; nRow < nRowCount; nRow++ ) + { + Reference< XPropertySet > xRowPropSet( xRows->getByIndex( nRow ), UNO_QUERY_THROW ); + sal_Int32 nRowHeight(0); + + xRowPropSet->getPropertyValue( "Height" ) >>= nRowHeight; + + mpFS->startElementNS(XML_a, XML_tr, + XML_h, OString::number(oox::drawingml::convertHmmToEmu(nRowHeight))); + for( sal_Int32 nColumn = 0; nColumn < nColumnCount; nColumn++ ) + { + Reference< XMergeableCell > xCell( xTable->getCellByPosition( nColumn, nRow ), + UNO_QUERY_THROW ); + sal_Int32 transposedIndexofCell = (nRow * nColumnCount) + nColumn; + + //assume we will open a cell, set to false below if we won't + bool bCellOpened = true; + + if(xCell->getColumnSpan() > 1 && xCell->getRowSpan() > 1) + { + // having both : horizontal and vertical merge + mpFS->startElementNS(XML_a, XML_tc, + XML_gridSpan, OString::number(xCell->getColumnSpan()), + XML_rowSpan, OString::number(xCell->getRowSpan())); + // since, XMergeableCell doesn't have the information about + // cell having hMerge or vMerge. + // So, Populating the merged cell map in-order to use it to + // decide the attribute for the individual cell. + for(sal_Int32 columnIndex = nColumn; columnIndex < nColumn+xCell->getColumnSpan(); ++columnIndex) + { + for(sal_Int32 rowIndex = nRow; rowIndex < nRow+xCell->getRowSpan(); ++rowIndex) + { + sal_Int32 transposeIndexForMergeCell = + (rowIndex * nColumnCount) + columnIndex; + mergedCellMap[transposeIndexForMergeCell] = + std::make_pair(transposedIndexofCell, xCell); + } + } + + } + else if(xCell->getColumnSpan() > 1) + { + // having : horizontal merge + mpFS->startElementNS(XML_a, XML_tc, + XML_gridSpan, OString::number(xCell->getColumnSpan())); + for(sal_Int32 columnIndex = nColumn; columnIndex < nColumn + xCell->getColumnSpan(); ++columnIndex) { + sal_Int32 transposeIndexForMergeCell = (nRow*nColumnCount) + columnIndex; + mergedCellMap[transposeIndexForMergeCell] = + std::make_pair(transposedIndexofCell, xCell); + } + } + else if(xCell->getRowSpan() > 1) + { + // having : vertical merge + mpFS->startElementNS(XML_a, XML_tc, + XML_rowSpan, OString::number(xCell->getRowSpan())); + + for(sal_Int32 rowIndex = nRow; rowIndex < nRow + xCell->getRowSpan(); ++rowIndex) { + sal_Int32 transposeIndexForMergeCell = (rowIndex*nColumnCount) + nColumn; + mergedCellMap[transposeIndexForMergeCell] = + std::make_pair(transposedIndexofCell, xCell); + } + } + else + { + // now, the cell can be an independent cell or + // it can be a cell which is been merged to some parent cell + if(!xCell->isMerged()) + { + // independent cell + mpFS->startElementNS(XML_a, XML_tc); + } + else + { + // it a merged cell to some parent cell + // find the parent cell for the current cell at hand + transposeTableMap::iterator it = mergedCellMap.find(transposedIndexofCell); + if(it != mergedCellMap.end()) + { + sal_Int32 transposeIndexOfParent = it->second.first; + Reference< XMergeableCell > parentCell = it->second.second; + // finding the row and column index for the parent cell from transposed index + sal_Int32 parentColumnIndex = transposeIndexOfParent % nColumnCount; + sal_Int32 parentRowIndex = transposeIndexOfParent / nColumnCount; + if(nColumn == parentColumnIndex) + { + // the cell is vertical merge and it might have gridspan + if(parentCell->getColumnSpan() > 1) + { + // vMerge and has gridSpan + mpFS->startElementNS(XML_a, XML_tc, + XML_vMerge, OString::number(1), + XML_gridSpan, OString::number(xCell->getColumnSpan())); + } + else + { + // only vMerge + mpFS->startElementNS(XML_a, XML_tc, + XML_vMerge, OString::number(1)); + } + } + else if(nRow == parentRowIndex) + { + // the cell is horizontal merge and it might have rowspan + if(parentCell->getRowSpan() > 1) + { + // hMerge and has rowspan + mpFS->startElementNS(XML_a, XML_tc, + XML_hMerge, OString::number(1), + XML_rowSpan, OString::number(xCell->getRowSpan())); + } + else + { + // only hMerge + mpFS->startElementNS(XML_a, XML_tc, + XML_hMerge, OString::number(1)); + } + } + else + { + // has hMerge and vMerge + mpFS->startElementNS(XML_a, XML_tc, + XML_vMerge, OString::number(1), + XML_hMerge, OString::number(1)); + } + } + else + bCellOpened = false; + } + } + + if (bCellOpened) + { + WriteTextBox( xCell, XML_a ); + + Reference< XPropertySet > xCellPropSet(xCell, UNO_QUERY_THROW); + WriteTableCellProperties(xCellPropSet); + + mpFS->endElementNS( XML_a, XML_tc ); + } + } + + mpFS->endElementNS( XML_a, XML_tr ); + } + + mpFS->endElementNS( XML_a, XML_tbl ); + } + + mpFS->endElementNS( XML_a, XML_graphicData ); + mpFS->endElementNS( XML_a, XML_graphic ); +} + +void ShapeExport::WriteTableCellProperties(const Reference< XPropertySet>& xCellPropSet) +{ + sal_Int32 nLeftMargin(0), nRightMargin(0); + TextVerticalAdjust eVerticalAlignment; + const char* sVerticalAlignment; + + Any aLeftMargin = xCellPropSet->getPropertyValue("TextLeftDistance"); + aLeftMargin >>= nLeftMargin; + + Any aRightMargin = xCellPropSet->getPropertyValue("TextRightDistance"); + aRightMargin >>= nRightMargin; + + Any aVerticalAlignment = xCellPropSet->getPropertyValue("TextVerticalAdjust"); + aVerticalAlignment >>= eVerticalAlignment; + sVerticalAlignment = GetTextVerticalAdjust(eVerticalAlignment); + + sal_Int32 nRotateAngle = 0; + Any aRotateAngle = xCellPropSet->getPropertyValue("RotateAngle"); + aRotateAngle >>= nRotateAngle; + std::optional<OString> aTextVerticalValue = GetTextVerticalType(nRotateAngle); + + Sequence<PropertyValue> aGrabBag; + if( !aTextVerticalValue && + (xCellPropSet->getPropertyValue("CellInteropGrabBag") >>= aGrabBag) ) + { + for (auto const& rIt : std::as_const(aGrabBag)) + { + if (rIt.Name == "mso-tcPr-vert-value") + { + aTextVerticalValue = rIt.Value.get<OUString>().toUtf8(); + break; + } + } + } + + mpFS->startElementNS(XML_a, XML_tcPr, XML_anchor, sVerticalAlignment, + XML_vert, aTextVerticalValue, + XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeftMargin)), nLeftMargin > 0), + XML_marR, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nRightMargin)), nRightMargin > 0)); + + // Write background fill for table cell. + // TODO + // tcW : Table cell width + WriteTableCellBorders(xCellPropSet); + DrawingML::WriteFill(xCellPropSet); + mpFS->endElementNS( XML_a, XML_tcPr ); +} + +void ShapeExport::WriteBorderLine(const sal_Int32 XML_line, const BorderLine2& rBorderLine) +{ +// While importing the table cell border line width, it converts EMU->Hmm then divided result by 2. +// To get original value of LineWidth need to multiple by 2. + sal_Int32 nBorderWidth = rBorderLine.LineWidth; + nBorderWidth *= 2; + nBorderWidth = oox::drawingml::convertHmmToEmu( nBorderWidth ); + + if ( nBorderWidth > 0 ) + { + mpFS->startElementNS(XML_a, XML_line, XML_w, OString::number(nBorderWidth)); + if ( rBorderLine.Color == sal_Int32( COL_AUTO ) ) + mpFS->singleElementNS(XML_a, XML_noFill); + else + DrawingML::WriteSolidFill( ::Color(ColorTransparency, rBorderLine.Color) ); + + OUString sBorderStyle; + sal_Int16 nStyle = rBorderLine.LineStyle; + mAny.setValue(&nStyle, cppu::UnoType<sal_Int16>::get()); + switch (*o3tl::doAccess<sal_Int16>(mAny)) + { + case ::table::BorderLineStyle::SOLID: + sBorderStyle = "solid"; + break; + case ::table::BorderLineStyle::DOTTED: + sBorderStyle = "dot"; + break; + case ::table::BorderLineStyle::DASHED: + sBorderStyle = "dash"; + break; + case ::table::BorderLineStyle::DASH_DOT: + sBorderStyle = "dashDot"; + break; + case ::table::BorderLineStyle::DASH_DOT_DOT: + sBorderStyle = "sysDashDotDot"; + break; + } + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, sBorderStyle); + mpFS->endElementNS(XML_a, XML_line); + } + else if( nBorderWidth == 0) + { + mpFS->startElementNS(XML_a, XML_line); + mpFS->singleElementNS(XML_a, XML_noFill); + mpFS->endElementNS( XML_a, XML_line ); + } +} + +void ShapeExport::WriteTableCellBorders(const Reference< XPropertySet>& xCellPropSet) +{ + BorderLine2 aBorderLine; + +// lnL - Left Border Line Properties of table cell + xCellPropSet->getPropertyValue("LeftBorder") >>= aBorderLine; + WriteBorderLine( XML_lnL, aBorderLine ); + +// lnR - Right Border Line Properties of table cell + xCellPropSet->getPropertyValue("RightBorder") >>= aBorderLine; + WriteBorderLine( XML_lnR, aBorderLine ); + +// lnT - Top Border Line Properties of table cell + xCellPropSet->getPropertyValue("TopBorder") >>= aBorderLine; + WriteBorderLine( XML_lnT, aBorderLine ); + +// lnB - Bottom Border Line Properties of table cell + xCellPropSet->getPropertyValue("BottomBorder") >>= aBorderLine; + WriteBorderLine( XML_lnB, aBorderLine ); +} + +ShapeExport& ShapeExport::WriteTableShape( const Reference< XShape >& xShape ) +{ + FSHelperPtr pFS = GetFS(); + + pFS->startElementNS(mnXmlNamespace, XML_graphicFrame); + + pFS->startElementNS(mnXmlNamespace, XML_nvGraphicFramePr); + + Reference<XPropertySet> const xShapeProps(xShape, UNO_QUERY); + pFS->startElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + AddExtLst(pFS, xShapeProps); + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + + pFS->singleElementNS(mnXmlNamespace, XML_cNvGraphicFramePr); + + if( GetDocumentType() == DOCUMENT_PPTX ) + pFS->singleElementNS(mnXmlNamespace, XML_nvPr); + pFS->endElementNS( mnXmlNamespace, XML_nvGraphicFramePr ); + + WriteShapeTransformation( xShape, mnXmlNamespace ); + WriteTable( xShape ); + + pFS->endElementNS( mnXmlNamespace, XML_graphicFrame ); + + return *this; +} + +ShapeExport& ShapeExport::WriteTextShape( const Reference< XShape >& xShape ) +{ + FSHelperPtr pFS = GetFS(); + Reference<XPropertySet> xShapeProps(xShape, UNO_QUERY); + + pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp)); + + // non visual shape properties + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + pFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + pFS->startElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + OUString sURL; + if (GetProperty(xShapeProps, "URL")) + mAny >>= sURL; + + if (!sURL.isEmpty()) + { + OUString sRelId = mpFB->addRelation(mpFS->getOutputStream(), + oox::getRelationship(Relationship::HYPERLINK), + mpURLTransformer->getTransformedString(sURL), + mpURLTransformer->isExternalURL(sURL)); + + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); + } + AddExtLst(pFS, xShapeProps); + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + } + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr, XML_txBox, "1"); + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + WriteNonVisualProperties( xShape ); + pFS->endElementNS( mnXmlNamespace, XML_nvSpPr ); + } + + // visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_spPr); + WriteShapeTransformation( xShape, XML_a ); + WritePresetShape( "rect"_ostr ); + uno::Reference<beans::XPropertySet> xPropertySet(xShape, UNO_QUERY); + if (!IsFontworkShape(xShapeProps)) // Fontwork needs fill and outline in run properties instead. + { + WriteBlipOrNormalFill(xPropertySet, "Graphic", xShape->getSize()); + WriteOutline(xPropertySet); + WriteShapeEffects(xPropertySet); + } + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + WriteTextBox( xShape, mnXmlNamespace ); + + pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) ); + + return *this; +} + +void ShapeExport::WriteMathShape(Reference<XShape> const& xShape) +{ + Reference<XPropertySet> const xPropSet(xShape, UNO_QUERY); + assert(xPropSet.is()); + Reference<XModel> xMathModel; + xPropSet->getPropertyValue("Model") >>= xMathModel; + assert(xMathModel.is()); + assert(GetDocumentType() != DOCUMENT_DOCX); // should be written in DocxAttributeOutput + SAL_WARN_IF(GetDocumentType() == DOCUMENT_XLSX, "oox.shape", "Math export to XLSX isn't tested, should it happen here?"); + + // ECMA standard does not actually allow oMath outside of + // WordProcessingML so write a MCE like PPT 2010 does + mpFS->startElementNS(XML_mc, XML_AlternateContent); + mpFS->startElementNS(XML_mc, XML_Choice, + FSNS(XML_xmlns, XML_a14), mpFB->getNamespaceURL(OOX_NS(a14)), + XML_Requires, "a14"); + mpFS->startElementNS(mnXmlNamespace, XML_sp); + mpFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + mpFS->startElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + AddExtLst(mpFS, xPropSet); + mpFS->endElementNS(mnXmlNamespace, XML_cNvPr); + 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"_ostr); + 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::FormulaImExportBase *const pMagic( + dynamic_cast<oox::FormulaImExportBase*>(xMathModel.get())); + assert(pMagic); + pMagic->writeFormulaOoxml(GetFS(), GetFB()->getVersion(), GetDocumentType(), + FormulaImExportBase::eFormulaAlign::INLINE); + + mpFS->endElementNS(XML_a14, XML_m); + mpFS->endElementNS(XML_a, XML_p); + mpFS->endElementNS(mnXmlNamespace, XML_txBody); + mpFS->endElementNS(mnXmlNamespace, XML_sp); + mpFS->endElementNS(XML_mc, XML_Choice); + mpFS->startElementNS(XML_mc, XML_Fallback); + // TODO: export bitmap shape as fallback + mpFS->endElementNS(XML_mc, XML_Fallback); + mpFS->endElementNS(XML_mc, XML_AlternateContent); +} + +ShapeExport& ShapeExport::WriteOLE2Shape( const Reference< XShape >& xShape ) +{ + Reference< XPropertySet > xPropSet( xShape, UNO_QUERY ); + if (!xPropSet.is()) + return *this; + + enum { CHART, MATH, OTHER } eType(OTHER); + OUString clsid; + xPropSet->getPropertyValue("CLSID") >>= clsid; + if (!clsid.isEmpty()) + { + SvGlobalName aClassID; + bool const isValid = aClassID.MakeId(clsid); + assert(isValid); (void)isValid; + if (SotExchange::IsChart(aClassID)) + eType = CHART; + else if (SotExchange::IsMath(aClassID)) + eType = MATH; + } + + if (CHART == eType) + { + Reference< XChartDocument > xChartDoc; + xPropSet->getPropertyValue("Model") >>= xChartDoc; + assert(xChartDoc.is()); + //export the chart +#if !ENABLE_WASM_STRIP_CHART + // WASM_CHART change + // TODO: With Chart extracted this cannot really happen since + // no Chart could've been added at all + ChartExport aChartExport( mnXmlNamespace, GetFS(), xChartDoc, GetFB(), GetDocumentType() ); + aChartExport.WriteChartObj( xShape, GetNewShapeID( xShape ), ++mnChartCount ); +#endif + return *this; + } + + if (MATH == eType) + { + WriteMathShape(xShape); + return *this; + } + + uno::Reference<embed::XEmbeddedObject> const xObj( + xPropSet->getPropertyValue("EmbeddedObject"), uno::UNO_QUERY); + + if (!xObj.is()) + { + SAL_WARN("oox.shape", "ShapeExport::WriteOLE2Shape: no object"); + + // tdf#152436 Export the preview graphic of the object if the object is missing. + SdrObject* pSdrOLE2(SdrObject::getSdrObjectFromXShape(xShape)); + if (auto pOle2Obj = dynamic_cast<SdrOle2Obj*>(pSdrOLE2)) + { + const Graphic* pGraphic = pOle2Obj->GetGraphic(); + if (pGraphic) + WriteGraphicObjectShapePart(xShape, pGraphic); + } + + return *this; + } + + uno::Sequence<beans::PropertyValue> grabBag; + OUString entryName; + try + { + uno::Reference<beans::XPropertySet> const xParent( + uno::Reference<container::XChild>(xObj, uno::UNO_QUERY_THROW)->getParent(), + uno::UNO_QUERY_THROW); + + xParent->getPropertyValue("InteropGrabBag") >>= grabBag; + + entryName = uno::Reference<embed::XEmbedPersist>(xObj, uno::UNO_QUERY_THROW)->getEntryName(); + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("oox.shape", "ShapeExport::WriteOLE2Shape"); + return *this; + } + + OUString progID; + + for (auto const& it : std::as_const(grabBag)) + { + if (it.Name == "EmbeddedObjects") + { + uno::Sequence<beans::PropertyValue> objects; + it.Value >>= objects; + for (auto const& object : std::as_const(objects)) + { + if (object.Name == entryName) + { + uno::Sequence<beans::PropertyValue> props; + object.Value >>= props; + for (auto const& prop : std::as_const(props)) + { + if (prop.Name == "ProgID") + { + prop.Value >>= progID; + break; + } + } + break; + } + } + break; + } + } + + OUString sMediaType; + OUString sRelationType; + OUString sSuffix; + const char * pProgID(nullptr); + OString anotherProgID; + + uno::Reference<io::XInputStream> const xInStream = + oox::GetOLEObjectStream( + mpFB->getComponentContext(), xObj, progID, + sMediaType, sRelationType, sSuffix, pProgID); + + OUString sURL; + OUString sRelId; + if (!xInStream.is()) + { + xPropSet->getPropertyValue("LinkURL") >>= sURL; + if (sURL.isEmpty()) + return *this; + + sRelId = mpFB->addRelation(mpFS->getOutputStream(), + oox::getRelationship(Relationship::OLEOBJECT), sURL, true); + } + else + { + if (!pProgID && !progID.isEmpty()) + { + anotherProgID = OUStringToOString(progID, RTL_TEXTENCODING_UTF8); + pProgID = anotherProgID.getStr(); + } + + assert(!sMediaType.isEmpty()); + assert(!sRelationType.isEmpty()); + assert(!sSuffix.isEmpty()); + + OUString sNumber = OUString::number(++m_nEmbeddedObjects); + OUString sFileName = u"embeddings/oleObject"_ustr + sNumber + u"."_ustr + sSuffix; + OUString sFilePath = GetComponentDir() + u"/"_ustr + sFileName; + uno::Reference<io::XOutputStream> const xOutStream(mpFB->openFragmentStream(sFilePath, 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"); + } + + sRelId = mpFB->addRelation( + mpFS->getOutputStream(), sRelationType, + Concat2View(GetRelationCompPrefix() + sFileName)); + } + + sal_Int64 nAspect; + bool bShowAsIcon = (xPropSet->getPropertyValue("Aspect") >>= nAspect) + && nAspect == embed::Aspects::MSOLE_ICON; + + mpFS->startElementNS(mnXmlNamespace, XML_graphicFrame); + + mpFS->startElementNS(mnXmlNamespace, XML_nvGraphicFramePr); + + mpFS->startElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + AddExtLst(mpFS, xPropSet); + mpFS->endElementNS(mnXmlNamespace, XML_cNvPr); + + 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_showAsIcon, sax_fastparser::UseIf("1", bShowAsIcon), + XML_progId, pProgID, + FSNS(XML_r, XML_id), sRelId, + XML_spid, "" ); + } + else + { + mpFS->startElementNS( mnXmlNamespace, XML_oleObj, +//? XML_name, "Document", + XML_showAsIcon, sax_fastparser::UseIf("1", bShowAsIcon), + FSNS(XML_r, XML_id), sRelId, + // The spec says that this is a required attribute, but PowerPoint can only handle an empty value. + XML_spid, "" ); + } + + if (sURL.isEmpty()) + mpFS->singleElementNS(mnXmlNamespace, XML_embed); + else + mpFS->singleElementNS(mnXmlNamespace, XML_link, XML_updateAutomatic, "1"); + + // pic element + SdrObject* pSdrOLE2(SdrObject::getSdrObjectFromXShape(xShape)); + // The spec doesn't allow <p:pic> here, but PowerPoint requires it. + bool const bEcma = mpFB->getVersion() == oox::core::ECMA_376_1ST_EDITION; + if (bEcma) + if (auto pOle2Obj = dynamic_cast<SdrOle2Obj*>(pSdrOLE2)) + { + const Graphic* pGraphic = pOle2Obj->GetGraphic(); + if (pGraphic) + WriteGraphicObjectShapePart( xShape, pGraphic ); + } + + mpFS->endElementNS( mnXmlNamespace, XML_oleObj ); + + mpFS->endElementNS( XML_a, XML_graphicData ); + mpFS->endElementNS( XML_a, XML_graphic ); + + mpFS->endElementNS( mnXmlNamespace, XML_graphicFrame ); + + return *this; +} + +ShapeExport& ShapeExport::WriteUnknownShape( const Reference< XShape >& ) +{ + // Override this method to do something useful. + return *this; +} + +sal_Int32 ShapeExport::GetNewShapeID( const Reference< XShape >& rXShape ) +{ + return GetNewShapeID( rXShape, GetFB() ); +} + +sal_Int32 ShapeExport::GetNewShapeID( const Reference< XShape >& rXShape, XmlFilterBase* pFB ) +{ + if( !rXShape.is() ) + return -1; + + sal_Int32 nID = pFB->GetUniqueId(); + + (*mpShapeMap)[ rXShape ] = nID; + + return nID; +} + +sal_Int32 ShapeExport::GetShapeID( const Reference< XShape >& rXShape ) +{ + return GetShapeID( rXShape, mpShapeMap ); +} + +sal_Int32 ShapeExport::GetShapeID( const Reference< XShape >& rXShape, ShapeHashMap* pShapeMap ) +{ + if( !rXShape.is() ) + return -1; + + ShapeHashMap::const_iterator aIter = pShapeMap->find( rXShape ); + + if( aIter == pShapeMap->end() ) + return -1; + + return aIter->second; +} + +OUString ShapeExport::GetShapeName(const Reference<XShape>& xShape) +{ + Reference<XPropertySet> rXPropSet(xShape, UNO_QUERY); + + // Empty name keeps the object unnamed. + OUString sName; + + if (GetProperty(rXPropSet, "Name")) + mAny >>= sName; + return sName; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/export/vmlexport.cxx b/oox/source/export/vmlexport.cxx new file mode 100644 index 0000000000..1090b0857f --- /dev/null +++ b/oox/source/export/vmlexport.cxx @@ -0,0 +1,1613 @@ +/* -*- 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/msdffdef.hxx> +#include <svx/svdotext.hxx> +#include <svx/svdograf.hxx> +#include <svx/sdmetitm.hxx> +#include <utility> +#include <vcl/cvtgrf.hxx> +#include <filter/msfilter/msdffimp.hxx> +#include <filter/msfilter/util.hxx> +#include <filter/msfilter/escherex.hxx> +#include <o3tl/string_view.hxx> +#include <drawingml/fontworkhelpers.hxx> + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/text/HoriOrientation.hpp> +#include <com/sun/star/text/VertOrientation.hpp> +#include <com/sun/star/text/RelOrientation.hpp> +#include <com/sun/star/text/WritingMode2.hpp> +#include <com/sun/star/text/XTextFrame.hpp> + +#include <cstdio> + +using namespace sax_fastparser; +using namespace oox::vml; +using namespace com::sun::star; + +const sal_Int32 Tag_Container = 44444; +const sal_Int32 Tag_Commit = 44445; + +VMLExport::VMLExport( ::sax_fastparser::FSHelperPtr pSerializer, VMLTextExport* pTextExport ) + : EscherEx( std::make_shared<EscherExGlobal>(), nullptr, /*bOOXML=*/true ) + , m_pSerializer(std::move( pSerializer )) + , m_pTextExport( pTextExport ) + , m_eHOri( 0 ) + , m_eVOri( 0 ) + , m_eHRel( 0 ) + , m_eVRel( 0 ) + , m_bInline( false ) + , m_pSdrObject( nullptr ) + , m_nShapeType( ESCHER_ShpInst_Nil ) + , m_nShapeFlags(ShapeFlag::NONE) + , m_ShapeStyle( 200 ) + , m_aShapeTypeWritten( ESCHER_ShpInst_COUNT ) + , m_bSkipwzName( false ) + , m_bUseHashMarkForType( false ) + , m_bOverrideShapeIdGeneration( false ) + , m_nShapeIDCounter( 0 ) +{ + mnGroupLevel = 1; +} + +void VMLExport::SetFS( const ::sax_fastparser::FSHelperPtr& pSerializer ) +{ + m_pSerializer = pSerializer; +} + +VMLExport::~VMLExport() +{ +} + +void VMLExport::OpenContainer( sal_uInt16 nEscherContainer, int nRecInstance ) +{ + EscherEx::OpenContainer( nEscherContainer, nRecInstance ); + + if ( nEscherContainer != ESCHER_SpContainer ) + return; + + // opening a shape container + SAL_WARN_IF(m_nShapeType != ESCHER_ShpInst_Nil, "oox.vml", "opening shape inside of a shape!"); + m_nShapeType = ESCHER_ShpInst_Nil; + m_pShapeAttrList = FastSerializerHelper::createAttrList(); + + m_ShapeStyle.setLength(0); + m_ShapeStyle.ensureCapacity(200); + + // postpone the output so that we are able to write even the elements + // that we learn inside Commit() + m_pSerializer->mark(Tag_Container); +} + +void VMLExport::CloseContainer() +{ + if ( mRecTypes.back() == ESCHER_SpContainer ) + { + // write the shape now when we have all the info + sal_Int32 nShapeElement = StartShape(); + + m_pSerializer->mergeTopMarks(Tag_Container); + + EndShape( nShapeElement ); + + // cleanup + m_nShapeType = ESCHER_ShpInst_Nil; + m_pShapeAttrList = nullptr; + } + + EscherEx::CloseContainer(); +} + +sal_uInt32 VMLExport::EnterGroup( const OUString& rShapeName, const tools::Rectangle* pRect ) +{ + sal_uInt32 nShapeId = GenerateShapeId(); + + OStringBuffer aStyle( 200 ); + rtl::Reference<FastAttributeList> pAttrList = FastSerializerHelper::createAttrList(); + + pAttrList->add( XML_id, ShapeIdString( nShapeId ) ); + + if ( rShapeName.getLength() ) + pAttrList->add( XML_alt, rShapeName ); + + bool rbAbsolutePos = true; + //editAs + OUString rEditAs = EscherEx::GetEditAs(); + if (!rEditAs.isEmpty()) + { + pAttrList->add(XML_editas, rEditAs); + rbAbsolutePos = false; + } + + // style + if ( pRect ) + AddRectangleDimensions( aStyle, *pRect, rbAbsolutePos ); + + if ( !aStyle.isEmpty() ) + pAttrList->add( XML_style, aStyle ); + + // coordorigin/coordsize + if ( pRect && ( mnGroupLevel == 1 ) ) + { + pAttrList->add( XML_coordorigin, + OString::number( pRect->Left() ) + "," + OString::number( pRect->Top() ) ); + + pAttrList->add( XML_coordsize, + OString::number( pRect->Right() - pRect->Left() ) + "," + + OString::number( pRect->Bottom() - pRect->Top() ) ); + } + + m_pSerializer->startElementNS( XML_v, XML_group, pAttrList ); + + mnGroupLevel++; + return nShapeId; +} + +void VMLExport::LeaveGroup() +{ + --mnGroupLevel; + m_pSerializer->endElementNS( XML_v, XML_group ); +} + +void VMLExport::AddShape( sal_uInt32 nShapeType, ShapeFlag nShapeFlags, sal_uInt32 nShapeId ) +{ + m_nShapeType = nShapeType; + m_nShapeFlags = nShapeFlags; + + m_sShapeId = ShapeIdString( nShapeId ); + if (m_sShapeId.startsWith("_x0000_")) + { + // xml_id must be set elsewhere. The id is critical for matching VBA macros etc, + // and the spid is critical to link to the shape number elsewhere. + m_pShapeAttrList->addNS( XML_o, XML_spid, m_sShapeId ); + } + else if (IsWaterMarkShape(m_pSdrObject->GetName())) + { + // Shape is a watermark object - keep the original shape's name + // because Microsoft detects if it is a watermark by the actual name + m_pShapeAttrList->add( XML_id, m_pSdrObject->GetName() ); + // also ('o:spid') + m_pShapeAttrList->addNS( XML_o, XML_spid, m_sShapeId ); + } + else + { + m_pShapeAttrList->add(XML_id, m_sShapeId); + } +} + +bool VMLExport::IsWaterMarkShape(std::u16string_view rStr) +{ + if (rStr.empty() ) return false; + + return o3tl::starts_with(rStr, u"PowerPlusWaterMarkObject") || o3tl::starts_with(rStr, u"WordPictureWatermark"); +} + +void VMLExport::OverrideShapeIDGen(bool bOverrideShapeIdGen, const OString& sShapeIDPrefix) +{ + m_bOverrideShapeIdGeneration = bOverrideShapeIdGen; + if(bOverrideShapeIdGen) + { + assert(!sShapeIDPrefix.isEmpty()); + m_sShapeIDPrefix = sShapeIDPrefix; + } + else + m_sShapeIDPrefix.clear(); +} + +static void impl_AddArrowHead( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, sal_uInt32 nValue ) +{ + if ( !pAttrList ) + return; + + const char *pArrowHead = nullptr; + switch ( nValue ) + { + case ESCHER_LineNoEnd: pArrowHead = "none"; break; + case ESCHER_LineArrowEnd: pArrowHead = "block"; break; + case ESCHER_LineArrowStealthEnd: pArrowHead = "classic"; break; + case ESCHER_LineArrowDiamondEnd: pArrowHead = "diamond"; break; + case ESCHER_LineArrowOvalEnd: pArrowHead = "oval"; break; + case ESCHER_LineArrowOpenEnd: pArrowHead = "open"; break; + } + + if ( pArrowHead ) + pAttrList->add( nElement, pArrowHead ); +} + +static void impl_AddArrowLength( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, sal_uInt32 nValue ) +{ + if ( !pAttrList ) + return; + + const char *pArrowLength = nullptr; + switch ( nValue ) + { + case ESCHER_LineShortArrow: pArrowLength = "short"; break; + case ESCHER_LineMediumLenArrow: pArrowLength = "medium"; break; + case ESCHER_LineLongArrow: pArrowLength = "long"; break; + } + + if ( pArrowLength ) + pAttrList->add( nElement, pArrowLength ); +} + +static void impl_AddArrowWidth( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, sal_uInt32 nValue ) +{ + if ( !pAttrList ) + return; + + const char *pArrowWidth = nullptr; + switch ( nValue ) + { + case ESCHER_LineNarrowArrow: pArrowWidth = "narrow"; break; + case ESCHER_LineMediumWidthArrow: pArrowWidth = "medium"; break; + case ESCHER_LineWideArrow: pArrowWidth = "wide"; break; + } + + if ( pArrowWidth ) + pAttrList->add( nElement, pArrowWidth ); +} + +static void impl_AddBool( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, bool bValue ) +{ + if ( !pAttrList ) + return; + + pAttrList->add( nElement, bValue? "t": "f" ); +} + +static void impl_AddColor( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, sal_uInt32 nColor ) +{ + SAL_WARN_IF( nColor & 0xFF000000 , "oox.vml" , "TODO: this is not a RGB value!"); + + if ( !pAttrList || ( nColor & 0xFF000000 ) ) + return; + + nColor = ( ( nColor & 0xFF ) << 16 ) + ( nColor & 0xFF00 ) + ( ( nColor & 0xFF0000 ) >> 16 ); + + const char *pColor = nullptr; + char pRgbColor[10]; + switch ( nColor ) + { + case 0x000000: pColor = "black"; break; + case 0xC0C0C0: pColor = "silver"; break; + case 0x808080: pColor = "gray"; break; + case 0xFFFFFF: pColor = "white"; break; + case 0x800000: pColor = "maroon"; break; + case 0xFF0000: pColor = "red"; break; + case 0x800080: pColor = "purple"; break; + case 0xFF00FF: pColor = "fuchsia"; break; + case 0x008000: pColor = "green"; break; + case 0x00FF00: pColor = "lime"; break; + case 0x808000: pColor = "olive"; break; + case 0xFFFF00: pColor = "yellow"; break; + case 0x000080: pColor = "navy"; break; + case 0x0000FF: pColor = "blue"; break; + case 0x008080: pColor = "teal"; break; + case 0x00FFFF: pColor = "aqua"; break; + default: + { + snprintf( pRgbColor, sizeof( pRgbColor ), "#%06x", static_cast< unsigned int >( nColor ) ); // not too handy to use OString::valueOf() here :-( + pColor = pRgbColor; + } + break; + } + + pAttrList->add( nElement, pColor ); +} + +static void impl_AddInt( sax_fastparser::FastAttributeList *pAttrList, sal_Int32 nElement, sal_uInt32 nValue ) +{ + if ( !pAttrList ) + return; + + pAttrList->add( nElement, OString::number( nValue ) ); +} + +static sal_uInt16 impl_GetUInt16( const sal_uInt8* &pVal ) +{ + sal_uInt16 nRet = *pVal++; + nRet += ( *pVal++ ) << 8; + return nRet; +} + +static sal_Int32 impl_GetPointComponent( const sal_uInt8* &pVal, sal_uInt16 nPointSize ) +{ + sal_Int32 nRet = 0; + if ( ( nPointSize == 0xfff0 ) || ( nPointSize == 4 ) ) + { + sal_uInt16 nUnsigned = *pVal++; + nUnsigned += ( *pVal++ ) << 8; + + nRet = sal_Int16( nUnsigned ); + } + else if ( nPointSize == 8 ) + { + sal_uInt32 nUnsigned = *pVal++; + nUnsigned += ( *pVal++ ) << 8; + nUnsigned += ( *pVal++ ) << 16; + nUnsigned += ( *pVal++ ) << 24; + + nRet = nUnsigned; + } + + return nRet; +} + +void VMLExport::AddSdrObjectVMLObject( const SdrObject& rObj) +{ + m_pSdrObject = &rObj; +} +void VMLExport::Commit( EscherPropertyContainer& rProps, const tools::Rectangle& rRect ) +{ + if ( m_nShapeType == ESCHER_ShpInst_Nil ) + return; + + // postpone the output of the embedded elements so that they are written + // inside the shapes + m_pSerializer->mark(Tag_Commit); + + // dimensions + if ( m_nShapeType == ESCHER_ShpInst_Line ) + AddLineDimensions( rRect ); + else + { + if ( IsWaterMarkShape( m_pSdrObject->GetName() ) ) + { + // Watermark need some padding to be compatible with MSO + tools::Long nPaddingY = 0; + const SfxItemSet& rSet = m_pSdrObject->GetMergedItemSet(); + if ( const SdrMetricItem* pItem = rSet.GetItem( SDRATTR_TEXT_UPPERDIST ) ) + nPaddingY += pItem->GetValue(); + + tools::Rectangle aRect( rRect ); + aRect.setHeight( aRect.getOpenHeight() + nPaddingY ); + AddRectangleDimensions( m_ShapeStyle, aRect ); + } + else + AddRectangleDimensions( m_ShapeStyle, rRect ); + } + + // properties + // The numbers of defines ESCHER_Prop_foo and DFF_Prop_foo correspond to the PIDs in + // 'Microsoft Office Drawing 97-2007 Binary Format Specification'. + // The property values are set by EscherPropertyContainer::CreateCustomShapeProperties() method. + bool bAlreadyWritten[ 0xFFF ] = {}; + const EscherProperties &rOpts = rProps.GetOpts(); + for (auto const& opt : rOpts) + { + sal_uInt16 nId = ( opt.nPropId & 0x0FFF ); + + if ( bAlreadyWritten[ nId ] ) + continue; + + switch ( nId ) + { + case ESCHER_Prop_WrapText: // 133 + { + const char *pWrapType = nullptr; + switch ( opt.nPropValue ) + { + case ESCHER_WrapSquare: + case ESCHER_WrapByPoints: pWrapType = "square"; break; // these two are equivalent according to the docu + case ESCHER_WrapNone: pWrapType = "none"; break; + case ESCHER_WrapTopBottom: + case ESCHER_WrapThrough: + break; // last two are *undefined* in MS-ODRAW, don't exist in VML + } + if ( pWrapType ) + { + m_ShapeStyle.append(";mso-wrap-style:"); + m_ShapeStyle.append(pWrapType); + } + } + bAlreadyWritten[ ESCHER_Prop_WrapText ] = true; + break; + + case ESCHER_Prop_AnchorText: // 135 + { + char const* pValue(nullptr); + switch (opt.nPropValue) + { + case ESCHER_AnchorTop: + pValue = "top"; + break; + case ESCHER_AnchorMiddle: + pValue = "middle"; + break; + case ESCHER_AnchorBottom: + pValue = "bottom"; + break; + case ESCHER_AnchorTopCentered: + pValue = "top-center"; + break; + case ESCHER_AnchorMiddleCentered: + pValue = "middle-center"; + break; + case ESCHER_AnchorBottomCentered: + pValue = "bottom-center"; + break; + case ESCHER_AnchorTopBaseline: + pValue = "top-baseline"; + break; + case ESCHER_AnchorBottomBaseline: + pValue = "bottom-baseline"; + break; + case ESCHER_AnchorTopCenteredBaseline: + pValue = "top-center-baseline"; + break; + case ESCHER_AnchorBottomCenteredBaseline: + pValue = "bottom-center-baseline"; + break; + } + m_ShapeStyle.append(";v-text-anchor:"); + m_ShapeStyle.append(pValue); + } + break; + + case ESCHER_Prop_txflTextFlow: // 136 + { + // at least "bottom-to-top" only has an effect when it's on the v:textbox element, not on v:shape + assert(m_TextboxStyle.isEmpty()); + switch (opt.nPropValue) + { + case ESCHER_txflHorzN: + m_TextboxStyle.append("layout-flow:horizontal"); + break; + case ESCHER_txflTtoBA: + m_TextboxStyle.append("layout-flow:vertical"); + break; + case ESCHER_txflBtoT: + m_TextboxStyle.append("mso-layout-flow-alt:bottom-to-top"); + break; + default: + assert(false); // unimplemented in escher export + break; + } + } + break; + + // coordorigin + case ESCHER_Prop_geoLeft: // 320 + case ESCHER_Prop_geoTop: // 321 + { + sal_uInt32 nLeft = 0, nTop = 0; + + if ( nId == ESCHER_Prop_geoLeft ) + { + nLeft = opt.nPropValue; + rProps.GetOpt( ESCHER_Prop_geoTop, nTop ); + } + else + { + nTop = opt.nPropValue; + rProps.GetOpt( ESCHER_Prop_geoLeft, nLeft ); + } + if(nTop!=0 && nLeft!=0) + m_pShapeAttrList->add( XML_coordorigin, + OString::number( nLeft ) + "," + OString::number( nTop ) ); + } + bAlreadyWritten[ ESCHER_Prop_geoLeft ] = true; + bAlreadyWritten[ ESCHER_Prop_geoTop ] = true; + break; + + // coordsize + case ESCHER_Prop_geoRight: // 322 + case ESCHER_Prop_geoBottom: // 323 + { + sal_uInt32 nLeft = 0, nRight = 0, nTop = 0, nBottom = 0; + rProps.GetOpt( ESCHER_Prop_geoLeft, nLeft ); + rProps.GetOpt( ESCHER_Prop_geoTop, nTop ); + + if ( nId == ESCHER_Prop_geoRight ) + { + nRight = opt.nPropValue; + rProps.GetOpt( ESCHER_Prop_geoBottom, nBottom ); + } + else + { + nBottom = opt.nPropValue; + rProps.GetOpt( ESCHER_Prop_geoRight, nRight ); + } + + if(nBottom!=0 && nRight!=0 ) + m_pShapeAttrList->add( XML_coordsize, + OString::number( nRight - nLeft ) + "," + OString::number( nBottom - nTop ) ); + } + bAlreadyWritten[ ESCHER_Prop_geoRight ] = true; + bAlreadyWritten[ ESCHER_Prop_geoBottom ] = true; + break; + + case ESCHER_Prop_pVertices: // 325 + case ESCHER_Prop_pSegmentInfo: // 326 + { + EscherPropSortStruct aVertices; + EscherPropSortStruct aSegments; + + if ( rProps.GetOpt( ESCHER_Prop_pVertices, aVertices ) && + rProps.GetOpt( ESCHER_Prop_pSegmentInfo, aSegments ) ) + { + const sal_uInt8 *pVerticesIt = aVertices.nProp.data() + 6; + const sal_uInt8 *pSegmentIt = aSegments.nProp.data(); + OStringBuffer aPath( 512 ); + + sal_uInt16 nPointSize = aVertices.nProp[4] + ( aVertices.nProp[5] << 8 ); + + // number of segments + sal_uInt16 nSegments = impl_GetUInt16( pSegmentIt ); + pSegmentIt += 4; + + for ( ; nSegments; --nSegments ) + { + sal_uInt16 nSeg = impl_GetUInt16( pSegmentIt ); + + // The segment type is stored in the upper 3 bits + // and segment count is stored in the lower 13 + // bits. + unsigned char nSegmentType = (nSeg & 0xE000) >> 13; + unsigned short nSegmentCount = nSeg & 0x03FF; + + switch (nSegmentType) + { + case msopathMoveTo: + { + sal_Int32 nX = impl_GetPointComponent( pVerticesIt, nPointSize ); + sal_Int32 nY = impl_GetPointComponent( pVerticesIt, nPointSize ); + if (nX >= 0 && nY >= 0 ) + aPath.append( "m" + OString::number( nX ) + "," + OString::number( nY ) ); + break; + } + case msopathClientEscape: + break; + case msopathEscape: + { + // If the segment type is msopathEscape, the lower 13 bits are + // divided in a 5 bit escape code and 8 bit + // vertex count (not segment count!) + unsigned char nEscapeCode = (nSegmentCount & 0x1F00) >> 8; + unsigned char nVertexCount = nSegmentCount & 0x00FF; + pVerticesIt += nVertexCount; + + switch (nEscapeCode) + { + case 0xa: // nofill + aPath.append( "nf" ); + break; + case 0xb: // nostroke + aPath.append( "ns" ); + break; + } + + break; + } + case msopathLineTo: + for (unsigned short i = 0; i < nSegmentCount; ++i) + { + sal_Int32 nX = impl_GetPointComponent( pVerticesIt, nPointSize ); + sal_Int32 nY = impl_GetPointComponent( pVerticesIt, nPointSize ); + aPath.append( "l" + OString::number( nX ) + "," + OString::number( nY ) ); + } + break; + case msopathCurveTo: + for (unsigned short i = 0; i < nSegmentCount; ++i) + { + sal_Int32 nX1 = impl_GetPointComponent( pVerticesIt, nPointSize ); + sal_Int32 nY1 = impl_GetPointComponent( pVerticesIt, nPointSize ); + sal_Int32 nX2 = impl_GetPointComponent( pVerticesIt, nPointSize ); + sal_Int32 nY2 = impl_GetPointComponent( pVerticesIt, nPointSize ); + sal_Int32 nX3 = impl_GetPointComponent( pVerticesIt, nPointSize ); + sal_Int32 nY3 = impl_GetPointComponent( pVerticesIt, nPointSize ); + aPath.append( "c" + OString::number( nX1 ) + "," + OString::number( nY1 ) + "," + + OString::number( nX2 ) + "," + OString::number( nY2 ) + "," + + OString::number( nX3 ) + "," + OString::number( nY3 ) ); + } + break; + case msopathClose: + aPath.append( "x" ); + break; + case msopathEnd: + aPath.append( "e" ); + break; + default: + SAL_WARN("oox", "Totally b0rked"); + break; + case msopathInvalid: + SAL_WARN("oox", "Invalid - should never be found"); + break; + } + } + OString pathString = aPath.makeStringAndClear(); + if ( !pathString.isEmpty() && pathString != "xe" ) + m_pShapeAttrList->add( XML_path, pathString ); + } + else + SAL_WARN("oox.vml", "unhandled shape path, missing either pVertices or pSegmentInfo."); + } + bAlreadyWritten[ ESCHER_Prop_pVertices ] = true; + bAlreadyWritten[ ESCHER_Prop_pSegmentInfo ] = true; + break; + + case ESCHER_Prop_fillType: // 384 + case ESCHER_Prop_fillColor: // 385 + case ESCHER_Prop_fillBackColor: // 387 + case ESCHER_Prop_fillBlip: // 390 + case ESCHER_Prop_fNoFillHitTest: // 447 + case ESCHER_Prop_fillOpacity: // 386 + { + sal_uInt32 nValue; + rtl::Reference<sax_fastparser::FastAttributeList> pAttrList + = FastSerializerHelper::createAttrList(); + + bool imageData = false; + EscherPropSortStruct aStruct; + const SdrGrafObj* pSdrGrafObj = dynamic_cast<const SdrGrafObj*>(m_pSdrObject); + + if (pSdrGrafObj && pSdrGrafObj->isSignatureLine() && m_pTextExport) + { + rtl::Reference<sax_fastparser::FastAttributeList> pAttrListSignatureLine + = FastSerializerHelper::createAttrList(); + pAttrListSignatureLine->add(XML_issignatureline, "t"); + if (!pSdrGrafObj->getSignatureLineId().isEmpty()) + { + pAttrListSignatureLine->add( + XML_id, pSdrGrafObj->getSignatureLineId()); + } + if (!pSdrGrafObj->getSignatureLineSuggestedSignerName().isEmpty()) + { + pAttrListSignatureLine->add( + FSNS(XML_o, XML_suggestedsigner), + pSdrGrafObj->getSignatureLineSuggestedSignerName()); + } + if (!pSdrGrafObj->getSignatureLineSuggestedSignerTitle().isEmpty()) + { + pAttrListSignatureLine->add( + FSNS(XML_o, XML_suggestedsigner2), + pSdrGrafObj->getSignatureLineSuggestedSignerTitle()); + } + if (!pSdrGrafObj->getSignatureLineSuggestedSignerEmail().isEmpty()) + { + pAttrListSignatureLine->add( + FSNS(XML_o, XML_suggestedsigneremail), + pSdrGrafObj->getSignatureLineSuggestedSignerEmail()); + } + if (!pSdrGrafObj->getSignatureLineSigningInstructions().isEmpty()) + { + pAttrListSignatureLine->add(XML_signinginstructionsset, "t"); + pAttrListSignatureLine->add( + FSNS(XML_o, XML_signinginstructions), + pSdrGrafObj->getSignatureLineSigningInstructions()); + } + pAttrListSignatureLine->add( + XML_showsigndate, + pSdrGrafObj->isSignatureLineShowSignDate() ? "t" : "f"); + pAttrListSignatureLine->add( + XML_allowcomments, + pSdrGrafObj->isSignatureLineCanAddComment() ? "t" : "f"); + + m_pSerializer->singleElementNS( + XML_o, XML_signatureline, + pAttrListSignatureLine); + + // Get signature line graphic + const uno::Reference<graphic::XGraphic>& xGraphic + = pSdrGrafObj->getSignatureLineUnsignedGraphic(); + Graphic aGraphic(xGraphic); + OUString aImageId = m_pTextExport->GetDrawingML().writeGraphicToStorage(aGraphic, false); + pAttrList->add(FSNS(XML_r, XML_id), aImageId); + imageData = true; + } + else if (rProps.GetOpt(ESCHER_Prop_fillBlip, aStruct) && m_pTextExport) + { + SvMemoryStream aStream; + // The first bytes are WW8-specific, we're only interested in the PNG + int nHeaderSize = 25; + aStream.WriteBytes(aStruct.nProp.data() + nHeaderSize, + aStruct.nProp.size() - nHeaderSize); + aStream.Seek(0); + Graphic aGraphic; + GraphicConverter::Import(aStream, aGraphic); + OUString aImageId = m_pTextExport->GetDrawingML().writeGraphicToStorage(aGraphic, false); + pAttrList->add(FSNS(XML_r, XML_id), aImageId); + imageData = true; + } + + if (rProps.GetOpt(ESCHER_Prop_fNoFillHitTest, nValue)) + impl_AddBool(pAttrList.get(), FSNS(XML_o, XML_detectmouseclick), nValue != 0); + + if (imageData && ((pSdrGrafObj && pSdrGrafObj->isSignatureLine()) + || m_nShapeType == ESCHER_ShpInst_PictureFrame)) + m_pSerializer->singleElementNS( XML_v, XML_imagedata, pAttrList ); + else + { + if ( rProps.GetOpt( ESCHER_Prop_fillType, nValue ) ) + { + const char *pFillType = nullptr; + switch ( nValue ) + { + case ESCHER_FillSolid: pFillType = "solid"; break; + // TODO case ESCHER_FillPattern: pFillType = ""; break; + case ESCHER_FillTexture: pFillType = "tile"; break; + case ESCHER_FillPicture: pFillType = "frame"; break; + // TODO case ESCHER_FillShade: pFillType = ""; break; + // TODO case ESCHER_FillShadeCenter: pFillType = ""; break; + // TODO case ESCHER_FillShadeShape: pFillType = ""; break; + // TODO case ESCHER_FillShadeScale: pFillType = ""; break; + // TODO case ESCHER_FillShadeTitle: pFillType = ""; break; + // TODO case ESCHER_FillBackground: pFillType = ""; break; + default: + SAL_INFO("oox.vml", "Unhandled fill type: " << nValue); + break; + } + if ( pFillType ) + pAttrList->add( XML_type, pFillType ); + } + else if (!rProps.GetOpt(ESCHER_Prop_fillColor, nValue)) + pAttrList->add( XML_on, "false" ); + + if ( rProps.GetOpt( ESCHER_Prop_fillColor, nValue ) ) + impl_AddColor( m_pShapeAttrList.get(), XML_fillcolor, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_fillBackColor, nValue ) ) + impl_AddColor( pAttrList.get(), XML_color2, nValue ); + + if (rProps.GetOpt(ESCHER_Prop_fillOpacity, nValue)) + // Partly undo the transformation at the end of EscherPropertyContainer::CreateFillProperties(): VML opacity is 0..1. + pAttrList->add(XML_opacity, OString::number(double((nValue * 100) >> 16) / 100)); + m_pSerializer->singleElementNS( XML_v, XML_fill, pAttrList ); + + } + } + bAlreadyWritten[ ESCHER_Prop_fillType ] = true; + bAlreadyWritten[ ESCHER_Prop_fillColor ] = true; + bAlreadyWritten[ ESCHER_Prop_fillBackColor ] = true; + bAlreadyWritten[ ESCHER_Prop_fillBlip ] = true; + bAlreadyWritten[ ESCHER_Prop_fNoFillHitTest ] = true; + bAlreadyWritten[ ESCHER_Prop_fillOpacity ] = true; + break; + + case ESCHER_Prop_lineColor: // 448 + case ESCHER_Prop_lineWidth: // 459 + case ESCHER_Prop_lineDashing: // 462 + case ESCHER_Prop_lineStartArrowhead: // 464 + case ESCHER_Prop_lineEndArrowhead: // 465 + case ESCHER_Prop_lineStartArrowWidth: // 466 + case ESCHER_Prop_lineStartArrowLength: // 467 + case ESCHER_Prop_lineEndArrowWidth: // 468 + case ESCHER_Prop_lineEndArrowLength: // 469 + case ESCHER_Prop_lineJoinStyle: // 470 + case ESCHER_Prop_lineEndCapStyle: // 471 + { + sal_uInt32 nValue; + rtl::Reference<sax_fastparser::FastAttributeList> pAttrList = FastSerializerHelper::createAttrList(); + + if ( rProps.GetOpt( ESCHER_Prop_lineColor, nValue ) ) + impl_AddColor( pAttrList.get(), XML_color, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_lineWidth, nValue ) ) + impl_AddInt( pAttrList.get(), XML_weight, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_lineDashing, nValue ) ) + { + const char *pDashStyle = nullptr; + switch ( nValue ) + { + case ESCHER_LineSolid: pDashStyle = "solid"; break; + case ESCHER_LineDashSys: pDashStyle = "shortdash"; break; + case ESCHER_LineDotSys: pDashStyle = "shortdot"; break; + case ESCHER_LineDashDotSys: pDashStyle = "shortdashdot"; break; + case ESCHER_LineDashDotDotSys: pDashStyle = "shortdashdotdot"; break; + case ESCHER_LineDotGEL: pDashStyle = "dot"; break; + case ESCHER_LineDashGEL: pDashStyle = "dash"; break; + case ESCHER_LineLongDashGEL: pDashStyle = "longdash"; break; + case ESCHER_LineDashDotGEL: pDashStyle = "dashdot"; break; + case ESCHER_LineLongDashDotGEL: pDashStyle = "longdashdot"; break; + case ESCHER_LineLongDashDotDotGEL: pDashStyle = "longdashdotdot"; break; + } + if ( pDashStyle ) + pAttrList->add( XML_dashstyle, pDashStyle ); + } + + if ( rProps.GetOpt( ESCHER_Prop_lineStartArrowhead, nValue ) ) + impl_AddArrowHead( pAttrList.get(), XML_startarrow, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_lineEndArrowhead, nValue ) ) + impl_AddArrowHead( pAttrList.get(), XML_endarrow, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_lineStartArrowWidth, nValue ) ) + impl_AddArrowWidth( pAttrList.get(), XML_startarrowwidth, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_lineStartArrowLength, nValue ) ) + impl_AddArrowLength( pAttrList.get(), XML_startarrowlength, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_lineEndArrowWidth, nValue ) ) + impl_AddArrowWidth( pAttrList.get(), XML_endarrowwidth, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_lineEndArrowLength, nValue ) ) + impl_AddArrowLength( pAttrList.get(), XML_endarrowlength, nValue ); + + if ( rProps.GetOpt( ESCHER_Prop_lineJoinStyle, nValue ) ) + { + const char *pJoinStyle = nullptr; + switch ( nValue ) + { + case ESCHER_LineJoinBevel: pJoinStyle = "bevel"; break; + case ESCHER_LineJoinMiter: pJoinStyle = "miter"; break; + case ESCHER_LineJoinRound: pJoinStyle = "round"; break; + } + if ( pJoinStyle ) + pAttrList->add( XML_joinstyle, pJoinStyle ); + } + + if ( rProps.GetOpt( ESCHER_Prop_lineEndCapStyle, nValue ) ) + { + const char *pEndCap = nullptr; + switch ( nValue ) + { + case ESCHER_LineEndCapRound: pEndCap = "round"; break; + case ESCHER_LineEndCapSquare: pEndCap = "square"; break; + case ESCHER_LineEndCapFlat: pEndCap = "flat"; break; + } + if ( pEndCap ) + pAttrList->add( XML_endcap, pEndCap ); + } + + m_pSerializer->singleElementNS( XML_v, XML_stroke, pAttrList ); + } + bAlreadyWritten[ ESCHER_Prop_lineColor ] = true; + bAlreadyWritten[ ESCHER_Prop_lineWidth ] = true; + bAlreadyWritten[ ESCHER_Prop_lineDashing ] = true; + bAlreadyWritten[ ESCHER_Prop_lineStartArrowhead ] = true; + bAlreadyWritten[ ESCHER_Prop_lineEndArrowhead ] = true; + bAlreadyWritten[ ESCHER_Prop_lineStartArrowWidth ] = true; + bAlreadyWritten[ ESCHER_Prop_lineStartArrowLength ] = true; + bAlreadyWritten[ ESCHER_Prop_lineEndArrowWidth ] = true; + bAlreadyWritten[ ESCHER_Prop_lineEndArrowLength ] = true; + bAlreadyWritten[ ESCHER_Prop_lineJoinStyle ] = true; + bAlreadyWritten[ ESCHER_Prop_lineEndCapStyle ] = true; + break; + + case ESCHER_Prop_fHidden: + if ( !opt.nPropValue ) + m_ShapeStyle.append( ";visibility:hidden" ); + break; + case ESCHER_Prop_shadowColor: + case ESCHER_Prop_fshadowObscured: + { + sal_uInt32 nValue = 0; + bool bShadow = false; + bool bObscured = false; + if ( rProps.GetOpt( ESCHER_Prop_fshadowObscured, nValue ) ) + { + bShadow = (( nValue & 0x20002 ) == 0x20002 ); + bObscured = (( nValue & 0x10001 ) == 0x10001 ); + } + if ( bShadow ) + { + rtl::Reference<sax_fastparser::FastAttributeList> pAttrList = FastSerializerHelper::createAttrList(); + impl_AddBool( pAttrList.get(), XML_on, bShadow ); + impl_AddBool( pAttrList.get(), XML_obscured, bObscured ); + + if ( rProps.GetOpt( ESCHER_Prop_shadowColor, nValue ) ) + impl_AddColor( pAttrList.get(), XML_color, nValue ); + + m_pSerializer->singleElementNS( XML_v, XML_shadow, pAttrList ); + bAlreadyWritten[ ESCHER_Prop_fshadowObscured ] = true; + bAlreadyWritten[ ESCHER_Prop_shadowColor ] = true; + } + } + break; + case ESCHER_Prop_gtextUNICODE: + case ESCHER_Prop_gtextFont: + { + EscherPropSortStruct aUnicode; + if (rProps.GetOpt(ESCHER_Prop_gtextUNICODE, aUnicode)) + { + SvMemoryStream aStream; + + if(!opt.nProp.empty()) + { + aStream.WriteBytes(opt.nProp.data(), opt.nProp.size()); + } + + aStream.Seek(0); + OUString aTextPathString = SvxMSDffManager::MSDFFReadZString(aStream, opt.nProp.size(), true); + aStream.Seek(0); + + m_pSerializer->singleElementNS(XML_v, XML_path, XML_textpathok, "t"); + + rtl::Reference<sax_fastparser::FastAttributeList> pAttrList = FastSerializerHelper::createAttrList(); + pAttrList->add(XML_on, "t"); + pAttrList->add(XML_fitshape, "t"); + pAttrList->add(XML_string, aTextPathString); + EscherPropSortStruct aFont; + OUString aStyle; + if (rProps.GetOpt(ESCHER_Prop_gtextFont, aFont)) + { + aStream.WriteBytes(aFont.nProp.data(), aFont.nProp.size()); + aStream.Seek(0); + OUString aTextPathFont = SvxMSDffManager::MSDFFReadZString(aStream, aFont.nProp.size(), true); + aStyle += "font-family:\"" + aTextPathFont + "\""; + } + sal_uInt32 nSize; + if (rProps.GetOpt(ESCHER_Prop_gtextSize, nSize)) + { + float nSizeF = static_cast<sal_Int32>(nSize) / 65536.0; + OUString aSize = OUString::number(nSizeF); + aStyle += ";font-size:" + aSize + "pt"; + } + + sal_uInt32 nGtextFlags; + if (rProps.GetOpt(DFF_Prop_gtextFStrikethrough /*255*/, nGtextFlags)) + { + // The property is in fact a collection of flags. Two bytes contain the + // fUsegtextF* flags and the other two bytes at same place the associated + // On/Off flags. See '2.3.22.10 Geometry Text Boolean Properties' section + // in [MS-ODRAW]. + if ((nGtextFlags & 0x00200020) == 0x00200020) // DFF_Prop_gtextFBold = 250 + aStyle += ";font-weight:bold"; + if ((nGtextFlags & 0x00100010) == 0x00100010) // DFF_Prop_gtextFItalic = 251 + aStyle += ";font-style:italic"; + if ((nGtextFlags & 0x00800080) == 0x00800080) // no DFF, PID gtextFNormalize = 248 + aStyle += ";v-same-letter-heights:t"; + + // The value 'Fontwork character spacing' in LO is bound to field 'Scaling' + // not to 'Spacing' in character properties. In fact the characters are + // rendered with changed distance and width. The method in escherex.cxx has + // put a rounded value of 'CharScaleWidth' API property to + // DFF_Prop_gtextSpacing (=196) as integer part of 16.16 fixed point format. + // fUsegtextFTight and gtextFTight (244) of MS binary format are not used. + sal_uInt32 nGtextSpacing; + if (rProps.GetOpt(DFF_Prop_gtextSpacing, nGtextSpacing)) + aStyle += ";v-text-spacing:" + OUString::number(nGtextSpacing) + "f"; + } + + if (!aStyle.isEmpty()) + pAttrList->add(XML_style, aStyle); + + // tdf#153260. LO renders all Fontwork shapes as if trim="t" is set. Default + // value is "f". So always write out "t", otherwise import will reduce the + // shape height as workaround for "f". + pAttrList->add(XML_trim, "t"); + + m_pSerializer->singleElementNS(XML_v, XML_textpath, pAttrList); + } + + bAlreadyWritten[ESCHER_Prop_gtextUNICODE] = true; + bAlreadyWritten[ESCHER_Prop_gtextFont] = true; + } + break; + case DFF_Prop_adjustValue: + case DFF_Prop_adjust2Value: + { + // FIXME: tdf#153296: The currently exported markup for <v:shapetype> is based on + // OOXML presets and unusable in regard to handles. Fontwork shapes use dedicated + // own markup, see FontworkHelpers::GetVMLFontworkShapetypeMarkup. + // Thus this is restricted to preset Fontwork shapes. Such have maximal two + // adjustment values. + if ((mso_sptTextSimple <= m_nShapeType && m_nShapeType <= mso_sptTextOnRing) + || (mso_sptTextPlainText <= m_nShapeType && m_nShapeType <= mso_sptTextCanDown)) + { + sal_uInt32 nValue; + OString sAdj; + if (rProps.GetOpt(DFF_Prop_adjustValue, nValue)) + { + sAdj = OString::number(static_cast<sal_Int32>(nValue)); + if (rProps.GetOpt(DFF_Prop_adjust2Value, nValue)) + sAdj += "," + OString::number(static_cast<sal_Int32>(nValue)); + } + if (!sAdj.isEmpty()) + m_pShapeAttrList->add(XML_adj, sAdj); + bAlreadyWritten[DFF_Prop_adjustValue] = true; + bAlreadyWritten[DFF_Prop_adjust2Value] = true; + } + } + break; + case ESCHER_Prop_Rotation: + { + // The higher half of the variable contains the angle. + m_ShapeStyle.append(";rotation:" + OString::number(double(opt.nPropValue >> 16))); + bAlreadyWritten[ESCHER_Prop_Rotation] = true; + } + break; + case ESCHER_Prop_fNoLineDrawDash: + { + // See DffPropertyReader::ApplyLineAttributes(). + impl_AddBool( m_pShapeAttrList.get(), XML_stroked, (opt.nPropValue & 8) != 0 ); + bAlreadyWritten[ESCHER_Prop_fNoLineDrawDash] = true; + } + break; + case ESCHER_Prop_wzName: + { + SvMemoryStream aStream; + + if(!opt.nProp.empty()) + { + aStream.WriteBytes(opt.nProp.data(), opt.nProp.size()); + } + + aStream.Seek(0); + OUString idStr = SvxMSDffManager::MSDFFReadZString(aStream, opt.nProp.size(), true); + aStream.Seek(0); + if (!IsWaterMarkShape(m_pSdrObject->GetName()) && !m_bSkipwzName) + m_pShapeAttrList->add(XML_ID, idStr); + + // note that XML_ID is different from XML_id (although it looks like a LO + // implementation distinction without valid justification to me). + // FIXME: XML_ID produces invalid file, see tdf#153183 + bAlreadyWritten[ESCHER_Prop_wzName] = true; + } + break; + default: +#if OSL_DEBUG_LEVEL > 0 + const size_t opt_nProp_size(opt.nProp.size()); + SAL_WARN( "oox.vml", "TODO VMLExport::Commit(), unimplemented id: " << nId + << ", value: " << opt.nPropValue + << ", data: [" << opt_nProp_size << "]"); + if ( opt.nProp.size() ) + { + const sal_uInt8 *pIt = opt.nProp.data(); + OStringBuffer buf( " ( " ); + for ( int nCount = opt.nProp.size(); nCount; --nCount ) + { + buf.append( OString::number(static_cast<sal_Int32>(*pIt), 16) + " "); + ++pIt; + } + buf.append( ")" ); + SAL_WARN("oox.vml", std::string_view(buf)); + } +#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 ) + { + static constexpr OString aPt( "pt"_ostr ); + aLeft = OString::number( double( rRectangle.Left() ) / 20 ) + aPt; + aTop = OString::number( double( rRectangle.Top() ) / 20 ) + aPt; + aRight = OString::number( double( rRectangle.Right() ) / 20 ) + aPt; + aBottom = OString::number( double( rRectangle.Bottom() ) / 20 ) + aPt; + } + else + { + aLeft = OString::number( rRectangle.Left() ); + aTop = OString::number( rRectangle.Top() ); + aRight = OString::number( rRectangle.Right() ); + aBottom = OString::number( rRectangle.Bottom() ); + } + + m_pShapeAttrList->add( XML_from, aLeft + "," + aTop ); + + m_pShapeAttrList->add( XML_to, aRight + "," + aBottom ); +} + +void VMLExport::AddRectangleDimensions( OStringBuffer& rBuffer, const tools::Rectangle& rRectangle, bool rbAbsolutePos) +{ + if ( !rBuffer.isEmpty() ) + rBuffer.append( ";" ); + + if (rbAbsolutePos && !m_bInline) + { + rBuffer.append( "position:absolute;" ); + } + + if(m_bInline) + { + rBuffer.append( "width:" + OString::number( double( rRectangle.Right() - rRectangle.Left() ) / 20 ) + + "pt;height:" + OString::number( double( rRectangle.Bottom() - rRectangle.Top() ) / 20 ) + + "pt" ); + } + else if ( mnGroupLevel == 1 ) + { + rBuffer.append( "margin-left:" + OString::number( double( rRectangle.Left() ) / 20 ) + + "pt;margin-top:" + OString::number( double( rRectangle.Top() ) / 20 ) + + "pt;width:" + OString::number( double( rRectangle.Right() - rRectangle.Left() ) / 20 ) + + "pt;height:" + OString::number( double( rRectangle.Bottom() - rRectangle.Top() ) / 20 ) + + "pt" ); + } + else + { + rBuffer.append( "left:" + OString::number( rRectangle.Left() ) + + ";top:" + OString::number( rRectangle.Top() ) + + ";width:" + OString::number( rRectangle.Right() - rRectangle.Left() ) + + ";height:" + OString::number( rRectangle.Bottom() - rRectangle.Top() ) ); + } + + AddFlipXY(); +} + +void VMLExport::AddShapeAttribute( sal_Int32 nAttribute, std::string_view rValue ) +{ + m_pShapeAttrList->add( nAttribute, rValue ); +} + +static std::vector<OString> lcl_getShapeTypes() +{ + std::vector<OString> aRet; + + OUString aPath("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/filter/vml-shape-types"); + rtl::Bootstrap::expandMacros(aPath); + SvFileStream aStream(aPath, StreamMode::READ); + if (aStream.GetError() != ERRCODE_NONE) + SAL_WARN("oox", "failed to open vml-shape-types"); + OStringBuffer aLine; + bool bNotDone = aStream.ReadLine(aLine); + while (bNotDone) + { + // Filter out comments. + if (!o3tl::starts_with(aLine, "/")) + aRet.push_back(OString(aLine)); + bNotDone = aStream.ReadLine(aLine); + } + return aRet; +} + +static bool lcl_isTextBox(const SdrObject* pSdrObject) +{ + uno::Reference<beans::XPropertySet> xPropertySet(const_cast<SdrObject*>(pSdrObject)->getUnoShape(), uno::UNO_QUERY); + if (!xPropertySet.is()) + return false; + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); + if (!xPropertySetInfo->hasPropertyByName("TextBox")) + return false; + css::uno::Any aTextBox(xPropertySet->getPropertyValue("TextBox")); + if (!aTextBox.hasValue()) + return false; + return aTextBox.get<bool>(); +} + +static OUString lcl_getAnchorIdFromGrabBag(const SdrObject* pSdrObject) +{ + OUString aResult; + + uno::Reference<beans::XPropertySet> xShape(const_cast<SdrObject*>(pSdrObject)->getUnoShape(), uno::UNO_QUERY); + if (xShape->getPropertySetInfo()->hasPropertyByName("InteropGrabBag")) + { + comphelper::SequenceAsHashMap aInteropGrabBag(xShape->getPropertyValue("InteropGrabBag")); + auto it = aInteropGrabBag.find("AnchorId"); + if (it != aInteropGrabBag.end()) + it->second >>= aResult; + } + + return aResult; +} + +sal_uInt32 VMLExport::GenerateShapeId() +{ + if(!m_bOverrideShapeIdGeneration) + return EscherEx::GenerateShapeId(); + else + return m_nShapeIDCounter++; +} + +OString VMLExport::GetVMLShapeTypeDefinition( + std::string_view sShapeID, const bool bIsPictureFrame ) +{ + OString sShapeType; + if ( !bIsPictureFrame ) + // We don't have a shape definition for host control in presetShapeDefinitions.xml + // So use a definition copied from DOCX file created with MSO + sShapeType = OString::Concat("<v:shapetype id=\"_x0000_t") + sShapeID + + "\" coordsize=\"21600,21600\" o:spt=\"" + sShapeID + + "\" path=\"m,l,21600l21600,21600l21600,xe\">\n" + "<v:stroke joinstyle=\"miter\"/>\n" + "<v:path shadowok=\"f\" o:extrusionok=\"f\" strokeok=\"f\" fillok=\"f\" o:connecttype=\"rect\"/>\n" + "<o:lock v:ext=\"edit\" shapetype=\"t\"/>\n" + "</v:shapetype>"; + else + // We don't have a shape definition for picture frame in presetShapeDefinitions.xml + // So use a definition copied from DOCX file created with MSO + sShapeType = OString::Concat("<v:shapetype id=\"_x0000_t") + sShapeID + + "\" coordsize=\"21600,21600\" o:spt=\"" + sShapeID + + "\" o:preferrelative=\"t\" path=\"m@4@5l@4@11@9@11@9@5xe\" filled=\"f\" stroked=\"f\">\n" + "<v:stroke joinstyle=\"miter\"/>\n" + "<v:formulas>\n" + "<v:f eqn=\"if lineDrawn pixelLineWidth 0\"/>\n" + "<v:f eqn=\"sum @0 1 0\"/>\n" + "<v:f eqn=\"sum 0 0 @1\"/>\n" + "<v:f eqn=\"prod @2 1 2\"/>\n" + "<v:f eqn=\"prod @3 21600 pixelWidth\"/>\n" + "<v:f eqn=\"prod @3 21600 pixelHeight\"/>\n" + "<v:f eqn=\"sum @0 0 1\"/>\n" + "<v:f eqn=\"prod @6 1 2\"/>\n" + "<v:f eqn=\"prod @7 21600 pixelWidth\"/>\n" + "<v:f eqn=\"sum @8 21600 0\"/>\n" + "<v:f eqn=\"prod @7 21600 pixelHeight\"/>\n" + "<v:f eqn=\"sum @10 21600 0\"/>\n" + "</v:formulas>\n" + "<v:path o:extrusionok=\"f\" gradientshapeok=\"t\" o:connecttype=\"rect\"/>\n" + "<o:lock v:ext=\"edit\" aspectratio=\"t\"/>\n" + "</v:shapetype>"; + return sShapeType; +} + +sal_Int32 VMLExport::StartShape() +{ + if ( m_nShapeType == ESCHER_ShpInst_Nil ) + return -1; + + // some of the shapes have their own name ;-) + sal_Int32 nShapeElement = -1; + bool bReferToShapeType = false; + switch ( m_nShapeType ) + { + case ESCHER_ShpInst_NotPrimitive: nShapeElement = XML_shape; break; + case ESCHER_ShpInst_Rectangle: nShapeElement = XML_rect; break; + case ESCHER_ShpInst_RoundRectangle: nShapeElement = XML_roundrect; break; + case ESCHER_ShpInst_Ellipse: nShapeElement = XML_oval; break; + case ESCHER_ShpInst_Arc: nShapeElement = XML_arc; break; + case ESCHER_ShpInst_Line: nShapeElement = XML_line; break; + case ESCHER_ShpInst_HostControl: + { + bReferToShapeType = true; + nShapeElement = XML_shape; + if ( !m_aShapeTypeWritten[ m_nShapeType ] ) + { + m_pSerializer->write(GetVMLShapeTypeDefinition(OString::number(m_nShapeType), false)); + m_aShapeTypeWritten[ m_nShapeType ] = true; + } + break; + } + case ESCHER_ShpInst_PictureFrame: + { + bReferToShapeType = true; + nShapeElement = XML_shape; + if ( !m_aShapeTypeWritten[ m_nShapeType ] ) + { + m_pSerializer->write(GetVMLShapeTypeDefinition(OString::number(m_nShapeType), true)); + m_aShapeTypeWritten[ m_nShapeType ] = true; + } + break; + } + default: + nShapeElement = XML_shape; + if (m_pSdrObject->IsTextPath()) + { + bReferToShapeType = m_aShapeTypeWritten[m_nShapeType]; + if (!bReferToShapeType) + { + // Does a predefined markup exist at all? + OString sMarkup = FontworkHelpers::GetVMLFontworkShapetypeMarkup( + static_cast<MSO_SPT>(m_nShapeType)); + if (!sMarkup.isEmpty()) + { + m_pSerializer->write(sMarkup); + m_aShapeTypeWritten[m_nShapeType] = true; + bReferToShapeType = true; + } + } + // ToDo: The case bReferToShapeType==false happens for 'non-primitive' shapes for + // example. We need to get the geometry from CustomShapeGeometry in these cases. + } + else if ( m_nShapeType < ESCHER_ShpInst_COUNT ) + { + // a predefined shape? + static std::vector<OString> aShapeTypes = lcl_getShapeTypes(); + SAL_WARN_IF(m_nShapeType >= aShapeTypes.size(), "oox.vml", "Unknown shape type!"); + if (m_nShapeType < aShapeTypes.size() && aShapeTypes[m_nShapeType] != "NULL") + { + bReferToShapeType = true; + if ( !m_aShapeTypeWritten[ m_nShapeType ] ) + { + m_pSerializer->write(aShapeTypes[m_nShapeType]); + m_aShapeTypeWritten[ m_nShapeType ] = true; + } + } + else + { + // rectangle is probably the best fallback... + nShapeElement = XML_rect; + } + } + break; + } + + // anchoring + switch (m_eHOri) + { + case text::HoriOrientation::LEFT: + m_ShapeStyle.append(";mso-position-horizontal:left"); + break; + case text::HoriOrientation::CENTER: + m_ShapeStyle.append(";mso-position-horizontal:center"); + break; + case text::HoriOrientation::RIGHT: + m_ShapeStyle.append(";mso-position-horizontal:right"); + break; + case text::HoriOrientation::INSIDE: + m_ShapeStyle.append(";mso-position-horizontal:inside"); + break; + case text::HoriOrientation::OUTSIDE: + m_ShapeStyle.append(";mso-position-horizontal:outside"); + break; + default: + case text::HoriOrientation::NONE: + break; + } + switch (m_eHRel) + { + case text::RelOrientation::PAGE_PRINT_AREA: + m_ShapeStyle.append(";mso-position-horizontal-relative:margin"); + break; + case text::RelOrientation::PAGE_FRAME: + case text::RelOrientation::PAGE_LEFT: + case text::RelOrientation::PAGE_RIGHT: + m_ShapeStyle.append(";mso-position-horizontal-relative:page"); + break; + case text::RelOrientation::CHAR: + m_ShapeStyle.append(";mso-position-horizontal-relative:char"); + break; + default: + break; + } + + switch (m_eVOri) + { + case text::VertOrientation::TOP: + case text::VertOrientation::LINE_TOP: + case text::VertOrientation::CHAR_TOP: + m_ShapeStyle.append(";mso-position-vertical:top"); + break; + case text::VertOrientation::CENTER: + case text::VertOrientation::LINE_CENTER: + m_ShapeStyle.append(";mso-position-vertical:center"); + break; + case text::VertOrientation::BOTTOM: + case text::VertOrientation::LINE_BOTTOM: + case text::VertOrientation::CHAR_BOTTOM: + m_ShapeStyle.append(";mso-position-vertical:bottom"); + break; + default: + case text::VertOrientation::NONE: + break; + } + switch (m_eVRel) + { + case text::RelOrientation::PAGE_PRINT_AREA: + m_ShapeStyle.append(";mso-position-vertical-relative:margin"); + break; + case text::RelOrientation::PAGE_FRAME: + m_ShapeStyle.append(";mso-position-vertical-relative:page"); + break; + default: + break; + } + + if (!m_pSdrObject->getHyperlink().isEmpty()) + m_pShapeAttrList->add( + XML_href, m_pSdrObject->getHyperlink()); + + m_pShapeAttrList->addNS(XML_o, XML_allowincell, m_IsFollowingTextFlow ? "t" : "f"); + + // add style + m_pShapeAttrList->add( XML_style, m_ShapeStyle.makeStringAndClear() ); + + OUString sAnchorId = lcl_getAnchorIdFromGrabBag(m_pSdrObject); + if (!sAnchorId.isEmpty()) + m_pShapeAttrList->addNS(XML_wp14, XML_anchorId, sAnchorId); + + if ( nShapeElement >= 0 && !m_pShapeAttrList->hasAttribute( XML_type ) && bReferToShapeType ) + { + OString sType; + if (m_bUseHashMarkForType) + sType = "#"_ostr; + m_pShapeAttrList->add( XML_type, sType + + "_x0000_t" + OString::number( m_nShapeType ) ); + } + + // allow legacy id (which in form controls and textboxes + // by definition seems to have this otherwise illegal name). + m_pSerializer->setAllowXEscape(!m_sShapeIDPrefix.startsWith("_x0000_")); + + // start of the shape + m_pSerializer->startElementNS( XML_v, nShapeElement, m_pShapeAttrList ); + m_pSerializer->setAllowXEscape(true); + + 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 = DynCastSdrTextObj( m_pSdrObject ); + if (pTxtObj && m_pTextExport && !m_pSdrObject->IsTextPath() + && !IsWaterMarkShape(m_pSdrObject->GetName()) && !lcl_isTextBox(m_pSdrObject)) + { + std::optional<OutlinerParaObject> pParaObj; + + /* + #i13885# + When the object is actively being edited, that text is not set into + the objects normal text object, but lives in a separate object. + */ + if (pTxtObj->IsTextEditActive()) + { + pParaObj = pTxtObj->CreateEditOutlinerParaObject(); + } + else if (pTxtObj->GetOutlinerParaObject()) + { + pParaObj = *pTxtObj->GetOutlinerParaObject(); + } + + if( pParaObj ) + { + rtl::Reference<sax_fastparser::FastAttributeList> pTextboxAttrList = FastSerializerHelper::createAttrList(); + if (!textboxStyle.isEmpty()) + { + pTextboxAttrList->add(XML_style, textboxStyle); + } + + // this is reached only in case some text is attached to the shape + m_pSerializer->startElementNS(XML_v, XML_textbox, pTextboxAttrList); + m_pTextExport->WriteOutliner(*pParaObj); + m_pSerializer->endElementNS(XML_v, XML_textbox); + } + } + + return nShapeElement; +} + +void VMLExport::EndShape( sal_Int32 nShapeElement ) +{ + if ( nShapeElement < 0 ) + return; + + if (m_pTextExport && lcl_isTextBox(m_pSdrObject)) + { + uno::Reference<drawing::XShape> xShape {const_cast<SdrObject*>(m_pSdrObject)->getUnoShape(), uno::UNO_QUERY}; + uno::Reference<beans::XPropertySet> xPropertySet(xShape, uno::UNO_QUERY); + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); + bool bBottomToTop = false; + if (xPropertySetInfo->hasPropertyByName("CustomShapeGeometry")) + { + // In this case a DrawingML DOCX was imported. + auto aAny = xPropertySet->getPropertyValue("WritingMode"); + sal_Int16 nWritingMode; + if ((aAny >>= nWritingMode) && nWritingMode == text::WritingMode2::BT_LR) + bBottomToTop = true; + } + else + { + // In this case a pure VML DOCX was imported, so there is no CustomShapeGeometry. + auto pTextExport = m_pTextExport->GetDrawingML().GetTextExport(); + // FIXME: somewhy pTextExport is always nullptr, we should find its reason + if (pTextExport) + { + auto xTextFrame = pTextExport->GetUnoTextFrame(xShape); + uno::Reference<beans::XPropertySet> xPropSet(xTextFrame, uno::UNO_QUERY); + auto aAny = xPropSet->getPropertyValue("WritingMode"); + sal_Int16 nWritingMode; + if (aAny >>= nWritingMode) + { + switch (nWritingMode) + { + case text::WritingMode2::BT_LR: + bBottomToTop = true; + break; + default: + break; + } + } + } + } + rtl::Reference<sax_fastparser::FastAttributeList> pTextboxAttrList = FastSerializerHelper::createAttrList(); + if (bBottomToTop) + pTextboxAttrList->add(XML_style, "mso-layout-flow-alt:bottom-to-top"); + m_pSerializer->startElementNS(XML_v, XML_textbox, pTextboxAttrList); + + m_pTextExport->WriteVMLTextBox(uno::Reference<drawing::XShape>(xPropertySet, uno::UNO_QUERY_THROW)); + + m_pSerializer->endElementNS(XML_v, XML_textbox); + } + + if (m_pWrapAttrList) + { + m_pSerializer->singleElementNS(XML_w10, XML_wrap, m_pWrapAttrList); + } + + // end of the shape + m_pSerializer->endElementNS( XML_v, nShapeElement ); +} + +OString const & VMLExport::AddSdrObject( const SdrObject& rObj, + bool const bIsFollowingTextFlow, + sal_Int16 eHOri, sal_Int16 eVOri, sal_Int16 eHRel, sal_Int16 eVRel, + FastAttributeList* pWrapAttrList, + const bool bOOxmlExport, sal_uInt32 nId) +{ + m_pSdrObject = &rObj; + m_eHOri = eHOri; + m_eVOri = eVOri; + m_eHRel = eHRel; + m_eVRel = eVRel; + m_pWrapAttrList = pWrapAttrList; + m_bInline = false; + m_IsFollowingTextFlow = bIsFollowingTextFlow; + EscherEx::AddSdrObject(rObj, bOOxmlExport, nId); + return m_sShapeId; +} + +OString const & VMLExport::AddInlineSdrObject( const SdrObject& rObj, const bool bOOxmlExport ) +{ + m_pSdrObject = &rObj; + m_eHOri = -1; + m_eVOri = -1; + m_eHRel = -1; + m_eVRel = -1; + m_pWrapAttrList.clear(); + m_bInline = true; + m_IsFollowingTextFlow = true; + EscherEx::AddSdrObject(rObj, bOOxmlExport); + return m_sShapeId; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |