diff options
Diffstat (limited to 'oox/source/export/shapes.cxx')
-rw-r--r-- | oox/source/export/shapes.cxx | 2728 |
1 files changed, 2728 insertions, 0 deletions
diff --git a/oox/source/export/shapes.cxx b/oox/source/export/shapes.cxx new file mode 100644 index 000000000..df7394995 --- /dev/null +++ b/oox/source/export/shapes.cxx @@ -0,0 +1,2728 @@ +/* -*- 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 <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/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 <tools/globname.hxx> +#include <comphelper/classids.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/storagehelper.hxx> +#include <sot/exchange.hxx> +#include <utility> +#include <vcl/graph.hxx> +#include <vcl/outdev.hxx> +#include <filter/msfilter/escherex.hxx> +#include <svx/svdoashp.hxx> +#include <svx/svdoole2.hxx> +#include <tools/diagnose_ex.h> +#include <oox/export/chartexport.hxx> +#include <oox/mathml/export.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <oox/export/DMLPresetShapeExport.hxx> + +using namespace ::css; +using namespace ::css::beans; +using namespace ::css::uno; +using namespace ::css::drawing; +using namespace ::css::i18n; +using namespace ::css::table; +using namespace ::css::container; +using namespace ::css::document; +using namespace ::css::text; + +using ::css::io::XOutputStream; +using ::css::chart2::XChartDocument; +using ::css::frame::XModel; + +using ::oox::core::XmlFilterBase; +using ::sax_fastparser::FSHelperPtr; + + +namespace oox { + +static void lcl_ConvertProgID(std::u16string_view rProgID, + OUString & o_rMediaType, OUString & o_rRelationType, OUString & o_rFileExtension) +{ + if (rProgID == u"Excel.Sheet.12") + { + o_rMediaType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "xlsx"; + } + else if (o3tl::starts_with(rProgID, u"Excel.SheetBinaryMacroEnabled.12") ) + { + o_rMediaType = "application/vnd.ms-excel.sheet.binary.macroEnabled.12"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "xlsb"; + } + else if (o3tl::starts_with(rProgID, u"Excel.SheetMacroEnabled.12")) + { + o_rMediaType = "application/vnd.ms-excel.sheet.macroEnabled.12"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "xlsm"; + } + else if (o3tl::starts_with(rProgID, u"Excel.Sheet")) + { + o_rMediaType = "application/vnd.ms-excel"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "xls"; + } + else if (rProgID == u"PowerPoint.Show.12") + { + o_rMediaType = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "pptx"; + } + else if (rProgID == u"PowerPoint.ShowMacroEnabled.12") + { + o_rMediaType = "application/vnd.ms-powerpoint.presentation.macroEnabled.12"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "pptm"; + } + else if (o3tl::starts_with(rProgID, u"PowerPoint.Show")) + { + o_rMediaType = "application/vnd.ms-powerpoint"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "ppt"; + } + else if (o3tl::starts_with(rProgID, u"PowerPoint.Slide.12")) + { + o_rMediaType = "application/vnd.openxmlformats-officedocument.presentationml.slide"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "sldx"; + } + else if (rProgID == u"PowerPoint.SlideMacroEnabled.12") + { + o_rMediaType = "application/vnd.ms-powerpoint.slide.macroEnabled.12"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "sldm"; + } + else if (rProgID == u"Word.DocumentMacroEnabled.12") + { + o_rMediaType = "application/vnd.ms-word.document.macroEnabled.12"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "docm"; + } + else if (rProgID == u"Word.Document.12") + { + o_rMediaType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + o_rFileExtension = "docx"; + } + else if (rProgID == u"Word.Document.8") + { + o_rMediaType = "application/msword"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "doc"; + } + else if (rProgID == u"Excel.Chart.8") + { + o_rMediaType = "application/vnd.ms-excel"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "xls"; + } + else if (rProgID == u"AcroExch.Document.11") + { + o_rMediaType = "application/pdf"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "pdf"; + } + else + { + o_rMediaType = "application/vnd.openxmlformats-officedocument.oleObject"; + o_rRelationType = oox::getRelationship(Relationship::OLEOBJECT); + o_rFileExtension = "bin"; + } +} + +static uno::Reference<io::XInputStream> lcl_StoreOwnAsOOXML( + uno::Reference<uno::XComponentContext> const& xContext, + uno::Reference<embed::XEmbeddedObject> const& xObj, + char const*& o_rpProgID, + OUString & o_rMediaType, OUString & o_rRelationType, OUString & o_rSuffix) +{ + static struct { + struct { + sal_uInt32 n1; + sal_uInt16 n2, n3; + sal_uInt8 b8, b9, b10, b11, b12, b13, b14, b15; + } ClassId; + char const* pFilterName; + char const* pMediaType; + char const* pProgID; + char const* pSuffix; + } const s_Mapping[] = { + { {SO3_SW_CLASSID_60}, "MS Word 2007 XML", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "Word.Document.12", "docx" }, + { {SO3_SC_CLASSID_60}, "Calc MS Excel 2007 XML", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "Excel.Sheet.12", "xlsx" }, + { {SO3_SIMPRESS_CLASSID_60}, "Impress MS PowerPoint 2007 XML", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "PowerPoint.Show.12", "pptx" }, + // FIXME: Draw does not appear to have a MSO format export filter? +// { {SO3_SDRAW_CLASSID}, "", "", "", "" }, + { {SO3_SCH_CLASSID_60}, "unused", "", "", "" }, + { {SO3_SM_CLASSID_60}, "unused", "", "", "" }, + }; + + const char * pFilterName(nullptr); + SvGlobalName const classId(xObj->getClassID()); + for (auto & i : s_Mapping) + { + auto const& rId(i.ClassId); + SvGlobalName const temp(rId.n1, rId.n2, rId.n3, rId.b8, rId.b9, rId.b10, rId.b11, rId.b12, rId.b13, rId.b14, rId.b15); + if (temp == classId) + { + assert(SvGlobalName(SO3_SCH_CLASSID_60) != classId); // chart should be written elsewhere! + assert(SvGlobalName(SO3_SM_CLASSID_60) != classId); // formula should be written elsewhere! + pFilterName = i.pFilterName; + o_rMediaType = OUString::createFromAscii(i.pMediaType); + o_rpProgID = i.pProgID; + o_rSuffix = OUString::createFromAscii(i.pSuffix); + o_rRelationType = oox::getRelationship(Relationship::PACKAGE); + break; + } + } + + if (!pFilterName) + { + SAL_WARN("oox.shape", "oox::GetOLEObjectStream: unknown ClassId " << classId.GetHexName()); + return nullptr; + } + + if (embed::EmbedStates::LOADED == xObj->getCurrentState()) + { + xObj->changeState(embed::EmbedStates::RUNNING); + } + // use a temp stream - while it would work to store directly to a + // fragment stream, an error during export means we'd have to delete it + uno::Reference<io::XStream> const xTempStream( + xContext->getServiceManager()->createInstanceWithContext( + "com.sun.star.comp.MemoryStream", xContext), + uno::UNO_QUERY_THROW); + uno::Sequence<beans::PropertyValue> args( comphelper::InitPropertySequence({ + { "OutputStream", Any(xTempStream->getOutputStream()) }, + { "FilterName", Any(OUString::createFromAscii(pFilterName)) } + })); + uno::Reference<frame::XStorable> xStorable(xObj->getComponent(), uno::UNO_QUERY); + try + { + xStorable->storeToURL("private:stream", args); + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("oox.shape", "oox::GetOLEObjectStream"); + return nullptr; + } + xTempStream->getOutputStream()->closeOutput(); + return xTempStream->getInputStream(); +} + +uno::Reference<io::XInputStream> GetOLEObjectStream( + uno::Reference<uno::XComponentContext> const& xContext, + uno::Reference<embed::XEmbeddedObject> const& xObj, + std::u16string_view i_rProgID, + OUString & o_rMediaType, + OUString & o_rRelationType, + OUString & o_rSuffix, + const char *& o_rpProgID) +{ + uno::Reference<io::XInputStream> xInStream; + try + { + uno::Reference<document::XStorageBasedDocument> const xParent( + uno::Reference<container::XChild>(xObj, uno::UNO_QUERY_THROW)->getParent(), + uno::UNO_QUERY_THROW); + uno::Reference<embed::XStorage> const xParentStorage(xParent->getDocumentStorage()); + OUString const entryName( + uno::Reference<embed::XEmbedPersist>(xObj, uno::UNO_QUERY_THROW)->getEntryName()); + + if (xParentStorage->isStreamElement(entryName)) + { + lcl_ConvertProgID(i_rProgID, o_rMediaType, o_rRelationType, o_rSuffix); + xInStream = xParentStorage->cloneStreamElement(entryName)->getInputStream(); + // TODO: make it possible to take the sMediaType from the stream + } + else // the object is ODF - either the whole document is + { // ODF, or the OLE was edited so it was converted to ODF + xInStream = lcl_StoreOwnAsOOXML(xContext, xObj, + o_rpProgID, o_rMediaType, o_rRelationType, o_rSuffix); + } + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("oox.shape", "oox::GetOLEObjectStream"); + } + return xInStream; +} + +} // namespace oox + +namespace oox::drawingml { + +#define GETA(propName) \ + GetProperty( rXPropSet, #propName) + +#define GETAD(propName) \ + ( GetPropertyAndState( rXPropSet, rXPropState, #propName, eState ) && eState == beans::PropertyState_DIRECT_VALUE ) + +#define GET(variable, propName) \ + if ( GETA(propName) ) \ + mAny >>= variable; + +ShapeExport::ShapeExport( sal_Int32 nXmlNamespace, FSHelperPtr pFS, ShapeHashMap* pShapeMap, XmlFilterBase* pFB, DocumentType eDocumentType, DMLTextExport* pTextExport, bool bUserShapes ) + : DrawingML( std::move(pFS), pFB, eDocumentType, pTextExport ) + , m_nEmbeddedObjects(0) + , mnShapeIdMax( 1 ) + , mbUserShapes( bUserShapes ) + , mnXmlNamespace( nXmlNamespace ) + , maMapModeSrc( MapUnit::Map100thMM ) + , maMapModeDest( MapUnit::MapInch, Point(), Fraction( 1, 576 ), Fraction( 1, 576 ) ) + , mpShapeMap( pShapeMap ? pShapeMap : &maShapeMap ) +{ + mpURLTransformer = std::make_shared<URLTransformer>(); +} + +void ShapeExport::SetURLTranslator(const std::shared_ptr<URLTransformer>& pTransformer) +{ + mpURLTransformer = pTransformer; +} + +awt::Size ShapeExport::MapSize( const awt::Size& rSize ) const +{ + Size aRetSize( OutputDevice::LogicToLogic( Size( rSize.Width, rSize.Height ), maMapModeSrc, maMapModeDest ) ); + + if ( !aRetSize.Width() ) + aRetSize.AdjustWidth( 1 ); + if ( !aRetSize.Height() ) + aRetSize.AdjustHeight( 1 ); + return awt::Size( aRetSize.Width(), aRetSize.Height() ); +} + +static bool IsNonEmptySimpleText(const Reference<XInterface>& xIface) +{ + if (Reference<XSimpleText> xText{ xIface, UNO_QUERY }) + return xText->getString().getLength(); + + return false; +} + +bool ShapeExport::NonEmptyText( const Reference< XInterface >& xIface ) +{ + Reference< XPropertySet > xPropSet( xIface, UNO_QUERY ); + + if( xPropSet.is() ) + { + Reference< XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo(); + if ( xPropSetInfo.is() ) + { + if ( xPropSetInfo->hasPropertyByName( "IsEmptyPresentationObject" ) ) + { + bool bIsEmptyPresObj = false; + if ( xPropSet->getPropertyValue( "IsEmptyPresentationObject" ) >>= bIsEmptyPresObj ) + { + SAL_INFO("oox.shape", "empty presentation object " << bIsEmptyPresObj << " , props:"); + if( bIsEmptyPresObj ) + return true; + } + } + + if ( xPropSetInfo->hasPropertyByName( "IsPresentationObject" ) ) + { + bool bIsPresObj = false; + if ( xPropSet->getPropertyValue( "IsPresentationObject" ) >>= bIsPresObj ) + { + SAL_INFO("oox.shape", "presentation object " << bIsPresObj << ", props:"); + if( bIsPresObj ) + return true; + } + } + } + } + + return IsNonEmptySimpleText(xIface); +} + +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 + + // non visual shape properties + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + pFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + pFS->singleElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + } + 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); + Reference< XPropertySet > xProps( xShape, UNO_QUERY ); + if( xProps.is() ) { + if( bClosed ) + 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::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->singleElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + pFS->singleElementNS(mnXmlNamespace, XML_cNvGrpSpPr); + WriteNonVisualProperties(xShape ); + pFS->endElementNS(mnXmlNamespace, XML_nvGrpSpPr); + } + else + pFS->singleElementNS(mnXmlNamespace, XML_cNvGrpSpPr); + + // visual properties + pFS->startElementNS(mnXmlNamespace, XML_grpSpPr); + WriteShapeTransformation(xShape, XML_a, false, false, true); + pFS->endElementNS(mnXmlNamespace, XML_grpSpPr); + + uno::Reference<drawing::XShapes> xGroupShape(xShape, uno::UNO_QUERY_THROW); + uno::Reference<drawing::XShape> xParent = m_xParent; + m_xParent = xShape; + for (sal_Int32 i = 0; i < xGroupShape->getCount(); ++i) + { + uno::Reference<drawing::XShape> xChild(xGroupShape->getByIndex(i), uno::UNO_QUERY_THROW); + sal_Int32 nSavedNamespace = mnXmlNamespace; + + uno::Reference<lang::XServiceInfo> xServiceInfo(xChild, uno::UNO_QUERY_THROW); + if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes) + { + // tdf#128820: WriteGraphicObjectShapePart calls WriteTextShape for non-empty simple + // text objects, which needs writing into wps::wsp element, so make sure to use wps + // namespace for those objects + if (xServiceInfo->supportsService("com.sun.star.drawing.GraphicObjectShape") + && !IsNonEmptySimpleText(xChild)) + mnXmlNamespace = XML_pic; + else + mnXmlNamespace = XML_wps; + } + WriteShape(xChild); + + mnXmlNamespace = nSavedNamespace; + } + m_xParent = xParent; + + pFS->endElementNS(mnXmlNamespace, nGroupShapeToken); + return *this; +} + +static bool lcl_IsOnDenylist(OUString const & rShapeType) +{ + static const std::initializer_list<std::u16string_view> vDenylist = { + 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" + }; + + return std::find(vDenylist.begin(), vDenylist.end(), rShapeType) != vDenylist.end(); +} + +static bool lcl_IsOnAllowlist(OUString const & rShapeType) +{ + static const std::initializer_list<std::u16string_view> vAllowlist = { + u"heart", + u"puzzle", + u"col-60da8460", + u"col-502ad400" + }; + + return std::find(vAllowlist.begin(), vAllowlist.end(), rShapeType) != vAllowlist.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 const OUStringLiteral sPosition( u"Position" ); + bool bPosition = false; + EnhancedCustomShapeParameterPair aPosition; + for ( const PropertyValue& rPropVal: rPropSeq ) + { + if ( rPropVal.Name == sPosition ) + { + if ( rPropVal.Value >>= aPosition ) + bPosition = true; + } + } + if ( bPosition ) + { + sal_Int32 nXPosition = 0; + sal_Int32 nYPosition = 0; + // For polar handles, nXPosition is radius and nYPosition is angle + lcl_GetHandlePosition( nXPosition, aPosition.First , rSeq ); + lcl_GetHandlePosition( nYPosition, aPosition.Second, rSeq ); + rHandlePositionList.emplace_back( nXPosition, nYPosition ); + } + } +} + +static void lcl_AppendAdjustmentValue( std::vector< std::pair< sal_Int32, sal_Int32> > &rAvList, sal_Int32 nAdjIdx, sal_Int32 nValue ) +{ + rAvList.emplace_back( nAdjIdx , nValue ); +} + +static sal_Int32 lcl_NormalizeAngle( sal_Int32 nAngle ) +{ + nAngle = nAngle % 360; + return nAngle < 0 ? ( nAngle + 360 ) : nAngle ; +} + +static sal_Int32 lcl_CircleAngle2CustomShapeEllipseAngleOOX(const sal_Int32 nInternAngle, const sal_Int32 nWidth, const sal_Int32 nHeight) +{ + if (nWidth != 0 || nHeight != 0) + { + double fAngle = basegfx::deg2rad<100>(nInternAngle); // intern 1/100 deg to rad + fAngle = atan2(nHeight * sin(fAngle), nWidth * cos(fAngle)); // circle to ellipse + fAngle = basegfx::rad2deg<60000>(fAngle); // rad to OOXML angle unit + sal_Int32 nAngle = basegfx::fround(fAngle); // normalize + nAngle = nAngle % 21600000; + return nAngle < 0 ? (nAngle + 21600000) : nAngle; + } + else // should be handled by caller, dummy value + return 0; +} + +static OUString lcl_GetTarget(const css::uno::Reference<css::frame::XModel>& xModel, + std::u16string_view rURL) +{ + Reference<drawing::XDrawPagesSupplier> xDPS(xModel, uno::UNO_QUERY_THROW); + Reference<drawing::XDrawPages> xDrawPages(xDPS->getDrawPages(), uno::UNO_SET_THROW); + sal_uInt32 nPageCount = xDrawPages->getCount(); + OUString sTarget; + + for (sal_uInt32 i = 0; i < nPageCount; ++i) + { + Reference<XDrawPage> xDrawPage; + xDrawPages->getByIndex(i) >>= xDrawPage; + Reference<container::XNamed> xNamed(xDrawPage, UNO_QUERY); + if (!xNamed) + continue; + OUString sSlideName = "#" + xNamed->getName(); + if (rURL == sSlideName) + { + sTarget = "slide" + OUString::number(i + 1) + ".xml"; + break; + } + } + + return sTarget; +} + +ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape ) +{ + SAL_INFO("oox.shape", "write custom shape"); + Reference< XPropertySet > rXPropSet( xShape, UNO_QUERY ); + // First check, if this is a Fontwork-shape. For DrawingML, such a shape is a + // TextBox shape with body property prstTxWarp. + if (IsFontworkShape(rXPropSet)) + { + ShapeExport::WriteTextShape(xShape); // qualifier to prevent PowerPointShapeExport + return *this; + } + + bool bHasGeometrySeq(false); + Sequence< PropertyValue > aGeometrySeq; + OUString sShapeType("non-primitive"); // default in ODF + if (GETA(CustomShapeGeometry)) + { + SAL_INFO("oox.shape", "got custom shape geometry"); + if (mAny >>= aGeometrySeq) + { + bHasGeometrySeq = true; + SAL_INFO("oox.shape", "got custom shape geometry sequence"); + for (const PropertyValue& rProp : std::as_const(aGeometrySeq)) + { + SAL_INFO("oox.shape", "geometry property: " << rProp.Name); + if (rProp.Name == "Type") + rProp.Value >>= sShapeType; + } + } + } + + bool bPredefinedHandlesUsed = true; + bool bHasHandles = false; + + ShapeFlag nMirrorFlags = ShapeFlag::NONE; + MSO_SPT eShapeType = EscherPropertyContainer::GetCustomShapeType( xShape, nMirrorFlags, sShapeType ); + assert(dynamic_cast< SdrObjCustomShape* >(SdrObject::getSdrObjectFromXShape(xShape)) && "Not a SdrObjCustomShape (!)"); + SdrObjCustomShape& rSdrObjCustomShape(static_cast< SdrObjCustomShape& >(*SdrObject::getSdrObjectFromXShape(xShape))); + const bool bIsDefaultObject( + EscherPropertyContainer::IsDefaultObject( + rSdrObjCustomShape, + eShapeType)); + OString sPresetShape = msfilter::util::GetOOXMLPresetGeometry(sShapeType); + SAL_INFO("oox.shape", "custom shape type: " << sShapeType << " ==> " << sPresetShape); + + sal_Int32 nAdjustmentValuesIndex = -1; + awt::Rectangle aViewBox; + uno::Sequence<beans::PropertyValues> aHandles; + + bool bFlipH = false; + bool bFlipV = false; + + if (bHasGeometrySeq) + { + for (int i = 0; i < aGeometrySeq.getLength(); i++) + { + const PropertyValue& rProp = aGeometrySeq[ i ]; + SAL_INFO("oox.shape", "geometry property: " << rProp.Name); + + if ( rProp.Name == "MirroredX" ) + rProp.Value >>= bFlipH; + + if ( rProp.Name == "MirroredY" ) + rProp.Value >>= bFlipV; + if ( rProp.Name == "AdjustmentValues" ) + nAdjustmentValuesIndex = i; + else if ( rProp.Name == "Handles" ) + { + rProp.Value >>= aHandles; + if ( aHandles.hasElements() ) + bHasHandles = true; + if( !bIsDefaultObject ) + bPredefinedHandlesUsed = false; + // TODO: update nAdjustmentsWhichNeedsToBeConverted here + } + else if ( rProp.Name == "ViewBox" ) + rProp.Value >>= aViewBox; + } + } + + FSHelperPtr pFS = GetFS(); + // non visual shape properties + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + bool bUseBackground = false; + if (GETA(FillUseSlideBackground)) + mAny >>= bUseBackground; + if (bUseBackground) + mpFS->startElementNS(mnXmlNamespace, XML_sp, XML_useBgFill, "1"); + else + mpFS->startElementNS(mnXmlNamespace, XML_sp); + + bool isVisible = true ; + if( GETA (Visible)) + { + mAny >>= isVisible; + } + pFS->startElementNS( mnXmlNamespace, XML_nvSpPr ); + pFS->startElementNS( + mnXmlNamespace, XML_cNvPr, XML_id, + OString::number(GetShapeID(xShape) == -1 ? GetNewShapeID(xShape) : GetShapeID(xShape)), + XML_name, GetShapeName(xShape), XML_hidden, sax_fastparser::UseIf("1", !isVisible)); + + if( GETA( URL ) ) + { + OUString sURL; + mAny >>= sURL; + if( !sURL.isEmpty() ) + { + OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(), + oox::getRelationship(Relationship::HYPERLINK), + mpURLTransformer->getTransformedString(sURL), + mpURLTransformer->isExternalURL(sURL)); + + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); + } + } + + OUString sBookmark; + if (GETA(Bookmark)) + mAny >>= sBookmark; + + if (GETA(OnClick)) + { + OUString sPPAction; + presentation::ClickAction eClickAction = presentation::ClickAction_NONE; + mAny >>= eClickAction; + if (eClickAction != presentation::ClickAction_NONE) + { + switch (eClickAction) + { + case presentation::ClickAction_STOPPRESENTATION: + sPPAction = "ppaction://hlinkshowjump?jump=endshow"; + break; + case presentation::ClickAction_NEXTPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=nextslide"; + break; + case presentation::ClickAction_LASTPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=lastslide"; + break; + case presentation::ClickAction_PREVPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=previousslide"; + break; + case presentation::ClickAction_FIRSTPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=firstslide"; + break; + case presentation::ClickAction_BOOKMARK: + sBookmark = "#" + sBookmark; + break; + default: + break; + } + } + if (!sPPAction.isEmpty()) + pFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action, + sPPAction); + } + if (!sBookmark.isEmpty()) + { + bool bExtURL = URLTransformer().isExternalURL(sBookmark); + sBookmark = bExtURL ? sBookmark : lcl_GetTarget(GetFB()->getModel(), sBookmark); + + OUString sRelId + = mpFB->addRelation(mpFS->getOutputStream(), + bExtURL ? oox::getRelationship(Relationship::HYPERLINK) + : oox::getRelationship(Relationship::SLIDE), + sBookmark, bExtURL); + if (bExtURL) + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); + else + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId, + XML_action, "ppaction://hlinksldjump"); + } + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr); + WriteNonVisualProperties( xShape ); + pFS->endElementNS( mnXmlNamespace, XML_nvSpPr ); + } + else + { + pFS->startElementNS(mnXmlNamespace, XML_wsp); + 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 ); + WriteOutline( rXPropSet ); + WriteShapeEffects( rXPropSet ); + + bool bHas3DEffectinShape = false; + uno::Sequence<beans::PropertyValue> grabBag; + rXPropSet->getPropertyValue("InteropGrabBag") >>= grabBag; + + for (auto const& it : std::as_const(grabBag)) + if (it.Name == "3DEffectProperties") + bHas3DEffectinShape = true; + + if( bHas3DEffectinShape) + Write3DEffects( rXPropSet, /*bIsText=*/false ); + } + + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + pFS->startElementNS(mnXmlNamespace, XML_style); + WriteShapeStyle( rXPropSet ); + pFS->endElementNS( mnXmlNamespace, XML_style ); + + // write text + WriteTextBox( xShape, mnXmlNamespace ); + + pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) ); + + return *this; +} + +ShapeExport& ShapeExport::WriteEllipseShape( const Reference< XShape >& xShape ) +{ + SAL_INFO("oox.shape", "write ellipse shape"); + + FSHelperPtr pFS = GetFS(); + + pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp)); + + // TODO: connector ? + + // non visual shape properties + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + pFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + pFS->singleElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + pFS->singleElementNS( mnXmlNamespace, XML_cNvSpPr ); + WriteNonVisualProperties( xShape ); + pFS->endElementNS( mnXmlNamespace, XML_nvSpPr ); + } + else + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr); + + Reference< XPropertySet > xProps( xShape, UNO_QUERY ); + CircleKind eCircleKind(CircleKind_FULL); + if (xProps.is()) + xProps->getPropertyValue("CircleKind" ) >>= eCircleKind; + + // visual shape properties + pFS->startElementNS( mnXmlNamespace, XML_spPr ); + WriteShapeTransformation( xShape, XML_a ); + + if (CircleKind_FULL == eCircleKind) + WritePresetShape("ellipse"); + else + { + sal_Int32 nStartAngleIntern(9000); + sal_Int32 nEndAngleIntern(0); + if (xProps.is()) + { + xProps->getPropertyValue("CircleStartAngle" ) >>= nStartAngleIntern; + xProps->getPropertyValue("CircleEndAngle") >>= nEndAngleIntern; + } + std::vector< std::pair<sal_Int32,sal_Int32>> aAvList; + awt::Size aSize = xShape->getSize(); + if (aSize.Width != 0 || aSize.Height != 0) + { + // Our arc has 90° up, OOXML has 90° down, so mirror it. + // API angles are 1/100 degree. + sal_Int32 nStartAngleOOXML(lcl_CircleAngle2CustomShapeEllipseAngleOOX(36000 - nEndAngleIntern, aSize.Width, aSize.Height)); + sal_Int32 nEndAngleOOXML(lcl_CircleAngle2CustomShapeEllipseAngleOOX(36000 - nStartAngleIntern, aSize.Width, aSize.Height)); + lcl_AppendAdjustmentValue( aAvList, 1, nStartAngleOOXML); + lcl_AppendAdjustmentValue( aAvList, 2, nEndAngleOOXML); + } + switch (eCircleKind) + { + case CircleKind_ARC : + WritePresetShape("arc", aAvList); + break; + case CircleKind_SECTION : + WritePresetShape("pie", aAvList); + break; + case CircleKind_CUT : + WritePresetShape("chord", aAvList); + break; + default : + WritePresetShape("ellipse"); + } + } + if( xProps.is() ) + { + if (CircleKind_ARC == eCircleKind) + { + // An arc in ODF is never filled, even if a fill style other than + // "none" is set. OOXML arc can be filled, so set fill explicit to + // NONE, otherwise some hidden or inherited filling is shown. + FillStyle eFillStyle(FillStyle_NONE); + uno::Any aNewValue; + aNewValue <<= eFillStyle; + xProps->setPropertyValue("FillStyle", aNewValue); + } + WriteFill( xProps ); + WriteOutline( xProps ); + } + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + // write text + WriteTextBox( xShape, mnXmlNamespace ); + + pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) ); + + return *this; +} + +ShapeExport& ShapeExport::WriteGraphicObjectShape( const Reference< XShape >& xShape ) +{ + WriteGraphicObjectShapePart( xShape ); + + return *this; +} + +void ShapeExport::WriteGraphicObjectShapePart( const Reference< XShape >& xShape, const Graphic* pGraphic ) +{ + SAL_INFO("oox.shape", "write graphic object shape"); + + if (IsNonEmptySimpleText(xShape)) + { + SAL_INFO("oox.shape", "graphicObject: wrote only text"); + + WriteTextShape(xShape); + + return; + } + + SAL_INFO("oox.shape", "graphicObject without text"); + + uno::Reference<graphic::XGraphic> xGraphic; + OUString sMediaURL; + + Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY ); + + if (pGraphic) + { + xGraphic.set(pGraphic->GetXGraphic()); + } + else if (xShapeProps.is() && xShapeProps->getPropertySetInfo()->hasPropertyByName("Graphic")) + { + xShapeProps->getPropertyValue("Graphic") >>= xGraphic; + } + + bool bHasMediaURL = xShapeProps.is() && xShapeProps->getPropertySetInfo()->hasPropertyByName("MediaURL") && (xShapeProps->getPropertyValue("MediaURL") >>= sMediaURL); + + if (!xGraphic.is() && !bHasMediaURL) + { + SAL_INFO("oox.shape", "no graphic or media URL found"); + return; + } + + FSHelperPtr pFS = GetFS(); + XmlFilterBase* pFB = GetFB(); + + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + pFS->startElementNS(mnXmlNamespace, XML_pic); + else + pFS->startElementNS(mnXmlNamespace, XML_pic, + FSNS(XML_xmlns, XML_pic), pFB->getNamespaceURL(OOX_NS(dmlPicture))); + + pFS->startElementNS(mnXmlNamespace, XML_nvPicPr); + + presentation::ClickAction eClickAction = presentation::ClickAction_NONE; + OUString sDescr, sURL, sBookmark, sPPAction; + bool bHaveDesc; + + if ( ( bHaveDesc = GetProperty( xShapeProps, "Description" ) ) ) + mAny >>= sDescr; + if ( GetProperty( xShapeProps, "URL" ) ) + mAny >>= sURL; + if (GetProperty(xShapeProps, "Bookmark")) + mAny >>= sBookmark; + if (GetProperty(xShapeProps, "OnClick")) + mAny >>= eClickAction; + + pFS->startElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape), + XML_descr, sax_fastparser::UseIf(sDescr, bHaveDesc)); + + if (eClickAction != presentation::ClickAction_NONE) + { + switch (eClickAction) + { + case presentation::ClickAction_STOPPRESENTATION: + sPPAction = "ppaction://hlinkshowjump?jump=endshow"; + break; + case presentation::ClickAction_NEXTPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=nextslide"; + break; + case presentation::ClickAction_LASTPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=lastslide"; + break; + case presentation::ClickAction_PREVPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=previousslide"; + break; + case presentation::ClickAction_FIRSTPAGE: + sPPAction = "ppaction://hlinkshowjump?jump=firstslide"; + break; + case presentation::ClickAction_BOOKMARK: + sBookmark = "#" + sBookmark; + break; + default: + break; + } + } + + // OOXTODO: //cNvPr children: XML_extLst, XML_hlinkHover + if (bHasMediaURL || !sPPAction.isEmpty()) + pFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), "", XML_action, + bHasMediaURL ? "ppaction://media" : sPPAction); + if( !sURL.isEmpty() ) + { + OUString sRelId = mpFB->addRelation( mpFS->getOutputStream(), + oox::getRelationship(Relationship::HYPERLINK), + mpURLTransformer->getTransformedString(sURL), + mpURLTransformer->isExternalURL(sURL)); + + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); + } + + if (!sBookmark.isEmpty()) + { + bool bExtURL = URLTransformer().isExternalURL(sBookmark); + sBookmark = bExtURL ? sBookmark : lcl_GetTarget(GetFB()->getModel(), sBookmark); + + OUString sRelId = mpFB->addRelation(mpFS->getOutputStream(), + bExtURL ? oox::getRelationship(Relationship::HYPERLINK) + : oox::getRelationship(Relationship::SLIDE), + sBookmark, bExtURL); + + if (bExtURL) + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); + else + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId, XML_action, + "ppaction://hlinksldjump"); + } + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + + pFS->singleElementNS(mnXmlNamespace, XML_cNvPicPr + // OOXTODO: XML_preferRelativeSize + ); + if (bHasMediaURL) + WriteMediaNonVisualProperties(xShape); + else + WriteNonVisualProperties(xShape); + + pFS->endElementNS( mnXmlNamespace, XML_nvPicPr ); + + pFS->startElementNS(mnXmlNamespace, XML_blipFill); + + if (xGraphic.is()) + { + WriteXGraphicBlip(xShapeProps, xGraphic, mbUserShapes); + } + else if (bHasMediaURL) + { + Reference<graphic::XGraphic> xFallbackGraphic; + if (xShapeProps->getPropertySetInfo()->hasPropertyByName("FallbackGraphic")) + xShapeProps->getPropertyValue("FallbackGraphic") >>= xFallbackGraphic; + + WriteXGraphicBlip(xShapeProps, xFallbackGraphic, mbUserShapes); + } + + if (xGraphic.is()) + { + WriteSrcRectXGraphic(xShapeProps, xGraphic); + } + + // now we stretch always when we get pGraphic (when changing that + // behavior, test n#780830 for regression, where the OLE sheet might get tiled + bool bStretch = false; + if( !pGraphic && GetProperty( xShapeProps, "FillBitmapStretch" ) ) + mAny >>= bStretch; + + if ( pGraphic || bStretch ) + pFS->singleElementNS(XML_a, XML_stretch); + + if (bHasMediaURL) + { + // Graphic of media shapes is always stretched. + pFS->startElementNS(XML_a, XML_stretch); + pFS->singleElementNS(XML_a, XML_fillRect); + pFS->endElementNS(XML_a, XML_stretch); + } + + pFS->endElementNS( mnXmlNamespace, XML_blipFill ); + + // visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_spPr); + bool bFlipH = false; + if( xShapeProps->getPropertySetInfo()->hasPropertyByName("IsMirrored") ) + { + xShapeProps->getPropertyValue("IsMirrored") >>= bFlipH; + } + WriteShapeTransformation( xShape, XML_a, bFlipH, false, false, false, true ); + WritePresetShape( "rect" ); + // graphic object can come with the frame (bnc#654525) + WriteOutline( xShapeProps ); + + WriteShapeEffects( xShapeProps ); + Write3DEffects( xShapeProps, /*bIsText=*/false ); + + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + pFS->endElementNS( mnXmlNamespace, XML_pic ); +} + +static void lcl_Rotate(sal_Int32 nAngle, Point center, awt::Point& pt) +{ + sal_Int16 nCos, nSin; + switch (nAngle) + { + case 90: + nCos = 0; + nSin = 1; + break; + case 180: + nCos = -1; + nSin = 0; + break; + case 270: + nCos = 0; + nSin = -1; + break; + default: + return; + } + sal_Int32 x = pt.X - center.X(); + sal_Int32 y = pt.Y - center.Y(); + pt.X = center.X() + x * nCos - y * nSin; + pt.Y = center.Y() + y * nCos + x * nSin; +} + +static void lcl_FlipHFlipV(tools::Polygon aPoly, sal_Int32 nAngle, bool& rFlipH, bool& rFlipV) +{ + Point aStart = aPoly[0]; + Point aEnd = aPoly[aPoly.GetSize() - 1]; + + if (aStart.X() > aEnd.X() && aStart.Y() > aEnd.Y()) + { + if (nAngle) + { + if (nAngle == 90) + rFlipH = true; + if (nAngle == 270) + rFlipV = true; + } + else // 0° + { + rFlipH = true; + rFlipV = true; + } + } + + if (aStart.X() < aEnd.X() && aStart.Y() < aEnd.Y()) + { + if (nAngle) + { + if (nAngle != 270) + { + rFlipH = true; + rFlipV = true; + } + else + rFlipH = true; + } + } + + if (aStart.Y() < aEnd.Y() && aStart.X() > aEnd.X()) + { + if (nAngle) + { + if (nAngle == 180) + rFlipV = true; + if (nAngle == 270) + { + rFlipV = true; + rFlipH = true; + } + } + else // 0° + { + rFlipH = true; + } + } + + if (aStart.Y() > aEnd.Y() && aStart.X() < aEnd.X()) + { + if (nAngle) + { + if (nAngle == 90) + { + rFlipH = true; + rFlipV = true; + } + if (nAngle == 180) + rFlipH = true; + } + else // 0° + rFlipV = true; + } +} + +static sal_Int32 lcl_GetAngle(tools::Polygon aPoly) +{ + sal_Int32 nAngle; + Point aStartPoint = aPoly[0]; + Point aEndPoint = aPoly[aPoly.GetSize() - 1]; + if (aStartPoint.X() == aPoly[1].X()) + { + if ((aStartPoint.X() < aEndPoint.X() && aStartPoint.Y() > aEndPoint.Y()) + || (aStartPoint.X() > aEndPoint.X() && aStartPoint.Y() < aEndPoint.Y())) + { + nAngle = 90; + } + else + nAngle = 270; + } + else + { + if (aStartPoint.X() > aPoly[1].X()) + nAngle = 180; + else + nAngle = 0; + } + + return nAngle; +} + +// Adjust value decide the position, where the connector should turn. +static void lcl_GetConnectorAdjustValue(const Reference<XShape>& xShape, tools::Polygon aPoly, + ConnectorType eConnectorType, + std::vector<std::pair<sal_Int32, sal_Int32>>& rAvList) +{ + sal_Int32 nAdjCount = 0; + if (eConnectorType == ConnectorType_CURVE) + { + if (aPoly.GetSize() == 4) + { + if ((aPoly[0].X() == aPoly[1].X() && aPoly[2].X() == aPoly[3].X()) + || (aPoly[0].Y() == aPoly[1].Y() && aPoly[2].Y() == aPoly[3].Y())) + { + nAdjCount = 1; // curvedConnector3 + } + else + nAdjCount = 0; // curvedConnector2 + } + else if (aPoly.GetSize() > 4) + { + if ((aPoly[2].X() == aPoly[3].X() && aPoly[3].X() == aPoly[4].X()) + || (aPoly[2].Y() == aPoly[3].Y() && aPoly[3].Y() == aPoly[4].Y())) + { + nAdjCount = 3; // curvedConnector5 + } + else + nAdjCount = 2; // curvedConnector4 + } + } + else + { + switch (aPoly.GetSize()) + { + case 3: + nAdjCount = 0; // bentConnector2 + break; + case 4: + nAdjCount = 1; // bentConnector3 + break; + case 5: + nAdjCount = 2; // bentConnector4 + break; + case 6: + nAdjCount = 3; // bentConnector5 + break; + } + } + + if (nAdjCount) + { + sal_Int32 nAdjustValue; + Point aStart = aPoly[0]; + Point aEnd = aPoly[aPoly.GetSize() - 1]; + + for (sal_Int32 i = 1; i <= nAdjCount; ++i) + { + Point aPt = aPoly[i]; + + if (aEnd.Y() == aStart.Y()) + aEnd.setY(aStart.Y() + 1); + if (aEnd.X() == aStart.X()) + aEnd.setX(aStart.X() + 1); + + bool bVertical = aPoly[1].X() - aStart.X() != 0 ? true : false; + // vertical and horizon alternate + if (i % 2 == 1) + bVertical = !bVertical; + + if (eConnectorType == ConnectorType_CURVE) + { + awt::Size aSize = xShape->getSize(); + awt::Point aShapePosition = xShape->getPosition(); + tools::Rectangle aBoundRect = aPoly.GetBoundRect(); + + if (bVertical) + { + if ((aBoundRect.GetSize().Height() - aSize.Height) == 1) + aPt.setY(aPoly[i + 1].Y()); + else if (aStart.Y() > aPt.Y()) + aPt.setY(aShapePosition.Y); + else + aPt.setY(aShapePosition.Y + aSize.Height); + } + else + { + if ((aBoundRect.GetSize().Width() - aSize.Width) == 1) + aPt.setX(aPoly[i + 1].X()); + else if (aStart.X() > aPt.X()) + aPt.setX(aShapePosition.X); + else + aPt.setX(aShapePosition.X + aSize.Width); + } + } + + if (bVertical) + nAdjustValue = ((aPt.Y() - aStart.Y()) * 100000) / (aEnd.Y() - aStart.Y()); + else + nAdjustValue = ((aPt.X() - aStart.X()) * 100000) / (aEnd.X() - aStart.X()); + + rAvList.emplace_back(i, nAdjustValue); + } + } +} + +static sal_Int32 lcl_GetGluePointId(sal_Int32 nGluePointId) +{ + if (nGluePointId > 3) + return nGluePointId - 4; + else + { + // change id of the bounding box (1 <-> 3) + if (nGluePointId == 1) + return 3; // Right + else if (nGluePointId == 3) + return 1; // Left + } + + return nGluePointId; +} + +ShapeExport& ShapeExport::WriteConnectorShape( const Reference< XShape >& xShape ) +{ + bool bFlipH = false; + bool bFlipV = false; + sal_Int32 nAngle = 0; + sal_Int32 nStartGlueId = 0; + sal_Int32 nEndGlueId = 0; + + SAL_INFO("oox.shape", "write connector shape"); + + FSHelperPtr pFS = GetFS(); + + OUString sGeometry; + std::vector<std::pair<sal_Int32, sal_Int32>> aAdjustValueList; + Reference< XPropertySet > rXPropSet( xShape, UNO_QUERY ); + Reference< XPropertyState > rXPropState( xShape, UNO_QUERY ); + awt::Point aStartPoint, aEndPoint; + Reference< XShape > rXShapeA; + Reference< XShape > rXShapeB; + PropertyState eState; + ConnectorType eConnectorType = ConnectorType_STANDARD; + GET(eConnectorType, EdgeKind); + + switch( eConnectorType ) { + case ConnectorType_CURVE: + sGeometry = "curvedConnector"; + break; + case ConnectorType_LINES: + case ConnectorType_STANDARD: + sGeometry = "bentConnector"; + break; + default: + case ConnectorType_LINE: + sGeometry = "straightConnector1"; + break; + } + + if( GETAD( EdgeStartPoint ) ) { + mAny >>= aStartPoint; + if( GETAD( EdgeEndPoint ) ) { + mAny >>= aEndPoint; + } + } + GET( rXShapeA, EdgeStartConnection ); + GET( rXShapeB, EdgeEndConnection ); + + GET(nStartGlueId, StartGluePointIndex); + if (nStartGlueId != -1) + nStartGlueId = lcl_GetGluePointId(nStartGlueId); + GET(nEndGlueId, EndGluePointIndex); + if (nEndGlueId != -1) + nEndGlueId = lcl_GetGluePointId(nEndGlueId); + + // Position is relative to group in Word, but relative to anchor of group in API. + if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes && m_xParent.is()) + { + awt::Point aParentPos = m_xParent->getPosition(); + aStartPoint.X -= aParentPos.X; + aStartPoint.Y -= aParentPos.Y; + aEndPoint.X -= aParentPos.X; + aEndPoint.Y -= aParentPos.Y; + } + EscherConnectorListEntry aConnectorEntry( xShape, aStartPoint, rXShapeA, aEndPoint, rXShapeB ); + + if (eConnectorType != ConnectorType_LINE) + { + tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(xShape); + if (aPolyPolygon.Count() > 0) + { + tools::Polygon aPoly = aPolyPolygon.GetObject(0); + lcl_GetConnectorAdjustValue(xShape, aPoly, eConnectorType, aAdjustValueList); + nAngle = lcl_GetAngle(aPoly); + lcl_FlipHFlipV(aPoly, nAngle, bFlipH, bFlipV); + if (nAngle) + { + Point center((aEndPoint.X + aStartPoint.X) / 2, (aEndPoint.Y + aStartPoint.Y) / 2); + lcl_Rotate(nAngle, center, aStartPoint); + lcl_Rotate(nAngle, center, aEndPoint); + nAngle *= 60000; + } + sGeometry = sGeometry + OUString::number(aAdjustValueList.size() + 2); + } + } + + tools::Rectangle aRect( Point( aStartPoint.X, aStartPoint.Y ), Point( aEndPoint.X, aEndPoint.Y ) ); + if( aRect.getWidth() < 0 ) { + aRect.SetLeft(aEndPoint.X); + aRect.setWidth( aStartPoint.X - aEndPoint.X ); + if (eConnectorType == ConnectorType_LINE) + bFlipH = true; + } + + if( aRect.getHeight() < 0 ) { + aRect.SetTop(aEndPoint.Y); + aRect.setHeight( aStartPoint.Y - aEndPoint.Y ); + if (eConnectorType == ConnectorType_LINE) + bFlipV = true; + } + + // tdf#99810 connector shape (cxnSp) is not valid with namespace 'wps' + const auto nShapeNode = (mnXmlNamespace == XML_wps ? XML_wsp : XML_cxnSp); + pFS->startElementNS(mnXmlNamespace, nShapeNode); + + if (mnXmlNamespace == XML_wps) + { + // non visual connector shape drawing properties + pFS->singleElementNS(mnXmlNamespace, XML_cNvCnPr); + } + else + { + // non visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_nvCxnSpPr); + pFS->singleElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + // non visual connector shape drawing properties + pFS->startElementNS(mnXmlNamespace, XML_cNvCxnSpPr); + + if (GetShapeID(rXShapeA) == -1) + GetNewShapeID(rXShapeA); + if (GetShapeID(rXShapeB) == -1) + GetNewShapeID(rXShapeB); + WriteConnectorConnections(nStartGlueId, nEndGlueId, GetShapeID(rXShapeA), GetShapeID(rXShapeB)); + pFS->endElementNS(mnXmlNamespace, XML_cNvCxnSpPr); + if (GetDocumentType() == DOCUMENT_PPTX) + pFS->singleElementNS(mnXmlNamespace, XML_nvPr); + pFS->endElementNS(mnXmlNamespace, XML_nvCxnSpPr); + } + + // visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_spPr); + WriteTransformation( xShape, aRect, XML_a, bFlipH, bFlipV, nAngle ); + // TODO: write adjustments (ppt export doesn't work well there either) + WritePresetShape( sGeometry.toUtf8(), aAdjustValueList); + Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY ); + if( xShapeProps.is() ) + WriteOutline( xShapeProps ); + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + // connector shape (cxnSp) cannot contain text (txBody) (according to schema) + if( nShapeNode != XML_cxnSp ) + { + // write text + WriteTextBox( xShape, mnXmlNamespace ); + } + + pFS->endElementNS(mnXmlNamespace, nShapeNode); + + return *this; +} + +ShapeExport& ShapeExport::WriteLineShape( const Reference< XShape >& xShape ) +{ + bool bFlipH = false; + bool bFlipV = false; + + SAL_INFO("oox.shape", "write line shape"); + + FSHelperPtr pFS = GetFS(); + + pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp)); + + tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon( xShape ); + if( aPolyPolygon.Count() == 1 && aPolyPolygon[ 0 ].GetSize() == 2) + { + const tools::Polygon& rPoly = aPolyPolygon[ 0 ]; + + bFlipH = ( rPoly[ 0 ].X() > rPoly[ 1 ].X() ); + bFlipV = ( rPoly[ 0 ].Y() > rPoly[ 1 ].Y() ); + } + + // non visual shape properties + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + pFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + pFS->singleElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + } + pFS->singleElementNS( mnXmlNamespace, XML_cNvSpPr ); + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + WriteNonVisualProperties( xShape ); + pFS->endElementNS( mnXmlNamespace, XML_nvSpPr ); + } + + // visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_spPr); + WriteShapeTransformation( xShape, XML_a, bFlipH, bFlipV, true); + WritePresetShape( "line" ); + Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY ); + if( xShapeProps.is() ) + WriteOutline( xShapeProps ); + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + //write style + pFS->startElementNS(mnXmlNamespace, XML_style); + WriteShapeStyle( xShapeProps ); + pFS->endElementNS( mnXmlNamespace, XML_style ); + + // write text + WriteTextBox( xShape, mnXmlNamespace ); + + pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) ); + + return *this; +} + +ShapeExport& ShapeExport::WriteNonVisualDrawingProperties( const Reference< XShape >& xShape, const char* pName ) +{ + GetFS()->singleElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, pName ); + + return *this; +} + +ShapeExport& ShapeExport::WriteNonVisualProperties( const Reference< XShape >& ) +{ + // Override to generate //nvPr elements. + return *this; +} + +ShapeExport& ShapeExport::WriteRectangleShape( const Reference< XShape >& xShape ) +{ + SAL_INFO("oox.shape", "write rectangle shape"); + + FSHelperPtr pFS = GetFS(); + + pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp)); + + sal_Int32 nRadius = 0; + + Reference< XPropertySet > xShapeProps( xShape, UNO_QUERY ); + if( xShapeProps.is() ) + { + xShapeProps->getPropertyValue( "CornerRadius" ) >>= nRadius; + } + + if( nRadius ) + { + nRadius = MapSize( awt::Size( nRadius, 0 ) ).Width; + } + //TODO: use nRadius value more precisely than just deciding whether to use + // "rect" or "roundRect" preset shape below + + // non visual shape properties + if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes) + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr); + pFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + pFS->singleElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr); + WriteNonVisualProperties( xShape ); + pFS->endElementNS( mnXmlNamespace, XML_nvSpPr ); + + // visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_spPr); + WriteShapeTransformation( xShape, XML_a ); + WritePresetShape( nRadius == 0 ? "rect" : "roundRect" ); + Reference< XPropertySet > xProps( xShape, UNO_QUERY ); + if( xProps.is() ) + { + WriteFill( xProps ); + WriteOutline( xProps ); + } + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + // write text + WriteTextBox( xShape, mnXmlNamespace ); + + pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) ); + + return *this; +} + +typedef ShapeExport& (ShapeExport::*ShapeConverter)( const Reference< XShape >& ); +typedef std::unordered_map< const char*, ShapeConverter, rtl::CStringHash, rtl::CStringEqual> NameToConvertMapType; + +static const NameToConvertMapType& lcl_GetConverters() +{ + static NameToConvertMapType const shape_converters + { + // tdf#98736 export CaptionShape as TextShape, because it is non-ooxml shape and + // we can't export this shape as CustomShape + // TODO: WriteCaptionShape + { "com.sun.star.drawing.CaptionShape" , &ShapeExport::WriteTextShape }, + + { "com.sun.star.drawing.ClosedBezierShape" , &ShapeExport::WriteClosedPolyPolygonShape }, + { "com.sun.star.drawing.ConnectorShape" , &ShapeExport::WriteConnectorShape }, + { "com.sun.star.drawing.CustomShape" , &ShapeExport::WriteCustomShape }, + { "com.sun.star.drawing.EllipseShape" , &ShapeExport::WriteEllipseShape }, + { "com.sun.star.drawing.GraphicObjectShape" , &ShapeExport::WriteGraphicObjectShape }, + { "com.sun.star.drawing.LineShape" , &ShapeExport::WriteLineShape }, + { "com.sun.star.drawing.MediaShape" , &ShapeExport::WriteGraphicObjectShape }, + { "com.sun.star.drawing.OpenBezierShape" , &ShapeExport::WriteOpenPolyPolygonShape }, + { "com.sun.star.drawing.PolyPolygonShape" , &ShapeExport::WriteClosedPolyPolygonShape }, + { "com.sun.star.drawing.PolyLineShape" , &ShapeExport::WriteOpenPolyPolygonShape }, + { "com.sun.star.drawing.RectangleShape" , &ShapeExport::WriteRectangleShape }, + { "com.sun.star.drawing.OLE2Shape" , &ShapeExport::WriteOLE2Shape }, + { "com.sun.star.drawing.TableShape" , &ShapeExport::WriteTableShape }, + { "com.sun.star.drawing.TextShape" , &ShapeExport::WriteTextShape }, + { "com.sun.star.drawing.GroupShape" , &ShapeExport::WriteGroupShape }, + + { "com.sun.star.presentation.GraphicObjectShape" , &ShapeExport::WriteGraphicObjectShape }, + { "com.sun.star.presentation.MediaShape" , &ShapeExport::WriteGraphicObjectShape }, + { "com.sun.star.presentation.ChartShape" , &ShapeExport::WriteOLE2Shape }, + { "com.sun.star.presentation.OLE2Shape" , &ShapeExport::WriteOLE2Shape }, + { "com.sun.star.presentation.TableShape" , &ShapeExport::WriteTableShape }, + { "com.sun.star.presentation.TextShape" , &ShapeExport::WriteTextShape }, + + { "com.sun.star.presentation.DateTimeShape" , &ShapeExport::WriteTextShape }, + { "com.sun.star.presentation.FooterShape" , &ShapeExport::WriteTextShape }, + { "com.sun.star.presentation.HeaderShape" , &ShapeExport::WriteTextShape }, + { "com.sun.star.presentation.NotesShape" , &ShapeExport::WriteTextShape }, + { "com.sun.star.presentation.OutlinerShape" , &ShapeExport::WriteTextShape }, + { "com.sun.star.presentation.SlideNumberShape" , &ShapeExport::WriteTextShape }, + { "com.sun.star.presentation.TitleTextShape" , &ShapeExport::WriteTextShape }, + }; + return shape_converters; +} + +ShapeExport& ShapeExport::WriteShape( const Reference< XShape >& xShape ) +{ + if (!xShape) + throw lang::IllegalArgumentException(); + + OUString sShapeType = xShape->getShapeType(); + SAL_INFO("oox.shape", "write shape: " << sShapeType); + NameToConvertMapType::const_iterator aConverter + = lcl_GetConverters().find(sShapeType.toUtf8().getStr()); + if (aConverter == lcl_GetConverters().end()) + { + SAL_INFO("oox.shape", "unknown shape"); + return WriteUnknownShape( xShape ); + } + + if (GetDocumentType() == DOCUMENT_PPTX) + { + Reference< XPropertySet > xShapeProperties(xShape, UNO_QUERY); + if (xShapeProperties && xShapeProperties->getPropertySetInfo() + && xShapeProperties->getPropertySetInfo()->hasPropertyByName("IsPresentationObject") + && xShapeProperties->getPropertyValue("IsPresentationObject").hasValue()) + mbPlaceholder = xShapeProperties->getPropertyValue("IsPresentationObject").get<bool>(); + } + + (this->*(aConverter->second))( xShape ); + + return *this; +} + +static bool lcl_isTextBox(const Reference<XInterface>& xIface) +{ + uno::Reference<beans::XPropertySet> xPropertySet(xIface, uno::UNO_QUERY); + if (!xPropertySet.is()) + return false; + uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); + if (!xPropertySetInfo->hasPropertyByName("TextBox")) + return false; + css::uno::Any aTextBox(xPropertySet->getPropertyValue("TextBox")); + if (!aTextBox.hasValue()) + return false; + return aTextBox.get<bool>(); +} + +ShapeExport& ShapeExport::WriteTextBox( const Reference< XInterface >& xIface, sal_Int32 nXmlNamespace, bool bWritePropertiesAsLstStyles ) +{ + // In case this shape has an associated textbox, then export that, and we're done. + if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes && GetTextExport()) + { + if (lcl_isTextBox(xIface)) + { + GetTextExport()->WriteTextBox(uno::Reference<drawing::XShape>(xIface, uno::UNO_QUERY_THROW)); + WriteText( xIface, /*bBodyPr=*/true, /*bText=*/false, /*nXmlNamespace=*/nXmlNamespace ); + return *this; + } + } + + Reference< XText > xXText( xIface, UNO_QUERY ); + if( (NonEmptyText( xIface ) || GetDocumentType() == DOCUMENT_PPTX) + && xXText.is() ) + { + FSHelperPtr pFS = GetFS(); + + pFS->startElementNS(nXmlNamespace, + (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_txBody : XML_txbx)); + WriteText(xIface, /*bBodyPr=*/(GetDocumentType() != DOCUMENT_DOCX || mbUserShapes), /*bText=*/true, + /*nXmlNamespace=*/0, /*bWritePropertiesAsLstStyles=*/bWritePropertiesAsLstStyles); + pFS->endElementNS( nXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_txBody : XML_txbx) ); + if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes) + WriteText( xIface, /*bBodyPr=*/true, /*bText=*/false, /*nXmlNamespace=*/nXmlNamespace ); + } + else if (GetDocumentType() == DOCUMENT_DOCX && !mbUserShapes) + mpFS->singleElementNS(nXmlNamespace, XML_bodyPr); + + return *this; +} + +void ShapeExport::WriteTable( const Reference< XShape >& rXShape ) +{ + Reference< XTable > xTable; + Reference< XPropertySet > xPropSet( rXShape, UNO_QUERY ); + + mpFS->startElementNS(XML_a, XML_graphic); + mpFS->startElementNS(XML_a, XML_graphicData, + XML_uri, "http://schemas.openxmlformats.org/drawingml/2006/table"); + + if ( xPropSet.is() && ( xPropSet->getPropertyValue( "Model" ) >>= xTable ) ) + { + mpFS->startElementNS(XML_a, XML_tbl); + mpFS->startElementNS(XML_a, XML_tblPr); + WriteShapeEffects(xPropSet); + mpFS->endElementNS(XML_a, XML_tblPr); + + Reference< container::XIndexAccess > xColumns( xTable->getColumns(), UNO_QUERY_THROW ); + Reference< container::XIndexAccess > xRows( xTable->getRows(), UNO_QUERY_THROW ); + sal_uInt16 nRowCount = static_cast< sal_uInt16 >( xRows->getCount() ); + sal_uInt16 nColumnCount = static_cast< sal_uInt16 >( xColumns->getCount() ); + + mpFS->startElementNS(XML_a, XML_tblGrid); + + for ( sal_Int32 x = 0; x < nColumnCount; x++ ) + { + Reference< XPropertySet > xColPropSet( xColumns->getByIndex( x ), UNO_QUERY_THROW ); + sal_Int32 nWidth(0); + xColPropSet->getPropertyValue( "Width" ) >>= nWidth; + + mpFS->singleElementNS(XML_a, XML_gridCol, + XML_w, OString::number(oox::drawingml::convertHmmToEmu(nWidth))); + } + + mpFS->endElementNS( XML_a, XML_tblGrid ); + + // map for holding the transpose index of the merged cells and pair<parentTransposeIndex, parentCell> + typedef std::unordered_map<sal_Int32, std::pair<sal_Int32, Reference< XMergeableCell> > > transposeTableMap; + transposeTableMap mergedCellMap; + + for( sal_Int32 nRow = 0; nRow < nRowCount; nRow++ ) + { + Reference< XPropertySet > xRowPropSet( xRows->getByIndex( nRow ), UNO_QUERY_THROW ); + sal_Int32 nRowHeight(0); + + xRowPropSet->getPropertyValue( "Height" ) >>= nRowHeight; + + mpFS->startElementNS(XML_a, XML_tr, + XML_h, OString::number(oox::drawingml::convertHmmToEmu(nRowHeight))); + for( sal_Int32 nColumn = 0; nColumn < nColumnCount; nColumn++ ) + { + Reference< XMergeableCell > xCell( xTable->getCellByPosition( nColumn, nRow ), + UNO_QUERY_THROW ); + sal_Int32 transposedIndexofCell = (nRow * nColumnCount) + nColumn; + + //assume we will open a cell, set to false below if we won't + bool bCellOpened = true; + + if(xCell->getColumnSpan() > 1 && xCell->getRowSpan() > 1) + { + // having both : horizontal and vertical merge + mpFS->startElementNS(XML_a, XML_tc, + XML_gridSpan, OString::number(xCell->getColumnSpan()), + XML_rowSpan, OString::number(xCell->getRowSpan())); + // since, XMergeableCell doesn't have the information about + // cell having hMerge or vMerge. + // So, Populating the merged cell map in-order to use it to + // decide the attribute for the individual cell. + for(sal_Int32 columnIndex = nColumn; columnIndex < nColumn+xCell->getColumnSpan(); ++columnIndex) + { + for(sal_Int32 rowIndex = nRow; rowIndex < nRow+xCell->getRowSpan(); ++rowIndex) + { + sal_Int32 transposeIndexForMergeCell = + (rowIndex * nColumnCount) + columnIndex; + mergedCellMap[transposeIndexForMergeCell] = + std::make_pair(transposedIndexofCell, xCell); + } + } + + } + else if(xCell->getColumnSpan() > 1) + { + // having : horizontal merge + mpFS->startElementNS(XML_a, XML_tc, + XML_gridSpan, OString::number(xCell->getColumnSpan())); + for(sal_Int32 columnIndex = nColumn; columnIndex < nColumn + xCell->getColumnSpan(); ++columnIndex) { + sal_Int32 transposeIndexForMergeCell = (nRow*nColumnCount) + columnIndex; + mergedCellMap[transposeIndexForMergeCell] = + std::make_pair(transposedIndexofCell, xCell); + } + } + else if(xCell->getRowSpan() > 1) + { + // having : vertical merge + mpFS->startElementNS(XML_a, XML_tc, + XML_rowSpan, OString::number(xCell->getRowSpan())); + + for(sal_Int32 rowIndex = nRow; rowIndex < nRow + xCell->getRowSpan(); ++rowIndex) { + sal_Int32 transposeIndexForMergeCell = (rowIndex*nColumnCount) + nColumn; + mergedCellMap[transposeIndexForMergeCell] = + std::make_pair(transposedIndexofCell, xCell); + } + } + else + { + // now, the cell can be an independent cell or + // it can be a cell which is been merged to some parent cell + if(!xCell->isMerged()) + { + // independent cell + mpFS->startElementNS(XML_a, XML_tc); + } + else + { + // it a merged cell to some parent cell + // find the parent cell for the current cell at hand + transposeTableMap::iterator it = mergedCellMap.find(transposedIndexofCell); + if(it != mergedCellMap.end()) + { + sal_Int32 transposeIndexOfParent = it->second.first; + Reference< XMergeableCell > parentCell = it->second.second; + // finding the row and column index for the parent cell from transposed index + sal_Int32 parentColumnIndex = transposeIndexOfParent % nColumnCount; + sal_Int32 parentRowIndex = transposeIndexOfParent / nColumnCount; + if(nColumn == parentColumnIndex) + { + // the cell is vertical merge and it might have gridspan + if(parentCell->getColumnSpan() > 1) + { + // vMerge and has gridSpan + mpFS->startElementNS(XML_a, XML_tc, + XML_vMerge, OString::number(1), + XML_gridSpan, OString::number(xCell->getColumnSpan())); + } + else + { + // only vMerge + mpFS->startElementNS(XML_a, XML_tc, + XML_vMerge, OString::number(1)); + } + } + else if(nRow == parentRowIndex) + { + // the cell is horizontal merge and it might have rowspan + if(parentCell->getRowSpan() > 1) + { + // hMerge and has rowspan + mpFS->startElementNS(XML_a, XML_tc, + XML_hMerge, OString::number(1), + XML_rowSpan, OString::number(xCell->getRowSpan())); + } + else + { + // only hMerge + mpFS->startElementNS(XML_a, XML_tc, + XML_hMerge, OString::number(1)); + } + } + else + { + // has hMerge and vMerge + mpFS->startElementNS(XML_a, XML_tc, + XML_vMerge, OString::number(1), + XML_hMerge, OString::number(1)); + } + } + else + bCellOpened = false; + } + } + + if (bCellOpened) + { + WriteTextBox( xCell, XML_a ); + + Reference< XPropertySet > xCellPropSet(xCell, UNO_QUERY_THROW); + WriteTableCellProperties(xCellPropSet); + + mpFS->endElementNS( XML_a, XML_tc ); + } + } + + mpFS->endElementNS( XML_a, XML_tr ); + } + + mpFS->endElementNS( XML_a, XML_tbl ); + } + + mpFS->endElementNS( XML_a, XML_graphicData ); + mpFS->endElementNS( XML_a, XML_graphic ); +} + +void ShapeExport::WriteTableCellProperties(const Reference< XPropertySet>& xCellPropSet) +{ + sal_Int32 nLeftMargin(0), nRightMargin(0); + TextVerticalAdjust eVerticalAlignment; + const char* sVerticalAlignment; + + Any aLeftMargin = xCellPropSet->getPropertyValue("TextLeftDistance"); + aLeftMargin >>= nLeftMargin; + + Any aRightMargin = xCellPropSet->getPropertyValue("TextRightDistance"); + aRightMargin >>= nRightMargin; + + Any aVerticalAlignment = xCellPropSet->getPropertyValue("TextVerticalAdjust"); + aVerticalAlignment >>= eVerticalAlignment; + sVerticalAlignment = GetTextVerticalAdjust(eVerticalAlignment); + + mpFS->startElementNS(XML_a, XML_tcPr, XML_anchor, sVerticalAlignment, + XML_marL, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeftMargin)), nLeftMargin > 0), + XML_marR, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nRightMargin)), nRightMargin > 0)); + + // Write background fill for table cell. + // TODO + // tcW : Table cell width + WriteTableCellBorders(xCellPropSet); + DrawingML::WriteFill(xCellPropSet); + mpFS->endElementNS( XML_a, XML_tcPr ); +} + +void ShapeExport::WriteBorderLine(const sal_Int32 XML_line, const BorderLine2& rBorderLine) +{ +// While importing the table cell border line width, it converts EMU->Hmm then divided result by 2. +// To get original value of LineWidth need to multiple by 2. + sal_Int32 nBorderWidth = rBorderLine.LineWidth; + nBorderWidth *= 2; + nBorderWidth = oox::drawingml::convertHmmToEmu( nBorderWidth ); + + if ( nBorderWidth > 0 ) + { + mpFS->startElementNS(XML_a, XML_line, XML_w, OString::number(nBorderWidth)); + if ( rBorderLine.Color == sal_Int32( COL_AUTO ) ) + mpFS->singleElementNS(XML_a, XML_noFill); + else + DrawingML::WriteSolidFill( ::Color(ColorTransparency, rBorderLine.Color) ); + mpFS->endElementNS( XML_a, XML_line ); + } + else if( nBorderWidth == 0) + { + mpFS->startElementNS(XML_a, XML_line); + mpFS->singleElementNS(XML_a, XML_noFill); + mpFS->endElementNS( XML_a, XML_line ); + } +} + +void ShapeExport::WriteTableCellBorders(const Reference< XPropertySet>& xCellPropSet) +{ + BorderLine2 aBorderLine; + +// lnL - Left Border Line Properties of table cell + xCellPropSet->getPropertyValue("LeftBorder") >>= aBorderLine; + WriteBorderLine( XML_lnL, aBorderLine ); + +// lnR - Right Border Line Properties of table cell + xCellPropSet->getPropertyValue("RightBorder") >>= aBorderLine; + WriteBorderLine( XML_lnR, aBorderLine ); + +// lnT - Top Border Line Properties of table cell + xCellPropSet->getPropertyValue("TopBorder") >>= aBorderLine; + WriteBorderLine( XML_lnT, aBorderLine ); + +// lnB - Bottom Border Line Properties of table cell + xCellPropSet->getPropertyValue("BottomBorder") >>= aBorderLine; + WriteBorderLine( XML_lnB, aBorderLine ); +} + +ShapeExport& ShapeExport::WriteTableShape( const Reference< XShape >& xShape ) +{ + FSHelperPtr pFS = GetFS(); + + pFS->startElementNS(mnXmlNamespace, XML_graphicFrame); + + pFS->startElementNS(mnXmlNamespace, XML_nvGraphicFramePr); + + pFS->singleElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + + pFS->singleElementNS(mnXmlNamespace, XML_cNvGraphicFramePr); + + if( GetDocumentType() == DOCUMENT_PPTX ) + pFS->singleElementNS(mnXmlNamespace, XML_nvPr); + pFS->endElementNS( mnXmlNamespace, XML_nvGraphicFramePr ); + + WriteShapeTransformation( xShape, mnXmlNamespace ); + WriteTable( xShape ); + + pFS->endElementNS( mnXmlNamespace, XML_graphicFrame ); + + return *this; +} + +ShapeExport& ShapeExport::WriteTextShape( const Reference< XShape >& xShape ) +{ + FSHelperPtr pFS = GetFS(); + Reference<XPropertySet> xShapeProps(xShape, UNO_QUERY); + + pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp)); + + // non visual shape properties + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + pFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + pFS->startElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + OUString sURL; + if (GetProperty(xShapeProps, "URL")) + mAny >>= sURL; + + if (!sURL.isEmpty()) + { + OUString sRelId = mpFB->addRelation(mpFS->getOutputStream(), + oox::getRelationship(Relationship::HYPERLINK), + mpURLTransformer->getTransformedString(sURL), + mpURLTransformer->isExternalURL(sURL)); + + mpFS->singleElementNS(XML_a, XML_hlinkClick, FSNS(XML_r, XML_id), sRelId); + } + pFS->endElementNS(mnXmlNamespace, XML_cNvPr); + } + pFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr, XML_txBox, "1"); + if (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes) + { + WriteNonVisualProperties( xShape ); + pFS->endElementNS( mnXmlNamespace, XML_nvSpPr ); + } + + // visual shape properties + pFS->startElementNS(mnXmlNamespace, XML_spPr); + WriteShapeTransformation( xShape, XML_a ); + WritePresetShape( "rect" ); + uno::Reference<beans::XPropertySet> xPropertySet(xShape, UNO_QUERY); + if (!IsFontworkShape(xShapeProps)) // Fontwork needs fill and outline in run properties instead. + { + WriteBlipOrNormalFill(xPropertySet, "Graphic"); + WriteOutline(xPropertySet); + WriteShapeEffects(xPropertySet); + } + pFS->endElementNS( mnXmlNamespace, XML_spPr ); + + WriteTextBox( xShape, mnXmlNamespace ); + + pFS->endElementNS( mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX || mbUserShapes ? XML_sp : XML_wsp) ); + + return *this; +} + +void ShapeExport::WriteMathShape(Reference<XShape> const& xShape) +{ + Reference<XPropertySet> const xPropSet(xShape, UNO_QUERY); + assert(xPropSet.is()); + Reference<XModel> xMathModel; + xPropSet->getPropertyValue("Model") >>= xMathModel; + assert(xMathModel.is()); + assert(GetDocumentType() != DOCUMENT_DOCX); // should be written in DocxAttributeOutput + SAL_WARN_IF(GetDocumentType() == DOCUMENT_XLSX, "oox.shape", "Math export to XLSX isn't tested, should it happen here?"); + + // ECMA standard does not actually allow oMath outside of + // WordProcessingML so write a MCE like PPT 2010 does + mpFS->startElementNS(XML_mc, XML_AlternateContent); + mpFS->startElementNS(XML_mc, XML_Choice, + FSNS(XML_xmlns, XML_a14), mpFB->getNamespaceURL(OOX_NS(a14)), + XML_Requires, "a14"); + mpFS->startElementNS(mnXmlNamespace, XML_sp); + mpFS->startElementNS(mnXmlNamespace, XML_nvSpPr); + mpFS->singleElementNS(mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + mpFS->singleElementNS(mnXmlNamespace, XML_cNvSpPr, XML_txBox, "1"); + mpFS->singleElementNS(mnXmlNamespace, XML_nvPr); + mpFS->endElementNS(mnXmlNamespace, XML_nvSpPr); + mpFS->startElementNS(mnXmlNamespace, XML_spPr); + WriteShapeTransformation(xShape, XML_a); + WritePresetShape("rect"); + mpFS->endElementNS(mnXmlNamespace, XML_spPr); + mpFS->startElementNS(mnXmlNamespace, XML_txBody); + mpFS->startElementNS(XML_a, XML_bodyPr); + mpFS->endElementNS(XML_a, XML_bodyPr); + mpFS->startElementNS(XML_a, XML_p); + mpFS->startElementNS(XML_a14, XML_m); + + oox::FormulaExportBase *const pMagic(dynamic_cast<oox::FormulaExportBase*>(xMathModel.get())); + assert(pMagic); + pMagic->writeFormulaOoxml(GetFS(), GetFB()->getVersion(), GetDocumentType(), + FormulaExportBase::eFormulaAlign::INLINE); + + mpFS->endElementNS(XML_a14, XML_m); + mpFS->endElementNS(XML_a, XML_p); + mpFS->endElementNS(mnXmlNamespace, XML_txBody); + mpFS->endElementNS(mnXmlNamespace, XML_sp); + mpFS->endElementNS(XML_mc, XML_Choice); + mpFS->startElementNS(XML_mc, XML_Fallback); + // TODO: export bitmap shape as fallback + mpFS->endElementNS(XML_mc, XML_Fallback); + mpFS->endElementNS(XML_mc, XML_AlternateContent); +} + +ShapeExport& ShapeExport::WriteOLE2Shape( const Reference< XShape >& xShape ) +{ + Reference< XPropertySet > xPropSet( xShape, UNO_QUERY ); + if (!xPropSet.is()) + return *this; + + enum { CHART, MATH, OTHER } eType(OTHER); + OUString clsid; + xPropSet->getPropertyValue("CLSID") >>= clsid; + if (!clsid.isEmpty()) + { + SvGlobalName aClassID; + bool const isValid = aClassID.MakeId(clsid); + assert(isValid); (void)isValid; + if (SotExchange::IsChart(aClassID)) + eType = CHART; + else if (SotExchange::IsMath(aClassID)) + eType = MATH; + } + + if (CHART == eType) + { + Reference< XChartDocument > xChartDoc; + xPropSet->getPropertyValue("Model") >>= xChartDoc; + assert(xChartDoc.is()); + //export the chart +#if !ENABLE_WASM_STRIP_CHART + // WASM_CHART change + // TODO: With Chart extracted this cannot really happen since + // no Chart could've been added at all + ChartExport aChartExport( mnXmlNamespace, GetFS(), xChartDoc, GetFB(), GetDocumentType() ); + static sal_Int32 nChartCount = 0; + aChartExport.WriteChartObj( xShape, GetNewShapeID( xShape ), ++nChartCount ); +#endif + return *this; + } + + if (MATH == eType) + { + WriteMathShape(xShape); + return *this; + } + + uno::Reference<embed::XEmbeddedObject> const xObj( + xPropSet->getPropertyValue("EmbeddedObject"), uno::UNO_QUERY); + + if (!xObj.is()) + { + SAL_WARN("oox.shape", "ShapeExport::WriteOLE2Shape: no object"); + + // tdf#152436 Export the preview graphic of the object if the object is missing. + SdrObject* pSdrOLE2(SdrObject::getSdrObjectFromXShape(xShape)); + if (auto pOle2Obj = dynamic_cast<SdrOle2Obj*>(pSdrOLE2)) + { + const Graphic* pGraphic = pOle2Obj->GetGraphic(); + if (pGraphic) + WriteGraphicObjectShapePart(xShape, pGraphic); + } + + return *this; + } + + uno::Sequence<beans::PropertyValue> grabBag; + OUString entryName; + try + { + uno::Reference<beans::XPropertySet> const xParent( + uno::Reference<container::XChild>(xObj, uno::UNO_QUERY_THROW)->getParent(), + uno::UNO_QUERY_THROW); + + xParent->getPropertyValue("InteropGrabBag") >>= grabBag; + + entryName = uno::Reference<embed::XEmbedPersist>(xObj, uno::UNO_QUERY_THROW)->getEntryName(); + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("oox.shape", "ShapeExport::WriteOLE2Shape"); + return *this; + } + + OUString progID; + + for (auto const& it : std::as_const(grabBag)) + { + if (it.Name == "EmbeddedObjects") + { + uno::Sequence<beans::PropertyValue> objects; + it.Value >>= objects; + for (auto const& object : std::as_const(objects)) + { + if (object.Name == entryName) + { + uno::Sequence<beans::PropertyValue> props; + object.Value >>= props; + for (auto const& prop : std::as_const(props)) + { + if (prop.Name == "ProgID") + { + prop.Value >>= progID; + break; + } + } + break; + } + } + break; + } + } + + OUString sMediaType; + OUString sRelationType; + OUString sSuffix; + const char * pProgID(nullptr); + + uno::Reference<io::XInputStream> const xInStream = + oox::GetOLEObjectStream( + mpFB->getComponentContext(), xObj, progID, + sMediaType, sRelationType, sSuffix, pProgID); + + if (!xInStream.is()) + { + return *this; + } + + OString anotherProgID; + if (!pProgID && !progID.isEmpty()) + { + anotherProgID = OUStringToOString(progID, RTL_TEXTENCODING_UTF8); + pProgID = anotherProgID.getStr(); + } + + assert(!sMediaType.isEmpty()); + assert(!sRelationType.isEmpty()); + assert(!sSuffix.isEmpty()); + + OUString sFileName = "embeddings/oleObject" + OUString::number(++m_nEmbeddedObjects) + "." + sSuffix; + uno::Reference<io::XOutputStream> const xOutStream( + mpFB->openFragmentStream( + OUString::createFromAscii(GetComponentDir()) + "/" + sFileName, + sMediaType)); + assert(xOutStream.is()); // no reason why that could fail + + try { + ::comphelper::OStorageHelper::CopyInputToOutput(xInStream, xOutStream); + } catch (uno::Exception const&) { + TOOLS_WARN_EXCEPTION("oox.shape", "ShapeExport::WriteOLEObject"); + } + + OUString const sRelId = mpFB->addRelation( + mpFS->getOutputStream(), sRelationType, + OUStringConcatenation(OUString::createFromAscii(GetRelationCompPrefix()) + sFileName)); + + mpFS->startElementNS(mnXmlNamespace, XML_graphicFrame); + + mpFS->startElementNS(mnXmlNamespace, XML_nvGraphicFramePr); + + mpFS->singleElementNS( mnXmlNamespace, XML_cNvPr, + XML_id, OString::number(GetNewShapeID(xShape)), + XML_name, GetShapeName(xShape)); + + mpFS->singleElementNS(mnXmlNamespace, XML_cNvGraphicFramePr); + + if (GetDocumentType() == DOCUMENT_PPTX) + mpFS->singleElementNS(mnXmlNamespace, XML_nvPr); + mpFS->endElementNS( mnXmlNamespace, XML_nvGraphicFramePr ); + + WriteShapeTransformation( xShape, mnXmlNamespace ); + + mpFS->startElementNS(XML_a, XML_graphic); + mpFS->startElementNS(XML_a, XML_graphicData, + XML_uri, "http://schemas.openxmlformats.org/presentationml/2006/ole"); + if (pProgID) + { + mpFS->startElementNS( mnXmlNamespace, XML_oleObj, + XML_progId, pProgID, + FSNS(XML_r, XML_id), sRelId, + XML_spid, "" ); + } + else + { + mpFS->startElementNS( mnXmlNamespace, XML_oleObj, +//? XML_name, "Document", + FSNS(XML_r, XML_id), sRelId, + // The spec says that this is a required attribute, but PowerPoint can only handle an empty value. + XML_spid, "" ); + } + + mpFS->singleElementNS( mnXmlNamespace, XML_embed ); + + // pic element + SdrObject* pSdrOLE2(SdrObject::getSdrObjectFromXShape(xShape)); + // The spec doesn't allow <p:pic> here, but PowerPoint requires it. + bool bEcma = mpFB->getVersion() == oox::core::ECMA_DIALECT; + if (bEcma) + if (auto pOle2Obj = dynamic_cast<SdrOle2Obj*>(pSdrOLE2)) + { + const Graphic* pGraphic = pOle2Obj->GetGraphic(); + if (pGraphic) + WriteGraphicObjectShapePart( xShape, pGraphic ); + } + + mpFS->endElementNS( mnXmlNamespace, XML_oleObj ); + + mpFS->endElementNS( XML_a, XML_graphicData ); + mpFS->endElementNS( XML_a, XML_graphic ); + + mpFS->endElementNS( mnXmlNamespace, XML_graphicFrame ); + + return *this; +} + +ShapeExport& ShapeExport::WriteUnknownShape( const Reference< XShape >& ) +{ + // Override this method to do something useful. + return *this; +} + +sal_Int32 ShapeExport::GetNewShapeID( const Reference< XShape >& rXShape ) +{ + return GetNewShapeID( rXShape, GetFB() ); +} + +sal_Int32 ShapeExport::GetNewShapeID( const Reference< XShape >& rXShape, XmlFilterBase* pFB ) +{ + if( !rXShape.is() ) + return -1; + + sal_Int32 nID = pFB->GetUniqueId(); + + (*mpShapeMap)[ rXShape ] = nID; + + return nID; +} + +sal_Int32 ShapeExport::GetShapeID( const Reference< XShape >& rXShape ) +{ + return GetShapeID( rXShape, mpShapeMap ); +} + +sal_Int32 ShapeExport::GetShapeID( const Reference< XShape >& rXShape, ShapeHashMap* pShapeMap ) +{ + if( !rXShape.is() ) + return -1; + + ShapeHashMap::const_iterator aIter = pShapeMap->find( rXShape ); + + if( aIter == pShapeMap->end() ) + return -1; + + return aIter->second; +} + +OUString ShapeExport::GetShapeName(const Reference<XShape>& xShape) +{ + Reference<XPropertySet> rXPropSet(xShape, UNO_QUERY); + + // Empty name keeps the object unnamed. + OUString sName; + + if (GetProperty(rXPropSet, "Name")) + mAny >>= sName; + return sName; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |