diff options
Diffstat (limited to 'oox/source/export/drawingml.cxx')
-rw-r--r-- | oox/source/export/drawingml.cxx | 4634 |
1 files changed, 4634 insertions, 0 deletions
diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx new file mode 100644 index 000000000..de2d34979 --- /dev/null +++ b/oox/source/export/drawingml.cxx @@ -0,0 +1,4634 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_features.h> + +#include <config_folders.h> +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> +#include <oox/core/xmlfilterbase.hxx> +#include <oox/export/drawingml.hxx> +#include <oox/export/utils.hxx> +#include <oox/helper/propertyset.hxx> +#include <oox/drawingml/color.hxx> +#include <drawingml/fillproperties.hxx> +#include <drawingml/textparagraph.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/token/properties.hxx> +#include <oox/token/relationship.hxx> +#include <oox/token/tokens.hxx> +#include <oox/drawingml/drawingmltypes.hxx> +#include <svtools/unitconv.hxx> +#include <sax/fastattribs.hxx> +#include <tools/diagnose_ex.h> +#include <comphelper/processfactory.hxx> +#include <i18nlangtag/languagetag.hxx> + +#include <numeric> +#include <com/sun/star/awt/CharSet.hpp> +#include <com/sun/star/awt/FontDescriptor.hpp> +#include <com/sun/star/awt/FontSlant.hpp> +#include <com/sun/star/awt/FontStrikeout.hpp> +#include <com/sun/star/awt/FontWeight.hpp> +#include <com/sun/star/awt/FontUnderline.hpp> +#include <com/sun/star/awt/Gradient.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/beans/XPropertySetInfo.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/container/XIndexAccess.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/drawing/BitmapMode.hpp> +#include <com/sun/star/drawing/ColorMode.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp> +#include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp> +#include <com/sun/star/drawing/Hatch.hpp> +#include <com/sun/star/drawing/LineDash.hpp> +#include <com/sun/star/drawing/LineJoint.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <com/sun/star/drawing/TextFitToSizeType.hpp> +#include <com/sun/star/drawing/TextHorizontalAdjust.hpp> +#include <com/sun/star/drawing/TextVerticalAdjust.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/frame/XModel.hpp> +#include <com/sun/star/graphic/XGraphic.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/style/LineSpacing.hpp> +#include <com/sun/star/style/LineSpacingMode.hpp> +#include <com/sun/star/text/WritingMode.hpp> +#include <com/sun/star/text/WritingMode2.hpp> +#include <com/sun/star/text/GraphicCrop.hpp> +#include <com/sun/star/text/XText.hpp> +#include <com/sun/star/text/XTextContent.hpp> +#include <com/sun/star/text/XTextField.hpp> +#include <com/sun/star/text/XTextRange.hpp> +#include <com/sun/star/style/CaseMap.hpp> +#include <com/sun/star/xml/dom/XNodeList.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/xml/sax/XSAXSerializable.hpp> + +#include <comphelper/random.hxx> +#include <comphelper/seqstream.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/xmltools.hxx> +#include <o3tl/any.hxx> +#include <o3tl/safeint.hxx> +#include <tools/stream.hxx> +#include <unotools/fontdefs.hxx> +#include <vcl/cvtgrf.hxx> +#include <vcl/graph.hxx> +#include <vcl/svapp.hxx> +#include <rtl/strbuf.hxx> +#include <filter/msfilter/escherex.hxx> +#include <filter/msfilter/util.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/svxenum.hxx> +#include <editeng/unonames.hxx> +#include <editeng/unoprnms.hxx> +#include <editeng/flditem.hxx> +#include <svx/svdoashp.hxx> +#include <svx/svdomedia.hxx> +#include <svx/unoapi.hxx> +#include <svx/unoshape.hxx> +#include <svx/EnhancedCustomShape2d.hxx> + +using namespace ::css; +using namespace ::css::beans; +using namespace ::css::drawing; +using namespace ::css::i18n; +using namespace ::css::style; +using namespace ::css::text; +using namespace ::css::uno; +using namespace ::css::container; + +using ::css::io::XOutputStream; +using ::sax_fastparser::FSHelperPtr; +using ::sax_fastparser::FastSerializerHelper; + +namespace +{ +/// Extracts start or end alpha information from a transparency gradient. +sal_Int32 GetAlphaFromTransparenceGradient(const awt::Gradient& rGradient, bool bStart) +{ + // Our alpha is a gray color value. + sal_uInt8 nRed = ::Color(bStart ? rGradient.StartColor : rGradient.EndColor).GetRed(); + // drawingML alpha is a percentage on a 0..100000 scale. + return (255 - nRed) * oox::drawingml::MAX_PERCENT / 255; +} +} + +namespace oox::drawingml { + +URLTransformer::~URLTransformer() +{ +} + +OUString URLTransformer::getTransformedString(const OUString& rString) const +{ + return rString; +} + +bool URLTransformer::isExternalURL(const OUString& /*rURL*/) const +{ + return true; +} + +static css::uno::Any getLineDash( const css::uno::Reference<css::frame::XModel>& xModel, const OUString& rDashName ) + { + css::uno::Reference<css::lang::XMultiServiceFactory> xFact(xModel, css::uno::UNO_QUERY); + css::uno::Reference<css::container::XNameAccess> xNameAccess( + xFact->createInstance("com.sun.star.drawing.DashTable"), + css::uno::UNO_QUERY ); + if(xNameAccess.is()) + { + if (!xNameAccess->hasByName(rDashName)) + return css::uno::Any(); + + return xNameAccess->getByName(rDashName); + } + + return css::uno::Any(); + } + +namespace +{ +void WriteGradientPath(const awt::Gradient& rGradient, const FSHelperPtr& pFS, const bool bCircle) +{ + pFS->startElementNS(XML_a, XML_path, XML_path, bCircle ? "circle" : "rect"); + + // Write the focus rectangle. Work with the focus point, and assume + // that it extends 50% in all directions. The below + // left/top/right/bottom values are percentages, where 0 means the + // edge of the tile rectangle and 100% means the center of it. + rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList( + sax_fastparser::FastSerializerHelper::createAttrList()); + sal_Int32 nLeftPercent = rGradient.XOffset; + pAttributeList->add(XML_l, OString::number(nLeftPercent * PER_PERCENT)); + sal_Int32 nTopPercent = rGradient.YOffset; + pAttributeList->add(XML_t, OString::number(nTopPercent * PER_PERCENT)); + sal_Int32 nRightPercent = 100 - rGradient.XOffset; + pAttributeList->add(XML_r, OString::number(nRightPercent * PER_PERCENT)); + sal_Int32 nBottomPercent = 100 - rGradient.YOffset; + pAttributeList->add(XML_b, OString::number(nBottomPercent * PER_PERCENT)); + sax_fastparser::XFastAttributeListRef xAttributeList(pAttributeList.get()); + pFS->singleElementNS(XML_a, XML_fillToRect, xAttributeList); + + pFS->endElementNS(XML_a, XML_path); +} +} + +// not thread safe +int DrawingML::mnImageCounter = 1; +int DrawingML::mnWdpImageCounter = 1; +std::map<OUString, OUString> DrawingML::maWdpCache; + +sal_Int16 DrawingML::GetScriptType(const OUString& rStr) +{ + if (rStr.getLength() > 0) + { + static Reference<css::i18n::XBreakIterator> xBreakIterator = + css::i18n::BreakIterator::create(comphelper::getProcessComponentContext()); + + sal_Int16 nScriptType = xBreakIterator->getScriptType(rStr, 0); + + if (nScriptType == css::i18n::ScriptType::WEAK) + { + sal_Int32 nPos = xBreakIterator->nextScript(rStr, 0, nScriptType); + if (nPos < rStr.getLength()) + nScriptType = xBreakIterator->getScriptType(rStr, nPos); + + } + + if (nScriptType != css::i18n::ScriptType::WEAK) + return nScriptType; + } + + return css::i18n::ScriptType::LATIN; +} + +void DrawingML::ResetCounters() +{ + mnImageCounter = 1; + mnWdpImageCounter = 1; + maWdpCache.clear(); +} + +bool DrawingML::GetProperty( const Reference< XPropertySet >& rXPropertySet, const OUString& aName ) +{ + try + { + mAny = rXPropertySet->getPropertyValue(aName); + if (mAny.hasValue()) + return true; + } + catch( const Exception& ) + { + /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */ + } + return false; +} + +bool DrawingML::GetPropertyAndState( const Reference< XPropertySet >& rXPropertySet, const Reference< XPropertyState >& rXPropertyState, const OUString& aName, PropertyState& eState ) +{ + try + { + mAny = rXPropertySet->getPropertyValue(aName); + if (mAny.hasValue()) + { + eState = rXPropertyState->getPropertyState(aName); + return true; + } + } + catch( const Exception& ) + { + /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */ + } + return false; +} + +void DrawingML::WriteColor( ::Color nColor, sal_Int32 nAlpha ) +{ + // Transparency is a separate element. + OString sColor = OString::number( sal_uInt32(nColor) & 0x00FFFFFF, 16 ); + if( sColor.getLength() < 6 ) + { + OStringBuffer sBuf( "0" ); + int remains = 5 - sColor.getLength(); + + while( remains > 0 ) + { + sBuf.append( "0" ); + remains--; + } + + sBuf.append( sColor ); + + sColor = sBuf.getStr(); + } + if( nAlpha < MAX_PERCENT ) + { + mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor); + mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha)); + mpFS->endElementNS( XML_a, XML_srgbClr ); + + } + else + { + mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor); + } +} + +void DrawingML::WriteColor( const OUString& sColorSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) +{ + // prevent writing a tag with empty val attribute + if( sColorSchemeName.isEmpty() ) + return; + + if( aTransformations.hasElements() ) + { + mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName.toUtf8()); + WriteColorTransformations( aTransformations, nAlpha ); + mpFS->endElementNS( XML_a, XML_schemeClr ); + } + else if(nAlpha < MAX_PERCENT) + { + mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName.toUtf8()); + mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha)); + mpFS->endElementNS( XML_a, XML_schemeClr ); + } + else + { + mpFS->singleElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName.toUtf8()); + } +} + +void DrawingML::WriteColorTransformations( const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) +{ + for( const auto& rTransformation : aTransformations ) + { + sal_Int32 nToken = Color::getColorTransformationToken( rTransformation.Name ); + if( nToken != XML_TOKEN_INVALID && rTransformation.Value.hasValue() ) + { + if(nToken == XML_alpha && nAlpha < MAX_PERCENT) + { + mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nAlpha)); + } + else + { + sal_Int32 nValue = rTransformation.Value.get<sal_Int32>(); + mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nValue)); + } + } + } +} + +void DrawingML::WriteSolidFill( ::Color nColor, sal_Int32 nAlpha ) +{ + mpFS->startElementNS(XML_a, XML_solidFill); + WriteColor( nColor, nAlpha ); + mpFS->endElementNS( XML_a, XML_solidFill ); +} + +void DrawingML::WriteSolidFill( const OUString& sSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha ) +{ + mpFS->startElementNS(XML_a, XML_solidFill); + WriteColor( sSchemeName, aTransformations, nAlpha ); + mpFS->endElementNS( XML_a, XML_solidFill ); +} + +void DrawingML::WriteSolidFill( const Reference< XPropertySet >& rXPropSet ) +{ + // get fill color + if ( !GetProperty( rXPropSet, "FillColor" ) ) + return; + sal_uInt32 nFillColor = mAny.get<sal_uInt32>(); + + // get InteropGrabBag and search the relevant attributes + OUString sColorFillScheme; + sal_uInt32 nOriginalColor = 0; + Sequence< PropertyValue > aStyleProperties, aTransformations; + if ( GetProperty( rXPropSet, "InteropGrabBag" ) ) + { + Sequence< PropertyValue > aGrabBag; + mAny >>= aGrabBag; + for( const auto& rProp : std::as_const(aGrabBag) ) + { + if( rProp.Name == "SpPrSolidFillSchemeClr" ) + rProp.Value >>= sColorFillScheme; + else if( rProp.Name == "OriginalSolidFillClr" ) + rProp.Value >>= nOriginalColor; + else if( rProp.Name == "StyleFillRef" ) + rProp.Value >>= aStyleProperties; + else if( rProp.Name == "SpPrSolidFillSchemeClrTransformations" ) + rProp.Value >>= aTransformations; + } + } + + sal_Int32 nAlpha = MAX_PERCENT; + if( GetProperty( rXPropSet, "FillTransparence" ) ) + { + sal_Int32 nTransparency = 0; + mAny >>= nTransparency; + // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency()) + nAlpha = (MAX_PERCENT - ( PER_PERCENT * nTransparency ) ); + } + + // OOXML has no separate transparence gradient but uses transparency in the gradient stops. + // So we merge transparency and color and use gradient fill in such case. + awt::Gradient aTransparenceGradient; + bool bNeedGradientFill(false); + if (GetProperty(rXPropSet, "FillTransparenceGradient")) + { + mAny >>= aTransparenceGradient; + if (aTransparenceGradient.StartColor != aTransparenceGradient.EndColor) + bNeedGradientFill = true; + else if (aTransparenceGradient.StartColor != 0) + nAlpha = GetAlphaFromTransparenceGradient(aTransparenceGradient, true); + } + + // write XML + if (bNeedGradientFill) + { + awt::Gradient aPseudoColorGradient; + aPseudoColorGradient.XOffset = aTransparenceGradient.XOffset; + aPseudoColorGradient.YOffset = aTransparenceGradient.YOffset; + aPseudoColorGradient.StartIntensity = 100; + aPseudoColorGradient.EndIntensity = 100; + aPseudoColorGradient.Angle = aTransparenceGradient.Angle; + aPseudoColorGradient.Border = aTransparenceGradient.Border; + aPseudoColorGradient.Style = aTransparenceGradient.Style; + aPseudoColorGradient.StartColor = nFillColor; + aPseudoColorGradient.EndColor = nFillColor; + aPseudoColorGradient.StepCount = aTransparenceGradient.StepCount; + mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0"); + WriteGradientFill(aPseudoColorGradient, aTransparenceGradient); + mpFS->endElementNS( XML_a, XML_gradFill ); + } + else if ( nFillColor != nOriginalColor ) + { + // the user has set a different color for the shape + WriteSolidFill( ::Color(nFillColor & 0xffffff), nAlpha ); + } + else if ( !sColorFillScheme.isEmpty() ) + { + // the shape had a scheme color and the user didn't change it + WriteSolidFill( sColorFillScheme, aTransformations, nAlpha ); + } + else + { + // the shape had a custom color and the user didn't change it + // tdf#124013 + WriteSolidFill( ::Color(nFillColor & 0xffffff), nAlpha ); + } +} + +void DrawingML::WriteGradientStop(sal_uInt16 nStop, ::Color nColor, sal_Int32 nAlpha) +{ + mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nStop * 1000)); + WriteColor(nColor, nAlpha); + mpFS->endElementNS( XML_a, XML_gs ); +} + +::Color DrawingML::ColorWithIntensity( sal_uInt32 nColor, sal_uInt32 nIntensity ) +{ + return ::Color(( ( ( nColor & 0xff ) * nIntensity ) / 100 ) + | ( ( ( ( ( nColor & 0xff00 ) >> 8 ) * nIntensity ) / 100 ) << 8 ) + | ( ( ( ( ( nColor & 0xff0000 ) >> 8 ) * nIntensity ) / 100 ) << 8 )); +} + +bool DrawingML::EqualGradients( awt::Gradient aGradient1, awt::Gradient aGradient2 ) +{ + return aGradient1.Style == aGradient2.Style && + aGradient1.StartColor == aGradient2.StartColor && + aGradient1.EndColor == aGradient2.EndColor && + aGradient1.Angle == aGradient2.Angle && + aGradient1.Border == aGradient2.Border && + aGradient1.XOffset == aGradient2.XOffset && + aGradient1.YOffset == aGradient2.YOffset && + aGradient1.StartIntensity == aGradient2.StartIntensity && + aGradient1.EndIntensity == aGradient2.EndIntensity && + aGradient1.StepCount == aGradient2.StepCount; +} + +void DrawingML::WriteGradientFill( const Reference< XPropertySet >& rXPropSet ) +{ + awt::Gradient aGradient; + awt::Gradient aTransparenceGradient; + if (!GetProperty(rXPropSet, "FillGradient")) + return; + + aGradient = *o3tl::doAccess<awt::Gradient>(mAny); + + // get InteropGrabBag and search the relevant attributes + awt::Gradient aOriginalGradient; + Sequence< PropertyValue > aGradientStops; + if ( GetProperty( rXPropSet, "InteropGrabBag" ) ) + { + Sequence< PropertyValue > aGrabBag; + mAny >>= aGrabBag; + for( const auto& rProp : std::as_const(aGrabBag) ) + if( rProp.Name == "GradFillDefinition" ) + rProp.Value >>= aGradientStops; + else if( rProp.Name == "OriginalGradFill" ) + rProp.Value >>= aOriginalGradient; + } + + // check if an ooxml gradient had been imported and if the user has modified it + // Gradient grab-bag depends on theme grab-bag, which is implemented + // only for DOCX. + if( EqualGradients( aOriginalGradient, aGradient ) && GetDocumentType() == DOCUMENT_DOCX) + { + // If we have no gradient stops that means original gradient were defined by a theme. + if( aGradientStops.hasElements() ) + { + mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0"); + WriteGrabBagGradientFill(aGradientStops, aGradient); + mpFS->endElementNS( XML_a, XML_gradFill ); + } + } + else + { + mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0"); + if( GetProperty(rXPropSet, "FillTransparenceGradient") ) + aTransparenceGradient = *o3tl::doAccess<awt::Gradient>(mAny); + WriteGradientFill(aGradient, aTransparenceGradient); + mpFS->endElementNS( XML_a, XML_gradFill ); + } +} + +void DrawingML::WriteGrabBagGradientFill( const Sequence< PropertyValue >& aGradientStops, awt::Gradient rGradient ) +{ + // write back the original gradient + mpFS->startElementNS(XML_a, XML_gsLst); + + // get original stops and write them + for( const auto& rGradientStop : aGradientStops ) + { + Sequence< PropertyValue > aGradientStop; + rGradientStop.Value >>= aGradientStop; + + // get values + OUString sSchemeClr; + double nPos = 0; + sal_Int16 nTransparency = 0; + ::Color nRgbClr; + Sequence< PropertyValue > aTransformations; + for( const auto& rProp : std::as_const(aGradientStop) ) + { + if( rProp.Name == "SchemeClr" ) + rProp.Value >>= sSchemeClr; + else if( rProp.Name == "RgbClr" ) + rProp.Value >>= nRgbClr; + else if( rProp.Name == "Pos" ) + rProp.Value >>= nPos; + else if( rProp.Name == "Transparency" ) + rProp.Value >>= nTransparency; + else if( rProp.Name == "Transformations" ) + rProp.Value >>= aTransformations; + } + // write stop + mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nPos * 100000.0).getStr()); + if( sSchemeClr.isEmpty() ) + { + // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency()) + sal_Int32 nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency ); + WriteColor( nRgbClr, nAlpha ); + } + else + { + WriteColor( sSchemeClr, aTransformations ); + } + mpFS->endElementNS( XML_a, XML_gs ); + } + mpFS->endElementNS( XML_a, XML_gsLst ); + + switch (rGradient.Style) + { + default: + mpFS->singleElementNS( + XML_a, XML_lin, XML_ang, + OString::number((((3600 - rGradient.Angle + 900) * 6000) % 21600000))); + break; + case awt::GradientStyle_RADIAL: + WriteGradientPath(rGradient, mpFS, true); + break; + } +} + +void DrawingML::WriteGradientFill(awt::Gradient rGradient, awt::Gradient rTransparenceGradient, + const uno::Reference<beans::XPropertySet>& rXPropSet) +{ + sal_Int32 nStartAlpha; + sal_Int32 nEndAlpha; + if( rXPropSet.is() && GetProperty(rXPropSet, "FillTransparence") ) + { + sal_Int32 nTransparency = 0; + mAny >>= nTransparency; + nStartAlpha = nEndAlpha = (MAX_PERCENT - (PER_PERCENT * nTransparency)); + } + else + { + nStartAlpha = GetAlphaFromTransparenceGradient(rTransparenceGradient, true); + nEndAlpha = GetAlphaFromTransparenceGradient(rTransparenceGradient, false); + } + switch( rGradient.Style ) + { + default: + case awt::GradientStyle_LINEAR: + { + mpFS->startElementNS(XML_a, XML_gsLst); + WriteGradientStop(rGradient.Border, ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity), + nStartAlpha); + WriteGradientStop(100, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity), + nEndAlpha); + mpFS->endElementNS( XML_a, XML_gsLst ); + mpFS->singleElementNS( + XML_a, XML_lin, XML_ang, + OString::number((((3600 - rGradient.Angle + 900) * 6000) % 21600000))); + break; + } + + case awt::GradientStyle_AXIAL: + { + mpFS->startElementNS(XML_a, XML_gsLst); + WriteGradientStop(0, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity), + nEndAlpha); + if (rGradient.Border > 0 && rGradient.Border < 100) + { + WriteGradientStop(rGradient.Border/2, + ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity), + nEndAlpha); + } + WriteGradientStop(50, ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity), + nStartAlpha); + if (rGradient.Border > 0 && rGradient.Border < 100) + { + WriteGradientStop(100 - rGradient.Border/2, + ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity), + nEndAlpha); + } + WriteGradientStop(100, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity), + nEndAlpha); + mpFS->endElementNS(XML_a, XML_gsLst); + mpFS->singleElementNS( + XML_a, XML_lin, XML_ang, + OString::number((((3600 - rGradient.Angle + 900) * 6000) % 21600000))); + break; + } + + case awt::GradientStyle_RADIAL: + case awt::GradientStyle_ELLIPTICAL: + case awt::GradientStyle_RECT: + case awt::GradientStyle_SQUARE: + { + mpFS->startElementNS(XML_a, XML_gsLst); + WriteGradientStop(0, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity), + nEndAlpha); + if (rGradient.Border > 0 && rGradient.Border < 100) + { + // Map border to an additional gradient stop, which has the + // same color as the final stop. + WriteGradientStop(100 - rGradient.Border, + ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity), + nStartAlpha); + } + WriteGradientStop(100, + ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity), + nStartAlpha); + mpFS->endElementNS(XML_a, XML_gsLst); + + WriteGradientPath(rGradient, mpFS, rGradient.Style == awt::GradientStyle_RADIAL || rGradient.Style == awt::GradientStyle_ELLIPTICAL); + break; + } + } +} + +void DrawingML::WriteLineArrow( const Reference< XPropertySet >& rXPropSet, bool bLineStart ) +{ + ESCHER_LineEnd eLineEnd; + sal_Int32 nArrowLength; + sal_Int32 nArrowWidth; + + if ( !EscherPropertyContainer::GetLineArrow( bLineStart, rXPropSet, eLineEnd, nArrowLength, nArrowWidth ) ) + return; + + const char* len; + const char* type; + const char* width; + + switch( nArrowLength ) + { + case ESCHER_LineShortArrow: + len = "sm"; + break; + default: + case ESCHER_LineMediumLenArrow: + len = "med"; + break; + case ESCHER_LineLongArrow: + len = "lg"; + break; + } + + switch( eLineEnd ) + { + default: + case ESCHER_LineNoEnd: + type = "none"; + break; + case ESCHER_LineArrowEnd: + type = "triangle"; + break; + case ESCHER_LineArrowStealthEnd: + type = "stealth"; + break; + case ESCHER_LineArrowDiamondEnd: + type = "diamond"; + break; + case ESCHER_LineArrowOvalEnd: + type = "oval"; + break; + case ESCHER_LineArrowOpenEnd: + type = "arrow"; + break; + } + + switch( nArrowWidth ) + { + case ESCHER_LineNarrowArrow: + width = "sm"; + break; + default: + case ESCHER_LineMediumWidthArrow: + width = "med"; + break; + case ESCHER_LineWideArrow: + width = "lg"; + break; + } + + mpFS->singleElementNS( XML_a, bLineStart ? XML_headEnd : XML_tailEnd, + XML_len, len, + XML_type, type, + XML_w, width ); +} + +void DrawingML::WriteOutline( const Reference<XPropertySet>& rXPropSet, Reference< frame::XModel > const & xModel ) +{ + drawing::LineStyle aLineStyle( drawing::LineStyle_NONE ); + if (GetProperty(rXPropSet, "LineStyle")) + mAny >>= aLineStyle; + + const LineCap aLineCap = GetProperty(rXPropSet, "LineCap") ? mAny.get<drawing::LineCap>() : LineCap_BUTT; + + sal_uInt32 nLineWidth = 0; + sal_uInt32 nEmuLineWidth = 0; + ::Color nColor; + sal_Int32 nColorAlpha = MAX_PERCENT; + bool bColorSet = false; + const char* cap = nullptr; + drawing::LineDash aLineDash; + bool bDashSet = false; + bool bNoFill = false; + + + // get InteropGrabBag and search the relevant attributes + OUString sColorFillScheme; + + ::Color nOriginalColor; + ::Color nStyleColor; + sal_uInt32 nStyleLineWidth = 0; + + Sequence<PropertyValue> aStyleProperties; + Sequence<PropertyValue> aTransformations; + + drawing::LineStyle aStyleLineStyle(drawing::LineStyle_NONE); + drawing::LineJoint aStyleLineJoint(drawing::LineJoint_NONE); + + if (GetProperty(rXPropSet, "InteropGrabBag")) + { + Sequence<PropertyValue> aGrabBag; + mAny >>= aGrabBag; + + for (const auto& rProp : std::as_const(aGrabBag)) + { + if( rProp.Name == "SpPrLnSolidFillSchemeClr" ) + rProp.Value >>= sColorFillScheme; + else if( rProp.Name == "OriginalLnSolidFillClr" ) + rProp.Value >>= nOriginalColor; + else if( rProp.Name == "StyleLnRef" ) + rProp.Value >>= aStyleProperties; + else if( rProp.Name == "SpPrLnSolidFillSchemeClrTransformations" ) + rProp.Value >>= aTransformations; + else if( rProp.Name == "EmuLineWidth" ) + rProp.Value >>= nEmuLineWidth; + } + for (const auto& rStyleProp : std::as_const(aStyleProperties)) + { + if( rStyleProp.Name == "Color" ) + rStyleProp.Value >>= nStyleColor; + else if( rStyleProp.Name == "LineStyle" ) + rStyleProp.Value >>= aStyleLineStyle; + else if( rStyleProp.Name == "LineJoint" ) + rStyleProp.Value >>= aStyleLineJoint; + else if( rStyleProp.Name == "LineWidth" ) + rStyleProp.Value >>= nStyleLineWidth; + } + } + + if (GetProperty(rXPropSet, "LineWidth")) + mAny >>= nLineWidth; + + switch (aLineStyle) + { + case drawing::LineStyle_NONE: + bNoFill = true; + break; + case drawing::LineStyle_DASH: + if (GetProperty(rXPropSet, "LineDash")) + { + aLineDash = mAny.get<drawing::LineDash>(); + //this query is good for shapes, but in the case of charts it returns 0 values + if (aLineDash.Dots == 0 && aLineDash.DotLen == 0 && aLineDash.Dashes == 0 && aLineDash.DashLen == 0 && aLineDash.Distance == 0) { + OUString aLineDashName; + if (GetProperty(rXPropSet, "LineDashName")) + mAny >>= aLineDashName; + if (!aLineDashName.isEmpty() && xModel) { + css::uno::Any aAny = getLineDash(xModel, aLineDashName); + aAny >>= aLineDash; + } + } + } + else + { + //export the linestyle of chart wall (plot area) and chart page + OUString aLineDashName; + if (GetProperty(rXPropSet, "LineDashName")) + mAny >>= aLineDashName; + if (!aLineDashName.isEmpty() && xModel) { + css::uno::Any aAny = getLineDash(xModel, aLineDashName); + aAny >>= aLineDash; + } + } + bDashSet = true; + if (aLineDash.Style == DashStyle_ROUND || aLineDash.Style == DashStyle_ROUNDRELATIVE) + { + cap = "rnd"; + } + + SAL_INFO("oox.shape", "dash dots: " << aLineDash.Dots << " dashes: " << aLineDash.Dashes + << " dotlen: " << aLineDash.DotLen << " dashlen: " << aLineDash.DashLen << " distance: " << aLineDash.Distance); + + [[fallthrough]]; + case drawing::LineStyle_SOLID: + default: + if (GetProperty(rXPropSet, "LineColor")) + { + nColor = ::Color(mAny.get<sal_uInt32>() & 0xffffff); + bColorSet = true; + } + if (GetProperty(rXPropSet, "LineTransparence")) + { + nColorAlpha = MAX_PERCENT - (mAny.get<sal_Int16>() * PER_PERCENT); + } + if (aLineCap == LineCap_ROUND) + cap = "rnd"; + else if (aLineCap == LineCap_SQUARE) + cap = "sq"; + break; + } + + // if the line-width was not modified after importing then the original EMU value will be exported to avoid unexpected conversion (rounding) error + if (nEmuLineWidth == 0 || static_cast<sal_uInt32>(oox::drawingml::convertEmuToHmm(nEmuLineWidth)) != nLineWidth) + nEmuLineWidth = oox::drawingml::convertHmmToEmu(nLineWidth); + mpFS->startElementNS( XML_a, XML_ln, + XML_cap, cap, + XML_w, nLineWidth == 0 || (nLineWidth > 1 && nStyleLineWidth != nLineWidth) ? + OString::number(nEmuLineWidth).getStr() : nullptr ); + + if( bColorSet ) + { + if( nColor != nOriginalColor ) + { + // the user has set a different color for the line + WriteSolidFill( nColor, nColorAlpha ); + } + else if( !sColorFillScheme.isEmpty() ) + { + // the line had a scheme color and the user didn't change it + WriteSolidFill( sColorFillScheme, aTransformations ); + } + else + { + WriteSolidFill( nColor, nColorAlpha ); + } + } + + if( bDashSet && aStyleLineStyle != drawing::LineStyle_DASH ) + { + // Try to detect if it might come from ms preset line style import. + // MS Office styles are always relative, both binary and OOXML. + // "dot" is always the first dash and "dash" the second one. All OOXML presets linestyles + // start with the longer one. Definitions are in OOXML part 1, 20.1.10.49 + // The tests are strict, for to not catch styles from standard.sod (as of Aug 2019). + bool bIsConverted = false; + + bool bIsRelative(aLineDash.Style == DashStyle_RECTRELATIVE || aLineDash.Style == DashStyle_ROUNDRELATIVE); + if ( bIsRelative && aLineDash.Dots == 1) + { // The length were tweaked on import in case of prstDash. Revert it here. + sal_uInt32 nDotLen = aLineDash.DotLen; + sal_uInt32 nDashLen = aLineDash.DashLen; + sal_uInt32 nDistance = aLineDash.Distance; + if (aLineCap != LineCap_BUTT && nDistance >= 99) + { + nDistance -= 99; + nDotLen += 99; + if (nDashLen > 0) + nDashLen += 99; + } + // LO uses length 0 for 100%, if the attribute is missing in ODF. + // Other applications might write 100%. Make is unique for the conditions. + if (nDotLen == 0) + nDotLen = 100; + if (nDashLen == 0 && aLineDash.Dashes > 0) + nDashLen = 100; + bIsConverted = true; + if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dot"); + } + else if (nDotLen == 400 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dash"); + } + else if (nDotLen == 400 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dashDot"); + } + else if (nDotLen == 800 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDash"); + } + else if (nDotLen == 800 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDot"); + } + else if (nDotLen == 800 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 300) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDotDot"); + } + else if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDot"); + } + else if (nDotLen == 300 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDash"); + } + else if (nDotLen == 300 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 100) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDot"); + } + else if (nDotLen == 300 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 100) + { + mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDotDot"); + } + else + bIsConverted = false; + } + // Do not map our own line styles to OOXML prstDash values, because custDash gives better results. + if (!bIsConverted) + { + mpFS->startElementNS(XML_a, XML_custDash); + // In case of hairline we would need the current pixel size. Instead use a reasonable + // ersatz for it. The value is the same as SMALLEST_DASH_WIDTH in xattr.cxx. + // (And it makes sure fLineWidth is not zero in below division.) + double fLineWidth = nLineWidth > 0 ? nLineWidth : 26.95; + int i; + double fSp = bIsRelative ? aLineDash.Distance : aLineDash.Distance * 100.0 / fLineWidth; + // LO uses line width, in case Distance is zero. MS Office would use a space of zero length. + // So set 100% explicitly. + if (aLineDash.Distance <= 0) + fSp = 100.0; + // In case of custDash, round caps are included in dash length in MS Office. Square caps are added + // to dash length, same as in ODF. Change the length values accordingly. + if (aLineCap == LineCap_ROUND && fSp > 99.0) + fSp -= 99.0; + + if (aLineDash.Dots > 0) + { + double fD = bIsRelative ? aLineDash.DotLen : aLineDash.DotLen * 100.0 / fLineWidth; + // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended. + if (aLineDash.DotLen == 0) + fD = 100.0; + // Tweak dash length, see above. + if (aLineCap == LineCap_ROUND && fSp > 99.0) + fD += 99.0; + + for( i = 0; i < aLineDash.Dots; i ++ ) + { + mpFS->singleElementNS( XML_a, XML_ds, + XML_d , write1000thOfAPercent(fD), + XML_sp, write1000thOfAPercent(fSp) ); + } + } + if ( aLineDash.Dashes > 0 ) + { + double fD = bIsRelative ? aLineDash.DashLen : aLineDash.DashLen * 100.0 / fLineWidth; + // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended. + if (aLineDash.DashLen == 0) + fD = 100.0; + // Tweak dash length, see above. + if (aLineCap == LineCap_ROUND && fSp > 99.0) + fD += 99.0; + + for( i = 0; i < aLineDash.Dashes; i ++ ) + { + mpFS->singleElementNS( XML_a , XML_ds, + XML_d , write1000thOfAPercent(fD), + XML_sp, write1000thOfAPercent(fSp) ); + } + } + + SAL_WARN_IF(nLineWidth <= 0, + "oox.shape", "while writing outline - custom dash - line width was < 0 : " << nLineWidth); + SAL_WARN_IF(aLineDash.Dashes < 0, + "oox.shape", "while writing outline - custom dash - number of dashes was < 0 : " << aLineDash.Dashes); + SAL_WARN_IF(aLineDash.Dashes > 0 && aLineDash.DashLen <= 0, + "oox.shape", "while writing outline - custom dash - dash length was < 0 : " << aLineDash.DashLen); + SAL_WARN_IF(aLineDash.Dots < 0, + "oox.shape", "while writing outline - custom dash - number of dots was < 0 : " << aLineDash.Dots); + SAL_WARN_IF(aLineDash.Dots > 0 && aLineDash.DotLen <= 0, + "oox.shape", "while writing outline - custom dash - dot length was < 0 : " << aLineDash.DotLen); + SAL_WARN_IF(aLineDash.Distance <= 0, + "oox.shape", "while writing outline - custom dash - distance was < 0 : " << aLineDash.Distance); + + mpFS->endElementNS( XML_a, XML_custDash ); + } + } + + if (!bNoFill && nLineWidth > 1 && GetProperty(rXPropSet, "LineJoint")) + { + LineJoint eLineJoint = mAny.get<LineJoint>(); + + if( aStyleLineJoint == LineJoint_NONE || aStyleLineJoint != eLineJoint ) + { + // style-defined line joint does not exist, or is different from the shape's joint + switch( eLineJoint ) + { + case LineJoint_NONE: + case LineJoint_BEVEL: + mpFS->singleElementNS(XML_a, XML_bevel); + break; + default: + case LineJoint_MIDDLE: + case LineJoint_MITER: + mpFS->singleElementNS(XML_a, XML_miter); + break; + case LineJoint_ROUND: + mpFS->singleElementNS(XML_a, XML_round); + break; + } + } + } + + if( !bNoFill ) + { + WriteLineArrow( rXPropSet, true ); + WriteLineArrow( rXPropSet, false ); + } + else + { + mpFS->singleElementNS(XML_a, XML_noFill); + } + + mpFS->endElementNS( XML_a, XML_ln ); +} + +const char* DrawingML::GetComponentDir() const +{ + switch ( meDocumentType ) + { + case DOCUMENT_DOCX: return "word"; + case DOCUMENT_PPTX: return "ppt"; + case DOCUMENT_XLSX: return "xl"; + } + + return "unknown"; +} + +const char* DrawingML::GetRelationCompPrefix() const +{ + switch ( meDocumentType ) + { + case DOCUMENT_DOCX: return ""; + case DOCUMENT_PPTX: + case DOCUMENT_XLSX: return "../"; + } + + return "unknown"; +} + +OUString DrawingML::WriteImage( const Graphic& rGraphic , bool bRelPathToMedia ) +{ + GfxLink aLink = rGraphic.GetGfxLink (); + OUString sMediaType; + const char* pExtension = ""; + OUString sRelId; + + SvMemoryStream aStream; + const void* aData = aLink.GetData(); + std::size_t nDataSize = aLink.GetDataSize(); + + switch ( aLink.GetType() ) + { + case GfxLinkType::NativeGif: + sMediaType = "image/gif"; + pExtension = ".gif"; + break; + + // #i15508# added BMP type for better exports + // export not yet active, so adding for reference (not checked) + case GfxLinkType::NativeBmp: + sMediaType = "image/bmp"; + pExtension = ".bmp"; + break; + + case GfxLinkType::NativeJpg: + sMediaType = "image/jpeg"; + pExtension = ".jpeg"; + break; + case GfxLinkType::NativePng: + sMediaType = "image/png"; + pExtension = ".png"; + break; + case GfxLinkType::NativeTif: + sMediaType = "image/tiff"; + pExtension = ".tif"; + break; + case GfxLinkType::NativeWmf: + sMediaType = "image/x-wmf"; + pExtension = ".wmf"; + break; + case GfxLinkType::NativeMet: + sMediaType = "image/x-met"; + pExtension = ".met"; + break; + case GfxLinkType::NativePct: + sMediaType = "image/x-pict"; + pExtension = ".pct"; + break; + case GfxLinkType::NativeMov: + sMediaType = "application/movie"; + pExtension = ".MOV"; + break; + default: + { + GraphicType aType = rGraphic.GetType(); + if ( aType == GraphicType::Bitmap || aType == GraphicType::GdiMetafile) + { + if ( aType == GraphicType::Bitmap ) + { + (void)GraphicConverter::Export( aStream, rGraphic, ConvertDataFormat::PNG ); + sMediaType = "image/png"; + pExtension = ".png"; + } + else + { + (void)GraphicConverter::Export( aStream, rGraphic, ConvertDataFormat::EMF ); + sMediaType = "image/x-emf"; + pExtension = ".emf"; + } + } + else + { + SAL_WARN("oox.shape", "unhandled graphic type " << static_cast<int>(aType) ); + /*Earlier, even in case of unhandled graphic types we were + proceeding to write the image, which would eventually + write an empty image with a zero size, and return a valid + relationID, which is incorrect. + */ + return sRelId; + } + + aData = aStream.GetData(); + nDataSize = aStream.GetEndOfData(); + break; + } + } + + Reference< XOutputStream > xOutStream = mpFB->openFragmentStream( OUStringBuffer() + .appendAscii( GetComponentDir() ) + .append( "/media/image" ) + .append( static_cast<sal_Int32>(mnImageCounter) ) + .appendAscii( pExtension ) + .makeStringAndClear(), + sMediaType ); + xOutStream->writeBytes( Sequence< sal_Int8 >( static_cast<const sal_Int8*>(aData), nDataSize ) ); + xOutStream->closeOutput(); + + const OString sRelPathToMedia = "media/image"; + OString sRelationCompPrefix; + if ( bRelPathToMedia ) + sRelationCompPrefix = "../"; + else + sRelationCompPrefix = GetRelationCompPrefix(); + sRelId = mpFB->addRelation( mpFS->getOutputStream(), + oox::getRelationship(Relationship::IMAGE), + OUStringBuffer() + .appendAscii( sRelationCompPrefix.getStr() ) + .appendAscii( sRelPathToMedia.getStr() ) + .append( static_cast<sal_Int32>(mnImageCounter ++) ) + .appendAscii( pExtension ) + .makeStringAndClear() ); + + return sRelId; +} + +void DrawingML::WriteMediaNonVisualProperties(const css::uno::Reference<css::drawing::XShape>& xShape) +{ + SdrMediaObj* pMediaObj = dynamic_cast<SdrMediaObj*>(GetSdrObjectFromXShape(xShape)); + if (!pMediaObj) + return; + + // extension + OUString aExtension; + const OUString& rURL(pMediaObj->getURL()); + int nLastDot = rURL.lastIndexOf('.'); + if (nLastDot >= 0) + aExtension = rURL.copy(nLastDot); + + bool bEmbed = rURL.startsWith("vnd.sun.star.Package:"); + Relationship eMediaType = Relationship::VIDEO; + + // mime type +#if HAVE_FEATURE_AVMEDIA + OUString aMimeType(pMediaObj->getMediaProperties().getMimeType()); +#else + OUString aMimeType("none"); +#endif + if (aMimeType == "application/vnd.sun.star.media") + { + // try to set something better + // TODO fix the importer to actually set the mimetype on import + if (aExtension.equalsIgnoreAsciiCase(".avi")) + aMimeType = "video/x-msvideo"; + else if (aExtension.equalsIgnoreAsciiCase(".flv")) + aMimeType = "video/x-flv"; + else if (aExtension.equalsIgnoreAsciiCase(".mp4")) + aMimeType = "video/mp4"; + else if (aExtension.equalsIgnoreAsciiCase(".mov")) + aMimeType = "video/quicktime"; + else if (aExtension.equalsIgnoreAsciiCase(".ogv")) + aMimeType = "video/ogg"; + else if (aExtension.equalsIgnoreAsciiCase(".wmv")) + aMimeType = "video/x-ms-wmv"; + else if (aExtension.equalsIgnoreAsciiCase(".wav")) + { + aMimeType = "audio/x-wav"; + eMediaType = Relationship::AUDIO; + } + } + + OUString aVideoFileRelId; + OUString aMediaRelId; + + if (bEmbed) + { + // copy the video stream + Reference<XOutputStream> xOutStream = mpFB->openFragmentStream(OUStringBuffer() + .appendAscii(GetComponentDir()) + .append("/media/media") + .append(static_cast<sal_Int32>(mnImageCounter)) + .append(aExtension) + .makeStringAndClear(), + aMimeType); + + uno::Reference<io::XInputStream> xInputStream(pMediaObj->GetInputStream()); + comphelper::OStorageHelper::CopyInputToOutput(xInputStream, xOutStream); + + xOutStream->closeOutput(); + + // create the relation + OUString aPath = OUStringBuffer().appendAscii(GetRelationCompPrefix()) + .append("media/media") + .append(static_cast<sal_Int32>(mnImageCounter++)) + .append(aExtension) + .makeStringAndClear(); + aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), aPath); + aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), aPath); + } + else + { + aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), rURL); + aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), rURL); + } + + GetFS()->startElementNS(XML_p, XML_nvPr); + + GetFS()->singleElementNS(XML_a, eMediaType == Relationship::VIDEO ? XML_videoFile : XML_audioFile, + FSNS(XML_r, XML_link), aVideoFileRelId.toUtf8()); + + GetFS()->startElementNS(XML_p, XML_extLst); + // media extensions; google this ID for details + GetFS()->startElementNS(XML_p, XML_ext, XML_uri, "{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}"); + + GetFS()->singleElementNS(XML_p14, XML_media, + bEmbed? FSNS(XML_r, XML_embed): FSNS(XML_r, XML_link), aMediaRelId.toUtf8()); + + GetFS()->endElementNS(XML_p, XML_ext); + GetFS()->endElementNS(XML_p, XML_extLst); + + GetFS()->endElementNS(XML_p, XML_nvPr); +} + +void DrawingML::WriteImageBrightnessContrastTransparence(uno::Reference<beans::XPropertySet> const & rXPropSet) +{ + sal_Int16 nBright = 0; + sal_Int32 nContrast = 0; + sal_Int32 nTransparence = 0; + + if (GetProperty(rXPropSet, "AdjustLuminance")) + nBright = mAny.get<sal_Int16>(); + if (GetProperty(rXPropSet, "AdjustContrast")) + nContrast = mAny.get<sal_Int32>(); + // Used for shapes with picture fill + if (GetProperty(rXPropSet, "FillTransparence")) + nTransparence = mAny.get<sal_Int32>(); + // Used for pictures + if (nTransparence == 0 && GetProperty(rXPropSet, "Transparency")) + nTransparence = static_cast<sal_Int32>(mAny.get<sal_Int16>()); + + if (GetProperty(rXPropSet, "GraphicColorMode")) + { + drawing::ColorMode aColorMode; + mAny >>= aColorMode; + if (aColorMode == drawing::ColorMode_GREYS) + mpFS->singleElementNS(XML_a, XML_grayscl); + else if (aColorMode == drawing::ColorMode_MONO) + //black/white has a 0,5 threshold in LibreOffice + mpFS->singleElementNS(XML_a, XML_biLevel, XML_thresh, OString::number(50000)); + else if (aColorMode == drawing::ColorMode_WATERMARK) + { + //map watermark with mso washout + nBright = 70; + nContrast = -70; + } + } + + + if (nBright || nContrast) + { + mpFS->singleElementNS(XML_a, XML_lum, + XML_bright, nBright ? OString::number(nBright * 1000).getStr() : nullptr, + XML_contrast, nContrast ? OString::number(nContrast * 1000).getStr() : nullptr); + } + + if (nTransparence) + { + sal_Int32 nAlphaMod = (100 - nTransparence ) * PER_PERCENT; + mpFS->singleElementNS(XML_a, XML_alphaModFix, XML_amt, OString::number(nAlphaMod)); + } +} + +OUString DrawingML::WriteXGraphicBlip(uno::Reference<beans::XPropertySet> const & rXPropSet, + uno::Reference<graphic::XGraphic> const & rxGraphic, + bool bRelPathToMedia) +{ + OUString sRelId; + + if (!rxGraphic.is()) + return sRelId; + + Graphic aGraphic(rxGraphic); + if (mpTextExport) + { + BitmapChecksum nChecksum = aGraphic.GetChecksum(); + sRelId = mpTextExport->FindRelId(nChecksum); + } + if (sRelId.isEmpty()) + { + sRelId = WriteImage(aGraphic, bRelPathToMedia); + if (mpTextExport) + { + BitmapChecksum nChecksum = aGraphic.GetChecksum(); + mpTextExport->CacheRelId(nChecksum, sRelId); + } + } + + mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId.toUtf8()); + + WriteImageBrightnessContrastTransparence(rXPropSet); + + WriteArtisticEffect(rXPropSet); + + mpFS->endElementNS(XML_a, XML_blip); + + return sRelId; +} + +void DrawingML::WriteXGraphicBlipMode(uno::Reference<beans::XPropertySet> const & rXPropSet, + uno::Reference<graphic::XGraphic> const & rxGraphic) +{ + BitmapMode eBitmapMode(BitmapMode_NO_REPEAT); + if (GetProperty(rXPropSet, "FillBitmapMode")) + mAny >>= eBitmapMode; + + SAL_INFO("oox.shape", "fill bitmap mode: " << int(eBitmapMode)); + + switch (eBitmapMode) + { + case BitmapMode_REPEAT: + mpFS->singleElementNS(XML_a, XML_tile); + break; + case BitmapMode_STRETCH: + WriteXGraphicStretch(rXPropSet, rxGraphic); + break; + default: + break; + } +} + +void DrawingML::WriteBlipOrNormalFill( const Reference< XPropertySet >& xPropSet, const OUString& rURLPropName ) +{ + // check for blip and otherwise fall back to normal fill + // we always store normal fill properties but OOXML + // uses a choice between our fill props and BlipFill + if (GetProperty ( xPropSet, rURLPropName )) + WriteBlipFill( xPropSet, rURLPropName ); + else + WriteFill(xPropSet); +} + +void DrawingML::WriteBlipFill( const Reference< XPropertySet >& rXPropSet, const OUString& sURLPropName ) +{ + WriteBlipFill( rXPropSet, sURLPropName, XML_a ); +} + +void DrawingML::WriteBlipFill( const Reference< XPropertySet >& rXPropSet, const OUString& sURLPropName, sal_Int32 nXmlNamespace ) +{ + if ( !GetProperty( rXPropSet, sURLPropName ) ) + return; + + uno::Reference<graphic::XGraphic> xGraphic; + if (mAny.has<uno::Reference<awt::XBitmap>>()) + { + uno::Reference<awt::XBitmap> xBitmap = mAny.get<uno::Reference<awt::XBitmap>>(); + if (xBitmap.is()) + xGraphic.set(xBitmap, uno::UNO_QUERY); + } + else if (mAny.has<uno::Reference<graphic::XGraphic>>()) + { + xGraphic = mAny.get<uno::Reference<graphic::XGraphic>>(); + } + + if (xGraphic.is()) + { + bool bWriteMode = false; + if (sURLPropName == "FillBitmap" || sURLPropName == "BackGraphic") + bWriteMode = true; + WriteXGraphicBlipFill(rXPropSet, xGraphic, nXmlNamespace, bWriteMode); + } +} + +void DrawingML::WriteXGraphicBlipFill(uno::Reference<beans::XPropertySet> const & rXPropSet, + uno::Reference<graphic::XGraphic> const & rxGraphic, + sal_Int32 nXmlNamespace, bool bWriteMode, bool bRelPathToMedia) +{ + if (!rxGraphic.is() ) + return; + + mpFS->startElementNS(nXmlNamespace , XML_blipFill, XML_rotWithShape, "0"); + + WriteXGraphicBlip(rXPropSet, rxGraphic, bRelPathToMedia); + + if (GetDocumentType() != DOCUMENT_DOCX) + { + // Write the crop rectangle of Impress as a source rectangle. + WriteSrcRectXGraphic(rXPropSet, rxGraphic); + } + + if (bWriteMode) + { + WriteXGraphicBlipMode(rXPropSet, rxGraphic); + } + else if(GetProperty(rXPropSet, "FillBitmapStretch")) + { + bool bStretch = mAny.get<bool>(); + + if (bStretch) + { + WriteXGraphicStretch(rXPropSet, rxGraphic); + } + } + mpFS->endElementNS(nXmlNamespace, XML_blipFill); +} + +void DrawingML::WritePattFill( const Reference< XPropertySet >& rXPropSet ) +{ + if ( GetProperty( rXPropSet, "FillHatch" ) ) + { + drawing::Hatch aHatch; + mAny >>= aHatch; + WritePattFill(rXPropSet, aHatch); + } +} + +void DrawingML::WritePattFill(const Reference<XPropertySet>& rXPropSet, const css::drawing::Hatch& rHatch) +{ + mpFS->startElementNS(XML_a, XML_pattFill, XML_prst, GetHatchPattern(rHatch)); + + mpFS->startElementNS(XML_a, XML_fgClr); + WriteColor(::Color(rHatch.Color)); + mpFS->endElementNS( XML_a , XML_fgClr ); + + ::Color nColor = COL_WHITE; + sal_Int32 nAlpha = 0; + + if ( GetProperty( rXPropSet, "FillBackground" ) ) + { + bool isBackgroundFilled = false; + mAny >>= isBackgroundFilled; + if( isBackgroundFilled ) + { + nAlpha = MAX_PERCENT; + + if( GetProperty( rXPropSet, "FillColor" ) ) + { + mAny >>= nColor; + } + } + } + + mpFS->startElementNS(XML_a, XML_bgClr); + WriteColor(nColor, nAlpha); + mpFS->endElementNS( XML_a , XML_bgClr ); + + mpFS->endElementNS( XML_a , XML_pattFill ); +} + +void DrawingML::WriteGraphicCropProperties(uno::Reference<beans::XPropertySet> const & rXPropSet, Size const & rOriginalSize, MapMode const & rMapMode) +{ + if (!GetProperty(rXPropSet, "GraphicCrop")) + return; + + Size aOriginalSize(rOriginalSize); + + // GraphicCrop is in mm100, so in case the original size is in pixels, convert it over. + if (rMapMode.GetMapUnit() == MapUnit::MapPixel) + aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, MapMode(MapUnit::Map100thMM)); + + css::text::GraphicCrop aGraphicCropStruct; + mAny >>= aGraphicCropStruct; + + if ( (0 != aGraphicCropStruct.Left) || (0 != aGraphicCropStruct.Top) || (0 != aGraphicCropStruct.Right) || (0 != aGraphicCropStruct.Bottom) ) + { + mpFS->singleElementNS( XML_a, XML_srcRect, + XML_l, OString::number(rtl::math::round(aGraphicCropStruct.Left * 100000.0 / aOriginalSize.Width())), + XML_t, OString::number(rtl::math::round(aGraphicCropStruct.Top * 100000.0 / aOriginalSize.Height())), + XML_r, OString::number(rtl::math::round(aGraphicCropStruct.Right * 100000.0 / aOriginalSize.Width())), + XML_b, OString::number(rtl::math::round(aGraphicCropStruct.Bottom * 100000.0 / aOriginalSize.Height())) ); + } +} + +void DrawingML::WriteSrcRectXGraphic(uno::Reference<beans::XPropertySet> const & rxPropertySet, + uno::Reference<graphic::XGraphic> const & rxGraphic) +{ + Graphic aGraphic(rxGraphic); + Size aOriginalSize = aGraphic.GetPrefSize(); + const MapMode& rMapMode = aGraphic.GetPrefMapMode(); + WriteGraphicCropProperties(rxPropertySet, aOriginalSize, rMapMode); +} + +void DrawingML::WriteXGraphicStretch(uno::Reference<beans::XPropertySet> const & rXPropSet, + uno::Reference<graphic::XGraphic> const & rxGraphic) +{ + if (GetDocumentType() != DOCUMENT_DOCX) + { + // Limiting the area used for stretching is not supported in Impress. + mpFS->singleElementNS(XML_a, XML_stretch); + return; + } + + mpFS->startElementNS(XML_a, XML_stretch); + + bool bCrop = false; + if (GetProperty(rXPropSet, "GraphicCrop")) + { + css::text::GraphicCrop aGraphicCropStruct; + mAny >>= aGraphicCropStruct; + + if ((0 != aGraphicCropStruct.Left) + || (0 != aGraphicCropStruct.Top) + || (0 != aGraphicCropStruct.Right) + || (0 != aGraphicCropStruct.Bottom)) + { + Graphic aGraphic(rxGraphic); + Size aOriginalSize(aGraphic.GetPrefSize()); + mpFS->singleElementNS(XML_a, XML_fillRect, + XML_l, OString::number(((aGraphicCropStruct.Left) * 100000) / aOriginalSize.Width()), + XML_t, OString::number(((aGraphicCropStruct.Top) * 100000) / aOriginalSize.Height()), + XML_r, OString::number(((aGraphicCropStruct.Right) * 100000) / aOriginalSize.Width()), + XML_b, OString::number(((aGraphicCropStruct.Bottom) * 100000) / aOriginalSize.Height())); + bCrop = true; + } + } + + if (!bCrop) + { + mpFS->singleElementNS(XML_a, XML_fillRect); + } + + mpFS->endElementNS(XML_a, XML_stretch); +} + +void DrawingML::WriteTransformation(const tools::Rectangle& rRect, + sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, sal_Int32 nRotation, bool bIsGroupShape) +{ + + mpFS->startElementNS( nXmlNamespace, XML_xfrm, + XML_flipH, bFlipH ? "1" : nullptr, + XML_flipV, bFlipV ? "1" : nullptr, + XML_rot, (nRotation % 21600000) ? OString::number(nRotation).getStr() : nullptr ); + + sal_Int32 nLeft = rRect.Left(); + sal_Int32 nTop = rRect.Top(); + if (GetDocumentType() == DOCUMENT_DOCX && !m_xParent.is()) + { + nLeft = 0; + nTop = 0; + } + + mpFS->singleElementNS(XML_a, XML_off, + XML_x, OString::number(oox::drawingml::convertHmmToEmu(nLeft)), + XML_y, OString::number(oox::drawingml::convertHmmToEmu(nTop))); + mpFS->singleElementNS(XML_a, XML_ext, + XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())), + XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight()))); + + if (GetDocumentType() != DOCUMENT_DOCX && bIsGroupShape) + { + mpFS->singleElementNS(XML_a, XML_chOff, + XML_x, OString::number(oox::drawingml::convertHmmToEmu(nLeft)), + XML_y, OString::number(oox::drawingml::convertHmmToEmu(nTop))); + mpFS->singleElementNS(XML_a, XML_chExt, + XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())), + XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight()))); + } + + mpFS->endElementNS( nXmlNamespace, XML_xfrm ); +} + +void DrawingML::WriteShapeTransformation( const Reference< XShape >& rXShape, sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, bool bSuppressRotation, bool bSuppressFlipping, bool bFlippedBeforeRotation ) +{ + SAL_INFO("oox.shape", "write shape transformation"); + + sal_Int32 nRotation=0; + awt::Point aPos = rXShape->getPosition(); + awt::Size aSize = rXShape->getSize(); + + bool bFlipHWrite = bFlipH && !bSuppressFlipping; + bool bFlipVWrite = bFlipV && !bSuppressFlipping; + bFlipH = bFlipH && !bFlippedBeforeRotation; + bFlipV = bFlipV && !bFlippedBeforeRotation; + + if (GetDocumentType() == DOCUMENT_DOCX && m_xParent.is()) + { + awt::Point aParentPos = m_xParent->getPosition(); + aPos.X -= aParentPos.X; + aPos.Y -= aParentPos.Y; + } + + if ( aSize.Width < 0 ) + aSize.Width = 1000; + if ( aSize.Height < 0 ) + aSize.Height = 1000; + if (!bSuppressRotation) + { + SdrObject* pShape = GetSdrObjectFromXShape( rXShape ); + nRotation = pShape ? pShape->GetRotateAngle() : 0; + if ( GetDocumentType() != DOCUMENT_DOCX ) + { + int faccos=bFlipV ? -1 : 1; + int facsin=bFlipH ? -1 : 1; + aPos.X-=(1-faccos*cos(nRotation*F_PI18000))*aSize.Width/2-facsin*sin(nRotation*F_PI18000)*aSize.Height/2; + aPos.Y-=(1-faccos*cos(nRotation*F_PI18000))*aSize.Height/2+facsin*sin(nRotation*F_PI18000)*aSize.Width/2; + } + + // The RotateAngle property's value is independent from any flipping, and that's exactly what we need here. + uno::Reference<beans::XPropertySet> xPropertySet(rXShape, uno::UNO_QUERY); + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); + if (xPropertySetInfo->hasPropertyByName("RotateAngle")) + xPropertySet->getPropertyValue("RotateAngle") >>= nRotation; + } + + // OOXML flips shapes before rotating them. + if(bFlipH != bFlipV) + nRotation = nRotation * -1 + 36000; + + WriteTransformation(tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), nXmlNamespace, + bFlipHWrite, bFlipVWrite, ExportRotateClockwisify(nRotation), IsGroupShape( rXShape )); +} + +void DrawingML::WriteRunProperties( const Reference< XPropertySet >& rRun, bool bIsField, sal_Int32 nElement, bool bCheckDirect, + bool& rbOverridingCharHeight, sal_Int32& rnCharHeight, sal_Int16 nScriptType ) +{ + Reference< XPropertySet > rXPropSet = rRun; + Reference< XPropertyState > rXPropState( rRun, UNO_QUERY ); + OUString usLanguage; + PropertyState eState; + bool bComplex = ( nScriptType == css::i18n::ScriptType::COMPLEX ); + const char* bold = "0"; + const char* italic = nullptr; + const char* underline = nullptr; + const char* strikeout = nullptr; + const char* cap = nullptr; + sal_Int32 nSize = 1800; + sal_Int32 nCharEscapement = 0; + sal_Int32 nCharKerning = 0; + + if ( nElement == XML_endParaRPr && rbOverridingCharHeight ) + { + nSize = rnCharHeight; + } + else if (GetProperty(rXPropSet, "CharHeight")) + { + nSize = static_cast<sal_Int32>(100*(*o3tl::doAccess<float>(mAny))); + if ( nElement == XML_rPr ) + { + rbOverridingCharHeight = true; + rnCharHeight = nSize; + } + } + + if (GetProperty(rXPropSet, "CharKerning")) + nCharKerning = static_cast<sal_Int32>(*o3tl::doAccess<sal_Int16>(mAny)); + /** While setting values in propertymap, + * CharKerning converted using GetTextSpacingPoint + * i.e set @ https://opengrok.libreoffice.org/xref/core/oox/source/drawingml/textcharacterproperties.cxx#129 + * therefore to get original value CharKerning need to be convert. + * https://opengrok.libreoffice.org/xref/core/oox/source/drawingml/drawingmltypes.cxx#95 + **/ + nCharKerning = ((nCharKerning * 720)-360) / 254; + + if ((bComplex && GetProperty(rXPropSet, "CharWeightComplex")) + || GetProperty(rXPropSet, "CharWeight")) + { + if ( *o3tl::doAccess<float>(mAny) >= awt::FontWeight::SEMIBOLD ) + bold = "1"; + } + + if ((bComplex && GetProperty(rXPropSet, "CharPostureComplex")) + || GetProperty(rXPropSet, "CharPosture")) + switch ( *o3tl::doAccess<awt::FontSlant>(mAny) ) + { + case awt::FontSlant_OBLIQUE : + case awt::FontSlant_ITALIC : + italic = "1"; + break; + default: + break; + } + + if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharUnderline", eState) + && eState == beans::PropertyState_DIRECT_VALUE) + || GetProperty(rXPropSet, "CharUnderline")) + { + switch ( *o3tl::doAccess<sal_Int16>(mAny) ) + { + case awt::FontUnderline::SINGLE : + underline = "sng"; + break; + case awt::FontUnderline::DOUBLE : + underline = "dbl"; + break; + case awt::FontUnderline::DOTTED : + underline = "dotted"; + break; + case awt::FontUnderline::DASH : + underline = "dash"; + break; + case awt::FontUnderline::LONGDASH : + underline = "dashLong"; + break; + case awt::FontUnderline::DASHDOT : + underline = "dotDash"; + break; + case awt::FontUnderline::DASHDOTDOT : + underline = "dotDotDash"; + break; + case awt::FontUnderline::WAVE : + underline = "wavy"; + break; + case awt::FontUnderline::DOUBLEWAVE : + underline = "wavyDbl"; + break; + case awt::FontUnderline::BOLD : + underline = "heavy"; + break; + case awt::FontUnderline::BOLDDOTTED : + underline = "dottedHeavy"; + break; + case awt::FontUnderline::BOLDDASH : + underline = "dashHeavy"; + break; + case awt::FontUnderline::BOLDLONGDASH : + underline = "dashLongHeavy"; + break; + case awt::FontUnderline::BOLDDASHDOT : + underline = "dotDashHeavy"; + break; + case awt::FontUnderline::BOLDDASHDOTDOT : + underline = "dotDotDashHeavy"; + break; + case awt::FontUnderline::BOLDWAVE : + underline = "wavyHeavy"; + break; + } + } + + if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharStrikeout", eState) + && eState == beans::PropertyState_DIRECT_VALUE) + || GetProperty(rXPropSet, "CharStrikeout")) + { + switch ( *o3tl::doAccess<sal_Int16>(mAny) ) + { + case awt::FontStrikeout::NONE : + strikeout = "noStrike"; + break; + case awt::FontStrikeout::SINGLE : + // LibO supports further values of character + // strikeout, OOXML standard (20.1.10.78, + // ST_TextStrikeType) however specifies only + // 3 - single, double and none. Approximate + // the remaining ones by single strike (better + // some strike than none at all). + // TODO: figure out how to do this better + case awt::FontStrikeout::BOLD : + case awt::FontStrikeout::SLASH : + case awt::FontStrikeout::X : + strikeout = "sngStrike"; + break; + case awt::FontStrikeout::DOUBLE : + strikeout = "dblStrike"; + break; + } + } + + bool bLang = false; + switch(nScriptType) + { + case css::i18n::ScriptType::ASIAN: + bLang = GetProperty(rXPropSet, "CharLocaleAsian"); break; + case css::i18n::ScriptType::COMPLEX: + bLang = GetProperty(rXPropSet, "CharLocaleComplex"); break; + default: + bLang = GetProperty(rXPropSet, "CharLocale"); break; + } + + if (bLang) + { + css::lang::Locale aLocale; + mAny >>= aLocale; + LanguageTag aLanguageTag( aLocale); + if (!aLanguageTag.isSystemLocale()) + usLanguage = aLanguageTag.getBcp47MS(); + } + + if (GetPropertyAndState(rXPropSet, rXPropState, "CharEscapement", eState) + && eState == beans::PropertyState_DIRECT_VALUE) + mAny >>= nCharEscapement; + + if (nCharEscapement + && (GetPropertyAndState(rXPropSet, rXPropState, "CharEscapementHeight", eState) + && eState == beans::PropertyState_DIRECT_VALUE)) + { + sal_uInt32 nCharEscapementHeight = 0; + mAny >>= nCharEscapementHeight; + nSize = (nSize * nCharEscapementHeight) / 100; + // MSO uses default ~58% size + nSize = (nSize / 0.58); + } + + if (GetProperty(rXPropSet, "CharCaseMap")) + { + switch ( *o3tl::doAccess<sal_Int16>(mAny) ) + { + case CaseMap::UPPERCASE : + cap = "all"; + break; + case CaseMap::SMALLCAPS : + cap = "small"; + break; + } + } + + mpFS->startElementNS( XML_a, nElement, + XML_b, bold, + XML_i, italic, + XML_lang, usLanguage.isEmpty() ? nullptr : usLanguage.toUtf8().getStr(), + XML_sz, OString::number(nSize), + // For Condensed character spacing spc value is negative. + XML_spc, nCharKerning ? OString::number(nCharKerning).getStr() : nullptr, + XML_strike, strikeout, + XML_u, underline, + XML_baseline, nCharEscapement == 0 ? nullptr : OString::number(nCharEscapement*1000).getStr(), + XML_cap, cap ); + + // mso doesn't like text color to be placed after typeface + if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharColor", eState) + && eState == beans::PropertyState_DIRECT_VALUE) + || GetProperty(rXPropSet, "CharColor")) + { + ::Color color( *o3tl::doAccess<sal_uInt32>(mAny) ); + SAL_INFO("oox.shape", "run color: " << sal_uInt32(color) << " auto: " << sal_uInt32(COL_AUTO)); + + // WriteSolidFill() handles MAX_PERCENT as "no transparency". + sal_Int32 nTransparency = MAX_PERCENT; + if (rXPropSet->getPropertySetInfo()->hasPropertyByName("CharTransparence")) + { + rXPropSet->getPropertyValue("CharTransparence") >>= nTransparency; + // UNO scale is 0..100, OOXML scale is 0..100000; also UNO tracks transparency, OOXML + // tracks opacity. + nTransparency = MAX_PERCENT - (nTransparency * PER_PERCENT); + } + + // tdf#104219 In LibreOffice and MS Office, there are two types of colors: + // Automatic and Fixed. OOXML is setting automatic color, by not providing color. + if( color != COL_AUTO ) + { + color.SetTransparency(0); + // TODO: special handle embossed/engraved + WriteSolidFill(color, nTransparency); + } + } + + // tdf#128096, exporting XML_highlight to docx already works fine, + // so make sure this code is only run when exporting to pptx, just in case + if (GetDocumentType() == DOCUMENT_PPTX) + { + if (GetProperty(rXPropSet, "CharBackColor")) + { + ::Color color(*o3tl::doAccess<sal_uInt32>(mAny)); + if( color != COL_AUTO ) + { + mpFS->startElementNS(XML_a, XML_highlight); + WriteColor( color ); + mpFS->endElementNS( XML_a, XML_highlight ); + } + } + } + + if (underline + && ((bCheckDirect + && GetPropertyAndState(rXPropSet, rXPropState, "CharUnderlineColor", eState) + && eState == beans::PropertyState_DIRECT_VALUE) + || GetProperty(rXPropSet, "CharUnderlineColor"))) + { + ::Color color(*o3tl::doAccess<sal_uInt32>(mAny)); + // if color is automatic, then we shouldn't write information about color but to take color from character + if( color != COL_AUTO ) + { + mpFS->startElementNS(XML_a, XML_uFill); + WriteSolidFill( color ); + mpFS->endElementNS( XML_a, XML_uFill ); + } + else + { + mpFS->singleElementNS(XML_a, XML_uFillTx); + } + } + + if (GetProperty(rXPropSet, "CharFontName")) + { + const char* const pitch = nullptr; + const char* const charset = nullptr; + OUString usTypeface; + + mAny >>= usTypeface; + OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) ); + if (!aSubstName.isEmpty()) + usTypeface = aSubstName; + + mpFS->singleElementNS( XML_a, XML_latin, + XML_typeface, usTypeface.toUtf8(), + XML_pitchFamily, pitch, + XML_charset, charset ); + } + + if ((bComplex + && (GetPropertyAndState(rXPropSet, rXPropState, "CharFontNameComplex", eState) + && eState == beans::PropertyState_DIRECT_VALUE)) + || (!bComplex + && (GetPropertyAndState(rXPropSet, rXPropState, "CharFontNameAsian", eState) + && eState == beans::PropertyState_DIRECT_VALUE))) + { + const char* const pitch = nullptr; + const char* const charset = nullptr; + OUString usTypeface; + + mAny >>= usTypeface; + OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) ); + if (!aSubstName.isEmpty()) + usTypeface = aSubstName; + + mpFS->singleElementNS( XML_a, bComplex ? XML_cs : XML_ea, + XML_typeface, usTypeface.toUtf8(), + XML_pitchFamily, pitch, + XML_charset, charset ); + } + + if( bIsField ) + { + Reference< XTextField > rXTextField; + if (GetProperty(rXPropSet, "TextField")) + mAny >>= rXTextField; + if( rXTextField.is() ) + rXPropSet.set( rXTextField, UNO_QUERY ); + } + + // field properties starts here + if (GetProperty(rXPropSet, "URL")) + { + OUString sURL; + + mAny >>= sURL; + if( !sURL.isEmpty() ) { + OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(), + oox::getRelationship(Relationship::HYPERLINK), + sURL, true ); + + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId.toUtf8()); + } + } + + mpFS->endElementNS( XML_a, nElement ); +} + +OUString DrawingML::GetFieldValue( const css::uno::Reference< css::text::XTextRange >& rRun, bool& bIsURLField ) +{ + Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY ); + OUString aFieldType, aFieldValue; + + if (GetProperty(rXPropSet, "TextPortionType")) + { + aFieldType = *o3tl::doAccess<OUString>(mAny); + SAL_INFO("oox.shape", "field type: " << aFieldType); + } + + if( aFieldType == "TextField" ) + { + Reference< XTextField > rXTextField; + if (GetProperty(rXPropSet, "TextField")) + mAny >>= rXTextField; + if( rXTextField.is() ) + { + rXPropSet.set( rXTextField, UNO_QUERY ); + if( rXPropSet.is() ) + { + OUString aFieldKind( rXTextField->getPresentation( true ) ); + SAL_INFO("oox.shape", "field kind: " << aFieldKind); + if( aFieldKind == "Page" ) + { + aFieldValue = "slidenum"; + } + else if( aFieldKind == "Pages" ) + { + aFieldValue = "slidecount"; + } + else if( aFieldKind == "PageName" ) + { + aFieldValue = "slidename"; + } + else if( aFieldKind == "URL" ) + { + bIsURLField = true; + if (GetProperty(rXPropSet, "Representation")) + mAny >>= aFieldValue; + + } + else if(aFieldKind == "Date") + { + sal_Int32 nNumFmt = -1; + rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt; + switch(static_cast<SvxDateFormat>(nNumFmt)) + { + case SvxDateFormat::StdSmall: + case SvxDateFormat::A: aFieldValue = "datetime"; // 13/02/96 + break; + case SvxDateFormat::B: aFieldValue = "datetime1"; // 13/02/1996 + break; + case SvxDateFormat::StdBig: + case SvxDateFormat::D: aFieldValue = "datetime3"; // 13 February 1996 + break; + default: break; + } + } + else if(aFieldKind == "ExtTime") + { + sal_Int32 nNumFmt = -1; + rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt; + switch(static_cast<SvxTimeFormat>(nNumFmt)) + { + case SvxTimeFormat::Standard: + case SvxTimeFormat::HH24_MM_SS: + aFieldValue = "datetime11"; // 13:49:38 + break; + case SvxTimeFormat::HH24_MM: + aFieldValue = "datetime10"; // 13:49 + break; + case SvxTimeFormat::HH12_MM: + aFieldValue = "datetime12"; // 01:49 PM + break; + case SvxTimeFormat::HH12_MM_SS: + aFieldValue = "datetime13"; // 01:49:38 PM + break; + default: break; + } + } + else if(aFieldKind == "ExtFile") + { + sal_Int32 nNumFmt = -1; + rXPropSet->getPropertyValue(UNO_TC_PROP_FILE_FORMAT) >>= nNumFmt; + switch(nNumFmt) + { + case 0: aFieldValue = "file"; // Path/File name + break; + case 1: aFieldValue = "file1"; // Path + break; + case 2: aFieldValue = "file2"; // File name without extension + break; + case 3: aFieldValue = "file3"; // File name with extension + } + } + else if(aFieldKind == "Author") + { + aFieldValue = "author"; + } + } + } + } + return aFieldValue; +} + +void DrawingML::WriteRun( const Reference< XTextRange >& rRun, + bool& rbOverridingCharHeight, sal_Int32& rnCharHeight) +{ + Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY ); + sal_Int16 nLevel = -1; + if (GetProperty(rXPropSet, "NumberingLevel")) + mAny >>= nLevel; + + bool bNumberingIsNumber = true; + if (GetProperty(rXPropSet, "NumberingIsNumber")) + mAny >>= bNumberingIsNumber; + + bool bIsURLField = false; + OUString sFieldValue = GetFieldValue( rRun, bIsURLField ); + bool bWriteField = !( sFieldValue.isEmpty() || bIsURLField ); + + OUString sText = rRun->getString(); + + //if there is no text following the bullet, add a space after the bullet + if (nLevel !=-1 && bNumberingIsNumber && sText.isEmpty() ) + sText=" "; + + if ( bIsURLField ) + sText = sFieldValue; + + if( sText.isEmpty()) + { + Reference< XPropertySet > xPropSet( rRun, UNO_QUERY ); + + try + { + if( !xPropSet.is() || !( xPropSet->getPropertyValue( "PlaceholderText" ) >>= sText ) ) + return; + if( sText.isEmpty() ) + return; + } + catch (const Exception &) + { + return; + } + } + + if (sText == "\n") + { + mpFS->singleElementNS(XML_a, XML_br); + } + else + { + if( bWriteField ) + { + OString sUUID(comphelper::xml::generateGUIDString()); + mpFS->startElementNS( XML_a, XML_fld, + XML_id, sUUID.getStr(), + XML_type, sFieldValue.toUtf8() ); + } + else + { + mpFS->startElementNS(XML_a, XML_r); + } + + Reference< XPropertySet > xPropSet( rRun, uno::UNO_QUERY ); + + WriteRunProperties( xPropSet, bIsURLField, XML_rPr, true, rbOverridingCharHeight, rnCharHeight, GetScriptType(sText) ); + mpFS->startElementNS(XML_a, XML_t); + mpFS->writeEscaped( sText ); + mpFS->endElementNS( XML_a, XML_t ); + + if( bWriteField ) + mpFS->endElementNS( XML_a, XML_fld ); + else + mpFS->endElementNS( XML_a, XML_r ); + } +} + +static OUString GetAutoNumType(SvxNumType nNumberingType, bool bSDot, bool bPBehind, bool bPBoth) +{ + OUString sPrefixSuffix; + + if (bPBoth) + sPrefixSuffix = "ParenBoth"; + else if (bPBehind) + sPrefixSuffix = "ParenR"; + else if (bSDot) + sPrefixSuffix = "Period"; + + switch( nNumberingType ) + { + case SVX_NUM_CHARS_UPPER_LETTER_N : + case SVX_NUM_CHARS_UPPER_LETTER : + return "alphaUc" + sPrefixSuffix; + + case SVX_NUM_CHARS_LOWER_LETTER_N : + case SVX_NUM_CHARS_LOWER_LETTER : + return "alphaLc" + sPrefixSuffix; + + case SVX_NUM_ROMAN_UPPER : + return "romanUc" + sPrefixSuffix; + + case SVX_NUM_ROMAN_LOWER : + return "romanLc" + sPrefixSuffix; + + case SVX_NUM_ARABIC : + { + if (sPrefixSuffix.isEmpty()) + return "arabicPlain"; + else + return "arabic" + sPrefixSuffix; + } + default: + break; + } + + return OUString(); +} + +void DrawingML::WriteParagraphNumbering(const Reference< XPropertySet >& rXPropSet, float fFirstCharHeight, sal_Int16 nLevel ) +{ + if (nLevel < 0 || !GetProperty(rXPropSet, "NumberingRules")) + return; + + Reference< XIndexAccess > rXIndexAccess; + + if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount()) + return; + + SAL_INFO("oox.shape", "numbering rules"); + + Sequence<PropertyValue> aPropertySequence; + rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence; + + if (!aPropertySequence.hasElements()) + return; + + SvxNumType nNumberingType = SVX_NUM_NUMBER_NONE; + bool bSDot = false; + bool bPBehind = false; + bool bPBoth = false; + sal_Unicode aBulletChar = 0x2022; // a bullet + awt::FontDescriptor aFontDesc; + bool bHasFontDesc = false; + uno::Reference<graphic::XGraphic> xGraphic; + sal_Int16 nBulletRelSize = 0; + sal_Int16 nStartWith = 1; + ::Color nBulletColor; + bool bHasBulletColor = false; + awt::Size aGraphicSize; + + for ( const PropertyValue& rPropValue : std::as_const(aPropertySequence) ) + { + OUString aPropName( rPropValue.Name ); + SAL_INFO("oox.shape", "pro name: " << aPropName); + if ( aPropName == "NumberingType" ) + { + nNumberingType = static_cast<SvxNumType>(*o3tl::doAccess<sal_Int16>(rPropValue.Value)); + } + else if ( aPropName == "Prefix" ) + { + if( *o3tl::doAccess<OUString>(rPropValue.Value) == ")") + bPBoth = true; + } + else if ( aPropName == "Suffix" ) + { + auto s = o3tl::doAccess<OUString>(rPropValue.Value); + if( *s == ".") + bSDot = true; + else if( *s == ")") + bPBehind = true; + } + else if(aPropName == "BulletColor") + { + nBulletColor = ::Color(*o3tl::doAccess<sal_uInt32>(rPropValue.Value)); + bHasBulletColor = true; + } + else if ( aPropName == "BulletChar" ) + { + aBulletChar = (*o3tl::doAccess<OUString>(rPropValue.Value))[ 0 ]; + } + else if ( aPropName == "BulletFont" ) + { + aFontDesc = *o3tl::doAccess<awt::FontDescriptor>(rPropValue.Value); + bHasFontDesc = true; + + // Our numbullet dialog has set the wrong textencoding for our "StarSymbol" font, + // instead of a Unicode encoding the encoding RTL_TEXTENCODING_SYMBOL was used. + // Because there might exist a lot of damaged documemts I added this two lines + // which fixes the bullet problem for the export. + if ( aFontDesc.Name.equalsIgnoreAsciiCase("StarSymbol") ) + aFontDesc.CharSet = RTL_TEXTENCODING_MS_1252; + + } + else if ( aPropName == "BulletRelSize" ) + { + nBulletRelSize = *o3tl::doAccess<sal_Int16>(rPropValue.Value); + } + else if ( aPropName == "StartWith" ) + { + nStartWith = *o3tl::doAccess<sal_Int16>(rPropValue.Value); + } + else if (aPropName == "GraphicBitmap") + { + auto xBitmap = rPropValue.Value.get<uno::Reference<awt::XBitmap>>(); + xGraphic.set(xBitmap, uno::UNO_QUERY); + } + else if ( aPropName == "GraphicSize" ) + { + aGraphicSize = *o3tl::doAccess<awt::Size>(rPropValue.Value); + SAL_INFO("oox.shape", "graphic size: " << aGraphicSize.Width << "x" << aGraphicSize.Height); + } + } + + if (nNumberingType == SVX_NUM_NUMBER_NONE) + return; + + Graphic aGraphic(xGraphic); + if (xGraphic.is() && aGraphic.GetType() != GraphicType::NONE) + { + long nFirstCharHeightMm = TransformMetric(fFirstCharHeight * 100.f, FieldUnit::POINT, FieldUnit::MM); + float fBulletSizeRel = aGraphicSize.Height / static_cast<float>(nFirstCharHeightMm) / OOX_BULLET_LIST_SCALE_FACTOR; + + OUString sRelationId; + + if (fBulletSizeRel < 1.0f) + { + // Add padding to get the bullet point centered in PPT + Size aDestSize(64, 64); + float fBulletSizeRelX = fBulletSizeRel / aGraphicSize.Height * aGraphicSize.Width; + long nPaddingX = std::max<long>(0, std::lround((aDestSize.Width() - fBulletSizeRelX * aDestSize.Width()) / 2.f)); + long nPaddingY = std::lround((aDestSize.Height() - fBulletSizeRel * aDestSize.Height()) / 2.f); + tools::Rectangle aDestRect(nPaddingX, nPaddingY, aDestSize.Width() - nPaddingX, aDestSize.Height() - nPaddingY); + + AlphaMask aMask(aDestSize); + aMask.Erase(255); + BitmapEx aSourceBitmap(aGraphic.GetBitmapEx()); + aSourceBitmap.Scale(aDestRect.GetSize()); + tools::Rectangle aSourceRect(Point(0, 0), aDestRect.GetSize()); + BitmapEx aDestBitmap(Bitmap(aDestSize, 24), aMask); + aDestBitmap.CopyPixel(aDestRect, aSourceRect, &aSourceBitmap); + Graphic aDestGraphic(aDestBitmap); + sRelationId = WriteImage(aDestGraphic); + fBulletSizeRel = 1.0f; + } + else + { + sRelationId = WriteImage(aGraphic); + } + + mpFS->singleElementNS( XML_a, XML_buSzPct, + XML_val, OString::number(std::min<sal_Int32>(std::lround(100000.f * fBulletSizeRel), 400000))); + mpFS->startElementNS(XML_a, XML_buBlip); + mpFS->singleElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelationId.toUtf8()); + mpFS->endElementNS( XML_a, XML_buBlip ); + } + else + { + if(bHasBulletColor) + { + if (nBulletColor == COL_AUTO ) + { + nBulletColor = ::Color(mbIsBackgroundDark ? 0xffffff : 0x000000); + } + mpFS->startElementNS(XML_a, XML_buClr); + WriteColor( nBulletColor ); + mpFS->endElementNS( XML_a, XML_buClr ); + } + + if( nBulletRelSize && nBulletRelSize != 100 ) + mpFS->singleElementNS( XML_a, XML_buSzPct, + XML_val, OString::number(std::clamp<sal_Int32>(1000*nBulletRelSize, 25000, 400000))); + if( bHasFontDesc ) + { + if ( SVX_NUM_CHAR_SPECIAL == nNumberingType ) + aBulletChar = SubstituteBullet( aBulletChar, aFontDesc ); + mpFS->singleElementNS( XML_a, XML_buFont, + XML_typeface, aFontDesc.Name.toUtf8(), + XML_charset, (aFontDesc.CharSet == awt::CharSet::SYMBOL) ? "2" : nullptr ); + } + + OUString aAutoNumType = GetAutoNumType( nNumberingType, bSDot, bPBehind, bPBoth ); + + if (!aAutoNumType.isEmpty()) + { + mpFS->singleElementNS(XML_a, XML_buAutoNum, + XML_type, aAutoNumType.toUtf8(), + XML_startAt, nStartWith > 1 ? OString::number(nStartWith).getStr() : nullptr); + } + else + { + mpFS->singleElementNS(XML_a, XML_buChar, XML_char, OUString(aBulletChar).toUtf8()); + } + } +} + +void DrawingML::WriteParagraphTabStops(const Reference<XPropertySet>& rXPropSet) +{ + css::uno::Sequence<css::style::TabStop> aTabStops; + if (GetProperty(rXPropSet, "ParaTabStops")) + aTabStops = *o3tl::doAccess<css::uno::Sequence<css::style::TabStop>>(mAny); + + if (aTabStops.getLength() > 0) + mpFS->startElementNS(XML_a, XML_tabLst); + + for (const css::style::TabStop& rTabStop : std::as_const(aTabStops)) + { + OString sPosition = OString::number(GetPointFromCoordinate(rTabStop.Position)); + OString sAlignment; + switch (rTabStop.Alignment) + { + case css::style::TabAlign_DECIMAL: + sAlignment = "dec"; + break; + case css::style::TabAlign_RIGHT: + sAlignment = "r"; + break; + case css::style::TabAlign_CENTER: + sAlignment = "ctr"; + break; + case css::style::TabAlign_LEFT: + default: + sAlignment = "l"; + } + mpFS->singleElementNS(XML_a, XML_tab, XML_algn, sAlignment, XML_pos, sPosition); + } + if (aTabStops.getLength() > 0) + mpFS->endElementNS(XML_a, XML_tabLst); +} + +bool DrawingML::IsGroupShape( const Reference< XShape >& rXShape ) +{ + bool bRet = false; + if ( rXShape.is() ) + { + uno::Reference<lang::XServiceInfo> xServiceInfo(rXShape, uno::UNO_QUERY_THROW); + bRet = xServiceInfo->supportsService("com.sun.star.drawing.GroupShape"); + } + return bRet; +} + +bool DrawingML::IsDiagram(const Reference<XShape>& rXShape) +{ + uno::Reference<beans::XPropertySet> xPropSet(rXShape, uno::UNO_QUERY); + if (!xPropSet.is()) + return false; + + // if the shape doesn't have the InteropGrabBag property, it's not a diagram + uno::Reference<beans::XPropertySetInfo> xPropSetInfo = xPropSet->getPropertySetInfo(); + OUString aName = UNO_NAME_MISC_OBJ_INTEROPGRABBAG; + if (!xPropSetInfo->hasPropertyByName(aName)) + return false; + + uno::Sequence<beans::PropertyValue> propList; + xPropSet->getPropertyValue(aName) >>= propList; + return std::any_of(std::cbegin(propList), std::cend(propList), + [](const beans::PropertyValue& rProp) { + // if we find any of the diagram components, it's a diagram + OUString propName = rProp.Name; + return propName == "OOXData" || propName == "OOXLayout" || propName == "OOXStyle" + || propName == "OOXColor" || propName == "OOXDrawing"; + }); +} + +sal_Int32 DrawingML::getBulletMarginIndentation (const Reference< XPropertySet >& rXPropSet,sal_Int16 nLevel, const OUString& propName) +{ + if (nLevel < 0 || !GetProperty(rXPropSet, "NumberingRules")) + return 0; + + Reference< XIndexAccess > rXIndexAccess; + + if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount()) + return 0; + + SAL_INFO("oox.shape", "numbering rules"); + + Sequence<PropertyValue> aPropertySequence; + rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence; + + if (!aPropertySequence.hasElements()) + return 0; + + for ( const PropertyValue& rPropValue : std::as_const(aPropertySequence) ) + { + OUString aPropName( rPropValue.Name ); + SAL_INFO("oox.shape", "pro name: " << aPropName); + if ( aPropName == propName ) + return *o3tl::doAccess<sal_Int32>(rPropValue.Value); + } + + return 0; +} + +const char* DrawingML::GetAlignment( style::ParagraphAdjust nAlignment ) +{ + const char* sAlignment = nullptr; + + switch( nAlignment ) + { + case style::ParagraphAdjust_CENTER: + sAlignment = "ctr"; + break; + case style::ParagraphAdjust_RIGHT: + sAlignment = "r"; + break; + case style::ParagraphAdjust_BLOCK: + sAlignment = "just"; + break; + default: + ; + } + + return sAlignment; +} + +void DrawingML::WriteLinespacing( const LineSpacing& rSpacing ) +{ + if( rSpacing.Mode == LineSpacingMode::PROP ) + { + mpFS->singleElementNS( XML_a, XML_spcPct, + XML_val, OString::number(static_cast<sal_Int32>(rSpacing.Height)*1000)); + } + else + { + mpFS->singleElementNS( XML_a, XML_spcPts, + XML_val, OString::number(std::lround(rSpacing.Height / 25.4 * 72))); + } +} + +void DrawingML::WriteParagraphProperties( const Reference< XTextContent >& rParagraph, float fFirstCharHeight) +{ + Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY ); + Reference< XPropertyState > rXPropState( rParagraph, UNO_QUERY ); + PropertyState eState; + + if( !rXPropSet.is() || !rXPropState.is() ) + return; + + sal_Int16 nLevel = -1; + if (GetProperty(rXPropSet, "NumberingLevel")) + mAny >>= nLevel; + + sal_Int16 nTmp = sal_Int16(style::ParagraphAdjust_LEFT); + if (GetProperty(rXPropSet, "ParaAdjust")) + mAny >>= nTmp; + style::ParagraphAdjust nAlignment = static_cast<style::ParagraphAdjust>(nTmp); + + bool bHasLinespacing = false; + LineSpacing aLineSpacing; + if (GetPropertyAndState(rXPropSet, rXPropState, "ParaLineSpacing", eState) + && eState == beans::PropertyState_DIRECT_VALUE) + bHasLinespacing = ( mAny >>= aLineSpacing ); + + bool bRtl = false; + if (GetProperty(rXPropSet, "WritingMode")) + { + sal_Int16 nWritingMode; + if( ( mAny >>= nWritingMode ) && nWritingMode == text::WritingMode2::RL_TB ) + { + bRtl = true; + } + } + + sal_Int32 nParaLeftMargin = 0; + sal_Int32 nParaFirstLineIndent = 0; + + if (GetProperty(rXPropSet, "ParaLeftMargin")) + mAny >>= nParaLeftMargin; + if (GetProperty(rXPropSet, "ParaFirstLineIndent")) + mAny >>= nParaFirstLineIndent; + + sal_Int32 nParaTopMargin = 0; + sal_Int32 nParaBottomMargin = 0; + + if (GetProperty(rXPropSet, "ParaTopMargin")) + mAny >>= nParaTopMargin; + if (GetProperty(rXPropSet, "ParaBottomMargin")) + mAny >>= nParaBottomMargin; + + sal_Int32 nLeftMargin = getBulletMarginIndentation ( rXPropSet, nLevel,"LeftMargin"); + sal_Int32 nLineIndentation = getBulletMarginIndentation ( rXPropSet, nLevel,"FirstLineOffset"); + + if( !(nLevel != -1 + || nAlignment != style::ParagraphAdjust_LEFT + || bHasLinespacing) ) + return; + + if (nParaLeftMargin) // For Paragraph + mpFS->startElementNS( XML_a, XML_pPr, + XML_lvl, nLevel > 0 ? OString::number(nLevel).getStr() : nullptr, + XML_marL, nParaLeftMargin > 0 ? OString::number(oox::drawingml::convertHmmToEmu(nParaLeftMargin)).getStr() : nullptr, + XML_indent, nParaFirstLineIndent ? OString::number(oox::drawingml::convertHmmToEmu(nParaFirstLineIndent)).getStr() : nullptr, + XML_algn, GetAlignment( nAlignment ), + XML_rtl, bRtl ? ToPsz10(bRtl) : nullptr ); + else + mpFS->startElementNS( XML_a, XML_pPr, + XML_lvl, nLevel > 0 ? OString::number(nLevel).getStr() : nullptr, + XML_marL, nLeftMargin > 0 ? OString::number(oox::drawingml::convertHmmToEmu(nLeftMargin)).getStr() : nullptr, + XML_indent, nLineIndentation ? OString::number(oox::drawingml::convertHmmToEmu(nLineIndentation)).getStr() : nullptr, + XML_algn, GetAlignment( nAlignment ), + XML_rtl, bRtl ? ToPsz10(bRtl) : nullptr ); + + + if( bHasLinespacing ) + { + mpFS->startElementNS(XML_a, XML_lnSpc); + WriteLinespacing( aLineSpacing ); + mpFS->endElementNS( XML_a, XML_lnSpc ); + } + + if( nParaTopMargin != 0 ) + { + mpFS->startElementNS(XML_a, XML_spcBef); + { + mpFS->singleElementNS( XML_a, XML_spcPts, + XML_val, OString::number(std::lround(nParaTopMargin / 25.4 * 72))); + } + mpFS->endElementNS( XML_a, XML_spcBef ); + } + + if( nParaBottomMargin != 0 ) + { + mpFS->startElementNS(XML_a, XML_spcAft); + { + mpFS->singleElementNS( XML_a, XML_spcPts, + XML_val, OString::number(std::lround(nParaBottomMargin / 25.4 * 72))); + } + mpFS->endElementNS( XML_a, XML_spcAft ); + } + + WriteParagraphNumbering( rXPropSet, fFirstCharHeight, nLevel ); + + WriteParagraphTabStops( rXPropSet ); + + mpFS->endElementNS( XML_a, XML_pPr ); +} + +void DrawingML::WriteParagraph( const Reference< XTextContent >& rParagraph, + bool& rbOverridingCharHeight, sal_Int32& rnCharHeight ) +{ + Reference< XEnumerationAccess > access( rParagraph, UNO_QUERY ); + if( !access.is() ) + return; + + Reference< XEnumeration > enumeration( access->createEnumeration() ); + if( !enumeration.is() ) + return; + + mpFS->startElementNS(XML_a, XML_p); + + bool bPropertiesWritten = false; + while( enumeration->hasMoreElements() ) + { + Reference< XTextRange > run; + Any any ( enumeration->nextElement() ); + + if (any >>= run) + { + if( !bPropertiesWritten ) + { + float fFirstCharHeight = rnCharHeight / 1000.; + Reference< XPropertySet > xFirstRunPropSet (run, UNO_QUERY); + Reference< XPropertySetInfo > xFirstRunPropSetInfo = xFirstRunPropSet->getPropertySetInfo(); + if( xFirstRunPropSetInfo->hasPropertyByName("CharHeight") ) + fFirstCharHeight = xFirstRunPropSet->getPropertyValue("CharHeight").get<float>(); + WriteParagraphProperties( rParagraph, fFirstCharHeight ); + bPropertiesWritten = true; + } + WriteRun( run, rbOverridingCharHeight, rnCharHeight ); + } + } + Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY ); + WriteRunProperties( rXPropSet, false, XML_endParaRPr, false, rbOverridingCharHeight, rnCharHeight ); + + mpFS->endElementNS( XML_a, XML_p ); +} + +void DrawingML::WriteText( const Reference< XInterface >& rXIface, const OUString& presetWarp, bool bBodyPr, bool bText, sal_Int32 nXmlNamespace ) +{ + Reference< XText > xXText( rXIface, UNO_QUERY ); + Reference< XPropertySet > rXPropSet( rXIface, UNO_QUERY ); + + if( !xXText.is() ) + return; + + sal_Int32 nTextPreRotateAngle = 0; + double nTextRotateAngle = 0; + bool bIsFontworkShape(presetWarp.startsWith("text") && (presetWarp != "textNoShape")); + +#define DEFLRINS 254 +#define DEFTBINS 127 + sal_Int32 nLeft, nRight, nTop, nBottom; + nLeft = nRight = DEFLRINS; + nTop = nBottom = DEFTBINS; + + // top inset looks a bit different compared to ppt export + // check if something related doesn't work as expected + if (GetProperty(rXPropSet, "TextLeftDistance")) + mAny >>= nLeft; + if (GetProperty(rXPropSet, "TextRightDistance")) + mAny >>= nRight; + if (GetProperty(rXPropSet, "TextUpperDistance")) + mAny >>= nTop; + if (GetProperty(rXPropSet, "TextLowerDistance")) + mAny >>= nBottom; + + TextVerticalAdjust eVerticalAlignment( TextVerticalAdjust_TOP ); + const char* sVerticalAlignment = nullptr; + if (GetProperty(rXPropSet, "TextVerticalAdjust")) + mAny >>= eVerticalAlignment; + if( eVerticalAlignment != TextVerticalAdjust_TOP ) + sVerticalAlignment = GetTextVerticalAdjust(eVerticalAlignment); + + const char* sWritingMode = nullptr; + bool bVertical = false; + if (GetProperty(rXPropSet, "TextWritingMode")) + { + WritingMode eMode; + + if( ( mAny >>= eMode ) && eMode == WritingMode_TB_RL ) + { + sWritingMode = "eaVert"; + bVertical = true; + } + } + + Sequence<drawing::EnhancedCustomShapeAdjustmentValue> aAdjustmentSeq; + uno::Sequence<beans::PropertyValue> aTextPathSeq; + bool bScaleX(false); + + if (GetProperty(rXPropSet, "CustomShapeGeometry")) + { + Sequence< PropertyValue > aProps; + if ( mAny >>= aProps ) + { + for ( const auto& rProp : std::as_const(aProps) ) + { + if ( rProp.Name == "TextPreRotateAngle" && ( rProp.Value >>= nTextPreRotateAngle ) ) + { + if ( nTextPreRotateAngle == -90 ) + { + sWritingMode = "vert"; + bVertical = true; + } + else if ( nTextPreRotateAngle == -270 ) + { + sWritingMode = "vert270"; + bVertical = true; + } + if (!bIsFontworkShape) + break; + } + else if (rProp.Name == "AdjustmentValues") + rProp.Value >>= aAdjustmentSeq; + else if( rProp.Name == "TextRotateAngle" ) + rProp.Value >>= nTextRotateAngle; + else if (rProp.Name == "TextPath") + { + rProp.Value >>= aTextPathSeq; + for (const auto& rTextPath : std::as_const(aTextPathSeq)) + { + if (rTextPath.Name == "ScaleX") + rTextPath.Value >>= bScaleX; + } + } + } + } + } + + bool bFromWordArt = !bScaleX + && ( presetWarp == "textArchDown" || presetWarp == "textArchUp" + || presetWarp == "textButton" || presetWarp == "textCircle"); + + TextHorizontalAdjust eHorizontalAlignment( TextHorizontalAdjust_CENTER ); + bool bHorizontalCenter = false; + if (GetProperty(rXPropSet, "TextHorizontalAdjust")) + mAny >>= eHorizontalAlignment; + if( eHorizontalAlignment == TextHorizontalAdjust_CENTER ) + bHorizontalCenter = true; + else if( bVertical && eHorizontalAlignment == TextHorizontalAdjust_LEFT ) + sVerticalAlignment = "b"; + + bool bHasWrap = false; + bool bWrap = false; + // Only custom shapes obey the TextWordWrap option, normal text always wraps. + if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, "TextWordWrap")) + { + mAny >>= bWrap; + bHasWrap = true; + } + + if (bBodyPr) + { + const char* pWrap = bHasWrap && !bWrap ? "none" : nullptr; + if (GetDocumentType() == DOCUMENT_DOCX) + { + // In case of DOCX, if we want to have the same effect as + // TextShape's automatic word wrapping, then we need to set + // wrapping to square. + uno::Reference<lang::XServiceInfo> xServiceInfo(rXIface, uno::UNO_QUERY); + if (xServiceInfo.is() && xServiceInfo->supportsService("com.sun.star.drawing.TextShape")) + pWrap = "square"; + } + mpFS->startElementNS( (nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr, + XML_wrap, pWrap, + XML_fromWordArt, bFromWordArt ? "1" : nullptr, + XML_lIns, (nLeft != DEFLRINS) ? OString::number(oox::drawingml::convertHmmToEmu(nLeft)).getStr() : nullptr, + XML_rIns, (nRight != DEFLRINS) ? OString::number(oox::drawingml::convertHmmToEmu(nRight)).getStr() : nullptr, + XML_tIns, (nTop != DEFTBINS) ? OString::number(oox::drawingml::convertHmmToEmu(nTop)).getStr() : nullptr, + XML_bIns, (nBottom != DEFTBINS) ? OString::number(oox::drawingml::convertHmmToEmu(nBottom)).getStr() : nullptr, + XML_anchor, sVerticalAlignment, + XML_anchorCtr, bHorizontalCenter ? "1" : nullptr, + XML_vert, sWritingMode, + XML_rot, ((nTextPreRotateAngle + nTextRotateAngle) != 0) ? oox::drawingml::calcRotationValue( (nTextPreRotateAngle + nTextRotateAngle) * 100 ).getStr() : nullptr ); + if (bIsFontworkShape) + { + if (aAdjustmentSeq.hasElements()) + { + mpFS->startElementNS(XML_a, XML_prstTxWarp, XML_prst, presetWarp.toUtf8()); + mpFS->startElementNS(XML_a, XML_avLst); + for (sal_Int32 i = 0, nElems = aAdjustmentSeq.getLength(); i < nElems; ++i ) + { + OString sName = "adj" + (( nElems > 1 ) ? OString::number(i + 1) : OString()); + double fValue(0.0); + if (aAdjustmentSeq[i].Value.getValueTypeClass() == TypeClass_DOUBLE) + aAdjustmentSeq[i].Value >>= fValue; + else + { + sal_Int32 nNumber(0); + aAdjustmentSeq[i].Value >>= nNumber; + fValue = static_cast<double>(nNumber); + } + // Convert from binary coordinate system with viewBox "0 0 21600 21600" and simple degree + // to DrawingML with coordinate range 0..100000 and angle in 1/60000 degree. + // Reverse to conversion in lcl_createPresetShape in drawingml/shape.cxx on import. + if (presetWarp == "textArchDown" || presetWarp == "textArchUp" + || presetWarp == "textButton" || presetWarp == "textCircle" + || ((i == 0) && (presetWarp == "textArchDownPour" || presetWarp == "textArchUpPour" + || presetWarp == "textButtonPour" || presetWarp == "textCirclePour"))) + { + fValue *= 60000.0; + } + else if ((i == 1) && (presetWarp == "textDoubleWave1" || presetWarp == "textWave1" + || presetWarp == "textWave2" || presetWarp == "textWave4")) + { + fValue = fValue / 0.216 - 50000.0; + } + else if ((i == 1) && (presetWarp == "textArchDownPour" || presetWarp == "textArchUpPour" + || presetWarp == "textButtonPour" || presetWarp == "textCirclePour")) + { + fValue /= 0.108; + } + else + { + fValue /= 0.216; + } + OString sFmla = "val " + OString::number(std::lround(fValue)); + mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla); + } + mpFS->endElementNS( XML_a, XML_avLst ); + mpFS->endElementNS(XML_a, XML_prstTxWarp); + } + else + { + mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, presetWarp.toUtf8()); + } + } + + if (GetDocumentType() == DOCUMENT_DOCX || GetDocumentType() == DOCUMENT_XLSX) + { + // tdf#112312: only custom shapes obey the TextAutoGrowHeight option + bool bTextAutoGrowHeight = false; + uno::Reference<drawing::XShape> xShape(rXIface, uno::UNO_QUERY); + auto pSdrObjCustomShape = xShape.is() ? dynamic_cast<SdrObjCustomShape*>(GetSdrObjectFromXShape(xShape)) : nullptr; + if (pSdrObjCustomShape && GetProperty(rXPropSet, "TextAutoGrowHeight")) + { + mAny >>= bTextAutoGrowHeight; + } + mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit)); + } + if (GetDocumentType() == DOCUMENT_PPTX) + { + TextFitToSizeType eFit = TextFitToSizeType_NONE; + if (GetProperty(rXPropSet, "TextFitToSize")) + mAny >>= eFit; + + if (eFit == TextFitToSizeType_AUTOFIT) + { + const sal_Int32 MAX_SCALE_VAL = 100000; + sal_Int32 nFontScale = MAX_SCALE_VAL; + SvxShapeText* pTextShape = dynamic_cast<SvxShapeText*>(rXIface.get()); + if (pTextShape) + { + SdrTextObj* pTextObject = dynamic_cast<SdrTextObj*>(pTextShape->GetSdrObject()); + if (pTextObject) + { + double fScaleY = pTextObject->GetFontScaleY(); + nFontScale = static_cast<sal_uInt32>(fScaleY * 100) * 1000; + } + } + + mpFS->singleElementNS(XML_a, XML_normAutofit, XML_fontScale, + ( nFontScale < MAX_SCALE_VAL && nFontScale > 0 ) ? OString::number(nFontScale).getStr() : nullptr); + } + else + { + // tdf#127030: Only custom shapes obey the TextAutoGrowHeight option. + bool bTextAutoGrowHeight = false; + if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, "TextAutoGrowHeight")) + mAny >>= bTextAutoGrowHeight; + mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit)); + } + } + + WriteShape3DEffects( rXPropSet ); + + mpFS->endElementNS((nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr); + } + + Reference< XEnumerationAccess > access( xXText, UNO_QUERY ); + if( !access.is() || !bText ) + return; + + Reference< XEnumeration > enumeration( access->createEnumeration() ); + if( !enumeration.is() ) + return; + + uno::Reference<drawing::XShape> xShape(rXIface, uno::UNO_QUERY); + SdrObject* pSdrObject = xShape.is() ? GetSdrObjectFromXShape(xShape) : nullptr; + const SdrTextObj* pTxtObj = dynamic_cast<SdrTextObj*>( pSdrObject ); + if (pTxtObj && mpTextExport) + { + const OutlinerParaObject* pParaObj = nullptr; + bool bOwnParaObj = false; + + /* + #i13885# + When the object is actively being edited, that text is not set into + the objects normal text object, but lives in a separate object. + */ + if (pTxtObj->IsTextEditActive()) + { + pParaObj = pTxtObj->CreateEditOutlinerParaObject().release(); + bOwnParaObj = true; + } + else + pParaObj = pTxtObj->GetOutlinerParaObject(); + + if (pParaObj) + { + // this is reached only in case some text is attached to the shape + mpTextExport->WriteOutliner(*pParaObj); + if (bOwnParaObj) + delete pParaObj; + } + return; + } + + bool bOverridingCharHeight = false; + sal_Int32 nCharHeight = -1; + + while( enumeration->hasMoreElements() ) + { + Reference< XTextContent > paragraph; + Any any ( enumeration->nextElement() ); + + if( any >>= paragraph) + WriteParagraph( paragraph, bOverridingCharHeight, nCharHeight ); + } +} + +void DrawingML::WritePresetShape( const char* pShape , std::vector< std::pair<sal_Int32,sal_Int32>> & rAvList ) +{ + mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape); + if ( !rAvList.empty() ) + { + + mpFS->startElementNS(XML_a, XML_avLst); + for (auto const& elem : rAvList) + { + OString sName = "adj" + ( ( elem.first > 0 ) ? OString::number(elem.first) : OString() ); + OString sFmla = "val " + OString::number( elem.second ); + + mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla); + } + mpFS->endElementNS( XML_a, XML_avLst ); + } + else + mpFS->singleElementNS(XML_a, XML_avLst); + + mpFS->endElementNS( XML_a, XML_prstGeom ); +} + +void DrawingML::WritePresetShape( const char* pShape ) +{ + mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape); + mpFS->singleElementNS(XML_a, XML_avLst); + mpFS->endElementNS( XML_a, XML_prstGeom ); +} + +static std::map< OString, std::vector<OString> > lcl_getAdjNames() +{ + std::map< OString, std::vector<OString> > aRet; + + OUString aPath("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/filter/oox-drawingml-adj-names"); + rtl::Bootstrap::expandMacros(aPath); + SvFileStream aStream(aPath, StreamMode::READ); + if (aStream.GetError() != ERRCODE_NONE) + SAL_WARN("oox.shape", "failed to open oox-drawingml-adj-names"); + OString aLine; + bool bNotDone = aStream.ReadLine(aLine); + while (bNotDone) + { + sal_Int32 nIndex = 0; + // Each line is in a "key\tvalue" format: read the key, the rest is the value. + OString aKey = aLine.getToken(0, '\t', nIndex); + OString aValue = aLine.copy(nIndex); + aRet[aKey].push_back(aValue); + bNotDone = aStream.ReadLine(aLine); + } + return aRet; +} + +void DrawingML::WritePresetShape( const char* pShape, MSO_SPT eShapeType, bool bPredefinedHandlesUsed, const PropertyValue& rProp ) +{ + static std::map< OString, std::vector<OString> > aAdjMap = lcl_getAdjNames(); + // If there are predefined adj names for this shape type, look them up now. + std::vector<OString> aAdjustments; + if (aAdjMap.find(OString(pShape)) != aAdjMap.end()) + aAdjustments = aAdjMap[OString(pShape)]; + + mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape); + mpFS->startElementNS(XML_a, XML_avLst); + + Sequence< drawing::EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq; + if ( ( rProp.Value >>= aAdjustmentSeq ) + && eShapeType != mso_sptActionButtonForwardNext // we have adjustments values for these type of shape, but MSO doesn't like them + && eShapeType != mso_sptActionButtonBackPrevious // so they are now disabled + && OString(pShape) != "rect" //some shape types are commented out in pCustomShapeTypeTranslationTable[] & are being defaulted to rect & rect does not have adjustment values/name. + ) + { + SAL_INFO("oox.shape", "adj seq len: " << aAdjustmentSeq.getLength()); + sal_Int32 nAdjustmentsWhichNeedsToBeConverted = 0; + if ( bPredefinedHandlesUsed ) + EscherPropertyContainer::LookForPolarHandles( eShapeType, nAdjustmentsWhichNeedsToBeConverted ); + + sal_Int32 nValue, nLength = aAdjustmentSeq.getLength(); + // aAdjustments will give info about the number of adj values for a particular geometry. For example for hexagon aAdjustments.size() will be 2 and for circular arrow it will be 5 as per lcl_getAdjNames. + // Sometimes there are more values than needed, so we ignore the excessive ones. + if (aAdjustments.size() <= o3tl::make_unsigned(nLength)) + { + for (sal_Int32 i = 0; i < static_cast<sal_Int32>(aAdjustments.size()); i++) + { + if( EscherPropertyContainer::GetAdjustmentValue( aAdjustmentSeq[ i ], i, nAdjustmentsWhichNeedsToBeConverted, nValue ) ) + { + // If the document model doesn't have an adjustment name (e.g. shape was created from VML), then take it from the predefined list. + OString aAdjName = aAdjustmentSeq[i].Name.isEmpty() + ? aAdjustments[i] + : aAdjustmentSeq[i].Name.toUtf8(); + + mpFS->singleElementNS( XML_a, XML_gd, + XML_name, aAdjName, + XML_fmla, "val " + OString::number(nValue)); + } + } + } + } + + mpFS->endElementNS( XML_a, XML_avLst ); + mpFS->endElementNS( XML_a, XML_prstGeom ); +} + +bool DrawingML::WriteCustomGeometry( + const Reference< XShape >& rXShape, + const SdrObjCustomShape& rSdrObjCustomShape) +{ + uno::Reference< beans::XPropertySet > aXPropSet; + uno::Any aAny( rXShape->queryInterface(cppu::UnoType<beans::XPropertySet>::get())); + + if ( ! (aAny >>= aXPropSet) ) + return false; + + try + { + aAny = aXPropSet->getPropertyValue( "CustomShapeGeometry" ); + if ( !aAny.hasValue() ) + return false; + } + catch( const ::uno::Exception& ) + { + return false; + } + + + auto pGeometrySeq = o3tl::tryAccess<uno::Sequence<beans::PropertyValue>>(aAny); + + if ( pGeometrySeq ) + { + for( const beans::PropertyValue& rProp : *pGeometrySeq ) + { + if ( rProp.Name == "Path" ) + { + uno::Sequence<beans::PropertyValue> aPathProp; + rProp.Value >>= aPathProp; + + uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs; + uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments; + uno::Sequence<awt::Size> aPathSize; + for (const beans::PropertyValue& rPathProp : std::as_const(aPathProp)) + { + if (rPathProp.Name == "Coordinates") + rPathProp.Value >>= aPairs; + else if (rPathProp.Name == "Segments") + rPathProp.Value >>= aSegments; + else if (rPathProp.Name == "SubViewSize") + rPathProp.Value >>= aPathSize; + } + + if ( !aPairs.hasElements() ) + return false; + + if ( !aSegments.hasElements() ) + { + aSegments = uno::Sequence<drawing::EnhancedCustomShapeSegment>(4); + aSegments[0].Count = 1; + aSegments[0].Command = drawing::EnhancedCustomShapeSegmentCommand::MOVETO; + aSegments[1].Count = static_cast<sal_Int16>(std::min( aPairs.getLength() - 1, sal_Int32(32767) )); + aSegments[1].Command = drawing::EnhancedCustomShapeSegmentCommand::LINETO; + aSegments[2].Count = 0; + aSegments[2].Command = drawing::EnhancedCustomShapeSegmentCommand::CLOSESUBPATH; + aSegments[3].Count = 0; + aSegments[3].Command = drawing::EnhancedCustomShapeSegmentCommand::ENDSUBPATH; + } + + int nExpectedPairCount = std::accumulate(std::cbegin(aSegments), std::cend(aSegments), 0, + [](const int nSum, const drawing::EnhancedCustomShapeSegment& rSegment) { return nSum + rSegment.Count; }); + + if ( nExpectedPairCount > aPairs.getLength() ) + { + SAL_WARN("oox.shape", "Segments need " << nExpectedPairCount << " coordinates, but Coordinates have only " << aPairs.getLength() << " pairs."); + return false; + } + + mpFS->startElementNS(XML_a, XML_custGeom); + mpFS->singleElementNS(XML_a, XML_avLst); + mpFS->singleElementNS(XML_a, XML_gdLst); + mpFS->singleElementNS(XML_a, XML_ahLst); + mpFS->singleElementNS(XML_a, XML_rect, XML_l, "l", XML_t, "t", + XML_r, "r", XML_b, "b"); + mpFS->startElementNS(XML_a, XML_pathLst); + + if ( aPathSize.hasElements() ) + { + mpFS->startElementNS( XML_a, XML_path, + XML_w, OString::number(aPathSize[0].Width), + XML_h, OString::number(aPathSize[0].Height) ); + } + else + { + sal_Int32 nXMin(0); + aPairs[0].First.Value >>= nXMin; + sal_Int32 nXMax = nXMin; + sal_Int32 nYMin(0); + aPairs[0].Second.Value >>= nYMin; + sal_Int32 nYMax = nYMin; + + for ( const auto& rPair : std::as_const(aPairs) ) + { + sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, rSdrObjCustomShape); + sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, rSdrObjCustomShape); + if (nX < nXMin) + nXMin = nX; + if (nY < nYMin) + nYMin = nY; + if (nX > nXMax) + nXMax = nX; + if (nY > nYMax) + nYMax = nY; + } + mpFS->startElementNS( XML_a, XML_path, + XML_w, OString::number(nXMax - nXMin), + XML_h, OString::number(nYMax - nYMin) ); + } + + + int nPairIndex = 0; + bool bOK = true; + for (const auto& rSegment : std::as_const(aSegments)) + { + if ( rSegment.Command == drawing::EnhancedCustomShapeSegmentCommand::CLOSESUBPATH ) + { + mpFS->singleElementNS(XML_a, XML_close); + } + for (int k = 0; k < rSegment.Count && bOK; ++k) + { + switch( rSegment.Command ) + { + case drawing::EnhancedCustomShapeSegmentCommand::MOVETO : + { + if (nPairIndex >= aPairs.getLength()) + bOK = false; + else + { + mpFS->startElementNS(XML_a, XML_moveTo); + WriteCustomGeometryPoint(aPairs[nPairIndex], rSdrObjCustomShape); + mpFS->endElementNS( XML_a, XML_moveTo ); + nPairIndex++; + } + break; + } + case drawing::EnhancedCustomShapeSegmentCommand::LINETO : + { + if (nPairIndex >= aPairs.getLength()) + bOK = false; + else + { + mpFS->startElementNS(XML_a, XML_lnTo); + WriteCustomGeometryPoint(aPairs[nPairIndex], rSdrObjCustomShape); + mpFS->endElementNS( XML_a, XML_lnTo ); + nPairIndex++; + } + break; + } + case drawing::EnhancedCustomShapeSegmentCommand::CURVETO : + { + if (nPairIndex + 2 >= aPairs.getLength()) + bOK = false; + else + { + mpFS->startElementNS(XML_a, XML_cubicBezTo); + for( sal_uInt8 l = 0; l <= 2; ++l ) + { + WriteCustomGeometryPoint(aPairs[nPairIndex+l], rSdrObjCustomShape); + } + mpFS->endElementNS( XML_a, XML_cubicBezTo ); + nPairIndex += 3; + } + break; + } + case drawing::EnhancedCustomShapeSegmentCommand::ANGLEELLIPSETO : + case drawing::EnhancedCustomShapeSegmentCommand::ANGLEELLIPSE : + { + nPairIndex += 3; + break; + } + case drawing::EnhancedCustomShapeSegmentCommand::ARCTO : + case drawing::EnhancedCustomShapeSegmentCommand::ARC : + case drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARCTO : + case drawing::EnhancedCustomShapeSegmentCommand::CLOCKWISEARC : + { + nPairIndex += 4; + break; + } + case drawing::EnhancedCustomShapeSegmentCommand::ELLIPTICALQUADRANTX : + case drawing::EnhancedCustomShapeSegmentCommand::ELLIPTICALQUADRANTY : + { + nPairIndex++; + break; + } + case drawing::EnhancedCustomShapeSegmentCommand::QUADRATICCURVETO : + { + if (nPairIndex + 1 >= aPairs.getLength()) + bOK = false; + else + { + mpFS->startElementNS(XML_a, XML_quadBezTo); + for( sal_uInt8 l = 0; l < 2; ++l ) + { + WriteCustomGeometryPoint(aPairs[nPairIndex+l], rSdrObjCustomShape); + } + mpFS->endElementNS( XML_a, XML_quadBezTo ); + nPairIndex += 2; + } + break; + } + case drawing::EnhancedCustomShapeSegmentCommand::ARCANGLETO : + { + nPairIndex += 2; + break; + } + default: + // do nothing + break; + } + } + if (!bOK) + break; + } + mpFS->endElementNS( XML_a, XML_path ); + mpFS->endElementNS( XML_a, XML_pathLst ); + mpFS->endElementNS( XML_a, XML_custGeom ); + return bOK; + } + } + } + return false; +} + +void DrawingML::WriteCustomGeometryPoint( + const drawing::EnhancedCustomShapeParameterPair& rParamPair, + const SdrObjCustomShape& rSdrObjCustomShape) +{ + sal_Int32 nX = GetCustomGeometryPointValue(rParamPair.First, rSdrObjCustomShape); + sal_Int32 nY = GetCustomGeometryPointValue(rParamPair.Second, rSdrObjCustomShape); + + mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(nX), XML_y, OString::number(nY)); +} + +sal_Int32 DrawingML::GetCustomGeometryPointValue( + const css::drawing::EnhancedCustomShapeParameter& rParam, + const SdrObjCustomShape& rSdrObjCustomShape) +{ + const EnhancedCustomShape2d aCustoShape2d(const_cast< SdrObjCustomShape& >(rSdrObjCustomShape)); + double fValue = 0.0; + aCustoShape2d.GetParameter(fValue, rParam, false, false); + sal_Int32 nValue(std::lround(fValue)); + + return nValue; +} + +void DrawingML::WritePolyPolygon( const tools::PolyPolygon& rPolyPolygon, const bool bClosed ) +{ + // In case of Writer, the parent element is <wps:spPr>, and there the + // <a:custGeom> element is not optional. + if (rPolyPolygon.Count() < 1 && GetDocumentType() != DOCUMENT_DOCX) + return; + + mpFS->startElementNS(XML_a, XML_custGeom); + mpFS->singleElementNS(XML_a, XML_avLst); + mpFS->singleElementNS(XML_a, XML_gdLst); + mpFS->singleElementNS(XML_a, XML_ahLst); + mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b"); + + mpFS->startElementNS( XML_a, XML_pathLst ); + + const tools::Rectangle aRect( rPolyPolygon.GetBoundRect() ); + + // Put all polygons of rPolyPolygon in the same path element + // to subtract the overlapped areas. + mpFS->startElementNS( XML_a, XML_path, + XML_w, OString::number(aRect.GetWidth()), + XML_h, OString::number(aRect.GetHeight()) ); + + for( sal_uInt16 i = 0; i < rPolyPolygon.Count(); i ++ ) + { + + const tools::Polygon& rPoly = rPolyPolygon[ i ]; + + if( rPoly.GetSize() > 0 ) + { + mpFS->startElementNS(XML_a, XML_moveTo); + + mpFS->singleElementNS( XML_a, XML_pt, + XML_x, OString::number(rPoly[0].X() - aRect.Left()), + XML_y, OString::number(rPoly[0].Y() - aRect.Top()) ); + + mpFS->endElementNS( XML_a, XML_moveTo ); + } + + for( sal_uInt16 j = 1; j < rPoly.GetSize(); j ++ ) + { + PolyFlags flags = rPoly.GetFlags(j); + if( flags == PolyFlags::Control ) + { + // a:cubicBezTo can only contain 3 a:pt elements, so we need to make sure of this + if( j+2 < rPoly.GetSize() && rPoly.GetFlags(j+1) == PolyFlags::Control && rPoly.GetFlags(j+2) != PolyFlags::Control ) + { + + mpFS->startElementNS(XML_a, XML_cubicBezTo); + for( sal_uInt8 k = 0; k <= 2; ++k ) + { + mpFS->singleElementNS(XML_a, XML_pt, + XML_x, OString::number(rPoly[j+k].X() - aRect.Left()), + XML_y, OString::number(rPoly[j+k].Y() - aRect.Top())); + + } + mpFS->endElementNS( XML_a, XML_cubicBezTo ); + j += 2; + } + } + else if( flags == PolyFlags::Normal ) + { + mpFS->startElementNS(XML_a, XML_lnTo); + mpFS->singleElementNS( XML_a, XML_pt, + XML_x, OString::number(rPoly[j].X() - aRect.Left()), + XML_y, OString::number(rPoly[j].Y() - aRect.Top()) ); + mpFS->endElementNS( XML_a, XML_lnTo ); + } + } + } + if (bClosed) + mpFS->singleElementNS( XML_a, XML_close); + mpFS->endElementNS( XML_a, XML_path ); + + mpFS->endElementNS( XML_a, XML_pathLst ); + + mpFS->endElementNS( XML_a, XML_custGeom ); +} + +void DrawingML::WriteConnectorConnections( EscherConnectorListEntry& rConnectorEntry, sal_Int32 nStartID, sal_Int32 nEndID ) +{ + if( nStartID != -1 ) + { + mpFS->singleElementNS( XML_a, XML_stCxn, + XML_id, OString::number(nStartID), + XML_idx, OString::number(rConnectorEntry.GetConnectorRule(true)) ); + } + if( nEndID != -1 ) + { + mpFS->singleElementNS( XML_a, XML_endCxn, + XML_id, OString::number(nEndID), + XML_idx, OString::number(rConnectorEntry.GetConnectorRule(false)) ); + } +} + +sal_Unicode DrawingML::SubstituteBullet( sal_Unicode cBulletId, css::awt::FontDescriptor& rFontDesc ) +{ + if ( IsStarSymbol(rFontDesc.Name) ) + { + rtl_TextEncoding eCharSet = rFontDesc.CharSet; + cBulletId = msfilter::util::bestFitOpenSymbolToMSFont(cBulletId, eCharSet, rFontDesc.Name); + rFontDesc.CharSet = eCharSet; + } + + return cBulletId; +} + +sax_fastparser::FSHelperPtr DrawingML::CreateOutputStream ( + const OUString& sFullStream, + const OUString& sRelativeStream, + const Reference< XOutputStream >& xParentRelation, + const char* sContentType, + const char* sRelationshipType, + OUString* pRelationshipId ) +{ + OUString sRelationshipId; + if (xParentRelation.is()) + sRelationshipId = GetFB()->addRelation( xParentRelation, OUString::createFromAscii( sRelationshipType), sRelativeStream ); + else + sRelationshipId = GetFB()->addRelation( OUString::createFromAscii( sRelationshipType ), sRelativeStream ); + + if( pRelationshipId ) + *pRelationshipId = sRelationshipId; + + sax_fastparser::FSHelperPtr p = GetFB()->openFragmentStreamWithSerializer( sFullStream, OUString::createFromAscii( sContentType ) ); + + return p; +} + +void DrawingML::WriteFill( const Reference< XPropertySet >& xPropSet ) +{ + if ( !GetProperty( xPropSet, "FillStyle" ) ) + return; + FillStyle aFillStyle( FillStyle_NONE ); + xPropSet->getPropertyValue( "FillStyle" ) >>= aFillStyle; + + // map full transparent background to no fill + if ( aFillStyle == FillStyle_SOLID && GetProperty( xPropSet, "FillTransparence" ) ) + { + sal_Int16 nVal = 0; + xPropSet->getPropertyValue( "FillTransparence" ) >>= nVal; + if ( nVal == 100 ) + aFillStyle = FillStyle_NONE; + } + if (aFillStyle == FillStyle_SOLID && GetProperty( xPropSet, "FillTransparenceGradient")) + { + awt::Gradient aTransparenceGradient; + mAny >>= aTransparenceGradient; + if (aTransparenceGradient.StartColor == 0xffffff && aTransparenceGradient.EndColor == 0xffffff) + aFillStyle = FillStyle_NONE; + } + + switch( aFillStyle ) + { + case FillStyle_SOLID : + WriteSolidFill( xPropSet ); + break; + case FillStyle_GRADIENT : + WriteGradientFill( xPropSet ); + break; + case FillStyle_BITMAP : + WriteBlipFill( xPropSet, "FillBitmap" ); + break; + case FillStyle_HATCH : + WritePattFill( xPropSet ); + break; + case FillStyle_NONE: + mpFS->singleElementNS(XML_a, XML_noFill); + break; + default: + ; + } +} + +void DrawingML::WriteStyleProperties( sal_Int32 nTokenId, const Sequence< PropertyValue >& aProperties ) +{ + if( aProperties.hasElements() ) + { + OUString sSchemeClr; + sal_uInt32 nIdx = 0; + Sequence< PropertyValue > aTransformations; + for( const auto& rProp : aProperties) + { + if( rProp.Name == "SchemeClr" ) + rProp.Value >>= sSchemeClr; + else if( rProp.Name == "Idx" ) + rProp.Value >>= nIdx; + else if( rProp.Name == "Transformations" ) + rProp.Value >>= aTransformations; + } + mpFS->startElementNS(XML_a, nTokenId, XML_idx, OString::number(nIdx)); + WriteColor(sSchemeClr, aTransformations); + mpFS->endElementNS( XML_a, nTokenId ); + } + else + { + // write mock <a:*Ref> tag + mpFS->singleElementNS(XML_a, nTokenId, XML_idx, OString::number(0)); + } +} + +void DrawingML::WriteShapeStyle( const Reference< XPropertySet >& xPropSet ) +{ + // check existence of the grab bag + if ( !GetProperty( xPropSet, "InteropGrabBag" ) ) + return; + + // extract the relevant properties from the grab bag + Sequence< PropertyValue > aGrabBag; + Sequence< PropertyValue > aFillRefProperties, aLnRefProperties, aEffectRefProperties; + mAny >>= aGrabBag; + for( const auto& rProp : std::as_const(aGrabBag)) + { + if( rProp.Name == "StyleFillRef" ) + rProp.Value >>= aFillRefProperties; + else if( rProp.Name == "StyleLnRef" ) + rProp.Value >>= aLnRefProperties; + else if( rProp.Name == "StyleEffectRef" ) + rProp.Value >>= aEffectRefProperties; + } + + WriteStyleProperties( XML_lnRef, aLnRefProperties ); + WriteStyleProperties( XML_fillRef, aFillRefProperties ); + WriteStyleProperties( XML_effectRef, aEffectRefProperties ); + + // write mock <a:fontRef> + mpFS->singleElementNS(XML_a, XML_fontRef, XML_idx, "minor"); +} + +void DrawingML::WriteShapeEffect( const OUString& sName, const Sequence< PropertyValue >& aEffectProps ) +{ + if( !aEffectProps.hasElements() ) + return; + + // assign the proper tag and enable bContainsColor if necessary + sal_Int32 nEffectToken = 0; + bool bContainsColor = false; + if( sName == "outerShdw" ) + { + nEffectToken = FSNS( XML_a, XML_outerShdw ); + bContainsColor = true; + } + else if( sName == "innerShdw" ) + { + nEffectToken = FSNS( XML_a, XML_innerShdw ); + bContainsColor = true; + } + else if( sName == "glow" ) + { + nEffectToken = FSNS( XML_a, XML_glow ); + bContainsColor = true; + } + else if( sName == "softEdge" ) + nEffectToken = FSNS( XML_a, XML_softEdge ); + else if( sName == "reflection" ) + nEffectToken = FSNS( XML_a, XML_reflection ); + else if( sName == "blur" ) + nEffectToken = FSNS( XML_a, XML_blur ); + + OUString sSchemeClr; + ::Color nRgbClr; + sal_Int32 nAlpha = MAX_PERCENT; + Sequence< PropertyValue > aTransformations; + sax_fastparser::FastAttributeList *aOuterShdwAttrList = FastSerializerHelper::createAttrList(); + sax_fastparser::XFastAttributeListRef xOuterShdwAttrList( aOuterShdwAttrList ); + for( const auto& rEffectProp : aEffectProps ) + { + if( rEffectProp.Name == "Attribs" ) + { + // read tag attributes + uno::Sequence< beans::PropertyValue > aOuterShdwProps; + rEffectProp.Value >>= aOuterShdwProps; + for( const auto& rOuterShdwProp : std::as_const(aOuterShdwProps) ) + { + if( rOuterShdwProp.Name == "algn" ) + { + OUString sVal; + rOuterShdwProp.Value >>= sVal; + aOuterShdwAttrList->add( XML_algn, OUStringToOString( sVal, RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if( rOuterShdwProp.Name == "blurRad" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_blurRad, OString::number( nVal ).getStr() ); + } + else if( rOuterShdwProp.Name == "dir" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_dir, OString::number( nVal ).getStr() ); + } + else if( rOuterShdwProp.Name == "dist" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_dist, OString::number( nVal ).getStr() ); + } + else if( rOuterShdwProp.Name == "kx" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_kx, OString::number( nVal ).getStr() ); + } + else if( rOuterShdwProp.Name == "ky" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_ky, OString::number( nVal ).getStr() ); + } + else if( rOuterShdwProp.Name == "rotWithShape" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_rotWithShape, OString::number( nVal ).getStr() ); + } + else if( rOuterShdwProp.Name == "sx" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_sx, OString::number( nVal ).getStr() ); + } + else if( rOuterShdwProp.Name == "sy" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_sy, OString::number( nVal ).getStr() ); + } + else if( rOuterShdwProp.Name == "rad" ) + { + sal_Int64 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_rad, OString::number( nVal ).getStr() ); + } + else if( rOuterShdwProp.Name == "endA" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_endA, OString::number( nVal ).getStr() ); + } + else if( rOuterShdwProp.Name == "endPos" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_endPos, OString::number( nVal ).getStr() ); + } + else if( rOuterShdwProp.Name == "fadeDir" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_fadeDir, OString::number( nVal ).getStr() ); + } + else if( rOuterShdwProp.Name == "stA" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_stA, OString::number( nVal ).getStr() ); + } + else if( rOuterShdwProp.Name == "stPos" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_stPos, OString::number( nVal ).getStr() ); + } + else if( rOuterShdwProp.Name == "grow" ) + { + sal_Int32 nVal = 0; + rOuterShdwProp.Value >>= nVal; + aOuterShdwAttrList->add( XML_grow, OString::number( nVal ).getStr() ); + } + } + } + else if(rEffectProp.Name == "RgbClr") + { + rEffectProp.Value >>= nRgbClr; + } + else if(rEffectProp.Name == "RgbClrTransparency") + { + sal_Int32 nTransparency; + if (rEffectProp.Value >>= nTransparency) + // Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency()) + nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency ); + } + else if(rEffectProp.Name == "SchemeClr") + { + rEffectProp.Value >>= sSchemeClr; + } + else if(rEffectProp.Name == "SchemeClrTransformations") + { + rEffectProp.Value >>= aTransformations; + } + } + + if( nEffectToken <= 0 ) + return; + + mpFS->startElement( nEffectToken, xOuterShdwAttrList ); + + if( bContainsColor ) + { + if( sSchemeClr.isEmpty() ) + WriteColor( nRgbClr, nAlpha ); + else + WriteColor( sSchemeClr, aTransformations ); + } + + mpFS->endElement( nEffectToken ); +} + +static sal_Int32 lcl_CalculateDist(const double dX, const double dY) +{ + return static_cast< sal_Int32 >(sqrt(dX*dX + dY*dY) * 360); +} + +static sal_Int32 lcl_CalculateDir(const double dX, const double dY) +{ + return (static_cast< sal_Int32 >(basegfx::rad2deg(atan2(dY,dX)) * 60000) + 21600000) % 21600000; +} + +void DrawingML::WriteShapeEffects( const Reference< XPropertySet >& rXPropSet ) +{ + Sequence< PropertyValue > aGrabBag, aEffects, aOuterShdwProps; + if( GetProperty( rXPropSet, "InteropGrabBag" ) ) + { + mAny >>= aGrabBag; + auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag), + [](const PropertyValue& rProp) { return rProp.Name == "EffectProperties"; }); + if (pProp != std::cend(aGrabBag)) + { + pProp->Value >>= aEffects; + auto pEffect = std::find_if(std::cbegin(aEffects), std::cend(aEffects), + [](const PropertyValue& rEffect) { return rEffect.Name == "outerShdw"; }); + if (pEffect != std::cend(aEffects)) + pEffect->Value >>= aOuterShdwProps; + } + } + + // tdf#132201: the order of effects is important. Effects order (CT_EffectList in ECMA-376): + // blur -> fillOverlay -> glow -> innerShdw -> outerShdw -> prstShdw -> reflection -> softEdge + + if( !aEffects.hasElements() ) + { + bool bHasShadow = false; + if( GetProperty( rXPropSet, "Shadow" ) ) + mAny >>= bHasShadow; + bool bHasEffects = bHasShadow; + if (!bHasEffects && GetProperty(rXPropSet, "GlowEffectRadius")) + { + sal_Int32 rad = 0; + mAny >>= rad; + bHasEffects = rad > 0; + } + if (!bHasEffects && GetProperty(rXPropSet, "SoftEdgeRadius")) + { + sal_Int32 rad = 0; + mAny >>= rad; + bHasEffects = rad > 0; + } + + if (bHasEffects) + { + mpFS->startElementNS(XML_a, XML_effectLst); + WriteGlowEffect(rXPropSet); + if( bHasShadow ) + { + Sequence< PropertyValue > aShadowGrabBag( 3 ); + Sequence< PropertyValue > aShadowAttribsGrabBag( 2 ); + + double dX = +0.0, dY = +0.0; + rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX; + rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY; + + aShadowAttribsGrabBag[0].Name = "dist"; + aShadowAttribsGrabBag[0].Value <<= lcl_CalculateDist(dX, dY); + aShadowAttribsGrabBag[1].Name = "dir"; + aShadowAttribsGrabBag[1].Value <<= lcl_CalculateDir(dX, dY); + + aShadowGrabBag[0].Name = "Attribs"; + aShadowGrabBag[0].Value <<= aShadowAttribsGrabBag; + aShadowGrabBag[1].Name = "RgbClr"; + aShadowGrabBag[1].Value = rXPropSet->getPropertyValue( "ShadowColor" ); + aShadowGrabBag[2].Name = "RgbClrTransparency"; + aShadowGrabBag[2].Value = rXPropSet->getPropertyValue( "ShadowTransparence" ); + + WriteShapeEffect( "outerShdw", aShadowGrabBag ); + } + WriteSoftEdgeEffect(rXPropSet); + mpFS->endElementNS(XML_a, XML_effectLst); + } + } + else + { + for( auto& rOuterShdwProp : aOuterShdwProps ) + { + if( rOuterShdwProp.Name == "Attribs" ) + { + Sequence< PropertyValue > aAttribsProps; + rOuterShdwProp.Value >>= aAttribsProps; + + double dX = +0.0, dY = +0.0; + rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX; + rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY; + + for( auto& rAttribsProp : aAttribsProps ) + { + if( rAttribsProp.Name == "dist" ) + { + rAttribsProp.Value <<= lcl_CalculateDist(dX, dY); + } + else if( rAttribsProp.Name == "dir" ) + { + rAttribsProp.Value <<= lcl_CalculateDir(dX, dY); + } + } + + rOuterShdwProp.Value <<= aAttribsProps; + } + else if( rOuterShdwProp.Name == "RgbClr" ) + { + rOuterShdwProp.Value = rXPropSet->getPropertyValue( "ShadowColor" ); + } + else if( rOuterShdwProp.Name == "RgbClrTransparency" ) + { + rOuterShdwProp.Value = rXPropSet->getPropertyValue( "ShadowTransparence" ); + } + } + + mpFS->startElementNS(XML_a, XML_effectLst); + bool bGlowWritten = false; + for( const auto& rEffect : std::as_const(aEffects) ) + { + if (!bGlowWritten + && (rEffect.Name == "innerShdw" || rEffect.Name == "outerShdw" + || rEffect.Name == "prstShdw" || rEffect.Name == "reflection" + || rEffect.Name == "softEdge")) + { + WriteGlowEffect(rXPropSet); + bGlowWritten = true; + } + + if( rEffect.Name == "outerShdw" ) + { + WriteShapeEffect( rEffect.Name, aOuterShdwProps ); + } + else + { + Sequence< PropertyValue > aEffectProps; + rEffect.Value >>= aEffectProps; + WriteShapeEffect( rEffect.Name, aEffectProps ); + } + } + if (!bGlowWritten) + WriteGlowEffect(rXPropSet); + WriteSoftEdgeEffect(rXPropSet); // the last + + mpFS->endElementNS(XML_a, XML_effectLst); + } +} + +void DrawingML::WriteGlowEffect(const Reference< XPropertySet >& rXPropSet) +{ + sal_Int32 nRad = 0; + rXPropSet->getPropertyValue("GlowEffectRadius") >>= nRad; + if (!nRad) + return; + + Sequence< PropertyValue > aGlowAttribs(1); + aGlowAttribs[0].Name = "rad"; + aGlowAttribs[0].Value <<= oox::drawingml::convertHmmToEmu(nRad); + Sequence< PropertyValue > aGlowProps(3); + aGlowProps[0].Name = "Attribs"; + aGlowProps[0].Value <<= aGlowAttribs; + aGlowProps[1].Name = "RgbClr"; + aGlowProps[1].Value = rXPropSet->getPropertyValue("GlowEffectColor"); + aGlowProps[2].Name = "RgbClrTransparency"; + aGlowProps[2].Value = rXPropSet->getPropertyValue("GlowEffectTransparency"); + // TODO other stuff like saturation or luminance + + WriteShapeEffect("glow", aGlowProps); +} + +void DrawingML::WriteSoftEdgeEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet) +{ + sal_Int32 nRad = 0; + rXPropSet->getPropertyValue("SoftEdgeRadius") >>= nRad; + if (!nRad) + return; + + css::uno::Sequence<css::beans::PropertyValue> aAttribs(1); + aAttribs[0].Name = "rad"; + aAttribs[0].Value <<= oox::drawingml::convertHmmToEmu(nRad); + css::uno::Sequence<css::beans::PropertyValue> aProps(1); + aProps[0].Name = "Attribs"; + aProps[0].Value <<= aAttribs; + + WriteShapeEffect("softEdge", aProps); +} + +void DrawingML::WriteShape3DEffects( const Reference< XPropertySet >& xPropSet ) +{ + // check existence of the grab bag + if( !GetProperty( xPropSet, "InteropGrabBag" ) ) + return; + + // extract the relevant properties from the grab bag + Sequence< PropertyValue > aGrabBag, aEffectProps, aLightRigProps, aShape3DProps; + mAny >>= aGrabBag; + auto pShapeProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag), + [](const PropertyValue& rProp) { return rProp.Name == "3DEffectProperties"; }); + if (pShapeProp != std::cend(aGrabBag)) + { + Sequence< PropertyValue > a3DEffectProps; + pShapeProp->Value >>= a3DEffectProps; + for( const auto& r3DEffectProp : std::as_const(a3DEffectProps) ) + { + if( r3DEffectProp.Name == "Camera" ) + r3DEffectProp.Value >>= aEffectProps; + else if( r3DEffectProp.Name == "LightRig" ) + r3DEffectProp.Value >>= aLightRigProps; + else if( r3DEffectProp.Name == "Shape3D" ) + r3DEffectProp.Value >>= aShape3DProps; + } + } + + auto pTextProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag), + [](const PropertyValue& rProp) { return rProp.Name == "Text3DEffectProperties"; }); + + if (pTextProp != std::cend(aGrabBag)) + { + Sequence< PropertyValue > a3DEffectProps; + pTextProp->Value >>= a3DEffectProps; + for( const auto& r3DEffectProp : std::as_const(a3DEffectProps) ) + { + if( r3DEffectProp.Name == "Camera" ) + r3DEffectProp.Value >>= aEffectProps; + else if( r3DEffectProp.Name == "LightRig" ) + r3DEffectProp.Value >>= aLightRigProps; + else if( r3DEffectProp.Name == "Shape3D" ) + r3DEffectProp.Value >>= aShape3DProps; + } + } + + if( !aEffectProps.hasElements() && !aLightRigProps.hasElements() && !aShape3DProps.hasElements() ) + return; + + bool bCameraRotationPresent = false; + sax_fastparser::FastAttributeList *aCameraAttrList = FastSerializerHelper::createAttrList(); + sax_fastparser::XFastAttributeListRef xCameraAttrList( aCameraAttrList ); + sax_fastparser::FastAttributeList *aCameraRotationAttrList = FastSerializerHelper::createAttrList(); + sax_fastparser::XFastAttributeListRef xRotAttrList( aCameraRotationAttrList ); + for( const auto& rEffectProp : std::as_const(aEffectProps) ) + { + if( rEffectProp.Name == "prst" ) + { + OUString sVal; + rEffectProp.Value >>= sVal; + aCameraAttrList->add( XML_prst, OUStringToOString( sVal, RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if( rEffectProp.Name == "fov" ) + { + float fVal = 0; + rEffectProp.Value >>= fVal; + aCameraAttrList->add( XML_fov, OString::number( fVal * 60000 ).getStr() ); + } + else if( rEffectProp.Name == "zoom" ) + { + float fVal = 1; + rEffectProp.Value >>= fVal; + aCameraAttrList->add( XML_zoom, OString::number( fVal * 100000 ).getStr() ); + } + else if( rEffectProp.Name == "rotLat" || + rEffectProp.Name == "rotLon" || + rEffectProp.Name == "rotRev" ) + { + sal_Int32 nVal = 0, nToken = XML_none; + rEffectProp.Value >>= nVal; + if( rEffectProp.Name == "rotLat" ) + nToken = XML_lat; + else if( rEffectProp.Name == "rotLon" ) + nToken = XML_lon; + else if( rEffectProp.Name == "rotRev" ) + nToken = XML_rev; + aCameraRotationAttrList->add( nToken, OString::number( nVal ).getStr() ); + bCameraRotationPresent = true; + } + } + + bool bLightRigRotationPresent = false; + sax_fastparser::FastAttributeList *aLightRigAttrList = FastSerializerHelper::createAttrList(); + sax_fastparser::XFastAttributeListRef xLightAttrList( aLightRigAttrList ); + sax_fastparser::FastAttributeList *aLightRigRotationAttrList = FastSerializerHelper::createAttrList(); + sax_fastparser::XFastAttributeListRef xLightRotAttrList( aLightRigRotationAttrList ); + for( const auto& rLightRigProp : std::as_const(aLightRigProps) ) + { + if( rLightRigProp.Name == "rig" || rLightRigProp.Name == "dir" ) + { + OUString sVal; + sal_Int32 nToken = XML_none; + rLightRigProp.Value >>= sVal; + if( rLightRigProp.Name == "rig" ) + nToken = XML_rig; + else if( rLightRigProp.Name == "dir" ) + nToken = XML_dir; + aLightRigAttrList->add( nToken, OUStringToOString( sVal, RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if( rLightRigProp.Name == "rotLat" || + rLightRigProp.Name == "rotLon" || + rLightRigProp.Name == "rotRev" ) + { + sal_Int32 nVal = 0, nToken = XML_none; + rLightRigProp.Value >>= nVal; + if( rLightRigProp.Name == "rotLat" ) + nToken = XML_lat; + else if( rLightRigProp.Name == "rotLon" ) + nToken = XML_lon; + else if( rLightRigProp.Name == "rotRev" ) + nToken = XML_rev; + aLightRigRotationAttrList->add( nToken, OString::number( nVal ).getStr() ); + bLightRigRotationPresent = true; + } + } + + mpFS->startElementNS(XML_a, XML_scene3d); + + if( aEffectProps.hasElements() ) + { + mpFS->startElementNS( XML_a, XML_camera, xCameraAttrList ); + if( bCameraRotationPresent ) + { + mpFS->singleElementNS( XML_a, XML_rot, xRotAttrList ); + } + mpFS->endElementNS( XML_a, XML_camera ); + } + else + { + // a:camera with Word default values - Word won't open the document if this is not present + mpFS->singleElementNS(XML_a, XML_camera, XML_prst, "orthographicFront"); + } + + if( aEffectProps.hasElements() ) + { + mpFS->startElementNS( XML_a, XML_lightRig, xLightAttrList ); + if( bLightRigRotationPresent ) + { + mpFS->singleElementNS( XML_a, XML_rot, xLightRotAttrList ); + } + mpFS->endElementNS( XML_a, XML_lightRig ); + } + else + { + // a:lightRig with Word default values - Word won't open the document if this is not present + mpFS->singleElementNS(XML_a, XML_lightRig, XML_rig, "threePt", XML_dir, "t"); + } + + mpFS->endElementNS( XML_a, XML_scene3d ); + + if( !aShape3DProps.hasElements() ) + return; + + bool bBevelTPresent = false, bBevelBPresent = false; + Sequence< PropertyValue > aExtrusionColorProps, aContourColorProps; + sax_fastparser::FastAttributeList *aBevelTAttrList = FastSerializerHelper::createAttrList(); + sax_fastparser::XFastAttributeListRef xBevelTAttrList( aBevelTAttrList ); + sax_fastparser::FastAttributeList *aBevelBAttrList = FastSerializerHelper::createAttrList(); + sax_fastparser::XFastAttributeListRef xBevelBAttrList( aBevelBAttrList ); + sax_fastparser::FastAttributeList *aShape3DAttrList = FastSerializerHelper::createAttrList(); + for( const auto& rShape3DProp : std::as_const(aShape3DProps) ) + { + if( rShape3DProp.Name == "extrusionH" || rShape3DProp.Name == "contourW" || rShape3DProp.Name == "z" ) + { + sal_Int32 nVal = 0, nToken = XML_none; + rShape3DProp.Value >>= nVal; + if( rShape3DProp.Name == "extrusionH" ) + nToken = XML_extrusionH; + else if( rShape3DProp.Name == "contourW" ) + nToken = XML_contourW; + else if( rShape3DProp.Name == "z" ) + nToken = XML_z; + aShape3DAttrList->add( nToken, OString::number( nVal ).getStr() ); + } + else if( rShape3DProp.Name == "prstMaterial" ) + { + OUString sVal; + rShape3DProp.Value >>= sVal; + aShape3DAttrList->add( XML_prstMaterial, OUStringToOString( sVal, RTL_TEXTENCODING_UTF8 ).getStr() ); + } + else if( rShape3DProp.Name == "extrusionClr" ) + { + rShape3DProp.Value >>= aExtrusionColorProps; + } + else if( rShape3DProp.Name == "contourClr" ) + { + rShape3DProp.Value >>= aContourColorProps; + } + else if( rShape3DProp.Name == "bevelT" || rShape3DProp.Name == "bevelB" ) + { + Sequence< PropertyValue > aBevelProps; + rShape3DProp.Value >>= aBevelProps; + if ( !aBevelProps.hasElements() ) + continue; + + sax_fastparser::FastAttributeList *aBevelAttrList = nullptr; + if( rShape3DProp.Name == "bevelT" ) + { + bBevelTPresent = true; + aBevelAttrList = aBevelTAttrList; + } + else + { + bBevelBPresent = true; + aBevelAttrList = aBevelBAttrList; + } + for( const auto& rBevelProp : std::as_const(aBevelProps) ) + { + if( rBevelProp.Name == "w" || rBevelProp.Name == "h" ) + { + sal_Int32 nVal = 0, nToken = XML_none; + rBevelProp.Value >>= nVal; + if( rBevelProp.Name == "w" ) + nToken = XML_w; + else if( rBevelProp.Name == "h" ) + nToken = XML_h; + aBevelAttrList->add( nToken, OString::number( nVal ).getStr() ); + } + else if( rBevelProp.Name == "prst" ) + { + OUString sVal; + rBevelProp.Value >>= sVal; + aBevelAttrList->add( XML_prst, OUStringToOString( sVal, RTL_TEXTENCODING_UTF8 ).getStr() ); + } + } + + } + } + + sax_fastparser::XFastAttributeListRef xAttrList( aShape3DAttrList ); + mpFS->startElementNS( XML_a, XML_sp3d, xAttrList ); + if( bBevelTPresent ) + { + mpFS->singleElementNS( XML_a, XML_bevelT, xBevelTAttrList ); + } + if( bBevelBPresent ) + { + mpFS->singleElementNS( XML_a, XML_bevelB, xBevelBAttrList ); + } + if( aExtrusionColorProps.hasElements() ) + { + OUString sSchemeClr; + ::Color nColor; + sal_Int32 nTransparency(0); + Sequence< PropertyValue > aColorTransformations; + for( const auto& rExtrusionColorProp : std::as_const(aExtrusionColorProps) ) + { + if( rExtrusionColorProp.Name == "schemeClr" ) + rExtrusionColorProp.Value >>= sSchemeClr; + else if( rExtrusionColorProp.Name == "schemeClrTransformations" ) + rExtrusionColorProp.Value >>= aColorTransformations; + else if( rExtrusionColorProp.Name == "rgbClr" ) + rExtrusionColorProp.Value >>= nColor; + else if( rExtrusionColorProp.Name == "rgbClrTransparency" ) + rExtrusionColorProp.Value >>= nTransparency; + } + mpFS->startElementNS(XML_a, XML_extrusionClr); + + if( sSchemeClr.isEmpty() ) + WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) ); + else + WriteColor( sSchemeClr, aColorTransformations ); + + mpFS->endElementNS( XML_a, XML_extrusionClr ); + } + if( aContourColorProps.hasElements() ) + { + OUString sSchemeClr; + ::Color nColor; + sal_Int32 nTransparency(0); + Sequence< PropertyValue > aColorTransformations; + for( const auto& rContourColorProp : std::as_const(aContourColorProps) ) + { + if( rContourColorProp.Name == "schemeClr" ) + rContourColorProp.Value >>= sSchemeClr; + else if( rContourColorProp.Name == "schemeClrTransformations" ) + rContourColorProp.Value >>= aColorTransformations; + else if( rContourColorProp.Name == "rgbClr" ) + rContourColorProp.Value >>= nColor; + else if( rContourColorProp.Name == "rgbClrTransparency" ) + rContourColorProp.Value >>= nTransparency; + } + mpFS->startElementNS(XML_a, XML_contourClr); + + if( sSchemeClr.isEmpty() ) + WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) ); + else + WriteColor( sSchemeClr, aContourColorProps ); + + mpFS->endElementNS( XML_a, XML_contourClr ); + } + mpFS->endElementNS( XML_a, XML_sp3d ); +} + +void DrawingML::WriteArtisticEffect( const Reference< XPropertySet >& rXPropSet ) +{ + if( !GetProperty( rXPropSet, "InteropGrabBag" ) ) + return; + + PropertyValue aEffect; + Sequence< PropertyValue > aGrabBag; + mAny >>= aGrabBag; + auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag), + [](const PropertyValue& rProp) { return rProp.Name == "ArtisticEffectProperties"; }); + if (pProp != std::cend(aGrabBag)) + pProp->Value >>= aEffect; + sal_Int32 nEffectToken = ArtisticEffectProperties::getEffectToken( aEffect.Name ); + if( nEffectToken == XML_none ) + return; + + Sequence< PropertyValue > aAttrs; + aEffect.Value >>= aAttrs; + sax_fastparser::FastAttributeList *aAttrList = FastSerializerHelper::createAttrList(); + OString sRelId; + for( const auto& rAttr : std::as_const(aAttrs) ) + { + sal_Int32 nToken = ArtisticEffectProperties::getEffectToken( rAttr.Name ); + if( nToken != XML_none ) + { + sal_Int32 nVal = 0; + rAttr.Value >>= nVal; + aAttrList->add( nToken, OString::number( nVal ).getStr() ); + } + else if( rAttr.Name == "OriginalGraphic" ) + { + Sequence< PropertyValue > aGraphic; + rAttr.Value >>= aGraphic; + Sequence< sal_Int8 > aGraphicData; + OUString sGraphicId; + for( const auto& rProp : std::as_const(aGraphic) ) + { + if( rProp.Name == "Id" ) + rProp.Value >>= sGraphicId; + else if( rProp.Name == "Data" ) + rProp.Value >>= aGraphicData; + } + sRelId = WriteWdpPicture( sGraphicId, aGraphicData ); + } + } + + mpFS->startElementNS(XML_a, XML_extLst); + mpFS->startElementNS(XML_a, XML_ext, XML_uri, "{BEBA8EAE-BF5A-486C-A8C5-ECC9F3942E4B}"); + mpFS->startElementNS( XML_a14, XML_imgProps, + FSNS(XML_xmlns, XML_a14), mpFB->getNamespaceURL(OOX_NS(a14)).toUtf8() ); + mpFS->startElementNS(XML_a14, XML_imgLayer, FSNS(XML_r, XML_embed), sRelId); + mpFS->startElementNS(XML_a14, XML_imgEffect); + + sax_fastparser::XFastAttributeListRef xAttrList( aAttrList ); + mpFS->singleElementNS( XML_a14, nEffectToken, xAttrList ); + + mpFS->endElementNS( XML_a14, XML_imgEffect ); + mpFS->endElementNS( XML_a14, XML_imgLayer ); + mpFS->endElementNS( XML_a14, XML_imgProps ); + mpFS->endElementNS( XML_a, XML_ext ); + mpFS->endElementNS( XML_a, XML_extLst ); +} + +OString DrawingML::WriteWdpPicture( const OUString& rFileId, const Sequence< sal_Int8 >& rPictureData ) +{ + std::map<OUString, OUString>::iterator aCachedItem = maWdpCache.find( rFileId ); + if( aCachedItem != maWdpCache.end() ) + return OUStringToOString( aCachedItem->second, RTL_TEXTENCODING_UTF8 ); + + OUString sFileName = "media/hdphoto" + OUString::number( mnWdpImageCounter++ ) + ".wdp"; + Reference< XOutputStream > xOutStream = mpFB->openFragmentStream( OUStringBuffer() + .appendAscii( GetComponentDir() ) + .append( "/" ) + .append( sFileName ) + .makeStringAndClear(), + "image/vnd.ms-photo" ); + OUString sId; + xOutStream->writeBytes( rPictureData ); + xOutStream->closeOutput(); + + sId = mpFB->addRelation( mpFS->getOutputStream(), + oox::getRelationship(Relationship::HDPHOTO), + OUStringBuffer() + .appendAscii( GetRelationCompPrefix() ) + .append( sFileName ) + .makeStringAndClear() ); + + maWdpCache[rFileId] = sId; + return OUStringToOString( sId, RTL_TEXTENCODING_UTF8 ); +} + +void DrawingML::WriteDiagram(const css::uno::Reference<css::drawing::XShape>& rXShape, int nDiagramId) +{ + uno::Reference<beans::XPropertySet> xPropSet(rXShape, uno::UNO_QUERY); + + uno::Reference<xml::dom::XDocument> dataDom; + uno::Reference<xml::dom::XDocument> layoutDom; + uno::Reference<xml::dom::XDocument> styleDom; + uno::Reference<xml::dom::XDocument> colorDom; + uno::Reference<xml::dom::XDocument> drawingDom; + uno::Sequence<uno::Sequence<uno::Any>> xDataRelSeq; + uno::Sequence<uno::Any> diagramDrawing; + + // retrieve the doms from the GrabBag + uno::Sequence<beans::PropertyValue> propList; + xPropSet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= propList; + for (const auto& rProp : std::as_const(propList)) + { + OUString propName = rProp.Name; + if (propName == "OOXData") + rProp.Value >>= dataDom; + else if (propName == "OOXLayout") + rProp.Value >>= layoutDom; + else if (propName == "OOXStyle") + rProp.Value >>= styleDom; + else if (propName == "OOXColor") + rProp.Value >>= colorDom; + else if (propName == "OOXDrawing") + { + rProp.Value >>= diagramDrawing; + diagramDrawing[0] + >>= drawingDom; // if there is OOXDrawing property then set drawingDom here only. + } + else if (propName == "OOXDiagramDataRels") + rProp.Value >>= xDataRelSeq; + } + + // check that we have the 4 mandatory XDocuments + // if not, there was an error importing and we won't output anything + if (!dataDom.is() || !layoutDom.is() || !styleDom.is() || !colorDom.is()) + return; + + // generate a unique id + sax_fastparser::FastAttributeList* pDocPrAttrList + = sax_fastparser::FastSerializerHelper::createAttrList(); + pDocPrAttrList->add(XML_id, OString::number(nDiagramId).getStr()); + OUString sName = "Diagram" + OUString::number(nDiagramId); + pDocPrAttrList->add(XML_name, OUStringToOString(sName, RTL_TEXTENCODING_UTF8).getStr()); + sax_fastparser::XFastAttributeListRef xDocPrAttrListRef(pDocPrAttrList); + + if (GetDocumentType() == DOCUMENT_DOCX) + { + mpFS->singleElementNS(XML_wp, XML_docPr, xDocPrAttrListRef); + mpFS->singleElementNS(XML_wp, XML_cNvGraphicFramePr); + + mpFS->startElementNS( + XML_a, XML_graphic, FSNS(XML_xmlns, XML_a), + mpFB->getNamespaceURL(OOX_NS(dml)).toUtf8()); + } + else + { + mpFS->startElementNS(XML_p, XML_nvGraphicFramePr); + + mpFS->singleElementNS(XML_p, XML_cNvPr, xDocPrAttrListRef); + mpFS->singleElementNS(XML_p, XML_cNvGraphicFramePr); + + mpFS->startElementNS(XML_p, XML_nvPr); + mpFS->startElementNS(XML_p, XML_extLst); + // change tracking extension - required in PPTX + mpFS->startElementNS(XML_p, XML_ext, XML_uri, "{D42A27DB-BD31-4B8C-83A1-F6EECF244321}"); + mpFS->singleElementNS(XML_p14, XML_modId, + FSNS(XML_xmlns, XML_p14), mpFB->getNamespaceURL(OOX_NS(p14)).toUtf8(), + XML_val, + OString::number(comphelper::rng::uniform_uint_distribution(1, SAL_MAX_UINT32))); + mpFS->endElementNS(XML_p, XML_ext); + mpFS->endElementNS(XML_p, XML_extLst); + mpFS->endElementNS(XML_p, XML_nvPr); + + mpFS->endElementNS(XML_p, XML_nvGraphicFramePr); + + // store size and position of background shape instead of group shape + // as some shapes may be outside + css::uno::Reference<css::drawing::XShapes> xShapes(rXShape, uno::UNO_QUERY); + if (xShapes.is() && xShapes->hasElements()) + { + css::uno::Reference<css::drawing::XShape> xShapeBg(xShapes->getByIndex(0), + uno::UNO_QUERY); + awt::Point aPos = xShapeBg->getPosition(); + awt::Size aSize = xShapeBg->getSize(); + WriteTransformation( + tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), + XML_p, false, false, 0, false); + } + + mpFS->startElementNS(XML_a, XML_graphic); + } + + mpFS->startElementNS(XML_a, XML_graphicData, XML_uri, + "http://schemas.openxmlformats.org/drawingml/2006/diagram"); + + OUString sRelationCompPrefix = OUString::createFromAscii(GetRelationCompPrefix()); + + // add data relation + OUString dataFileName = "diagrams/data" + OUString::number(nDiagramId) + ".xml"; + OString dataRelId = OUStringToOString( + mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDATA), + sRelationCompPrefix + dataFileName), + RTL_TEXTENCODING_UTF8); + + // add layout relation + OUString layoutFileName = "diagrams/layout" + OUString::number(nDiagramId) + ".xml"; + OString layoutRelId + = OUStringToOString(mpFB->addRelation(mpFS->getOutputStream(), + oox::getRelationship(Relationship::DIAGRAMLAYOUT), + sRelationCompPrefix + layoutFileName), + RTL_TEXTENCODING_UTF8); + + // add style relation + OUString styleFileName = "diagrams/quickStyle" + OUString::number(nDiagramId) + ".xml"; + OString styleRelId + = OUStringToOString(mpFB->addRelation(mpFS->getOutputStream(), + oox::getRelationship(Relationship::DIAGRAMQUICKSTYLE), + sRelationCompPrefix + styleFileName), + RTL_TEXTENCODING_UTF8); + + // add color relation + OUString colorFileName = "diagrams/colors" + OUString::number(nDiagramId) + ".xml"; + OString colorRelId + = OUStringToOString(mpFB->addRelation(mpFS->getOutputStream(), + oox::getRelationship(Relationship::DIAGRAMCOLORS), + sRelationCompPrefix + colorFileName), + RTL_TEXTENCODING_UTF8); + + OUString drawingFileName; + if (drawingDom.is()) + { + // add drawing relation + drawingFileName = "diagrams/drawing" + OUString::number(nDiagramId) + ".xml"; + OUString drawingRelId = mpFB->addRelation( + mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDRAWING), + sRelationCompPrefix + drawingFileName); + + // the data dom contains a reference to the drawing relation. We need to update it with the new generated + // relation value before writing the dom to a file + + // Get the dsp:damaModelExt node from the dom + uno::Reference<xml::dom::XNodeList> nodeList = dataDom->getElementsByTagNameNS( + "http://schemas.microsoft.com/office/drawing/2008/diagram", "dataModelExt"); + + // There must be one element only so get it + uno::Reference<xml::dom::XNode> node = nodeList->item(0); + + // Get the list of attributes of the node + uno::Reference<xml::dom::XNamedNodeMap> nodeMap = node->getAttributes(); + + // Get the node with the relId attribute and set its new value + uno::Reference<xml::dom::XNode> relIdNode = nodeMap->getNamedItem("relId"); + relIdNode->setNodeValue(drawingRelId); + } + + mpFS->singleElementNS(XML_dgm, XML_relIds, + FSNS(XML_xmlns, XML_dgm), mpFB->getNamespaceURL(OOX_NS(dmlDiagram)).toUtf8(), + FSNS(XML_xmlns, XML_r), mpFB->getNamespaceURL(OOX_NS(officeRel)).toUtf8(), + FSNS(XML_r, XML_dm), dataRelId, FSNS(XML_r, XML_lo), layoutRelId, + FSNS(XML_r, XML_qs), styleRelId, FSNS(XML_r, XML_cs), colorRelId); + + mpFS->endElementNS(XML_a, XML_graphicData); + mpFS->endElementNS(XML_a, XML_graphic); + + uno::Reference<xml::sax::XSAXSerializable> serializer; + uno::Reference<xml::sax::XWriter> writer + = xml::sax::Writer::create(comphelper::getProcessComponentContext()); + + OUString sDir = OUString::createFromAscii(GetComponentDir()); + + // write data file + serializer.set(dataDom, uno::UNO_QUERY); + uno::Reference<io::XOutputStream> xDataOutputStream = mpFB->openFragmentStream( + sDir + "/" + dataFileName, + "application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml"); + writer->setOutputStream(xDataOutputStream); + serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), + uno::Sequence<beans::StringPair>()); + + // write the associated Images and rels for data file + writeDiagramRels(xDataRelSeq, xDataOutputStream, "OOXDiagramDataRels", nDiagramId); + + // write layout file + serializer.set(layoutDom, uno::UNO_QUERY); + writer->setOutputStream(mpFB->openFragmentStream( + sDir + "/" + layoutFileName, + "application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml")); + serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), + uno::Sequence<beans::StringPair>()); + + // write style file + serializer.set(styleDom, uno::UNO_QUERY); + writer->setOutputStream(mpFB->openFragmentStream( + sDir + "/" + styleFileName, + "application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml")); + serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), + uno::Sequence<beans::StringPair>()); + + // write color file + serializer.set(colorDom, uno::UNO_QUERY); + writer->setOutputStream(mpFB->openFragmentStream( + sDir + "/" + colorFileName, + "application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml")); + serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), + uno::Sequence<beans::StringPair>()); + + // write drawing file + if (!drawingDom.is()) + return; + + serializer.set(drawingDom, uno::UNO_QUERY); + uno::Reference<io::XOutputStream> xDrawingOutputStream = mpFB->openFragmentStream( + sDir + "/" + drawingFileName, "application/vnd.ms-office.drawingml.diagramDrawing+xml"); + writer->setOutputStream(xDrawingOutputStream); + serializer->serialize( + uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW), + uno::Sequence<beans::StringPair>()); + + // write the associated Images and rels for drawing file + uno::Sequence<uno::Sequence<uno::Any>> xDrawingRelSeq; + diagramDrawing[1] >>= xDrawingRelSeq; + writeDiagramRels(xDrawingRelSeq, xDrawingOutputStream, "OOXDiagramDrawingRels", nDiagramId); +} + +void DrawingML::writeDiagramRels(const uno::Sequence<uno::Sequence<uno::Any>>& xRelSeq, + const uno::Reference<io::XOutputStream>& xOutStream, + const OUString& sGrabBagProperyName, int nDiagramId) +{ + // add image relationships of OOXData, OOXDiagram + OUString sType(oox::getRelationship(Relationship::IMAGE)); + uno::Reference<xml::sax::XWriter> xWriter + = xml::sax::Writer::create(comphelper::getProcessComponentContext()); + xWriter->setOutputStream(xOutStream); + + // retrieve the relationships from Sequence + for (sal_Int32 j = 0; j < xRelSeq.getLength(); j++) + { + // diagramDataRelTuple[0] => RID, + // diagramDataRelTuple[1] => xInputStream + // diagramDataRelTuple[2] => extension + uno::Sequence<uno::Any> diagramDataRelTuple = xRelSeq[j]; + + OUString sRelId; + OUString sExtension; + diagramDataRelTuple[0] >>= sRelId; + diagramDataRelTuple[2] >>= sExtension; + OUString sContentType; + if (sExtension.equalsIgnoreAsciiCase(".WMF")) + sContentType = "image/x-wmf"; + else + sContentType = "image/" + sExtension.copy(1); + sRelId = sRelId.copy(3); + + StreamDataSequence dataSeq; + diagramDataRelTuple[1] >>= dataSeq; + uno::Reference<io::XInputStream> dataImagebin( + new ::comphelper::SequenceInputStream(dataSeq)); + + //nDiagramId is used to make the name unique irrespective of the number of smart arts. + OUString sFragment = "media/" + sGrabBagProperyName + OUString::number(nDiagramId) + "_" + + OUString::number(j) + sExtension; + + PropertySet aProps(xOutStream); + aProps.setAnyProperty(PROP_RelId, uno::makeAny(sRelId.toInt32())); + + mpFB->addRelation(xOutStream, sType, "../" + sFragment); + + OUString sDir = OUString::createFromAscii(GetComponentDir()); + uno::Reference<io::XOutputStream> xBinOutStream + = mpFB->openFragmentStream(sDir + "/" + sFragment, sContentType); + + try + { + comphelper::OStorageHelper::CopyInputToOutput(dataImagebin, xBinOutStream); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("oox.drawingml", "DrawingML::writeDiagramRels Failed to copy grabbaged Image"); + } + dataImagebin->closeInput(); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |