Files
libreoffice/oox/source/export/drawingml.cxx
Regina Henschel 0818a3dcaf tdf#148784 consider StretchX in OOXML export
The export to custGeom uses currently a static version of the shape. Its
vertices are calculated with EnhancedCustomShape2d::GetParameter(). That
has parameters to determine whether ReplaceGeoWidth and ReplaceGeoHeight
has to be used. It needs to be used, in case the shape has property
StretchX or StretchY. That was missing. It is added now in cases where
GetParameter() returns a coordinate.

Not all cases are covered by unit tests. Further files for manual testing
are attached to the bug.

Change-Id: Idcdd081f855ed6c4e3a84dba08f8a2148ddfe54c
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/133463
Tested-by: Jenkins
Reviewed-by: Regina Henschel <rb.henschel@t-online.de>
2022-04-27 14:12:29 +02:00

5875 lines
228 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <config_features.h>
#include <config_folders.h>
#include <rtl/bootstrap.hxx>
#include <sal/log.hxx>
#include <oox/core/xmlfilterbase.hxx>
#include <oox/export/drawingml.hxx>
#include <oox/export/utils.hxx>
#include <oox/helper/propertyset.hxx>
#include <oox/drawingml/color.hxx>
#include <drawingml/fillproperties.hxx>
#include <drawingml/textparagraph.hxx>
#include <oox/token/namespaces.hxx>
#include <oox/token/properties.hxx>
#include <oox/token/relationship.hxx>
#include <oox/token/tokens.hxx>
#include <oox/drawingml/drawingmltypes.hxx>
#include <svtools/unitconv.hxx>
#include <sax/fastattribs.hxx>
#include <tools/diagnose_ex.h>
#include <comphelper/processfactory.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/range/b2drange.hxx>
#include <numeric>
#include <string_view>
#include <com/sun/star/awt/CharSet.hpp>
#include <com/sun/star/awt/FontDescriptor.hpp>
#include <com/sun/star/awt/FontSlant.hpp>
#include <com/sun/star/awt/FontStrikeout.hpp>
#include <com/sun/star/awt/FontWeight.hpp>
#include <com/sun/star/awt/FontUnderline.hpp>
#include <com/sun/star/awt/Gradient.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/XPropertyState.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/container/XEnumerationAccess.hpp>
#include <com/sun/star/container/XIndexAccess.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/drawing/BitmapMode.hpp>
#include <com/sun/star/drawing/ColorMode.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeAdjustmentValue.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeSegment.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeSegmentCommand.hpp>
#include <com/sun/star/drawing/Hatch.hpp>
#include <com/sun/star/drawing/LineDash.hpp>
#include <com/sun/star/drawing/LineJoint.hpp>
#include <com/sun/star/drawing/LineStyle.hpp>
#include <com/sun/star/drawing/TextFitToSizeType.hpp>
#include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
#include <com/sun/star/drawing/TextVerticalAdjust.hpp>
#include <com/sun/star/drawing/XShape.hpp>
#include <com/sun/star/drawing/XShapes.hpp>
#include <com/sun/star/drawing/FillStyle.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/graphic/XGraphic.hpp>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <com/sun/star/i18n/BreakIterator.hpp>
#include <com/sun/star/i18n/XBreakIterator.hpp>
#include <com/sun/star/io/XOutputStream.hpp>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/style/LineSpacing.hpp>
#include <com/sun/star/style/LineSpacingMode.hpp>
#include <com/sun/star/text/WritingMode.hpp>
#include <com/sun/star/text/WritingMode2.hpp>
#include <com/sun/star/text/GraphicCrop.hpp>
#include <com/sun/star/text/XText.hpp>
#include <com/sun/star/text/XTextColumns.hpp>
#include <com/sun/star/text/XTextContent.hpp>
#include <com/sun/star/text/XTextField.hpp>
#include <com/sun/star/text/XTextRange.hpp>
#include <com/sun/star/text/XTextFrame.hpp>
#include <com/sun/star/style/CaseMap.hpp>
#include <com/sun/star/xml/dom/XNodeList.hpp>
#include <com/sun/star/xml/sax/Writer.hpp>
#include <com/sun/star/xml/sax/XSAXSerializable.hpp>
#include <com/sun/star/container/XNamed.hpp>
#include <com/sun/star/drawing/XDrawPages.hpp>
#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
#include <comphelper/propertyvalue.hxx>
#include <comphelper/random.hxx>
#include <comphelper/seqstream.hxx>
#include <comphelper/storagehelper.hxx>
#include <comphelper/xmltools.hxx>
#include <o3tl/any.hxx>
#include <o3tl/safeint.hxx>
#include <tools/stream.hxx>
#include <unotools/fontdefs.hxx>
#include <vcl/cvtgrf.hxx>
#include <vcl/svapp.hxx>
#include <rtl/strbuf.hxx>
#include <filter/msfilter/escherex.hxx>
#include <filter/msfilter/util.hxx>
#include <editeng/outlobj.hxx>
#include <editeng/svxenum.hxx>
#include <editeng/unonames.hxx>
#include <editeng/unoprnms.hxx>
#include <editeng/flditem.hxx>
#include <editeng/escapementitem.hxx>
#include <svx/svdoashp.hxx>
#include <svx/svdomedia.hxx>
#include <svx/unoshape.hxx>
#include <svx/EnhancedCustomShape2d.hxx>
#include <drawingml/presetgeometrynames.hxx>
using namespace ::css;
using namespace ::css::beans;
using namespace ::css::drawing;
using namespace ::css::i18n;
using namespace ::css::style;
using namespace ::css::text;
using namespace ::css::uno;
using namespace ::css::container;
using namespace ::com::sun::star::drawing::EnhancedCustomShapeSegmentCommand;
using ::css::io::XOutputStream;
using ::sax_fastparser::FSHelperPtr;
using ::sax_fastparser::FastSerializerHelper;
namespace
{
/// Extracts start or end alpha information from a transparency gradient.
sal_Int32 GetAlphaFromTransparenceGradient(const awt::Gradient& rGradient, bool bStart)
{
// Our alpha is a gray color value.
sal_uInt8 nRed = ::Color(ColorTransparency, bStart ? rGradient.StartColor : rGradient.EndColor).GetRed();
// drawingML alpha is a percentage on a 0..100000 scale.
return (255 - nRed) * oox::drawingml::MAX_PERCENT / 255;
}
const char* g_aPredefinedClrNames[] = {
"dk1",
"lt1",
"dk2",
"lt2",
"accent1",
"accent2",
"accent3",
"accent4",
"accent5",
"accent6",
"hlink",
"folHlink",
};
}
namespace oox::drawingml {
URLTransformer::~URLTransformer()
{
}
OUString URLTransformer::getTransformedString(const OUString& rString) const
{
return rString;
}
bool URLTransformer::isExternalURL(const OUString& rURL) const
{
bool bExternal = true;
if (rURL.startsWith("#"))
bExternal = false;
return bExternal;
}
static css::uno::Any getLineDash( const css::uno::Reference<css::frame::XModel>& xModel, const OUString& rDashName )
{
css::uno::Reference<css::lang::XMultiServiceFactory> xFact(xModel, css::uno::UNO_QUERY);
css::uno::Reference<css::container::XNameAccess> xNameAccess(
xFact->createInstance("com.sun.star.drawing.DashTable"),
css::uno::UNO_QUERY );
if(xNameAccess.is())
{
if (!xNameAccess->hasByName(rDashName))
return css::uno::Any();
return xNameAccess->getByName(rDashName);
}
return css::uno::Any();
}
namespace
{
void WriteGradientPath(const awt::Gradient& rGradient, const FSHelperPtr& pFS, const bool bCircle)
{
pFS->startElementNS(XML_a, XML_path, XML_path, bCircle ? "circle" : "rect");
// Write the focus rectangle. Work with the focus point, and assume
// that it extends 50% in all directions. The below
// left/top/right/bottom values are percentages, where 0 means the
// edge of the tile rectangle and 100% means the center of it.
rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList(
sax_fastparser::FastSerializerHelper::createAttrList());
sal_Int32 nLeftPercent = rGradient.XOffset;
pAttributeList->add(XML_l, OString::number(nLeftPercent * PER_PERCENT));
sal_Int32 nTopPercent = rGradient.YOffset;
pAttributeList->add(XML_t, OString::number(nTopPercent * PER_PERCENT));
sal_Int32 nRightPercent = 100 - rGradient.XOffset;
pAttributeList->add(XML_r, OString::number(nRightPercent * PER_PERCENT));
sal_Int32 nBottomPercent = 100 - rGradient.YOffset;
pAttributeList->add(XML_b, OString::number(nBottomPercent * PER_PERCENT));
pFS->singleElementNS(XML_a, XML_fillToRect, pAttributeList);
pFS->endElementNS(XML_a, XML_path);
}
}
// not thread safe
int DrawingML::mnImageCounter = 1;
int DrawingML::mnWdpImageCounter = 1;
std::map<OUString, OUString> DrawingML::maWdpCache;
sal_Int32 DrawingML::mnDrawingMLCount = 0;
sal_Int32 DrawingML::mnVmlCount = 0;
std::stack<std::unordered_map<BitmapChecksum, OUString>> DrawingML::maExportGraphics;
sal_Int16 DrawingML::GetScriptType(const OUString& rStr)
{
if (rStr.getLength() > 0)
{
static Reference<css::i18n::XBreakIterator> xBreakIterator =
css::i18n::BreakIterator::create(comphelper::getProcessComponentContext());
sal_Int16 nScriptType = xBreakIterator->getScriptType(rStr, 0);
if (nScriptType == css::i18n::ScriptType::WEAK)
{
sal_Int32 nPos = xBreakIterator->nextScript(rStr, 0, nScriptType);
if (nPos < rStr.getLength())
nScriptType = xBreakIterator->getScriptType(rStr, nPos);
}
if (nScriptType != css::i18n::ScriptType::WEAK)
return nScriptType;
}
return css::i18n::ScriptType::LATIN;
}
void DrawingML::ResetCounters()
{
mnImageCounter = 1;
mnWdpImageCounter = 1;
maWdpCache.clear();
}
void DrawingML::ResetMlCounters()
{
mnDrawingMLCount = 0;
mnVmlCount = 0;
}
void DrawingML::PushExportGraphics()
{
maExportGraphics.emplace();
}
void DrawingML::PopExportGraphics()
{
maExportGraphics.pop();
}
bool DrawingML::GetProperty( const Reference< XPropertySet >& rXPropertySet, const OUString& aName )
{
try
{
mAny = rXPropertySet->getPropertyValue(aName);
if (mAny.hasValue())
return true;
}
catch( const Exception& )
{
/* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
}
return false;
}
bool DrawingML::GetPropertyAndState( const Reference< XPropertySet >& rXPropertySet, const Reference< XPropertyState >& rXPropertyState, const OUString& aName, PropertyState& eState )
{
try
{
mAny = rXPropertySet->getPropertyValue(aName);
if (mAny.hasValue())
{
eState = rXPropertyState->getPropertyState(aName);
return true;
}
}
catch( const Exception& )
{
/* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
}
return false;
}
namespace
{
/// Gets hexa value of color on string format.
OString getColorStr(const ::Color nColor)
{
// Transparency is a separate element.
OString sColor = OString::number(sal_uInt32(nColor) & 0x00FFFFFF, 16);
if (sColor.getLength() < 6)
{
OStringBuffer sBuf("0");
int remains = 5 - sColor.getLength();
while (remains > 0)
{
sBuf.append("0");
remains--;
}
sBuf.append(sColor);
sColor = sBuf.getStr();
}
return sColor;
}
}
void DrawingML::WriteColor( ::Color nColor, sal_Int32 nAlpha )
{
const auto sColor = getColorStr(nColor);
if( nAlpha < MAX_PERCENT )
{
mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
mpFS->endElementNS( XML_a, XML_srgbClr );
}
else
{
mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor);
}
}
void DrawingML::WriteColor( const OUString& sColorSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
{
// prevent writing a tag with empty val attribute
if( sColorSchemeName.isEmpty() )
return;
if( aTransformations.hasElements() )
{
mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
WriteColorTransformations( aTransformations, nAlpha );
mpFS->endElementNS( XML_a, XML_schemeClr );
}
else if(nAlpha < MAX_PERCENT)
{
mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
mpFS->endElementNS( XML_a, XML_schemeClr );
}
else
{
mpFS->singleElementNS(XML_a, XML_schemeClr, XML_val, sColorSchemeName);
}
}
void DrawingML::WriteColor( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
{
const auto sColor = getColorStr(nColor);
if( aTransformations.hasElements() )
{
mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
WriteColorTransformations(aTransformations, nAlpha);
mpFS->endElementNS(XML_a, XML_srgbClr);
}
else if(nAlpha < MAX_PERCENT)
{
mpFS->startElementNS(XML_a, XML_srgbClr, XML_val, sColor);
mpFS->singleElementNS(XML_a, XML_alpha, XML_val, OString::number(nAlpha));
mpFS->endElementNS(XML_a, XML_srgbClr);
}
else
{
mpFS->singleElementNS(XML_a, XML_srgbClr, XML_val, sColor);
}
}
void DrawingML::WriteColorTransformations( const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
{
for( const auto& rTransformation : aTransformations )
{
sal_Int32 nToken = Color::getColorTransformationToken( rTransformation.Name );
if( nToken != XML_TOKEN_INVALID && rTransformation.Value.hasValue() )
{
if(nToken == XML_alpha && nAlpha < MAX_PERCENT)
{
mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nAlpha));
}
else
{
sal_Int32 nValue = rTransformation.Value.get<sal_Int32>();
mpFS->singleElementNS(XML_a, nToken, XML_val, OString::number(nValue));
}
}
}
}
bool DrawingML::WriteCharColor(const css::uno::Reference<css::beans::XPropertySet>& xPropertySet)
{
if (!xPropertySet->getPropertySetInfo()->hasPropertyByName("CharColorTheme"))
{
return false;
}
sal_Int32 nCharColorTheme = -1;
xPropertySet->getPropertyValue("CharColorTheme") >>= nCharColorTheme;
if (nCharColorTheme < 0 || nCharColorTheme > 11)
{
return false;
}
const char* pColorName = g_aPredefinedClrNames[nCharColorTheme];
sal_Int32 nCharColorTintOrShade{};
xPropertySet->getPropertyValue("CharColorTintOrShade") >>= nCharColorTintOrShade;
if (nCharColorTintOrShade != 0)
{
return false;
}
mpFS->startElementNS(XML_a, XML_solidFill);
mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, pColorName);
sal_Int32 nCharColorLumMod{};
xPropertySet->getPropertyValue("CharColorLumMod") >>= nCharColorLumMod;
if (nCharColorLumMod != 10000)
{
mpFS->singleElementNS(XML_a, XML_lumMod, XML_val, OString::number(nCharColorLumMod * 10));
}
sal_Int32 nCharColorLumOff{};
xPropertySet->getPropertyValue("CharColorLumOff") >>= nCharColorLumOff;
if (nCharColorLumOff != 0)
{
mpFS->singleElementNS(XML_a, XML_lumOff, XML_val, OString::number(nCharColorLumOff * 10));
}
mpFS->endElementNS(XML_a, XML_schemeClr);
mpFS->endElementNS(XML_a, XML_solidFill);
return true;
}
void DrawingML::WriteSolidFill( ::Color nColor, sal_Int32 nAlpha )
{
mpFS->startElementNS(XML_a, XML_solidFill);
WriteColor( nColor, nAlpha );
mpFS->endElementNS( XML_a, XML_solidFill );
}
void DrawingML::WriteSolidFill( const OUString& sSchemeName, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
{
mpFS->startElementNS(XML_a, XML_solidFill);
WriteColor( sSchemeName, aTransformations, nAlpha );
mpFS->endElementNS( XML_a, XML_solidFill );
}
void DrawingML::WriteSolidFill( const ::Color nColor, const Sequence< PropertyValue >& aTransformations, sal_Int32 nAlpha )
{
mpFS->startElementNS(XML_a, XML_solidFill);
WriteColor(nColor, aTransformations, nAlpha);
mpFS->endElementNS(XML_a, XML_solidFill);
}
void DrawingML::WriteSolidFill( const Reference< XPropertySet >& rXPropSet )
{
// get fill color
if ( !GetProperty( rXPropSet, "FillColor" ) )
return;
sal_uInt32 nFillColor = mAny.get<sal_uInt32>();
// get InteropGrabBag and search the relevant attributes
OUString sColorFillScheme;
sal_uInt32 nOriginalColor = 0;
Sequence< PropertyValue > aStyleProperties, aTransformations;
if ( GetProperty( rXPropSet, "InteropGrabBag" ) )
{
Sequence< PropertyValue > aGrabBag;
mAny >>= aGrabBag;
for( const auto& rProp : std::as_const(aGrabBag) )
{
if( rProp.Name == "SpPrSolidFillSchemeClr" )
rProp.Value >>= sColorFillScheme;
else if( rProp.Name == "OriginalSolidFillClr" )
rProp.Value >>= nOriginalColor;
else if( rProp.Name == "StyleFillRef" )
rProp.Value >>= aStyleProperties;
else if( rProp.Name == "SpPrSolidFillSchemeClrTransformations" )
rProp.Value >>= aTransformations;
}
}
sal_Int32 nAlpha = MAX_PERCENT;
if( GetProperty( rXPropSet, "FillTransparence" ) )
{
sal_Int32 nTransparency = 0;
mAny >>= nTransparency;
// Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
nAlpha = (MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
}
// OOXML has no separate transparence gradient but uses transparency in the gradient stops.
// So we merge transparency and color and use gradient fill in such case.
awt::Gradient aTransparenceGradient;
bool bNeedGradientFill(false);
if (GetProperty(rXPropSet, "FillTransparenceGradient"))
{
mAny >>= aTransparenceGradient;
if (aTransparenceGradient.StartColor != aTransparenceGradient.EndColor)
bNeedGradientFill = true;
else if (aTransparenceGradient.StartColor != 0)
nAlpha = GetAlphaFromTransparenceGradient(aTransparenceGradient, true);
}
// write XML
if (bNeedGradientFill)
{
awt::Gradient aPseudoColorGradient;
aPseudoColorGradient.XOffset = aTransparenceGradient.XOffset;
aPseudoColorGradient.YOffset = aTransparenceGradient.YOffset;
aPseudoColorGradient.StartIntensity = 100;
aPseudoColorGradient.EndIntensity = 100;
aPseudoColorGradient.Angle = aTransparenceGradient.Angle;
aPseudoColorGradient.Border = aTransparenceGradient.Border;
aPseudoColorGradient.Style = aTransparenceGradient.Style;
aPseudoColorGradient.StartColor = nFillColor;
aPseudoColorGradient.EndColor = nFillColor;
aPseudoColorGradient.StepCount = aTransparenceGradient.StepCount;
mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
WriteGradientFill(aPseudoColorGradient, aTransparenceGradient);
mpFS->endElementNS( XML_a, XML_gradFill );
}
else if ( nFillColor != nOriginalColor )
{
// the user has set a different color for the shape
if (aTransformations.hasElements() || !WriteFillColor(rXPropSet))
{
WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha);
}
}
else if ( !sColorFillScheme.isEmpty() )
{
// the shape had a scheme color and the user didn't change it
WriteSolidFill( sColorFillScheme, aTransformations, nAlpha );
}
else
{
// the shape had a custom color and the user didn't change it
// tdf#124013
WriteSolidFill( ::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha );
}
}
bool DrawingML::WriteFillColor(const uno::Reference<beans::XPropertySet>& xPropertySet)
{
if (!xPropertySet->getPropertySetInfo()->hasPropertyByName("FillColorTheme"))
{
return false;
}
sal_Int32 nFillColorTheme = -1;
xPropertySet->getPropertyValue("FillColorTheme") >>= nFillColorTheme;
if (nFillColorTheme < 0 || nFillColorTheme > 11)
{
return false;
}
const char* pColorName = g_aPredefinedClrNames[nFillColorTheme];
mpFS->startElementNS(XML_a, XML_solidFill);
mpFS->singleElementNS(XML_a, XML_schemeClr, XML_val, pColorName);
mpFS->endElementNS(XML_a, XML_solidFill);
return true;
}
void DrawingML::WriteGradientStop(sal_uInt16 nStop, ::Color nColor, sal_Int32 nAlpha)
{
mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nStop * 1000));
WriteColor(nColor, nAlpha);
mpFS->endElementNS( XML_a, XML_gs );
}
::Color DrawingML::ColorWithIntensity( sal_uInt32 nColor, sal_uInt32 nIntensity )
{
return ::Color(ColorTransparency, ( ( ( nColor & 0xff ) * nIntensity ) / 100 )
| ( ( ( ( ( nColor & 0xff00 ) >> 8 ) * nIntensity ) / 100 ) << 8 )
| ( ( ( ( ( nColor & 0xff0000 ) >> 8 ) * nIntensity ) / 100 ) << 8 ));
}
bool DrawingML::EqualGradients( awt::Gradient aGradient1, awt::Gradient aGradient2 )
{
return aGradient1.Style == aGradient2.Style &&
aGradient1.StartColor == aGradient2.StartColor &&
aGradient1.EndColor == aGradient2.EndColor &&
aGradient1.Angle == aGradient2.Angle &&
aGradient1.Border == aGradient2.Border &&
aGradient1.XOffset == aGradient2.XOffset &&
aGradient1.YOffset == aGradient2.YOffset &&
aGradient1.StartIntensity == aGradient2.StartIntensity &&
aGradient1.EndIntensity == aGradient2.EndIntensity &&
aGradient1.StepCount == aGradient2.StepCount;
}
void DrawingML::WriteGradientFill( const Reference< XPropertySet >& rXPropSet )
{
awt::Gradient aGradient;
if (!GetProperty(rXPropSet, "FillGradient"))
return;
aGradient = *o3tl::doAccess<awt::Gradient>(mAny);
// get InteropGrabBag and search the relevant attributes
awt::Gradient aOriginalGradient;
Sequence< PropertyValue > aGradientStops;
if ( GetProperty( rXPropSet, "InteropGrabBag" ) )
{
Sequence< PropertyValue > aGrabBag;
mAny >>= aGrabBag;
for( const auto& rProp : std::as_const(aGrabBag) )
if( rProp.Name == "GradFillDefinition" )
rProp.Value >>= aGradientStops;
else if( rProp.Name == "OriginalGradFill" )
rProp.Value >>= aOriginalGradient;
}
// check if an ooxml gradient had been imported and if the user has modified it
// Gradient grab-bag depends on theme grab-bag, which is implemented
// only for DOCX.
if( EqualGradients( aOriginalGradient, aGradient ) && GetDocumentType() == DOCUMENT_DOCX)
{
// If we have no gradient stops that means original gradient were defined by a theme.
if( aGradientStops.hasElements() )
{
mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
WriteGrabBagGradientFill(aGradientStops, aGradient);
mpFS->endElementNS( XML_a, XML_gradFill );
}
}
else
{
awt::Gradient aTransparenceGradient;
mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
OUString sFillTransparenceGradientName;
if (GetProperty(rXPropSet, "FillTransparenceGradientName")
&& (mAny >>= sFillTransparenceGradientName)
&& !sFillTransparenceGradientName.isEmpty())
{
if (GetProperty(rXPropSet, "FillTransparenceGradient"))
aTransparenceGradient = *o3tl::doAccess<awt::Gradient>(mAny);
}
else if (GetProperty(rXPropSet, "FillTransparence"))
{
// currently only StartColor and EndColor are evaluated in WriteGradientFill()
sal_Int32 nTransparency = 0;
mAny >>= nTransparency;
// convert percent to gray color
nTransparency = nTransparency * 255/100;
const sal_Int32 aGrayColor = static_cast<sal_Int32>( nTransparency | nTransparency << 8 | nTransparency << 16 );
aTransparenceGradient.StartColor = aGrayColor;
aTransparenceGradient.EndColor = aGrayColor;
}
WriteGradientFill(aGradient, aTransparenceGradient);
mpFS->endElementNS(XML_a, XML_gradFill);
}
}
void DrawingML::WriteGrabBagGradientFill( const Sequence< PropertyValue >& aGradientStops, awt::Gradient rGradient )
{
// write back the original gradient
mpFS->startElementNS(XML_a, XML_gsLst);
// get original stops and write them
for( const auto& rGradientStop : aGradientStops )
{
Sequence< PropertyValue > aGradientStop;
rGradientStop.Value >>= aGradientStop;
// get values
OUString sSchemeClr;
double nPos = 0;
sal_Int16 nTransparency = 0;
::Color nRgbClr;
Sequence< PropertyValue > aTransformations;
for( const auto& rProp : std::as_const(aGradientStop) )
{
if( rProp.Name == "SchemeClr" )
rProp.Value >>= sSchemeClr;
else if( rProp.Name == "RgbClr" )
rProp.Value >>= nRgbClr;
else if( rProp.Name == "Pos" )
rProp.Value >>= nPos;
else if( rProp.Name == "Transparency" )
rProp.Value >>= nTransparency;
else if( rProp.Name == "Transformations" )
rProp.Value >>= aTransformations;
}
// write stop
mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nPos * 100000.0).getStr());
if( sSchemeClr.isEmpty() )
{
// Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
sal_Int32 nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency );
WriteColor( nRgbClr, nAlpha );
}
else
{
WriteColor( sSchemeClr, aTransformations );
}
mpFS->endElementNS( XML_a, XML_gs );
}
mpFS->endElementNS( XML_a, XML_gsLst );
switch (rGradient.Style)
{
default:
mpFS->singleElementNS(
XML_a, XML_lin, XML_ang,
OString::number(((3600 - rGradient.Angle + 900) * 6000) % 21600000));
break;
case awt::GradientStyle_RADIAL:
WriteGradientPath(rGradient, mpFS, true);
break;
}
}
void DrawingML::WriteGradientFill(awt::Gradient rGradient, awt::Gradient rTransparenceGradient,
const uno::Reference<beans::XPropertySet>& rXPropSet)
{
sal_Int32 nStartAlpha;
sal_Int32 nEndAlpha;
if( rXPropSet.is() && GetProperty(rXPropSet, "FillTransparence") )
{
sal_Int32 nTransparency = 0;
mAny >>= nTransparency;
nStartAlpha = nEndAlpha = (MAX_PERCENT - (PER_PERCENT * nTransparency));
}
else
{
nStartAlpha = GetAlphaFromTransparenceGradient(rTransparenceGradient, true);
nEndAlpha = GetAlphaFromTransparenceGradient(rTransparenceGradient, false);
}
switch( rGradient.Style )
{
default:
case awt::GradientStyle_LINEAR:
{
mpFS->startElementNS(XML_a, XML_gsLst);
WriteGradientStop(rGradient.Border, ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
nStartAlpha);
WriteGradientStop(100, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
nEndAlpha);
mpFS->endElementNS( XML_a, XML_gsLst );
mpFS->singleElementNS(
XML_a, XML_lin, XML_ang,
OString::number(((3600 - rGradient.Angle + 900) * 6000) % 21600000));
break;
}
case awt::GradientStyle_AXIAL:
{
mpFS->startElementNS(XML_a, XML_gsLst);
WriteGradientStop(0, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
nEndAlpha);
if (rGradient.Border > 0 && rGradient.Border < 100)
{
WriteGradientStop(rGradient.Border/2,
ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
nEndAlpha);
}
WriteGradientStop(50, ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
nStartAlpha);
if (rGradient.Border > 0 && rGradient.Border < 100)
{
WriteGradientStop(100 - rGradient.Border/2,
ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
nEndAlpha);
}
WriteGradientStop(100, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
nEndAlpha);
mpFS->endElementNS(XML_a, XML_gsLst);
mpFS->singleElementNS(
XML_a, XML_lin, XML_ang,
OString::number(((3600 - rGradient.Angle + 900) * 6000) % 21600000));
break;
}
case awt::GradientStyle_RADIAL:
case awt::GradientStyle_ELLIPTICAL:
case awt::GradientStyle_RECT:
case awt::GradientStyle_SQUARE:
{
mpFS->startElementNS(XML_a, XML_gsLst);
WriteGradientStop(0, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
nEndAlpha);
if (rGradient.Border > 0 && rGradient.Border < 100)
{
// Map border to an additional gradient stop, which has the
// same color as the final stop.
WriteGradientStop(100 - rGradient.Border,
ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
nStartAlpha);
}
WriteGradientStop(100,
ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
nStartAlpha);
mpFS->endElementNS(XML_a, XML_gsLst);
WriteGradientPath(rGradient, mpFS, rGradient.Style == awt::GradientStyle_RADIAL || rGradient.Style == awt::GradientStyle_ELLIPTICAL);
break;
}
}
}
void DrawingML::WriteLineArrow( const Reference< XPropertySet >& rXPropSet, bool bLineStart )
{
ESCHER_LineEnd eLineEnd;
sal_Int32 nArrowLength;
sal_Int32 nArrowWidth;
if ( !EscherPropertyContainer::GetLineArrow( bLineStart, rXPropSet, eLineEnd, nArrowLength, nArrowWidth ) )
return;
const char* len;
const char* type;
const char* width;
switch( nArrowLength )
{
case ESCHER_LineShortArrow:
len = "sm";
break;
default:
case ESCHER_LineMediumLenArrow:
len = "med";
break;
case ESCHER_LineLongArrow:
len = "lg";
break;
}
switch( eLineEnd )
{
default:
case ESCHER_LineNoEnd:
type = "none";
break;
case ESCHER_LineArrowEnd:
type = "triangle";
break;
case ESCHER_LineArrowStealthEnd:
type = "stealth";
break;
case ESCHER_LineArrowDiamondEnd:
type = "diamond";
break;
case ESCHER_LineArrowOvalEnd:
type = "oval";
break;
case ESCHER_LineArrowOpenEnd:
type = "arrow";
break;
}
switch( nArrowWidth )
{
case ESCHER_LineNarrowArrow:
width = "sm";
break;
default:
case ESCHER_LineMediumWidthArrow:
width = "med";
break;
case ESCHER_LineWideArrow:
width = "lg";
break;
}
mpFS->singleElementNS( XML_a, bLineStart ? XML_headEnd : XML_tailEnd,
XML_len, len,
XML_type, type,
XML_w, width );
}
void DrawingML::WriteOutline( const Reference<XPropertySet>& rXPropSet, Reference< frame::XModel > const & xModel )
{
drawing::LineStyle aLineStyle( drawing::LineStyle_NONE );
if (GetProperty(rXPropSet, "LineStyle"))
mAny >>= aLineStyle;
const LineCap aLineCap = GetProperty(rXPropSet, "LineCap") ? mAny.get<drawing::LineCap>() : LineCap_BUTT;
sal_uInt32 nLineWidth = 0;
sal_uInt32 nEmuLineWidth = 0;
::Color nColor;
sal_Int32 nColorAlpha = MAX_PERCENT;
bool bColorSet = false;
const char* cap = nullptr;
drawing::LineDash aLineDash;
bool bDashSet = false;
bool bNoFill = false;
// get InteropGrabBag and search the relevant attributes
OUString sColorFillScheme;
::Color aResolvedColorFillScheme;
::Color nOriginalColor;
::Color nStyleColor;
sal_uInt32 nStyleLineWidth = 0;
Sequence<PropertyValue> aStyleProperties;
Sequence<PropertyValue> aTransformations;
drawing::LineStyle aStyleLineStyle(drawing::LineStyle_NONE);
drawing::LineJoint aStyleLineJoint(drawing::LineJoint_NONE);
if (GetProperty(rXPropSet, "InteropGrabBag"))
{
Sequence<PropertyValue> aGrabBag;
mAny >>= aGrabBag;
for (const auto& rProp : std::as_const(aGrabBag))
{
if( rProp.Name == "SpPrLnSolidFillSchemeClr" )
rProp.Value >>= sColorFillScheme;
if( rProp.Name == "SpPrLnSolidFillResolvedSchemeClr" )
rProp.Value >>= aResolvedColorFillScheme;
else if( rProp.Name == "OriginalLnSolidFillClr" )
rProp.Value >>= nOriginalColor;
else if( rProp.Name == "StyleLnRef" )
rProp.Value >>= aStyleProperties;
else if( rProp.Name == "SpPrLnSolidFillSchemeClrTransformations" )
rProp.Value >>= aTransformations;
else if( rProp.Name == "EmuLineWidth" )
rProp.Value >>= nEmuLineWidth;
}
for (const auto& rStyleProp : std::as_const(aStyleProperties))
{
if( rStyleProp.Name == "Color" )
rStyleProp.Value >>= nStyleColor;
else if( rStyleProp.Name == "LineStyle" )
rStyleProp.Value >>= aStyleLineStyle;
else if( rStyleProp.Name == "LineJoint" )
rStyleProp.Value >>= aStyleLineJoint;
else if( rStyleProp.Name == "LineWidth" )
rStyleProp.Value >>= nStyleLineWidth;
}
}
if (GetProperty(rXPropSet, "LineWidth"))
mAny >>= nLineWidth;
switch (aLineStyle)
{
case drawing::LineStyle_NONE:
bNoFill = true;
break;
case drawing::LineStyle_DASH:
if (GetProperty(rXPropSet, "LineDash"))
{
aLineDash = mAny.get<drawing::LineDash>();
//this query is good for shapes, but in the case of charts it returns 0 values
if (aLineDash.Dots == 0 && aLineDash.DotLen == 0 && aLineDash.Dashes == 0 && aLineDash.DashLen == 0 && aLineDash.Distance == 0) {
OUString aLineDashName;
if (GetProperty(rXPropSet, "LineDashName"))
mAny >>= aLineDashName;
if (!aLineDashName.isEmpty() && xModel) {
css::uno::Any aAny = getLineDash(xModel, aLineDashName);
aAny >>= aLineDash;
}
}
}
else
{
//export the linestyle of chart wall (plot area) and chart page
OUString aLineDashName;
if (GetProperty(rXPropSet, "LineDashName"))
mAny >>= aLineDashName;
if (!aLineDashName.isEmpty() && xModel) {
css::uno::Any aAny = getLineDash(xModel, aLineDashName);
aAny >>= aLineDash;
}
}
bDashSet = true;
if (aLineDash.Style == DashStyle_ROUND || aLineDash.Style == DashStyle_ROUNDRELATIVE)
{
cap = "rnd";
}
SAL_INFO("oox.shape", "dash dots: " << aLineDash.Dots << " dashes: " << aLineDash.Dashes
<< " dotlen: " << aLineDash.DotLen << " dashlen: " << aLineDash.DashLen << " distance: " << aLineDash.Distance);
[[fallthrough]];
case drawing::LineStyle_SOLID:
default:
if (GetProperty(rXPropSet, "LineColor"))
{
nColor = ::Color(ColorTransparency, mAny.get<sal_uInt32>() & 0xffffff);
bColorSet = true;
}
if (GetProperty(rXPropSet, "LineTransparence"))
{
nColorAlpha = MAX_PERCENT - (mAny.get<sal_Int16>() * PER_PERCENT);
}
if (aLineCap == LineCap_ROUND)
cap = "rnd";
else if (aLineCap == LineCap_SQUARE)
cap = "sq";
break;
}
// if the line-width was not modified after importing then the original EMU value will be exported to avoid unexpected conversion (rounding) error
if (nEmuLineWidth == 0 || static_cast<sal_uInt32>(oox::drawingml::convertEmuToHmm(nEmuLineWidth)) != nLineWidth)
nEmuLineWidth = oox::drawingml::convertHmmToEmu(nLineWidth);
mpFS->startElementNS( XML_a, XML_ln,
XML_cap, cap,
XML_w, sax_fastparser::UseIf(OString::number(nEmuLineWidth),
nLineWidth == 0 || (nLineWidth > 1 && nStyleLineWidth != nLineWidth)) );
if( bColorSet )
{
if( nColor != nOriginalColor )
{
// the user has set a different color for the line
WriteSolidFill( nColor, nColorAlpha );
}
else if( !sColorFillScheme.isEmpty() )
{
// the line had a scheme color and the user didn't change it
WriteSolidFill( aResolvedColorFillScheme, aTransformations );
}
else
{
WriteSolidFill( nColor, nColorAlpha );
}
}
if( bDashSet && aStyleLineStyle != drawing::LineStyle_DASH )
{
// Try to detect if it might come from ms preset line style import.
// MS Office styles are always relative, both binary and OOXML.
// "dot" is always the first dash and "dash" the second one. All OOXML presets linestyles
// start with the longer one. Definitions are in OOXML part 1, 20.1.10.49
// The tests are strict, for to not catch styles from standard.sod (as of Aug 2019).
bool bIsConverted = false;
bool bIsRelative(aLineDash.Style == DashStyle_RECTRELATIVE || aLineDash.Style == DashStyle_ROUNDRELATIVE);
if ( bIsRelative && aLineDash.Dots == 1)
{ // The length were tweaked on import in case of prstDash. Revert it here.
sal_uInt32 nDotLen = aLineDash.DotLen;
sal_uInt32 nDashLen = aLineDash.DashLen;
sal_uInt32 nDistance = aLineDash.Distance;
if (aLineCap != LineCap_BUTT && nDistance >= 99)
{
nDistance -= 99;
nDotLen += 99;
if (nDashLen > 0)
nDashLen += 99;
}
// LO uses length 0 for 100%, if the attribute is missing in ODF.
// Other applications might write 100%. Make is unique for the conditions.
if (nDotLen == 0)
nDotLen = 100;
if (nDashLen == 0 && aLineDash.Dashes > 0)
nDashLen = 100;
bIsConverted = true;
if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dot");
}
else if (nDotLen == 400 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dash");
}
else if (nDotLen == 400 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dashDot");
}
else if (nDotLen == 800 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDash");
}
else if (nDotLen == 800 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDot");
}
else if (nDotLen == 800 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 300)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDotDot");
}
else if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDot");
}
else if (nDotLen == 300 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDash");
}
else if (nDotLen == 300 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 100)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDot");
}
else if (nDotLen == 300 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 100)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDotDot");
}
else
bIsConverted = false;
}
// Do not map our own line styles to OOXML prstDash values, because custDash gives better results.
if (!bIsConverted)
{
mpFS->startElementNS(XML_a, XML_custDash);
// In case of hairline we would need the current pixel size. Instead use a reasonable
// ersatz for it. The value is the same as SMALLEST_DASH_WIDTH in xattr.cxx.
// (And it makes sure fLineWidth is not zero in below division.)
double fLineWidth = nLineWidth > 0 ? nLineWidth : 26.95;
int i;
double fSp = bIsRelative ? aLineDash.Distance : aLineDash.Distance * 100.0 / fLineWidth;
// LO uses line width, in case Distance is zero. MS Office would use a space of zero length.
// So set 100% explicitly.
if (aLineDash.Distance <= 0)
fSp = 100.0;
// In case of custDash, round caps are included in dash length in MS Office. Square caps are added
// to dash length, same as in ODF. Change the length values accordingly.
if (aLineCap == LineCap_ROUND && fSp > 99.0)
fSp -= 99.0;
if (aLineDash.Dots > 0)
{
double fD = bIsRelative ? aLineDash.DotLen : aLineDash.DotLen * 100.0 / fLineWidth;
// LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended.
if (aLineDash.DotLen == 0)
fD = 100.0;
// Tweak dash length, see above.
if (aLineCap == LineCap_ROUND && fSp > 99.0)
fD += 99.0;
for( i = 0; i < aLineDash.Dots; i ++ )
{
mpFS->singleElementNS( XML_a, XML_ds,
XML_d , write1000thOfAPercent(fD),
XML_sp, write1000thOfAPercent(fSp) );
}
}
if ( aLineDash.Dashes > 0 )
{
double fD = bIsRelative ? aLineDash.DashLen : aLineDash.DashLen * 100.0 / fLineWidth;
// LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended.
if (aLineDash.DashLen == 0)
fD = 100.0;
// Tweak dash length, see above.
if (aLineCap == LineCap_ROUND && fSp > 99.0)
fD += 99.0;
for( i = 0; i < aLineDash.Dashes; i ++ )
{
mpFS->singleElementNS( XML_a , XML_ds,
XML_d , write1000thOfAPercent(fD),
XML_sp, write1000thOfAPercent(fSp) );
}
}
SAL_WARN_IF(nLineWidth <= 0,
"oox.shape", "while writing outline - custom dash - line width was < 0 : " << nLineWidth);
SAL_WARN_IF(aLineDash.Dashes < 0,
"oox.shape", "while writing outline - custom dash - number of dashes was < 0 : " << aLineDash.Dashes);
SAL_WARN_IF(aLineDash.Dashes > 0 && aLineDash.DashLen <= 0,
"oox.shape", "while writing outline - custom dash - dash length was < 0 : " << aLineDash.DashLen);
SAL_WARN_IF(aLineDash.Dots < 0,
"oox.shape", "while writing outline - custom dash - number of dots was < 0 : " << aLineDash.Dots);
SAL_WARN_IF(aLineDash.Dots > 0 && aLineDash.DotLen <= 0,
"oox.shape", "while writing outline - custom dash - dot length was < 0 : " << aLineDash.DotLen);
SAL_WARN_IF(aLineDash.Distance <= 0,
"oox.shape", "while writing outline - custom dash - distance was < 0 : " << aLineDash.Distance);
mpFS->endElementNS( XML_a, XML_custDash );
}
}
if (!bNoFill && nLineWidth > 1 && GetProperty(rXPropSet, "LineJoint"))
{
LineJoint eLineJoint = mAny.get<LineJoint>();
if( aStyleLineJoint == LineJoint_NONE || aStyleLineJoint != eLineJoint )
{
// style-defined line joint does not exist, or is different from the shape's joint
switch( eLineJoint )
{
case LineJoint_NONE:
case LineJoint_BEVEL:
mpFS->singleElementNS(XML_a, XML_bevel);
break;
default:
case LineJoint_MIDDLE:
case LineJoint_MITER:
mpFS->singleElementNS(XML_a, XML_miter);
break;
case LineJoint_ROUND:
mpFS->singleElementNS(XML_a, XML_round);
break;
}
}
}
if( !bNoFill )
{
WriteLineArrow( rXPropSet, true );
WriteLineArrow( rXPropSet, false );
}
else
{
mpFS->singleElementNS(XML_a, XML_noFill);
}
mpFS->endElementNS( XML_a, XML_ln );
}
const char* DrawingML::GetComponentDir() const
{
switch ( meDocumentType )
{
case DOCUMENT_DOCX: return "word";
case DOCUMENT_PPTX: return "ppt";
case DOCUMENT_XLSX: return "xl";
}
return "unknown";
}
const char* DrawingML::GetRelationCompPrefix() const
{
switch ( meDocumentType )
{
case DOCUMENT_DOCX: return "";
case DOCUMENT_PPTX:
case DOCUMENT_XLSX: return "../";
}
return "unknown";
}
OUString DrawingML::WriteImage( const Graphic& rGraphic , bool bRelPathToMedia )
{
GfxLink aLink = rGraphic.GetGfxLink ();
BitmapChecksum aChecksum = rGraphic.GetChecksum();
OUString sMediaType;
const char* pExtension = "";
OUString sRelId;
OUString sPath;
// tdf#74670 tdf#91286 Save image only once
if (!maExportGraphics.empty())
{
auto aIterator = maExportGraphics.top().find(aChecksum);
if (aIterator != maExportGraphics.top().end())
sPath = aIterator->second;
}
if (sPath.isEmpty())
{
SvMemoryStream aStream;
const void* aData = aLink.GetData();
std::size_t nDataSize = aLink.GetDataSize();
switch (aLink.GetType())
{
case GfxLinkType::NativeGif:
sMediaType = "image/gif";
pExtension = ".gif";
break;
// #i15508# added BMP type for better exports
// export not yet active, so adding for reference (not checked)
case GfxLinkType::NativeBmp:
sMediaType = "image/bmp";
pExtension = ".bmp";
break;
case GfxLinkType::NativeJpg:
sMediaType = "image/jpeg";
pExtension = ".jpeg";
break;
case GfxLinkType::NativePng:
sMediaType = "image/png";
pExtension = ".png";
break;
case GfxLinkType::NativeTif:
sMediaType = "image/tiff";
pExtension = ".tif";
break;
case GfxLinkType::NativeWmf:
sMediaType = "image/x-wmf";
pExtension = ".wmf";
break;
case GfxLinkType::NativeMet:
sMediaType = "image/x-met";
pExtension = ".met";
break;
case GfxLinkType::NativePct:
sMediaType = "image/x-pict";
pExtension = ".pct";
break;
case GfxLinkType::NativeMov:
sMediaType = "application/movie";
pExtension = ".MOV";
break;
default:
{
GraphicType aType = rGraphic.GetType();
if (aType == GraphicType::Bitmap || aType == GraphicType::GdiMetafile)
{
if (aType == GraphicType::Bitmap)
{
(void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::PNG);
sMediaType = "image/png";
pExtension = ".png";
}
else
{
(void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::EMF);
sMediaType = "image/x-emf";
pExtension = ".emf";
}
}
else
{
SAL_WARN("oox.shape", "unhandled graphic type " << static_cast<int>(aType));
/*Earlier, even in case of unhandled graphic types we were
proceeding to write the image, which would eventually
write an empty image with a zero size, and return a valid
relationID, which is incorrect.
*/
return sRelId;
}
aData = aStream.GetData();
nDataSize = aStream.GetEndOfData();
break;
}
}
Reference<XOutputStream> xOutStream = mpFB->openFragmentStream(
OUStringBuffer()
.appendAscii(GetComponentDir())
.append("/media/image" + OUString::number(mnImageCounter))
.appendAscii(pExtension)
.makeStringAndClear(),
sMediaType);
xOutStream->writeBytes(Sequence<sal_Int8>(static_cast<const sal_Int8*>(aData), nDataSize));
xOutStream->closeOutput();
const OString sRelPathToMedia = "media/image";
OString sRelationCompPrefix;
if (bRelPathToMedia)
sRelationCompPrefix = "../";
else
sRelationCompPrefix = GetRelationCompPrefix();
sPath = OUStringBuffer()
.appendAscii(sRelationCompPrefix.getStr())
.appendAscii(sRelPathToMedia.getStr())
.append(static_cast<sal_Int32>(mnImageCounter++))
.appendAscii(pExtension)
.makeStringAndClear();
if (!maExportGraphics.empty())
maExportGraphics.top()[aChecksum] = sPath;
}
sRelId = mpFB->addRelation( mpFS->getOutputStream(),
oox::getRelationship(Relationship::IMAGE),
sPath );
return sRelId;
}
void DrawingML::WriteMediaNonVisualProperties(const css::uno::Reference<css::drawing::XShape>& xShape)
{
SdrMediaObj* pMediaObj = dynamic_cast<SdrMediaObj*>(SdrObject::getSdrObjectFromXShape(xShape));
if (!pMediaObj)
return;
// extension
OUString aExtension;
const OUString& rURL(pMediaObj->getURL());
int nLastDot = rURL.lastIndexOf('.');
if (nLastDot >= 0)
aExtension = rURL.copy(nLastDot);
bool bEmbed = rURL.startsWith("vnd.sun.star.Package:");
Relationship eMediaType = Relationship::VIDEO;
// mime type
#if HAVE_FEATURE_AVMEDIA
OUString aMimeType(pMediaObj->getMediaProperties().getMimeType());
#else
OUString aMimeType("none");
#endif
if (aMimeType == "application/vnd.sun.star.media")
{
// try to set something better
// TODO fix the importer to actually set the mimetype on import
if (aExtension.equalsIgnoreAsciiCase(".avi"))
aMimeType = "video/x-msvideo";
else if (aExtension.equalsIgnoreAsciiCase(".flv"))
aMimeType = "video/x-flv";
else if (aExtension.equalsIgnoreAsciiCase(".mp4"))
aMimeType = "video/mp4";
else if (aExtension.equalsIgnoreAsciiCase(".mov"))
aMimeType = "video/quicktime";
else if (aExtension.equalsIgnoreAsciiCase(".ogv"))
aMimeType = "video/ogg";
else if (aExtension.equalsIgnoreAsciiCase(".wmv"))
aMimeType = "video/x-ms-wmv";
else if (aExtension.equalsIgnoreAsciiCase(".wav"))
{
aMimeType = "audio/x-wav";
eMediaType = Relationship::AUDIO;
}
else if (aExtension.equalsIgnoreAsciiCase(".m4a"))
{
aMimeType = "audio/mp4";
eMediaType = Relationship::AUDIO;
}
else if (aExtension.equalsIgnoreAsciiCase(".mp3"))
{
aMimeType = "audio/mp3";
eMediaType = Relationship::AUDIO;
}
}
OUString aVideoFileRelId;
OUString aMediaRelId;
if (bEmbed)
{
// copy the video stream
Reference<XOutputStream> xOutStream = mpFB->openFragmentStream(OUStringBuffer()
.appendAscii(GetComponentDir())
.append("/media/media" +
OUString::number(mnImageCounter) +
aExtension)
.makeStringAndClear(),
aMimeType);
uno::Reference<io::XInputStream> xInputStream(pMediaObj->GetInputStream());
comphelper::OStorageHelper::CopyInputToOutput(xInputStream, xOutStream);
xOutStream->closeOutput();
// create the relation
OUString aPath = OUStringBuffer().appendAscii(GetRelationCompPrefix())
.append("media/media" + OUString::number(mnImageCounter++) + aExtension)
.makeStringAndClear();
aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), aPath);
aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), aPath);
}
else
{
aVideoFileRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(eMediaType), rURL, true);
aMediaRelId = mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::MEDIA), rURL, true);
}
GetFS()->startElementNS(XML_p, XML_nvPr);
GetFS()->singleElementNS(XML_a, eMediaType == Relationship::VIDEO ? XML_videoFile : XML_audioFile,
FSNS(XML_r, XML_link), aVideoFileRelId);
GetFS()->startElementNS(XML_p, XML_extLst);
// media extensions; google this ID for details
GetFS()->startElementNS(XML_p, XML_ext, XML_uri, "{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}");
GetFS()->singleElementNS(XML_p14, XML_media,
bEmbed? FSNS(XML_r, XML_embed): FSNS(XML_r, XML_link), aMediaRelId);
GetFS()->endElementNS(XML_p, XML_ext);
GetFS()->endElementNS(XML_p, XML_extLst);
GetFS()->endElementNS(XML_p, XML_nvPr);
}
void DrawingML::WriteImageBrightnessContrastTransparence(uno::Reference<beans::XPropertySet> const & rXPropSet)
{
sal_Int16 nBright = 0;
sal_Int32 nContrast = 0;
sal_Int32 nTransparence = 0;
if (GetProperty(rXPropSet, "AdjustLuminance"))
nBright = mAny.get<sal_Int16>();
if (GetProperty(rXPropSet, "AdjustContrast"))
nContrast = mAny.get<sal_Int32>();
// Used for shapes with picture fill
if (GetProperty(rXPropSet, "FillTransparence"))
nTransparence = mAny.get<sal_Int32>();
// Used for pictures
if (nTransparence == 0 && GetProperty(rXPropSet, "Transparency"))
nTransparence = static_cast<sal_Int32>(mAny.get<sal_Int16>());
if (GetProperty(rXPropSet, "GraphicColorMode"))
{
drawing::ColorMode aColorMode;
mAny >>= aColorMode;
if (aColorMode == drawing::ColorMode_GREYS)
mpFS->singleElementNS(XML_a, XML_grayscl);
else if (aColorMode == drawing::ColorMode_MONO)
//black/white has a 0,5 threshold in LibreOffice
mpFS->singleElementNS(XML_a, XML_biLevel, XML_thresh, OString::number(50000));
else if (aColorMode == drawing::ColorMode_WATERMARK)
{
//map watermark with mso washout
nBright = 70;
nContrast = -70;
}
}
if (nBright || nContrast)
{
mpFS->singleElementNS(XML_a, XML_lum,
XML_bright, sax_fastparser::UseIf(OString::number(nBright * 1000), nBright != 0),
XML_contrast, sax_fastparser::UseIf(OString::number(nContrast * 1000), nContrast != 0));
}
if (nTransparence)
{
sal_Int32 nAlphaMod = (100 - nTransparence ) * PER_PERCENT;
mpFS->singleElementNS(XML_a, XML_alphaModFix, XML_amt, OString::number(nAlphaMod));
}
}
OUString DrawingML::WriteXGraphicBlip(uno::Reference<beans::XPropertySet> const & rXPropSet,
uno::Reference<graphic::XGraphic> const & rxGraphic,
bool bRelPathToMedia)
{
OUString sRelId;
if (!rxGraphic.is())
return sRelId;
Graphic aGraphic(rxGraphic);
sRelId = WriteImage(aGraphic, bRelPathToMedia);
mpFS->startElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelId);
WriteImageBrightnessContrastTransparence(rXPropSet);
WriteArtisticEffect(rXPropSet);
mpFS->endElementNS(XML_a, XML_blip);
return sRelId;
}
void DrawingML::WriteXGraphicBlipMode(uno::Reference<beans::XPropertySet> const & rXPropSet,
uno::Reference<graphic::XGraphic> const & rxGraphic)
{
BitmapMode eBitmapMode(BitmapMode_NO_REPEAT);
if (GetProperty(rXPropSet, "FillBitmapMode"))
mAny >>= eBitmapMode;
SAL_INFO("oox.shape", "fill bitmap mode: " << int(eBitmapMode));
switch (eBitmapMode)
{
case BitmapMode_REPEAT:
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));
sal_Int32 nAlpha = MAX_PERCENT;
if (GetProperty(rXPropSet, "FillTransparence"))
{
sal_Int32 nTransparency = 0;
mAny >>= nTransparency;
nAlpha = (MAX_PERCENT - (PER_PERCENT * nTransparency));
}
mpFS->startElementNS(XML_a, XML_fgClr);
WriteColor(::Color(ColorTransparency, rHatch.Color), nAlpha);
mpFS->endElementNS( XML_a , XML_fgClr );
::Color nColor = COL_WHITE;
if ( GetProperty( rXPropSet, "FillBackground" ) )
{
bool isBackgroundFilled = false;
mAny >>= isBackgroundFilled;
if( isBackgroundFilled )
{
if( GetProperty( rXPropSet, "FillColor" ) )
{
mAny >>= nColor;
}
}
else
nAlpha = 0;
}
mpFS->startElementNS(XML_a, XML_bgClr);
WriteColor(nColor, nAlpha);
mpFS->endElementNS( XML_a , XML_bgClr );
mpFS->endElementNS( XML_a , XML_pattFill );
}
void DrawingML::WriteGraphicCropProperties(uno::Reference<beans::XPropertySet> const & rXPropSet,
Size const & rOriginalSize,
MapMode const & rMapMode)
{
if (!GetProperty(rXPropSet, "GraphicCrop"))
return;
css::text::GraphicCrop aGraphicCropStruct;
mAny >>= aGraphicCropStruct;
if(GetProperty(rXPropSet, "CustomShapeGeometry"))
{
// tdf#134210 GraphicCrop property is handled in import filter because of LibreOffice has not core
// feature. We cropped the bitmap physically and MSO shouldn't crop bitmap one more time. When we
// have core feature for graphic cropping in custom shapes, we should uncomment the code anymore.
mpFS->singleElementNS( XML_a, XML_srcRect);
}
else
{
Size aOriginalSize(rOriginalSize);
// GraphicCrop is in mm100, so in case the original size is in pixels, convert it over.
if (rMapMode.GetMapUnit() == MapUnit::MapPixel)
aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, MapMode(MapUnit::Map100thMM));
if ( (0 != aGraphicCropStruct.Left) || (0 != aGraphicCropStruct.Top) || (0 != aGraphicCropStruct.Right) || (0 != aGraphicCropStruct.Bottom) )
{
mpFS->singleElementNS( XML_a, XML_srcRect,
XML_l, OString::number(rtl::math::round(aGraphicCropStruct.Left * 100000.0 / aOriginalSize.Width())),
XML_t, OString::number(rtl::math::round(aGraphicCropStruct.Top * 100000.0 / aOriginalSize.Height())),
XML_r, OString::number(rtl::math::round(aGraphicCropStruct.Right * 100000.0 / aOriginalSize.Width())),
XML_b, OString::number(rtl::math::round(aGraphicCropStruct.Bottom * 100000.0 / aOriginalSize.Height())) );
}
}
}
void DrawingML::WriteSrcRectXGraphic(uno::Reference<beans::XPropertySet> const & rxPropertySet,
uno::Reference<graphic::XGraphic> const & rxGraphic)
{
Graphic aGraphic(rxGraphic);
Size aOriginalSize = aGraphic.GetPrefSize();
const MapMode& rMapMode = aGraphic.GetPrefMapMode();
WriteGraphicCropProperties(rxPropertySet, aOriginalSize, rMapMode);
}
void DrawingML::WriteXGraphicStretch(uno::Reference<beans::XPropertySet> const & rXPropSet,
uno::Reference<graphic::XGraphic> const & rxGraphic)
{
if (GetDocumentType() != DOCUMENT_DOCX)
{
// Limiting the area used for stretching is not supported in Impress.
mpFS->singleElementNS(XML_a, XML_stretch);
return;
}
mpFS->startElementNS(XML_a, XML_stretch);
bool bCrop = false;
if (GetProperty(rXPropSet, "GraphicCrop"))
{
css::text::GraphicCrop aGraphicCropStruct;
mAny >>= aGraphicCropStruct;
if ((0 != aGraphicCropStruct.Left)
|| (0 != aGraphicCropStruct.Top)
|| (0 != aGraphicCropStruct.Right)
|| (0 != aGraphicCropStruct.Bottom))
{
Graphic aGraphic(rxGraphic);
Size aOriginalSize(aGraphic.GetPrefSize());
mpFS->singleElementNS(XML_a, XML_fillRect,
XML_l, OString::number(((aGraphicCropStruct.Left) * 100000) / aOriginalSize.Width()),
XML_t, OString::number(((aGraphicCropStruct.Top) * 100000) / aOriginalSize.Height()),
XML_r, OString::number(((aGraphicCropStruct.Right) * 100000) / aOriginalSize.Width()),
XML_b, OString::number(((aGraphicCropStruct.Bottom) * 100000) / aOriginalSize.Height()));
bCrop = true;
}
}
if (!bCrop)
{
mpFS->singleElementNS(XML_a, XML_fillRect);
}
mpFS->endElementNS(XML_a, XML_stretch);
}
namespace
{
bool IsTopGroupObj(const uno::Reference<drawing::XShape>& xShape)
{
SdrObject* pObject = SdrObject::getSdrObjectFromXShape(xShape);
if (!pObject)
return false;
if (pObject->getParentSdrObjectFromSdrObject())
return false;
return pObject->IsGroupObject();
}
}
void DrawingML::WriteTransformation(const Reference< XShape >& xShape, const tools::Rectangle& rRect,
sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, sal_Int32 nRotation, bool bIsGroupShape)
{
mpFS->startElementNS( nXmlNamespace, XML_xfrm,
XML_flipH, sax_fastparser::UseIf("1", bFlipH),
XML_flipV, sax_fastparser::UseIf("1", bFlipV),
XML_rot, sax_fastparser::UseIf(OString::number(nRotation), nRotation % 21600000 != 0));
sal_Int32 nLeft = rRect.Left();
sal_Int32 nTop = rRect.Top();
if (GetDocumentType() == DOCUMENT_DOCX && !m_xParent.is())
{
nLeft = 0;
nTop = 0;
}
sal_Int32 nChildLeft = nLeft;
sal_Int32 nChildTop = nTop;
mpFS->singleElementNS(XML_a, XML_off,
XML_x, OString::number(oox::drawingml::convertHmmToEmu(nLeft)),
XML_y, OString::number(oox::drawingml::convertHmmToEmu(nTop)));
mpFS->singleElementNS(XML_a, XML_ext,
XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())),
XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight())));
if (bIsGroupShape && (GetDocumentType() != DOCUMENT_DOCX || IsTopGroupObj(xShape)))
{
mpFS->singleElementNS(XML_a, XML_chOff,
XML_x, OString::number(oox::drawingml::convertHmmToEmu(nChildLeft)),
XML_y, OString::number(oox::drawingml::convertHmmToEmu(nChildTop)));
mpFS->singleElementNS(XML_a, XML_chExt,
XML_cx, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetWidth())),
XML_cy, OString::number(oox::drawingml::convertHmmToEmu(rRect.GetHeight())));
}
mpFS->endElementNS( nXmlNamespace, XML_xfrm );
}
void DrawingML::WriteShapeTransformation( const Reference< XShape >& rXShape, sal_Int32 nXmlNamespace, bool bFlipH, bool bFlipV, bool bSuppressRotation, bool bSuppressFlipping, bool bFlippedBeforeRotation )
{
SAL_INFO("oox.shape", "write shape transformation");
Degree100 nRotation;
Degree100 nCameraRotation;
awt::Point aPos = rXShape->getPosition();
awt::Size aSize = rXShape->getSize();
bool bFlipHWrite = bFlipH && !bSuppressFlipping;
bool bFlipVWrite = bFlipV && !bSuppressFlipping;
bFlipH = bFlipH && !bFlippedBeforeRotation;
bFlipV = bFlipV && !bFlippedBeforeRotation;
if (GetDocumentType() == DOCUMENT_DOCX && m_xParent.is())
{
awt::Point aParentPos = m_xParent->getPosition();
aPos.X -= aParentPos.X;
aPos.Y -= aParentPos.Y;
}
if ( aSize.Width < 0 )
aSize.Width = 1000;
if ( aSize.Height < 0 )
aSize.Height = 1000;
if (!bSuppressRotation)
{
SdrObject* pShape = SdrObject::getSdrObjectFromXShape(rXShape);
nRotation = pShape ? pShape->GetRotateAngle() : 0_deg100;
if ( GetDocumentType() != DOCUMENT_DOCX )
{
int faccos=bFlipV ? -1 : 1;
int facsin=bFlipH ? -1 : 1;
aPos.X-=(1-faccos*cos(toRadians(nRotation)))*aSize.Width/2-facsin*sin(toRadians(nRotation))*aSize.Height/2;
aPos.Y-=(1-faccos*cos(toRadians(nRotation)))*aSize.Height/2+facsin*sin(toRadians(nRotation))*aSize.Width/2;
}
else if (m_xParent.is() && nRotation != 0_deg100)
{
// Position for rotated shapes inside group is not set by DocxSdrExport.
basegfx::B2DRange aRect(-aSize.Width / 2.0, -aSize.Height / 2.0, aSize.Width / 2.0,
aSize.Height / 2.0);
basegfx::B2DHomMatrix aRotateMatrix =
basegfx::utils::createRotateB2DHomMatrix(toRadians(nRotation));
aRect.transform(aRotateMatrix);
aPos.X += -aSize.Width / 2.0 - aRect.getMinX();
aPos.Y += -aSize.Height / 2.0 - aRect.getMinY();
}
// The RotateAngle property's value is independent from any flipping, and that's exactly what we need here.
uno::Reference<beans::XPropertySet> xPropertySet(rXShape, uno::UNO_QUERY);
uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
if (xPropertySetInfo->hasPropertyByName("RotateAngle"))
{
sal_Int32 nTmp;
if (xPropertySet->getPropertyValue("RotateAngle") >>= nTmp)
nRotation = Degree100(nTmp);
}
// tdf#133037: restore original rotate angle before output
if (nRotation && xPropertySetInfo->hasPropertyByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG))
{
uno::Sequence<beans::PropertyValue> aGrabBagProps;
xPropertySet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= aGrabBagProps;
auto p3DEffectProps = std::find_if(std::cbegin(aGrabBagProps), std::cend(aGrabBagProps),
[](const PropertyValue& rProp) { return rProp.Name == "3DEffectProperties"; });
if (p3DEffectProps != std::cend(aGrabBagProps))
{
uno::Sequence<beans::PropertyValue> a3DEffectProps;
p3DEffectProps->Value >>= a3DEffectProps;
auto pCameraProps = std::find_if(std::cbegin(a3DEffectProps), std::cend(a3DEffectProps),
[](const PropertyValue& rProp) { return rProp.Name == "Camera"; });
if (pCameraProps != std::cend(a3DEffectProps))
{
uno::Sequence<beans::PropertyValue> aCameraProps;
pCameraProps->Value >>= aCameraProps;
auto pZRotationProp = std::find_if(std::cbegin(aCameraProps), std::cend(aCameraProps),
[](const PropertyValue& rProp) { return rProp.Name == "rotRev"; });
if (pZRotationProp != std::cend(aCameraProps))
{
sal_Int32 nTmp = 0;
pZRotationProp->Value >>= nTmp;
nCameraRotation = NormAngle36000(Degree100(nTmp / -600));
}
}
}
}
}
// OOXML flips shapes before rotating them.
if(bFlipH != bFlipV)
nRotation = 36000_deg100 - nRotation;
WriteTransformation(rXShape, tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)), nXmlNamespace,
bFlipHWrite, bFlipVWrite, ExportRotateClockwisify(nRotation + nCameraRotation), IsGroupShape( rXShape ));
}
static OUString lcl_GetTarget(const css::uno::Reference<css::frame::XModel>& xModel, std::u16string_view rURL)
{
Reference<drawing::XDrawPagesSupplier> xDPS(xModel, uno::UNO_QUERY_THROW);
Reference<drawing::XDrawPages> xDrawPages(xDPS->getDrawPages(), uno::UNO_SET_THROW);
sal_uInt32 nPageCount = xDrawPages->getCount();
OUString sTarget;
for (sal_uInt32 i = 0; i < nPageCount; ++i)
{
Reference<XDrawPage> xDrawPage;
xDrawPages->getByIndex(i) >>= xDrawPage;
Reference<container::XNamed> xNamed(xDrawPage, UNO_QUERY);
if (!xNamed)
continue;
OUString sSlideName = "#" + xNamed->getName();
if (rURL == sSlideName)
{
sTarget = "slide" + OUString::number(i + 1) + ".xml";
break;
}
}
return sTarget;
}
void DrawingML::WriteRunProperties( const Reference< XPropertySet >& rRun, bool bIsField, sal_Int32 nElement,
bool bCheckDirect,bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
sal_Int16 nScriptType, const Reference< XPropertySet >& rXShapePropSet)
{
Reference< XPropertySet > rXPropSet = rRun;
Reference< XPropertyState > rXPropState( rRun, UNO_QUERY );
OUString usLanguage;
PropertyState eState;
bool bComplex = ( nScriptType == css::i18n::ScriptType::COMPLEX );
const char* bold = "0";
const char* italic = nullptr;
const char* underline = nullptr;
const char* strikeout = nullptr;
const char* cap = nullptr;
sal_Int32 nSize = 1800;
sal_Int32 nCharEscapement = 0;
sal_Int32 nCharKerning = 0;
sal_Int32 nCharEscapementHeight = 0;
if ( nElement == XML_endParaRPr && rbOverridingCharHeight )
{
nSize = rnCharHeight;
}
else if (GetProperty(rXPropSet, "CharHeight"))
{
nSize = static_cast<sal_Int32>(100*(*o3tl::doAccess<float>(mAny)));
if ( nElement == XML_rPr || nElement == XML_defRPr )
{
rbOverridingCharHeight = true;
rnCharHeight = nSize;
}
}
if (GetProperty(rXPropSet, "CharKerning"))
nCharKerning = static_cast<sal_Int32>(*o3tl::doAccess<sal_Int16>(mAny));
/** While setting values in propertymap,
* CharKerning converted using GetTextSpacingPoint
* i.e set @ https://opengrok.libreoffice.org/xref/core/oox/source/drawingml/textcharacterproperties.cxx#129
* therefore to get original value CharKerning need to be convert.
* https://opengrok.libreoffice.org/xref/core/oox/source/drawingml/drawingmltypes.cxx#95
**/
nCharKerning = ((nCharKerning * 720)-360) / 254;
if ((bComplex && GetProperty(rXPropSet, "CharWeightComplex"))
|| GetProperty(rXPropSet, "CharWeight"))
{
if ( *o3tl::doAccess<float>(mAny) >= awt::FontWeight::SEMIBOLD )
bold = "1";
}
if ((bComplex && GetProperty(rXPropSet, "CharPostureComplex"))
|| GetProperty(rXPropSet, "CharPosture"))
switch ( *o3tl::doAccess<awt::FontSlant>(mAny) )
{
case awt::FontSlant_OBLIQUE :
case awt::FontSlant_ITALIC :
italic = "1";
break;
default:
break;
}
if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharUnderline", eState)
&& eState == beans::PropertyState_DIRECT_VALUE)
|| GetProperty(rXPropSet, "CharUnderline"))
{
switch ( *o3tl::doAccess<sal_Int16>(mAny) )
{
case awt::FontUnderline::SINGLE :
underline = "sng";
break;
case awt::FontUnderline::DOUBLE :
underline = "dbl";
break;
case awt::FontUnderline::DOTTED :
underline = "dotted";
break;
case awt::FontUnderline::DASH :
underline = "dash";
break;
case awt::FontUnderline::LONGDASH :
underline = "dashLong";
break;
case awt::FontUnderline::DASHDOT :
underline = "dotDash";
break;
case awt::FontUnderline::DASHDOTDOT :
underline = "dotDotDash";
break;
case awt::FontUnderline::WAVE :
underline = "wavy";
break;
case awt::FontUnderline::DOUBLEWAVE :
underline = "wavyDbl";
break;
case awt::FontUnderline::BOLD :
underline = "heavy";
break;
case awt::FontUnderline::BOLDDOTTED :
underline = "dottedHeavy";
break;
case awt::FontUnderline::BOLDDASH :
underline = "dashHeavy";
break;
case awt::FontUnderline::BOLDLONGDASH :
underline = "dashLongHeavy";
break;
case awt::FontUnderline::BOLDDASHDOT :
underline = "dotDashHeavy";
break;
case awt::FontUnderline::BOLDDASHDOTDOT :
underline = "dotDotDashHeavy";
break;
case awt::FontUnderline::BOLDWAVE :
underline = "wavyHeavy";
break;
}
}
if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharStrikeout", eState)
&& eState == beans::PropertyState_DIRECT_VALUE)
|| GetProperty(rXPropSet, "CharStrikeout"))
{
switch ( *o3tl::doAccess<sal_Int16>(mAny) )
{
case awt::FontStrikeout::NONE :
strikeout = "noStrike";
break;
case awt::FontStrikeout::SINGLE :
// LibO supports further values of character
// strikeout, OOXML standard (20.1.10.78,
// ST_TextStrikeType) however specifies only
// 3 - single, double and none. Approximate
// the remaining ones by single strike (better
// some strike than none at all).
// TODO: figure out how to do this better
case awt::FontStrikeout::BOLD :
case awt::FontStrikeout::SLASH :
case awt::FontStrikeout::X :
strikeout = "sngStrike";
break;
case awt::FontStrikeout::DOUBLE :
strikeout = "dblStrike";
break;
}
}
bool bLang = false;
switch(nScriptType)
{
case css::i18n::ScriptType::ASIAN:
bLang = GetProperty(rXPropSet, "CharLocaleAsian"); break;
case css::i18n::ScriptType::COMPLEX:
bLang = GetProperty(rXPropSet, "CharLocaleComplex"); break;
default:
bLang = GetProperty(rXPropSet, "CharLocale"); break;
}
if (bLang)
{
css::lang::Locale aLocale;
mAny >>= aLocale;
LanguageTag aLanguageTag( aLocale);
if (!aLanguageTag.isSystemLocale())
usLanguage = aLanguageTag.getBcp47MS();
}
if (GetPropertyAndState(rXPropSet, rXPropState, "CharEscapement", eState)
&& eState == beans::PropertyState_DIRECT_VALUE)
mAny >>= nCharEscapement;
if (GetPropertyAndState(rXPropSet, rXPropState, "CharEscapementHeight", eState)
&& eState == beans::PropertyState_DIRECT_VALUE)
mAny >>= nCharEscapementHeight;
if (DFLT_ESC_AUTO_SUPER == nCharEscapement)
{
// Raised by the differences between the ascenders (ascent = baseline to top of highest letter).
// The ascent is generally about 80% of the total font height.
// That is why DFLT_ESC_PROP (58) leads to 33% (DFLT_ESC_SUPER)
nCharEscapement = .8 * (100 - nCharEscapementHeight);
}
else if (DFLT_ESC_AUTO_SUB == nCharEscapement)
{
// Lowered by the differences between the descenders (descent = baseline to bottom of lowest letter).
// The descent is generally about 20% of the total font height.
// That is why DFLT_ESC_PROP (58) leads to 8% (DFLT_ESC_SUB)
nCharEscapement = .2 * -(100 - nCharEscapementHeight);
}
if (nCharEscapement && nCharEscapementHeight)
{
nSize = (nSize * nCharEscapementHeight) / 100;
// MSO uses default ~58% size
nSize = (nSize / 0.58);
}
if (GetProperty(rXPropSet, "CharCaseMap"))
{
switch ( *o3tl::doAccess<sal_Int16>(mAny) )
{
case CaseMap::UPPERCASE :
cap = "all";
break;
case CaseMap::SMALLCAPS :
cap = "small";
break;
}
}
mpFS->startElementNS( XML_a, nElement,
XML_b, bold,
XML_i, italic,
XML_lang, sax_fastparser::UseIf(usLanguage, !usLanguage.isEmpty()),
XML_sz, OString::number(nSize),
// For Condensed character spacing spc value is negative.
XML_spc, sax_fastparser::UseIf(OString::number(nCharKerning), nCharKerning != 0),
XML_strike, strikeout,
XML_u, underline,
XML_baseline, sax_fastparser::UseIf(OString::number(nCharEscapement*1000), nCharEscapement != 0),
XML_cap, cap );
// Fontwork-shapes in LO have text outline and fill from shape stroke and shape fill
// PowerPoint has this as run properties
if (IsFontworkShape(rXShapePropSet))
{
WriteOutline(rXShapePropSet);
WriteBlipOrNormalFill(rXShapePropSet, "Graphic");
WriteShapeEffects(rXShapePropSet);
}
else
{
// mso doesn't like text color to be placed after typeface
if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, "CharColor", eState)
&& eState == beans::PropertyState_DIRECT_VALUE)
|| GetProperty(rXPropSet, "CharColor"))
{
::Color color( ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny) );
SAL_INFO("oox.shape", "run color: " << sal_uInt32(color) << " auto: " << sal_uInt32(COL_AUTO));
// WriteSolidFill() handles MAX_PERCENT as "no transparency".
sal_Int32 nTransparency = MAX_PERCENT;
if (rXPropSet->getPropertySetInfo()->hasPropertyByName("CharTransparence"))
{
rXPropSet->getPropertyValue("CharTransparence") >>= nTransparency;
// UNO scale is 0..100, OOXML scale is 0..100000; also UNO tracks transparency, OOXML
// tracks opacity.
nTransparency = MAX_PERCENT - (nTransparency * PER_PERCENT);
}
bool bContoured = false;
if (GetProperty(rXPropSet, "CharContoured"))
bContoured = *o3tl::doAccess<bool>(mAny);
// tdf#127696 If the CharContoured is true, then the text color is white and the outline color is the CharColor.
if (bContoured)
{
mpFS->startElementNS(XML_a, XML_ln);
if (color == COL_AUTO)
{
mbIsBackgroundDark ? WriteSolidFill(COL_WHITE) : WriteSolidFill(COL_BLACK);
}
else
{
color.SetAlpha(255);
if (!WriteCharColor(rXPropSet))
WriteSolidFill(color, nTransparency);
}
mpFS->endElementNS(XML_a, XML_ln);
WriteSolidFill(COL_WHITE);
}
// tdf#104219 In LibreOffice and MS Office, there are two types of colors:
// Automatic and Fixed. OOXML is setting automatic color, by not providing color.
else if( color != COL_AUTO )
{
color.SetAlpha(255);
// TODO: special handle embossed/engraved
if (!WriteCharColor(rXPropSet))
{
WriteSolidFill(color, nTransparency);
}
}
}
}
// tdf#128096, exporting XML_highlight to docx already works fine,
// so make sure this code is only run when exporting to pptx, just in case
if (GetDocumentType() == DOCUMENT_PPTX)
{
if (GetProperty(rXPropSet, "CharBackColor"))
{
::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny));
if( color != COL_AUTO )
{
mpFS->startElementNS(XML_a, XML_highlight);
WriteColor( color );
mpFS->endElementNS( XML_a, XML_highlight );
}
}
}
if (underline
&& ((bCheckDirect
&& GetPropertyAndState(rXPropSet, rXPropState, "CharUnderlineColor", eState)
&& eState == beans::PropertyState_DIRECT_VALUE)
|| GetProperty(rXPropSet, "CharUnderlineColor")))
{
::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny));
// if color is automatic, then we shouldn't write information about color but to take color from character
if( color != COL_AUTO )
{
mpFS->startElementNS(XML_a, XML_uFill);
WriteSolidFill( color );
mpFS->endElementNS( XML_a, XML_uFill );
}
else
{
mpFS->singleElementNS(XML_a, XML_uFillTx);
}
}
if (GetProperty(rXPropSet, "CharFontName"))
{
const char* const pitch = nullptr;
const char* const charset = nullptr;
OUString usTypeface;
mAny >>= usTypeface;
OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) );
if (!aSubstName.isEmpty())
usTypeface = aSubstName;
mpFS->singleElementNS( XML_a, XML_latin,
XML_typeface, usTypeface,
XML_pitchFamily, pitch,
XML_charset, charset );
}
if ((bComplex
&& (GetPropertyAndState(rXPropSet, rXPropState, "CharFontNameComplex", eState)
&& eState == beans::PropertyState_DIRECT_VALUE))
|| (!bComplex
&& (GetPropertyAndState(rXPropSet, rXPropState, "CharFontNameAsian", eState)
&& eState == beans::PropertyState_DIRECT_VALUE)))
{
const char* const pitch = nullptr;
const char* const charset = nullptr;
OUString usTypeface;
mAny >>= usTypeface;
OUString aSubstName( GetSubsFontName( usTypeface, SubsFontFlags::ONLYONE | SubsFontFlags::MS ) );
if (!aSubstName.isEmpty())
usTypeface = aSubstName;
mpFS->singleElementNS( XML_a, bComplex ? XML_cs : XML_ea,
XML_typeface, usTypeface,
XML_pitchFamily, pitch,
XML_charset, charset );
}
if( bIsField )
{
Reference< XTextField > rXTextField;
if (GetProperty(rXPropSet, "TextField"))
mAny >>= rXTextField;
if( rXTextField.is() )
rXPropSet.set( rXTextField, UNO_QUERY );
}
// field properties starts here
if (GetProperty(rXPropSet, "URL"))
{
OUString sURL;
mAny >>= sURL;
if (!sURL.isEmpty())
{
if (!sURL.match("#action?jump="))
{
bool bExtURL = URLTransformer().isExternalURL(sURL);
sURL = bExtURL ? sURL : lcl_GetTarget(GetFB()->getModel(), sURL);
OUString sRelId
= mpFB->addRelation(mpFS->getOutputStream(),
bExtURL ? oox::getRelationship(Relationship::HYPERLINK)
: oox::getRelationship(Relationship::SLIDE),
sURL, bExtURL);
if (bExtURL)
mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
else
mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId,
XML_action, "ppaction://hlinksldjump");
}
else
{
sal_Int32 nIndex = sURL.indexOf('=');
OUString aDestination(sURL.copy(nIndex + 1));
mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action,
"ppaction://hlinkshowjump?jump=" + aDestination);
}
}
}
mpFS->endElementNS( XML_a, nElement );
}
OUString DrawingML::GetFieldValue( const css::uno::Reference< css::text::XTextRange >& rRun, bool& bIsURLField )
{
Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY );
OUString aFieldType, aFieldValue;
if (GetProperty(rXPropSet, "TextPortionType"))
{
aFieldType = *o3tl::doAccess<OUString>(mAny);
SAL_INFO("oox.shape", "field type: " << aFieldType);
}
if( aFieldType == "TextField" )
{
Reference< XTextField > rXTextField;
if (GetProperty(rXPropSet, "TextField"))
mAny >>= rXTextField;
if( rXTextField.is() )
{
rXPropSet.set( rXTextField, UNO_QUERY );
if( rXPropSet.is() )
{
OUString aFieldKind( rXTextField->getPresentation( true ) );
SAL_INFO("oox.shape", "field kind: " << aFieldKind);
if( aFieldKind == "Page" )
{
aFieldValue = "slidenum";
}
else if( aFieldKind == "Pages" )
{
aFieldValue = "slidecount";
}
else if( aFieldKind == "PageName" )
{
aFieldValue = "slidename";
}
else if( aFieldKind == "URL" )
{
bIsURLField = true;
if (GetProperty(rXPropSet, "Representation"))
mAny >>= aFieldValue;
}
else if(aFieldKind == "Date")
{
sal_Int32 nNumFmt = -1;
rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt;
aFieldValue = GetDatetimeTypeFromDate(static_cast<SvxDateFormat>(nNumFmt));
}
else if(aFieldKind == "ExtTime")
{
sal_Int32 nNumFmt = -1;
rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt;
aFieldValue = GetDatetimeTypeFromTime(static_cast<SvxTimeFormat>(nNumFmt));
}
else if(aFieldKind == "ExtFile")
{
sal_Int32 nNumFmt = -1;
rXPropSet->getPropertyValue(UNO_TC_PROP_FILE_FORMAT) >>= nNumFmt;
switch(nNumFmt)
{
case 0: aFieldValue = "file"; // Path/File name
break;
case 1: aFieldValue = "file1"; // Path
break;
case 2: aFieldValue = "file2"; // File name without extension
break;
case 3: aFieldValue = "file3"; // File name with extension
}
}
else if(aFieldKind == "Author")
{
aFieldValue = "author";
}
}
}
}
return aFieldValue;
}
OUString DrawingML::GetDatetimeTypeFromDate(SvxDateFormat eDate)
{
return GetDatetimeTypeFromDateTime(eDate, SvxTimeFormat::AppDefault);
}
OUString DrawingML::GetDatetimeTypeFromTime(SvxTimeFormat eTime)
{
return GetDatetimeTypeFromDateTime(SvxDateFormat::AppDefault, eTime);
}
OUString DrawingML::GetDatetimeTypeFromDateTime(SvxDateFormat eDate, SvxTimeFormat eTime)
{
OUString aDateField;
switch (eDate)
{
case SvxDateFormat::StdSmall:
case SvxDateFormat::A:
aDateField = "datetime";
break;
case SvxDateFormat::B:
aDateField = "datetime1"; // 13/02/1996
break;
case SvxDateFormat::C:
aDateField = "datetime5";
break;
case SvxDateFormat::D:
aDateField = "datetime3"; // 13 February 1996
break;
case SvxDateFormat::StdBig:
case SvxDateFormat::E:
case SvxDateFormat::F:
aDateField = "datetime2";
break;
default:
break;
}
OUString aTimeField;
switch (eTime)
{
case SvxTimeFormat::Standard:
case SvxTimeFormat::HH24_MM_SS:
case SvxTimeFormat::HH24_MM_SS_00:
aTimeField = "datetime11"; // 13:49:38
break;
case SvxTimeFormat::HH24_MM:
aTimeField = "datetime10"; // 13:49
break;
case SvxTimeFormat::HH12_MM:
case SvxTimeFormat::HH12_MM_AMPM:
aTimeField = "datetime12"; // 01:49 PM
break;
case SvxTimeFormat::HH12_MM_SS:
case SvxTimeFormat::HH12_MM_SS_AMPM:
case SvxTimeFormat::HH12_MM_SS_00:
case SvxTimeFormat::HH12_MM_SS_00_AMPM:
aTimeField = "datetime13"; // 01:49:38 PM
break;
default:
break;
}
if (!aDateField.isEmpty() && aTimeField.isEmpty())
return aDateField;
else if (!aTimeField.isEmpty() && aDateField.isEmpty())
return aTimeField;
else if (!aDateField.isEmpty() && !aTimeField.isEmpty())
{
if (aTimeField == "datetime11" || aTimeField == "datetime13")
// only datetime format that has Date and HH:MM:SS
return "datetime9"; // dd/mm/yyyy H:MM:SS
else
// only datetime format that has Date and HH:MM
return "datetime8"; // dd/mm/yyyy H:MM
}
else
return "";
}
void DrawingML::WriteRun( const Reference< XTextRange >& rRun,
bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet)
{
Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY );
sal_Int16 nLevel = -1;
if (GetProperty(rXPropSet, "NumberingLevel"))
mAny >>= nLevel;
bool bNumberingIsNumber = true;
if (GetProperty(rXPropSet, "NumberingIsNumber"))
mAny >>= bNumberingIsNumber;
float nFontSize = -1;
if (GetProperty(rXPropSet, "CharHeight"))
mAny >>= nFontSize;
bool bIsURLField = false;
OUString sFieldValue = GetFieldValue( rRun, bIsURLField );
bool bWriteField = !( sFieldValue.isEmpty() || bIsURLField );
OUString sText = rRun->getString();
//if there is no text following the bullet, add a space after the bullet
if (nLevel !=-1 && bNumberingIsNumber && sText.isEmpty() )
sText=" ";
if ( bIsURLField )
sText = sFieldValue;
if( sText.isEmpty())
{
Reference< XPropertySet > xPropSet( rRun, UNO_QUERY );
try
{
if( !xPropSet.is() || !( xPropSet->getPropertyValue( "PlaceholderText" ) >>= sText ) )
return;
if( sText.isEmpty() )
return;
}
catch (const Exception &)
{
return;
}
}
if (sText == "\n")
{
// Empty run? Do not forget to write the font size in case of pptx:
if ((GetDocumentType() == DOCUMENT_PPTX) && (nFontSize != -1))
{
mpFS->startElementNS(XML_a, XML_br);
mpFS->singleElementNS(XML_a, XML_rPr, XML_sz,
OString::number(nFontSize * 100).getStr());
mpFS->endElementNS(XML_a, XML_br);
}
else
mpFS->singleElementNS(XML_a, XML_br);
}
else
{
if( bWriteField )
{
OString sUUID(comphelper::xml::generateGUIDString());
mpFS->startElementNS( XML_a, XML_fld,
XML_id, sUUID.getStr(),
XML_type, sFieldValue );
}
else
{
mpFS->startElementNS(XML_a, XML_r);
}
Reference< XPropertySet > xPropSet( rRun, uno::UNO_QUERY );
WriteRunProperties( xPropSet, bIsURLField, XML_rPr, true, rbOverridingCharHeight, rnCharHeight, GetScriptType(sText), rXShapePropSet);
mpFS->startElementNS(XML_a, XML_t);
mpFS->writeEscaped( sText );
mpFS->endElementNS( XML_a, XML_t );
if( bWriteField )
mpFS->endElementNS( XML_a, XML_fld );
else
mpFS->endElementNS( XML_a, XML_r );
}
}
static OUString GetAutoNumType(SvxNumType nNumberingType, bool bSDot, bool bPBehind, bool bPBoth)
{
OUString sPrefixSuffix;
if (bPBoth)
sPrefixSuffix = "ParenBoth";
else if (bPBehind)
sPrefixSuffix = "ParenR";
else if (bSDot)
sPrefixSuffix = "Period";
switch( nNumberingType )
{
case SVX_NUM_CHARS_UPPER_LETTER_N :
case SVX_NUM_CHARS_UPPER_LETTER :
return "alphaUc" + sPrefixSuffix;
case SVX_NUM_CHARS_LOWER_LETTER_N :
case SVX_NUM_CHARS_LOWER_LETTER :
return "alphaLc" + sPrefixSuffix;
case SVX_NUM_ROMAN_UPPER :
return "romanUc" + sPrefixSuffix;
case SVX_NUM_ROMAN_LOWER :
return "romanLc" + sPrefixSuffix;
case SVX_NUM_ARABIC :
{
if (sPrefixSuffix.isEmpty())
return "arabicPlain";
else
return "arabic" + sPrefixSuffix;
}
default:
break;
}
return OUString();
}
void DrawingML::WriteParagraphNumbering(const Reference< XPropertySet >& rXPropSet, float fFirstCharHeight, sal_Int16 nLevel )
{
if (nLevel < 0 || !GetProperty(rXPropSet, "NumberingRules"))
{
if (GetDocumentType() == DOCUMENT_PPTX)
{
mpFS->singleElementNS(XML_a, XML_buNone);
}
return;
}
Reference< XIndexAccess > rXIndexAccess;
if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount())
return;
SAL_INFO("oox.shape", "numbering rules");
Sequence<PropertyValue> aPropertySequence;
rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence;
if (!aPropertySequence.hasElements())
return;
SvxNumType nNumberingType = SVX_NUM_NUMBER_NONE;
bool bSDot = false;
bool bPBehind = false;
bool bPBoth = false;
sal_Unicode aBulletChar = 0x2022; // a bullet
awt::FontDescriptor aFontDesc;
bool bHasFontDesc = false;
uno::Reference<graphic::XGraphic> xGraphic;
sal_Int16 nBulletRelSize = 0;
sal_Int16 nStartWith = 1;
::Color nBulletColor;
bool bHasBulletColor = false;
awt::Size aGraphicSize;
for ( const PropertyValue& rPropValue : std::as_const(aPropertySequence) )
{
OUString aPropName( rPropValue.Name );
SAL_INFO("oox.shape", "pro name: " << aPropName);
if ( aPropName == "NumberingType" )
{
nNumberingType = static_cast<SvxNumType>(*o3tl::doAccess<sal_Int16>(rPropValue.Value));
}
else if ( aPropName == "Prefix" )
{
if( *o3tl::doAccess<OUString>(rPropValue.Value) == ")")
bPBoth = true;
}
else if ( aPropName == "Suffix" )
{
auto s = o3tl::doAccess<OUString>(rPropValue.Value);
if( *s == ".")
bSDot = true;
else if( *s == ")")
bPBehind = true;
}
else if(aPropName == "BulletColor")
{
nBulletColor = ::Color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(rPropValue.Value));
bHasBulletColor = true;
}
else if ( aPropName == "BulletChar" )
{
aBulletChar = (*o3tl::doAccess<OUString>(rPropValue.Value))[ 0 ];
}
else if ( aPropName == "BulletFont" )
{
aFontDesc = *o3tl::doAccess<awt::FontDescriptor>(rPropValue.Value);
bHasFontDesc = true;
// Our numbullet dialog has set the wrong textencoding for our "StarSymbol" font,
// instead of a Unicode encoding the encoding RTL_TEXTENCODING_SYMBOL was used.
// Because there might exist a lot of damaged documents I added this two lines
// which fixes the bullet problem for the export.
if ( aFontDesc.Name.equalsIgnoreAsciiCase("StarSymbol") )
aFontDesc.CharSet = RTL_TEXTENCODING_MS_1252;
}
else if ( aPropName == "BulletRelSize" )
{
nBulletRelSize = *o3tl::doAccess<sal_Int16>(rPropValue.Value);
}
else if ( aPropName == "StartWith" )
{
nStartWith = *o3tl::doAccess<sal_Int16>(rPropValue.Value);
}
else if (aPropName == "GraphicBitmap")
{
auto xBitmap = rPropValue.Value.get<uno::Reference<awt::XBitmap>>();
xGraphic.set(xBitmap, uno::UNO_QUERY);
}
else if ( aPropName == "GraphicSize" )
{
aGraphicSize = *o3tl::doAccess<awt::Size>(rPropValue.Value);
SAL_INFO("oox.shape", "graphic size: " << aGraphicSize.Width << "x" << aGraphicSize.Height);
}
}
if (nNumberingType == SVX_NUM_NUMBER_NONE)
return;
Graphic aGraphic(xGraphic);
if (xGraphic.is() && aGraphic.GetType() != GraphicType::NONE)
{
tools::Long nFirstCharHeightMm = TransformMetric(fFirstCharHeight * 100.f, FieldUnit::POINT, FieldUnit::MM);
float fBulletSizeRel = aGraphicSize.Height / static_cast<float>(nFirstCharHeightMm) / OOX_BULLET_LIST_SCALE_FACTOR;
OUString sRelationId;
if (fBulletSizeRel < 1.0f)
{
// Add padding to get the bullet point centered in PPT
Size aDestSize(64, 64);
float fBulletSizeRelX = fBulletSizeRel / aGraphicSize.Height * aGraphicSize.Width;
tools::Long nPaddingX = std::max<tools::Long>(0, std::lround((aDestSize.Width() - fBulletSizeRelX * aDestSize.Width()) / 2.f));
tools::Long nPaddingY = std::lround((aDestSize.Height() - fBulletSizeRel * aDestSize.Height()) / 2.f);
tools::Rectangle aDestRect(nPaddingX, nPaddingY, aDestSize.Width() - nPaddingX, aDestSize.Height() - nPaddingY);
AlphaMask aMask(aDestSize);
aMask.Erase(255);
BitmapEx aSourceBitmap(aGraphic.GetBitmapEx());
aSourceBitmap.Scale(aDestRect.GetSize());
tools::Rectangle aSourceRect(Point(0, 0), aDestRect.GetSize());
BitmapEx aDestBitmap(Bitmap(aDestSize, vcl::PixelFormat::N24_BPP), aMask);
aDestBitmap.CopyPixel(aDestRect, aSourceRect, &aSourceBitmap);
Graphic aDestGraphic(aDestBitmap);
sRelationId = WriteImage(aDestGraphic);
fBulletSizeRel = 1.0f;
}
else
{
sRelationId = WriteImage(aGraphic);
}
mpFS->singleElementNS( XML_a, XML_buSzPct,
XML_val, OString::number(std::min<sal_Int32>(std::lround(100000.f * fBulletSizeRel), 400000)));
mpFS->startElementNS(XML_a, XML_buBlip);
mpFS->singleElementNS(XML_a, XML_blip, FSNS(XML_r, XML_embed), sRelationId);
mpFS->endElementNS( XML_a, XML_buBlip );
}
else
{
if(bHasBulletColor)
{
if (nBulletColor == COL_AUTO )
{
nBulletColor = ::Color(ColorTransparency, mbIsBackgroundDark ? 0xffffff : 0x000000);
}
mpFS->startElementNS(XML_a, XML_buClr);
WriteColor( nBulletColor );
mpFS->endElementNS( XML_a, XML_buClr );
}
if( nBulletRelSize && nBulletRelSize != 100 )
mpFS->singleElementNS( XML_a, XML_buSzPct,
XML_val, OString::number(std::clamp<sal_Int32>(1000*nBulletRelSize, 25000, 400000)));
if( bHasFontDesc )
{
if ( SVX_NUM_CHAR_SPECIAL == nNumberingType )
aBulletChar = SubstituteBullet( aBulletChar, aFontDesc );
mpFS->singleElementNS( XML_a, XML_buFont,
XML_typeface, aFontDesc.Name,
XML_charset, sax_fastparser::UseIf("2", aFontDesc.CharSet == awt::CharSet::SYMBOL));
}
OUString aAutoNumType = GetAutoNumType( nNumberingType, bSDot, bPBehind, bPBoth );
if (!aAutoNumType.isEmpty())
{
mpFS->singleElementNS(XML_a, XML_buAutoNum,
XML_type, aAutoNumType,
XML_startAt, sax_fastparser::UseIf(OString::number(nStartWith), nStartWith > 1));
}
else
{
mpFS->singleElementNS(XML_a, XML_buChar, XML_char, OUString(aBulletChar));
}
}
}
void DrawingML::WriteParagraphTabStops(const Reference<XPropertySet>& rXPropSet)
{
css::uno::Sequence<css::style::TabStop> aTabStops;
if (GetProperty(rXPropSet, "ParaTabStops"))
aTabStops = *o3tl::doAccess<css::uno::Sequence<css::style::TabStop>>(mAny);
if (aTabStops.getLength() > 0)
mpFS->startElementNS(XML_a, XML_tabLst);
for (const css::style::TabStop& rTabStop : std::as_const(aTabStops))
{
OString sPosition = OString::number(GetPointFromCoordinate(rTabStop.Position));
OString sAlignment;
switch (rTabStop.Alignment)
{
case css::style::TabAlign_DECIMAL:
sAlignment = "dec";
break;
case css::style::TabAlign_RIGHT:
sAlignment = "r";
break;
case css::style::TabAlign_CENTER:
sAlignment = "ctr";
break;
case css::style::TabAlign_LEFT:
default:
sAlignment = "l";
}
mpFS->singleElementNS(XML_a, XML_tab, XML_algn, sAlignment, XML_pos, sPosition);
}
if (aTabStops.getLength() > 0)
mpFS->endElementNS(XML_a, XML_tabLst);
}
bool DrawingML::IsGroupShape( const Reference< XShape >& rXShape )
{
bool bRet = false;
if ( rXShape.is() )
{
uno::Reference<lang::XServiceInfo> xServiceInfo(rXShape, uno::UNO_QUERY_THROW);
bRet = xServiceInfo->supportsService("com.sun.star.drawing.GroupShape");
}
return bRet;
}
sal_Int32 DrawingML::getBulletMarginIndentation (const Reference< XPropertySet >& rXPropSet,sal_Int16 nLevel, std::u16string_view propName)
{
if (nLevel < 0 || !GetProperty(rXPropSet, "NumberingRules"))
return 0;
Reference< XIndexAccess > rXIndexAccess;
if (!(mAny >>= rXIndexAccess) || nLevel >= rXIndexAccess->getCount())
return 0;
SAL_INFO("oox.shape", "numbering rules");
Sequence<PropertyValue> aPropertySequence;
rXIndexAccess->getByIndex(nLevel) >>= aPropertySequence;
if (!aPropertySequence.hasElements())
return 0;
for ( const PropertyValue& rPropValue : std::as_const(aPropertySequence) )
{
OUString aPropName( rPropValue.Name );
SAL_INFO("oox.shape", "pro name: " << aPropName);
if ( aPropName == propName )
return *o3tl::doAccess<sal_Int32>(rPropValue.Value);
}
return 0;
}
const char* DrawingML::GetAlignment( style::ParagraphAdjust nAlignment )
{
const char* sAlignment = nullptr;
switch( nAlignment )
{
case style::ParagraphAdjust_CENTER:
sAlignment = "ctr";
break;
case style::ParagraphAdjust_RIGHT:
sAlignment = "r";
break;
case style::ParagraphAdjust_BLOCK:
sAlignment = "just";
break;
default:
;
}
return sAlignment;
}
void DrawingML::WriteLinespacing(const LineSpacing& rSpacing, float fFirstCharHeight)
{
if( rSpacing.Mode == LineSpacingMode::PROP )
{
mpFS->singleElementNS( XML_a, XML_spcPct,
XML_val, OString::number(static_cast<sal_Int32>(rSpacing.Height)*1000));
}
else if (rSpacing.Mode == LineSpacingMode::MINIMUM
&& fFirstCharHeight > static_cast<float>(rSpacing.Height) * 0.001 * 72.0 / 2.54)
{
// 100% proportional line spacing = single line spacing
mpFS->singleElementNS(XML_a, XML_spcPct, XML_val,
OString::number(static_cast<sal_Int32>(100000)));
}
else
{
mpFS->singleElementNS( XML_a, XML_spcPts,
XML_val, OString::number(std::lround(rSpacing.Height / 25.4 * 72)));
}
}
bool DrawingML::WriteParagraphProperties( const Reference< XTextContent >& rParagraph, float fFirstCharHeight, sal_Int32 nElement)
{
Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY );
Reference< XPropertyState > rXPropState( rParagraph, UNO_QUERY );
PropertyState eState;
if( !rXPropSet.is() || !rXPropState.is() )
return false;
sal_Int16 nLevel = -1;
if (GetProperty(rXPropSet, "NumberingLevel"))
mAny >>= nLevel;
sal_Int16 nTmp = sal_Int16(style::ParagraphAdjust_LEFT);
if (GetProperty(rXPropSet, "ParaAdjust"))
mAny >>= nTmp;
style::ParagraphAdjust nAlignment = static_cast<style::ParagraphAdjust>(nTmp);
bool bHasLinespacing = false;
LineSpacing aLineSpacing;
if (GetPropertyAndState(rXPropSet, rXPropState, "ParaLineSpacing", eState)
&& (mAny >>= aLineSpacing)
&& (eState == beans::PropertyState_DIRECT_VALUE ||
// only export if it differs from the default 100% line spacing
aLineSpacing.Mode != LineSpacingMode::PROP || aLineSpacing.Height != 100))
bHasLinespacing = true;
bool bRtl = false;
if (GetProperty(rXPropSet, "WritingMode"))
{
sal_Int16 nWritingMode;
if( ( mAny >>= nWritingMode ) && nWritingMode == text::WritingMode2::RL_TB )
{
bRtl = true;
}
}
sal_Int32 nParaLeftMargin = 0;
sal_Int32 nParaFirstLineIndent = 0;
if (GetProperty(rXPropSet, "ParaLeftMargin"))
mAny >>= nParaLeftMargin;
if (GetProperty(rXPropSet, "ParaFirstLineIndent"))
mAny >>= nParaFirstLineIndent;
sal_Int32 nParaTopMargin = 0;
sal_Int32 nParaBottomMargin = 0;
if (GetProperty(rXPropSet, "ParaTopMargin"))
mAny >>= nParaTopMargin;
if (GetProperty(rXPropSet, "ParaBottomMargin"))
mAny >>= nParaBottomMargin;
sal_Int32 nLeftMargin = getBulletMarginIndentation ( rXPropSet, nLevel,u"LeftMargin");
sal_Int32 nLineIndentation = getBulletMarginIndentation ( rXPropSet, nLevel,u"FirstLineOffset");
if( !(nLevel != -1
|| nAlignment != style::ParagraphAdjust_LEFT
|| bHasLinespacing) )
return false;
if (nParaLeftMargin) // For Paragraph
mpFS->startElementNS( XML_a, nElement,
XML_lvl, sax_fastparser::UseIf(OString::number(nLevel), nLevel > 0),
XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaLeftMargin)), nParaLeftMargin > 0),
XML_indent, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nParaFirstLineIndent)), nParaFirstLineIndent != 0),
XML_algn, GetAlignment( nAlignment ),
XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl));
else
mpFS->startElementNS( XML_a, nElement,
XML_lvl, sax_fastparser::UseIf(OString::number(nLevel), nLevel > 0),
XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeftMargin)), nLeftMargin > 0),
XML_indent, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLineIndentation)), nLineIndentation != 0),
XML_algn, GetAlignment( nAlignment ),
XML_rtl, sax_fastparser::UseIf(ToPsz10(bRtl), bRtl));
if( bHasLinespacing )
{
mpFS->startElementNS(XML_a, XML_lnSpc);
WriteLinespacing(aLineSpacing, fFirstCharHeight);
mpFS->endElementNS( XML_a, XML_lnSpc );
}
if( nParaTopMargin != 0 )
{
mpFS->startElementNS(XML_a, XML_spcBef);
{
mpFS->singleElementNS( XML_a, XML_spcPts,
XML_val, OString::number(std::lround(nParaTopMargin / 25.4 * 72)));
}
mpFS->endElementNS( XML_a, XML_spcBef );
}
if( nParaBottomMargin != 0 )
{
mpFS->startElementNS(XML_a, XML_spcAft);
{
mpFS->singleElementNS( XML_a, XML_spcPts,
XML_val, OString::number(std::lround(nParaBottomMargin / 25.4 * 72)));
}
mpFS->endElementNS( XML_a, XML_spcAft );
}
WriteParagraphNumbering( rXPropSet, fFirstCharHeight, nLevel );
WriteParagraphTabStops( rXPropSet );
// do not end element for lstStyles since, defRPr should be stacked inside it
if( nElement != XML_lvl1pPr )
mpFS->endElementNS( XML_a, nElement );
return true;
}
void DrawingML::WriteLstStyles(const css::uno::Reference<css::text::XTextContent>& rParagraph,
bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet)
{
Reference<XEnumerationAccess> xAccess(rParagraph, UNO_QUERY);
if (!xAccess.is())
return;
Reference<XEnumeration> xEnumeration(xAccess->createEnumeration());
if (!xEnumeration.is())
return;
Reference<XTextRange> rRun;
if (!xEnumeration->hasMoreElements())
return;
Any aAny(xEnumeration->nextElement());
if (aAny >>= rRun)
{
float fFirstCharHeight = rnCharHeight / 1000.;
Reference<XPropertySet> xFirstRunPropSet(rRun, UNO_QUERY);
Reference<XPropertySetInfo> xFirstRunPropSetInfo
= xFirstRunPropSet->getPropertySetInfo();
if (xFirstRunPropSetInfo->hasPropertyByName("CharHeight"))
fFirstCharHeight = xFirstRunPropSet->getPropertyValue("CharHeight").get<float>();
mpFS->startElementNS(XML_a, XML_lstStyle);
if( !WriteParagraphProperties(rParagraph, fFirstCharHeight, XML_lvl1pPr) )
mpFS->startElementNS(XML_a, XML_lvl1pPr);
WriteRunProperties(xFirstRunPropSet, false, XML_defRPr, true, rbOverridingCharHeight,
rnCharHeight, GetScriptType(rRun->getString()), rXShapePropSet);
mpFS->endElementNS(XML_a, XML_lvl1pPr);
mpFS->endElementNS(XML_a, XML_lstStyle);
}
}
void DrawingML::WriteParagraph( const Reference< XTextContent >& rParagraph,
bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet)
{
Reference< XEnumerationAccess > access( rParagraph, UNO_QUERY );
if( !access.is() )
return;
Reference< XEnumeration > enumeration( access->createEnumeration() );
if( !enumeration.is() )
return;
mpFS->startElementNS(XML_a, XML_p);
bool bPropertiesWritten = false;
while( enumeration->hasMoreElements() )
{
Reference< XTextRange > run;
Any any ( enumeration->nextElement() );
if (any >>= run)
{
if( !bPropertiesWritten )
{
float fFirstCharHeight = rnCharHeight / 1000.;
Reference< XPropertySet > xFirstRunPropSet (run, UNO_QUERY);
Reference< XPropertySetInfo > xFirstRunPropSetInfo = xFirstRunPropSet->getPropertySetInfo();
if( xFirstRunPropSetInfo->hasPropertyByName("CharHeight") )
{
fFirstCharHeight = xFirstRunPropSet->getPropertyValue("CharHeight").get<float>();
rnCharHeight = 100 * fFirstCharHeight;
rbOverridingCharHeight = true;
}
WriteParagraphProperties(rParagraph, fFirstCharHeight, XML_pPr);
bPropertiesWritten = true;
}
WriteRun( run, rbOverridingCharHeight, rnCharHeight, rXShapePropSet);
}
}
Reference< XPropertySet > rXPropSet( rParagraph, UNO_QUERY );
sal_Int16 nDummy = -1;
WriteRunProperties(rXPropSet, false, XML_endParaRPr, false, rbOverridingCharHeight,
rnCharHeight, nDummy, rXShapePropSet);
mpFS->endElementNS( XML_a, XML_p );
}
bool DrawingML::IsFontworkShape(const css::uno::Reference<css::beans::XPropertySet>& rXShapePropSet)
{
bool bResult(false);
if (rXShapePropSet.is())
{
Sequence<PropertyValue> aCustomShapeGeometryProps;
if (GetProperty(rXShapePropSet, "CustomShapeGeometry"))
{
mAny >>= aCustomShapeGeometryProps;
uno::Sequence<beans::PropertyValue> aTextPathSeq;
for (const auto& rProp : std::as_const(aCustomShapeGeometryProps))
{
if (rProp.Name == "TextPath")
{
rProp.Value >>= aTextPathSeq;
for (const auto& rTextPathItem : std::as_const(aTextPathSeq))
{
if (rTextPathItem.Name == "TextPath")
{
rTextPathItem.Value >>= bResult;
break;
}
}
break;
}
}
}
}
return bResult;
}
void DrawingML::WriteText(const Reference<XInterface>& rXIface, bool bBodyPr, bool bText,
sal_Int32 nXmlNamespace, bool bWritePropertiesAsLstStyles)
{
// ToDo: Fontwork in DOCX
Reference< XText > xXText( rXIface, UNO_QUERY );
if( !xXText.is() )
return;
Reference< XPropertySet > rXPropSet( rXIface, UNO_QUERY );
sal_Int32 nTextPreRotateAngle = 0;
double nTextRotateAngle = 0;
#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;
sVerticalAlignment = GetTextVerticalAdjust(eVerticalAlignment);
const char* sWritingMode = nullptr;
bool bVertical = false;
if (GetProperty(rXPropSet, "TextWritingMode"))
{
WritingMode eMode;
if( ( mAny >>= eMode ) && eMode == WritingMode_TB_RL )
{
sWritingMode = "eaVert";
bVertical = true;
}
}
bool bIsFontworkShape(IsFontworkShape(rXPropSet));
Sequence<drawing::EnhancedCustomShapeAdjustmentValue> aAdjustmentSeq;
uno::Sequence<beans::PropertyValue> aTextPathSeq;
bool bScaleX(false);
OUString sShapeType("non-primitive");
// ToDo move to InteropGrabBag
OUString sMSWordPresetTextWarp;
if (GetProperty(rXPropSet, "CustomShapeGeometry"))
{
Sequence< PropertyValue > aProps;
if ( mAny >>= aProps )
{
for ( const auto& rProp : std::as_const(aProps) )
{
if ( rProp.Name == "TextPreRotateAngle" && ( rProp.Value >>= nTextPreRotateAngle ) )
{
if ( nTextPreRotateAngle == -90 )
{
sWritingMode = "vert";
bVertical = true;
}
else if ( nTextPreRotateAngle == -270 )
{
sWritingMode = "vert270";
bVertical = true;
}
}
else if (rProp.Name == "AdjustmentValues")
rProp.Value >>= aAdjustmentSeq;
else if( rProp.Name == "TextRotateAngle" )
rProp.Value >>= nTextRotateAngle;
else if (rProp.Name == "Type")
rProp.Value >>= sShapeType;
else if (rProp.Name == "TextPath")
{
rProp.Value >>= aTextPathSeq;
for (const auto& rTextPathItem : std::as_const(aTextPathSeq))
{
if (rTextPathItem.Name == "ScaleX")
rTextPathItem.Value >>= bScaleX;
}
}
else if (rProp.Name == "PresetTextWarp")
rProp.Value >>= sMSWordPresetTextWarp;
}
}
}
else
{
if (mpTextExport)
{
uno::Reference<drawing::XShape> xShape(rXIface, uno::UNO_QUERY);
if (xShape)
{
auto xTextFrame = mpTextExport->GetUnoTextFrame(xShape);
if (xTextFrame)
{
uno::Reference<beans::XPropertySet> xPropSet(xTextFrame, uno::UNO_QUERY);
auto aAny = xPropSet->getPropertyValue("WritingMode");
sal_Int16 nWritingMode;
if (aAny >>= nWritingMode)
{
switch (nWritingMode)
{
case WritingMode2::TB_RL:
sWritingMode = "vert";
bVertical = true;
break;
case WritingMode2::BT_LR:
sWritingMode = "vert270";
bVertical = true;
break;
default:
break;
}
}
}
}
}
}
OUString sPresetWarp(PresetGeometryTypeNames::GetMsoName(sShapeType));
// ODF may have user defined TextPath, use "textPlain" as ersatz.
if (sPresetWarp.isEmpty())
sPresetWarp = bIsFontworkShape ? std::u16string_view(u"textPlain") : std::u16string_view(u"textNoShape");
bool bFromWordArt = !bScaleX
&& ( sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp"
|| sPresetWarp == "textButton" || sPresetWarp == "textCircle");
TextHorizontalAdjust eHorizontalAlignment( TextHorizontalAdjust_CENTER );
bool bHorizontalCenter = false;
if (GetProperty(rXPropSet, "TextHorizontalAdjust"))
mAny >>= eHorizontalAlignment;
if( eHorizontalAlignment == TextHorizontalAdjust_CENTER )
bHorizontalCenter = true;
else if( bVertical && eHorizontalAlignment == TextHorizontalAdjust_LEFT )
sVerticalAlignment = "b";
bool bHasWrap = false;
bool bWrap = false;
// Only custom shapes obey the TextWordWrap option, normal text always wraps.
if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, "TextWordWrap"))
{
mAny >>= bWrap;
bHasWrap = true;
}
if (bBodyPr)
{
const char* pWrap = (bHasWrap && !bWrap) || bIsFontworkShape ? "none" : nullptr;
if (GetDocumentType() == DOCUMENT_DOCX)
{
// In case of DOCX, if we want to have the same effect as
// TextShape's automatic word wrapping, then we need to set
// wrapping to square.
uno::Reference<lang::XServiceInfo> xServiceInfo(rXIface, uno::UNO_QUERY);
if (xServiceInfo.is() && xServiceInfo->supportsService("com.sun.star.drawing.TextShape"))
pWrap = "square";
}
std::optional<OUString> sHorzOverflow;
std::optional<OUString> sVertOverflow;
sal_Int32 nShapeRotateAngle = rXPropSet->getPropertyValue("RotateAngle").get<sal_Int32>() / 300;
sal_Int16 nCols = 0;
sal_Int32 nColSpacing = -1;
if (GetProperty(rXPropSet, "TextColumns"))
{
if (css::uno::Reference<css::text::XTextColumns> xCols{ mAny, css::uno::UNO_QUERY })
{
nCols = xCols->getColumnCount();
if (css::uno::Reference<css::beans::XPropertySet> xProps{ mAny,
css::uno::UNO_QUERY })
{
if (GetProperty(xProps, "AutomaticDistance"))
mAny >>= nColSpacing;
}
}
}
std::optional<OString> isUpright;
if (GetProperty(rXPropSet, "InteropGrabBag"))
{
if (rXPropSet->getPropertySetInfo()->hasPropertyByName("InteropGrabBag"))
{
bool bUpright = false;
sal_Int32 nOldShapeRotation = 0;
sal_Int32 nOldTextRotation = 0;
uno::Sequence<beans::PropertyValue> aGrabBag;
rXPropSet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
for (const auto& aProp : std::as_const(aGrabBag))
{
if (aProp.Name == "Upright")
{
aProp.Value >>= bUpright;
isUpright = OString(bUpright ? "1" : "0");
}
else if (aProp.Name == "horzOverflow")
{
OUString sValue;
aProp.Value >>= sValue;
sHorzOverflow = sValue;
}
else if (aProp.Name == "vertOverflow")
{
OUString sValue;
aProp.Value >>= sValue;
sVertOverflow = sValue;
}
}
if (bUpright)
{
for (const auto& aProp : std::as_const(aGrabBag))
{
if (aProp.Name == "nShapeRotationAtImport")
aProp.Value >>= nOldShapeRotation;
else if (aProp.Name == "nTextRotationAtImport")
aProp.Value >>= nOldTextRotation;
}
// So our shape with the textbox in it was not rotated.
// Keep upright and make the preRotateAngle 0, it is an attribute
// of textBodyPr and must be 0 when upright is true, otherwise
// bad rotation happens in MSO.
if (nShapeRotateAngle == nOldShapeRotation && nShapeRotateAngle == nOldTextRotation)
nTextPreRotateAngle = 0;
// So we rotated the shape, in this case lose upright and do
// as LO normally does.
else
isUpright.reset();
}
}
}
mpFS->startElementNS( (nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr,
XML_numCol, sax_fastparser::UseIf(OString::number(nCols), nCols > 0),
XML_spcCol, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nColSpacing)), nCols > 0 && nColSpacing >= 0),
XML_wrap, pWrap,
XML_horzOverflow, sHorzOverflow,
XML_vertOverflow, sVertOverflow,
XML_fromWordArt, sax_fastparser::UseIf("1", bFromWordArt),
XML_lIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeft)), nLeft != DEFLRINS),
XML_rIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nRight)), nRight != DEFLRINS),
XML_tIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nTop)), nTop != DEFTBINS),
XML_bIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nBottom)), nBottom != DEFTBINS),
XML_anchor, sVerticalAlignment,
XML_anchorCtr, sax_fastparser::UseIf("1", bHorizontalCenter),
XML_vert, sWritingMode,
XML_upright, isUpright,
XML_rot, sax_fastparser::UseIf(oox::drawingml::calcRotationValue((nTextPreRotateAngle + nTextRotateAngle) * 100), (nTextPreRotateAngle + nTextRotateAngle) != 0));
if (bIsFontworkShape)
{
if (aAdjustmentSeq.hasElements())
{
mpFS->startElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp);
mpFS->startElementNS(XML_a, XML_avLst);
bool bHasTwoHandles(
sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour"
|| sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour"
|| sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1"
|| sPresetWarp == "textWave2" || sPresetWarp == "textWave4");
for (sal_Int32 i = 0, nElems = aAdjustmentSeq.getLength(); i < nElems; ++i )
{
OString sName = "adj" + (bHasTwoHandles ? OString::number(i + 1) : OString());
double fValue(0.0);
if (aAdjustmentSeq[i].Value.getValueTypeClass() == TypeClass_DOUBLE)
aAdjustmentSeq[i].Value >>= fValue;
else
{
sal_Int32 nNumber(0);
aAdjustmentSeq[i].Value >>= nNumber;
fValue = static_cast<double>(nNumber);
}
// Convert from binary coordinate system with viewBox "0 0 21600 21600" and simple degree
// to DrawingML with coordinate range 0..100000 and angle in 1/60000 degree.
// Reverse to conversion in lcl_createPresetShape in drawingml/shape.cxx on import.
if (sPresetWarp == "textArchDown" || sPresetWarp == "textArchUp"
|| sPresetWarp == "textButton" || sPresetWarp == "textCircle"
|| ((i == 0)
&& (sPresetWarp == "textArchDownPour" || sPresetWarp == "textArchUpPour"
|| sPresetWarp == "textButtonPour" || sPresetWarp == "textCirclePour")))
{
fValue *= 60000.0;
if (fValue < 0)
fValue += 21600000;
}
else if ((i == 1)
&& (sPresetWarp == "textDoubleWave1" || sPresetWarp == "textWave1"
|| sPresetWarp == "textWave2" || sPresetWarp == "textWave4"))
{
fValue = fValue / 0.216 - 50000.0;
}
else if ((i == 1)
&& (sPresetWarp == "textArchDownPour"
|| sPresetWarp == "textArchUpPour"
|| sPresetWarp == "textButtonPour"
|| sPresetWarp == "textCirclePour"))
{
fValue /= 0.108;
}
else
{
fValue /= 0.216;
}
OString sFmla = "val " + OString::number(std::lround(fValue));
mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla);
// There exists faulty Favorite shapes with one handle but two adjustment values.
if (!bHasTwoHandles)
break;
}
mpFS->endElementNS(XML_a, XML_avLst);
mpFS->endElementNS(XML_a, XML_prstTxWarp);
}
else
{
mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sPresetWarp);
}
}
else if (GetDocumentType() == DOCUMENT_DOCX)
{
// interim solution for fdo#80897, roundtrip DOCX > LO > DOCX
if (!sMSWordPresetTextWarp.isEmpty())
mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, sMSWordPresetTextWarp);
}
if (GetDocumentType() == DOCUMENT_DOCX || GetDocumentType() == DOCUMENT_XLSX)
{
// tdf#112312: only custom shapes obey the TextAutoGrowHeight option
bool bTextAutoGrowHeight = false;
uno::Reference<drawing::XShape> xShape(rXIface, uno::UNO_QUERY);
auto pSdrObjCustomShape = xShape.is() ? dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape)) : nullptr;
if (pSdrObjCustomShape && GetProperty(rXPropSet, "TextAutoGrowHeight"))
{
mAny >>= bTextAutoGrowHeight;
}
mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit));
}
if (GetDocumentType() == DOCUMENT_PPTX)
{
TextFitToSizeType eFit = TextFitToSizeType_NONE;
if (GetProperty(rXPropSet, "TextFitToSize"))
mAny >>= eFit;
if (eFit == TextFitToSizeType_AUTOFIT)
{
const sal_Int32 MAX_SCALE_VAL = 100000;
sal_Int32 nFontScale = MAX_SCALE_VAL;
SvxShapeText* pTextShape = dynamic_cast<SvxShapeText*>(rXIface.get());
if (pTextShape)
{
SdrTextObj* pTextObject = dynamic_cast<SdrTextObj*>(pTextShape->GetSdrObject());
if (pTextObject)
nFontScale = pTextObject->GetFontScaleY() * 1000;
}
mpFS->singleElementNS(XML_a, XML_normAutofit, XML_fontScale,
sax_fastparser::UseIf(OString::number(nFontScale), nFontScale < MAX_SCALE_VAL && nFontScale > 0));
}
else
{
// tdf#127030: Only custom shapes obey the TextAutoGrowHeight option.
bool bTextAutoGrowHeight = false;
if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, "TextAutoGrowHeight"))
mAny >>= bTextAutoGrowHeight;
mpFS->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit));
}
}
Write3DEffects( rXPropSet, /*bIsText=*/true );
mpFS->endElementNS((nXmlNamespace ? nXmlNamespace : XML_a), XML_bodyPr);
}
Reference< XEnumerationAccess > access( xXText, UNO_QUERY );
if( !access.is() || !bText )
return;
Reference< XEnumeration > enumeration( access->createEnumeration() );
if( !enumeration.is() )
return;
uno::Reference<drawing::XShape> xShape(rXIface, uno::UNO_QUERY);
SdrObject* pSdrObject = xShape.is() ? SdrObject::getSdrObjectFromXShape(xShape) : nullptr;
const SdrTextObj* pTxtObj = dynamic_cast<SdrTextObj*>( pSdrObject );
if (pTxtObj && mpTextExport)
{
std::optional<OutlinerParaObject> pParaObj;
/*
#i13885#
When the object is actively being edited, that text is not set into
the objects normal text object, but lives in a separate object.
*/
if (pTxtObj->IsTextEditActive())
{
pParaObj = pTxtObj->CreateEditOutlinerParaObject();
}
else if (pTxtObj->GetOutlinerParaObject())
pParaObj = *pTxtObj->GetOutlinerParaObject();
if (pParaObj)
{
// this is reached only in case some text is attached to the shape
mpTextExport->WriteOutliner(*pParaObj);
}
return;
}
bool bOverridingCharHeight = false;
sal_Int32 nCharHeight = -1;
bool bFirstParagraph = true;
while( enumeration->hasMoreElements() )
{
Reference< XTextContent > paragraph;
Any any ( enumeration->nextElement() );
if( any >>= paragraph)
{
if (bFirstParagraph && bWritePropertiesAsLstStyles)
WriteLstStyles(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet);
WriteParagraph(paragraph, bOverridingCharHeight, nCharHeight, rXPropSet);
bFirstParagraph = false;
}
}
}
void DrawingML::WritePresetShape( const OString& pShape , std::vector< std::pair<sal_Int32,sal_Int32>> & rAvList )
{
mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
if ( !rAvList.empty() )
{
mpFS->startElementNS(XML_a, XML_avLst);
for (auto const& elem : rAvList)
{
OString sName = "adj" + ( ( elem.first > 0 ) ? OString::number(elem.first) : OString() );
OString sFmla = "val " + OString::number( elem.second );
mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla);
}
mpFS->endElementNS( XML_a, XML_avLst );
}
else
mpFS->singleElementNS(XML_a, XML_avLst);
mpFS->endElementNS( XML_a, XML_prstGeom );
}
void DrawingML::WritePresetShape( const OString& pShape )
{
mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
mpFS->singleElementNS(XML_a, XML_avLst);
mpFS->endElementNS( XML_a, XML_prstGeom );
}
static std::map< OString, std::vector<OString> > lcl_getAdjNames()
{
std::map< OString, std::vector<OString> > aRet;
OUString aPath("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/filter/oox-drawingml-adj-names");
rtl::Bootstrap::expandMacros(aPath);
SvFileStream aStream(aPath, StreamMode::READ);
if (aStream.GetError() != ERRCODE_NONE)
SAL_WARN("oox.shape", "failed to open oox-drawingml-adj-names");
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 OString& pShape, MSO_SPT eShapeType, bool bPredefinedHandlesUsed, const PropertyValue& rProp )
{
static std::map< OString, std::vector<OString> > aAdjMap = lcl_getAdjNames();
// If there are predefined adj names for this shape type, look them up now.
std::vector<OString> aAdjustments;
if (aAdjMap.find(pShape) != aAdjMap.end())
aAdjustments = aAdjMap[pShape];
mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
mpFS->startElementNS(XML_a, XML_avLst);
Sequence< drawing::EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq;
if ( ( rProp.Value >>= aAdjustmentSeq )
&& eShapeType != mso_sptActionButtonForwardNext // we have adjustments values for these type of shape, but MSO doesn't like them
&& eShapeType != mso_sptActionButtonBackPrevious // so they are now disabled
&& pShape != "rect" //some shape types are commented out in pCustomShapeTypeTranslationTable[] & are being defaulted to rect & rect does not have adjustment values/name.
)
{
SAL_INFO("oox.shape", "adj seq len: " << aAdjustmentSeq.getLength());
sal_Int32 nAdjustmentsWhichNeedsToBeConverted = 0;
if ( bPredefinedHandlesUsed )
EscherPropertyContainer::LookForPolarHandles( eShapeType, nAdjustmentsWhichNeedsToBeConverted );
sal_Int32 nValue, nLength = aAdjustmentSeq.getLength();
// aAdjustments will give info about the number of adj values for a particular geometry. For example for hexagon aAdjustments.size() will be 2 and for circular arrow it will be 5 as per lcl_getAdjNames.
// Sometimes there are more values than needed, so we ignore the excessive ones.
if (aAdjustments.size() <= o3tl::make_unsigned(nLength))
{
for (sal_Int32 i = 0; i < static_cast<sal_Int32>(aAdjustments.size()); i++)
{
if( EscherPropertyContainer::GetAdjustmentValue( aAdjustmentSeq[ i ], i, nAdjustmentsWhichNeedsToBeConverted, nValue ) )
{
// If the document model doesn't have an adjustment name (e.g. shape was created from VML), then take it from the predefined list.
OString aAdjName = aAdjustmentSeq[i].Name.isEmpty()
? aAdjustments[i]
: aAdjustmentSeq[i].Name.toUtf8();
mpFS->singleElementNS( XML_a, XML_gd,
XML_name, aAdjName,
XML_fmla, "val " + OString::number(nValue));
}
}
}
}
mpFS->endElementNS( XML_a, XML_avLst );
mpFS->endElementNS( XML_a, XML_prstGeom );
}
namespace // helpers for DrawingML::WriteCustomGeometry
{
sal_Int32
FindNextCommandEndSubpath(const sal_Int32 nStart,
const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments)
{
sal_Int32 i = nStart < 0 ? 0 : nStart;
while (i < rSegments.getLength() && rSegments[i].Command != ENDSUBPATH)
i++;
return i;
}
bool HasCommandInSubPath(const sal_Int16 nCommand, const sal_Int32 nFirst, const sal_Int32 nLast,
const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments)
{
for (sal_Int32 i = nFirst < 0 ? 0 : nFirst; i <= nLast && i < rSegments.getLength(); i++)
{
if (rSegments[i].Command == nCommand)
return true;
}
return false;
}
// Ellipse is given by radii fwR and fhR and center (fCx|fCy). The ray from center through point RayP
// intersects the ellipse in point S and this point S has angle fAngleDeg in degrees.
void getEllipsePointAndAngleFromRayPoint(double& rfAngleDeg, double& rfSx, double& rfSy,
const double fWR, const double fHR, const double fCx,
const double fCy, const double fRayPx, const double fRayPy)
{
if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR))
{
rfSx = fCx; // needed for getting new 'current point'
rfSy = fCy;
}
else
{
// center ellipse at origin, stretch in y-direction to circle, flip to Math orientation
// and get angle
double fCircleMathAngle = atan2(-fWR / fHR * (fRayPy - fCy), fRayPx - fCx);
// use angle for intersection point on circle and stretch back to ellipse
double fPointMathEllipse_x = fWR * cos(fCircleMathAngle);
double fPointMathEllipse_y = fHR * sin(fCircleMathAngle);
// get angle of intersection point on ellipse
double fEllipseMathAngle = atan2(fPointMathEllipse_y, fPointMathEllipse_x);
// convert from Math to View orientation and shift ellipse back from origin
rfAngleDeg = -basegfx::rad2deg(fEllipseMathAngle);
rfSx = fPointMathEllipse_x + fCx;
rfSy = -fPointMathEllipse_y + fCy;
}
}
void getEllipsePointFromViewAngle(double& rfSx, double& rfSy, const double fWR, const double fHR,
const double fCx, const double fCy, const double fViewAngleDeg)
{
if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR))
{
rfSx = fCx; // needed for getting new 'current point'
rfSy = fCy;
}
else
{
double fX = cos(basegfx::deg2rad(fViewAngleDeg)) / fWR;
double fY = sin(basegfx::deg2rad(fViewAngleDeg)) / fHR;
double fRadius = 1.0 / std::hypot(fX, fY);
rfSx = fCx + fRadius * cos(basegfx::deg2rad(fViewAngleDeg));
rfSy = fCy + fRadius * sin(basegfx::deg2rad(fViewAngleDeg));
}
}
sal_Int32 GetCustomGeometryPointValue(const css::drawing::EnhancedCustomShapeParameter& rParam,
const EnhancedCustomShape2d& rCustomShape2d,
const bool bReplaceGeoWidth, const bool bReplaceGeoHeight)
{
double fValue = 0.0;
rCustomShape2d.GetParameter(fValue, rParam, bReplaceGeoWidth, bReplaceGeoHeight);
sal_Int32 nValue(std::lround(fValue));
return nValue;
}
struct TextAreaRect
{
OString left;
OString top;
OString right;
OString bottom;
};
struct Guide
{
OString sName;
OString sFormula;
};
void prepareTextArea(const EnhancedCustomShape2d& rEnhancedCustomShape2d,
std::vector<Guide>& rGuideList, TextAreaRect& rTextAreaRect)
{
tools::Rectangle aTextAreaLO(rEnhancedCustomShape2d.GetTextRect());
tools::Rectangle aLogicRectLO(rEnhancedCustomShape2d.GetLogicRect());
if (aTextAreaLO == aLogicRectLO)
{
rTextAreaRect.left = "l";
rTextAreaRect.top = "t";
rTextAreaRect.right = "r";
rTextAreaRect.bottom = "b";
return;
}
// Flip aTextAreaLO if shape is flipped
if (rEnhancedCustomShape2d.IsFlipHorz())
aTextAreaLO.Move((aLogicRectLO.Center().X() - aTextAreaLO.Center().X()) * 2, 0);
if (rEnhancedCustomShape2d.IsFlipVert())
aTextAreaLO.Move(0, (aLogicRectLO.Center().Y() - aTextAreaLO.Center().Y()) * 2);
Guide aGuide;
// horizontal
const sal_Int32 nWidth = aLogicRectLO.Right() - aLogicRectLO.Left();
const OString sWidth = OString::number(oox::drawingml::convertHmmToEmu(nWidth));
// left
aGuide.sName = "textAreaLeft";
sal_Int32 nHelp = aTextAreaLO.Left() - aLogicRectLO.Left();
const OString sLeft = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
aGuide.sFormula = "*/ " + sLeft + " w " + sWidth;
rTextAreaRect.left = aGuide.sName;
rGuideList.push_back(aGuide);
// right
aGuide.sName = "textAreaRight";
nHelp = aTextAreaLO.Right() - aLogicRectLO.Left();
const OString sRight = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
aGuide.sFormula = "*/ " + sRight + " w " + sWidth;
rTextAreaRect.right = aGuide.sName;
rGuideList.push_back(aGuide);
// vertical
const sal_Int32 nHeight = aLogicRectLO.Bottom() - aLogicRectLO.Top();
const OString sHeight = OString::number(oox::drawingml::convertHmmToEmu(nHeight));
// top
aGuide.sName = "textAreaTop";
nHelp = aTextAreaLO.Top() - aLogicRectLO.Top();
const OString sTop = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
aGuide.sFormula = "*/ " + sTop + " h " + sHeight;
rTextAreaRect.top = aGuide.sName;
rGuideList.push_back(aGuide);
// bottom
aGuide.sName = "textAreaBottom";
nHelp = aTextAreaLO.Bottom() - aLogicRectLO.Top();
const OString sBottom = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
aGuide.sFormula = "*/ " + sBottom + " h " + sHeight;
rTextAreaRect.bottom = aGuide.sName;
rGuideList.push_back(aGuide);
return;
}
}
bool DrawingML::WriteCustomGeometry(
const Reference< XShape >& rXShape,
const SdrObjCustomShape& rSdrObjCustomShape)
{
uno::Reference< beans::XPropertySet > aXPropSet;
uno::Any aAny( rXShape->queryInterface(cppu::UnoType<beans::XPropertySet>::get()));
if ( ! (aAny >>= aXPropSet) )
return false;
try
{
aAny = aXPropSet->getPropertyValue( "CustomShapeGeometry" );
if ( !aAny.hasValue() )
return false;
}
catch( const ::uno::Exception& )
{
return false;
}
auto pGeometrySeq = o3tl::tryAccess<uno::Sequence<beans::PropertyValue>>(aAny);
if (!pGeometrySeq)
return false;
auto pPathProp = std::find_if(std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq),
[](const PropertyValue& rProp) { return rProp.Name == "Path"; });
if (pPathProp == std::cend(*pGeometrySeq))
return false;
uno::Sequence<beans::PropertyValue> aPathProp;
pPathProp->Value >>= aPathProp;
uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs;
uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments;
uno::Sequence<awt::Size> aPathSize;
bool bReplaceGeoWidth = false;
bool bReplaceGeoHeight = false;
for (const beans::PropertyValue& rPathProp : std::as_const(aPathProp))
{
if (rPathProp.Name == "Coordinates")
rPathProp.Value >>= aPairs;
else if (rPathProp.Name == "Segments")
rPathProp.Value >>= aSegments;
else if (rPathProp.Name == "SubViewSize")
rPathProp.Value >>= aPathSize;
else if (rPathProp.Name == "StretchX")
bReplaceGeoWidth = true;
else if (rPathProp.Name == "StretchY")
bReplaceGeoHeight = true;
}
if ( !aPairs.hasElements() )
return false;
if ( !aSegments.hasElements() )
{
aSegments = uno::Sequence<drawing::EnhancedCustomShapeSegment>
{
{ MOVETO, 1 },
{ LINETO,
static_cast<sal_Int16>(std::min( aPairs.getLength() - 1, sal_Int32(32767) )) },
{ CLOSESUBPATH, 0 },
{ ENDSUBPATH, 0 }
};
};
int nExpectedPairCount = std::accumulate(std::cbegin(aSegments), std::cend(aSegments), 0,
[](const int nSum, const drawing::EnhancedCustomShapeSegment& rSegment) { return nSum + rSegment.Count; });
if ( nExpectedPairCount > aPairs.getLength() )
{
SAL_WARN("oox.shape", "Segments need " << nExpectedPairCount << " coordinates, but Coordinates have only " << aPairs.getLength() << " pairs.");
return false;
}
// A EnhancedCustomShape2d caches the equation results. Therefore we use only one of it for the
// entire method.
const EnhancedCustomShape2d aCustomShape2d(const_cast<SdrObjCustomShape&>(rSdrObjCustomShape));
TextAreaRect aTextAreaRect;
std::vector<Guide> aGuideList; // for now only for <a:rect>
prepareTextArea(aCustomShape2d, aGuideList, aTextAreaRect);
mpFS->startElementNS(XML_a, XML_custGeom);
mpFS->singleElementNS(XML_a, XML_avLst);
if (aGuideList.empty())
{
mpFS->singleElementNS(XML_a, XML_gdLst);
}
else
{
mpFS->startElementNS(XML_a, XML_gdLst);
for (auto const& elem : aGuideList)
{
mpFS->singleElementNS(XML_a, XML_gd, XML_name, elem.sName, XML_fmla, elem.sFormula);
}
mpFS->endElementNS(XML_a, XML_gdLst);
}
mpFS->singleElementNS(XML_a, XML_ahLst);
mpFS->singleElementNS(XML_a, XML_rect, XML_l, aTextAreaRect.left, XML_t, aTextAreaRect.top,
XML_r, aTextAreaRect.right, XML_b, aTextAreaRect.bottom);
mpFS->startElementNS(XML_a, XML_pathLst);
// Prepare width and height for <a:path>
bool bUseGlobalViewBox(false);
// nViewBoxWidth must be integer otherwise ReplaceGeoWidth in aCustomShape2d.GetParameter() is not
// triggered; same for height.
sal_Int32 nViewBoxWidth(0);
sal_Int32 nViewBoxHeight(0);
if (!aPathSize.hasElements())
{
bUseGlobalViewBox = true;
// If draw:viewBox is missing in draw:enhancedGeometry, then import sets
// viewBox="0 0 21600 21600". Missing ViewBox can only occur, if user has manipulated
// current file via macro. Author of macro has to fix it.
auto pProp = std::find_if(
std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq),
[](const beans::PropertyValue& rGeomProp) { return rGeomProp.Name == "ViewBox"; });
if (pProp != std::cend(*pGeometrySeq))
{
css::awt::Rectangle aViewBox;
if (pProp->Value >>= aViewBox)
{
nViewBoxWidth = aViewBox.Width;
nViewBoxHeight = aViewBox.Height;
css::drawing::EnhancedCustomShapeParameter aECSP;
aECSP.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL;
aECSP.Value <<= nViewBoxWidth;
double fRetValue;
aCustomShape2d.GetParameter(fRetValue, aECSP, true, false);
nViewBoxWidth = basegfx::fround(fRetValue);
aECSP.Value <<= nViewBoxHeight;
aCustomShape2d.GetParameter(fRetValue, aECSP, false, true);
nViewBoxHeight = basegfx::fround(fRetValue);
}
}
// Import from oox or documents, which are imported from oox and saved to strict ODF, might
// have no subViewSize but viewBox="0 0 0 0". We need to generate width and height in those
// cases. Even if that is fixed, we need the substitute for old documents.
if ((nViewBoxWidth == 0 && nViewBoxHeight == 0) || pProp == std::cend(*pGeometrySeq))
{
// Generate a substitute based on point coordinates
sal_Int32 nXMin(0);
aPairs[0].First.Value >>= nXMin;
sal_Int32 nXMax = nXMin;
sal_Int32 nYMin(0);
aPairs[0].Second.Value >>= nYMin;
sal_Int32 nYMax = nYMin;
for (const auto& rPair : std::as_const(aPairs))
{
sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, aCustomShape2d,
bReplaceGeoWidth, false);
sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, aCustomShape2d, false,
bReplaceGeoHeight);
if (nX < nXMin)
nXMin = nX;
if (nY < nYMin)
nYMin = nY;
if (nX > nXMax)
nXMax = nX;
if (nY > nYMax)
nYMax = nY;
}
nViewBoxWidth = std::max(nXMax, nXMax - nXMin);
nViewBoxHeight = std::max(nYMax, nYMax - nYMin);
}
// ToDo: Other values of left,top than 0,0 are not considered yet. Such would require a
// shift of the resulting path coordinates.
}
// Iterate over subpaths
sal_Int32 nPairIndex = 0; // index over "Coordinates"
sal_Int32 nPathSizeIndex = 0; // index over "SubViewSize"
sal_Int32 nSubpathStartIndex(0); // index over "Segments"
sal_Int32 nSubPathIndex(0); // serial number of current subpath
do
{
bool bOK(true); // catch faulty paths were commands do not correspond to points
// get index of next command ENDSUBPATH; if such doesn't exist use index behind last segment
sal_Int32 nNextNcommandIndex = FindNextCommandEndSubpath(nSubpathStartIndex, aSegments);
// Prepare attributes for a:path start element
// NOFILL or one of the LIGHTEN commands
std::optional<OString> sFill;
if (HasCommandInSubPath(NOFILL, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
sFill = "none";
else if (HasCommandInSubPath(DARKEN, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
sFill = "darken";
else if (HasCommandInSubPath(DARKENLESS, nSubpathStartIndex, nNextNcommandIndex - 1,
aSegments))
sFill = "darkenLess";
else if (HasCommandInSubPath(LIGHTEN, nSubpathStartIndex, nNextNcommandIndex - 1,
aSegments))
sFill = "lighten";
else if (HasCommandInSubPath(LIGHTENLESS, nSubpathStartIndex, nNextNcommandIndex - 1,
aSegments))
sFill = "lightenLess";
else
{
// shading info might be in object type, e.g. "Octagon Bevel".
sal_Int32 nLuminanceChange(aCustomShape2d.GetLuminanceChange(nSubPathIndex));
if (nLuminanceChange <= -40)
sFill = "darken";
else if (nLuminanceChange <= -10)
sFill = "darkenLess";
else if (nLuminanceChange >= 40)
sFill = "lighten";
else if (nLuminanceChange >= 10)
sFill = "lightenLess";
}
// NOSTROKE
std::optional<OString> sStroke;
if (HasCommandInSubPath(NOSTROKE, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
sStroke = "0";
// Write a:path start element
mpFS->startElementNS(
XML_a, XML_path, XML_fill, sFill, XML_stroke, sStroke, XML_w,
OString::number(bUseGlobalViewBox ? nViewBoxWidth : aPathSize[nPathSizeIndex].Width),
XML_h,
OString::number(bUseGlobalViewBox ? nViewBoxHeight : aPathSize[nPathSizeIndex].Height));
// Arcs drawn by commands ELLIPTICALQUADRANTX and ELLIPTICALQUADRANTY depend on the position
// of the target point in regard to the current point. Therefore we need to track the
// current point. A current point is not defined in the beginning.
double fCurrentX(0.0);
double fCurrentY(0.0);
bool bCurrentValid(false);
// Actually write the subpath
for (sal_Int32 nSegmentIndex = nSubpathStartIndex; nSegmentIndex < nNextNcommandIndex;
++nSegmentIndex)
{
const auto& rSegment(aSegments[nSegmentIndex]);
if (rSegment.Command == CLOSESUBPATH)
{
mpFS->singleElementNS(XML_a, XML_close); // command Z has no parameter
// ODF 1.4 specifies, that the start of the subpath becomes the current point.
// But that is not implemented yet. Currently LO keeps the last current point.
}
for (sal_Int32 k = 0; k < rSegment.Count && bOK; ++k)
{
bOK = WriteCustomGeometrySegment(rSegment.Command, k, aPairs, nPairIndex, fCurrentX,
fCurrentY, bCurrentValid, aCustomShape2d,
bReplaceGeoWidth, bReplaceGeoHeight);
}
} // end loop over all commands of subpath
// finish this subpath in any case
mpFS->endElementNS(XML_a, XML_path);
if (!bOK)
break; // exit loop if not enough values in aPairs
// step forward to next subpath
nSubpathStartIndex = nNextNcommandIndex + 1;
nPathSizeIndex++;
nSubPathIndex++;
} while (nSubpathStartIndex < aSegments.getLength());
mpFS->endElementNS(XML_a, XML_pathLst);
mpFS->endElementNS(XML_a, XML_custGeom);
return true; // We have written custGeom even if path is poorly structured.
}
bool DrawingML::WriteCustomGeometrySegment(
const sal_Int16 eCommand, const sal_Int32 nCount,
const uno::Sequence<css::drawing::EnhancedCustomShapeParameterPair>& rPairs,
sal_Int32& rnPairIndex, double& rfCurrentX, double& rfCurrentY, bool& rbCurrentValid,
const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth,
const bool bReplaceGeoHeight)
{
switch (eCommand)
{
case MOVETO:
{
if (rnPairIndex >= rPairs.getLength())
return false;
mpFS->startElementNS(XML_a, XML_moveTo);
WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
bReplaceGeoHeight);
mpFS->endElementNS(XML_a, XML_moveTo);
rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth,
false);
rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false,
bReplaceGeoHeight);
rbCurrentValid = true;
rnPairIndex++;
break;
}
case LINETO:
{
if (rnPairIndex >= rPairs.getLength())
return false;
// LINETO without valid current point is a faulty path. LO is tolerant and makes a
// moveTo instead. Do the same on export. MS OFFICE requires a current point for lnTo,
// otherwise it shows nothing of the shape.
if (rbCurrentValid)
{
mpFS->startElementNS(XML_a, XML_lnTo);
WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
bReplaceGeoHeight);
mpFS->endElementNS(XML_a, XML_lnTo);
}
else
{
mpFS->startElementNS(XML_a, XML_moveTo);
WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
bReplaceGeoHeight);
mpFS->endElementNS(XML_a, XML_moveTo);
}
rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth,
false);
rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false,
bReplaceGeoHeight);
rbCurrentValid = true;
rnPairIndex++;
break;
}
case CURVETO:
{
if (rnPairIndex + 2 >= rPairs.getLength())
return false;
mpFS->startElementNS(XML_a, XML_cubicBezTo);
for (sal_uInt8 i = 0; i <= 2; ++i)
{
WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth,
bReplaceGeoHeight);
}
mpFS->endElementNS(XML_a, XML_cubicBezTo);
rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth,
false);
rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 2].Second, false,
bReplaceGeoHeight);
rbCurrentValid = true;
rnPairIndex += 3;
break;
}
case ANGLEELLIPSETO:
case ANGLEELLIPSE:
{
if (rnPairIndex + 2 >= rPairs.getLength())
return false;
// Read parameters
double fCx = 0.0;
rCustomShape2d.GetParameter(fCx, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
double fCy = 0.0;
rCustomShape2d.GetParameter(fCy, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
double fWR = 0.0;
rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex + 1].First, false, false);
double fHR = 0.0;
rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex + 1].Second, false, false);
double fStartAngle = 0.0;
rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 2].First, false, false);
double fEndAngle = 0.0;
rCustomShape2d.GetParameter(fEndAngle, rPairs[rnPairIndex + 2].Second, false, false);
// Prepare start and swing angle
sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
sal_Int32 nSwingAng = 0;
if (basegfx::fTools::equalZero(fStartAngle)
&& basegfx::fTools::equalZero(fEndAngle - 360.0))
nSwingAng = 360 * 60000; // special case full circle
else
{
nSwingAng = std::lround((fEndAngle - fStartAngle) * 60000);
if (nSwingAng < 0)
nSwingAng += 360 * 60000;
}
// calculate start point on ellipse
double fSx = 0.0;
double fSy = 0.0;
getEllipsePointFromViewAngle(fSx, fSy, fWR, fHR, fCx, fCy, fStartAngle);
// write markup for going to start point
// lnTo requires a valid current point
if (eCommand == ANGLEELLIPSETO && rbCurrentValid)
{
mpFS->startElementNS(XML_a, XML_lnTo);
mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)),
XML_y, OString::number(std::lround(fSy)));
mpFS->endElementNS(XML_a, XML_lnTo);
}
else
{
mpFS->startElementNS(XML_a, XML_moveTo);
mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)),
XML_y, OString::number(std::lround(fSy)));
mpFS->endElementNS(XML_a, XML_moveTo);
}
// write markup for arcTo
if (!basegfx::fTools::equalZero(fWR) && !basegfx::fTools::equalZero(fHR))
mpFS->singleElement(
FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR,
OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng),
XML_swAng, OString::number(nSwingAng));
getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy, fEndAngle);
rbCurrentValid = true;
rnPairIndex += 3;
break;
}
case ARCTO:
case ARC:
case CLOCKWISEARCTO:
case CLOCKWISEARC:
{
if (rnPairIndex + 3 >= rPairs.getLength())
return false;
// read parameters
double fX1 = 0.0;
rCustomShape2d.GetParameter(fX1, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
double fY1 = 0.0;
rCustomShape2d.GetParameter(fY1, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
double fX2 = 0.0;
rCustomShape2d.GetParameter(fX2, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth,
false);
double fY2 = 0.0;
rCustomShape2d.GetParameter(fY2, rPairs[rnPairIndex + 1].Second, false,
bReplaceGeoHeight);
double fX3 = 0.0;
rCustomShape2d.GetParameter(fX3, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth,
false);
double fY3 = 0.0;
rCustomShape2d.GetParameter(fY3, rPairs[rnPairIndex + 2].Second, false,
bReplaceGeoHeight);
double fX4 = 0.0;
rCustomShape2d.GetParameter(fX4, rPairs[rnPairIndex + 3].First, bReplaceGeoWidth,
false);
double fY4 = 0.0;
rCustomShape2d.GetParameter(fY4, rPairs[rnPairIndex + 3].Second, false,
bReplaceGeoHeight);
// calculate ellipse parameter
const double fWR = (fX2 - fX1) / 2.0;
const double fHR = (fY2 - fY1) / 2.0;
const double fCx = (fX1 + fX2) / 2.0;
const double fCy = (fY1 + fY2) / 2.0;
// calculate start angle
double fStartAngle = 0.0;
double fPx = 0.0;
double fPy = 0.0;
getEllipsePointAndAngleFromRayPoint(fStartAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX3,
fY3);
// markup for going to start point
// lnTo requires a valid current point.
if ((eCommand == ARCTO || eCommand == CLOCKWISEARCTO) && rbCurrentValid)
{
mpFS->startElementNS(XML_a, XML_lnTo);
mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)),
XML_y, OString::number(std::lround(fPy)));
mpFS->endElementNS(XML_a, XML_lnTo);
}
else
{
mpFS->startElementNS(XML_a, XML_moveTo);
mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)),
XML_y, OString::number(std::lround(fPy)));
mpFS->endElementNS(XML_a, XML_moveTo);
}
// calculate swing angle
double fEndAngle = 0.0;
getEllipsePointAndAngleFromRayPoint(fEndAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX4, fY4);
double fSwingAngle(fEndAngle - fStartAngle);
const bool bIsClockwise(eCommand == CLOCKWISEARCTO || eCommand == CLOCKWISEARC);
if (bIsClockwise && fSwingAngle < 0)
fSwingAngle += 360.0;
else if (!bIsClockwise && fSwingAngle > 0)
fSwingAngle -= 360.0;
// markup for arcTo
// ToDo: write markup for case zero width or height of ellipse
const sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
const sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000));
mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)),
XML_hR, OString::number(std::lround(fHR)), XML_stAng,
OString::number(nStartAng), XML_swAng, OString::number(nSwingAng));
rfCurrentX = fPx;
rfCurrentY = fPy;
rbCurrentValid = true;
rnPairIndex += 4;
break;
}
case ELLIPTICALQUADRANTX:
case ELLIPTICALQUADRANTY:
{
if (rnPairIndex >= rPairs.getLength())
return false;
// read parameters
double fX = 0.0;
rCustomShape2d.GetParameter(fX, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
double fY = 0.0;
rCustomShape2d.GetParameter(fY, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
// Prepare parameters for arcTo
if (rbCurrentValid)
{
double fWR = std::abs(rfCurrentX - fX);
double fHR = std::abs(rfCurrentY - fY);
double fStartAngle(0.0);
double fSwingAngle(0.0);
// The starting direction of the arc toggles between X and Y
if ((eCommand == ELLIPTICALQUADRANTX && !(nCount % 2))
|| (eCommand == ELLIPTICALQUADRANTY && (nCount % 2)))
{
// arc starts horizontal
fStartAngle = fY < rfCurrentY ? 90.0 : 270.0;
const bool bClockwise = (fX < rfCurrentX && fY < rfCurrentY)
|| (fX > rfCurrentX && fY > rfCurrentY);
fSwingAngle = bClockwise ? 90.0 : -90.0;
}
else
{
// arc starts vertical
fStartAngle = fX < rfCurrentX ? 0.0 : 180.0;
const bool bClockwise = (fX < rfCurrentX && fY > rfCurrentY)
|| (fX > rfCurrentX && fY < rfCurrentY);
fSwingAngle = bClockwise ? 90.0 : -90.0;
}
sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000));
mpFS->singleElement(
FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR,
OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng),
XML_swAng, OString::number(nSwingAng));
}
else
{
// faulty path, but we continue with the target point
mpFS->startElementNS(XML_a, XML_moveTo);
WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
bReplaceGeoHeight);
mpFS->endElementNS(XML_a, XML_moveTo);
}
rfCurrentX = fX;
rfCurrentY = fY;
rbCurrentValid = true;
rnPairIndex++;
break;
}
case QUADRATICCURVETO:
{
if (rnPairIndex + 1 >= rPairs.getLength())
return false;
mpFS->startElementNS(XML_a, XML_quadBezTo);
for (sal_uInt8 i = 0; i < 2; ++i)
{
WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth,
bReplaceGeoHeight);
}
mpFS->endElementNS(XML_a, XML_quadBezTo);
rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth,
false);
rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 1].Second, false,
bReplaceGeoHeight);
rbCurrentValid = true;
rnPairIndex += 2;
break;
}
case ARCANGLETO:
{
if (rnPairIndex + 1 >= rPairs.getLength())
return false;
double fWR = 0.0;
rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex].First, false, false);
double fHR = 0.0;
rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex].Second, false, false);
double fStartAngle = 0.0;
rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 1].First, false, false);
sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
double fSwingAng = 0.0;
rCustomShape2d.GetParameter(fSwingAng, rPairs[rnPairIndex + 1].Second, false, false);
sal_Int32 nSwingAng(std::lround(fSwingAng * 60000));
mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(fWR), XML_hR,
OString::number(fHR), XML_stAng, OString::number(nStartAng),
XML_swAng, OString::number(nSwingAng));
double fPx = 0.0;
double fPy = 0.0;
getEllipsePointFromViewAngle(fPx, fPy, fWR, fHR, 0.0, 0.0, fStartAngle);
double fCx = rfCurrentX - fPx;
double fCy = rfCurrentY - fPy;
getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy,
fStartAngle + fSwingAng);
rbCurrentValid = true;
rnPairIndex += 2;
break;
}
default:
// do nothing
break;
}
return true;
}
void DrawingML::WriteCustomGeometryPoint(
const drawing::EnhancedCustomShapeParameterPair& rParamPair,
const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth,
const bool bReplaceGeoHeight)
{
sal_Int32 nX
= GetCustomGeometryPointValue(rParamPair.First, rCustomShape2d, bReplaceGeoWidth, false);
sal_Int32 nY
= GetCustomGeometryPointValue(rParamPair.Second, rCustomShape2d, false, bReplaceGeoHeight);
mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(nX), XML_y, OString::number(nY));
}
void DrawingML::WriteEmptyCustomGeometry()
{
// This method is used for export to docx in case WriteCustomGeometry fails.
mpFS->startElementNS(XML_a, XML_custGeom);
mpFS->singleElementNS(XML_a, XML_avLst);
mpFS->singleElementNS(XML_a, XML_gdLst);
mpFS->singleElementNS(XML_a, XML_ahLst);
mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b");
mpFS->singleElementNS(XML_a, XML_pathLst);
mpFS->endElementNS(XML_a, XML_custGeom);
}
// version for SdrPathObj
void DrawingML::WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape,
const bool bClosed)
{
tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(rXShape);
// In case of Writer, the parent element is <wps:spPr>, and there the
// <a:custGeom> element is not optional.
if (aPolyPolygon.Count() < 1 && GetDocumentType() != DOCUMENT_DOCX)
return;
mpFS->startElementNS(XML_a, XML_custGeom);
mpFS->singleElementNS(XML_a, XML_avLst);
mpFS->singleElementNS(XML_a, XML_gdLst);
mpFS->singleElementNS(XML_a, XML_ahLst);
mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b");
mpFS->startElementNS(XML_a, XML_pathLst);
awt::Size aSize = rXShape->getSize();
awt::Point aPos = rXShape->getPosition();
Reference<XPropertySet> xPropertySet(rXShape, UNO_QUERY);
uno::Reference<XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
if (xPropertySetInfo->hasPropertyByName("AnchorPosition"))
{
awt::Point aAnchorPosition;
xPropertySet->getPropertyValue("AnchorPosition") >>= aAnchorPosition;
aPos.X += aAnchorPosition.X;
aPos.Y += aAnchorPosition.Y;
}
// Only closed SdrPathObj can be filled
std::optional<OString> sFill;
if (!bClosed)
sFill = "none"; // for possible values see ST_PathFillMode in OOXML standard
// Put all polygons of rPolyPolygon in the same path element
// to subtract the overlapped areas.
mpFS->startElementNS(XML_a, XML_path, XML_fill, sFill, XML_w, OString::number(aSize.Width),
XML_h, OString::number(aSize.Height));
for (sal_uInt16 i = 0; i < aPolyPolygon.Count(); i++)
{
const tools::Polygon& aPoly = aPolyPolygon[i];
if (aPoly.GetSize() > 0)
{
mpFS->startElementNS(XML_a, XML_moveTo);
mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[0].X() - aPos.X),
XML_y, OString::number(aPoly[0].Y() - aPos.Y));
mpFS->endElementNS(XML_a, XML_moveTo);
}
for (sal_uInt16 j = 1; j < aPoly.GetSize(); j++)
{
PolyFlags flags = aPoly.GetFlags(j);
if (flags == PolyFlags::Control)
{
// a:cubicBezTo can only contain 3 a:pt elements, so we need to make sure of this
if (j + 2 < aPoly.GetSize() && aPoly.GetFlags(j + 1) == PolyFlags::Control
&& aPoly.GetFlags(j + 2) != PolyFlags::Control)
{
mpFS->startElementNS(XML_a, XML_cubicBezTo);
for (sal_uInt8 k = 0; k <= 2; ++k)
{
mpFS->singleElementNS(XML_a, XML_pt, XML_x,
OString::number(aPoly[j + k].X() - aPos.X), XML_y,
OString::number(aPoly[j + k].Y() - aPos.Y));
}
mpFS->endElementNS(XML_a, XML_cubicBezTo);
j += 2;
}
}
else if (flags == PolyFlags::Normal)
{
mpFS->startElementNS(XML_a, XML_lnTo);
mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[j].X() - aPos.X),
XML_y, OString::number(aPoly[j].Y() - aPos.Y));
mpFS->endElementNS(XML_a, XML_lnTo);
}
}
}
if (bClosed)
mpFS->singleElementNS(XML_a, XML_close);
mpFS->endElementNS(XML_a, XML_path);
mpFS->endElementNS(XML_a, XML_pathLst);
mpFS->endElementNS(XML_a, XML_custGeom);
}
void DrawingML::WriteConnectorConnections( 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,
std::u16string_view sRelativeStream,
const Reference< XOutputStream >& xParentRelation,
const char* sContentType,
const char* sRelationshipType,
OUString* pRelationshipId )
{
OUString sRelationshipId;
if (xParentRelation.is())
sRelationshipId = GetFB()->addRelation( xParentRelation, OUString::createFromAscii( sRelationshipType), sRelativeStream );
else
sRelationshipId = GetFB()->addRelation( OUString::createFromAscii( sRelationshipType ), sRelativeStream );
if( pRelationshipId )
*pRelationshipId = sRelationshipId;
sax_fastparser::FSHelperPtr p = GetFB()->openFragmentStreamWithSerializer( sFullStream, OUString::createFromAscii( sContentType ) );
return p;
}
void DrawingML::WriteFill( const Reference< XPropertySet >& xPropSet )
{
if ( !GetProperty( xPropSet, "FillStyle" ) )
return;
FillStyle aFillStyle( FillStyle_NONE );
xPropSet->getPropertyValue( "FillStyle" ) >>= aFillStyle;
// map full transparent background to no fill
if ( aFillStyle == FillStyle_SOLID && GetProperty( xPropSet, "FillTransparence" ) )
{
sal_Int16 nVal = 0;
xPropSet->getPropertyValue( "FillTransparence" ) >>= nVal;
if ( nVal == 100 )
aFillStyle = FillStyle_NONE;
}
if (aFillStyle == FillStyle_SOLID && GetProperty( xPropSet, "FillTransparenceGradient"))
{
awt::Gradient aTransparenceGradient;
mAny >>= aTransparenceGradient;
if (aTransparenceGradient.StartColor == 0xffffff && aTransparenceGradient.EndColor == 0xffffff)
aFillStyle = FillStyle_NONE;
}
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( std::u16string_view sName, const Sequence< PropertyValue >& aEffectProps )
{
if( !aEffectProps.hasElements() )
return;
// assign the proper tag and enable bContainsColor if necessary
sal_Int32 nEffectToken = 0;
bool bContainsColor = false;
if( sName == u"outerShdw" )
{
nEffectToken = FSNS( XML_a, XML_outerShdw );
bContainsColor = true;
}
else if( sName == u"innerShdw" )
{
nEffectToken = FSNS( XML_a, XML_innerShdw );
bContainsColor = true;
}
else if( sName == u"glow" )
{
nEffectToken = FSNS( XML_a, XML_glow );
bContainsColor = true;
}
else if( sName == u"softEdge" )
nEffectToken = FSNS( XML_a, XML_softEdge );
else if( sName == u"reflection" )
nEffectToken = FSNS( XML_a, XML_reflection );
else if( sName == u"blur" )
nEffectToken = FSNS( XML_a, XML_blur );
OUString sSchemeClr;
::Color nRgbClr;
sal_Int32 nAlpha = MAX_PERCENT;
Sequence< PropertyValue > aTransformations;
rtl::Reference<sax_fastparser::FastAttributeList> aOuterShdwAttrList = FastSerializerHelper::createAttrList();
for( const auto& rEffectProp : aEffectProps )
{
if( rEffectProp.Name == "Attribs" )
{
// read tag attributes
uno::Sequence< beans::PropertyValue > aOuterShdwProps;
rEffectProp.Value >>= aOuterShdwProps;
for( const auto& rOuterShdwProp : std::as_const(aOuterShdwProps) )
{
if( rOuterShdwProp.Name == "algn" )
{
OUString sVal;
rOuterShdwProp.Value >>= sVal;
aOuterShdwAttrList->add( XML_algn, sVal );
}
else if( rOuterShdwProp.Name == "blurRad" )
{
sal_Int64 nVal = 0;
rOuterShdwProp.Value >>= nVal;
aOuterShdwAttrList->add( XML_blurRad, OString::number( nVal ).getStr() );
}
else if( rOuterShdwProp.Name == "dir" )
{
sal_Int32 nVal = 0;
rOuterShdwProp.Value >>= nVal;
aOuterShdwAttrList->add( XML_dir, OString::number( nVal ).getStr() );
}
else if( rOuterShdwProp.Name == "dist" )
{
sal_Int32 nVal = 0;
rOuterShdwProp.Value >>= nVal;
aOuterShdwAttrList->add( XML_dist, OString::number( nVal ).getStr() );
}
else if( rOuterShdwProp.Name == "kx" )
{
sal_Int32 nVal = 0;
rOuterShdwProp.Value >>= nVal;
aOuterShdwAttrList->add( XML_kx, OString::number( nVal ).getStr() );
}
else if( rOuterShdwProp.Name == "ky" )
{
sal_Int32 nVal = 0;
rOuterShdwProp.Value >>= nVal;
aOuterShdwAttrList->add( XML_ky, OString::number( nVal ).getStr() );
}
else if( rOuterShdwProp.Name == "rotWithShape" )
{
sal_Int32 nVal = 0;
rOuterShdwProp.Value >>= nVal;
aOuterShdwAttrList->add( XML_rotWithShape, OString::number( nVal ).getStr() );
}
else if( rOuterShdwProp.Name == "sx" )
{
sal_Int32 nVal = 0;
rOuterShdwProp.Value >>= nVal;
aOuterShdwAttrList->add( XML_sx, OString::number( nVal ).getStr() );
}
else if( rOuterShdwProp.Name == "sy" )
{
sal_Int32 nVal = 0;
rOuterShdwProp.Value >>= nVal;
aOuterShdwAttrList->add( XML_sy, OString::number( nVal ).getStr() );
}
else if( rOuterShdwProp.Name == "rad" )
{
sal_Int64 nVal = 0;
rOuterShdwProp.Value >>= nVal;
aOuterShdwAttrList->add( XML_rad, OString::number( nVal ).getStr() );
}
else if( rOuterShdwProp.Name == "endA" )
{
sal_Int32 nVal = 0;
rOuterShdwProp.Value >>= nVal;
aOuterShdwAttrList->add( XML_endA, OString::number( nVal ).getStr() );
}
else if( rOuterShdwProp.Name == "endPos" )
{
sal_Int32 nVal = 0;
rOuterShdwProp.Value >>= nVal;
aOuterShdwAttrList->add( XML_endPos, OString::number( nVal ).getStr() );
}
else if( rOuterShdwProp.Name == "fadeDir" )
{
sal_Int32 nVal = 0;
rOuterShdwProp.Value >>= nVal;
aOuterShdwAttrList->add( XML_fadeDir, OString::number( nVal ).getStr() );
}
else if( rOuterShdwProp.Name == "stA" )
{
sal_Int32 nVal = 0;
rOuterShdwProp.Value >>= nVal;
aOuterShdwAttrList->add( XML_stA, OString::number( nVal ).getStr() );
}
else if( rOuterShdwProp.Name == "stPos" )
{
sal_Int32 nVal = 0;
rOuterShdwProp.Value >>= nVal;
aOuterShdwAttrList->add( XML_stPos, OString::number( nVal ).getStr() );
}
else if( rOuterShdwProp.Name == "grow" )
{
sal_Int32 nVal = 0;
rOuterShdwProp.Value >>= nVal;
aOuterShdwAttrList->add( XML_grow, OString::number( nVal ).getStr() );
}
}
}
else if(rEffectProp.Name == "RgbClr")
{
rEffectProp.Value >>= nRgbClr;
}
else if(rEffectProp.Name == "RgbClrTransparency")
{
sal_Int32 nTransparency;
if (rEffectProp.Value >>= nTransparency)
// Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency );
}
else if(rEffectProp.Name == "SchemeClr")
{
rEffectProp.Value >>= sSchemeClr;
}
else if(rEffectProp.Name == "SchemeClrTransformations")
{
rEffectProp.Value >>= aTransformations;
}
}
if( nEffectToken <= 0 )
return;
mpFS->startElement( nEffectToken, aOuterShdwAttrList );
if( bContainsColor )
{
if( sSchemeClr.isEmpty() )
WriteColor( nRgbClr, nAlpha );
else
WriteColor( sSchemeClr, aTransformations );
}
mpFS->endElement( nEffectToken );
}
static sal_Int32 lcl_CalculateDist(const double dX, const double dY)
{
return static_cast< sal_Int32 >(sqrt(dX*dX + dY*dY) * 360);
}
static sal_Int32 lcl_CalculateDir(const double dX, const double dY)
{
return (static_cast< sal_Int32 >(basegfx::rad2deg<60000>(atan2(dY,dX))) + 21600000) % 21600000;
}
void DrawingML::WriteShapeEffects( const Reference< XPropertySet >& rXPropSet )
{
Sequence< PropertyValue > aGrabBag, aEffects, aOuterShdwProps;
bool bHasInteropGrabBag = rXPropSet->getPropertySetInfo()->hasPropertyByName("InteropGrabBag");
if (bHasInteropGrabBag && GetProperty(rXPropSet, "InteropGrabBag"))
{
mAny >>= aGrabBag;
auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
[](const PropertyValue& rProp) { return rProp.Name == "EffectProperties"; });
if (pProp != std::cend(aGrabBag))
{
pProp->Value >>= aEffects;
auto pEffect = std::find_if(std::cbegin(aEffects), std::cend(aEffects),
[](const PropertyValue& rEffect) { return rEffect.Name == "outerShdw"; });
if (pEffect != std::cend(aEffects))
pEffect->Value >>= aOuterShdwProps;
}
}
// tdf#132201: the order of effects is important. Effects order (CT_EffectList in ECMA-376):
// blur -> fillOverlay -> glow -> innerShdw -> outerShdw -> prstShdw -> reflection -> softEdge
if( !aEffects.hasElements() )
{
bool bHasShadow = false;
if( GetProperty( rXPropSet, "Shadow" ) )
mAny >>= bHasShadow;
bool bHasEffects = bHasShadow;
if (!bHasEffects && GetProperty(rXPropSet, "GlowEffectRadius"))
{
sal_Int32 rad = 0;
mAny >>= rad;
bHasEffects = rad > 0;
}
if (!bHasEffects && GetProperty(rXPropSet, "SoftEdgeRadius"))
{
sal_Int32 rad = 0;
mAny >>= rad;
bHasEffects = rad > 0;
}
if (bHasEffects)
{
mpFS->startElementNS(XML_a, XML_effectLst);
WriteGlowEffect(rXPropSet);
if( bHasShadow )
{
double dX = +0.0, dY = +0.0;
sal_Int32 nBlur =0;
rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX;
rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY;
rXPropSet->getPropertyValue( "ShadowBlur" ) >>= nBlur;
Sequence< PropertyValue > aShadowAttribsGrabBag{
comphelper::makePropertyValue("dist", lcl_CalculateDist(dX, dY)),
comphelper::makePropertyValue("dir", lcl_CalculateDir(dX, dY)),
comphelper::makePropertyValue("blurRad", oox::drawingml::convertHmmToEmu(nBlur)),
comphelper::makePropertyValue("rotWithShape", false) //ooxml default is 'true', so must write it
};
Sequence< PropertyValue > aShadowGrabBag{
comphelper::makePropertyValue("Attribs", aShadowAttribsGrabBag),
comphelper::makePropertyValue("RgbClr", rXPropSet->getPropertyValue( "ShadowColor" )),
comphelper::makePropertyValue("RgbClrTransparency", rXPropSet->getPropertyValue( "ShadowTransparence" ))
};
WriteShapeEffect( u"outerShdw", aShadowGrabBag );
}
WriteSoftEdgeEffect(rXPropSet);
mpFS->endElementNS(XML_a, XML_effectLst);
}
}
else
{
for( auto& rOuterShdwProp : asNonConstRange(aOuterShdwProps) )
{
if( rOuterShdwProp.Name == "Attribs" )
{
Sequence< PropertyValue > aAttribsProps;
rOuterShdwProp.Value >>= aAttribsProps;
double dX = +0.0, dY = +0.0;
sal_Int32 nBlur =0;
rXPropSet->getPropertyValue( "ShadowXDistance" ) >>= dX;
rXPropSet->getPropertyValue( "ShadowYDistance" ) >>= dY;
rXPropSet->getPropertyValue( "ShadowBlur" ) >>= nBlur;
for( auto& rAttribsProp : asNonConstRange(aAttribsProps) )
{
if( rAttribsProp.Name == "dist" )
{
rAttribsProp.Value <<= lcl_CalculateDist(dX, dY);
}
else if( rAttribsProp.Name == "dir" )
{
rAttribsProp.Value <<= lcl_CalculateDir(dX, dY);
}
else if( rAttribsProp.Name == "blurRad" )
{
rAttribsProp.Value <<= oox::drawingml::convertHmmToEmu(nBlur);
}
}
rOuterShdwProp.Value <<= aAttribsProps;
}
else if( rOuterShdwProp.Name == "RgbClr" )
{
rOuterShdwProp.Value = rXPropSet->getPropertyValue( "ShadowColor" );
}
else if( rOuterShdwProp.Name == "RgbClrTransparency" )
{
rOuterShdwProp.Value = rXPropSet->getPropertyValue( "ShadowTransparence" );
}
}
mpFS->startElementNS(XML_a, XML_effectLst);
bool bGlowWritten = false;
for( const auto& rEffect : std::as_const(aEffects) )
{
if (!bGlowWritten
&& (rEffect.Name == "innerShdw" || rEffect.Name == "outerShdw"
|| rEffect.Name == "prstShdw" || rEffect.Name == "reflection"
|| rEffect.Name == "softEdge"))
{
WriteGlowEffect(rXPropSet);
bGlowWritten = true;
}
if( rEffect.Name == "outerShdw" )
{
WriteShapeEffect( rEffect.Name, aOuterShdwProps );
}
else
{
Sequence< PropertyValue > aEffectProps;
rEffect.Value >>= aEffectProps;
WriteShapeEffect( rEffect.Name, aEffectProps );
}
}
if (!bGlowWritten)
WriteGlowEffect(rXPropSet);
WriteSoftEdgeEffect(rXPropSet); // the last
mpFS->endElementNS(XML_a, XML_effectLst);
}
}
void DrawingML::WriteGlowEffect(const Reference< XPropertySet >& rXPropSet)
{
if (!rXPropSet->getPropertySetInfo()->hasPropertyByName("GlowEffectRadius"))
{
return;
}
sal_Int32 nRad = 0;
rXPropSet->getPropertyValue("GlowEffectRadius") >>= nRad;
if (!nRad)
return;
Sequence< PropertyValue > aGlowAttribs{ comphelper::makePropertyValue(
"rad", oox::drawingml::convertHmmToEmu(nRad)) };
Sequence< PropertyValue > aGlowProps{
comphelper::makePropertyValue("Attribs", aGlowAttribs),
comphelper::makePropertyValue("RgbClr", rXPropSet->getPropertyValue("GlowEffectColor")),
comphelper::makePropertyValue("RgbClrTransparency", rXPropSet->getPropertyValue("GlowEffectTransparency"))
};
// TODO other stuff like saturation or luminance
WriteShapeEffect(u"glow", aGlowProps);
}
void DrawingML::WriteSoftEdgeEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet)
{
if (!rXPropSet->getPropertySetInfo()->hasPropertyByName("SoftEdgeRadius"))
{
return;
}
sal_Int32 nRad = 0;
rXPropSet->getPropertyValue("SoftEdgeRadius") >>= nRad;
if (!nRad)
return;
css::uno::Sequence<css::beans::PropertyValue> aAttribs{ comphelper::makePropertyValue(
"rad", oox::drawingml::convertHmmToEmu(nRad)) };
css::uno::Sequence<css::beans::PropertyValue> aProps{ comphelper::makePropertyValue("Attribs",
aAttribs) };
WriteShapeEffect(u"softEdge", aProps);
}
void DrawingML::Write3DEffects( const Reference< XPropertySet >& xPropSet, bool bIsText )
{
// check existence of the grab bag
if( !GetProperty( xPropSet, "InteropGrabBag" ) )
return;
// extract the relevant properties from the grab bag
Sequence< PropertyValue > aGrabBag, aEffectProps, aLightRigProps, aShape3DProps;
mAny >>= aGrabBag;
auto pShapeProp = std::find_if( std::cbegin(aGrabBag), std::cend(aGrabBag),
[bIsText](const PropertyValue& rProp)
{ return rProp.Name == (bIsText ? u"Text3DEffectProperties" : u"3DEffectProperties"); });
if (pShapeProp != std::cend(aGrabBag))
{
Sequence< PropertyValue > a3DEffectProps;
pShapeProp->Value >>= a3DEffectProps;
for( const auto& r3DEffectProp : std::as_const(a3DEffectProps) )
{
if( r3DEffectProp.Name == "Camera" )
r3DEffectProp.Value >>= aEffectProps;
else if( r3DEffectProp.Name == "LightRig" )
r3DEffectProp.Value >>= aLightRigProps;
else if( r3DEffectProp.Name == "Shape3D" )
r3DEffectProp.Value >>= aShape3DProps;
}
}
if( !aEffectProps.hasElements() && !aLightRigProps.hasElements() && !aShape3DProps.hasElements() )
return;
bool bCameraRotationPresent = false;
rtl::Reference<sax_fastparser::FastAttributeList> aCameraAttrList = FastSerializerHelper::createAttrList();
rtl::Reference<sax_fastparser::FastAttributeList> aCameraRotationAttrList = FastSerializerHelper::createAttrList();
for( const auto& rEffectProp : std::as_const(aEffectProps) )
{
if( rEffectProp.Name == "prst" )
{
OUString sVal;
rEffectProp.Value >>= sVal;
aCameraAttrList->add(XML_prst, sVal);
}
else if( rEffectProp.Name == "fov" )
{
float fVal = 0;
rEffectProp.Value >>= fVal;
aCameraAttrList->add( XML_fov, OString::number( fVal * 60000 ).getStr() );
}
else if( rEffectProp.Name == "zoom" )
{
float fVal = 1;
rEffectProp.Value >>= fVal;
aCameraAttrList->add( XML_zoom, OString::number( fVal * 100000 ).getStr() );
}
else if( rEffectProp.Name == "rotLat" ||
rEffectProp.Name == "rotLon" ||
rEffectProp.Name == "rotRev" )
{
sal_Int32 nVal = 0, nToken = XML_none;
rEffectProp.Value >>= nVal;
if( rEffectProp.Name == "rotLat" )
nToken = XML_lat;
else if( rEffectProp.Name == "rotLon" )
nToken = XML_lon;
else if( rEffectProp.Name == "rotRev" )
nToken = XML_rev;
aCameraRotationAttrList->add( nToken, OString::number( nVal ).getStr() );
bCameraRotationPresent = true;
}
}
bool bLightRigRotationPresent = false;
rtl::Reference<sax_fastparser::FastAttributeList> aLightRigAttrList = FastSerializerHelper::createAttrList();
rtl::Reference<sax_fastparser::FastAttributeList> aLightRigRotationAttrList = FastSerializerHelper::createAttrList();
for( const auto& rLightRigProp : std::as_const(aLightRigProps) )
{
if( rLightRigProp.Name == "rig" || rLightRigProp.Name == "dir" )
{
OUString sVal;
sal_Int32 nToken = XML_none;
rLightRigProp.Value >>= sVal;
if( rLightRigProp.Name == "rig" )
nToken = XML_rig;
else if( rLightRigProp.Name == "dir" )
nToken = XML_dir;
aLightRigAttrList->add(nToken, sVal);
}
else if( rLightRigProp.Name == "rotLat" ||
rLightRigProp.Name == "rotLon" ||
rLightRigProp.Name == "rotRev" )
{
sal_Int32 nVal = 0, nToken = XML_none;
rLightRigProp.Value >>= nVal;
if( rLightRigProp.Name == "rotLat" )
nToken = XML_lat;
else if( rLightRigProp.Name == "rotLon" )
nToken = XML_lon;
else if( rLightRigProp.Name == "rotRev" )
nToken = XML_rev;
aLightRigRotationAttrList->add( nToken, OString::number( nVal ).getStr() );
bLightRigRotationPresent = true;
}
}
mpFS->startElementNS(XML_a, XML_scene3d);
if( aEffectProps.hasElements() )
{
mpFS->startElementNS( XML_a, XML_camera, aCameraAttrList );
if( bCameraRotationPresent )
{
mpFS->singleElementNS( XML_a, XML_rot, aCameraRotationAttrList );
}
mpFS->endElementNS( XML_a, XML_camera );
}
else
{
// a:camera with Word default values - Word won't open the document if this is not present
mpFS->singleElementNS(XML_a, XML_camera, XML_prst, "orthographicFront");
}
if( aEffectProps.hasElements() )
{
mpFS->startElementNS( XML_a, XML_lightRig, aLightRigAttrList );
if( bLightRigRotationPresent )
{
mpFS->singleElementNS( XML_a, XML_rot, aLightRigRotationAttrList );
}
mpFS->endElementNS( XML_a, XML_lightRig );
}
else
{
// a:lightRig with Word default values - Word won't open the document if this is not present
mpFS->singleElementNS(XML_a, XML_lightRig, XML_rig, "threePt", XML_dir, "t");
}
mpFS->endElementNS( XML_a, XML_scene3d );
if( !aShape3DProps.hasElements() )
return;
bool bBevelTPresent = false, bBevelBPresent = false;
Sequence< PropertyValue > aExtrusionColorProps, aContourColorProps;
rtl::Reference<sax_fastparser::FastAttributeList> aBevelTAttrList = FastSerializerHelper::createAttrList();
rtl::Reference<sax_fastparser::FastAttributeList> aBevelBAttrList = FastSerializerHelper::createAttrList();
rtl::Reference<sax_fastparser::FastAttributeList> aShape3DAttrList = FastSerializerHelper::createAttrList();
for( const auto& rShape3DProp : std::as_const(aShape3DProps) )
{
if( rShape3DProp.Name == "extrusionH" || rShape3DProp.Name == "contourW" || rShape3DProp.Name == "z" )
{
sal_Int32 nVal = 0, nToken = XML_none;
rShape3DProp.Value >>= nVal;
if( rShape3DProp.Name == "extrusionH" )
nToken = XML_extrusionH;
else if( rShape3DProp.Name == "contourW" )
nToken = XML_contourW;
else if( rShape3DProp.Name == "z" )
nToken = XML_z;
aShape3DAttrList->add( nToken, OString::number( nVal ).getStr() );
}
else if( rShape3DProp.Name == "prstMaterial" )
{
OUString sVal;
rShape3DProp.Value >>= sVal;
aShape3DAttrList->add(XML_prstMaterial, sVal);
}
else if( rShape3DProp.Name == "extrusionClr" )
{
rShape3DProp.Value >>= aExtrusionColorProps;
}
else if( rShape3DProp.Name == "contourClr" )
{
rShape3DProp.Value >>= aContourColorProps;
}
else if( rShape3DProp.Name == "bevelT" || rShape3DProp.Name == "bevelB" )
{
Sequence< PropertyValue > aBevelProps;
rShape3DProp.Value >>= aBevelProps;
if ( !aBevelProps.hasElements() )
continue;
rtl::Reference<sax_fastparser::FastAttributeList> aBevelAttrList;
if( rShape3DProp.Name == "bevelT" )
{
bBevelTPresent = true;
aBevelAttrList = aBevelTAttrList;
}
else
{
bBevelBPresent = true;
aBevelAttrList = aBevelBAttrList;
}
for( const auto& rBevelProp : std::as_const(aBevelProps) )
{
if( rBevelProp.Name == "w" || rBevelProp.Name == "h" )
{
sal_Int32 nVal = 0, nToken = XML_none;
rBevelProp.Value >>= nVal;
if( rBevelProp.Name == "w" )
nToken = XML_w;
else if( rBevelProp.Name == "h" )
nToken = XML_h;
aBevelAttrList->add( nToken, OString::number( nVal ).getStr() );
}
else if( rBevelProp.Name == "prst" )
{
OUString sVal;
rBevelProp.Value >>= sVal;
aBevelAttrList->add(XML_prst, sVal);
}
}
}
}
mpFS->startElementNS( XML_a, XML_sp3d, aShape3DAttrList );
if( bBevelTPresent )
{
mpFS->singleElementNS( XML_a, XML_bevelT, aBevelTAttrList );
}
if( bBevelBPresent )
{
mpFS->singleElementNS( XML_a, XML_bevelB, aBevelBAttrList );
}
if( aExtrusionColorProps.hasElements() )
{
OUString sSchemeClr;
::Color nColor;
sal_Int32 nTransparency(0);
Sequence< PropertyValue > aColorTransformations;
for( const auto& rExtrusionColorProp : std::as_const(aExtrusionColorProps) )
{
if( rExtrusionColorProp.Name == "schemeClr" )
rExtrusionColorProp.Value >>= sSchemeClr;
else if( rExtrusionColorProp.Name == "schemeClrTransformations" )
rExtrusionColorProp.Value >>= aColorTransformations;
else if( rExtrusionColorProp.Name == "rgbClr" )
rExtrusionColorProp.Value >>= nColor;
else if( rExtrusionColorProp.Name == "rgbClrTransparency" )
rExtrusionColorProp.Value >>= nTransparency;
}
mpFS->startElementNS(XML_a, XML_extrusionClr);
if( sSchemeClr.isEmpty() )
WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
else
WriteColor( sSchemeClr, aColorTransformations );
mpFS->endElementNS( XML_a, XML_extrusionClr );
}
if( aContourColorProps.hasElements() )
{
OUString sSchemeClr;
::Color nColor;
sal_Int32 nTransparency(0);
Sequence< PropertyValue > aColorTransformations;
for( const auto& rContourColorProp : std::as_const(aContourColorProps) )
{
if( rContourColorProp.Name == "schemeClr" )
rContourColorProp.Value >>= sSchemeClr;
else if( rContourColorProp.Name == "schemeClrTransformations" )
rContourColorProp.Value >>= aColorTransformations;
else if( rContourColorProp.Name == "rgbClr" )
rContourColorProp.Value >>= nColor;
else if( rContourColorProp.Name == "rgbClrTransparency" )
rContourColorProp.Value >>= nTransparency;
}
mpFS->startElementNS(XML_a, XML_contourClr);
if( sSchemeClr.isEmpty() )
WriteColor( nColor, MAX_PERCENT - ( PER_PERCENT * nTransparency ) );
else
WriteColor( sSchemeClr, aContourColorProps );
mpFS->endElementNS( XML_a, XML_contourClr );
}
mpFS->endElementNS( XML_a, XML_sp3d );
}
void DrawingML::WriteArtisticEffect( const Reference< XPropertySet >& rXPropSet )
{
if( !GetProperty( rXPropSet, "InteropGrabBag" ) )
return;
PropertyValue aEffect;
Sequence< PropertyValue > aGrabBag;
mAny >>= aGrabBag;
auto pProp = std::find_if(std::cbegin(aGrabBag), std::cend(aGrabBag),
[](const PropertyValue& rProp) { return rProp.Name == "ArtisticEffectProperties"; });
if (pProp != std::cend(aGrabBag))
pProp->Value >>= aEffect;
sal_Int32 nEffectToken = ArtisticEffectProperties::getEffectToken( aEffect.Name );
if( nEffectToken == XML_none )
return;
Sequence< PropertyValue > aAttrs;
aEffect.Value >>= aAttrs;
rtl::Reference<sax_fastparser::FastAttributeList> aAttrList = FastSerializerHelper::createAttrList();
OString sRelId;
for( const auto& rAttr : std::as_const(aAttrs) )
{
sal_Int32 nToken = ArtisticEffectProperties::getEffectToken( rAttr.Name );
if( nToken != XML_none )
{
sal_Int32 nVal = 0;
rAttr.Value >>= nVal;
aAttrList->add( nToken, OString::number( nVal ).getStr() );
}
else if( rAttr.Name == "OriginalGraphic" )
{
Sequence< PropertyValue > aGraphic;
rAttr.Value >>= aGraphic;
Sequence< sal_Int8 > aGraphicData;
OUString sGraphicId;
for( const auto& rProp : std::as_const(aGraphic) )
{
if( rProp.Name == "Id" )
rProp.Value >>= sGraphicId;
else if( rProp.Name == "Data" )
rProp.Value >>= aGraphicData;
}
sRelId = WriteWdpPicture( sGraphicId, aGraphicData );
}
}
mpFS->startElementNS(XML_a, XML_extLst);
mpFS->startElementNS(XML_a, XML_ext, XML_uri, "{BEBA8EAE-BF5A-486C-A8C5-ECC9F3942E4B}");
mpFS->startElementNS( XML_a14, XML_imgProps,
FSNS(XML_xmlns, XML_a14), mpFB->getNamespaceURL(OOX_NS(a14)) );
mpFS->startElementNS(XML_a14, XML_imgLayer, FSNS(XML_r, XML_embed), sRelId);
mpFS->startElementNS(XML_a14, XML_imgEffect);
mpFS->singleElementNS( XML_a14, nEffectToken, aAttrList );
mpFS->endElementNS( XML_a14, XML_imgEffect );
mpFS->endElementNS( XML_a14, XML_imgLayer );
mpFS->endElementNS( XML_a14, XML_imgProps );
mpFS->endElementNS( XML_a, XML_ext );
mpFS->endElementNS( XML_a, XML_extLst );
}
OString DrawingML::WriteWdpPicture( const OUString& rFileId, const Sequence< sal_Int8 >& rPictureData )
{
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( "/" + 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
rtl::Reference<sax_fastparser::FastAttributeList> pDocPrAttrList
= sax_fastparser::FastSerializerHelper::createAttrList();
pDocPrAttrList->add(XML_id, OString::number(nDiagramId).getStr());
OString sName = "Diagram" + OString::number(nDiagramId);
pDocPrAttrList->add(XML_name, sName);
if (GetDocumentType() == DOCUMENT_DOCX)
{
mpFS->singleElementNS(XML_wp, XML_docPr, pDocPrAttrList);
mpFS->singleElementNS(XML_wp, XML_cNvGraphicFramePr);
mpFS->startElementNS(XML_a, XML_graphic, FSNS(XML_xmlns, XML_a),
mpFB->getNamespaceURL(OOX_NS(dml)));
}
else
{
mpFS->startElementNS(XML_p, XML_nvGraphicFramePr);
mpFS->singleElementNS(XML_p, XML_cNvPr, pDocPrAttrList);
mpFS->singleElementNS(XML_p, XML_cNvGraphicFramePr);
mpFS->startElementNS(XML_p, XML_nvPr);
mpFS->startElementNS(XML_p, XML_extLst);
// change tracking extension - required in PPTX
mpFS->startElementNS(XML_p, XML_ext, XML_uri, "{D42A27DB-BD31-4B8C-83A1-F6EECF244321}");
mpFS->singleElementNS(XML_p14, XML_modId,
FSNS(XML_xmlns, XML_p14), mpFB->getNamespaceURL(OOX_NS(p14)),
XML_val,
OString::number(comphelper::rng::uniform_uint_distribution(1, SAL_MAX_UINT32)));
mpFS->endElementNS(XML_p, XML_ext);
mpFS->endElementNS(XML_p, XML_extLst);
mpFS->endElementNS(XML_p, XML_nvPr);
mpFS->endElementNS(XML_p, XML_nvGraphicFramePr);
// store size and position of background shape instead of group shape
// as some shapes may be outside
css::uno::Reference<css::drawing::XShapes> xShapes(rXShape, uno::UNO_QUERY);
if (xShapes.is() && xShapes->hasElements())
{
css::uno::Reference<css::drawing::XShape> xShapeBg(xShapes->getByIndex(0),
uno::UNO_QUERY);
awt::Point aPos = xShapeBg->getPosition();
awt::Size aSize = xShapeBg->getSize();
WriteTransformation(
xShapeBg, tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)),
XML_p, false, false, 0, false);
}
mpFS->startElementNS(XML_a, XML_graphic);
}
mpFS->startElementNS(XML_a, XML_graphicData, XML_uri,
"http://schemas.openxmlformats.org/drawingml/2006/diagram");
OUString sRelationCompPrefix = OUString::createFromAscii(GetRelationCompPrefix());
// add data relation
OUString dataFileName = "diagrams/data" + OUString::number(nDiagramId) + ".xml";
OUString dataRelId =
mpFB->addRelation(mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDATA),
OUStringConcatenation(sRelationCompPrefix + dataFileName));
// add layout relation
OUString layoutFileName = "diagrams/layout" + OUString::number(nDiagramId) + ".xml";
OUString layoutRelId = mpFB->addRelation(mpFS->getOutputStream(),
oox::getRelationship(Relationship::DIAGRAMLAYOUT),
OUStringConcatenation(sRelationCompPrefix + layoutFileName));
// add style relation
OUString styleFileName = "diagrams/quickStyle" + OUString::number(nDiagramId) + ".xml";
OUString styleRelId = mpFB->addRelation(mpFS->getOutputStream(),
oox::getRelationship(Relationship::DIAGRAMQUICKSTYLE),
OUStringConcatenation(sRelationCompPrefix + styleFileName));
// add color relation
OUString colorFileName = "diagrams/colors" + OUString::number(nDiagramId) + ".xml";
OUString colorRelId = mpFB->addRelation(mpFS->getOutputStream(),
oox::getRelationship(Relationship::DIAGRAMCOLORS),
OUStringConcatenation(sRelationCompPrefix + colorFileName));
OUString drawingFileName;
if (drawingDom.is())
{
// add drawing relation
drawingFileName = "diagrams/drawing" + OUString::number(nDiagramId) + ".xml";
OUString drawingRelId = mpFB->addRelation(
mpFS->getOutputStream(), oox::getRelationship(Relationship::DIAGRAMDRAWING),
OUStringConcatenation(sRelationCompPrefix + drawingFileName));
// the data dom contains a reference to the drawing relation. We need to update it with the new generated
// relation value before writing the dom to a file
// Get the dsp:damaModelExt node from the dom
uno::Reference<xml::dom::XNodeList> nodeList = dataDom->getElementsByTagNameNS(
"http://schemas.microsoft.com/office/drawing/2008/diagram", "dataModelExt");
// There must be one element only so get it
uno::Reference<xml::dom::XNode> node = nodeList->item(0);
// Get the list of attributes of the node
uno::Reference<xml::dom::XNamedNodeMap> nodeMap = node->getAttributes();
// Get the node with the relId attribute and set its new value
uno::Reference<xml::dom::XNode> relIdNode = nodeMap->getNamedItem("relId");
relIdNode->setNodeValue(drawingRelId);
}
mpFS->singleElementNS(XML_dgm, XML_relIds,
FSNS(XML_xmlns, XML_dgm), mpFB->getNamespaceURL(OOX_NS(dmlDiagram)),
FSNS(XML_xmlns, XML_r), mpFB->getNamespaceURL(OOX_NS(officeRel)),
FSNS(XML_r, XML_dm), dataRelId, FSNS(XML_r, XML_lo), layoutRelId,
FSNS(XML_r, XML_qs), styleRelId, FSNS(XML_r, XML_cs), colorRelId);
mpFS->endElementNS(XML_a, XML_graphicData);
mpFS->endElementNS(XML_a, XML_graphic);
uno::Reference<xml::sax::XSAXSerializable> serializer;
uno::Reference<xml::sax::XWriter> writer
= xml::sax::Writer::create(comphelper::getProcessComponentContext());
OUString sDir = OUString::createFromAscii(GetComponentDir());
// write data file
serializer.set(dataDom, uno::UNO_QUERY);
uno::Reference<io::XOutputStream> xDataOutputStream = mpFB->openFragmentStream(
sDir + "/" + dataFileName,
"application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml");
writer->setOutputStream(xDataOutputStream);
serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
uno::Sequence<beans::StringPair>());
// write the associated Images and rels for data file
writeDiagramRels(xDataRelSeq, xDataOutputStream, u"OOXDiagramDataRels", nDiagramId);
// write layout file
serializer.set(layoutDom, uno::UNO_QUERY);
writer->setOutputStream(mpFB->openFragmentStream(
sDir + "/" + layoutFileName,
"application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml"));
serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
uno::Sequence<beans::StringPair>());
// write style file
serializer.set(styleDom, uno::UNO_QUERY);
writer->setOutputStream(mpFB->openFragmentStream(
sDir + "/" + styleFileName,
"application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml"));
serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
uno::Sequence<beans::StringPair>());
// write color file
serializer.set(colorDom, uno::UNO_QUERY);
writer->setOutputStream(mpFB->openFragmentStream(
sDir + "/" + colorFileName,
"application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml"));
serializer->serialize(uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
uno::Sequence<beans::StringPair>());
// write drawing file
if (!drawingDom.is())
return;
serializer.set(drawingDom, uno::UNO_QUERY);
uno::Reference<io::XOutputStream> xDrawingOutputStream = mpFB->openFragmentStream(
sDir + "/" + drawingFileName, "application/vnd.ms-office.drawingml.diagramDrawing+xml");
writer->setOutputStream(xDrawingOutputStream);
serializer->serialize(
uno::Reference<xml::sax::XDocumentHandler>(writer, uno::UNO_QUERY_THROW),
uno::Sequence<beans::StringPair>());
// write the associated Images and rels for drawing file
uno::Sequence<uno::Sequence<uno::Any>> xDrawingRelSeq;
diagramDrawing[1] >>= xDrawingRelSeq;
writeDiagramRels(xDrawingRelSeq, xDrawingOutputStream, u"OOXDiagramDrawingRels", nDiagramId);
}
void DrawingML::writeDiagramRels(const uno::Sequence<uno::Sequence<uno::Any>>& xRelSeq,
const uno::Reference<io::XOutputStream>& xOutStream,
std::u16string_view sGrabBagProperyName, int nDiagramId)
{
// add image relationships of OOXData, OOXDiagram
OUString sType(oox::getRelationship(Relationship::IMAGE));
uno::Reference<xml::sax::XWriter> xWriter
= xml::sax::Writer::create(comphelper::getProcessComponentContext());
xWriter->setOutputStream(xOutStream);
// retrieve the relationships from Sequence
for (sal_Int32 j = 0; j < xRelSeq.getLength(); j++)
{
// diagramDataRelTuple[0] => RID,
// diagramDataRelTuple[1] => xInputStream
// diagramDataRelTuple[2] => extension
uno::Sequence<uno::Any> diagramDataRelTuple = xRelSeq[j];
OUString sRelId;
OUString sExtension;
diagramDataRelTuple[0] >>= sRelId;
diagramDataRelTuple[2] >>= sExtension;
OUString sContentType;
if (sExtension.equalsIgnoreAsciiCase(".WMF"))
sContentType = "image/x-wmf";
else
sContentType = OUString::Concat("image/") + sExtension.subView(1);
sRelId = sRelId.copy(3);
StreamDataSequence dataSeq;
diagramDataRelTuple[1] >>= dataSeq;
uno::Reference<io::XInputStream> dataImagebin(
new ::comphelper::SequenceInputStream(dataSeq));
//nDiagramId is used to make the name unique irrespective of the number of smart arts.
OUString sFragment = OUString::Concat("media/") + sGrabBagProperyName
+ OUString::number(nDiagramId) + "_"
+ OUString::number(j) + sExtension;
PropertySet aProps(xOutStream);
aProps.setAnyProperty(PROP_RelId, uno::makeAny(sRelId.toInt32()));
mpFB->addRelation(xOutStream, sType, OUStringConcatenation("../" + sFragment));
OUString sDir = OUString::createFromAscii(GetComponentDir());
uno::Reference<io::XOutputStream> xBinOutStream
= mpFB->openFragmentStream(sDir + "/" + sFragment, sContentType);
try
{
comphelper::OStorageHelper::CopyInputToOutput(dataImagebin, xBinOutStream);
}
catch (const uno::Exception&)
{
TOOLS_WARN_EXCEPTION("oox.drawingml", "DrawingML::writeDiagramRels Failed to copy grabbaged Image");
}
dataImagebin->closeInput();
}
}
void DrawingML::WriteFromTo(const uno::Reference<css::drawing::XShape>& rXShape, const awt::Size& aPageSize,
const FSHelperPtr& pDrawing)
{
awt::Point aTopLeft = rXShape->getPosition();
awt::Size aSize = rXShape->getSize();
SdrObject* pObj = SdrObject::getSdrObjectFromXShape(rXShape);
if (pObj)
{
Degree100 nRotation = pObj->GetRotateAngle();
if (nRotation)
{
sal_Int16 nHalfWidth = aSize.Width / 2;
sal_Int16 nHalfHeight = aSize.Height / 2;
// aTopLeft needs correction for rotated customshapes
if (pObj->GetObjIdentifier() == SdrObjKind::CustomShape)
{
// Center of bounding box of the rotated shape
const auto aSnapRectCenter(pObj->GetSnapRect().Center());
aTopLeft.X = aSnapRectCenter.X() - nHalfWidth;
aTopLeft.Y = aSnapRectCenter.Y() - nHalfHeight;
}
// MSO changes the anchor positions at these angles and that does an extra 90 degrees
// rotation on our shapes, so we output it in such position that MSO
// can draw this shape correctly.
if ((nRotation >= 4500_deg100 && nRotation < 13500_deg100) || (nRotation >= 22500_deg100 && nRotation < 31500_deg100))
{
aTopLeft.X = aTopLeft.X - nHalfHeight + nHalfWidth;
aTopLeft.Y = aTopLeft.Y - nHalfWidth + nHalfHeight;
std::swap(aSize.Width, aSize.Height);
}
}
}
tools::Rectangle aLocation(aTopLeft.X, aTopLeft.Y, aTopLeft.X + aSize.Width, aTopLeft.Y + aSize.Height);
double nXpos = static_cast<double>(aLocation.TopLeft().getX()) / static_cast<double>(aPageSize.Width);
double nYpos = static_cast<double>(aLocation.TopLeft().getY()) / static_cast<double>(aPageSize.Height);
pDrawing->startElement(FSNS(XML_cdr, XML_from));
pDrawing->startElement(FSNS(XML_cdr, XML_x));
pDrawing->write(nXpos);
pDrawing->endElement(FSNS(XML_cdr, XML_x));
pDrawing->startElement(FSNS(XML_cdr, XML_y));
pDrawing->write(nYpos);
pDrawing->endElement(FSNS(XML_cdr, XML_y));
pDrawing->endElement(FSNS(XML_cdr, XML_from));
nXpos = static_cast<double>(aLocation.BottomRight().getX()) / static_cast<double>(aPageSize.Width);
nYpos = static_cast<double>(aLocation.BottomRight().getY()) / static_cast<double>(aPageSize.Height);
pDrawing->startElement(FSNS(XML_cdr, XML_to));
pDrawing->startElement(FSNS(XML_cdr, XML_x));
pDrawing->write(nXpos);
pDrawing->endElement(FSNS(XML_cdr, XML_x));
pDrawing->startElement(FSNS(XML_cdr, XML_y));
pDrawing->write(nYpos);
pDrawing->endElement(FSNS(XML_cdr, XML_y));
pDrawing->endElement(FSNS(XML_cdr, XML_to));
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */