3817965ded
and use it where possible Change-Id: I3efc7a642f73661ce606c917c0323ba9948521c6 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/134265 Tested-by: Jenkins Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
5891 lines
228 KiB
C++
5891 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 <o3tl/string_view.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 (!WriteFillColor(rXPropSet))
|
|
{
|
|
WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha);
|
|
}
|
|
}
|
|
else if ( !sColorFillScheme.isEmpty() )
|
|
{
|
|
// the shape had a scheme color and the user didn't change it
|
|
WriteSolidFill( sColorFillScheme, aTransformations, nAlpha );
|
|
}
|
|
else
|
|
{
|
|
// the shape had a custom color and the user didn't change it
|
|
// tdf#124013
|
|
WriteSolidFill( ::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha );
|
|
}
|
|
}
|
|
|
|
bool DrawingML::WriteFillColor(const uno::Reference<beans::XPropertySet>& xPropertySet)
|
|
{
|
|
if (!xPropertySet->getPropertySetInfo()->hasPropertyByName("FillColorTheme"))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
sal_Int32 nFillColorTheme = -1;
|
|
xPropertySet->getPropertyValue("FillColorTheme") >>= nFillColorTheme;
|
|
if (nFillColorTheme < 0 || nFillColorTheme > 11)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const char* pColorName = g_aPredefinedClrNames[nFillColorTheme];
|
|
|
|
mpFS->startElementNS(XML_a, XML_solidFill);
|
|
mpFS->startElementNS(XML_a, XML_schemeClr, XML_val, pColorName);
|
|
|
|
sal_Int32 nFillColorLumMod{};
|
|
xPropertySet->getPropertyValue("FillColorLumMod") >>= nFillColorLumMod;
|
|
if (nFillColorLumMod != 10000)
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_lumMod, XML_val, OString::number(nFillColorLumMod * 10));
|
|
}
|
|
|
|
sal_Int32 nFillColorLumOff{};
|
|
xPropertySet->getPropertyValue("FillColorLumOff") >>= nFillColorLumOff;
|
|
if (nFillColorLumOff != 0)
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_lumOff, XML_val, OString::number(nFillColorLumOff * 10));
|
|
}
|
|
|
|
mpFS->endElementNS(XML_a, XML_schemeClr);
|
|
mpFS->endElementNS(XML_a, XML_solidFill);
|
|
|
|
return true;
|
|
}
|
|
|
|
void DrawingML::WriteGradientStop(sal_uInt16 nStop, ::Color nColor, sal_Int32 nAlpha)
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nStop * 1000));
|
|
WriteColor(nColor, nAlpha);
|
|
mpFS->endElementNS( XML_a, XML_gs );
|
|
}
|
|
|
|
::Color DrawingML::ColorWithIntensity( sal_uInt32 nColor, sal_uInt32 nIntensity )
|
|
{
|
|
return ::Color(ColorTransparency, ( ( ( nColor & 0xff ) * nIntensity ) / 100 )
|
|
| ( ( ( ( ( nColor & 0xff00 ) >> 8 ) * nIntensity ) / 100 ) << 8 )
|
|
| ( ( ( ( ( nColor & 0xff0000 ) >> 8 ) * nIntensity ) / 100 ) << 8 ));
|
|
}
|
|
|
|
bool DrawingML::EqualGradients( awt::Gradient aGradient1, awt::Gradient aGradient2 )
|
|
{
|
|
return aGradient1.Style == aGradient2.Style &&
|
|
aGradient1.StartColor == aGradient2.StartColor &&
|
|
aGradient1.EndColor == aGradient2.EndColor &&
|
|
aGradient1.Angle == aGradient2.Angle &&
|
|
aGradient1.Border == aGradient2.Border &&
|
|
aGradient1.XOffset == aGradient2.XOffset &&
|
|
aGradient1.YOffset == aGradient2.YOffset &&
|
|
aGradient1.StartIntensity == aGradient2.StartIntensity &&
|
|
aGradient1.EndIntensity == aGradient2.EndIntensity &&
|
|
aGradient1.StepCount == aGradient2.StepCount;
|
|
}
|
|
|
|
void DrawingML::WriteGradientFill( const Reference< XPropertySet >& rXPropSet )
|
|
{
|
|
awt::Gradient aGradient;
|
|
if (!GetProperty(rXPropSet, "FillGradient"))
|
|
return;
|
|
|
|
aGradient = *o3tl::doAccess<awt::Gradient>(mAny);
|
|
|
|
// get InteropGrabBag and search the relevant attributes
|
|
awt::Gradient aOriginalGradient;
|
|
Sequence< PropertyValue > aGradientStops;
|
|
if ( GetProperty( rXPropSet, "InteropGrabBag" ) )
|
|
{
|
|
Sequence< PropertyValue > aGrabBag;
|
|
mAny >>= aGrabBag;
|
|
for( const auto& rProp : std::as_const(aGrabBag) )
|
|
if( rProp.Name == "GradFillDefinition" )
|
|
rProp.Value >>= aGradientStops;
|
|
else if( rProp.Name == "OriginalGradFill" )
|
|
rProp.Value >>= aOriginalGradient;
|
|
}
|
|
|
|
// check if an ooxml gradient had been imported and if the user has modified it
|
|
// Gradient grab-bag depends on theme grab-bag, which is implemented
|
|
// only for DOCX.
|
|
if( EqualGradients( aOriginalGradient, aGradient ) && GetDocumentType() == DOCUMENT_DOCX)
|
|
{
|
|
// If we have no gradient stops that means original gradient were defined by a theme.
|
|
if( aGradientStops.hasElements() )
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
|
|
WriteGrabBagGradientFill(aGradientStops, aGradient);
|
|
mpFS->endElementNS( XML_a, XML_gradFill );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
awt::Gradient aTransparenceGradient;
|
|
mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
|
|
OUString sFillTransparenceGradientName;
|
|
if (GetProperty(rXPropSet, "FillTransparenceGradientName")
|
|
&& (mAny >>= sFillTransparenceGradientName)
|
|
&& !sFillTransparenceGradientName.isEmpty())
|
|
{
|
|
if (GetProperty(rXPropSet, "FillTransparenceGradient"))
|
|
aTransparenceGradient = *o3tl::doAccess<awt::Gradient>(mAny);
|
|
}
|
|
else if (GetProperty(rXPropSet, "FillTransparence"))
|
|
{
|
|
// currently only StartColor and EndColor are evaluated in WriteGradientFill()
|
|
sal_Int32 nTransparency = 0;
|
|
mAny >>= nTransparency;
|
|
// convert percent to gray color
|
|
nTransparency = nTransparency * 255/100;
|
|
const sal_Int32 aGrayColor = static_cast<sal_Int32>( nTransparency | nTransparency << 8 | nTransparency << 16 );
|
|
aTransparenceGradient.StartColor = aGrayColor;
|
|
aTransparenceGradient.EndColor = aGrayColor;
|
|
}
|
|
WriteGradientFill(aGradient, aTransparenceGradient);
|
|
mpFS->endElementNS(XML_a, XML_gradFill);
|
|
}
|
|
}
|
|
|
|
void DrawingML::WriteGrabBagGradientFill( const Sequence< PropertyValue >& aGradientStops, awt::Gradient rGradient )
|
|
{
|
|
// write back the original gradient
|
|
mpFS->startElementNS(XML_a, XML_gsLst);
|
|
|
|
// get original stops and write them
|
|
for( const auto& rGradientStop : aGradientStops )
|
|
{
|
|
Sequence< PropertyValue > aGradientStop;
|
|
rGradientStop.Value >>= aGradientStop;
|
|
|
|
// get values
|
|
OUString sSchemeClr;
|
|
double nPos = 0;
|
|
sal_Int16 nTransparency = 0;
|
|
::Color nRgbClr;
|
|
Sequence< PropertyValue > aTransformations;
|
|
for( const auto& rProp : std::as_const(aGradientStop) )
|
|
{
|
|
if( rProp.Name == "SchemeClr" )
|
|
rProp.Value >>= sSchemeClr;
|
|
else if( rProp.Name == "RgbClr" )
|
|
rProp.Value >>= nRgbClr;
|
|
else if( rProp.Name == "Pos" )
|
|
rProp.Value >>= nPos;
|
|
else if( rProp.Name == "Transparency" )
|
|
rProp.Value >>= nTransparency;
|
|
else if( rProp.Name == "Transformations" )
|
|
rProp.Value >>= aTransformations;
|
|
}
|
|
// write stop
|
|
mpFS->startElementNS(XML_a, XML_gs, XML_pos, OString::number(nPos * 100000.0).getStr());
|
|
if( sSchemeClr.isEmpty() )
|
|
{
|
|
// Calculate alpha value (see oox/source/drawingml/color.cxx : getTransparency())
|
|
sal_Int32 nAlpha = MAX_PERCENT - ( PER_PERCENT * nTransparency );
|
|
WriteColor( nRgbClr, nAlpha );
|
|
}
|
|
else
|
|
{
|
|
WriteColor( sSchemeClr, aTransformations );
|
|
}
|
|
mpFS->endElementNS( XML_a, XML_gs );
|
|
}
|
|
mpFS->endElementNS( XML_a, XML_gsLst );
|
|
|
|
switch (rGradient.Style)
|
|
{
|
|
default:
|
|
mpFS->singleElementNS(
|
|
XML_a, XML_lin, XML_ang,
|
|
OString::number(((3600 - rGradient.Angle + 900) * 6000) % 21600000));
|
|
break;
|
|
case awt::GradientStyle_RADIAL:
|
|
WriteGradientPath(rGradient, mpFS, true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void DrawingML::WriteGradientFill(awt::Gradient rGradient, awt::Gradient rTransparenceGradient,
|
|
const uno::Reference<beans::XPropertySet>& rXPropSet)
|
|
{
|
|
sal_Int32 nStartAlpha;
|
|
sal_Int32 nEndAlpha;
|
|
if( rXPropSet.is() && GetProperty(rXPropSet, "FillTransparence") )
|
|
{
|
|
sal_Int32 nTransparency = 0;
|
|
mAny >>= nTransparency;
|
|
nStartAlpha = nEndAlpha = (MAX_PERCENT - (PER_PERCENT * nTransparency));
|
|
}
|
|
else
|
|
{
|
|
nStartAlpha = GetAlphaFromTransparenceGradient(rTransparenceGradient, true);
|
|
nEndAlpha = GetAlphaFromTransparenceGradient(rTransparenceGradient, false);
|
|
}
|
|
switch( rGradient.Style )
|
|
{
|
|
default:
|
|
case awt::GradientStyle_LINEAR:
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_gsLst);
|
|
WriteGradientStop(rGradient.Border, ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
|
|
nStartAlpha);
|
|
WriteGradientStop(100, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
|
|
nEndAlpha);
|
|
mpFS->endElementNS( XML_a, XML_gsLst );
|
|
mpFS->singleElementNS(
|
|
XML_a, XML_lin, XML_ang,
|
|
OString::number(((3600 - rGradient.Angle + 900) * 6000) % 21600000));
|
|
break;
|
|
}
|
|
|
|
case awt::GradientStyle_AXIAL:
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_gsLst);
|
|
WriteGradientStop(0, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
|
|
nEndAlpha);
|
|
if (rGradient.Border > 0 && rGradient.Border < 100)
|
|
{
|
|
WriteGradientStop(rGradient.Border/2,
|
|
ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
|
|
nEndAlpha);
|
|
}
|
|
WriteGradientStop(50, ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
|
|
nStartAlpha);
|
|
if (rGradient.Border > 0 && rGradient.Border < 100)
|
|
{
|
|
WriteGradientStop(100 - rGradient.Border/2,
|
|
ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
|
|
nEndAlpha);
|
|
}
|
|
WriteGradientStop(100, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
|
|
nEndAlpha);
|
|
mpFS->endElementNS(XML_a, XML_gsLst);
|
|
mpFS->singleElementNS(
|
|
XML_a, XML_lin, XML_ang,
|
|
OString::number(((3600 - rGradient.Angle + 900) * 6000) % 21600000));
|
|
break;
|
|
}
|
|
|
|
case awt::GradientStyle_RADIAL:
|
|
case awt::GradientStyle_ELLIPTICAL:
|
|
case awt::GradientStyle_RECT:
|
|
case awt::GradientStyle_SQUARE:
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_gsLst);
|
|
WriteGradientStop(0, ColorWithIntensity(rGradient.EndColor, rGradient.EndIntensity),
|
|
nEndAlpha);
|
|
if (rGradient.Border > 0 && rGradient.Border < 100)
|
|
{
|
|
// Map border to an additional gradient stop, which has the
|
|
// same color as the final stop.
|
|
WriteGradientStop(100 - rGradient.Border,
|
|
ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
|
|
nStartAlpha);
|
|
}
|
|
WriteGradientStop(100,
|
|
ColorWithIntensity(rGradient.StartColor, rGradient.StartIntensity),
|
|
nStartAlpha);
|
|
mpFS->endElementNS(XML_a, XML_gsLst);
|
|
|
|
WriteGradientPath(rGradient, mpFS, rGradient.Style == awt::GradientStyle_RADIAL || rGradient.Style == awt::GradientStyle_ELLIPTICAL);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawingML::WriteLineArrow( const Reference< XPropertySet >& rXPropSet, bool bLineStart )
|
|
{
|
|
ESCHER_LineEnd eLineEnd;
|
|
sal_Int32 nArrowLength;
|
|
sal_Int32 nArrowWidth;
|
|
|
|
if ( !EscherPropertyContainer::GetLineArrow( bLineStart, rXPropSet, eLineEnd, nArrowLength, nArrowWidth ) )
|
|
return;
|
|
|
|
const char* len;
|
|
const char* type;
|
|
const char* width;
|
|
|
|
switch( nArrowLength )
|
|
{
|
|
case ESCHER_LineShortArrow:
|
|
len = "sm";
|
|
break;
|
|
default:
|
|
case ESCHER_LineMediumLenArrow:
|
|
len = "med";
|
|
break;
|
|
case ESCHER_LineLongArrow:
|
|
len = "lg";
|
|
break;
|
|
}
|
|
|
|
switch( eLineEnd )
|
|
{
|
|
default:
|
|
case ESCHER_LineNoEnd:
|
|
type = "none";
|
|
break;
|
|
case ESCHER_LineArrowEnd:
|
|
type = "triangle";
|
|
break;
|
|
case ESCHER_LineArrowStealthEnd:
|
|
type = "stealth";
|
|
break;
|
|
case ESCHER_LineArrowDiamondEnd:
|
|
type = "diamond";
|
|
break;
|
|
case ESCHER_LineArrowOvalEnd:
|
|
type = "oval";
|
|
break;
|
|
case ESCHER_LineArrowOpenEnd:
|
|
type = "arrow";
|
|
break;
|
|
}
|
|
|
|
switch( nArrowWidth )
|
|
{
|
|
case ESCHER_LineNarrowArrow:
|
|
width = "sm";
|
|
break;
|
|
default:
|
|
case ESCHER_LineMediumWidthArrow:
|
|
width = "med";
|
|
break;
|
|
case ESCHER_LineWideArrow:
|
|
width = "lg";
|
|
break;
|
|
}
|
|
|
|
mpFS->singleElementNS( XML_a, bLineStart ? XML_headEnd : XML_tailEnd,
|
|
XML_len, len,
|
|
XML_type, type,
|
|
XML_w, width );
|
|
}
|
|
|
|
void DrawingML::WriteOutline( const Reference<XPropertySet>& rXPropSet, Reference< frame::XModel > const & xModel )
|
|
{
|
|
drawing::LineStyle aLineStyle( drawing::LineStyle_NONE );
|
|
if (GetProperty(rXPropSet, "LineStyle"))
|
|
mAny >>= aLineStyle;
|
|
|
|
const LineCap aLineCap = GetProperty(rXPropSet, "LineCap") ? mAny.get<drawing::LineCap>() : LineCap_BUTT;
|
|
|
|
sal_uInt32 nLineWidth = 0;
|
|
sal_uInt32 nEmuLineWidth = 0;
|
|
::Color nColor;
|
|
sal_Int32 nColorAlpha = MAX_PERCENT;
|
|
bool bColorSet = false;
|
|
const char* cap = nullptr;
|
|
drawing::LineDash aLineDash;
|
|
bool bDashSet = false;
|
|
bool bNoFill = false;
|
|
|
|
|
|
// get InteropGrabBag and search the relevant attributes
|
|
OUString sColorFillScheme;
|
|
::Color aResolvedColorFillScheme;
|
|
|
|
::Color nOriginalColor;
|
|
::Color nStyleColor;
|
|
sal_uInt32 nStyleLineWidth = 0;
|
|
|
|
Sequence<PropertyValue> aStyleProperties;
|
|
Sequence<PropertyValue> aTransformations;
|
|
|
|
drawing::LineStyle aStyleLineStyle(drawing::LineStyle_NONE);
|
|
drawing::LineJoint aStyleLineJoint(drawing::LineJoint_NONE);
|
|
|
|
if (GetProperty(rXPropSet, "InteropGrabBag"))
|
|
{
|
|
Sequence<PropertyValue> aGrabBag;
|
|
mAny >>= aGrabBag;
|
|
|
|
for (const auto& rProp : std::as_const(aGrabBag))
|
|
{
|
|
if( rProp.Name == "SpPrLnSolidFillSchemeClr" )
|
|
rProp.Value >>= sColorFillScheme;
|
|
if( rProp.Name == "SpPrLnSolidFillResolvedSchemeClr" )
|
|
rProp.Value >>= aResolvedColorFillScheme;
|
|
else if( rProp.Name == "OriginalLnSolidFillClr" )
|
|
rProp.Value >>= nOriginalColor;
|
|
else if( rProp.Name == "StyleLnRef" )
|
|
rProp.Value >>= aStyleProperties;
|
|
else if( rProp.Name == "SpPrLnSolidFillSchemeClrTransformations" )
|
|
rProp.Value >>= aTransformations;
|
|
else if( rProp.Name == "EmuLineWidth" )
|
|
rProp.Value >>= nEmuLineWidth;
|
|
}
|
|
for (const auto& rStyleProp : std::as_const(aStyleProperties))
|
|
{
|
|
if( rStyleProp.Name == "Color" )
|
|
rStyleProp.Value >>= nStyleColor;
|
|
else if( rStyleProp.Name == "LineStyle" )
|
|
rStyleProp.Value >>= aStyleLineStyle;
|
|
else if( rStyleProp.Name == "LineJoint" )
|
|
rStyleProp.Value >>= aStyleLineJoint;
|
|
else if( rStyleProp.Name == "LineWidth" )
|
|
rStyleProp.Value >>= nStyleLineWidth;
|
|
}
|
|
}
|
|
|
|
if (GetProperty(rXPropSet, "LineWidth"))
|
|
mAny >>= nLineWidth;
|
|
|
|
switch (aLineStyle)
|
|
{
|
|
case drawing::LineStyle_NONE:
|
|
bNoFill = true;
|
|
break;
|
|
case drawing::LineStyle_DASH:
|
|
if (GetProperty(rXPropSet, "LineDash"))
|
|
{
|
|
aLineDash = mAny.get<drawing::LineDash>();
|
|
//this query is good for shapes, but in the case of charts it returns 0 values
|
|
if (aLineDash.Dots == 0 && aLineDash.DotLen == 0 && aLineDash.Dashes == 0 && aLineDash.DashLen == 0 && aLineDash.Distance == 0) {
|
|
OUString aLineDashName;
|
|
if (GetProperty(rXPropSet, "LineDashName"))
|
|
mAny >>= aLineDashName;
|
|
if (!aLineDashName.isEmpty() && xModel) {
|
|
css::uno::Any aAny = getLineDash(xModel, aLineDashName);
|
|
aAny >>= aLineDash;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//export the linestyle of chart wall (plot area) and chart page
|
|
OUString aLineDashName;
|
|
if (GetProperty(rXPropSet, "LineDashName"))
|
|
mAny >>= aLineDashName;
|
|
if (!aLineDashName.isEmpty() && xModel) {
|
|
css::uno::Any aAny = getLineDash(xModel, aLineDashName);
|
|
aAny >>= aLineDash;
|
|
}
|
|
}
|
|
bDashSet = true;
|
|
if (aLineDash.Style == DashStyle_ROUND || aLineDash.Style == DashStyle_ROUNDRELATIVE)
|
|
{
|
|
cap = "rnd";
|
|
}
|
|
|
|
SAL_INFO("oox.shape", "dash dots: " << aLineDash.Dots << " dashes: " << aLineDash.Dashes
|
|
<< " dotlen: " << aLineDash.DotLen << " dashlen: " << aLineDash.DashLen << " distance: " << aLineDash.Distance);
|
|
|
|
[[fallthrough]];
|
|
case drawing::LineStyle_SOLID:
|
|
default:
|
|
if (GetProperty(rXPropSet, "LineColor"))
|
|
{
|
|
nColor = ::Color(ColorTransparency, mAny.get<sal_uInt32>() & 0xffffff);
|
|
bColorSet = true;
|
|
}
|
|
if (GetProperty(rXPropSet, "LineTransparence"))
|
|
{
|
|
nColorAlpha = MAX_PERCENT - (mAny.get<sal_Int16>() * PER_PERCENT);
|
|
}
|
|
if (aLineCap == LineCap_ROUND)
|
|
cap = "rnd";
|
|
else if (aLineCap == LineCap_SQUARE)
|
|
cap = "sq";
|
|
break;
|
|
}
|
|
|
|
// if the line-width was not modified after importing then the original EMU value will be exported to avoid unexpected conversion (rounding) error
|
|
if (nEmuLineWidth == 0 || static_cast<sal_uInt32>(oox::drawingml::convertEmuToHmm(nEmuLineWidth)) != nLineWidth)
|
|
nEmuLineWidth = oox::drawingml::convertHmmToEmu(nLineWidth);
|
|
mpFS->startElementNS( XML_a, XML_ln,
|
|
XML_cap, cap,
|
|
XML_w, sax_fastparser::UseIf(OString::number(nEmuLineWidth),
|
|
nLineWidth == 0 || (nLineWidth > 1 && nStyleLineWidth != nLineWidth)) );
|
|
|
|
if( bColorSet )
|
|
{
|
|
if( nColor != nOriginalColor )
|
|
{
|
|
// the user has set a different color for the line
|
|
WriteSolidFill( nColor, nColorAlpha );
|
|
}
|
|
else if( !sColorFillScheme.isEmpty() )
|
|
{
|
|
// the line had a scheme color and the user didn't change it
|
|
WriteSolidFill( aResolvedColorFillScheme, aTransformations );
|
|
}
|
|
else
|
|
{
|
|
WriteSolidFill( nColor, nColorAlpha );
|
|
}
|
|
}
|
|
|
|
if( bDashSet && aStyleLineStyle != drawing::LineStyle_DASH )
|
|
{
|
|
// Try to detect if it might come from ms preset line style import.
|
|
// MS Office styles are always relative, both binary and OOXML.
|
|
// "dot" is always the first dash and "dash" the second one. All OOXML presets linestyles
|
|
// start with the longer one. Definitions are in OOXML part 1, 20.1.10.49
|
|
// The tests are strict, for to not catch styles from standard.sod (as of Aug 2019).
|
|
bool bIsConverted = false;
|
|
|
|
bool bIsRelative(aLineDash.Style == DashStyle_RECTRELATIVE || aLineDash.Style == DashStyle_ROUNDRELATIVE);
|
|
if ( bIsRelative && aLineDash.Dots == 1)
|
|
{ // The length were tweaked on import in case of prstDash. Revert it here.
|
|
sal_uInt32 nDotLen = aLineDash.DotLen;
|
|
sal_uInt32 nDashLen = aLineDash.DashLen;
|
|
sal_uInt32 nDistance = aLineDash.Distance;
|
|
if (aLineCap != LineCap_BUTT && nDistance >= 99)
|
|
{
|
|
nDistance -= 99;
|
|
nDotLen += 99;
|
|
if (nDashLen > 0)
|
|
nDashLen += 99;
|
|
}
|
|
// LO uses length 0 for 100%, if the attribute is missing in ODF.
|
|
// Other applications might write 100%. Make is unique for the conditions.
|
|
if (nDotLen == 0)
|
|
nDotLen = 100;
|
|
if (nDashLen == 0 && aLineDash.Dashes > 0)
|
|
nDashLen = 100;
|
|
bIsConverted = true;
|
|
if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dot");
|
|
}
|
|
else if (nDotLen == 400 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dash");
|
|
}
|
|
else if (nDotLen == 400 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dashDot");
|
|
}
|
|
else if (nDotLen == 800 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDash");
|
|
}
|
|
else if (nDotLen == 800 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDot");
|
|
}
|
|
else if (nDotLen == 800 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 300)
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDotDot");
|
|
}
|
|
else if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDot");
|
|
}
|
|
else if (nDotLen == 300 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDash");
|
|
}
|
|
else if (nDotLen == 300 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 100)
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDot");
|
|
}
|
|
else if (nDotLen == 300 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 100)
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDotDot");
|
|
}
|
|
else
|
|
bIsConverted = false;
|
|
}
|
|
// Do not map our own line styles to OOXML prstDash values, because custDash gives better results.
|
|
if (!bIsConverted)
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_custDash);
|
|
// In case of hairline we would need the current pixel size. Instead use a reasonable
|
|
// ersatz for it. The value is the same as SMALLEST_DASH_WIDTH in xattr.cxx.
|
|
// (And it makes sure fLineWidth is not zero in below division.)
|
|
double fLineWidth = nLineWidth > 0 ? nLineWidth : 26.95;
|
|
int i;
|
|
double fSp = bIsRelative ? aLineDash.Distance : aLineDash.Distance * 100.0 / fLineWidth;
|
|
// LO uses line width, in case Distance is zero. MS Office would use a space of zero length.
|
|
// So set 100% explicitly.
|
|
if (aLineDash.Distance <= 0)
|
|
fSp = 100.0;
|
|
// In case of custDash, round caps are included in dash length in MS Office. Square caps are added
|
|
// to dash length, same as in ODF. Change the length values accordingly.
|
|
if (aLineCap == LineCap_ROUND && fSp > 99.0)
|
|
fSp -= 99.0;
|
|
|
|
if (aLineDash.Dots > 0)
|
|
{
|
|
double fD = bIsRelative ? aLineDash.DotLen : aLineDash.DotLen * 100.0 / fLineWidth;
|
|
// LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended.
|
|
if (aLineDash.DotLen == 0)
|
|
fD = 100.0;
|
|
// Tweak dash length, see above.
|
|
if (aLineCap == LineCap_ROUND && fSp > 99.0)
|
|
fD += 99.0;
|
|
|
|
for( i = 0; i < aLineDash.Dots; i ++ )
|
|
{
|
|
mpFS->singleElementNS( XML_a, XML_ds,
|
|
XML_d , write1000thOfAPercent(fD),
|
|
XML_sp, write1000thOfAPercent(fSp) );
|
|
}
|
|
}
|
|
if ( aLineDash.Dashes > 0 )
|
|
{
|
|
double fD = bIsRelative ? aLineDash.DashLen : aLineDash.DashLen * 100.0 / fLineWidth;
|
|
// LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended.
|
|
if (aLineDash.DashLen == 0)
|
|
fD = 100.0;
|
|
// Tweak dash length, see above.
|
|
if (aLineCap == LineCap_ROUND && fSp > 99.0)
|
|
fD += 99.0;
|
|
|
|
for( i = 0; i < aLineDash.Dashes; i ++ )
|
|
{
|
|
mpFS->singleElementNS( XML_a , XML_ds,
|
|
XML_d , write1000thOfAPercent(fD),
|
|
XML_sp, write1000thOfAPercent(fSp) );
|
|
}
|
|
}
|
|
|
|
SAL_WARN_IF(nLineWidth <= 0,
|
|
"oox.shape", "while writing outline - custom dash - line width was < 0 : " << nLineWidth);
|
|
SAL_WARN_IF(aLineDash.Dashes < 0,
|
|
"oox.shape", "while writing outline - custom dash - number of dashes was < 0 : " << aLineDash.Dashes);
|
|
SAL_WARN_IF(aLineDash.Dashes > 0 && aLineDash.DashLen <= 0,
|
|
"oox.shape", "while writing outline - custom dash - dash length was < 0 : " << aLineDash.DashLen);
|
|
SAL_WARN_IF(aLineDash.Dots < 0,
|
|
"oox.shape", "while writing outline - custom dash - number of dots was < 0 : " << aLineDash.Dots);
|
|
SAL_WARN_IF(aLineDash.Dots > 0 && aLineDash.DotLen <= 0,
|
|
"oox.shape", "while writing outline - custom dash - dot length was < 0 : " << aLineDash.DotLen);
|
|
SAL_WARN_IF(aLineDash.Distance <= 0,
|
|
"oox.shape", "while writing outline - custom dash - distance was < 0 : " << aLineDash.Distance);
|
|
|
|
mpFS->endElementNS( XML_a, XML_custDash );
|
|
}
|
|
}
|
|
|
|
if (!bNoFill && nLineWidth > 1 && GetProperty(rXPropSet, "LineJoint"))
|
|
{
|
|
LineJoint eLineJoint = mAny.get<LineJoint>();
|
|
|
|
if( aStyleLineJoint == LineJoint_NONE || aStyleLineJoint != eLineJoint )
|
|
{
|
|
// style-defined line joint does not exist, or is different from the shape's joint
|
|
switch( eLineJoint )
|
|
{
|
|
case LineJoint_NONE:
|
|
case LineJoint_BEVEL:
|
|
mpFS->singleElementNS(XML_a, XML_bevel);
|
|
break;
|
|
default:
|
|
case LineJoint_MIDDLE:
|
|
case LineJoint_MITER:
|
|
mpFS->singleElementNS(XML_a, XML_miter);
|
|
break;
|
|
case LineJoint_ROUND:
|
|
mpFS->singleElementNS(XML_a, XML_round);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !bNoFill )
|
|
{
|
|
WriteLineArrow( rXPropSet, true );
|
|
WriteLineArrow( rXPropSet, false );
|
|
}
|
|
else
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_noFill);
|
|
}
|
|
|
|
mpFS->endElementNS( XML_a, XML_ln );
|
|
}
|
|
|
|
const char* DrawingML::GetComponentDir() const
|
|
{
|
|
switch ( meDocumentType )
|
|
{
|
|
case DOCUMENT_DOCX: return "word";
|
|
case DOCUMENT_PPTX: return "ppt";
|
|
case DOCUMENT_XLSX: return "xl";
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
const char* DrawingML::GetRelationCompPrefix() const
|
|
{
|
|
switch ( meDocumentType )
|
|
{
|
|
case DOCUMENT_DOCX: return "";
|
|
case DOCUMENT_PPTX:
|
|
case DOCUMENT_XLSX: return "../";
|
|
}
|
|
|
|
return "unknown";
|
|
}
|
|
|
|
OUString DrawingML::WriteImage( const Graphic& rGraphic , bool bRelPathToMedia )
|
|
{
|
|
GfxLink aLink = rGraphic.GetGfxLink ();
|
|
BitmapChecksum aChecksum = rGraphic.GetChecksum();
|
|
OUString sMediaType;
|
|
const char* pExtension = "";
|
|
OUString sRelId;
|
|
OUString sPath;
|
|
|
|
// tdf#74670 tdf#91286 Save image only once
|
|
if (!maExportGraphics.empty())
|
|
{
|
|
auto aIterator = maExportGraphics.top().find(aChecksum);
|
|
if (aIterator != maExportGraphics.top().end())
|
|
sPath = aIterator->second;
|
|
}
|
|
|
|
if (sPath.isEmpty())
|
|
{
|
|
SvMemoryStream aStream;
|
|
const void* aData = aLink.GetData();
|
|
std::size_t nDataSize = aLink.GetDataSize();
|
|
|
|
switch (aLink.GetType())
|
|
{
|
|
case GfxLinkType::NativeGif:
|
|
sMediaType = "image/gif";
|
|
pExtension = ".gif";
|
|
break;
|
|
|
|
// #i15508# added BMP type for better exports
|
|
// export not yet active, so adding for reference (not checked)
|
|
case GfxLinkType::NativeBmp:
|
|
sMediaType = "image/bmp";
|
|
pExtension = ".bmp";
|
|
break;
|
|
|
|
case GfxLinkType::NativeJpg:
|
|
sMediaType = "image/jpeg";
|
|
pExtension = ".jpeg";
|
|
break;
|
|
case GfxLinkType::NativePng:
|
|
sMediaType = "image/png";
|
|
pExtension = ".png";
|
|
break;
|
|
case GfxLinkType::NativeTif:
|
|
sMediaType = "image/tiff";
|
|
pExtension = ".tif";
|
|
break;
|
|
case GfxLinkType::NativeWmf:
|
|
sMediaType = "image/x-wmf";
|
|
pExtension = ".wmf";
|
|
break;
|
|
case GfxLinkType::NativeMet:
|
|
sMediaType = "image/x-met";
|
|
pExtension = ".met";
|
|
break;
|
|
case GfxLinkType::NativePct:
|
|
sMediaType = "image/x-pict";
|
|
pExtension = ".pct";
|
|
break;
|
|
case GfxLinkType::NativeMov:
|
|
sMediaType = "application/movie";
|
|
pExtension = ".MOV";
|
|
break;
|
|
default:
|
|
{
|
|
GraphicType aType = rGraphic.GetType();
|
|
if (aType == GraphicType::Bitmap || aType == GraphicType::GdiMetafile)
|
|
{
|
|
if (aType == GraphicType::Bitmap)
|
|
{
|
|
(void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::PNG);
|
|
sMediaType = "image/png";
|
|
pExtension = ".png";
|
|
}
|
|
else
|
|
{
|
|
(void)GraphicConverter::Export(aStream, rGraphic, ConvertDataFormat::EMF);
|
|
sMediaType = "image/x-emf";
|
|
pExtension = ".emf";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SAL_WARN("oox.shape", "unhandled graphic type " << static_cast<int>(aType));
|
|
/*Earlier, even in case of unhandled graphic types we were
|
|
proceeding to write the image, which would eventually
|
|
write an empty image with a zero size, and return a valid
|
|
relationID, which is incorrect.
|
|
*/
|
|
return sRelId;
|
|
}
|
|
|
|
aData = aStream.GetData();
|
|
nDataSize = aStream.GetEndOfData();
|
|
break;
|
|
}
|
|
}
|
|
|
|
Reference<XOutputStream> xOutStream = mpFB->openFragmentStream(
|
|
OUStringBuffer()
|
|
.appendAscii(GetComponentDir())
|
|
.append("/media/image" + OUString::number(mnImageCounter))
|
|
.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('=');
|
|
std::u16string_view aDestination(sURL.subView(nIndex + 1));
|
|
mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action,
|
|
OUString::Concat("ppaction://hlinkshowjump?jump=") + aDestination);
|
|
}
|
|
}
|
|
}
|
|
mpFS->endElementNS( XML_a, nElement );
|
|
}
|
|
|
|
OUString DrawingML::GetFieldValue( const css::uno::Reference< css::text::XTextRange >& rRun, bool& bIsURLField )
|
|
{
|
|
Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY );
|
|
OUString aFieldType, aFieldValue;
|
|
|
|
if (GetProperty(rXPropSet, "TextPortionType"))
|
|
{
|
|
aFieldType = *o3tl::doAccess<OUString>(mAny);
|
|
SAL_INFO("oox.shape", "field type: " << aFieldType);
|
|
}
|
|
|
|
if( aFieldType == "TextField" )
|
|
{
|
|
Reference< XTextField > rXTextField;
|
|
if (GetProperty(rXPropSet, "TextField"))
|
|
mAny >>= rXTextField;
|
|
if( rXTextField.is() )
|
|
{
|
|
rXPropSet.set( rXTextField, UNO_QUERY );
|
|
if( rXPropSet.is() )
|
|
{
|
|
OUString aFieldKind( rXTextField->getPresentation( true ) );
|
|
SAL_INFO("oox.shape", "field kind: " << aFieldKind);
|
|
if( aFieldKind == "Page" )
|
|
{
|
|
aFieldValue = "slidenum";
|
|
}
|
|
else if( aFieldKind == "Pages" )
|
|
{
|
|
aFieldValue = "slidecount";
|
|
}
|
|
else if( aFieldKind == "PageName" )
|
|
{
|
|
aFieldValue = "slidename";
|
|
}
|
|
else if( aFieldKind == "URL" )
|
|
{
|
|
bIsURLField = true;
|
|
if (GetProperty(rXPropSet, "Representation"))
|
|
mAny >>= aFieldValue;
|
|
|
|
}
|
|
else if(aFieldKind == "Date")
|
|
{
|
|
sal_Int32 nNumFmt = -1;
|
|
rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt;
|
|
aFieldValue = GetDatetimeTypeFromDate(static_cast<SvxDateFormat>(nNumFmt));
|
|
}
|
|
else if(aFieldKind == "ExtTime")
|
|
{
|
|
sal_Int32 nNumFmt = -1;
|
|
rXPropSet->getPropertyValue(UNO_TC_PROP_NUMFORMAT) >>= nNumFmt;
|
|
aFieldValue = GetDatetimeTypeFromTime(static_cast<SvxTimeFormat>(nNumFmt));
|
|
}
|
|
else if(aFieldKind == "ExtFile")
|
|
{
|
|
sal_Int32 nNumFmt = -1;
|
|
rXPropSet->getPropertyValue(UNO_TC_PROP_FILE_FORMAT) >>= nNumFmt;
|
|
switch(nNumFmt)
|
|
{
|
|
case 0: aFieldValue = "file"; // Path/File name
|
|
break;
|
|
case 1: aFieldValue = "file1"; // Path
|
|
break;
|
|
case 2: aFieldValue = "file2"; // File name without extension
|
|
break;
|
|
case 3: aFieldValue = "file3"; // File name with extension
|
|
}
|
|
}
|
|
else if(aFieldKind == "Author")
|
|
{
|
|
aFieldValue = "author";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return aFieldValue;
|
|
}
|
|
|
|
OUString DrawingML::GetDatetimeTypeFromDate(SvxDateFormat eDate)
|
|
{
|
|
return GetDatetimeTypeFromDateTime(eDate, SvxTimeFormat::AppDefault);
|
|
}
|
|
|
|
OUString DrawingML::GetDatetimeTypeFromTime(SvxTimeFormat eTime)
|
|
{
|
|
return GetDatetimeTypeFromDateTime(SvxDateFormat::AppDefault, eTime);
|
|
}
|
|
|
|
OUString DrawingML::GetDatetimeTypeFromDateTime(SvxDateFormat eDate, SvxTimeFormat eTime)
|
|
{
|
|
OUString aDateField;
|
|
switch (eDate)
|
|
{
|
|
case SvxDateFormat::StdSmall:
|
|
case SvxDateFormat::A:
|
|
aDateField = "datetime";
|
|
break;
|
|
case SvxDateFormat::B:
|
|
aDateField = "datetime1"; // 13/02/1996
|
|
break;
|
|
case SvxDateFormat::C:
|
|
aDateField = "datetime5";
|
|
break;
|
|
case SvxDateFormat::D:
|
|
aDateField = "datetime3"; // 13 February 1996
|
|
break;
|
|
case SvxDateFormat::StdBig:
|
|
case SvxDateFormat::E:
|
|
case SvxDateFormat::F:
|
|
aDateField = "datetime2";
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
OUString aTimeField;
|
|
switch (eTime)
|
|
{
|
|
case SvxTimeFormat::Standard:
|
|
case SvxTimeFormat::HH24_MM_SS:
|
|
case SvxTimeFormat::HH24_MM_SS_00:
|
|
aTimeField = "datetime11"; // 13:49:38
|
|
break;
|
|
case SvxTimeFormat::HH24_MM:
|
|
aTimeField = "datetime10"; // 13:49
|
|
break;
|
|
case SvxTimeFormat::HH12_MM:
|
|
case SvxTimeFormat::HH12_MM_AMPM:
|
|
aTimeField = "datetime12"; // 01:49 PM
|
|
break;
|
|
case SvxTimeFormat::HH12_MM_SS:
|
|
case SvxTimeFormat::HH12_MM_SS_AMPM:
|
|
case SvxTimeFormat::HH12_MM_SS_00:
|
|
case SvxTimeFormat::HH12_MM_SS_00_AMPM:
|
|
aTimeField = "datetime13"; // 01:49:38 PM
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!aDateField.isEmpty() && aTimeField.isEmpty())
|
|
return aDateField;
|
|
else if (!aTimeField.isEmpty() && aDateField.isEmpty())
|
|
return aTimeField;
|
|
else if (!aDateField.isEmpty() && !aTimeField.isEmpty())
|
|
{
|
|
if (aTimeField == "datetime11" || aTimeField == "datetime13")
|
|
// only datetime format that has Date and HH:MM:SS
|
|
return "datetime9"; // dd/mm/yyyy H:MM:SS
|
|
else
|
|
// only datetime format that has Date and HH:MM
|
|
return "datetime8"; // dd/mm/yyyy H:MM
|
|
}
|
|
else
|
|
return "";
|
|
}
|
|
|
|
void DrawingML::WriteRun( const Reference< XTextRange >& rRun,
|
|
bool& rbOverridingCharHeight, sal_Int32& rnCharHeight,
|
|
const css::uno::Reference< css::beans::XPropertySet >& rXShapePropSet)
|
|
{
|
|
Reference< XPropertySet > rXPropSet( rRun, UNO_QUERY );
|
|
sal_Int16 nLevel = -1;
|
|
if (GetProperty(rXPropSet, "NumberingLevel"))
|
|
mAny >>= nLevel;
|
|
|
|
bool bNumberingIsNumber = true;
|
|
if (GetProperty(rXPropSet, "NumberingIsNumber"))
|
|
mAny >>= bNumberingIsNumber;
|
|
|
|
float nFontSize = -1;
|
|
if (GetProperty(rXPropSet, "CharHeight"))
|
|
mAny >>= nFontSize;
|
|
|
|
bool bIsURLField = false;
|
|
OUString sFieldValue = GetFieldValue( rRun, bIsURLField );
|
|
bool bWriteField = !( sFieldValue.isEmpty() || bIsURLField );
|
|
|
|
OUString sText = rRun->getString();
|
|
|
|
//if there is no text following the bullet, add a space after the bullet
|
|
if (nLevel !=-1 && bNumberingIsNumber && sText.isEmpty() )
|
|
sText=" ";
|
|
|
|
if ( bIsURLField )
|
|
sText = sFieldValue;
|
|
|
|
if( sText.isEmpty())
|
|
{
|
|
Reference< XPropertySet > xPropSet( rRun, UNO_QUERY );
|
|
|
|
try
|
|
{
|
|
if( !xPropSet.is() || !( xPropSet->getPropertyValue( "PlaceholderText" ) >>= sText ) )
|
|
return;
|
|
if( sText.isEmpty() )
|
|
return;
|
|
}
|
|
catch (const Exception &)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (sText == "\n")
|
|
{
|
|
// Empty run? Do not forget to write the font size in case of pptx:
|
|
if ((GetDocumentType() == DOCUMENT_PPTX) && (nFontSize != -1))
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_br);
|
|
mpFS->singleElementNS(XML_a, XML_rPr, XML_sz,
|
|
OString::number(nFontSize * 100).getStr());
|
|
mpFS->endElementNS(XML_a, XML_br);
|
|
}
|
|
else
|
|
mpFS->singleElementNS(XML_a, XML_br);
|
|
}
|
|
else
|
|
{
|
|
if( bWriteField )
|
|
{
|
|
OString sUUID(comphelper::xml::generateGUIDString());
|
|
mpFS->startElementNS( XML_a, XML_fld,
|
|
XML_id, sUUID.getStr(),
|
|
XML_type, sFieldValue );
|
|
}
|
|
else
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_r);
|
|
}
|
|
|
|
Reference< XPropertySet > xPropSet( rRun, uno::UNO_QUERY );
|
|
|
|
WriteRunProperties( xPropSet, bIsURLField, XML_rPr, true, rbOverridingCharHeight, rnCharHeight, GetScriptType(sText), rXShapePropSet);
|
|
mpFS->startElementNS(XML_a, XML_t);
|
|
mpFS->writeEscaped( sText );
|
|
mpFS->endElementNS( XML_a, XML_t );
|
|
|
|
if( bWriteField )
|
|
mpFS->endElementNS( XML_a, XML_fld );
|
|
else
|
|
mpFS->endElementNS( XML_a, XML_r );
|
|
}
|
|
}
|
|
|
|
static OUString GetAutoNumType(SvxNumType nNumberingType, bool bSDot, bool bPBehind, bool bPBoth)
|
|
{
|
|
OUString sPrefixSuffix;
|
|
|
|
if (bPBoth)
|
|
sPrefixSuffix = "ParenBoth";
|
|
else if (bPBehind)
|
|
sPrefixSuffix = "ParenR";
|
|
else if (bSDot)
|
|
sPrefixSuffix = "Period";
|
|
|
|
switch( nNumberingType )
|
|
{
|
|
case SVX_NUM_CHARS_UPPER_LETTER_N :
|
|
case SVX_NUM_CHARS_UPPER_LETTER :
|
|
return "alphaUc" + sPrefixSuffix;
|
|
|
|
case SVX_NUM_CHARS_LOWER_LETTER_N :
|
|
case SVX_NUM_CHARS_LOWER_LETTER :
|
|
return "alphaLc" + sPrefixSuffix;
|
|
|
|
case SVX_NUM_ROMAN_UPPER :
|
|
return "romanUc" + sPrefixSuffix;
|
|
|
|
case SVX_NUM_ROMAN_LOWER :
|
|
return "romanLc" + sPrefixSuffix;
|
|
|
|
case SVX_NUM_ARABIC :
|
|
{
|
|
if (sPrefixSuffix.isEmpty())
|
|
return "arabicPlain";
|
|
else
|
|
return "arabic" + sPrefixSuffix;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return OUString();
|
|
}
|
|
|
|
void DrawingML::WriteParagraphNumbering(const Reference< XPropertySet >& rXPropSet, float fFirstCharHeight, sal_Int16 nLevel )
|
|
{
|
|
if (nLevel < 0 || !GetProperty(rXPropSet, "NumberingRules"))
|
|
{
|
|
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");
|
|
OStringBuffer aLine;
|
|
bool bNotDone = aStream.ReadLine(aLine);
|
|
while (bNotDone)
|
|
{
|
|
sal_Int32 nIndex = 0;
|
|
// Each line is in a "key\tvalue" format: read the key, the rest is the value.
|
|
OString aKey( o3tl::getToken(aLine, 0, '\t', nIndex) );
|
|
OString aValue( std::string_view(aLine).substr(nIndex) );
|
|
aRet[aKey].push_back(aValue);
|
|
bNotDone = aStream.ReadLine(aLine);
|
|
}
|
|
return aRet;
|
|
}
|
|
|
|
void DrawingML::WritePresetShape( const OString& pShape, MSO_SPT eShapeType, bool bPredefinedHandlesUsed, const PropertyValue& rProp )
|
|
{
|
|
static std::map< OString, std::vector<OString> > aAdjMap = lcl_getAdjNames();
|
|
// If there are predefined adj names for this shape type, look them up now.
|
|
std::vector<OString> aAdjustments;
|
|
if (aAdjMap.find(pShape) != aAdjMap.end())
|
|
aAdjustments = aAdjMap[pShape];
|
|
|
|
mpFS->startElementNS(XML_a, XML_prstGeom, XML_prst, pShape);
|
|
mpFS->startElementNS(XML_a, XML_avLst);
|
|
|
|
Sequence< drawing::EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq;
|
|
if ( ( rProp.Value >>= aAdjustmentSeq )
|
|
&& eShapeType != mso_sptActionButtonForwardNext // we have adjustments values for these type of shape, but MSO doesn't like them
|
|
&& eShapeType != mso_sptActionButtonBackPrevious // so they are now disabled
|
|
&& pShape != "rect" //some shape types are commented out in pCustomShapeTypeTranslationTable[] & are being defaulted to rect & rect does not have adjustment values/name.
|
|
)
|
|
{
|
|
SAL_INFO("oox.shape", "adj seq len: " << aAdjustmentSeq.getLength());
|
|
sal_Int32 nAdjustmentsWhichNeedsToBeConverted = 0;
|
|
if ( bPredefinedHandlesUsed )
|
|
EscherPropertyContainer::LookForPolarHandles( eShapeType, nAdjustmentsWhichNeedsToBeConverted );
|
|
|
|
sal_Int32 nValue, nLength = aAdjustmentSeq.getLength();
|
|
// aAdjustments will give info about the number of adj values for a particular geometry. For example for hexagon aAdjustments.size() will be 2 and for circular arrow it will be 5 as per lcl_getAdjNames.
|
|
// Sometimes there are more values than needed, so we ignore the excessive ones.
|
|
if (aAdjustments.size() <= o3tl::make_unsigned(nLength))
|
|
{
|
|
for (sal_Int32 i = 0; i < static_cast<sal_Int32>(aAdjustments.size()); i++)
|
|
{
|
|
if( EscherPropertyContainer::GetAdjustmentValue( aAdjustmentSeq[ i ], i, nAdjustmentsWhichNeedsToBeConverted, nValue ) )
|
|
{
|
|
// If the document model doesn't have an adjustment name (e.g. shape was created from VML), then take it from the predefined list.
|
|
OString aAdjName = aAdjustmentSeq[i].Name.isEmpty()
|
|
? aAdjustments[i]
|
|
: aAdjustmentSeq[i].Name.toUtf8();
|
|
|
|
mpFS->singleElementNS( XML_a, XML_gd,
|
|
XML_name, aAdjName,
|
|
XML_fmla, "val " + OString::number(nValue));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mpFS->endElementNS( XML_a, XML_avLst );
|
|
mpFS->endElementNS( XML_a, XML_prstGeom );
|
|
}
|
|
|
|
namespace // helpers for DrawingML::WriteCustomGeometry
|
|
{
|
|
sal_Int32
|
|
FindNextCommandEndSubpath(const sal_Int32 nStart,
|
|
const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments)
|
|
{
|
|
sal_Int32 i = nStart < 0 ? 0 : nStart;
|
|
while (i < rSegments.getLength() && rSegments[i].Command != ENDSUBPATH)
|
|
i++;
|
|
return i;
|
|
}
|
|
|
|
bool HasCommandInSubPath(const sal_Int16 nCommand, const sal_Int32 nFirst, const sal_Int32 nLast,
|
|
const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments)
|
|
{
|
|
for (sal_Int32 i = nFirst < 0 ? 0 : nFirst; i <= nLast && i < rSegments.getLength(); i++)
|
|
{
|
|
if (rSegments[i].Command == nCommand)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Ellipse is given by radii fwR and fhR and center (fCx|fCy). The ray from center through point RayP
|
|
// intersects the ellipse in point S and this point S has angle fAngleDeg in degrees.
|
|
void getEllipsePointAndAngleFromRayPoint(double& rfAngleDeg, double& rfSx, double& rfSy,
|
|
const double fWR, const double fHR, const double fCx,
|
|
const double fCy, const double fRayPx, const double fRayPy)
|
|
{
|
|
if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR))
|
|
{
|
|
rfSx = fCx; // needed for getting new 'current point'
|
|
rfSy = fCy;
|
|
}
|
|
else
|
|
{
|
|
// center ellipse at origin, stretch in y-direction to circle, flip to Math orientation
|
|
// and get angle
|
|
double fCircleMathAngle = atan2(-fWR / fHR * (fRayPy - fCy), fRayPx - fCx);
|
|
// use angle for intersection point on circle and stretch back to ellipse
|
|
double fPointMathEllipse_x = fWR * cos(fCircleMathAngle);
|
|
double fPointMathEllipse_y = fHR * sin(fCircleMathAngle);
|
|
// get angle of intersection point on ellipse
|
|
double fEllipseMathAngle = atan2(fPointMathEllipse_y, fPointMathEllipse_x);
|
|
// convert from Math to View orientation and shift ellipse back from origin
|
|
rfAngleDeg = -basegfx::rad2deg(fEllipseMathAngle);
|
|
rfSx = fPointMathEllipse_x + fCx;
|
|
rfSy = -fPointMathEllipse_y + fCy;
|
|
}
|
|
}
|
|
|
|
void getEllipsePointFromViewAngle(double& rfSx, double& rfSy, const double fWR, const double fHR,
|
|
const double fCx, const double fCy, const double fViewAngleDeg)
|
|
{
|
|
if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR))
|
|
{
|
|
rfSx = fCx; // needed for getting new 'current point'
|
|
rfSy = fCy;
|
|
}
|
|
else
|
|
{
|
|
double fX = cos(basegfx::deg2rad(fViewAngleDeg)) / fWR;
|
|
double fY = sin(basegfx::deg2rad(fViewAngleDeg)) / fHR;
|
|
double fRadius = 1.0 / std::hypot(fX, fY);
|
|
rfSx = fCx + fRadius * cos(basegfx::deg2rad(fViewAngleDeg));
|
|
rfSy = fCy + fRadius * sin(basegfx::deg2rad(fViewAngleDeg));
|
|
}
|
|
}
|
|
|
|
sal_Int32 GetCustomGeometryPointValue(const css::drawing::EnhancedCustomShapeParameter& rParam,
|
|
const EnhancedCustomShape2d& rCustomShape2d,
|
|
const bool bReplaceGeoWidth, const bool bReplaceGeoHeight)
|
|
{
|
|
double fValue = 0.0;
|
|
rCustomShape2d.GetParameter(fValue, rParam, bReplaceGeoWidth, bReplaceGeoHeight);
|
|
sal_Int32 nValue(std::lround(fValue));
|
|
|
|
return nValue;
|
|
}
|
|
|
|
struct TextAreaRect
|
|
{
|
|
OString left;
|
|
OString top;
|
|
OString right;
|
|
OString bottom;
|
|
};
|
|
|
|
struct Guide
|
|
{
|
|
OString sName;
|
|
OString sFormula;
|
|
};
|
|
|
|
void prepareTextArea(const EnhancedCustomShape2d& rEnhancedCustomShape2d,
|
|
std::vector<Guide>& rGuideList, TextAreaRect& rTextAreaRect)
|
|
{
|
|
tools::Rectangle aTextAreaLO(rEnhancedCustomShape2d.GetTextRect());
|
|
tools::Rectangle aLogicRectLO(rEnhancedCustomShape2d.GetLogicRect());
|
|
if (aTextAreaLO == aLogicRectLO)
|
|
{
|
|
rTextAreaRect.left = "l";
|
|
rTextAreaRect.top = "t";
|
|
rTextAreaRect.right = "r";
|
|
rTextAreaRect.bottom = "b";
|
|
return;
|
|
}
|
|
// Flip aTextAreaLO if shape is flipped
|
|
if (rEnhancedCustomShape2d.IsFlipHorz())
|
|
aTextAreaLO.Move((aLogicRectLO.Center().X() - aTextAreaLO.Center().X()) * 2, 0);
|
|
if (rEnhancedCustomShape2d.IsFlipVert())
|
|
aTextAreaLO.Move(0, (aLogicRectLO.Center().Y() - aTextAreaLO.Center().Y()) * 2);
|
|
|
|
Guide aGuide;
|
|
// horizontal
|
|
const sal_Int32 nWidth = aLogicRectLO.Right() - aLogicRectLO.Left();
|
|
const OString sWidth = OString::number(oox::drawingml::convertHmmToEmu(nWidth));
|
|
|
|
// left
|
|
aGuide.sName = "textAreaLeft";
|
|
sal_Int32 nHelp = aTextAreaLO.Left() - aLogicRectLO.Left();
|
|
const OString sLeft = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
|
|
aGuide.sFormula = "*/ " + sLeft + " w " + sWidth;
|
|
rTextAreaRect.left = aGuide.sName;
|
|
rGuideList.push_back(aGuide);
|
|
|
|
// right
|
|
aGuide.sName = "textAreaRight";
|
|
nHelp = aTextAreaLO.Right() - aLogicRectLO.Left();
|
|
const OString sRight = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
|
|
aGuide.sFormula = "*/ " + sRight + " w " + sWidth;
|
|
rTextAreaRect.right = aGuide.sName;
|
|
rGuideList.push_back(aGuide);
|
|
|
|
// vertical
|
|
const sal_Int32 nHeight = aLogicRectLO.Bottom() - aLogicRectLO.Top();
|
|
const OString sHeight = OString::number(oox::drawingml::convertHmmToEmu(nHeight));
|
|
|
|
// top
|
|
aGuide.sName = "textAreaTop";
|
|
nHelp = aTextAreaLO.Top() - aLogicRectLO.Top();
|
|
const OString sTop = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
|
|
aGuide.sFormula = "*/ " + sTop + " h " + sHeight;
|
|
rTextAreaRect.top = aGuide.sName;
|
|
rGuideList.push_back(aGuide);
|
|
|
|
// bottom
|
|
aGuide.sName = "textAreaBottom";
|
|
nHelp = aTextAreaLO.Bottom() - aLogicRectLO.Top();
|
|
const OString sBottom = OString::number(oox::drawingml::convertHmmToEmu(nHelp));
|
|
aGuide.sFormula = "*/ " + sBottom + " h " + sHeight;
|
|
rTextAreaRect.bottom = aGuide.sName;
|
|
rGuideList.push_back(aGuide);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool DrawingML::WriteCustomGeometry(
|
|
const Reference< XShape >& rXShape,
|
|
const SdrObjCustomShape& rSdrObjCustomShape)
|
|
{
|
|
uno::Reference< beans::XPropertySet > aXPropSet;
|
|
uno::Any aAny( rXShape->queryInterface(cppu::UnoType<beans::XPropertySet>::get()));
|
|
|
|
if ( ! (aAny >>= aXPropSet) )
|
|
return false;
|
|
|
|
try
|
|
{
|
|
aAny = aXPropSet->getPropertyValue( "CustomShapeGeometry" );
|
|
if ( !aAny.hasValue() )
|
|
return false;
|
|
}
|
|
catch( const ::uno::Exception& )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto pGeometrySeq = o3tl::tryAccess<uno::Sequence<beans::PropertyValue>>(aAny);
|
|
if (!pGeometrySeq)
|
|
return false;
|
|
|
|
auto pPathProp = std::find_if(std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq),
|
|
[](const PropertyValue& rProp) { return rProp.Name == "Path"; });
|
|
if (pPathProp == std::cend(*pGeometrySeq))
|
|
return false;
|
|
|
|
uno::Sequence<beans::PropertyValue> aPathProp;
|
|
pPathProp->Value >>= aPathProp;
|
|
|
|
uno::Sequence<drawing::EnhancedCustomShapeParameterPair> aPairs;
|
|
uno::Sequence<drawing::EnhancedCustomShapeSegment> aSegments;
|
|
uno::Sequence<awt::Size> aPathSize;
|
|
bool bReplaceGeoWidth = false;
|
|
bool bReplaceGeoHeight = false;
|
|
for (const beans::PropertyValue& rPathProp : std::as_const(aPathProp))
|
|
{
|
|
if (rPathProp.Name == "Coordinates")
|
|
rPathProp.Value >>= aPairs;
|
|
else if (rPathProp.Name == "Segments")
|
|
rPathProp.Value >>= aSegments;
|
|
else if (rPathProp.Name == "SubViewSize")
|
|
rPathProp.Value >>= aPathSize;
|
|
else if (rPathProp.Name == "StretchX")
|
|
bReplaceGeoWidth = true;
|
|
else if (rPathProp.Name == "StretchY")
|
|
bReplaceGeoHeight = true;
|
|
}
|
|
|
|
if ( !aPairs.hasElements() )
|
|
return false;
|
|
|
|
if ( !aSegments.hasElements() )
|
|
{
|
|
aSegments = uno::Sequence<drawing::EnhancedCustomShapeSegment>
|
|
{
|
|
{ MOVETO, 1 },
|
|
{ LINETO,
|
|
static_cast<sal_Int16>(std::min( aPairs.getLength() - 1, sal_Int32(32767) )) },
|
|
{ CLOSESUBPATH, 0 },
|
|
{ ENDSUBPATH, 0 }
|
|
};
|
|
};
|
|
|
|
int nExpectedPairCount = std::accumulate(std::cbegin(aSegments), std::cend(aSegments), 0,
|
|
[](const int nSum, const drawing::EnhancedCustomShapeSegment& rSegment) { return nSum + rSegment.Count; });
|
|
|
|
if ( nExpectedPairCount > aPairs.getLength() )
|
|
{
|
|
SAL_WARN("oox.shape", "Segments need " << nExpectedPairCount << " coordinates, but Coordinates have only " << aPairs.getLength() << " pairs.");
|
|
return false;
|
|
}
|
|
|
|
// A EnhancedCustomShape2d caches the equation results. Therefore we use only one of it for the
|
|
// entire method.
|
|
const EnhancedCustomShape2d aCustomShape2d(const_cast<SdrObjCustomShape&>(rSdrObjCustomShape));
|
|
|
|
TextAreaRect aTextAreaRect;
|
|
std::vector<Guide> aGuideList; // for now only for <a:rect>
|
|
prepareTextArea(aCustomShape2d, aGuideList, aTextAreaRect);
|
|
mpFS->startElementNS(XML_a, XML_custGeom);
|
|
mpFS->singleElementNS(XML_a, XML_avLst);
|
|
if (aGuideList.empty())
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_gdLst);
|
|
}
|
|
else
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_gdLst);
|
|
for (auto const& elem : aGuideList)
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_gd, XML_name, elem.sName, XML_fmla, elem.sFormula);
|
|
}
|
|
mpFS->endElementNS(XML_a, XML_gdLst);
|
|
}
|
|
mpFS->singleElementNS(XML_a, XML_ahLst);
|
|
mpFS->singleElementNS(XML_a, XML_rect, XML_l, aTextAreaRect.left, XML_t, aTextAreaRect.top,
|
|
XML_r, aTextAreaRect.right, XML_b, aTextAreaRect.bottom);
|
|
mpFS->startElementNS(XML_a, XML_pathLst);
|
|
|
|
// Prepare width and height for <a:path>
|
|
bool bUseGlobalViewBox(false);
|
|
|
|
// nViewBoxWidth must be integer otherwise ReplaceGeoWidth in aCustomShape2d.GetParameter() is not
|
|
// triggered; same for height.
|
|
sal_Int32 nViewBoxWidth(0);
|
|
sal_Int32 nViewBoxHeight(0);
|
|
if (!aPathSize.hasElements())
|
|
{
|
|
bUseGlobalViewBox = true;
|
|
// If draw:viewBox is missing in draw:enhancedGeometry, then import sets
|
|
// viewBox="0 0 21600 21600". Missing ViewBox can only occur, if user has manipulated
|
|
// current file via macro. Author of macro has to fix it.
|
|
auto pProp = std::find_if(
|
|
std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq),
|
|
[](const beans::PropertyValue& rGeomProp) { return rGeomProp.Name == "ViewBox"; });
|
|
if (pProp != std::cend(*pGeometrySeq))
|
|
{
|
|
css::awt::Rectangle aViewBox;
|
|
if (pProp->Value >>= aViewBox)
|
|
{
|
|
nViewBoxWidth = aViewBox.Width;
|
|
nViewBoxHeight = aViewBox.Height;
|
|
css::drawing::EnhancedCustomShapeParameter aECSP;
|
|
aECSP.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL;
|
|
aECSP.Value <<= nViewBoxWidth;
|
|
double fRetValue;
|
|
aCustomShape2d.GetParameter(fRetValue, aECSP, true, false);
|
|
nViewBoxWidth = basegfx::fround(fRetValue);
|
|
aECSP.Value <<= nViewBoxHeight;
|
|
aCustomShape2d.GetParameter(fRetValue, aECSP, false, true);
|
|
nViewBoxHeight = basegfx::fround(fRetValue);
|
|
}
|
|
}
|
|
// Import from oox or documents, which are imported from oox and saved to strict ODF, might
|
|
// have no subViewSize but viewBox="0 0 0 0". We need to generate width and height in those
|
|
// cases. Even if that is fixed, we need the substitute for old documents.
|
|
if ((nViewBoxWidth == 0 && nViewBoxHeight == 0) || pProp == std::cend(*pGeometrySeq))
|
|
{
|
|
// Generate a substitute based on point coordinates
|
|
sal_Int32 nXMin(0);
|
|
aPairs[0].First.Value >>= nXMin;
|
|
sal_Int32 nXMax = nXMin;
|
|
sal_Int32 nYMin(0);
|
|
aPairs[0].Second.Value >>= nYMin;
|
|
sal_Int32 nYMax = nYMin;
|
|
|
|
for (const auto& rPair : std::as_const(aPairs))
|
|
{
|
|
sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, aCustomShape2d,
|
|
bReplaceGeoWidth, false);
|
|
sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, aCustomShape2d, false,
|
|
bReplaceGeoHeight);
|
|
if (nX < nXMin)
|
|
nXMin = nX;
|
|
if (nY < nYMin)
|
|
nYMin = nY;
|
|
if (nX > nXMax)
|
|
nXMax = nX;
|
|
if (nY > nYMax)
|
|
nYMax = nY;
|
|
}
|
|
nViewBoxWidth = std::max(nXMax, nXMax - nXMin);
|
|
nViewBoxHeight = std::max(nYMax, nYMax - nYMin);
|
|
}
|
|
// ToDo: Other values of left,top than 0,0 are not considered yet. Such would require a
|
|
// shift of the resulting path coordinates.
|
|
}
|
|
|
|
// Iterate over subpaths
|
|
sal_Int32 nPairIndex = 0; // index over "Coordinates"
|
|
sal_Int32 nPathSizeIndex = 0; // index over "SubViewSize"
|
|
sal_Int32 nSubpathStartIndex(0); // index over "Segments"
|
|
sal_Int32 nSubPathIndex(0); // serial number of current subpath
|
|
do
|
|
{
|
|
bool bOK(true); // catch faulty paths were commands do not correspond to points
|
|
// get index of next command ENDSUBPATH; if such doesn't exist use index behind last segment
|
|
sal_Int32 nNextNcommandIndex = FindNextCommandEndSubpath(nSubpathStartIndex, aSegments);
|
|
|
|
// Prepare attributes for a:path start element
|
|
// NOFILL or one of the LIGHTEN commands
|
|
std::optional<OString> sFill;
|
|
if (HasCommandInSubPath(NOFILL, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
|
|
sFill = "none";
|
|
else if (HasCommandInSubPath(DARKEN, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
|
|
sFill = "darken";
|
|
else if (HasCommandInSubPath(DARKENLESS, nSubpathStartIndex, nNextNcommandIndex - 1,
|
|
aSegments))
|
|
sFill = "darkenLess";
|
|
else if (HasCommandInSubPath(LIGHTEN, nSubpathStartIndex, nNextNcommandIndex - 1,
|
|
aSegments))
|
|
sFill = "lighten";
|
|
else if (HasCommandInSubPath(LIGHTENLESS, nSubpathStartIndex, nNextNcommandIndex - 1,
|
|
aSegments))
|
|
sFill = "lightenLess";
|
|
else
|
|
{
|
|
// shading info might be in object type, e.g. "Octagon Bevel".
|
|
sal_Int32 nLuminanceChange(aCustomShape2d.GetLuminanceChange(nSubPathIndex));
|
|
if (nLuminanceChange <= -40)
|
|
sFill = "darken";
|
|
else if (nLuminanceChange <= -10)
|
|
sFill = "darkenLess";
|
|
else if (nLuminanceChange >= 40)
|
|
sFill = "lighten";
|
|
else if (nLuminanceChange >= 10)
|
|
sFill = "lightenLess";
|
|
}
|
|
// NOSTROKE
|
|
std::optional<OString> sStroke;
|
|
if (HasCommandInSubPath(NOSTROKE, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
|
|
sStroke = "0";
|
|
|
|
// Write a:path start element
|
|
mpFS->startElementNS(
|
|
XML_a, XML_path, XML_fill, sFill, XML_stroke, sStroke, XML_w,
|
|
OString::number(bUseGlobalViewBox ? nViewBoxWidth : aPathSize[nPathSizeIndex].Width),
|
|
XML_h,
|
|
OString::number(bUseGlobalViewBox ? nViewBoxHeight : aPathSize[nPathSizeIndex].Height));
|
|
|
|
// Arcs drawn by commands ELLIPTICALQUADRANTX and ELLIPTICALQUADRANTY depend on the position
|
|
// of the target point in regard to the current point. Therefore we need to track the
|
|
// current point. A current point is not defined in the beginning.
|
|
double fCurrentX(0.0);
|
|
double fCurrentY(0.0);
|
|
bool bCurrentValid(false);
|
|
// Actually write the subpath
|
|
for (sal_Int32 nSegmentIndex = nSubpathStartIndex; nSegmentIndex < nNextNcommandIndex;
|
|
++nSegmentIndex)
|
|
{
|
|
const auto& rSegment(aSegments[nSegmentIndex]);
|
|
if (rSegment.Command == CLOSESUBPATH)
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_close); // command Z has no parameter
|
|
// ODF 1.4 specifies, that the start of the subpath becomes the current point.
|
|
// But that is not implemented yet. Currently LO keeps the last current point.
|
|
}
|
|
for (sal_Int32 k = 0; k < rSegment.Count && bOK; ++k)
|
|
{
|
|
bOK = WriteCustomGeometrySegment(rSegment.Command, k, aPairs, nPairIndex, fCurrentX,
|
|
fCurrentY, bCurrentValid, aCustomShape2d,
|
|
bReplaceGeoWidth, bReplaceGeoHeight);
|
|
}
|
|
} // end loop over all commands of subpath
|
|
// finish this subpath in any case
|
|
mpFS->endElementNS(XML_a, XML_path);
|
|
|
|
if (!bOK)
|
|
break; // exit loop if not enough values in aPairs
|
|
|
|
// step forward to next subpath
|
|
nSubpathStartIndex = nNextNcommandIndex + 1;
|
|
nPathSizeIndex++;
|
|
nSubPathIndex++;
|
|
} while (nSubpathStartIndex < aSegments.getLength());
|
|
|
|
mpFS->endElementNS(XML_a, XML_pathLst);
|
|
mpFS->endElementNS(XML_a, XML_custGeom);
|
|
return true; // We have written custGeom even if path is poorly structured.
|
|
}
|
|
|
|
bool DrawingML::WriteCustomGeometrySegment(
|
|
const sal_Int16 eCommand, const sal_Int32 nCount,
|
|
const uno::Sequence<css::drawing::EnhancedCustomShapeParameterPair>& rPairs,
|
|
sal_Int32& rnPairIndex, double& rfCurrentX, double& rfCurrentY, bool& rbCurrentValid,
|
|
const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth,
|
|
const bool bReplaceGeoHeight)
|
|
{
|
|
switch (eCommand)
|
|
{
|
|
case MOVETO:
|
|
{
|
|
if (rnPairIndex >= rPairs.getLength())
|
|
return false;
|
|
|
|
mpFS->startElementNS(XML_a, XML_moveTo);
|
|
WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
|
|
bReplaceGeoHeight);
|
|
mpFS->endElementNS(XML_a, XML_moveTo);
|
|
rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth,
|
|
false);
|
|
rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false,
|
|
bReplaceGeoHeight);
|
|
rbCurrentValid = true;
|
|
rnPairIndex++;
|
|
break;
|
|
}
|
|
case LINETO:
|
|
{
|
|
if (rnPairIndex >= rPairs.getLength())
|
|
return false;
|
|
// LINETO without valid current point is a faulty path. LO is tolerant and makes a
|
|
// moveTo instead. Do the same on export. MS OFFICE requires a current point for lnTo,
|
|
// otherwise it shows nothing of the shape.
|
|
if (rbCurrentValid)
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_lnTo);
|
|
WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
|
|
bReplaceGeoHeight);
|
|
mpFS->endElementNS(XML_a, XML_lnTo);
|
|
}
|
|
else
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_moveTo);
|
|
WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
|
|
bReplaceGeoHeight);
|
|
mpFS->endElementNS(XML_a, XML_moveTo);
|
|
}
|
|
rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth,
|
|
false);
|
|
rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false,
|
|
bReplaceGeoHeight);
|
|
rbCurrentValid = true;
|
|
rnPairIndex++;
|
|
break;
|
|
}
|
|
case CURVETO:
|
|
{
|
|
if (rnPairIndex + 2 >= rPairs.getLength())
|
|
return false;
|
|
|
|
mpFS->startElementNS(XML_a, XML_cubicBezTo);
|
|
for (sal_uInt8 i = 0; i <= 2; ++i)
|
|
{
|
|
WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth,
|
|
bReplaceGeoHeight);
|
|
}
|
|
mpFS->endElementNS(XML_a, XML_cubicBezTo);
|
|
rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth,
|
|
false);
|
|
rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 2].Second, false,
|
|
bReplaceGeoHeight);
|
|
rbCurrentValid = true;
|
|
rnPairIndex += 3;
|
|
break;
|
|
}
|
|
case ANGLEELLIPSETO:
|
|
case ANGLEELLIPSE:
|
|
{
|
|
if (rnPairIndex + 2 >= rPairs.getLength())
|
|
return false;
|
|
|
|
// Read parameters
|
|
double fCx = 0.0;
|
|
rCustomShape2d.GetParameter(fCx, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
|
|
double fCy = 0.0;
|
|
rCustomShape2d.GetParameter(fCy, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
|
|
double fWR = 0.0;
|
|
rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex + 1].First, false, false);
|
|
double fHR = 0.0;
|
|
rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex + 1].Second, false, false);
|
|
double fStartAngle = 0.0;
|
|
rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 2].First, false, false);
|
|
double fEndAngle = 0.0;
|
|
rCustomShape2d.GetParameter(fEndAngle, rPairs[rnPairIndex + 2].Second, false, false);
|
|
|
|
// Prepare start and swing angle
|
|
sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
|
|
sal_Int32 nSwingAng = 0;
|
|
if (basegfx::fTools::equalZero(fStartAngle)
|
|
&& basegfx::fTools::equalZero(fEndAngle - 360.0))
|
|
nSwingAng = 360 * 60000; // special case full circle
|
|
else
|
|
{
|
|
nSwingAng = std::lround((fEndAngle - fStartAngle) * 60000);
|
|
if (nSwingAng < 0)
|
|
nSwingAng += 360 * 60000;
|
|
}
|
|
|
|
// calculate start point on ellipse
|
|
double fSx = 0.0;
|
|
double fSy = 0.0;
|
|
getEllipsePointFromViewAngle(fSx, fSy, fWR, fHR, fCx, fCy, fStartAngle);
|
|
|
|
// write markup for going to start point
|
|
// lnTo requires a valid current point
|
|
if (eCommand == ANGLEELLIPSETO && rbCurrentValid)
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_lnTo);
|
|
mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)),
|
|
XML_y, OString::number(std::lround(fSy)));
|
|
mpFS->endElementNS(XML_a, XML_lnTo);
|
|
}
|
|
else
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_moveTo);
|
|
mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fSx)),
|
|
XML_y, OString::number(std::lround(fSy)));
|
|
mpFS->endElementNS(XML_a, XML_moveTo);
|
|
}
|
|
// write markup for arcTo
|
|
if (!basegfx::fTools::equalZero(fWR) && !basegfx::fTools::equalZero(fHR))
|
|
mpFS->singleElement(
|
|
FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR,
|
|
OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng),
|
|
XML_swAng, OString::number(nSwingAng));
|
|
|
|
getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy, fEndAngle);
|
|
rbCurrentValid = true;
|
|
rnPairIndex += 3;
|
|
break;
|
|
}
|
|
case ARCTO:
|
|
case ARC:
|
|
case CLOCKWISEARCTO:
|
|
case CLOCKWISEARC:
|
|
{
|
|
if (rnPairIndex + 3 >= rPairs.getLength())
|
|
return false;
|
|
|
|
// read parameters
|
|
double fX1 = 0.0;
|
|
rCustomShape2d.GetParameter(fX1, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
|
|
double fY1 = 0.0;
|
|
rCustomShape2d.GetParameter(fY1, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
|
|
double fX2 = 0.0;
|
|
rCustomShape2d.GetParameter(fX2, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth,
|
|
false);
|
|
double fY2 = 0.0;
|
|
rCustomShape2d.GetParameter(fY2, rPairs[rnPairIndex + 1].Second, false,
|
|
bReplaceGeoHeight);
|
|
double fX3 = 0.0;
|
|
rCustomShape2d.GetParameter(fX3, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth,
|
|
false);
|
|
double fY3 = 0.0;
|
|
rCustomShape2d.GetParameter(fY3, rPairs[rnPairIndex + 2].Second, false,
|
|
bReplaceGeoHeight);
|
|
double fX4 = 0.0;
|
|
rCustomShape2d.GetParameter(fX4, rPairs[rnPairIndex + 3].First, bReplaceGeoWidth,
|
|
false);
|
|
double fY4 = 0.0;
|
|
rCustomShape2d.GetParameter(fY4, rPairs[rnPairIndex + 3].Second, false,
|
|
bReplaceGeoHeight);
|
|
// calculate ellipse parameter
|
|
const double fWR = (fX2 - fX1) / 2.0;
|
|
const double fHR = (fY2 - fY1) / 2.0;
|
|
const double fCx = (fX1 + fX2) / 2.0;
|
|
const double fCy = (fY1 + fY2) / 2.0;
|
|
// calculate start angle
|
|
double fStartAngle = 0.0;
|
|
double fPx = 0.0;
|
|
double fPy = 0.0;
|
|
getEllipsePointAndAngleFromRayPoint(fStartAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX3,
|
|
fY3);
|
|
// markup for going to start point
|
|
// lnTo requires a valid current point.
|
|
if ((eCommand == ARCTO || eCommand == CLOCKWISEARCTO) && rbCurrentValid)
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_lnTo);
|
|
mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)),
|
|
XML_y, OString::number(std::lround(fPy)));
|
|
mpFS->endElementNS(XML_a, XML_lnTo);
|
|
}
|
|
else
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_moveTo);
|
|
mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(std::lround(fPx)),
|
|
XML_y, OString::number(std::lround(fPy)));
|
|
mpFS->endElementNS(XML_a, XML_moveTo);
|
|
}
|
|
// calculate swing angle
|
|
double fEndAngle = 0.0;
|
|
getEllipsePointAndAngleFromRayPoint(fEndAngle, fPx, fPy, fWR, fHR, fCx, fCy, fX4, fY4);
|
|
double fSwingAngle(fEndAngle - fStartAngle);
|
|
const bool bIsClockwise(eCommand == CLOCKWISEARCTO || eCommand == CLOCKWISEARC);
|
|
if (bIsClockwise && fSwingAngle < 0)
|
|
fSwingAngle += 360.0;
|
|
else if (!bIsClockwise && fSwingAngle > 0)
|
|
fSwingAngle -= 360.0;
|
|
// markup for arcTo
|
|
// ToDo: write markup for case zero width or height of ellipse
|
|
const sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
|
|
const sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000));
|
|
mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)),
|
|
XML_hR, OString::number(std::lround(fHR)), XML_stAng,
|
|
OString::number(nStartAng), XML_swAng, OString::number(nSwingAng));
|
|
rfCurrentX = fPx;
|
|
rfCurrentY = fPy;
|
|
rbCurrentValid = true;
|
|
rnPairIndex += 4;
|
|
break;
|
|
}
|
|
case ELLIPTICALQUADRANTX:
|
|
case ELLIPTICALQUADRANTY:
|
|
{
|
|
if (rnPairIndex >= rPairs.getLength())
|
|
return false;
|
|
|
|
// read parameters
|
|
double fX = 0.0;
|
|
rCustomShape2d.GetParameter(fX, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
|
|
double fY = 0.0;
|
|
rCustomShape2d.GetParameter(fY, rPairs[rnPairIndex].Second, false, bReplaceGeoHeight);
|
|
|
|
// Prepare parameters for arcTo
|
|
if (rbCurrentValid)
|
|
{
|
|
double fWR = std::abs(rfCurrentX - fX);
|
|
double fHR = std::abs(rfCurrentY - fY);
|
|
double fStartAngle(0.0);
|
|
double fSwingAngle(0.0);
|
|
// The starting direction of the arc toggles between X and Y
|
|
if ((eCommand == ELLIPTICALQUADRANTX && !(nCount % 2))
|
|
|| (eCommand == ELLIPTICALQUADRANTY && (nCount % 2)))
|
|
{
|
|
// arc starts horizontal
|
|
fStartAngle = fY < rfCurrentY ? 90.0 : 270.0;
|
|
const bool bClockwise = (fX < rfCurrentX && fY < rfCurrentY)
|
|
|| (fX > rfCurrentX && fY > rfCurrentY);
|
|
fSwingAngle = bClockwise ? 90.0 : -90.0;
|
|
}
|
|
else
|
|
{
|
|
// arc starts vertical
|
|
fStartAngle = fX < rfCurrentX ? 0.0 : 180.0;
|
|
const bool bClockwise = (fX < rfCurrentX && fY > rfCurrentY)
|
|
|| (fX > rfCurrentX && fY < rfCurrentY);
|
|
fSwingAngle = bClockwise ? 90.0 : -90.0;
|
|
}
|
|
sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
|
|
sal_Int32 nSwingAng(std::lround(fSwingAngle * 60000));
|
|
mpFS->singleElement(
|
|
FSNS(XML_a, XML_arcTo), XML_wR, OString::number(std::lround(fWR)), XML_hR,
|
|
OString::number(std::lround(fHR)), XML_stAng, OString::number(nStartAng),
|
|
XML_swAng, OString::number(nSwingAng));
|
|
}
|
|
else
|
|
{
|
|
// faulty path, but we continue with the target point
|
|
mpFS->startElementNS(XML_a, XML_moveTo);
|
|
WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
|
|
bReplaceGeoHeight);
|
|
mpFS->endElementNS(XML_a, XML_moveTo);
|
|
}
|
|
rfCurrentX = fX;
|
|
rfCurrentY = fY;
|
|
rbCurrentValid = true;
|
|
rnPairIndex++;
|
|
break;
|
|
}
|
|
case QUADRATICCURVETO:
|
|
{
|
|
if (rnPairIndex + 1 >= rPairs.getLength())
|
|
return false;
|
|
|
|
mpFS->startElementNS(XML_a, XML_quadBezTo);
|
|
for (sal_uInt8 i = 0; i < 2; ++i)
|
|
{
|
|
WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth,
|
|
bReplaceGeoHeight);
|
|
}
|
|
mpFS->endElementNS(XML_a, XML_quadBezTo);
|
|
rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth,
|
|
false);
|
|
rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 1].Second, false,
|
|
bReplaceGeoHeight);
|
|
rbCurrentValid = true;
|
|
rnPairIndex += 2;
|
|
break;
|
|
}
|
|
case ARCANGLETO:
|
|
{
|
|
if (rnPairIndex + 1 >= rPairs.getLength())
|
|
return false;
|
|
|
|
double fWR = 0.0;
|
|
rCustomShape2d.GetParameter(fWR, rPairs[rnPairIndex].First, false, false);
|
|
double fHR = 0.0;
|
|
rCustomShape2d.GetParameter(fHR, rPairs[rnPairIndex].Second, false, false);
|
|
double fStartAngle = 0.0;
|
|
rCustomShape2d.GetParameter(fStartAngle, rPairs[rnPairIndex + 1].First, false, false);
|
|
sal_Int32 nStartAng(std::lround(fStartAngle * 60000));
|
|
double fSwingAng = 0.0;
|
|
rCustomShape2d.GetParameter(fSwingAng, rPairs[rnPairIndex + 1].Second, false, false);
|
|
sal_Int32 nSwingAng(std::lround(fSwingAng * 60000));
|
|
mpFS->singleElement(FSNS(XML_a, XML_arcTo), XML_wR, OString::number(fWR), XML_hR,
|
|
OString::number(fHR), XML_stAng, OString::number(nStartAng),
|
|
XML_swAng, OString::number(nSwingAng));
|
|
double fPx = 0.0;
|
|
double fPy = 0.0;
|
|
getEllipsePointFromViewAngle(fPx, fPy, fWR, fHR, 0.0, 0.0, fStartAngle);
|
|
double fCx = rfCurrentX - fPx;
|
|
double fCy = rfCurrentY - fPy;
|
|
getEllipsePointFromViewAngle(rfCurrentX, rfCurrentY, fWR, fHR, fCx, fCy,
|
|
fStartAngle + fSwingAng);
|
|
rbCurrentValid = true;
|
|
rnPairIndex += 2;
|
|
break;
|
|
}
|
|
default:
|
|
// do nothing
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void DrawingML::WriteCustomGeometryPoint(
|
|
const drawing::EnhancedCustomShapeParameterPair& rParamPair,
|
|
const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth,
|
|
const bool bReplaceGeoHeight)
|
|
{
|
|
sal_Int32 nX
|
|
= GetCustomGeometryPointValue(rParamPair.First, rCustomShape2d, bReplaceGeoWidth, false);
|
|
sal_Int32 nY
|
|
= GetCustomGeometryPointValue(rParamPair.Second, rCustomShape2d, false, bReplaceGeoHeight);
|
|
|
|
mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(nX), XML_y, OString::number(nY));
|
|
}
|
|
|
|
void DrawingML::WriteEmptyCustomGeometry()
|
|
{
|
|
// This method is used for export to docx in case WriteCustomGeometry fails.
|
|
mpFS->startElementNS(XML_a, XML_custGeom);
|
|
mpFS->singleElementNS(XML_a, XML_avLst);
|
|
mpFS->singleElementNS(XML_a, XML_gdLst);
|
|
mpFS->singleElementNS(XML_a, XML_ahLst);
|
|
mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b");
|
|
mpFS->singleElementNS(XML_a, XML_pathLst);
|
|
mpFS->endElementNS(XML_a, XML_custGeom);
|
|
}
|
|
|
|
// version for SdrPathObj
|
|
void DrawingML::WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape,
|
|
const bool bClosed)
|
|
{
|
|
tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(rXShape);
|
|
// In case of Writer, the parent element is <wps:spPr>, and there the
|
|
// <a:custGeom> element is not optional.
|
|
if (aPolyPolygon.Count() < 1 && GetDocumentType() != DOCUMENT_DOCX)
|
|
return;
|
|
|
|
mpFS->startElementNS(XML_a, XML_custGeom);
|
|
mpFS->singleElementNS(XML_a, XML_avLst);
|
|
mpFS->singleElementNS(XML_a, XML_gdLst);
|
|
mpFS->singleElementNS(XML_a, XML_ahLst);
|
|
mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b");
|
|
|
|
mpFS->startElementNS(XML_a, XML_pathLst);
|
|
|
|
awt::Size aSize = rXShape->getSize();
|
|
awt::Point aPos = rXShape->getPosition();
|
|
Reference<XPropertySet> xPropertySet(rXShape, UNO_QUERY);
|
|
uno::Reference<XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo();
|
|
if (xPropertySetInfo->hasPropertyByName("AnchorPosition"))
|
|
{
|
|
awt::Point aAnchorPosition;
|
|
xPropertySet->getPropertyValue("AnchorPosition") >>= aAnchorPosition;
|
|
aPos.X += aAnchorPosition.X;
|
|
aPos.Y += aAnchorPosition.Y;
|
|
}
|
|
|
|
// Only closed SdrPathObj can be filled
|
|
std::optional<OString> sFill;
|
|
if (!bClosed)
|
|
sFill = "none"; // for possible values see ST_PathFillMode in OOXML standard
|
|
|
|
// Put all polygons of rPolyPolygon in the same path element
|
|
// to subtract the overlapped areas.
|
|
mpFS->startElementNS(XML_a, XML_path, XML_fill, sFill, XML_w, OString::number(aSize.Width),
|
|
XML_h, OString::number(aSize.Height));
|
|
|
|
for (sal_uInt16 i = 0; i < aPolyPolygon.Count(); i++)
|
|
{
|
|
const tools::Polygon& aPoly = aPolyPolygon[i];
|
|
|
|
if (aPoly.GetSize() > 0)
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_moveTo);
|
|
|
|
mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[0].X() - aPos.X),
|
|
XML_y, OString::number(aPoly[0].Y() - aPos.Y));
|
|
|
|
mpFS->endElementNS(XML_a, XML_moveTo);
|
|
}
|
|
|
|
for (sal_uInt16 j = 1; j < aPoly.GetSize(); j++)
|
|
{
|
|
PolyFlags flags = aPoly.GetFlags(j);
|
|
if (flags == PolyFlags::Control)
|
|
{
|
|
// a:cubicBezTo can only contain 3 a:pt elements, so we need to make sure of this
|
|
if (j + 2 < aPoly.GetSize() && aPoly.GetFlags(j + 1) == PolyFlags::Control
|
|
&& aPoly.GetFlags(j + 2) != PolyFlags::Control)
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_cubicBezTo);
|
|
for (sal_uInt8 k = 0; k <= 2; ++k)
|
|
{
|
|
mpFS->singleElementNS(XML_a, XML_pt, XML_x,
|
|
OString::number(aPoly[j + k].X() - aPos.X), XML_y,
|
|
OString::number(aPoly[j + k].Y() - aPos.Y));
|
|
}
|
|
mpFS->endElementNS(XML_a, XML_cubicBezTo);
|
|
j += 2;
|
|
}
|
|
}
|
|
else if (flags == PolyFlags::Normal)
|
|
{
|
|
mpFS->startElementNS(XML_a, XML_lnTo);
|
|
mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(aPoly[j].X() - aPos.X),
|
|
XML_y, OString::number(aPoly[j].Y() - aPos.Y));
|
|
mpFS->endElementNS(XML_a, XML_lnTo);
|
|
}
|
|
}
|
|
}
|
|
if (bClosed)
|
|
mpFS->singleElementNS(XML_a, XML_close);
|
|
mpFS->endElementNS(XML_a, XML_path);
|
|
|
|
mpFS->endElementNS(XML_a, XML_pathLst);
|
|
|
|
mpFS->endElementNS(XML_a, XML_custGeom);
|
|
}
|
|
|
|
void DrawingML::WriteConnectorConnections( 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::Any(sRelId.toInt32()));
|
|
|
|
mpFB->addRelation(xOutStream, sType, OUStringConcatenation("../" + sFragment));
|
|
|
|
OUString sDir = OUString::createFromAscii(GetComponentDir());
|
|
uno::Reference<io::XOutputStream> xBinOutStream
|
|
= mpFB->openFragmentStream(sDir + "/" + sFragment, sContentType);
|
|
|
|
try
|
|
{
|
|
comphelper::OStorageHelper::CopyInputToOutput(dataImagebin, xBinOutStream);
|
|
}
|
|
catch (const uno::Exception&)
|
|
{
|
|
TOOLS_WARN_EXCEPTION("oox.drawingml", "DrawingML::writeDiagramRels Failed to copy grabbaged Image");
|
|
}
|
|
dataImagebin->closeInput();
|
|
}
|
|
}
|
|
|
|
void DrawingML::WriteFromTo(const uno::Reference<css::drawing::XShape>& rXShape, const awt::Size& aPageSize,
|
|
const FSHelperPtr& pDrawing)
|
|
{
|
|
awt::Point aTopLeft = rXShape->getPosition();
|
|
awt::Size aSize = rXShape->getSize();
|
|
|
|
SdrObject* pObj = SdrObject::getSdrObjectFromXShape(rXShape);
|
|
if (pObj)
|
|
{
|
|
Degree100 nRotation = pObj->GetRotateAngle();
|
|
if (nRotation)
|
|
{
|
|
sal_Int16 nHalfWidth = aSize.Width / 2;
|
|
sal_Int16 nHalfHeight = aSize.Height / 2;
|
|
// aTopLeft needs correction for rotated customshapes
|
|
if (pObj->GetObjIdentifier() == SdrObjKind::CustomShape)
|
|
{
|
|
// Center of bounding box of the rotated shape
|
|
const auto aSnapRectCenter(pObj->GetSnapRect().Center());
|
|
aTopLeft.X = aSnapRectCenter.X() - nHalfWidth;
|
|
aTopLeft.Y = aSnapRectCenter.Y() - nHalfHeight;
|
|
}
|
|
|
|
// MSO changes the anchor positions at these angles and that does an extra 90 degrees
|
|
// rotation on our shapes, so we output it in such position that MSO
|
|
// can draw this shape correctly.
|
|
if ((nRotation >= 4500_deg100 && nRotation < 13500_deg100) || (nRotation >= 22500_deg100 && nRotation < 31500_deg100))
|
|
{
|
|
aTopLeft.X = aTopLeft.X - nHalfHeight + nHalfWidth;
|
|
aTopLeft.Y = aTopLeft.Y - nHalfWidth + nHalfHeight;
|
|
|
|
std::swap(aSize.Width, aSize.Height);
|
|
}
|
|
}
|
|
}
|
|
|
|
tools::Rectangle aLocation(aTopLeft.X, aTopLeft.Y, aTopLeft.X + aSize.Width, aTopLeft.Y + aSize.Height);
|
|
double nXpos = static_cast<double>(aLocation.TopLeft().getX()) / static_cast<double>(aPageSize.Width);
|
|
double nYpos = static_cast<double>(aLocation.TopLeft().getY()) / static_cast<double>(aPageSize.Height);
|
|
|
|
pDrawing->startElement(FSNS(XML_cdr, XML_from));
|
|
pDrawing->startElement(FSNS(XML_cdr, XML_x));
|
|
pDrawing->write(nXpos);
|
|
pDrawing->endElement(FSNS(XML_cdr, XML_x));
|
|
pDrawing->startElement(FSNS(XML_cdr, XML_y));
|
|
pDrawing->write(nYpos);
|
|
pDrawing->endElement(FSNS(XML_cdr, XML_y));
|
|
pDrawing->endElement(FSNS(XML_cdr, XML_from));
|
|
|
|
nXpos = static_cast<double>(aLocation.BottomRight().getX()) / static_cast<double>(aPageSize.Width);
|
|
nYpos = static_cast<double>(aLocation.BottomRight().getY()) / static_cast<double>(aPageSize.Height);
|
|
|
|
pDrawing->startElement(FSNS(XML_cdr, XML_to));
|
|
pDrawing->startElement(FSNS(XML_cdr, XML_x));
|
|
pDrawing->write(nXpos);
|
|
pDrawing->endElement(FSNS(XML_cdr, XML_x));
|
|
pDrawing->startElement(FSNS(XML_cdr, XML_y));
|
|
pDrawing->write(nYpos);
|
|
pDrawing->endElement(FSNS(XML_cdr, XML_y));
|
|
pDrawing->endElement(FSNS(XML_cdr, XML_to));
|
|
}
|
|
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|