diff options
Diffstat (limited to 'filter/source/svg/svgexport.cxx')
-rw-r--r-- | filter/source/svg/svgexport.cxx | 2877 |
1 files changed, 2877 insertions, 0 deletions
diff --git a/filter/source/svg/svgexport.cxx b/filter/source/svg/svgexport.cxx new file mode 100644 index 000000000..c21b041b1 --- /dev/null +++ b/filter/source/svg/svgexport.cxx @@ -0,0 +1,2877 @@ +/* -*- 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 "svgwriter.hxx" +#include "svgfontexport.hxx" +#include "svgfilter.hxx" +#include <svgscript.hxx> + +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/animations/XAnimationNodeSupplier.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/XMasterPageTarget.hpp> +#include <com/sun/star/drawing/GraphicExportFilter.hpp> +#include <com/sun/star/presentation/XPresentationSupplier.hpp> +#include <com/sun/star/style/ParagraphAdjust.hpp> +#include <com/sun/star/text/textfield/Type.hpp> +#include <com/sun/star/util/MeasureUnit.hpp> +#include <com/sun/star/xml/sax/Writer.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/ShapeCollection.hpp> + +#include <rtl/bootstrap.hxx> +#include <svx/unopage.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdoutl.hxx> +#include <editeng/outliner.hxx> +#include <editeng/flditem.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <comphelper/sequenceashashmap.hxx> +#include <i18nlangtag/lang.h> +#include <svl/numformat.hxx> +#include <tools/debug.hxx> +#include <tools/urlobj.hxx> +#include <unotools/streamwrap.hxx> +#include <unotools/tempfile.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <vcl/filter/SvmReader.hxx> +#include <xmloff/unointerfacetouniqueidentifiermapper.hxx> +#include <xmloff/namespacemap.hxx> +#include <xmloff/xmlnamespace.hxx> +#include <xmloff/xmltoken.hxx> +#include <xmloff/animationexport.hxx> +#include <svx/svdograf.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdxcgv.hxx> +#include <sal/log.hxx> +#include <tools/diagnose_ex.h> + +#include <memory> +#include <string_view> + +using namespace css::animations; +using namespace css::presentation; +using namespace ::com::sun::star::graphic; +using namespace ::com::sun::star::geometry; +using namespace ::com::sun::star; +using namespace ::xmloff::token; + +// - ooo elements and attributes - + +#define NSPREFIX u"ooo:" + +constexpr OUStringLiteral SVG_PROP_TINYPROFILE = u"TinyMode"; +constexpr OUStringLiteral SVG_PROP_DTDSTRING = u"DTDString"; +constexpr OUStringLiteral SVG_PROP_EMBEDFONTS = u"EmbedFonts"; +constexpr OUStringLiteral SVG_PROP_NATIVEDECORATION = u"UseNativeTextDecoration"; +constexpr OUStringLiteral SVG_PROP_OPACITY = u"Opacity"; +constexpr OUStringLiteral SVG_PROP_POSITIONED_CHARACTERS = u"UsePositionedCharacters"; + +// ooo xml elements +constexpr OUStringLiteral aOOOElemTextField = NSPREFIX "text_field"; + + +// ooo xml attributes for meta_slide +constexpr OUStringLiteral aOOOAttrSlide = NSPREFIX "slide"; +constexpr OUStringLiteral aOOOAttrMaster = NSPREFIX "master"; +constexpr OUStringLiteral aOOOAttrHasCustomBackground = NSPREFIX "has-custom-background"; +constexpr OUStringLiteral aOOOAttrDisplayName = NSPREFIX "display-name"; +constexpr OUStringLiteral aOOOAttrBackgroundVisibility = NSPREFIX "background-visibility"; +constexpr OUStringLiteral aOOOAttrMasterObjectsVisibility = NSPREFIX "master-objects-visibility"; +constexpr OUStringLiteral aOOOAttrSlideDuration = NSPREFIX "slide-duration"; +constexpr OUStringLiteral aOOOAttrDateTimeField = NSPREFIX "date-time-field"; +constexpr OUStringLiteral aOOOAttrFooterField = NSPREFIX "footer-field"; +constexpr OUStringLiteral aOOOAttrHasTransition = NSPREFIX "has-transition"; + +// ooo xml attributes for pages and shapes +constexpr OUStringLiteral aOOOAttrName = NSPREFIX "name"; + +constexpr OUStringLiteral constSvgNamespace = u"http://www.w3.org/2000/svg"; + + +/** Text Field Class Hierarchy + This is a set of classes for exporting text field meta info. + */ + +namespace { + +class TextField +{ +protected: + SVGFilter::ObjectSet mMasterPageSet; +public: + TextField() = default; + TextField(TextField const &) = default; + TextField(TextField &&) = default; + TextField & operator =(TextField const &) = default; + TextField & operator =(TextField &&) = default; + + virtual OUString getClassName() const + { + return "TextField"; + } + virtual bool equalTo( const TextField & aTextField ) const = 0; + virtual void growCharSet( SVGFilter::UCharSetMapMap & aTextFieldCharSets ) const = 0; + virtual void elementExport( SVGExport* pSVGExport ) const + { + pSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", getClassName() ); + } + void insertMasterPage( const Reference< css::drawing::XDrawPage>& xMasterPage ) + { + mMasterPageSet.insert( xMasterPage ); + } + virtual ~TextField() {} +protected: + void implGrowCharSet( SVGFilter::UCharSetMapMap & aTextFieldCharSets, const OUString& sText, const OUString& sTextFieldId ) const + { + const sal_Unicode * ustr = sText.getStr(); + sal_Int32 nLength = sText.getLength(); + for (auto const& masterPage : mMasterPageSet) + { + const Reference< XInterface > & xMasterPage = masterPage; + for( sal_Int32 i = 0; i < nLength; ++i ) + { + aTextFieldCharSets[ xMasterPage ][ sTextFieldId ].insert( ustr[i] ); + } + } + } +}; + + +class FixedTextField : public TextField +{ +public: + OUString text; + + virtual OUString getClassName() const override + { + return "FixedTextField"; + } + virtual bool equalTo( const TextField & aTextField ) const override + { + if( const FixedTextField* aFixedTextField = dynamic_cast< const FixedTextField* >( &aTextField ) ) + { + return ( text == aFixedTextField->text ); + } + return false; + } + virtual void elementExport( SVGExport* pSVGExport ) const override + { + TextField::elementExport( pSVGExport ); + SvXMLElementExport aExp( *pSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + pSVGExport->GetDocHandler()->characters( text ); + } +}; + + +class FixedDateTimeField : public FixedTextField +{ +public: + FixedDateTimeField() {} + virtual OUString getClassName() const override + { + return "FixedDateTimeField"; + } + virtual void growCharSet( SVGFilter::UCharSetMapMap & aTextFieldCharSets ) const override + { + implGrowCharSet( aTextFieldCharSets, text, aOOOAttrDateTimeField ); + } +}; + + +class FooterField : public FixedTextField +{ +public: + FooterField() {} + virtual OUString getClassName() const override + { + return "FooterField"; + } + virtual void growCharSet( SVGFilter::UCharSetMapMap & aTextFieldCharSets ) const override + { + static constexpr OUStringLiteral sFieldId = aOOOAttrFooterField; + implGrowCharSet( aTextFieldCharSets, text, sFieldId ); + } +}; + + +class VariableTextField : public TextField +{ +public: + virtual OUString getClassName() const override + { + return "VariableTextField"; + } +}; + + +class VariableDateTimeField : public VariableTextField +{ +public: + sal_Int32 format; + + VariableDateTimeField() + : format(0) + { + } + virtual OUString getClassName() const override + { + return "VariableDateTimeField"; + } + virtual bool equalTo( const TextField & aTextField ) const override + { + if( const VariableDateTimeField* aField = dynamic_cast< const VariableDateTimeField* >( &aTextField ) ) + { + return ( format == aField->format ); + } + return false; + } + virtual void elementExport( SVGExport* pSVGExport ) const override + { + VariableTextField::elementExport( pSVGExport ); + OUString sDateFormat, sTimeFormat; + SvxDateFormat eDateFormat = static_cast<SvxDateFormat>( format & 0x0f ); + if( eDateFormat != SvxDateFormat::AppDefault ) + { + switch( eDateFormat ) + { + case SvxDateFormat::StdSmall: + case SvxDateFormat::A: // 13.02.96 + sDateFormat.clear(); + break; + case SvxDateFormat::C: // 13.Feb 1996 + sDateFormat.clear(); + break; + case SvxDateFormat::D: // 13.February 1996 + sDateFormat.clear(); + break; + case SvxDateFormat::E: // Tue, 13.February 1996 + sDateFormat.clear(); + break; + case SvxDateFormat::StdBig: + case SvxDateFormat::F: // Tuesday, 13.February 1996 + sDateFormat.clear(); + break; + // default case + case SvxDateFormat::B: // 13.02.1996 + default: + sDateFormat.clear(); + break; + } + } + + SvxTimeFormat eTimeFormat = static_cast<SvxTimeFormat>( ( format >> 4 ) & 0x0f ); + if( eTimeFormat != SvxTimeFormat::AppDefault ) + { + switch( eTimeFormat ) + { + case SvxTimeFormat::HH24_MM_SS: // 13:49:38 + sTimeFormat.clear(); + break; + case SvxTimeFormat::HH12_MM_AMPM: // 01:49 PM + case SvxTimeFormat::HH12_MM: + sTimeFormat.clear(); + break; + case SvxTimeFormat::HH12_MM_SS_AMPM: // 01:49:38 PM + case SvxTimeFormat::HH12_MM_SS: + sTimeFormat.clear(); + break; + // default case + case SvxTimeFormat::HH24_MM: // 13:49 + default: + sTimeFormat.clear(); + break; + } + } + + OUString sDateTimeFormat = sDateFormat + " " + sTimeFormat; + + pSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "date-time-format", sDateTimeFormat ); + SvXMLElementExport aExp( *pSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + } + virtual void growCharSet( SVGFilter::UCharSetMapMap & aTextFieldCharSets ) const override + { + // we use the unicode char set in an improper way: we put in the date/time format + // in order to pass it to the CalcFieldValue method + static const OUString sFieldId = aOOOAttrDateTimeField + "-variable"; + for (auto const& masterPage : mMasterPageSet) + { + aTextFieldCharSets[ masterPage ][ sFieldId ].insert( static_cast<sal_Unicode>( format ) ); + } + } +}; + + +bool operator==( const TextField & aLhsTextField, const TextField & aRhsTextField ) +{ + return aLhsTextField.equalTo( aRhsTextField ); +} + +} + +SVGExport::SVGExport( + const css::uno::Reference< css::uno::XComponentContext >& rContext, + const Reference< XDocumentHandler >& rxHandler, + const Sequence< PropertyValue >& rFilterData ) + : SvXMLExport( rContext, "", + util::MeasureUnit::MM_100TH, + xmloff::token::XML_TOKEN_INVALID, + SvXMLExportFlags::META|SvXMLExportFlags::PRETTY ) +{ + SetDocHandler( rxHandler ); + GetDocHandler()->startDocument(); + + // initializing filter settings from filter data + comphelper::SequenceAsHashMap aFilterDataHashMap = rFilterData; + + // TinyProfile + mbIsUseTinyProfile = aFilterDataHashMap.getUnpackedValueOrDefault(SVG_PROP_TINYPROFILE, false); + + // DTD string + mbIsUseDTDString = aFilterDataHashMap.getUnpackedValueOrDefault(SVG_PROP_DTDSTRING, true); + + // Font Embedding + comphelper::SequenceAsHashMap::const_iterator iter = aFilterDataHashMap.find(SVG_PROP_EMBEDFONTS); + if(iter==aFilterDataHashMap.end()) + { + OUString v; + mbIsEmbedFonts = !rtl::Bootstrap::get("SVG_DISABLE_FONT_EMBEDDING", v); + } + else + { + if(!(iter->second >>= mbIsEmbedFonts)) + mbIsEmbedFonts = false; + } + + // Native Decoration + mbIsUseNativeTextDecoration = !mbIsUseTinyProfile && aFilterDataHashMap.getUnpackedValueOrDefault(SVG_PROP_NATIVEDECORATION, true); + + // Tiny Opacity (supported from SVG Tiny 1.2) + mbIsUseOpacity = aFilterDataHashMap.getUnpackedValueOrDefault(SVG_PROP_OPACITY, true); + + // Positioned Characters (The old method) + mbIsUsePositionedCharacters = aFilterDataHashMap.getUnpackedValueOrDefault(SVG_PROP_POSITIONED_CHARACTERS, false); + + // add namespaces + GetNamespaceMap_().Add( + GetXMLToken(XML_NP_PRESENTATION), + GetXMLToken(XML_N_PRESENTATION), + XML_NAMESPACE_PRESENTATION); + + GetNamespaceMap_().Add( + GetXMLToken(XML_NP_SMIL), + GetXMLToken(XML_N_SMIL_COMPAT), + XML_NAMESPACE_SMIL); + + GetNamespaceMap_().Add( + GetXMLToken(XML_NP_ANIMATION), + GetXMLToken(XML_N_ANIMATION), + XML_NAMESPACE_ANIMATION); +} + +SVGExport::~SVGExport() +{ + GetDocHandler()->endDocument(); +} + +ObjectRepresentation::ObjectRepresentation() +{ +} + +ObjectRepresentation::ObjectRepresentation( const Reference< XInterface >& rxObject, + const GDIMetaFile& rMtf ) : + mxObject( rxObject ), + mxMtf( new GDIMetaFile( rMtf ) ) +{ +} + +ObjectRepresentation::ObjectRepresentation( const ObjectRepresentation& rPresentation ) : + mxObject( rPresentation.mxObject ), + mxMtf( rPresentation.mxMtf ? new GDIMetaFile( *rPresentation.mxMtf ) : nullptr ) +{ +} + +ObjectRepresentation& ObjectRepresentation::operator=( const ObjectRepresentation& rPresentation ) +{ + // Check for self-assignment + if (this == &rPresentation) + return *this; + + mxObject = rPresentation.mxObject; + mxMtf.reset(rPresentation.mxMtf ? new GDIMetaFile(*rPresentation.mxMtf) : nullptr); + + return *this; +} + + +namespace +{ + +BitmapChecksum GetBitmapChecksum( const MetaAction* pAction ) +{ + if( !pAction ) + { + OSL_FAIL( "GetBitmapChecksum: passed MetaAction pointer is null." ); + return 0; + } + + BitmapChecksum nChecksum = 0; + const MetaActionType nType = pAction->GetType(); + + switch( nType ) + { + case MetaActionType::BMPSCALE: + { + const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction); + // The conversion to BitmapEx is needed since a Bitmap object is + // converted to BitmapEx before passing it to SVGActionWriter::ImplWriteBmp + // where the checksum is checked for matching. + nChecksum = BitmapEx( pA->GetBitmap() ).GetChecksum(); + } + break; + case MetaActionType::BMPEXSCALE: + { + const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction); + nChecksum = pA->GetBitmapEx().GetChecksum(); + } + break; + default: break; + } + return nChecksum; +} + +MetaAction* CreateMetaBitmapAction( const MetaAction* pAction, const Point& rPt, const Size& rSz ) +{ + if( !pAction ) + { + OSL_FAIL( "CreateMetaBitmapAction: passed MetaAction pointer is null." ); + return nullptr; + } + + MetaAction* pResAction = nullptr; + const MetaActionType nType = pAction->GetType(); + switch( nType ) + { + case MetaActionType::BMPSCALE: + { + const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction); + pResAction = new MetaBmpScaleAction( rPt, rSz, pA->GetBitmap() ); + } + break; + case MetaActionType::BMPEXSCALE: + { + const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction); + pResAction = new MetaBmpExScaleAction( rPt, rSz, pA->GetBitmapEx() ); + } + break; + default: break; + } + return pResAction; +} + +void MetaBitmapActionGetPoint( const MetaAction* pAction, Point& rPt ) +{ + if( !pAction ) + { + OSL_FAIL( "MetaBitmapActionGetPoint: passed MetaAction pointer is null." ); + return; + } + const MetaActionType nType = pAction->GetType(); + switch( nType ) + { + case MetaActionType::BMPSCALE: + { + const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction); + rPt = pA->GetPoint(); + } + break; + case MetaActionType::BMPEXSCALE: + { + const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction); + rPt = pA->GetPoint(); + } + break; + default: break; + } +} + +void MetaBitmapActionGetSize( const MetaAction* pAction, Size& rSz ) +{ + if( !pAction ) + { + OSL_FAIL( "MetaBitmapActionGetSize: passed MetaAction pointer is null." ); + return; + } + const MetaActionType nType = pAction->GetType(); + switch( nType ) + { + case MetaActionType::BMPSCALE: + { + const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction); + rSz = pA->GetSize(); + } + break; + case MetaActionType::BMPEXSCALE: + { + const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction); + rSz = pA->GetSize(); + } + break; + default: break; + } +} + +void MetaBitmapActionGetOrigSize( const MetaAction* pAction, Size& rSz ) +{ + if( !pAction ) + { + OSL_FAIL( "MetaBitmapActionGetOrigSize: passed MetaAction pointer is null." ); + return; + } + + const MetaActionType nType = pAction->GetType(); + MapMode aSourceMode( MapUnit::MapPixel ); + MapMode aTargetMode( MapUnit::Map100thMM ); + + switch( nType ) + { + case MetaActionType::BMPSCALE: + { + const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction); + const Bitmap& rBitmap = pA->GetBitmap(); + rSz = rBitmap.GetSizePixel(); + } + break; + case MetaActionType::BMPEXSCALE: + { + const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction); + const BitmapEx& rBitmap = pA->GetBitmapEx(); + rSz = rBitmap.GetSizePixel(); + } + break; + default: break; + } + rSz = OutputDevice::LogicToLogic( rSz, aSourceMode, aTargetMode ); +} + +OUString getPatternIdForTiledBackground( std::u16string_view sSlideId, BitmapChecksum nChecksum ) +{ + return OUString::Concat("bg-pattern.") + sSlideId + "." + OUString::number( nChecksum ); +} + +OUString getIdForTiledBackground( std::u16string_view sSlideId, BitmapChecksum nChecksum ) +{ + return OUString::Concat("bg-") + sSlideId + "." + OUString::number( nChecksum ); +} + +} // end anonymous namespace + +size_t HashBitmap::operator()( const ObjectRepresentation& rObjRep ) const +{ + const GDIMetaFile& aMtf = rObjRep.GetRepresentation(); + if( aMtf.GetActionSize() == 1 ) + { + return static_cast< size_t >( GetBitmapChecksum( aMtf.GetAction( 0 ) ) ); + } + else + { + OSL_FAIL( "HashBitmap: metafile should have a single action." ); + return 0; + } +} + + +bool EqualityBitmap::operator()( const ObjectRepresentation& rObjRep1, + const ObjectRepresentation& rObjRep2 ) const +{ + const GDIMetaFile& aMtf1 = rObjRep1.GetRepresentation(); + const GDIMetaFile& aMtf2 = rObjRep2.GetRepresentation(); + if( aMtf1.GetActionSize() == 1 && aMtf2.GetActionSize() == 1 ) + { + BitmapChecksum nChecksum1 = GetBitmapChecksum( aMtf1.GetAction( 0 ) ); + BitmapChecksum nChecksum2 = GetBitmapChecksum( aMtf2.GetAction( 0 ) ); + return ( nChecksum1 == nChecksum2 ); + } + else + { + OSL_FAIL( "EqualityBitmap: metafile should have a single action." ); + return false; + } +} + + +bool SVGFilter::implExport( const Sequence< PropertyValue >& rDescriptor ) +{ + Reference< XOutputStream > xOStm; + std::unique_ptr<SvStream> pOStm; + sal_Int32 nLength = rDescriptor.getLength(); + const PropertyValue* pValue = rDescriptor.getConstArray(); + + maFilterData.realloc( 0 ); + + for ( sal_Int32 i = 0 ; i < nLength; ++i) + { + if ( pValue[ i ].Name == "OutputStream" ) + pValue[ i ].Value >>= xOStm; + else if ( pValue[ i ].Name == "FileName" ) + { + OUString aFileName; + + pValue[ i ].Value >>= aFileName; + pOStm = ::utl::UcbStreamHelper::CreateStream( aFileName, StreamMode::WRITE | StreamMode::TRUNC ); + + if( pOStm ) + xOStm.set( new ::utl::OOutputStreamWrapper ( *pOStm ) ); + } + else if ( pValue[ i ].Name == "FilterData" ) + { + pValue[ i ].Value >>= maFilterData; + } + } + + if(mbWriterFilter || mbCalcFilter) + return implExportWriterOrCalc(xOStm); + + return implExportImpressOrDraw(xOStm); +} + +bool SVGFilter::implExportImpressOrDraw( const Reference< XOutputStream >& rxOStm) +{ + Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() ) ; + bool bRet = false; + + if( rxOStm.is() ) + { + if( !mSelectedPages.empty() && !mMasterPageTargets.empty() ) + { + Reference< XDocumentHandler > xDocHandler = implCreateExportDocumentHandler( rxOStm ); + + if( xDocHandler.is() ) + { + mbPresentation = Reference< XPresentationSupplier >( mxSrcDoc, UNO_QUERY ).is(); + mpObjects = new ObjectMap; + + // mpSVGExport = new SVGExport( xDocHandler ); + mpSVGExport = new SVGExport( xContext, xDocHandler, maFilterData ); + + // create an id for each draw page + for( const auto& rPage : mSelectedPages ) + implRegisterInterface( rPage ); + + // create an id for each master page + for(const uno::Reference<drawing::XDrawPage> & mMasterPageTarget : mMasterPageTargets) + implRegisterInterface( mMasterPageTarget ); + + SdrModel* pSdrModel(nullptr); + + try + { + mxDefaultPage = mSelectedPages[0]; + + if( mxDefaultPage.is() ) + { + SvxDrawPage* pSvxDrawPage = comphelper::getFromUnoTunnel<SvxDrawPage>( mxDefaultPage ); + + if( pSvxDrawPage ) + { + mpDefaultSdrPage = pSvxDrawPage->GetSdrPage(); + pSdrModel = &mpDefaultSdrPage->getSdrModelFromSdrPage(); + SdrOutliner& rOutl = pSdrModel->GetDrawOutliner(); + + maOldFieldHdl = rOutl.GetCalcFieldValueHdl(); + maNewFieldHdl = LINK(this, SVGFilter, CalcFieldHdl); + rOutl.SetCalcFieldValueHdl(maNewFieldHdl); + } + + bRet = implExportDocument(); + } + } + catch( ... ) + { + delete mpSVGDoc; + mpSVGDoc = nullptr; + SAL_WARN("filter.svg", "Exception caught"); + } + + if( nullptr != pSdrModel ) + { + // fdo#62682 The maNewFieldHdl can end up getting copied + // into various other outliners which live past this + // method, so get the full list of outliners and restore + // the maOldFieldHdl for all that have ended up using + // maNewFieldHdl + std::vector<SdrOutliner*> aOutliners(pSdrModel->GetActiveOutliners()); + for (auto const& outliner : aOutliners) + { + if (maNewFieldHdl == outliner->GetCalcFieldValueHdl()) + outliner->SetCalcFieldValueHdl(maOldFieldHdl); + } + } + + delete mpSVGWriter; + mpSVGWriter = nullptr; + mpSVGExport = nullptr; // pointed object is released by xSVGExport dtor at the end of this scope + delete mpSVGFontExport; + mpSVGFontExport = nullptr; + delete mpObjects; + mpObjects = nullptr; + mbPresentation = false; + } + } + } + return bRet; +} + + +bool SVGFilter::implExportWriterOrCalc( const Reference< XOutputStream >& rxOStm ) +{ + Reference< XComponentContext > xContext( ::comphelper::getProcessComponentContext() ) ; + bool bRet = false; + + if( rxOStm.is() ) + { + Reference< XDocumentHandler > xDocHandler = implCreateExportDocumentHandler( rxOStm ); + + if( xDocHandler.is() ) + { + mpObjects = new ObjectMap; + + // mpSVGExport = new SVGExport( xDocHandler ); + mpSVGExport = new SVGExport( xContext, xDocHandler, maFilterData ); + + try + { + mxDefaultPage = mSelectedPages[0]; + bRet = implExportDocument(); + } + catch( ... ) + { + TOOLS_WARN_EXCEPTION( "filter.svg", "" ); + delete mpSVGDoc; + mpSVGDoc = nullptr; + } + + delete mpSVGWriter; + mpSVGWriter = nullptr; + mpSVGExport = nullptr; // pointed object is released by xSVGExport dtor at the end of this scope + delete mpSVGFontExport; + mpSVGFontExport = nullptr; + delete mpObjects; + mpObjects = nullptr; + } + } + return bRet; +} + +bool SVGFilter::implExportWriterTextGraphic( const Reference< view::XSelectionSupplier >& xSelectionSupplier ) +{ + Any selection = xSelectionSupplier->getSelection(); + uno::Reference<lang::XServiceInfo> xSelection; + selection >>= xSelection; + if (!xSelection || !xSelection->supportsService("com.sun.star.text.TextGraphicObject")) + return true; + + uno::Reference<beans::XPropertySet> xPropertySet(xSelection, uno::UNO_QUERY); + + uno::Reference<graphic::XGraphic> xOriginalGraphic; + xPropertySet->getPropertyValue("Graphic") >>= xOriginalGraphic; + const Graphic aOriginalGraphic(xOriginalGraphic); + + uno::Reference<graphic::XGraphic> xTransformedGraphic; + xPropertySet->getPropertyValue( + mbIsPreview ? OUString("GraphicPreview") : OUString("TransformedGraphic")) + >>= xTransformedGraphic; + + if (!xTransformedGraphic.is()) + return false; + const Graphic aTransformedGraphic(xTransformedGraphic); + bool bSameGraphic = aTransformedGraphic == aOriginalGraphic || + aOriginalGraphic.GetChecksum() == aTransformedGraphic.GetChecksum(); + const Graphic aGraphic = bSameGraphic ? aOriginalGraphic : aTransformedGraphic; + uno::Reference<graphic::XGraphic> xGraphic = bSameGraphic ? xOriginalGraphic : xTransformedGraphic; + + // Calculate size from Graphic + Point aPos( OutputDevice::LogicToLogic(aGraphic.GetPrefMapMode().GetOrigin(), aGraphic.GetPrefMapMode(), MapMode(MapUnit::Map100thMM)) ); + Size aSize( OutputDevice::LogicToLogic(aGraphic.GetPrefSize(), aGraphic.GetPrefMapMode(), MapMode(MapUnit::Map100thMM)) ); + + assert(mSelectedPages.size() == 1); + SvxDrawPage* pSvxDrawPage(comphelper::getFromUnoTunnel<SvxDrawPage>(mSelectedPages[0])); + if(pSvxDrawPage == nullptr || pSvxDrawPage->GetSdrPage() == nullptr) + return false; + + SdrGrafObj* pGraphicObj = new SdrGrafObj(pSvxDrawPage->GetSdrPage()->getSdrModelFromSdrPage(), aGraphic, tools::Rectangle( aPos, aSize )); + uno::Reference< drawing::XShape > xShape = GetXShapeForSdrObject(pGraphicObj); + uno::Reference< XPropertySet > xShapePropSet(xShape, uno::UNO_QUERY); + xShapePropSet->setPropertyValue("Graphic", uno::Any(xGraphic)); + + maShapeSelection = drawing::ShapeCollection::create(comphelper::getProcessComponentContext()); + maShapeSelection->add(xShape); + + return true; +} + + +Reference< XWriter > SVGFilter::implCreateExportDocumentHandler( const Reference< XOutputStream >& rxOStm ) +{ + Reference< XWriter > xSaxWriter; + + if( rxOStm.is() ) + { + xSaxWriter = Writer::create( ::comphelper::getProcessComponentContext() ); + xSaxWriter->setOutputStream( rxOStm ); + } + + return xSaxWriter; +} + + +bool SVGFilter::implLookForFirstVisiblePage() +{ + sal_Int32 nCurPage = 0, nLastPage = mSelectedPages.size() - 1; + + if(!mbPresentation || mbSinglePage) + { + mnVisiblePage = nCurPage; + } + + while( ( nCurPage <= nLastPage ) && ( -1 == mnVisiblePage ) ) + { + const Reference< css::drawing::XDrawPage > & xDrawPage = mSelectedPages[nCurPage]; + + if( xDrawPage.is() ) + { + Reference< XPropertySet > xPropSet( xDrawPage, UNO_QUERY ); + + if( xPropSet.is() ) + { + bool bVisible = false; + + if( ( xPropSet->getPropertyValue( "Visible" ) >>= bVisible ) && bVisible ) + { + mnVisiblePage = nCurPage; + } + } + } + ++nCurPage; + } + + return ( mnVisiblePage != -1 ); +} + + +bool SVGFilter::implExportDocument() +{ + sal_Int32 nDocX = 0, nDocY = 0; // #i124608# + sal_Int32 nDocWidth = 0, nDocHeight = 0; + bool bRet = false; + sal_Int32 nLastPage = mSelectedPages.size() - 1; + + mbSinglePage = (nLastPage == 0); + mnVisiblePage = -1; + + const Reference< XPropertySet > xDefaultPagePropertySet( mxDefaultPage, UNO_QUERY ); + + // #i124608# + mbExportShapeSelection = mbSinglePage && maShapeSelection.is() && maShapeSelection->getCount(); + + if(xDefaultPagePropertySet.is()) + { + xDefaultPagePropertySet->getPropertyValue( "Width" ) >>= nDocWidth; + xDefaultPagePropertySet->getPropertyValue( "Height" ) >>= nDocHeight; + } + + if(mbExportShapeSelection) + { + // #i124608# create BoundRange and set nDocX, nDocY, nDocWidth and nDocHeight + basegfx::B2DRange aShapeRange; + + for(sal_Int32 a(0); a < maShapeSelection->getCount(); a++) + { + Reference< css::drawing::XShape > xShapeCandidate; + if((maShapeSelection->getByIndex(a) >>= xShapeCandidate) && xShapeCandidate.is()) + { + Reference< XPropertySet > xShapePropSet( xShapeCandidate, UNO_QUERY ); + css::awt::Rectangle aBoundRect; + if( xShapePropSet.is() && ( xShapePropSet->getPropertyValue( "BoundRect" ) >>= aBoundRect )) + { + aShapeRange.expand(basegfx::B2DTuple(aBoundRect.X, aBoundRect.Y)); + aShapeRange.expand(basegfx::B2DTuple(aBoundRect.X + aBoundRect.Width, + aBoundRect.Y + aBoundRect.Height)); + } + } + } + + if(!aShapeRange.isEmpty()) + { + nDocX = basegfx::fround(aShapeRange.getMinX()); + nDocY = basegfx::fround(aShapeRange.getMinY()); + nDocWidth = basegfx::fround(aShapeRange.getWidth()); + nDocHeight = basegfx::fround(aShapeRange.getHeight()); + } + } + + if(mbWriterFilter || mbCalcFilter) + implExportDocumentHeaderWriterOrCalc(nDocX, nDocY, nDocWidth, nDocHeight); + else + implExportDocumentHeaderImpressOrDraw(nDocX, nDocY, nDocWidth, nDocHeight); + + + if( implLookForFirstVisiblePage() ) // OK! We found at least one visible page. + { + if( mbPresentation && !mbExportShapeSelection ) + { + implGenerateMetaData(); + implExportAnimations(); + } + else + { + implGetPagePropSet( mSelectedPages[0] ); + } + + // Create the (Shape, GDIMetaFile) map + if( implCreateObjects() ) + { + ::std::vector< ObjectRepresentation > aObjects( mpObjects->size() ); + sal_uInt32 nPos = 0; + + for (auto const& elem : *mpObjects) + { + aObjects[ nPos++ ] = elem.second; + } + + mpSVGFontExport = new SVGFontExport( *mpSVGExport, std::move(aObjects) ); + mpSVGWriter = new SVGActionWriter( *mpSVGExport, *mpSVGFontExport ); + + if( mpSVGExport->IsEmbedFonts() ) + { + mpSVGFontExport->EmbedFonts(); + } + if( !mpSVGExport->IsUsePositionedCharacters() ) + { + implExportTextShapeIndex(); + implEmbedBulletGlyphs(); + implExportTextEmbeddedBitmaps(); + implExportBackgroundBitmaps(); + mpSVGWriter->SetEmbeddedBitmapRefs( &maBitmapActionMap ); + implExportTiledBackground(); + } + if( mbIsPreview ) + mpSVGWriter->SetPreviewMode(); + + // #i124608# export a given object selection, so no MasterPage export at all + if (!mbExportShapeSelection) + implExportMasterPages( mMasterPageTargets, 0, mMasterPageTargets.size() - 1 ); + implExportDrawPages( mSelectedPages, 0, nLastPage ); + + if( mbPresentation && !mbExportShapeSelection ) + { + implGenerateScript(); + } + + delete mpSVGDoc; + mpSVGDoc = nullptr; + bRet = true; + } + } + + return bRet; +} + +void SVGFilter::implExportDocumentHeaderImpressOrDraw(sal_Int32 nDocX, sal_Int32 nDocY, + sal_Int32 nDocWidth, sal_Int32 nDocHeight) +{ + const Reference< XExtendedDocumentHandler > xExtDocHandler( mpSVGExport->GetDocHandler(), UNO_QUERY ); + if( xExtDocHandler.is() && !mpSVGExport->IsUseTinyProfile() ) + { + xExtDocHandler->unknown( SVG_DTD_STRING ); + } + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "version", "1.2" ); + + if( mpSVGExport->IsUseTinyProfile() ) + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "baseProfile", "tiny" ); + + // The following if block means that the slide size is not adapted + // to the size of the browser window, moreover the slide is top left aligned + // instead of centered: + OUString aAttr; + if( !mbPresentation ) + { + aAttr = OUString::number( nDocWidth * 0.01 ) + "mm"; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", aAttr ); + + aAttr = OUString::number( nDocHeight * 0.01 ) + "mm"; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", aAttr ); + } + + // #i124608# set viewBox explicitly to the exported content + if (mbExportShapeSelection) + { + aAttr = OUString::number(nDocX) + " " + OUString::number(nDocY) + " "; + } + else + { + aAttr = "0 0 "; + } + + aAttr += OUString::number(nDocWidth) + " " + OUString::number(nDocHeight); + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "viewBox", aAttr ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "preserveAspectRatio", "xMidYMid" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "fill-rule", "evenodd" ); + + // standard line width is based on 1 pixel on a 90 DPI device (0.28222mmm) + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "stroke-width", OUString::number( 28.222 ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "stroke-linejoin", "round" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns", constSvgNamespace ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:ooo", "http://xml.openoffice.org/svg/export" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:xlink", "http://www.w3.org/1999/xlink" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:presentation", "http://sun.com/xmlns/staroffice/presentation" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:smil", "http://www.w3.org/2001/SMIL20/" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:anim", "urn:oasis:names:tc:opendocument:xmlns:animation:1.0" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xml:space", "preserve" ); + + mpSVGDoc = new SvXMLElementExport( *mpSVGExport, XML_NAMESPACE_NONE, "svg", true, true ); + + // Create a ClipPath element that will be used for cutting bitmaps and other elements that could exceed the page margins. + if(mbExportShapeSelection) + return; + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "ClipPathGroup" ); + SvXMLElementExport aDefsElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true ); + { + msClipPathId = "presentation_clip_path"; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", msClipPathId ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "clipPathUnits", "userSpaceOnUse" ); + SvXMLElementExport aClipPathElem( *mpSVGExport, XML_NAMESPACE_NONE, "clipPath", true, true ); + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "x", OUString::number( nDocX ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "y", OUString::number( nDocY ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", OUString::number( nDocWidth ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", OUString::number( nDocHeight ) ); + SvXMLElementExport aRectElem( *mpSVGExport, XML_NAMESPACE_NONE, "rect", true, true ); + } + } + // Create a ClipPath element applied to the leaving slide in order + // to avoid that slide borders are visible during transition + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", "presentation_clip_path_shrink" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "clipPathUnits", "userSpaceOnUse" ); + SvXMLElementExport aClipPathElem( *mpSVGExport, XML_NAMESPACE_NONE, "clipPath", true, true ); + { + sal_Int32 nDocWidthExt = nDocWidth / 500; + sal_Int32 nDocHeightExt = nDocHeight / 500; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "x", OUString::number( nDocX + nDocWidthExt / 2 ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "y", OUString::number( nDocY + nDocHeightExt / 2) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", OUString::number( nDocWidth - nDocWidthExt ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", OUString::number( nDocHeight - nDocHeightExt ) ); + SvXMLElementExport aRectElem( *mpSVGExport, XML_NAMESPACE_NONE, "rect", true, true ); + } + } +} + +void SVGFilter::implExportDocumentHeaderWriterOrCalc(sal_Int32 nDocX, sal_Int32 nDocY, + sal_Int32 nDocWidth, sal_Int32 nDocHeight) +{ + OUString aAttr; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "version", "1.2" ); + + aAttr = OUString::number( nDocWidth * 0.01 ) + "mm"; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", aAttr ); + + aAttr = OUString::number( nDocHeight * 0.01 ) + "mm"; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", aAttr ); + + aAttr = OUString::number(nDocX) + " " + OUString::number(nDocY) + " " + + OUString::number(nDocWidth) + " " + OUString::number(nDocHeight); + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "viewBox", aAttr ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "preserveAspectRatio", "xMidYMid" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "fill-rule", "evenodd" ); + + // standard line width is based on 1 pixel on a 90 DPI device (0.28222mmm) + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "stroke-width", OUString::number( 28.222 ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "stroke-linejoin", "round" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns", constSvgNamespace ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:ooo", "http://xml.openoffice.org/svg/export" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:xlink", "http://www.w3.org/1999/xlink" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:office", "urn:oasis:names:tc:opendocument:xmlns:office:1.0" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xmlns:smil", "urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xml:space", "preserve" ); + + mpSVGDoc = new SvXMLElementExport( *mpSVGExport, XML_NAMESPACE_NONE, "svg", true, true ); +} + +/// Append aField to aFieldSet if it is not already present in the set and create the field id sFieldId +template< typename TextFieldType > +static OUString implGenerateFieldId( std::vector< std::unique_ptr<TextField> > & aFieldSet, + const TextFieldType & aField, + std::u16string_view sOOOElemField, + const Reference< css::drawing::XDrawPage >& xMasterPage ) +{ + bool bFound = false; + sal_Int32 i; + sal_Int32 nSize = aFieldSet.size(); + for( i = 0; i < nSize; ++i ) + { + if( *(aFieldSet[i]) == aField ) + { + bFound = true; + break; + } + } + OUString sFieldId(OUString::Concat(sOOOElemField) + "_"); + if( !bFound ) + { + aFieldSet.emplace_back( new TextFieldType( aField ) ); + } + aFieldSet[i]->insertMasterPage( xMasterPage ); + sFieldId += OUString::number( i ); + return sFieldId; +} + + +void SVGFilter::implGenerateMetaData() +{ + sal_Int32 nCount = mSelectedPages.size(); + if( nCount == 0 ) + return; + + // we wrap all meta presentation info into a svg:defs element + SvXMLElementExport aDefsElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true ); + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", NSPREFIX "meta_slides" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "number-of-slides", OUString::number( nCount ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "start-slide-number", OUString::number( mnVisiblePage ) ); + + if( mpSVGExport->IsUsePositionedCharacters() ) + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "use-positioned-chars", "true" ); + } + + // Add a (global) Page Numbering Type attribute for the document + // NOTE: at present pSdrModel->GetPageNumType() returns always css::style::NumberingType::ARABIC + // so the following code fragment is pretty useless + sal_Int32 nPageNumberingType = css::style::NumberingType::ARABIC; + SvxDrawPage* pSvxDrawPage = comphelper::getFromUnoTunnel<SvxDrawPage>( mSelectedPages[0] ); + if( pSvxDrawPage ) + { + SdrPage* pSdrPage = pSvxDrawPage->GetSdrPage(); + SdrModel& rSdrModel(pSdrPage->getSdrModelFromSdrPage()); + nPageNumberingType = rSdrModel.GetPageNumType(); + + // That is used by CalcFieldHdl method. + mVisiblePagePropSet.nPageNumberingType = nPageNumberingType; + } + if( nPageNumberingType != css::style::NumberingType::NUMBER_NONE ) + { + OUString sNumberingType; + switch( nPageNumberingType ) + { + case css::style::NumberingType::CHARS_UPPER_LETTER: + sNumberingType = "alpha-upper"; + break; + case css::style::NumberingType::CHARS_LOWER_LETTER: + sNumberingType = "alpha-lower"; + break; + case css::style::NumberingType::ROMAN_UPPER: + sNumberingType = "roman-upper"; + break; + case css::style::NumberingType::ROMAN_LOWER: + sNumberingType = "roman-lower"; + break; + case css::style::NumberingType::ARABIC: + // arabic numbering type is the default, so we do not append any attribute for it + default: + // in case the numbering type is not handled we fall back on arabic numbering + break; + } + if( !sNumberingType.isEmpty() ) + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "page-numbering-type", sNumberingType ); + } + + + { + SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + const OUString aId( NSPREFIX "meta_slide" ); + const OUString aElemTextFieldId( aOOOElemTextField ); + std::vector< std::unique_ptr<TextField> > aFieldSet; + + // dummy slide - used as leaving slide for transition on the first slide + if( mbPresentation ) + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", NSPREFIX "meta_dummy_slide" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrSlide, "dummy-slide" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrMaster, "dummy-master-page" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrBackgroundVisibility, "hidden" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrMasterObjectsVisibility, "hidden" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrHasTransition, "false" ); + SvXMLElementExport aMetaDummySlideElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + } + + for( sal_Int32 i = 0; i < nCount; ++i ) + { + const Reference< css::drawing::XDrawPage > & xDrawPage = mSelectedPages[i]; + Reference< css::drawing::XMasterPageTarget > xMasterPageTarget( xDrawPage, UNO_QUERY ); + if (!xMasterPageTarget.is()) + continue; + Reference< css::drawing::XDrawPage > xMasterPage = xMasterPageTarget->getMasterPage(); + OUString aSlideId(aId + "_" + OUString::number( i )); + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", aSlideId ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrSlide, implGetValidIDFromInterface( xDrawPage ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrMaster, implGetValidIDFromInterface( xMasterPage ) ); + + + if( mbPresentation ) + { + Reference< XPropertySet > xPropSet( xDrawPage, UNO_QUERY ); + + if( xPropSet.is() ) + { + OUString sDisplayName; + if (xPropSet->getPropertyValue("LinkDisplayName") >>= sDisplayName) + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrDisplayName, sDisplayName); + } + + bool bBackgroundVisibility = true; // default: visible + bool bBackgroundObjectsVisibility = true; // default: visible + + FixedDateTimeField aFixedDateTimeField; + VariableDateTimeField aVariableDateTimeField; + FooterField aFooterField; + + // check if the slide has a custom background which overlaps the master page background + Reference< XPropertySet > xBackground; + xPropSet->getPropertyValue( "Background" ) >>= xBackground; + if( xBackground.is() ) + { + drawing::FillStyle aFillStyle; + bool assigned = ( xBackground->getPropertyValue( "FillStyle" ) >>= aFillStyle ); + // has a custom background ? + if( assigned && aFillStyle != drawing::FillStyle_NONE ) + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrHasCustomBackground, "true" ); + } + + xPropSet->getPropertyValue( "IsBackgroundVisible" ) >>= bBackgroundVisibility; + // in case the attribute is set to its default value it is not appended to the meta-slide element + if( !bBackgroundVisibility ) // visibility default value: 'visible' + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrBackgroundVisibility, "hidden" ); + + // Page Number, Date/Time, Footer and Header Fields are regarded as background objects. + // So bBackgroundObjectsVisibility overrides visibility of master page text fields. + xPropSet->getPropertyValue( "IsBackgroundObjectsVisible" ) >>= bBackgroundObjectsVisibility; + if( bBackgroundObjectsVisibility ) // visibility default value: 'visible' + { + bool bPageNumberVisibility = false; // default: hidden + bool bDateTimeVisibility = true; // default: visible + bool bFooterVisibility = true; // default: visible + + // Page Number Field + xPropSet->getPropertyValue( "IsPageNumberVisible" ) >>= bPageNumberVisibility; + bPageNumberVisibility = bPageNumberVisibility && ( nPageNumberingType != css::style::NumberingType::NUMBER_NONE ); + if( bPageNumberVisibility ) // visibility default value: 'hidden' + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "page-number-visibility", "visible" ); + } + + // Date/Time Field + xPropSet->getPropertyValue( "IsDateTimeVisible" ) >>= bDateTimeVisibility; + if( bDateTimeVisibility ) // visibility default value: 'visible' + { + bool bDateTimeFixed = true; // default: fixed + xPropSet->getPropertyValue( "IsDateTimeFixed" ) >>= bDateTimeFixed; + if( bDateTimeFixed ) // we are interested only in the field text not in the date/time format + { + xPropSet->getPropertyValue( "DateTimeText" ) >>= aFixedDateTimeField.text; + if( !aFixedDateTimeField.text.isEmpty() ) + { + OUString sFieldId = implGenerateFieldId( aFieldSet, aFixedDateTimeField, aElemTextFieldId, xMasterPage ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrDateTimeField, sFieldId ); + } + } + else // the inverse applies: we are interested only in the date/time format not in the field text + { + xPropSet->getPropertyValue( "DateTimeFormat" ) >>= aVariableDateTimeField.format; + OUString sFieldId = implGenerateFieldId( aFieldSet, aVariableDateTimeField, aElemTextFieldId, xMasterPage ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrDateTimeField, sFieldId ); + } + } + else + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "date-time-visibility", "hidden" ); + } + + // Footer Field + xPropSet->getPropertyValue( "IsFooterVisible" ) >>= bFooterVisibility; + if( bFooterVisibility ) // visibility default value: 'visible' + { + xPropSet->getPropertyValue( "FooterText" ) >>= aFooterField.text; + if( !aFooterField.text.isEmpty() ) + { + OUString sFieldId = implGenerateFieldId( aFieldSet, aFooterField, aElemTextFieldId, xMasterPage ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrFooterField, sFieldId ); + } + } + else + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "footer-visibility", "hidden" ); + } + } + else + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrMasterObjectsVisibility, "hidden" ); + } + + sal_Int32 nChange(0); + + if( xPropSet->getPropertySetInfo()->hasPropertyByName( "Change" ) && + (xPropSet->getPropertyValue( "Change" ) >>= nChange ) && nChange == 1 ) + { + double fSlideDuration(0); + if( xPropSet->getPropertySetInfo()->hasPropertyByName( "HighResDuration" ) && + (xPropSet->getPropertyValue( "HighResDuration" ) >>= fSlideDuration) ) + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrSlideDuration, OUString::number(fSlideDuration) ); + } + } + // We look for a slide transition. + // Transition properties are exported together with animations. + sal_Int16 nTransitionType(0); + if( xPropSet->getPropertySetInfo()->hasPropertyByName( "TransitionType" ) && + (xPropSet->getPropertyValue( "TransitionType" ) >>= nTransitionType) ) + { + sal_Int16 nTransitionSubType(0); + if( xPropSet->getPropertyValue( "TransitionSubtype" ) >>= nTransitionSubType ) + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrHasTransition, "true" ); + } + } + + } + } + + { + SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + } // when the aExp2 destructor is called the </g> tag is appended to the output file + } + + // export text field elements + if( mbPresentation ) + { + for( sal_Int32 i = 0, nSize = aFieldSet.size(); i < nSize; ++i ) + { + OUString sElemId = OUString::Concat(aOOOElemTextField) + "_" + OUString::number( i ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sElemId ); + aFieldSet[i]->elementExport( mpSVGExport.get() ); + } + if( mpSVGExport->IsEmbedFonts() && mpSVGExport->IsUsePositionedCharacters() ) + { + for(const std::unique_ptr<TextField>& i : aFieldSet) + { + i->growCharSet( mTextFieldCharSets ); + } + } + } + + // text fields are used only for generating meta info so we don't need them anymore + aFieldSet.clear(); + } +} + + +void SVGFilter::implExportAnimations() +{ + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", "presentation-animations" ); + SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true ); + + for(const uno::Reference<drawing::XDrawPage> & mSelectedPage : mSelectedPages) + { + Reference< XPropertySet > xProps( mSelectedPage, UNO_QUERY ); + + if( xProps.is() && xProps->getPropertySetInfo()->hasPropertyByName( "TransitionType" ) ) + { + sal_Int16 nTransition = 0; + xProps->getPropertyValue( "TransitionType" ) >>= nTransition; + // we have a slide transition ? + bool bHasEffects = ( nTransition != 0 ); + + Reference< XAnimationNodeSupplier > xAnimNodeSupplier( mSelectedPage, UNO_QUERY ); + if( xAnimNodeSupplier.is() ) + { + Reference< XAnimationNode > xRootNode = xAnimNodeSupplier->getAnimationNode(); + if( xRootNode.is() ) + { + if( !bHasEffects ) + { + // first check if there are no animations + Reference< XEnumerationAccess > xEnumerationAccess( xRootNode, UNO_QUERY_THROW ); + Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW ); + if( xEnumeration->hasMoreElements() ) + { + // first child node may be an empty main sequence, check this + Reference< XAnimationNode > xMainNode( xEnumeration->nextElement(), UNO_QUERY_THROW ); + Reference< XEnumerationAccess > xMainEnumerationAccess( xMainNode, UNO_QUERY_THROW ); + Reference< XEnumeration > xMainEnumeration( xMainEnumerationAccess->createEnumeration(), UNO_SET_THROW ); + + // only export if the main sequence is not empty or if there are additional + // trigger sequences + bHasEffects = xMainEnumeration->hasMoreElements() || xEnumeration->hasMoreElements(); + } + } + if( bHasEffects ) + { + OUString sId = implGetValidIDFromInterface( mSelectedPage ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrSlide, sId ); + sId += "-animations"; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sId ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Animations" ); + SvXMLElementExport aDefsElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true ); + + rtl::Reference< xmloff::AnimationsExporter > xAnimationsExporter = new xmloff::AnimationsExporter( *mpSVGExport, xProps ); + xAnimationsExporter->prepare( xRootNode ); + xAnimationsExporter->exportAnimations( xRootNode ); + } + } + } + } + } +} + + +void SVGFilter::implExportTextShapeIndex() +{ + if(mbExportShapeSelection) + return; + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "TextShapeIndex" ); + SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true ); + + sal_Int32 nCount = mSelectedPages.size(); + for( sal_Int32 i = 0; i < nCount; ++i ) + { + const Reference< css::drawing::XDrawPage > & xDrawPage = mSelectedPages[i]; + if( mTextShapeIdListMap.find( xDrawPage ) != mTextShapeIdListMap.end() ) + { + OUString sTextShapeIdList = mTextShapeIdListMap[xDrawPage].trim(); + + const OUString& rPageId = implGetValidIDFromInterface( Reference<XInterface>(xDrawPage, UNO_QUERY) ); + if( !rPageId.isEmpty() && !sTextShapeIdList.isEmpty() ) + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrSlide, rPageId ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "id-list", sTextShapeIdList ); + SvXMLElementExport aGElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + } + } + } +} + + +void SVGFilter::implEmbedBulletGlyphs() +{ + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "EmbeddedBulletChars" ); + SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true ); + + OUString sPathData = "M 580,1141 L 1163,571 580,0 -4,571 580,1141 Z"; + implEmbedBulletGlyph( 57356, sPathData ); + sPathData = "M 8,1128 L 1137,1128 1137,0 8,0 8,1128 Z"; + implEmbedBulletGlyph( 57354, sPathData ); + sPathData = "M 174,0 L 602,739 174,1481 1456,739 174,0 Z M 1358,739 L 309,1346 659,739 1358,739 Z"; + implEmbedBulletGlyph( 10146, sPathData ); + sPathData = "M 2015,739 L 1276,0 717,0 1260,543 174,543 174,936 1260,936 717,1481 1274,1481 2015,739 Z"; + implEmbedBulletGlyph( 10132, sPathData ); + sPathData = "M 0,-2 C -7,14 -16,27 -25,37 L 356,567 C 262,823 215,952 215,954 215,979 228,992 255,992 264,992 276,990 289,987 310,991 331,999 354,1012 L 381,999 492,748 772,1049 836,1024 860,1049 C 881,1039 901,1025 922,1006 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 C 774,196 753,168 711,139 L 727,119 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 C 142,-110 111,-127 72,-127 30,-127 9,-110 8,-76 1,-67 -2,-52 -2,-32 -2,-23 -1,-13 0,-2 Z"; + implEmbedBulletGlyph( 10007, sPathData ); + sPathData = "M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 41,549 55,616 82,672 116,743 169,778 240,778 293,778 328,747 346,684 L 369,508 C 377,444 397,411 428,410 L 1163,1116 C 1174,1127 1196,1133 1229,1133 1271,1133 1292,1118 1292,1087 L 1292,965 C 1292,929 1282,901 1262,881 L 442,47 C 390,-6 338,-33 285,-33 Z"; + implEmbedBulletGlyph( 10004, sPathData ); + sPathData = "M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 223,773 276,916 383,1023 489,1130 632,1184 813,1184 992,1184 1136,1130 1245,1023 1353,916 1407,772 1407,592 1407,412 1353,268 1245,161 1136,54 992,0 813,0 Z"; + implEmbedBulletGlyph( 9679, sPathData ); + sPathData = "M 346,457 C 273,457 209,483 155,535 101,586 74,649 74,723 74,796 101,859 155,911 209,963 273,989 346,989 419,989 480,963 531,910 582,859 608,796 608,723 608,648 583,586 532,535 482,483 420,457 346,457 Z"; + implEmbedBulletGlyph( 8226, sPathData ); + sPathData = "M -4,459 L 1135,459 1135,606 -4,606 -4,459 Z"; + implEmbedBulletGlyph( 8211, sPathData ); + sPathData = "M 173,740 C 173,903 231,1043 346,1159 462,1274 601,1332 765,1332 928,1332 1067,1274 1183,1159 1299,1043 1357,903 1357,740 1357,577 1299,437 1183,322 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"; + implEmbedBulletGlyph( 61548, sPathData ); +} + + +void SVGFilter::implEmbedBulletGlyph( sal_Unicode cBullet, const OUString & sPathData ) +{ + OUString sId = "bullet-char-template-" + OUString::number( static_cast<sal_Int32>(cBullet) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sId ); + + OUString sFactor = OUString::number( 1.0 / 2048 ); + OUString sTransform = "scale(" + sFactor + ",-" + sFactor + ")"; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "transform", sTransform ); + + SvXMLElementExport aGElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "d", sPathData ); + SvXMLElementExport aPathElem( *mpSVGExport, XML_NAMESPACE_NONE, "path", true, true ); + + mpSVGExport->SetEmbeddedBulletGlyph(cBullet); +} + +void SVGFilter::implExportBackgroundBitmaps() +{ + if (maBitmapActionMap.empty()) + return; + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "BackgroundBitmaps" ); + SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true ); + + OUString sId; + for( const auto& rItem : maBitmapActionMap ) + { + BitmapChecksum nChecksum = rItem.first; + const GDIMetaFile& aEmbeddedBitmapMtf = *(rItem.second); + MetaAction* pBitmapAction = aEmbeddedBitmapMtf.GetAction( 0 ); + if( pBitmapAction ) + { + sId = "bitmap(" + OUString::number( nChecksum ) + ")"; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sId ); + + const Point aPos; // (0, 0) + const Size aSize = aEmbeddedBitmapMtf.GetPrefSize(); + mpSVGWriter->WriteMetaFile( aPos, aSize, aEmbeddedBitmapMtf, 0xffffffff ); + } + } +} + +void SVGFilter::implExportTiledBackground() +{ + if( maPatterProps.empty() ) + return; + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "BackgroundPatterns" ); + SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true ); + + for( const auto& [ rSlideId, rData ] : maPatterProps ) + { + auto aBitmapActionIt = maBitmapActionMap.find( rData.aBitmapChecksum ); + if( aBitmapActionIt != maBitmapActionMap.end() ) + { + // pattern element attributes + const OUString sPatternId = getPatternIdForTiledBackground( rSlideId, rData.aBitmapChecksum ); + // <pattern> <use> + { + // pattern element attributes + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sPatternId ); + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "x", OUString::number( rData.aPos.X() ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "y", OUString::number( rData.aPos.Y() ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", OUString::number( rData.aSize.Width() ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", OUString::number( rData.aSize.Height() ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "patternUnits", "userSpaceOnUse" ); + + SvXMLElementExport aPatternElem( *mpSVGExport, XML_NAMESPACE_NONE, "pattern", true, true ); + + // use element attributes + const Size& aOrigSize = aBitmapActionIt->second->GetPrefSize(); + OUString sTransform; + Fraction aFractionX( rData.aSize.Width(), aOrigSize.Width() ); + Fraction aFractionY( rData.aSize.Height(), aOrigSize.Height() ); + double scaleX = rtl_math_round( double(aFractionX), 3, rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected ); + double scaleY = rtl_math_round( double(aFractionY), 3, rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected ); + if( !rtl_math_approxEqual( scaleX, 1.0 ) || !rtl_math_approxEqual( scaleY, 1.0 ) ) + sTransform += " scale(" + OUString::number( double(aFractionX) ) + ", " + OUString::number( double(aFractionY) ) + ")"; + + if( !sTransform.isEmpty() ) + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "transform", sTransform ); + + // referenced bitmap + OUString sRefId = "#bitmap(" + OUString::number( rData.aBitmapChecksum ) + ")"; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xlink:href", sRefId ); + + SvXMLElementExport aUseElem( *mpSVGExport, XML_NAMESPACE_NONE, "use", true, true ); + } // </use> </pattern> + + // <g> <rect> + { + // group + const OUString sBgId = getIdForTiledBackground( rSlideId, rData.aBitmapChecksum ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sBgId ); + + SvXMLElementExport aGroupElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + + // rectangle + const OUString sUrl = "url(#" + sPatternId + ")"; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "x", "0" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "y", "0" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", OUString::number( rData.aSlideSize.Width() ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", OUString::number( rData.aSlideSize.Height() ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "stroke", "none" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "fill", sUrl ); + + SvXMLElementExport aRectElem( *mpSVGExport, XML_NAMESPACE_NONE, "rect", true, true ); + } // </g> </rect> + } + } +} + +/** SVGFilter::implExportTextEmbeddedBitmaps + We export bitmaps embedded into text shapes, such as those used by list + items with image style, only once in a specific defs element. + */ +void SVGFilter::implExportTextEmbeddedBitmaps() +{ + if (mEmbeddedBitmapActionSet.empty()) + return; + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "TextEmbeddedBitmaps" ); + SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true ); + + OUString sId; + + for (auto const& embeddedBitmapAction : mEmbeddedBitmapActionSet) + { + const GDIMetaFile& aMtf = embeddedBitmapAction.GetRepresentation(); + + if( aMtf.GetActionSize() == 1 ) + { + MetaAction* pAction = aMtf.GetAction( 0 ); + if( pAction ) + { + BitmapChecksum nId = GetBitmapChecksum( pAction ); + sId = "bitmap(" + OUString::number( nId ) + ")"; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sId ); + + const Reference< XInterface >& rxShape = embeddedBitmapAction.GetObject(); + Reference< XPropertySet > xShapePropSet( rxShape, UNO_QUERY ); + css::awt::Rectangle aBoundRect; + if( xShapePropSet.is() && ( xShapePropSet->getPropertyValue( "BoundRect" ) >>= aBoundRect ) ) + { + // Origin of the coordinate device must be (0,0). + const Point aTopLeft; + const Size aSize( aBoundRect.Width, aBoundRect.Height ); + + Point aPt; + MetaBitmapActionGetPoint( pAction, aPt ); + // The image must be exported with x, y attribute set to 0, + // on the contrary when referenced by a <use> element, + // specifying the wanted position, they will result + // misplaced. + pAction->Move( -aPt.X(), -aPt.Y() ); + mpSVGWriter->WriteMetaFile( aTopLeft, aSize, aMtf, 0xffffffff ); + // We reset to the original values so that when the <use> + // element is created the x, y attributes are correct. + pAction->Move( aPt.X(), aPt.Y() ); + } + else + { + OSL_FAIL( "implExportTextEmbeddedBitmaps: no shape bounding box." ); + return; + } + } + else + { + OSL_FAIL( "implExportTextEmbeddedBitmaps: metafile should have MetaBmpExScaleAction only." ); + return; + } + } + else + { + OSL_FAIL( "implExportTextEmbeddedBitmaps: metafile should have a single action." ); + return; + } + } +} + +void SVGFilter::implGenerateScript() +{ + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "type", "text/ecmascript" ); + + { + SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, "script", true, true ); + Reference< XExtendedDocumentHandler > xExtDocHandler( mpSVGExport->GetDocHandler(), UNO_QUERY ); + + if( xExtDocHandler.is() ) + { + for (size_t i = 0; i < N_SVGSCRIPT_FRAGMENTS; ++i) + { + xExtDocHandler->unknown(OUString::createFromAscii(g_SVGScripts[i])); + } + } + } +} + + +Any SVGFilter::implSafeGetPagePropSet( const OUString & sPropertyName, + const Reference< XPropertySet > & rxPropSet, + const Reference< XPropertySetInfo > & rxPropSetInfo ) +{ + Any result; + if( rxPropSetInfo->hasPropertyByName( sPropertyName ) ) + { + result = rxPropSet->getPropertyValue( sPropertyName ); + } + return result; +} + + +/** We collect info on master page elements visibility, and placeholder text shape content. + This method is used when exporting a single page as implGenerateMetaData is not invoked. + */ +void SVGFilter::implGetPagePropSet( const Reference< css::drawing::XDrawPage > & rxPage ) +{ + mVisiblePagePropSet.bIsBackgroundVisible = true; + mVisiblePagePropSet.bAreBackgroundObjectsVisible = true; + mVisiblePagePropSet.bIsPageNumberFieldVisible = false; + mVisiblePagePropSet.bIsHeaderFieldVisible = false; + mVisiblePagePropSet.bIsFooterFieldVisible = true; + mVisiblePagePropSet.bIsDateTimeFieldVisible = true; + mVisiblePagePropSet.bIsDateTimeFieldFixed = true; + mVisiblePagePropSet.nDateTimeFormat = SvxDateFormat::B; + mVisiblePagePropSet.nPageNumberingType = css::style::NumberingType::ARABIC; + + // We collect info on master page elements visibility, and placeholder text shape content. + Reference< XPropertySet > xPropSet( rxPage, UNO_QUERY ); + if( !xPropSet.is() ) + return; + + Reference< XPropertySetInfo > xPropSetInfo( xPropSet->getPropertySetInfo() ); + if( !xPropSetInfo.is() ) + return; + + implSafeGetPagePropSet( "IsBackgroundVisible", xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsBackgroundVisible; + implSafeGetPagePropSet( "IsBackgroundObjectsVisible", xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bAreBackgroundObjectsVisible; + implSafeGetPagePropSet( "IsPageNumberVisible", xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsPageNumberFieldVisible; + implSafeGetPagePropSet( "IsHeaderVisible", xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsHeaderFieldVisible; + implSafeGetPagePropSet( "IsFooterVisible", xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsFooterFieldVisible; + implSafeGetPagePropSet( "IsDateTimeVisible", xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsDateTimeFieldVisible; + + implSafeGetPagePropSet( "IsDateTimeFixed", xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsDateTimeFieldFixed; + sal_Int32 nTmp; + if (implSafeGetPagePropSet( "DateTimeFormat", xPropSet, xPropSetInfo ) >>= nTmp) + mVisiblePagePropSet.nDateTimeFormat = static_cast<SvxDateFormat>(nTmp); + + if( mVisiblePagePropSet.bIsPageNumberFieldVisible ) + { + SvxDrawPage* pSvxDrawPage = comphelper::getFromUnoTunnel<SvxDrawPage>( rxPage ); + if( pSvxDrawPage ) + { + SdrPage* pSdrPage = pSvxDrawPage->GetSdrPage(); + SdrModel& rSdrModel(pSdrPage->getSdrModelFromSdrPage()); + mVisiblePagePropSet.nPageNumberingType = rSdrModel.GetPageNumType(); + } + } +} + + +bool SVGFilter::implExportMasterPages( const std::vector< Reference< css::drawing::XDrawPage > > & rxPages, + sal_Int32 nFirstPage, sal_Int32 nLastPage ) +{ + DBG_ASSERT( nFirstPage <= nLastPage, + "SVGFilter::implExportMasterPages: nFirstPage > nLastPage" ); + + // When the exported slides are more than one we wrap master page elements + // with a svg <defs> element. + OUString aContainerTag = (!mbPresentation) ? OUString( "g" ) : OUString( "defs" ); + SvXMLElementExport aContainerElement( *mpSVGExport, XML_NAMESPACE_NONE, aContainerTag, true, true ); + + // dummy slide - used as leaving slide for transition on the first slide + if( mbPresentation ) + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", "dummy-master-page" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrName, "dummy-master-page" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Master_Slide" ); + SvXMLElementExport aMasterSlideElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", "bg-dummy-master-page" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Background" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "visibility", "hidden" ); + SvXMLElementExport aBackgroundElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + } + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", "bo-dummy-master-page" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "BackgroundObjects" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "visibility", "hidden" ); + SvXMLElementExport aBackgroundObjectElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + } + } + + bool bRet = false; + for( sal_Int32 i = nFirstPage; i <= nLastPage; ++i ) + { + if( rxPages[i].is() ) + { + // add id attribute + const OUString & sPageId = implGetValidIDFromInterface( rxPages[i] ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sPageId ); + + bRet = implExportPage( sPageId, rxPages[i], rxPages[i], true /* is a master page */ ) || bRet; + } + } + return bRet; +} + + +void SVGFilter::implExportDrawPages( const std::vector< Reference< css::drawing::XDrawPage > > & rxPages, + sal_Int32 nFirstPage, sal_Int32 nLastPage ) +{ + DBG_ASSERT( nFirstPage <= nLastPage, + "SVGFilter::implExportDrawPages: nFirstPage > nLastPage" ); + + // dummy slide - used as leaving slide for transition on the first slide + if( mbPresentation && !mbExportShapeSelection) + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "DummySlide" ); + SvXMLElementExport aDummySlideElement( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + { + SvXMLElementExport aGElement( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", "dummy-slide" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Slide" ); + OUString sClipPathAttrValue = "url(#" + msClipPathId + ")"; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "clip-path", sClipPathAttrValue ); + SvXMLElementExport aSlideElement( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrName, "dummy-page" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Page" ); + SvXMLElementExport aPageElement( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + } + } + } + } + + if(!mbExportShapeSelection) + { + // We wrap all slide in a group element with class name "SlideGroup". + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "SlideGroup" ); + SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + + for( sal_Int32 i = nFirstPage; i <= nLastPage; ++i ) + { + Reference< css::drawing::XShapes > xShapes; + + if (mbExportShapeSelection) + { + // #i124608# export a given object selection + xShapes = maShapeSelection; + } + else + { + xShapes = rxPages[i]; + } + + if( xShapes.is() ) + { + // Insert the <g> open tag related to the svg element for + // handling a slide visibility. + // In case the exported slides are more than one the initial + // visibility of each slide is set to 'hidden'. + if( mbPresentation ) + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "visibility", "hidden" ); + } + SvXMLElementExport aGElement( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + + + { + // Insert a further inner the <g> open tag for handling elements + // inserted before or after a slide: that is used for some + // when switching from the last to the first slide. + const OUString & sPageId = implGetValidIDFromInterface( rxPages[i] ); + OUString sContainerId = "container-" + sPageId; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sContainerId ); + SvXMLElementExport aContainerExp( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + + { + // add id attribute + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sPageId ); + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Slide" ); + + // Adding a clip path to each exported slide , so in case + // bitmaps or other elements exceed the slide margins, they are + // trimmed, even when they are shown inside a thumbnail view. + OUString sClipPathAttrValue = "url(#" + msClipPathId + ")"; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "clip-path", sClipPathAttrValue ); + + SvXMLElementExport aSlideElement( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + + implExportPage( sPageId, rxPages[i], xShapes, false /* is not a master page */ ); + } + } // append the </g> closing tag related to inserted elements + } // append the </g> closing tag related to the svg element handling the slide visibility + } + } + else + { + assert(maShapeSelection.is()); + assert(rxPages.size() == 1); + + const OUString & sPageId = implGetValidIDFromInterface( rxPages[0] ); + implExportPage( sPageId, rxPages[0], maShapeSelection, false /* is not a master page */ ); + } +} + + +bool SVGFilter::implExportPage( std::u16string_view sPageId, + const Reference< css::drawing::XDrawPage > & rxPage, + const Reference< css::drawing::XShapes > & xShapes, + bool bMaster ) +{ + bool bRet = false; + + { + OUString sPageName = implGetInterfaceName( rxPage ); + if( mbPresentation && !sPageName.isEmpty() ) + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrName, sPageName ); + + { + Reference< XExtendedDocumentHandler > xExtDocHandler( mpSVGExport->GetDocHandler(), UNO_QUERY ); + + if( xExtDocHandler.is() ) + { + OUString aDesc; + + if( bMaster ) + aDesc = "Master_Slide"; + else + aDesc = "Page"; + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", aDesc ); + } + } + + // insert the <g> open tag related to the DrawPage/MasterPage + SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + + // In case the page has a background object we append it . + if( mpObjects->find( rxPage ) != mpObjects->end() ) + { + const GDIMetaFile& rMtf = (*mpObjects)[ rxPage ].GetRepresentation(); + if( rMtf.GetActionSize() ) + { + // If this is not a master page wrap the slide custom background + // by a <defs> element. + // Slide custom background, if any, is referenced at a different position + // in order to not overlap background objects. + std::unique_ptr<SvXMLElementExport> xDefsExp; + if (!bMaster) // insert the <defs> open tag related to the slide background + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "SlideBackground" ); + xDefsExp.reset( new SvXMLElementExport( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true ) ); + } + { + // background id = "bg-" + page id + OUString sBackgroundId = OUString::Concat("bg-") + sPageId; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sBackgroundId ); + + // At present (LibreOffice 3.4.0) the 'IsBackgroundVisible' property is not handled + // by Impress; anyway we handle this property as referring only to the visibility + // of the master page background. So if a slide has its own background object, + // the visibility of such a background object is always inherited from the visibility + // of the parent slide regardless of the value of the 'IsBackgroundVisible' property. + // This means that we need to set up the visibility attribute only for the background + // element of a master page. + if( !mbPresentation && bMaster ) + { + if( !mVisiblePagePropSet.bIsBackgroundVisible ) + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "visibility", "hidden" ); + } + } + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Background" ); + + // insert the <g> open tag related to the Background + SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + + // append all elements that make up the Background + const Point aNullPt; + mpSVGWriter->WriteMetaFile( aNullPt, rMtf.GetPrefSize(), rMtf, SVGWRITER_WRITE_FILL ); + } // insert the </g> closing tag related to the Background + + } // insert the </defs> closing tag related to the slide background + } + + // In case we are dealing with a master page we need to group all its shapes + // into a group element, this group will make up the so named "background objects" + if( bMaster ) + { + // background objects id = "bo-" + page id + OUString sBackgroundObjectsId = OUString::Concat("bo-") + sPageId; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sBackgroundObjectsId ); + if( !mbPresentation ) + { + if( !mVisiblePagePropSet.bAreBackgroundObjectsVisible ) + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "visibility", "hidden" ); + } + } + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "BackgroundObjects" ); + + // insert the <g> open tag related to the Background Objects + SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + + // append all shapes that make up the Master Slide + bRet = implExportShapes( xShapes, true ) || bRet; + } // append the </g> closing tag related to the Background Objects + else + { + // append all shapes that make up the Slide + bRet = implExportShapes( xShapes, false ) || bRet; + } + } // append the </g> closing tag related to the Slide/Master_Slide + + return bRet; +} + + +bool SVGFilter::implExportShapes( const Reference< css::drawing::XShapes >& rxShapes, bool bMaster ) +{ + Reference< css::drawing::XShape > xShape; + bool bRet = false; + + for( sal_Int32 i = 0, nCount = rxShapes->getCount(); i < nCount; ++i ) + { + if( ( rxShapes->getByIndex( i ) >>= xShape ) && xShape.is() ) + bRet = implExportShape( xShape, bMaster ) || bRet; + + xShape = nullptr; + } + + return bRet; +} + + +bool SVGFilter::implExportShape( const Reference< css::drawing::XShape >& rxShape, bool bMaster ) +{ + Reference< XPropertySet > xShapePropSet( rxShape, UNO_QUERY ); + bool bRet = false; + + if( xShapePropSet.is() ) + { + const OUString aShapeType( rxShape->getShapeType() ); + bool bHideObj = false; + + if( mbPresentation ) + { + xShapePropSet->getPropertyValue( "IsEmptyPresentationObject" ) >>= bHideObj; + } + + OUString aShapeClass = implGetClassFromShape( rxShape ); + if( bMaster ) + { + if( aShapeClass == "TitleText" || aShapeClass == "Outline" ) + bHideObj = true; + } + + if( !bHideObj ) + { + if( aShapeType.lastIndexOf( "drawing.GroupShape" ) != -1 ) + { + Reference< css::drawing::XShapes > xShapes( rxShape, UNO_QUERY ); + + if( xShapes.is() ) + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "Group" ); + const OUString& rShapeId = implGetValidIDFromInterface( Reference<XInterface>(rxShape, UNO_QUERY) ); + if( !rShapeId.isEmpty() ) + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", rShapeId ); + } + SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + + bRet = implExportShapes( xShapes, bMaster ); + } + } + + if( !bRet && mpObjects->find( rxShape ) != mpObjects->end() ) + { + css::awt::Rectangle aBoundRect; + const GDIMetaFile& rMtf = (*mpObjects)[ rxShape ].GetRepresentation(); + + xShapePropSet->getPropertyValue( "BoundRect" ) >>= aBoundRect; + + const Point aTopLeft( aBoundRect.X, aBoundRect.Y ); + const Size aSize( aBoundRect.Width, aBoundRect.Height ); + + if( rMtf.GetActionSize() ) + { // for text field shapes we set up text-adjust attributes + // and set visibility to hidden + OUString aElementId; + + if( mbPresentation ) + { + bool bIsPageNumber = ( aShapeClass == "Slide_Number" ); + bool bIsFooter = ( aShapeClass == "Footer" ); + bool bIsDateTime = ( aShapeClass == "Date/Time" ); + bool bTextField = bIsPageNumber || bIsFooter || bIsDateTime; + if( bTextField ) + { + // to notify to the SVGActionWriter::ImplWriteActions method + // that we are dealing with a placeholder shape + aElementId = sPlaceholderTag; + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "visibility", "hidden" ); + } + + if( bTextField || ( aShapeClass == "TextShape" ) ) + { + sal_uInt16 nTextAdjust = sal_uInt16(ParagraphAdjust_LEFT); + OUString sTextAdjust; + xShapePropSet->getPropertyValue( "ParaAdjust" ) >>= nTextAdjust; + + switch( static_cast<ParagraphAdjust>(nTextAdjust) ) + { + case ParagraphAdjust_LEFT: + sTextAdjust = "left"; + break; + case ParagraphAdjust_CENTER: + sTextAdjust = "center"; + break; + case ParagraphAdjust_RIGHT: + sTextAdjust = "right"; + break; + default: + break; + } + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "text-adjust", sTextAdjust ); + } + } + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", aShapeClass ); + SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + + Reference< XExtendedDocumentHandler > xExtDocHandler( mpSVGExport->GetDocHandler(), UNO_QUERY ); + + OUString aTitle; + xShapePropSet->getPropertyValue( "Title" ) >>= aTitle; + if( !aTitle.isEmpty() ) + { + SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, "title", true, true ); + xExtDocHandler->characters( aTitle ); + } + + OUString aDescription; + xShapePropSet->getPropertyValue( "Description" ) >>= aDescription; + if( !aDescription.isEmpty() ) + { + SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, "desc", true, true ); + xExtDocHandler->characters( aDescription ); + } + + + const OUString& rShapeId = implGetValidIDFromInterface( Reference<XInterface>(rxShape, UNO_QUERY) ); + if( !rShapeId.isEmpty() ) + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", rShapeId ); + } + + const GDIMetaFile* pEmbeddedBitmapsMtf = nullptr; + if( mEmbeddedBitmapActionMap.find( rxShape ) != mEmbeddedBitmapActionMap.end() ) + { + pEmbeddedBitmapsMtf = &( mEmbeddedBitmapActionMap[ rxShape ].GetRepresentation() ); + } + + { + OUString aBookmark; + Reference<XPropertySetInfo> xShapePropSetInfo = xShapePropSet->getPropertySetInfo(); + if(xShapePropSetInfo->hasPropertyByName("Bookmark")) + { + xShapePropSet->getPropertyValue( "Bookmark" ) >>= aBookmark; + } + + SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + + // export the shape bounding box + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "BoundingBox" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "stroke", "none" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "fill", "none" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "x", OUString::number( aBoundRect.X ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "y", OUString::number( aBoundRect.Y ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", OUString::number( aBoundRect.Width ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", OUString::number( aBoundRect.Height ) ); + SvXMLElementExport aBB( *mpSVGExport, XML_NAMESPACE_NONE, "rect", true, true ); + } + + if( !aBookmark.isEmpty() ) + { + INetURLObject aINetURLObject(aBookmark); + if (!aINetURLObject.HasError() + && aINetURLObject.GetProtocol() != INetProtocol::Javascript) + { + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xlink:href", aBookmark); + SvXMLElementExport alinkA( *mpSVGExport, XML_NAMESPACE_NONE, "a", true, true ); + mpSVGWriter->WriteMetaFile( aTopLeft, aSize, rMtf, + 0xffffffff, + aElementId, + &rxShape, + pEmbeddedBitmapsMtf ); + } + } + else + { + mpSVGWriter->WriteMetaFile( aTopLeft, aSize, rMtf, + 0xffffffff, + aElementId, + &rxShape, + pEmbeddedBitmapsMtf ); + } + } + } + + bRet = true; + } + } + } + + return bRet; +} + + +bool SVGFilter::implCreateObjects() +{ + if (mbExportShapeSelection) + { + // #i124608# export a given object selection + if (!mSelectedPages.empty() && mSelectedPages[0].is()) + { + implCreateObjectsFromShapes(mSelectedPages[0], maShapeSelection); + return true; + } + return false; + } + + sal_Int32 i, nCount; + + for( i = 0, nCount = mMasterPageTargets.size(); i < nCount; ++i ) + { + const Reference< css::drawing::XDrawPage > & xMasterPage = mMasterPageTargets[i]; + + if( xMasterPage.is() ) + { + mCreateOjectsCurrentMasterPage = xMasterPage; + implCreateObjectsFromBackground( xMasterPage ); + + if( xMasterPage.is() ) + implCreateObjectsFromShapes( xMasterPage, xMasterPage ); + } + } + + for( i = 0, nCount = mSelectedPages.size(); i < nCount; ++i ) + { + const Reference< css::drawing::XDrawPage > & xDrawPage = mSelectedPages[i]; + + if( xDrawPage.is() ) + { + // TODO complete the implementation for exporting custom background for each slide + // implementation status: + // - hatch stroke color is set to 'none' so the hatch is not visible, why? + // - gradient look is not really awesome, too few colors are used; + // - gradient and hatch are not exported only once + // and then referenced in case more than one slide uses them. + Reference< XPropertySet > xPropSet( xDrawPage, UNO_QUERY ); + if( xPropSet.is() ) + { + Reference< XPropertySet > xBackground; + xPropSet->getPropertyValue( "Background" ) >>= xBackground; + if( xBackground.is() ) + { + drawing::FillStyle aFillStyle; + bool assigned = ( xBackground->getPropertyValue( "FillStyle" ) >>= aFillStyle ); + if( assigned && aFillStyle != drawing::FillStyle_NONE ) + { + implCreateObjectsFromBackground( xDrawPage ); + } + } + } + implCreateObjectsFromShapes( xDrawPage, xDrawPage ); + } + } + return true; +} + + +bool SVGFilter::implCreateObjectsFromShapes( const Reference< css::drawing::XDrawPage > & rxPage, const Reference< css::drawing::XShapes >& rxShapes ) +{ + Reference< css::drawing::XShape > xShape; + bool bRet = false; + + for( sal_Int32 i = 0, nCount = rxShapes->getCount(); i < nCount; ++i ) + { + if( ( rxShapes->getByIndex( i ) >>= xShape ) && xShape.is() ) + bRet = implCreateObjectsFromShape( rxPage, xShape ) || bRet; + + xShape = nullptr; + } + + return bRet; +} + + +bool SVGFilter::implCreateObjectsFromShape( const Reference< css::drawing::XDrawPage > & rxPage, const Reference< css::drawing::XShape >& rxShape ) +{ + bool bRet = false; + if( rxShape->getShapeType().lastIndexOf( "drawing.GroupShape" ) != -1 ) + { + Reference< css::drawing::XShapes > xShapes( rxShape, UNO_QUERY ); + + if( xShapes.is() ) + bRet = implCreateObjectsFromShapes( rxPage, xShapes ); + } + else + { + SdrObject* pObj = SdrObject::getSdrObjectFromXShape(rxShape); + + if( pObj ) + { + Graphic aGraphic(SdrExchangeView::GetObjGraphic(*pObj)); + + // Writer graphic shapes are handled differently + if( mbWriterFilter && aGraphic.GetType() == GraphicType::NONE ) + { + if (rxShape->getShapeType() == "com.sun.star.drawing.GraphicObjectShape") + { + uno::Reference<beans::XPropertySet> xPropertySet(rxShape, uno::UNO_QUERY); + uno::Reference<graphic::XGraphic> xGraphic; + xPropertySet->getPropertyValue("Graphic") >>= xGraphic; + + if (!xGraphic.is()) + return false; + + aGraphic = Graphic(xGraphic); + } + } + + if( aGraphic.GetType() != GraphicType::NONE ) + { + if( aGraphic.GetType() == GraphicType::Bitmap ) + { + GDIMetaFile aMtf; + const Point aNullPt; + const Size aSize( pObj->GetCurrentBoundRect().GetSize() ); + + aMtf.AddAction( new MetaBmpExScaleAction( aNullPt, aSize, aGraphic.GetBitmapEx() ) ); + aMtf.SetPrefSize( aSize ); + aMtf.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + + (*mpObjects)[ rxShape ] = ObjectRepresentation( rxShape, aMtf ); + } + else + { + if( aGraphic.GetGDIMetaFile().GetActionSize() ) + { + Reference< XText > xText( rxShape, UNO_QUERY ); + bool bIsTextShape = xText.is(); + + if( !mpSVGExport->IsUsePositionedCharacters() && bIsTextShape ) + { + Reference< XPropertySet > xShapePropSet( rxShape, UNO_QUERY ); + + if( xShapePropSet.is() ) + { + bool bHideObj = false; + + if( mbPresentation ) + { + xShapePropSet->getPropertyValue( "IsEmptyPresentationObject" ) >>= bHideObj; + } + + if( !bHideObj ) + { + // We create a map of text shape ids. + implRegisterInterface( rxShape ); + const OUString& rShapeId = implGetValidIDFromInterface( Reference<XInterface>(rxShape, UNO_QUERY) ); + if( !rShapeId.isEmpty() ) + { + mTextShapeIdListMap[rxPage] += rShapeId; + mTextShapeIdListMap[rxPage] += " "; + } + + // We create a set of bitmaps embedded into text shape. + GDIMetaFile aMtf; + const Size aSize( pObj->GetCurrentBoundRect().GetSize() ); + MetaAction* pAction; + bool bIsTextShapeStarted = false; + const GDIMetaFile& rMtf = aGraphic.GetGDIMetaFile(); + size_t nCount = rMtf.GetActionSize(); + for( size_t nCurAction = 0; nCurAction < nCount; ++nCurAction ) + { + pAction = rMtf.GetAction( nCurAction ); + const MetaActionType nType = pAction->GetType(); + + if( nType == MetaActionType::COMMENT ) + { + const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction); + if( pA->GetComment().equalsIgnoreAsciiCase("XTEXT_PAINTSHAPE_BEGIN") ) + { + bIsTextShapeStarted = true; + } + else if( pA->GetComment().equalsIgnoreAsciiCase( "XTEXT_PAINTSHAPE_END" ) ) + { + bIsTextShapeStarted = false; + } + } + if( bIsTextShapeStarted && ( nType == MetaActionType::BMPSCALE || nType == MetaActionType::BMPEXSCALE ) ) + { + GDIMetaFile aEmbeddedBitmapMtf; + aEmbeddedBitmapMtf.AddAction( pAction ); + aEmbeddedBitmapMtf.SetPrefSize( aSize ); + aEmbeddedBitmapMtf.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + mEmbeddedBitmapActionSet.insert( ObjectRepresentation( rxShape, aEmbeddedBitmapMtf ) ); + aMtf.AddAction( pAction ); + } + } + aMtf.SetPrefSize( aSize ); + aMtf.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + mEmbeddedBitmapActionMap[ rxShape ] = ObjectRepresentation( rxShape, aMtf ); + } + } + } + } + (*mpObjects)[ rxShape ] = ObjectRepresentation( rxShape, aGraphic.GetGDIMetaFile() ); + } + bRet = true; + } + } + } + + return bRet; +} + + +void SVGFilter::implCreateObjectsFromBackground( const Reference< css::drawing::XDrawPage >& rxDrawPage ) +{ + Reference< css::drawing::XGraphicExportFilter > xExporter = drawing::GraphicExportFilter::create( mxContext ); + + GDIMetaFile aMtf; + + utl::TempFile aFile; + aFile.EnableKillingFile(); + + Sequence< PropertyValue > aDescriptor{ + comphelper::makePropertyValue("FilterName", OUString( "SVM" )), + comphelper::makePropertyValue("URL", aFile.GetURL()), + comphelper::makePropertyValue("ExportOnlyBackground", true) + }; + + xExporter->setSourceDocument( Reference< XComponent >( rxDrawPage, UNO_QUERY ) ); + xExporter->filter( aDescriptor ); + SvmReader aReader( *aFile.GetStream( StreamMode::READ ) ); + aReader.Read( aMtf ); + + bool bIsBitmap = false; + bool bIsTiled = false; + + // look for background type + Reference< XPropertySet > xPropSet( rxDrawPage, UNO_QUERY ); + if( xPropSet.is() ) + { + Reference< XPropertySet > xBackground; + xPropSet->getPropertyValue( "Background" ) >>= xBackground; + if( xBackground.is() ) + { + drawing::FillStyle aFillStyle; + if( xBackground->getPropertyValue( "FillStyle" ) >>= aFillStyle ) + { + if( aFillStyle == drawing::FillStyle::FillStyle_BITMAP ) + { + bIsBitmap = true; + xBackground->getPropertyValue( "FillBitmapTile" ) >>= bIsTiled; + + // we do not handle tiled background with a row or column offset + sal_Int32 nFillBitmapOffsetX = 0, nFillBitmapOffsetY = 0; + xBackground->getPropertyValue( "FillBitmapOffsetX" ) >>= nFillBitmapOffsetX; + xBackground->getPropertyValue( "FillBitmapOffsetY" ) >>= nFillBitmapOffsetY; + bIsTiled = bIsTiled && ( nFillBitmapOffsetX == 0 && nFillBitmapOffsetY == 0 ); + } + } + } + } + + if( !bIsBitmap ) + { + (*mpObjects)[ rxDrawPage ] = ObjectRepresentation( rxDrawPage, aMtf ); + return; + } + + GDIMetaFile aTiledMtf; + bool bBitmapFound = false; + MetaAction* pAction; + size_t nCount = aMtf.GetActionSize(); + for( size_t nCurAction = 0; nCurAction < nCount; ++nCurAction ) + { + pAction = aMtf.GetAction( nCurAction ); + const MetaActionType nType = pAction->GetType(); + + // collect bitmap + if( nType == MetaActionType::BMPSCALE || nType == MetaActionType::BMPEXSCALE ) + { + if( bBitmapFound ) + continue; + bBitmapFound = true; // the subsequent bitmaps are still the same just translated + + BitmapChecksum nChecksum = GetBitmapChecksum( pAction ); + if( maBitmapActionMap.find( nChecksum ) == maBitmapActionMap.end() ) + { + Point aPos; // (0, 0) + Size aSize; + MetaBitmapActionGetOrigSize( pAction, aSize ); + MetaAction* pBitmapAction = CreateMetaBitmapAction( pAction, aPos, aSize ); + if( pBitmapAction ) + { + GDIMetaFile* pEmbeddedBitmapMtf = new GDIMetaFile(); + pEmbeddedBitmapMtf->AddAction( pBitmapAction ); + pEmbeddedBitmapMtf->SetPrefSize( aSize ); + pEmbeddedBitmapMtf->SetPrefMapMode( MapMode(MapUnit::Map100thMM) ); + + maBitmapActionMap[ nChecksum ].reset( pEmbeddedBitmapMtf ); + } + } + + if( bIsTiled ) + { + // collect data for <pattern> and <rect> + const OUString & sPageId = implGetValidIDFromInterface( rxDrawPage ); + Point aPos; + MetaBitmapActionGetPoint( pAction, aPos ); + Size aSize; + MetaBitmapActionGetSize( pAction, aSize ); + + sal_Int32 nSlideWidth = 0, nSlideHeight = 0; + xPropSet->getPropertyValue( "Width" ) >>= nSlideWidth; + xPropSet->getPropertyValue( "Height" ) >>= nSlideHeight; + + maPatterProps[ sPageId ] = { nChecksum, aPos, aSize, { nSlideWidth, nSlideHeight } }; + + // create meta comment action that is used to exporting + // a <use> element which points to the group element representing the background + const OUString sBgId = getIdForTiledBackground( sPageId, nChecksum ); + OString sComment = sTiledBackgroundTag + " " + sBgId.toUtf8(); + MetaCommentAction* pCommentAction = new MetaCommentAction( sComment ); + if( pCommentAction ) + aTiledMtf.AddAction( pCommentAction ); + } + } + else if( bIsTiled && nType != MetaActionType::CLIPREGION ) + { + aTiledMtf.AddAction( pAction ); + } + } + + (*mpObjects)[ rxDrawPage ] = ObjectRepresentation( rxDrawPage, bIsTiled ? aTiledMtf : aMtf ); +} + +OUString SVGFilter::implGetClassFromShape( const Reference< css::drawing::XShape >& rxShape ) +{ + OUString aRet; + const OUString aShapeType( rxShape->getShapeType() ); + + if( aShapeType.lastIndexOf( "drawing.GroupShape" ) != -1 ) + aRet = "Group"; + else if( aShapeType.lastIndexOf( "drawing.GraphicObjectShape" ) != -1 ) + aRet = "Graphic"; + else if( aShapeType.lastIndexOf( "drawing.OLE2Shape" ) != -1 ) + aRet = "OLE2"; + else if( aShapeType.lastIndexOf( "drawing.TextShape" ) != -1 ) + aRet = "TextShape"; + else if( aShapeType.lastIndexOf( "presentation.HeaderShape" ) != -1 ) + aRet = "Header"; + else if( aShapeType.lastIndexOf( "presentation.FooterShape" ) != -1 ) + aRet = "Footer"; + else if( aShapeType.lastIndexOf( "presentation.DateTimeShape" ) != -1 ) + aRet = "Date/Time"; + else if( aShapeType.lastIndexOf( "presentation.SlideNumberShape" ) != -1 ) + aRet = "Slide_Number"; + else if( aShapeType.lastIndexOf( "presentation.TitleTextShape" ) != -1 ) + aRet = "TitleText"; + else if( aShapeType.lastIndexOf( "presentation.OutlinerShape" ) != -1 ) + aRet = "Outline"; + else + aRet = aShapeType; + + return aRet; +} + + +void SVGFilter::implRegisterInterface( const Reference< XInterface >& rxIf ) +{ + if( rxIf.is() ) + mpSVGExport->getInterfaceToIdentifierMapper().registerReference( rxIf ); +} + + +const OUString & SVGFilter::implGetValidIDFromInterface( const Reference< XInterface >& rxIf ) +{ + return mpSVGExport->getInterfaceToIdentifierMapper().getIdentifier( rxIf ); +} + + +OUString SVGFilter::implGetInterfaceName( const Reference< XInterface >& rxIf ) +{ + Reference< XNamed > xNamed( rxIf, UNO_QUERY ); + OUString aRet; + if( xNamed.is() ) + { + aRet = xNamed->getName().replace( ' ', '_' ); + } + return aRet; +} + + +IMPL_LINK( SVGFilter, CalcFieldHdl, EditFieldInfo*, pInfo, void ) +{ + bool bFieldProcessed = false; + if( pInfo && mbPresentation ) + { + bFieldProcessed = true; + if( mpSVGExport->IsEmbedFonts() && mpSVGExport->IsUsePositionedCharacters() ) + { + // to notify to the SVGActionWriter::ImplWriteText method + // that we are dealing with a placeholder shape + OUStringBuffer aRepresentation(sPlaceholderTag); + + if( !mCreateOjectsCurrentMasterPage.is() ) + { + OSL_FAIL( "error: !mCreateOjectsCurrentMasterPage.is()" ); + return; + } + bool bHasCharSetMap = mTextFieldCharSets.find( mCreateOjectsCurrentMasterPage ) != mTextFieldCharSets.end(); + + static constexpr OUStringLiteral aHeaderId( NSPREFIX "header-field" ); + static constexpr OUStringLiteral aFooterId( aOOOAttrFooterField ); + static constexpr OUStringLiteral aDateTimeId( aOOOAttrDateTimeField ); + static const OUString aVariableDateTimeId( aOOOAttrDateTimeField + "-variable" ); + + const UCharSet * pCharSet = nullptr; + UCharSetMap * pCharSetMap = nullptr; + if( bHasCharSetMap ) + { + pCharSetMap = &( mTextFieldCharSets[ mCreateOjectsCurrentMasterPage ] ); + } + const SvxFieldData* pField = pInfo->GetField().GetField(); + if( bHasCharSetMap && ( pField->GetClassId() == text::textfield::Type::PRESENTATION_HEADER ) && ( pCharSetMap->find( aHeaderId ) != pCharSetMap->end() ) ) + { + pCharSet = &( (*pCharSetMap)[ aHeaderId ] ); + } + else if( bHasCharSetMap && ( pField->GetClassId() == text::textfield::Type::PRESENTATION_FOOTER ) && ( pCharSetMap->find( aFooterId ) != pCharSetMap->end() ) ) + { + pCharSet = &( (*pCharSetMap)[ aFooterId ] ); + } + else if( pField->GetClassId() == text::textfield::Type::PRESENTATION_DATE_TIME ) + { + if( bHasCharSetMap && ( pCharSetMap->find( aDateTimeId ) != pCharSetMap->end() ) ) + { + pCharSet = &( (*pCharSetMap)[ aDateTimeId ] ); + } + if( bHasCharSetMap && ( pCharSetMap->find( aVariableDateTimeId ) != pCharSetMap->end() ) && !(*pCharSetMap)[ aVariableDateTimeId ].empty() ) + { + SvxDateFormat eDateFormat = SvxDateFormat::B, eCurDateFormat; + const UCharSet & aCharSet = (*pCharSetMap)[ aVariableDateTimeId ]; + // we look for the most verbose date format + for (auto const& elem : aCharSet) + { + eCurDateFormat = static_cast<SvxDateFormat>( static_cast<int>(elem) & 0x0f ); + switch( eDateFormat ) + { + case SvxDateFormat::StdSmall: + case SvxDateFormat::A: // 13.02.96 + case SvxDateFormat::B: // 13.02.1996 + switch( eCurDateFormat ) + { + case SvxDateFormat::C: // 13.Feb 1996 + case SvxDateFormat::D: // 13.February 1996 + case SvxDateFormat::E: // Tue, 13.February 1996 + case SvxDateFormat::StdBig: + case SvxDateFormat::F: // Tuesday, 13.February 1996 + eDateFormat = eCurDateFormat; + break; + default: + break; + } + break; + case SvxDateFormat::C: // 13.Feb 1996 + case SvxDateFormat::D: // 13.February 1996 + switch( eCurDateFormat ) + { + case SvxDateFormat::E: // Tue, 13.February 1996 + case SvxDateFormat::StdBig: + case SvxDateFormat::F: // Tuesday, 13.February 1996 + eDateFormat = eCurDateFormat; + break; + default: + break; + } + break; + default: + break; + } + } + // Independently of the date format, we always put all these characters by default. + // They should be enough to cover every time format. + aRepresentation.append( "0123456789.:/-APM" ); + + if( eDateFormat != SvxDateFormat::AppDefault ) + { + OUStringBuffer sDate; + LanguageType eLang = pInfo->GetOutliner()->GetLanguage( pInfo->GetPara(), pInfo->GetPos() ); + SvNumberFormatter * pNumberFormatter = new SvNumberFormatter( ::comphelper::getProcessComponentContext(), LANGUAGE_SYSTEM ); + // We always collect the characters obtained by using the SvxDateFormat::B (as: 13.02.1996) + // so we are sure to include any unusual day|month|year separator. + Date aDate( 1, 1, 1996 ); + sDate.append( SvxDateField::GetFormatted( aDate, SvxDateFormat::B, *pNumberFormatter, eLang ) ); + switch( eDateFormat ) + { + case SvxDateFormat::E: // Tue, 13.February 1996 + case SvxDateFormat::StdBig: + case SvxDateFormat::F: // Tuesday, 13.February 1996 + for( sal_uInt16 i = 1; i <= 7; ++i ) // we get all days in a week + { + aDate.SetDay( i ); + sDate.append( SvxDateField::GetFormatted( aDate, eDateFormat, *pNumberFormatter, eLang ) ); + } + [[fallthrough]]; // We need months too! + case SvxDateFormat::C: // 13.Feb 1996 + case SvxDateFormat::D: // 13.February 1996 + for( sal_uInt16 i = 1; i <= 12; ++i ) // we get all months in a year + { + aDate.SetMonth( i ); + sDate.append( SvxDateField::GetFormatted( aDate, eDateFormat, *pNumberFormatter, eLang ) ); + } + break; + // coverity[dead_error_begin] - following conditions exist to avoid compiler warning + case SvxDateFormat::StdSmall: + case SvxDateFormat::A: // 13.02.96 + case SvxDateFormat::B: // 13.02.1996 + default: + // nothing to do here, we always collect the characters needed for these cases. + break; + } + aRepresentation.append( sDate ); + } + } + } + else if( pField->GetClassId() == text::textfield::Type::PAGE ) + { + switch( mVisiblePagePropSet.nPageNumberingType ) + { + case css::style::NumberingType::CHARS_UPPER_LETTER: + aRepresentation.append( "QWERTYUIOPASDFGHJKLZXCVBNM" ); + break; + case css::style::NumberingType::CHARS_LOWER_LETTER: + aRepresentation.append( "qwertyuiopasdfghjklzxcvbnm" ); + break; + case css::style::NumberingType::ROMAN_UPPER: + aRepresentation.append( "IVXLCDM" ); + break; + case css::style::NumberingType::ROMAN_LOWER: + aRepresentation.append( "ivxlcdm" ); + break; + // arabic numbering type is the default + case css::style::NumberingType::ARABIC: + // in case the numbering type is not handled we fall back on arabic numbering + default: + aRepresentation.append( "0123456789" ); + break; + } + } + else + { + bFieldProcessed = false; + } + if( bFieldProcessed ) + { + if( pCharSet != nullptr ) + { + for (auto const& elem : *pCharSet) + { + aRepresentation.append(elem); + } + } + pInfo->SetRepresentation( aRepresentation.makeStringAndClear() ); + } + } + else + { + bFieldProcessed = false; + } + + } + if (!bFieldProcessed) + maOldFieldHdl.Call( pInfo ); +} + + +void SVGExport::writeMtf( const GDIMetaFile& rMtf ) +{ + const Size aSize( OutputDevice::LogicToLogic(rMtf.GetPrefSize(), rMtf.GetPrefMapMode(), MapMode(MapUnit::MapMM)) ); + OUString aAttr; + Reference< XExtendedDocumentHandler> xExtDocHandler( GetDocHandler(), UNO_QUERY ); + + if( xExtDocHandler.is() && IsUseDTDString() ) + xExtDocHandler->unknown( SVG_DTD_STRING ); + + aAttr = OUString::number( aSize.Width() ) + "mm"; + AddAttribute( XML_NAMESPACE_NONE, "width", aAttr ); + + aAttr = OUString::number( aSize.Height() ) + "mm"; + AddAttribute( XML_NAMESPACE_NONE, "height", aAttr ); + + aAttr = "0 0 " + OUString::number( aSize.Width() * 100 ) + " " + + OUString::number( aSize.Height() * 100 ); + AddAttribute( XML_NAMESPACE_NONE, "viewBox", aAttr ); + + AddAttribute( XML_NAMESPACE_NONE, "version", "1.1" ); + + if( IsUseTinyProfile() ) + AddAttribute( XML_NAMESPACE_NONE, "baseProfile", "tiny" ); + + AddAttribute( XML_NAMESPACE_NONE, "xmlns", constSvgNamespace ); + // For <image xlink:href="...">. + AddAttribute(XML_NAMESPACE_XMLNS, "xlink", "http://www.w3.org/1999/xlink"); + AddAttribute( XML_NAMESPACE_NONE, "stroke-width", OUString::number( 28.222 ) ); + AddAttribute( XML_NAMESPACE_NONE, "stroke-linejoin", "round" ); + AddAttribute( XML_NAMESPACE_NONE, "xml:space", "preserve" ); + + { + SvXMLElementExport aSVG( *this, XML_NAMESPACE_NONE, "svg", true, true ); + + std::vector< ObjectRepresentation > aObjects; + + aObjects.emplace_back( Reference< XInterface >(), rMtf ); + SVGFontExport aSVGFontExport( *this, std::move(aObjects) ); + + Point aPoint100thmm( OutputDevice::LogicToLogic(rMtf.GetPrefMapMode().GetOrigin(), rMtf.GetPrefMapMode(), MapMode(MapUnit::Map100thMM)) ); + Size aSize100thmm( OutputDevice::LogicToLogic(rMtf.GetPrefSize(), rMtf.GetPrefMapMode(), MapMode(MapUnit::Map100thMM)) ); + + SVGActionWriter aWriter( *this, aSVGFontExport ); + aWriter.WriteMetaFile( aPoint100thmm, aSize100thmm, rMtf, + SVGWRITER_WRITE_FILL | SVGWRITER_WRITE_TEXT ); + } +} + +void SVGExport::SetEmbeddedBulletGlyph(sal_Unicode cBullet) +{ + maEmbeddedBulletGlyphs.insert(cBullet); +} + +bool SVGExport::IsEmbeddedBulletGlyph(sal_Unicode cBullet) const +{ + auto it = maEmbeddedBulletGlyphs.find(cBullet); + return it != maEmbeddedBulletGlyphs.end(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |