/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <config_wasm_strip.h>
#include <sal/config.h>
#include <sal/log.hxx>
#include <filter/msfilter/util.hxx>
#include <o3tl/string_view.hxx>
#include <o3tl/any.hxx>
#include <oox/core/xmlfilterbase.hxx>
#include <oox/export/shapes.hxx>
#include <oox/export/utils.hxx>
#include <oox/token/namespaces.hxx>
#include <oox/token/relationship.hxx>
#include <oox/token/tokens.hxx>
#include <initializer_list>
#include <string_view>
#include <com/sun/star/beans/PropertyValues.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/beans/XPropertyState.hpp>
#include <com/sun/star/container/XChild.hpp>
#include <com/sun/star/document/XExporter.hpp>
#include <com/sun/star/document/XStorageBasedDocument.hpp>
#include <com/sun/star/drawing/CircleKind.hpp>
#include <com/sun/star/drawing/FillStyle.hpp>
#include <com/sun/star/drawing/ConnectorType.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp>
#include <com/sun/star/embed/Aspects.hpp>
#include <com/sun/star/embed/EmbedStates.hpp>
#include <com/sun/star/embed/XEmbeddedObject.hpp>
#include <com/sun/star/embed/XEmbedPersist.hpp>
#include <com/sun/star/frame/XStorable.hpp>
#include <com/sun/star/graphic/XGraphic.hpp>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <com/sun/star/io/XOutputStream.hpp>
#include <com/sun/star/text/XSimpleText.hpp>
#include <com/sun/star/text/XText.hpp>
#include <com/sun/star/table/XTable.hpp>
#include <com/sun/star/table/XMergeableCell.hpp>
#include <com/sun/star/chart2/XChartDocument.hpp>
#include <com/sun/star/frame/XModel.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <com/sun/star/drawing/XDrawPages.hpp>
#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
#include <com/sun/star/presentation/ClickAction.hpp>
#include <com/sun/star/drawing/XGluePointsSupplier.hpp>
#include <com/sun/star/container/XIdentifierAccess.hpp>
#include <com/sun/star/table/BorderLineStyle.hpp>
#include <tools/globname.hxx>
#include <comphelper/classids.hxx>
#include <comphelper/propertysequence.hxx>
#include <comphelper/storagehelper.hxx>
#include <sot/exchange.hxx>
#include <utility>
#include <vcl/graph.hxx>
#include <vcl/outdev.hxx>
#include <filter/msfilter/escherex.hxx>
#include <svtools/embedhlp.hxx>
#include <svx/svdoashp.hxx>
#include <svx/svdoole2.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <oox/export/chartexport.hxx>
#include <oox/mathml/imexport.hxx>
#include <basegfx/numeric/ftools.hxx>
#include <oox/export/DMLPresetShapeExport.hxx>
#include <frozen/bits/defines.h>
#include <frozen/bits/elsa_std.h>
#include <frozen/set.h>
#include <frozen/unordered_map.h>
#include <sax/fastattribs.hxx>
using namespace ::css;
using namespace ::css::beans;
using namespace ::css::uno;
using namespace ::css::drawing;
using namespace ::css::table;
using namespace ::css::container;
using namespace ::css::document;
using namespace ::css::text;
using ::css::io::XOutputStream;
using ::css::chart2::XChartDocument;
using ::css::frame::XModel;
using ::oox::core::XmlFilterBase;
using ::sax_fastparser::FSHelperPtr;
namespace oox {
static void lcl_ConvertProgID(std::u16string_view rProgID,
OUString & o_rMediaType, OUString & o_rRelationType, OUString & o_rFileExten
sion)
{
if (rProgID == u"Excel.Sheet.12" )
{
o_rMediaType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ;
o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
o_rFileExtension = "xlsx" ;
}
else if (o3tl::starts_with(rProgID, u"Excel.SheetBinaryMacroEnabled.12" ) )
{
o_rMediaType = "application/vnd.ms-excel.sheet.binary.macroEnabled.12" ;
o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
o_rFileExtension = "xlsb" ;
}
else if (o3tl::starts_with(rProgID, u"Excel.SheetMacroEnabled.12" ))
{
o_rMediaType = "application/vnd.ms-excel.sheet.macroEnabled.12" ;
o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
o_rFileExtension = "xlsm" ;
}
else if (o3tl::starts_with(rProgID, u"Excel.Sheet" ))
{
o_rMediaType = "application/vnd.ms-excel" ;
o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
o_rFileExtension = "xls" ;
}
else if (rProgID == u"PowerPoint.Show.12" )
{
o_rMediaType = "application/vnd.openxmlformats-officedocument.presentationml.presentation" ;
o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
o_rFileExtension = "pptx" ;
}
else if (rProgID == u"PowerPoint.ShowMacroEnabled.12" )
{
o_rMediaType = "application/vnd.ms-powerpoint.presentation.macroEnabled.12" ;
o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
o_rFileExtension = "pptm" ;
}
else if (o3tl::starts_with(rProgID, u"PowerPoint.Show" ))
{
o_rMediaType = "application/vnd.ms-powerpoint" ;
o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
o_rFileExtension = "ppt" ;
}
else if (o3tl::starts_with(rProgID, u"PowerPoint.Slide.12" ))
{
o_rMediaType = "application/vnd.openxmlformats-officedocument.presentationml.slide" ;
o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
o_rFileExtension = "sldx" ;
}
else if (rProgID == u"PowerPoint.SlideMacroEnabled.12" )
{
o_rMediaType = "application/vnd.ms-powerpoint.slide.macroEnabled.12" ;
o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
o_rFileExtension = "sldm" ;
}
else if (rProgID == u"Word.DocumentMacroEnabled.12" )
{
o_rMediaType = "application/vnd.ms-word.document.macroEnabled.12" ;
o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
o_rFileExtension = "docm" ;
}
else if (rProgID == u"Word.Document.12" )
{
o_rMediaType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ;
o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
o_rFileExtension = "docx" ;
}
else if (rProgID == u"Word.Document.8" )
{
o_rMediaType = "application/msword" ;
o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
o_rFileExtension = "doc" ;
}
else if (rProgID == u"Excel.Chart.8" )
{
o_rMediaType = "application/vnd.ms-excel" ;
o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
o_rFileExtension = "xls" ;
}
else if (rProgID == u"AcroExch.Document.11" )
{
o_rMediaType = "application/pdf" ;
o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
o_rFileExtension = "pdf" ;
}
else
{
o_rMediaType = "application/vnd.openxmlformats-officedocument.oleObject" ;
o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT);
o_rFileExtension = "bin" ;
}
}
static uno::Reference<io::XInputStream> lcl_StoreOwnAsOOXML(
uno::Reference<uno::XComponentContext> const & xContext,
uno::Reference<embed::XEmbeddedObject> const & xObj,
char const *& o_rpProgID,
OUString & o_rMediaType, OUString & o_rRelationType, OUString & o_rSuffix)
{
static struct {
struct {
sal_uInt32 n1;
sal_uInt16 n2, n3;
sal_uInt8 b8, b9, b10, b11, b12, b13, b14, b15;
} ClassId;
char const * pFilterName;
char const * pMediaType;
char const * pProgID;
char const * pSuffix;
} const s_Mapping[] = {
{ {SO3_SW_CLASSID_60}, "MS Word 2007 XML" , "application/vnd.openxmlformats-officedocument.wordprocessingml.document" , "Word.Document.12" , "docx" },
{ {SO3_SC_CLASSID_60}, "Calc MS Excel 2007 XML" , "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" , "Excel.Sheet.12" , "xlsx" },
{ {SO3_SIMPRESS_CLASSID_60}, "Impress MS PowerPoint 2007 XML" , "application/vnd.openxmlformats-officedocument.presentationml.presentation" , "PowerPoint.Show.12" , "pptx" },
// FIXME: Draw does not appear to have a MSO format export filter?
// { {SO3_SDRAW_CLASSID}, "", "", "", "" },
{ {SO3_SCH_CLASSID_60}, "unused" , "" , "" , "" },
{ {SO3_SM_CLASSID_60}, "unused" , "" , "" , "" },
};
const char * pFilterName(nullptr);
SvGlobalName const classId(xObj->getClassID());
for (auto & i : s_Mapping)
{
auto const & rId(i.ClassId);
SvGlobalName const temp(rId.n1, rId.n2, rId.n3, rId.b8, rId.b9, rId.b10, rId.b11, rId.b12, rId.b13, rId.b14, rId.b15);
if (temp == classId)
{
assert(SvGlobalName(SO3_SCH_CLASSID_60) != classId); // chart should be written elsewhere!
assert(SvGlobalName(SO3_SM_CLASSID_60) != classId); // formula should be written elsewhere!
pFilterName = i.pFilterName;
o_rMediaType = OUString::createFromAscii(i.pMediaType);
o_rpProgID = i.pProgID;
o_rSuffix = OUString::createFromAscii(i.pSuffix);
o_rRelationType = oox::getRelationship(Relationship::PACKAGE);
break ;
}
}
if (!pFilterName)
{
SAL_WARN("oox.shape" , "oox::GetOLEObjectStream: unknown ClassId " << classId.GetHexName());
return nullptr;
}
if (embed::EmbedStates::LOADED == xObj->getCurrentState())
{
xObj->changeState(embed::EmbedStates::RUNNING);
}
// use a temp stream - while it would work to store directly to a
// fragment stream, an error during export means we'd have to delete it
uno::Reference<io::XStream> const xTempStream(
xContext->getServiceManager()->createInstanceWithContext(
u"com.sun.star.comp.MemoryStream" _ustr, xContext),
uno::UNO_QUERY_THROW);
uno::Sequence<beans::PropertyValue> args( comphelper::InitPropertySequence({
{ "OutputStream" , Any(xTempStream->getOutputStream()) },
{ "FilterName" , Any(OUString::createFromAscii(pFilterName)) }
}));
uno::Reference<frame::XStorable> xStorable(xObj->getComponent(), uno::UNO_QUERY);
try
{
xStorable->storeToURL(u"private:stream" _ustr, args);
}
catch (uno::Exception const &)
{
TOOLS_WARN_EXCEPTION("oox.shape" , "oox::GetOLEObjectStream" );
return nullptr;
}
xTempStream->getOutputStream()->closeOutput();
return xTempStream->getInputStream();
}
uno::Reference<io::XInputStream> GetOLEObjectStream(
uno::Reference<uno::XComponentContext> const & xContext,
uno::Reference<embed::XEmbeddedObject> const & xObj,
std::u16string_view i_rProgID,
OUString & o_rMediaType,
OUString & o_rRelationType,
OUString & o_rSuffix,
const char *& o_rpProgID)
{
uno::Reference<io::XInputStream> xInStream;
try
{
uno::Reference<document::XStorageBasedDocument> const xParent(
uno::Reference<container::XChild>(xObj, uno::UNO_QUERY_THROW)->getParent(),
uno::UNO_QUERY_THROW);
uno::Reference<embed::XStorage> const xParentStorage(xParent->getDocumentStorage());
OUString const entryName(
uno::Reference<embed::XEmbedPersist>(xObj, uno::UNO_QUERY_THROW)->getEntryName());
if (xParentStorage->isStreamElement(entryName))
{
lcl_ConvertProgID(i_rProgID, o_rMediaType, o_rRelationType, o_rSuffix);
xInStream = xParentStorage->cloneStreamElement(entryName)->getInputStream();
// TODO: make it possible to take the sMediaType from the stream
}
else // the object is ODF - either the whole document is
{ // ODF, or the OLE was edited so it was converted to ODF
xInStream = lcl_StoreOwnAsOOXML(xContext, xObj,
o_rpProgID, o_rMediaType, o_rRelationType, o_rSuffix);
}
}
catch (uno::Exception const &)
{
TOOLS_WARN_EXCEPTION("oox.shape" , "oox::GetOLEObjectStream" );
}
return xInStream;
}
} // namespace oox
namespace oox::drawingml {
ShapeExport::ShapeExport( sal_Int32 nXmlNamespace, FSHelperPtr pFS, ShapeHashMap* pShapeMap, XmlFilterBase* pFB, DocumentType eDocumentType, DMLTextExport* pTextExport, bool bUserShapes )
: DrawingML( std::move(pFS), pFB, eDocumentType, pTextExport )
, m_nEmbeddedObjects(0)
, mnShapeIdMax( 1 )
, mbUserShapes( bUserShapes )
, mnXmlNamespace( nXmlNamespace )
, maMapModeSrc( MapUnit::Map100thMM )
, maMapModeDest( MapUnit::MapInch, Point(), Fraction( 1, 576 ), Fraction( 1, 576 ) )
, mpShapeMap( pShapeMap ? pShapeMap : &maShapeMap )
{
mpURLTransformer = std::make_shared<URLTransformer>();
}
void ShapeExport::SetURLTranslator(const std::shared_ptr<URLTransformer>& pTransformer)
{
mpURLTransformer = pTransformer;
}
awt::Size ShapeExport::MapSize( const awt::Size& rSize ) const
{
Size aRetSize( OutputDevice::LogicToLogic( Size( rSize.Width, rSize.Height ), maMapModeSrc, maMapModeDest ) );
if ( !aRetSize.Width() )
aRetSize.AdjustWidth( 1 );
if ( !aRetSize.Height() )
aRetSize.AdjustHeight( 1 );
return awt::Size( aRetSize.Width(), aRetSize.Height() );
}
static bool IsNonEmptySimpleText(const Reference<XInterface>& xIface)
{
if (Reference<XSimpleText> xText{ xIface, UNO_QUERY })
return xText->getString().getLength();
return false ;
}
bool ShapeExport::NonEmptyText( const Reference< XInterface >& xIface )
{
Reference< XPropertySet > xPropSet( xIface, UNO_QUERY );
if ( xPropSet.is() )
{
Reference< XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
if ( xPropSetInfo.is() )
{
if ( xPropSetInfo->hasPropertyByName( u"IsEmptyPresentationObject" _ustr ) )
{
bool bIsEmptyPresObj = false ;
if ( xPropSet->getPropertyValue( u"IsEmptyPresentationObject" _ustr ) >>= bIsEmptyPresObj )
{
SAL_INFO("oox.shape" , "empty presentation object " << bIsEmptyPresObj << " , props:" );
if ( bIsEmptyPresObj )
return true ;
}
}
if ( xPropSetInfo->hasPropertyByName( u"IsPresentationObject" _ustr ) )
{
bool bIsPresObj = false ;
if ( xPropSet->getPropertyValue( u"IsPresentationObject" _ustr ) >>= bIsPresObj )
{
SAL_INFO("oox.shape" , "presentation object " << bIsPresObj << ", props:" );
if ( bIsPresObj )
return true ;
}
}
}
}
return IsNonEmptySimpleText(xIface);
}
static void AddExtLst(FSHelperPtr const & pFS, Reference<XPropertySet> const & xShape)
{
if (xShape->getPropertySetInfo()->hasPropertyByName(u"Decorative" _ustr)
&& xShape->getPropertyValue(u"Decorative" _ustr).get<bool >())
{
pFS->startElementNS(XML_a, XML_extLst);
// FSNS(XML_xmlns, XML_a), GetExport().GetFilter().getNamespaceURL(OOX_NS(dml)));
pFS->startElementNS(XML_a, XML_ext,
// MSO uses this "URI" which is obviously not a URI
XML_uri, "{C183D7F6-B498-43B3-948B-1728B52AA6E4}" );
pFS->singleElementNS(XML_adec, XML_decorative,
FSNS(XML_xmlns, XML_adec), "http://schemas.microsoft.com/office/drawing/2017/decorative ",
XML_val, "1" );
pFS->endElementNS(XML_a, XML_ext);
pFS->endElementNS(XML_a, XML_extLst);
}
}
ShapeExport& ShapeExport::WritePolyPolygonShape( const Reference< XShape >& xShape, const bool bClosed )
{
SAL_INFO("oox.shape" , "write polypolygon shape" );
FSHelperPtr pFS = GetFS();
pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp));
awt::Point aPos = xShape->getPosition();
// Position is relative to group for child elements in Word, but absolute in API.
if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes && m_xParent.is())
{
awt::Point aParentPos = m_xParent->getPosition();
aPos.X -= aParentPos.X;
aPos.Y -= aParentPos.Y;
}
awt::Size aSize = xShape->getSize();
tools::Rectangle aRect(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height));
#if OSL_DEBUG_LEVEL > 0
tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(xShape);
awt::Size size = MapSize( awt::Size( aRect.GetWidth(), aRect.GetHeight() ) );
SAL_INFO("oox.shape" , "poly count " << aPolyPolygon.Count());
SAL_INFO("oox.shape" , "size: " << size.Width << " x " << size.Height);
#endif
Reference<XPropertySet> const xProps(xShape, UNO_QUERY);
// non visual shape properties
if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
{
pFS->startElementNS(mnXmlNamespace, XML_nvSpPr);
pFS->startElementNS(mnXmlNamespace, XML_cNvPr,
XML_id, OString::number(GetNewShapeID(xShape)),
XML_name, GetShapeName(xShape));
AddExtLst(pFS, xProps);
pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
}
pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);
if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
{
WriteNonVisualProperties( xShape );
pFS->endElementNS( mnXmlNamespace, XML_nvSpPr );
}
// visual shape properties
pFS->startElementNS(mnXmlNamespace, XML_spPr);
WriteTransformation( xShape, aRect, XML_a );
WritePolyPolygon(xShape, bClosed);
if ( xProps.is() ) {
if ( bClosed )
WriteFill(xProps, aSize);
WriteOutline( xProps );
}
pFS->endElementNS( mnXmlNamespace, XML_spPr );
// write text
WriteTextBox( xShape, mnXmlNamespace );
pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) );
return *this ;
}
ShapeExport& ShapeExport::WriteClosedPolyPolygonShape( const Reference< XShape >& xShape )
{
return WritePolyPolygonShape( xShape, true );
}
ShapeExport& ShapeExport::WriteOpenPolyPolygonShape( const Reference< XShape >& xShape )
{
return WritePolyPolygonShape( xShape, false );
}
ShapeExport& ShapeExport::WriteGroupShape(const uno::Reference<drawing::XShape>& xShape)
{
FSHelperPtr pFS = GetFS();
sal_Int32 nGroupShapeToken = XML_grpSp;
if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes)
{
if (!m_xParent.is())
nGroupShapeToken = XML_wgp; // toplevel
else
mnXmlNamespace = XML_wpg;
}
pFS->startElementNS(mnXmlNamespace, nGroupShapeToken);
// non visual properties
if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
{
pFS->startElementNS(mnXmlNamespace, XML_nvGrpSpPr);
pFS->startElementNS(mnXmlNamespace, XML_cNvPr,
XML_id, OString::number(GetNewShapeID(xShape)),
XML_name, GetShapeName(xShape));
uno::Reference<beans::XPropertySet> const xShapeProps(xShape, uno::UNO_QUERY_THROW);
AddExtLst(pFS, xShapeProps);
pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
pFS->singleElementNS(mnXmlNamespace, XML_cNvGrpSpPr);
WriteNonVisualProperties(xShape );
pFS->endElementNS(mnXmlNamespace, XML_nvGrpSpPr);
}
else
pFS->singleElementNS(mnXmlNamespace, XML_cNvGrpSpPr);
// visual properties
pFS->startElementNS(mnXmlNamespace, XML_grpSpPr);
WriteShapeTransformation(xShape, XML_a, false , false , true );
pFS->endElementNS(mnXmlNamespace, XML_grpSpPr);
uno::Reference<drawing::XShapes> xGroupShape(xShape, uno::UNO_QUERY_THROW);
uno::Reference<drawing::XShape> xParent = m_xParent;
m_xParent = xShape;
for (sal_Int32 i = 0; i < xGroupShape->getCount(); ++i)
{
uno::Reference<drawing::XShape> xChild(xGroupShape->getByIndex(i), uno::UNO_QUERY_THROW);
sal_Int32 nSavedNamespace = mnXmlNamespace;
uno::Reference<lang::XServiceInfo> xServiceInfo(xChild, uno::UNO_QUERY_THROW);
if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes)
{
// tdf#128820: WriteGraphicObjectShapePart calls WriteTextShape for non-empty simple
// text objects, which needs writing into wps::wsp element, so make sure to use wps
// namespace for those objects
if (xServiceInfo->supportsService(u"com.sun.star.drawing.GraphicObjectShape" _ustr)
&& !IsNonEmptySimpleText(xChild))
mnXmlNamespace = XML_pic;
else
mnXmlNamespace = XML_wps;
}
WriteShape(xChild);
mnXmlNamespace = nSavedNamespace;
}
m_xParent = std::move(xParent);
pFS->endElementNS(mnXmlNamespace, nGroupShapeToken);
return *this ;
}
namespace
{
constexpr frozen::set<std::u16string_view, 57> constDenySet(
{
u"block-arc" ,
u"rectangle" ,
u"ellipse" ,
u"ring" ,
u"can" ,
u"cube" ,
u"paper" ,
u"frame" ,
u"forbidden" ,
u"smiley" ,
u"sun" ,
u"flower" ,
u"bracket-pair" ,
u"brace-pair" ,
u"quad-bevel" ,
u"round-rectangular-callout" ,
u"rectangular-callout" ,
u"round-callout" ,
u"cloud-callout" ,
u"line-callout-1" ,
u"line-callout-2" ,
u"line-callout-3" ,
u"paper" ,
u"vertical-scroll" ,
u"horizontal-scroll" ,
u"mso-spt34" ,
u"mso-spt75" ,
u"mso-spt164" ,
u"mso-spt180" ,
u"flowchart-process" ,
u"flowchart-alternate-process" ,
u"flowchart-decision" ,
u"flowchart-data" ,
u"flowchart-predefined-process" ,
u"flowchart-internal-storage" ,
u"flowchart-document" ,
u"flowchart-multidocument" ,
u"flowchart-terminator" ,
u"flowchart-preparation" ,
u"flowchart-manual-input" ,
u"flowchart-manual-operation" ,
u"flowchart-connector" ,
u"flowchart-off-page-connector" ,
u"flowchart-card" ,
u"flowchart-punched-tape" ,
u"flowchart-summing-junction" ,
u"flowchart-or" ,
u"flowchart-collate" ,
u"flowchart-sort" ,
u"flowchart-extract" ,
u"flowchart-merge" ,
u"flowchart-stored-data" ,
u"flowchart-delay" ,
u"flowchart-sequential-access" ,
u"flowchart-magnetic-disk" ,
u"flowchart-direct-access-storage" ,
u"flowchart-display"
});
constexpr frozen::set<std::u16string_view, 6> constAllowSet(
{
u"heart" ,
u"puzzle" ,
u"col-60da8460" ,
u"col-502ad400" ,
u"sinusoid" ,
u"mso-spt100"
});
} // end anonymous namespace
static bool lcl_IsOnDenylist(OUString const & rShapeType)
{
return constDenySet.find(rShapeType) != constDenySet.end();
}
static bool lcl_IsOnAllowlist(OUString const & rShapeType)
{
return constAllowSet.find(rShapeType) != constAllowSet.end();
}
static bool lcl_GetHandlePosition( sal_Int32 &nValue, const EnhancedCustomShapeParameter &rParam, const Sequence< EnhancedCustomShapeAdjustmentValue > &rSeq)
{
bool bAdj = false ;
if ( rParam.Value.getValueTypeClass() == TypeClass_DOUBLE )
{
double fValue(0.0);
if ( rParam.Value >>= fValue )
nValue = static_cast <sal_Int32>(fValue);
}
else
rParam.Value >>= nValue;
if ( rParam.Type == EnhancedCustomShapeParameterType::ADJUSTMENT)
{
bAdj = true ;
sal_Int32 nIdx = nValue;
if ( nIdx < rSeq.getLength() )
{
if ( rSeq[ nIdx ] .Value.getValueTypeClass() == TypeClass_DOUBLE )
{
double fValue(0.0);
rSeq[ nIdx ].Value >>= fValue;
nValue = fValue;
}
else
{
rSeq[ nIdx ].Value >>= nValue;
}
}
}
return bAdj;
}
static void lcl_AnalyzeHandles( const uno::Sequence<beans::PropertyValues> & rHandles,
std::vector< std::pair< sal_Int32, sal_Int32> > &rHandlePositionList,
const Sequence< EnhancedCustomShapeAdjustmentValue > &rSeq)
{
for ( const Sequence< PropertyValue >& rPropSeq : rHandles )
{
static constexpr OUStringLiteral sPosition( u"Position" );
bool bPosition = false ;
EnhancedCustomShapeParameterPair aPosition;
for ( const PropertyValue& rPropVal: rPropSeq )
{
if ( rPropVal.Name == sPosition )
{
if ( rPropVal.Value >>= aPosition )
bPosition = true ;
}
}
if ( bPosition )
{
sal_Int32 nXPosition = 0;
sal_Int32 nYPosition = 0;
// For polar handles, nXPosition is radius and nYPosition is angle
lcl_GetHandlePosition( nXPosition, aPosition.First , rSeq );
lcl_GetHandlePosition( nYPosition, aPosition.Second, rSeq );
rHandlePositionList.emplace_back( nXPosition, nYPosition );
}
}
}
static void lcl_AppendAdjustmentValue( std::vector< std::pair< sal_Int32, sal_Int32> > &rAvList, sal_Int32 nAdjIdx, sal_Int32 nValue )
{
rAvList.emplace_back( nAdjIdx , nValue );
}
static sal_Int32 lcl_NormalizeAngle( sal_Int32 nAngle )
{
nAngle = nAngle % 360;
return nAngle < 0 ? ( nAngle + 360 ) : nAngle ;
}
static sal_Int32 lcl_CircleAngle2CustomShapeEllipseAngleOOX(const sal_Int32 nInternAngle, const sal_Int32 nWidth, const sal_Int32 nHeight)
{
if (nWidth != 0 || nHeight != 0)
{
double fAngle = basegfx::deg2rad<100>(nInternAngle); // intern 1/100 deg to rad
fAngle = atan2(nHeight * sin(fAngle), nWidth * cos(fAngle)); // circle to ellipse
fAngle = basegfx::rad2deg<60000>(fAngle); // rad to OOXML angle unit
sal_Int32 nAngle = basegfx::fround(fAngle); // normalize
nAngle = nAngle % 21600000;
return nAngle < 0 ? (nAngle + 21600000) : nAngle;
}
else // should be handled by caller, dummy value
return 0;
}
static OUString lcl_GetTarget(const css::uno::Reference<css::frame::XModel>& xModel,
std::u16string_view rURL)
{
Reference<drawing::XDrawPagesSupplier> xDPS(xModel, uno::UNO_QUERY_THROW);
Reference<drawing::XDrawPages> xDrawPages(xDPS->getDrawPages(), uno::UNO_SET_THROW);
sal_uInt32 nPageCount = xDrawPages->getCount();
OUString sTarget;
for (sal_uInt32 i = 0; i < nPageCount; ++i)
{
Reference<XDrawPage> xDrawPage;
xDrawPages->getByIndex(i) >>= xDrawPage;
Reference<container::XNamed> xNamed(xDrawPage, UNO_QUERY);
if (!xNamed)
continue ;
OUString sSlideName = "#" + xNamed->getName();
if (rURL == sSlideName)
{
sTarget = "slide" + OUString::number(i + 1) + ".xml" ;
break ;
}
}
return sTarget;
}
ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape )
{
SAL_INFO("oox.shape" , "write custom shape" );
Reference< XPropertySet > rXPropSet( xShape, UNO_QUERY );
// First check, if this is a Fontwork-shape. For DrawingML, such a shape is a
// TextBox shape with body property prstTxWarp.
if (IsFontworkShape(rXPropSet))
{
ShapeExport::WriteTextShape(xShape); // qualifier to prevent PowerPointShapeExport
return *this ;
}
bool bHasGeometrySeq(false );
Sequence< PropertyValue > aGeometrySeq;
OUString sShapeType(u"non-primitive" _ustr); // default in ODF
if (GetProperty(rXPropSet, u"CustomShapeGeometry" _ustr))
{
SAL_INFO("oox.shape" , "got custom shape geometry" );
if (mAny >>= aGeometrySeq)
{
bHasGeometrySeq = true ;
SAL_INFO("oox.shape" , "got custom shape geometry sequence" );
for (const PropertyValue& rProp : aGeometrySeq)
{
SAL_INFO("oox.shape" , "geometry property: " << rProp.Name);
if (rProp.Name == "Type" )
rProp.Value >>= sShapeType;
}
}
}
bool bPredefinedHandlesUsed = true ;
bool bHasHandles = false ;
ShapeFlag nMirrorFlags = ShapeFlag::NONE;
MSO_SPT eShapeType = EscherPropertyContainer::GetCustomShapeType( xShape, nMirrorFlags, sShapeType );
assert(dynamic_cast < SdrObjCustomShape* >(SdrObject::getSdrObjectFromXShape(xShape)) && "Not a SdrObjCustomShape (!)" );
SdrObjCustomShape& rSdrObjCustomShape(static_cast < SdrObjCustomShape& >(*SdrObject::getSdrObjectFromXShape(xShape)));
const bool bIsDefaultObject(
EscherPropertyContainer::IsDefaultObject(
rSdrObjCustomShape,
eShapeType));
OString sPresetShape = msfilter::util::GetOOXMLPresetGeometry(sShapeType);
SAL_INFO("oox.shape" , "custom shape type: " << sShapeType << " ==> " << sPresetShape);
sal_Int32 nAdjustmentValuesIndex = -1;
awt::Rectangle aViewBox;
uno::Sequence<beans::PropertyValues> aHandles;
bool bFlipH = false ;
bool bFlipV = false ;
if (bHasGeometrySeq)
{
for (int i = 0; i < aGeometrySeq.getLength(); i++)
{
const PropertyValue& rProp = aGeometrySeq[ i ];
SAL_INFO("oox.shape" , "geometry property: " << rProp.Name);
if ( rProp.Name == "MirroredX" )
rProp.Value >>= bFlipH;
if ( rProp.Name == "MirroredY" )
rProp.Value >>= bFlipV;
if ( rProp.Name == "AdjustmentValues" )
nAdjustmentValuesIndex = i;
else if ( rProp.Name == "Handles" )
{
rProp.Value >>= aHandles;
if ( aHandles.hasElements() )
bHasHandles = true ;
if ( !bIsDefaultObject )
bPredefinedHandlesUsed = false ;
// TODO: update nAdjustmentsWhichNeedsToBeConverted here
}
else if ( rProp.Name == "ViewBox" )
rProp.Value >>= aViewBox;
}
}
FSHelperPtr pFS = GetFS();
// non visual shape properties
if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
{
// get InteropGrabBag to export attributes stored in the grabbag
uno::Sequence<beans::PropertyValue> aGrabBagProps;
rXPropSet->getPropertyValue(u"InteropGrabBag" _ustr) >>= aGrabBagProps;
bool bUseBackground = false ;
if (GetProperty(rXPropSet, u"FillUseSlideBackground" _ustr))
mAny >>= bUseBackground;
if (bUseBackground)
mpFS->startElementNS(mnXmlNamespace, XML_sp, XML_useBgFill, "1" );
else
{
rtl::Reference<sax_fastparser::FastAttributeList> pAttrListSp
= sax_fastparser::FastSerializerHelper::createAttrList();
for (auto const & it : aGrabBagProps)
{
// export macro attribute of <sp> element
if (it.Name == u"mso-sp-macro" _ustr)
{
OUString sMacro;
it.Value >>= sMacro;
if (!sMacro.isEmpty())
pAttrListSp->add(XML_macro, sMacro);
}
// export textlink attribute of <sp> element
if (it.Name == u"mso-sp-textlink" _ustr)
{
OUString sTextLink;
it.Value >>= sTextLink;
if (!sTextLink.isEmpty())
pAttrListSp->add(XML_textlink, sTextLink);
}
// export fLocksText attribute of <sp> element
if (it.Name == u"mso-sp-fLocksText" _ustr)
{
bool bFLocksText = true ; // default="true"
it.Value >>= bFLocksText;
pAttrListSp->add(XML_fLocksText, ToPsz10(bFLocksText));
}
// export fPublished attribute of <sp> element
if (it.Name == u"mso-sp-fPublished" _ustr)
{
bool bFPublished = false ;
it.Value >>= bFPublished;
pAttrListSp->add(XML_fPublished, ToPsz10(bFPublished));
}
}
// export <sp> element (with a namespace prefix)
mpFS->startElementNS(mnXmlNamespace, XML_sp, pAttrListSp);
}
bool isVisible = true ;
if ( GetProperty(rXPropSet, u"Visible" _ustr))
{
mAny >>= isVisible;
}
pFS->startElementNS( mnXmlNamespace, XML_nvSpPr );
// export descr attribute of <cNvPr> element
OUString sDescr;
if (GetProperty(rXPropSet, u"Description" _ustr))
mAny >>= sDescr;
// export title attribute of <cNvPr> element
OUString sTitle;
if (GetProperty(rXPropSet, u"Title" _ustr))
mAny >>= sTitle;
// export <cNvPr> element
pFS->startElementNS(
mnXmlNamespace, XML_cNvPr, XML_id,
OString::number(GetShapeID(xShape) == -1 ? GetNewShapeID(xShape) : GetShapeID(xShape)),
XML_name, GetShapeName(xShape), XML_hidden, sax_fastparser::UseIf("1" , !isVisible),
XML_descr, sax_fastparser::UseIf(sDescr, !sDescr.isEmpty()), XML_title,
sax_fastparser::UseIf(sTitle, !sTitle.isEmpty()));
rtl::Reference<sax_fastparser::FastAttributeList> pAttrListHlinkClick
= sax_fastparser::FastSerializerHelper::createAttrList();
for (auto const & it : aGrabBagProps)
{
// export tooltip attribute of <hlinkClick> element
if (it.Name == u"mso-hlinkClick-tooltip" _ustr)
{
OUString sTooltip;
it.Value >>= sTooltip;
if (!sTooltip.isEmpty())
pAttrListHlinkClick->add(XML_tooltip, sTooltip);
}
}
if ( GetProperty(rXPropSet, u"URL" _ustr) )
{
OUString sURL;
mAny >>= sURL;
if ( !sURL.isEmpty() )
{
OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(),
oox::getRelationship(Relationship::HYPERLINK),
mpURLTransformer->getTransformedString(sURL),
mpURLTransformer->isExternalURL(sURL));
mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
// pAttrListHlinkClick->add(FSNS(XML_r, XML_id), sRelId);
}
}
// // export <hlinkClick> element
// mpFS->singleElementNS(XML_a, XML_hlinkClick, pAttrListHlinkClick);
OUString sBookmark;
if (GetProperty(rXPropSet, u"Bookmark" _ustr))
mAny >>= sBookmark;
if (GetProperty(rXPropSet, u"OnClick" _ustr))
{
OUString sPPAction;
presentation::ClickAction eClickAction = presentation::ClickAction_NONE;
mAny >>= eClickAction;
if (eClickAction != presentation::ClickAction_NONE)
{
switch (eClickAction)
{
case presentation::ClickAction_STOPPRESENTATION:
sPPAction = "ppaction://hlinkshowjump?jump=endshow";
break ;
case presentation::ClickAction_NEXTPAGE:
sPPAction = "ppaction://hlinkshowjump?jump=nextslide";
break ;
case presentation::ClickAction_LASTPAGE:
sPPAction = "ppaction://hlinkshowjump?jump=lastslide";
break ;
case presentation::ClickAction_PREVPAGE:
sPPAction = "ppaction://hlinkshowjump?jump=previousslide";
break ;
case presentation::ClickAction_FIRSTPAGE:
sPPAction = "ppaction://hlinkshowjump?jump=firstslide";
break ;
case presentation::ClickAction_BOOKMARK:
sBookmark = "#" + sBookmark;
break ;
default :
break ;
}
}
if (!sPPAction.isEmpty())
pFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "" , XML_action,
sPPAction);
}
if (!sBookmark.isEmpty())
{
bool bExtURL = URLTransformer().isExternalURL(sBookmark);
sBookmark = bExtURL ? sBookmark : lcl_GetTarget(GetFB()->getModel(), sBookmark);
OUString sRelId
= mpFB->addRelation(mpFS->getOutputStream(),
bExtURL ? oox::getRelationship(Relationship::HYPERLINK)
: oox::getRelationship(Relationship::SLIDE),
sBookmark, bExtURL);
if (bExtURL)
mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
else
mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId,
XML_action, "ppaction://hlinksldjump");
}
AddExtLst(pFS, rXPropSet);
pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);
WriteNonVisualProperties( xShape );
pFS->endElementNS( mnXmlNamespace, XML_nvSpPr );
}
else
{
pFS->startElementNS(mnXmlNamespace, XML_wsp);
if (m_xParent.is())
{
pFS->startElementNS(mnXmlNamespace, XML_cNvPr, XML_id,
OString::number(GetShapeID(xShape) == -1 ? GetNewShapeID(xShape)
: GetShapeID(xShape)),
XML_name, GetShapeName(xShape));
if (GetProperty(rXPropSet, u"Hyperlink" _ustr))
{
OUString sURL;
mAny >>= sURL;
if (!sURL.isEmpty())
{
OUString sRelId = mpFB->addRelation(
mpFS->getOutputStream(), oox::getRelationship(Relationship::HYPERLINK),
mpURLTransformer->getTransformedString(sURL),
mpURLTransformer->isExternalURL(sURL));
mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
}
}
AddExtLst(pFS, rXPropSet);
pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
}
pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);
}
// visual shape properties
pFS->startElementNS(mnXmlNamespace, XML_spPr);
// we export non-primitive shapes to custom geometry
// we also export non-ooxml shapes which have handles/equations to custom geometry, because
// we cannot convert ODF equations to DrawingML equations. TODO: see what binary DOC export filter does.
// but our WritePolyPolygon()/WriteCustomGeometry() functions are incomplete, therefore we use a denylist
// we use a allowlist for shapes where mapping to MSO preset shape is not optimal
bool bCustGeom = true ;
bool bOnDenylist = false ;
if ( sShapeType == "ooxml-non-primitive" )
bCustGeom = true ;
else if ( sShapeType.startsWith("ooxml" ) )
bCustGeom = false ;
else if ( lcl_IsOnAllowlist(sShapeType) )
bCustGeom = true ;
else if ( lcl_IsOnDenylist(sShapeType) )
{
bCustGeom = false ;
bOnDenylist = true ;
}
bool bPresetWriteSuccessful = false ;
// Let the custom shapes what has name and preset information in OOXML, to be written
// as preset ones with parameters. Try that with this converter class.
if (!sShapeType.startsWith("ooxml" ) && sShapeType != "non-primitive" && !mbUserShapes
&& xShape->getShapeType() == "com.sun.star.drawing.CustomShape"
&& !lcl_IsOnAllowlist(sShapeType))
{
DMLPresetShapeExporter aCustomShapeConverter(this , xShape);
bPresetWriteSuccessful = aCustomShapeConverter.WriteShape();
}
// If preset writing has problems try to write the shape as it done before
if (bPresetWriteSuccessful)
;// Already written do nothing.
else if (bCustGeom)
{
WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV );
bool bSuccess = WriteCustomGeometry(xShape, rSdrObjCustomShape);
// In case of Writer, the parent element is <wps:spPr>, and there the <a:custGeom> element
// is not optional.
if (!bSuccess && GetDocumentType() == DOCUMENT_DOCX)
{
WriteEmptyCustomGeometry();
}
}
else if (bOnDenylist && bHasHandles && nAdjustmentValuesIndex !=-1 && !sShapeType.startsWith("mso-spt" ))
{
WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV );
Sequence< EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq;
std::vector< std::pair< sal_Int32, sal_Int32> > aHandlePositionList;
std::vector< std::pair< sal_Int32, sal_Int32> > aAvList;
aGeometrySeq[ nAdjustmentValuesIndex ].Value >>= aAdjustmentSeq ;
lcl_AnalyzeHandles( aHandles, aHandlePositionList, aAdjustmentSeq );
sal_Int32 nXPosition = 0;
sal_Int32 nYPosition = 0;
if ( !aHandlePositionList.empty() )
{
nXPosition = aHandlePositionList[0].first ;
nYPosition = aHandlePositionList[0].second ;
}
switch ( eShapeType )
{
case mso_sptBorderCallout1:
{
sal_Int32 adj3 = double (nYPosition)/aViewBox.Height *100000;
sal_Int32 adj4 = double (nXPosition)/aViewBox.Width *100000;
lcl_AppendAdjustmentValue( aAvList, 1, 18750 );
lcl_AppendAdjustmentValue( aAvList, 2, -8333 );
lcl_AppendAdjustmentValue( aAvList, 3, adj3 );
lcl_AppendAdjustmentValue( aAvList, 4, adj4 );
break ;
}
case mso_sptBorderCallout2:
{
sal_Int32 adj5 = double (nYPosition)/aViewBox.Height *100000;
sal_Int32 adj6 = double (nXPosition)/aViewBox.Width *100000;
sal_Int32 adj3 = 18750;
sal_Int32 adj4 = -16667;
lcl_AppendAdjustmentValue( aAvList, 1, 18750 );
lcl_AppendAdjustmentValue( aAvList, 2, -8333 );
if ( aHandlePositionList.size() > 1 )
{
nXPosition = aHandlePositionList[1].first ;
nYPosition = aHandlePositionList[1].second ;
adj3 = double (nYPosition)/aViewBox.Height *100000;
adj4 = double (nXPosition)/aViewBox.Width *100000;
}
lcl_AppendAdjustmentValue( aAvList, 3, adj3 );
lcl_AppendAdjustmentValue( aAvList, 4, adj4 );
lcl_AppendAdjustmentValue( aAvList, 5, adj5 );
lcl_AppendAdjustmentValue( aAvList, 6, adj6 );
break ;
}
case mso_sptWedgeRectCallout:
case mso_sptWedgeRRectCallout:
case mso_sptWedgeEllipseCallout:
case mso_sptCloudCallout:
{
sal_Int32 adj1 = (double (nXPosition)/aViewBox.Width -0.5) *100000;
sal_Int32 adj2 = (double (nYPosition)/aViewBox.Height -0.5) *100000;
lcl_AppendAdjustmentValue( aAvList, 1, adj1 );
lcl_AppendAdjustmentValue( aAvList, 2, adj2 );
if ( eShapeType == mso_sptWedgeRRectCallout)
{
lcl_AppendAdjustmentValue( aAvList, 3, 16667);
}
break ;
}
case mso_sptFoldedCorner:
{
sal_Int32 adj = double ( aViewBox.Width - nXPosition) / std::min( aViewBox.Width,aViewBox.Height ) * 100000;
lcl_AppendAdjustmentValue( aAvList, 0, adj );
break ;
}
case mso_sptDonut:
case mso_sptSun:
case mso_sptMoon:
case mso_sptNoSmoking:
case mso_sptHorizontalScroll:
case mso_sptBevel:
case mso_sptBracketPair:
{
sal_Int32 adj = double ( nXPosition )/aViewBox.Width*100000 ;
lcl_AppendAdjustmentValue( aAvList, 0, adj );
break ;
}
case mso_sptCan:
case mso_sptCube:
case mso_sptBracePair:
case mso_sptVerticalScroll:
{
sal_Int32 adj = double ( nYPosition )/aViewBox.Height *100000 ;
lcl_AppendAdjustmentValue( aAvList, 0, adj );
break ;
}
case mso_sptSmileyFace:
{
sal_Int32 adj = double ( nYPosition )/aViewBox.Height *100000 - 76458.0;
lcl_AppendAdjustmentValue( aAvList, 0, adj );
break ;
}
case mso_sptBlockArc:
{
sal_Int32 nRadius = 50000 * ( 1 - double (nXPosition) / 10800);
sal_Int32 nAngleStart = lcl_NormalizeAngle( nYPosition );
sal_Int32 nAngleEnd = lcl_NormalizeAngle( 180 - nAngleStart );
lcl_AppendAdjustmentValue( aAvList, 1, 21600000 / 360 * nAngleStart );
lcl_AppendAdjustmentValue( aAvList, 2, 21600000 / 360 * nAngleEnd );
lcl_AppendAdjustmentValue( aAvList, 3, nRadius );
break ;
}
// case mso_sptNil:
// case mso_sptBentConnector3:
// case mso_sptBorderCallout3:
default :
{
if ( sPresetShape == "frame" )
{
sal_Int32 adj1 = double ( nYPosition )/aViewBox.Height *100000 ;
lcl_AppendAdjustmentValue( aAvList, 1, adj1 );
}
break ;
}
}
WritePresetShape( sPresetShape , aAvList );
}
else // preset geometry
{
WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV );
if ( nAdjustmentValuesIndex != -1 )
{
WritePresetShape( sPresetShape, eShapeType, bPredefinedHandlesUsed,
aGeometrySeq[ nAdjustmentValuesIndex ] );
}
else
WritePresetShape( sPresetShape );
}
if ( rXPropSet.is() )
{
WriteFill(rXPropSet, xShape->getSize());
WriteOutline( rXPropSet );
WriteShapeEffects( rXPropSet );
bool bHas3DEffectinShape = false ;
uno::Sequence<beans::PropertyValue> grabBag;
rXPropSet->getPropertyValue(u"InteropGrabBag" _ustr) >>= grabBag;
for (auto const & it : grabBag)
if (it.Name == "3DEffectProperties" )
bHas3DEffectinShape = true ;
if ( bHas3DEffectinShape)
Write3DEffects( rXPropSet, /*bIsText=*/false );
}
pFS->endElementNS( mnXmlNamespace, XML_spPr );
pFS->startElementNS(mnXmlNamespace, XML_style);
WriteShapeStyle( rXPropSet );
pFS->endElementNS( mnXmlNamespace, XML_style );
// write text
WriteTextBox( xShape, mnXmlNamespace );
pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) );
return *this ;
}
ShapeExport& ShapeExport::WriteEllipseShape( const Reference< XShape >& xShape )
{
SAL_INFO("oox.shape" , "write ellipse shape" );
FSHelperPtr pFS = GetFS();
pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp));
// TODO: connector ?
Reference<XPropertySet> const xProps(xShape, UNO_QUERY);
// non visual shape properties
if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
{
pFS->startElementNS(mnXmlNamespace, XML_nvSpPr);
pFS->startElementNS(mnXmlNamespace, XML_cNvPr,
XML_id, OString::number(GetNewShapeID(xShape)),
XML_name, GetShapeName(xShape));
AddExtLst(pFS, xProps);
pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
pFS->singleElementNS( mnXmlNamespace, XML_cNvSpPr );
WriteNonVisualProperties( xShape );
pFS->endElementNS( mnXmlNamespace, XML_nvSpPr );
}
else
pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr);
CircleKind eCircleKind(CircleKind_FULL);
if (xProps.is())
xProps->getPropertyValue(u"CircleKind" _ustr ) >>= eCircleKind;
// visual shape properties
pFS->startElementNS( mnXmlNamespace, XML_spPr );
WriteShapeTransformation( xShape, XML_a );
if (CircleKind_FULL == eCircleKind)
WritePresetShape("ellipse" _ostr);
else
{
sal_Int32 nStartAngleIntern(9000);
sal_Int32 nEndAngleIntern(0);
if (xProps.is())
{
xProps->getPropertyValue(u"CircleStartAngle" _ustr ) >>= nStartAngleIntern;
xProps->getPropertyValue(u"CircleEndAngle" _ustr) >>= nEndAngleIntern;
}
std::vector< std::pair<sal_Int32,sal_Int32>> aAvList;
awt::Size aSize = xShape->getSize();
if (aSize.Width != 0 || aSize.Height != 0)
{
// Our arc has 90° up, OOXML has 90° down, so mirror it.
// API angles are 1/100 degree.
sal_Int32 nStartAngleOOXML(lcl_CircleAngle2CustomShapeEllipseAngleOOX(36000 - nEndAngleIntern, aSize.Width, aSize.Height));
sal_Int32 nEndAngleOOXML(lcl_CircleAngle2CustomShapeEllipseAngleOOX(36000 - nStartAngleIntern, aSize.Width, aSize.Height));
lcl_AppendAdjustmentValue( aAvList, 1, nStartAngleOOXML);
lcl_AppendAdjustmentValue( aAvList, 2, nEndAngleOOXML);
}
switch (eCircleKind)
{
case CircleKind_ARC :
WritePresetShape("arc" _ostr, aAvList);
break ;
case CircleKind_SECTION :
WritePresetShape("pie" _ostr, aAvList);
break ;
case CircleKind_CUT :
WritePresetShape("chord" _ostr, aAvList);
break ;
default :
WritePresetShape("ellipse" _ostr);
}
}
if ( xProps.is() )
{
if (CircleKind_ARC == eCircleKind)
{
// An arc in ODF is never filled, even if a fill style other than
// "none" is set. OOXML arc can be filled, so set fill explicit to
// NONE, otherwise some hidden or inherited filling is shown.
FillStyle eFillStyle(FillStyle_NONE);
uno::Any aNewValue;
aNewValue <<= eFillStyle;
xProps->setPropertyValue(u"FillStyle" _ustr, aNewValue);
}
WriteFill( xProps );
WriteOutline( xProps );
}
pFS->endElementNS( mnXmlNamespace, XML_spPr );
// write text
WriteTextBox( xShape, mnXmlNamespace );
pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) );
return *this ;
}
ShapeExport& ShapeExport::WriteGraphicObjectShape( const Reference< XShape >& xShape )
{
WriteGraphicObjectShapePart( xShape );
return *this ;
}
void ShapeExport::WriteGraphicObjectShapePart( const Reference< XShape >& xShape, const Graphic* pGraphic )
{
SAL_INFO("oox.shape" , "write graphic object shape" );
if (IsNonEmptySimpleText(xShape))
{
SAL_INFO("oox.shape" , "graphicObject: wrote only text" );
WriteTextShape(xShape);
return ;
}
SAL_INFO("oox.shape" , "graphicObject without text" );
uno::Reference<graphic::XGraphic> xGraphic;
OUString sMediaURL;
Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY );
if (pGraphic)
{
xGraphic.set(pGraphic->GetXGraphic());
}
else if (xShapeProps.is() && xShapeProps->getPropertySetInfo()->hasPropertyByName(u"Graphic" _ustr))
{
xShapeProps->getPropertyValue(u"Graphic" _ustr) >>= xGraphic;
}
// tdf#155903 Only for PPTX, Microsoft does not support this feature in Word and Excel.
bool bHasMediaURL = GetDocumentType() == DOCUMENT_PPTX && xShapeProps.is()
&& xShapeProps->getPropertySetInfo()->hasPropertyByName(u"MediaURL" _ustr)
&& (xShapeProps->getPropertyValue(u"MediaURL" _ustr) >>= sMediaURL);
if (!xGraphic.is() && !bHasMediaURL)
{
SAL_INFO("oox.shape" , "no graphic or media URL found" );
return ;
}
FSHelperPtr pFS = GetFS();
XmlFilterBase* pFB = GetFB();
if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes)
pFS->startElementNS(mnXmlNamespace, XML_pic);
else
pFS->startElementNS(mnXmlNamespace, XML_pic,
FSNS(XML_xmlns, XML_pic), pFB->getNamespaceURL(OOX_NS(dmlPicture)));
pFS->startElementNS(mnXmlNamespace, XML_nvPicPr);
presentation::ClickAction eClickAction = presentation::ClickAction_NONE;
OUString sDescr, sURL, sBookmark, sPPAction;
bool bHaveDesc;
if ( ( bHaveDesc = GetProperty( xShapeProps, u"Description" _ustr ) ) )
mAny >>= sDescr;
if ( GetProperty( xShapeProps, u"URL" _ustr ) )
mAny >>= sURL;
if (GetProperty(xShapeProps, u"Bookmark" _ustr))
mAny >>= sBookmark;
if (GetProperty(xShapeProps, u"OnClick" _ustr))
mAny >>= eClickAction;
pFS->startElementNS( mnXmlNamespace, XML_cNvPr,
XML_id, OString::number(GetNewShapeID(xShape)),
XML_name, GetShapeName(xShape),
XML_descr, sax_fastparser::UseIf(sDescr, bHaveDesc));
if (eClickAction != presentation::ClickAction_NONE)
{
switch (eClickAction)
{
case presentation::ClickAction_STOPPRESENTATION:
sPPAction = "ppaction://hlinkshowjump?jump=endshow";
break ;
case presentation::ClickAction_NEXTPAGE:
sPPAction = "ppaction://hlinkshowjump?jump=nextslide";
break ;
case presentation::ClickAction_LASTPAGE:
sPPAction = "ppaction://hlinkshowjump?jump=lastslide";
break ;
case presentation::ClickAction_PREVPAGE:
sPPAction = "ppaction://hlinkshowjump?jump=previousslide";
break ;
case presentation::ClickAction_FIRSTPAGE:
sPPAction = "ppaction://hlinkshowjump?jump=firstslide";
break ;
case presentation::ClickAction_BOOKMARK:
sBookmark = "#" + sBookmark;
break ;
default :
break ;
}
}
// OOXTODO: //cNvPr children: XML_extLst, XML_hlinkHover
if (bHasMediaURL || !sPPAction.isEmpty())
pFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "" , XML_action,
bHasMediaURL ? u"ppaction://media"_ustr : sPPAction);
if ( !sURL.isEmpty() )
{
OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(),
oox::getRelationship(Relationship::HYPERLINK),
mpURLTransformer->getTransformedString(sURL),
mpURLTransformer->isExternalURL(sURL));
mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
}
if (!sBookmark.isEmpty())
{
bool bExtURL = URLTransformer().isExternalURL(sBookmark);
sBookmark = bExtURL ? sBookmark : lcl_GetTarget(GetFB()->getModel(), sBookmark);
OUString sRelId = mpFB->addRelation(mpFS->getOutputStream(),
bExtURL ? oox::getRelationship(Relationship::HYPERLINK)
: oox::getRelationship(Relationship::SLIDE),
sBookmark, bExtURL);
if (bExtURL)
mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId);
else
mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId, XML_action,
"ppaction://hlinksldjump");
}
AddExtLst(pFS, xShapeProps);
pFS->endElementNS(mnXmlNamespace, XML_cNvPr);
pFS->singleElementNS(mnXmlNamespace, XML_cNvPicPr
// OOXTODO: XML_preferRelativeSize
);
if (bHasMediaURL)
WriteMediaNonVisualProperties(xShape);
else
WriteNonVisualProperties(xShape);
pFS->endElementNS( mnXmlNamespace, XML_nvPicPr );
pFS->startElementNS(mnXmlNamespace, XML_blipFill);
if (xGraphic.is())
{
WriteXGraphicBlip(xShapeProps, xGraphic, mbUserShapes);
}
else if (bHasMediaURL)
{
Reference<graphic::XGraphic> xFallbackGraphic;
if (xShapeProps->getPropertySetInfo()->hasPropertyByName(u"FallbackGraphic" _ustr))
xShapeProps->getPropertyValue(u"FallbackGraphic" _ustr) >>= xFallbackGraphic;
WriteXGraphicBlip(xShapeProps, xFallbackGraphic, mbUserShapes);
}
if (xGraphic.is())
{
WriteSrcRectXGraphic(xShapeProps, xGraphic);
}
// now we stretch always when we get pGraphic (when changing that
// behavior, test n#780830 for regression, where the OLE sheet might get tiled
bool bStretch = false ;
if ( !pGraphic && GetProperty( xShapeProps, u"FillBitmapStretch" _ustr ) )
mAny >>= bStretch;
if ( pGraphic || bStretch )
pFS->singleElementNS(XML_a, XML_stretch);
if (bHasMediaURL)
{
// Graphic of media shapes is always stretched.
pFS->startElementNS(XML_a, XML_stretch);
pFS->singleElementNS(XML_a, XML_fillRect);
pFS->endElementNS(XML_a, XML_stretch);
}
pFS->endElementNS( mnXmlNamespace, XML_blipFill );
// visual shape properties
pFS->startElementNS(mnXmlNamespace, XML_spPr);
bool bFlipH = false ;
if ( xShapeProps->getPropertySetInfo()->hasPropertyByName(u"IsMirrored" _ustr) )
{
xShapeProps->getPropertyValue(u"IsMirrored" _ustr) >>= bFlipH;
}
WriteShapeTransformation( xShape, XML_a, bFlipH, false , false , false , true );
WritePresetShape( "rect" _ostr );
WriteFill(xShapeProps);
// graphic object can come with the frame (bnc#654525)
WriteOutline( xShapeProps );
WriteShapeEffects( xShapeProps );
Write3DEffects( xShapeProps, /*bIsText=*/false );
pFS->endElementNS( mnXmlNamespace, XML_spPr );
pFS->endElementNS( mnXmlNamespace, XML_pic );
}
static void lcl_Rotate(sal_Int32 nAngle, Point center, awt::Point& pt)
{
sal_Int16 nCos, nSin;
switch (nAngle)
{
case 90:
nCos = 0;
nSin = 1;
break ;
case 180:
nCos = -1;
nSin = 0;
break ;
case 270:
nCos = 0;
nSin = -1;
break ;
default :
return ;
}
sal_Int32 x = pt.X - center.X();
sal_Int32 y = pt.Y - center.Y();
pt.X = center.X() + x * nCos - y * nSin;
pt.Y = center.Y() + y * nCos + x * nSin;
}
static void lcl_FlipHFlipV(const tools::Polygon& rPoly, sal_Int32 nAngle, bool & rFlipH, bool & rFlipV)
{
Point aStart = rPoly[0];
Point aEnd = rPoly[rPoly.GetSize() - 1];
if (aStart.X() > aEnd.X() && aStart.Y() > aEnd.Y())
{
if (nAngle)
{
if (nAngle == 90)
rFlipH = true ;
if (nAngle == 270)
rFlipV = true ;
}
else // 0°
{
rFlipH = true ;
rFlipV = true ;
}
}
if (aStart.X() < aEnd.X() && aStart.Y() < aEnd.Y())
{
if (nAngle)
{
if (nAngle != 270)
{
rFlipH = true ;
rFlipV = true ;
}
else
rFlipH = true ;
}
}
if (aStart.Y() < aEnd.Y() && aStart.X() > aEnd.X())
{
if (nAngle)
{
if (nAngle == 180)
rFlipV = true ;
if (nAngle == 270)
{
rFlipV = true ;
rFlipH = true ;
}
}
else // 0°
{
rFlipH = true ;
}
}
if (aStart.Y() > aEnd.Y() && aStart.X() < aEnd.X())
{
if (nAngle)
{
if (nAngle == 90)
{
rFlipH = true ;
rFlipV = true ;
}
if (nAngle == 180)
rFlipH = true ;
}
else // 0°
rFlipV = true ;
}
}
static sal_Int32 lcl_GetAngle(const tools::Polygon& rPoly)
{
sal_Int32 nAngle;
Point aStartPoint = rPoly[0];
Point aEndPoint = rPoly[rPoly.GetSize() - 1];
if (aStartPoint.X() == rPoly[1].X())
{
if ((aStartPoint.X() < aEndPoint.X() && aStartPoint.Y() > aEndPoint.Y())
|| (aStartPoint.X() > aEndPoint.X() && aStartPoint.Y() < aEndPoint.Y()))
{
nAngle = 90;
}
else
nAngle = 270;
}
else
{
if (aStartPoint.X() > rPoly[1].X())
nAngle = 180;
else
nAngle = 0;
}
return nAngle;
}
// Adjust value decide the position, where the connector should turn.
static void lcl_GetConnectorAdjustValue(const Reference<XShape>& xShape, const tools::Polygon& rPoly,
ConnectorType eConnectorType,
std::vector<std::pair<sal_Int32, sal_Int32>>& rAvList)
{
Reference<XPropertySet> xShapeProps(xShape, UNO_QUERY);
bool bIsOOXMLCurve(false );
xShapeProps->getPropertyValue(u"EdgeOOXMLCurve" _ustr) >>= bIsOOXMLCurve;
sal_Int32 nAdjCount = 0;
if (eConnectorType == ConnectorType_CURVE)
{
if (bIsOOXMLCurve)
{
nAdjCount = (rPoly.GetSize() - 4) / 3;
}
else if (rPoly.GetSize() == 4)
{
if ((rPoly[0].X() == rPoly[1].X() && rPoly[2].X() == rPoly[3].X())
|| (rPoly[0].Y() == rPoly[1].Y() && rPoly[2].Y() == rPoly[3].Y()))
{
nAdjCount = 1; // curvedConnector3, control vectors parallel
}
else
nAdjCount = 0; // curvedConnector2, control vectors orthogonal
}
else if (rPoly.GetSize() > 4)
{
if ((rPoly[2].X() == rPoly[3].X() && rPoly[3].X() == rPoly[4].X())
|| (rPoly[2].Y() == rPoly[3].Y() && rPoly[3].Y() == rPoly[4].Y()))
{
nAdjCount = 3; // curvedConnector5
}
else
nAdjCount = 2; // curvedConnector4
}
}
else
{
switch (rPoly.GetSize())
{
case 3:
nAdjCount = 0; // bentConnector2
break ;
case 4:
nAdjCount = 1; // bentConnector3
break ;
case 5:
nAdjCount = 2; // bentConnector4
break ;
case 6:
nAdjCount = 3; // bentConnector5
break ;
}
}
if (nAdjCount)
{
sal_Int32 nAdjustValue;
Point aStart = rPoly[0];
Point aEnd = rPoly[rPoly.GetSize() - 1];
for (sal_Int32 i = 1; i <= nAdjCount; ++i)
{
Point aPt = rPoly[i];
if (aEnd.Y() == aStart.Y())
aEnd.setY(aStart.Y() + 1);
if (aEnd.X() == aStart.X())
aEnd.setX(aStart.X() + 1);
bool bVertical = rPoly[1].X() - aStart.X() != 0 ? true : false ;
// vertical and horizon alternate
if (i % 2 == 1)
bVertical = !bVertical;
if (eConnectorType == ConnectorType_CURVE)
{
if (bIsOOXMLCurve)
{
aPt = rPoly[3 * i];
}
else
{
awt::Size aSize = xShape->getSize();
awt::Point aShapePosition = xShape->getPosition();
tools::Rectangle aBoundRect = rPoly.GetBoundRect();
if (bVertical)
{
if ((aBoundRect.GetSize().Height() - aSize.Height) == 1)
aPt.setY(rPoly[i + 1].Y());
else if (aStart.Y() > aPt.Y())
aPt.setY(aShapePosition.Y);
else
aPt.setY(aShapePosition.Y + aSize.Height);
}
else
{
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=96 H=96 G=95
¤ Dauer der Verarbeitung: 0.24 Sekunden
¤
*© Formatika GbR, Deutschland