/* -*- 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 <strings.hrc>
#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/scopeguard.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 <comphelper/diagnose_ex.hxx>
#include <tools/zcodec.hxx>
#include <rtl/crc.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::style;
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 OUString aOOOElemTextField = NSPREFIX "text_field"_ustr;
// ooo xml attributes for meta_slide
constexpr OUString aOOOAttrSlide = NSPREFIX "slide"_ustr;
constexpr OUString aOOOAttrMaster = NSPREFIX "master"_ustr;
constexpr OUStringLiteral aOOOAttrHasCustomBackground = NSPREFIX "has-custom-background";
constexpr OUStringLiteral aOOOAttrDisplayName = NSPREFIX "display-name";
constexpr OUString aOOOAttrBackgroundVisibility = NSPREFIX "background-visibility"_ustr;
constexpr OUString aOOOAttrMasterObjectsVisibility = NSPREFIX "master-objects-visibility"_ustr;
constexpr OUStringLiteral aOOOAttrSlideDuration = NSPREFIX "slide-duration";
constexpr OUString aOOOAttrDateTimeField = NSPREFIX "date-time-field"_ustr;
constexpr OUString aOOOAttrFooterField = NSPREFIX "footer-field"_ustr;
constexpr OUString aOOOAttrHasTransition = NSPREFIX "has-transition"_ustr;
// ooo xml attributes for pages and shapes
constexpr OUString aOOOAttrName = NSPREFIX "name"_ustr;
constexpr OUString constSvgNamespace = u"http://www.w3.org/2000/svg"_ustr;
/** 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 u"TextField"_ustr;
}
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, u"class"_ustr, 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 u"FixedTextField"_ustr;
}
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, u"g"_ustr, true, true );
pSVGExport->GetDocHandler()->characters( text );
}
};
class FixedDateTimeField : public FixedTextField
{
public:
FixedDateTimeField() {}
virtual OUString getClassName() const override
{
return u"FixedDateTimeField"_ustr;
}
virtual void growCharSet( SVGFilter::UCharSetMapMap & aTextFieldCharSets ) const override
{
implGrowCharSet( aTextFieldCharSets, text, aOOOAttrDateTimeField );
}
};
class FooterField : public FixedTextField
{
public:
FooterField() {}
virtual OUString getClassName() const override
{
return u"FooterField"_ustr;
}
virtual void growCharSet( SVGFilter::UCharSetMapMap & aTextFieldCharSets ) const override
{
static constexpr OUString sFieldId = aOOOAttrFooterField;
implGrowCharSet( aTextFieldCharSets, text, sFieldId );
}
};
class VariableTextField : public TextField
{
public:
virtual OUString getClassName() const override
{
return u"VariableTextField"_ustr;
}
};
class VariableDateTimeField : public VariableTextField
{
public:
sal_Int32 format;
VariableDateTimeField()
: format(0)
{
}
virtual OUString getClassName() const override
{
return u"VariableDateTimeField"_ustr;
}
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"_ustr, sDateTimeFormat );
SvXMLElementExport aExp( *pSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, 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, u""_ustr,
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(u"SVG_DISABLE_FONT_EMBEDDING"_ustr, 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_SVG),
GetXMLToken(XML_N_SVG_COMPAT),
XML_NAMESPACE_SVG);
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;
}
else if (pValue[i].Name == "StatusIndicator")
pValue[i].Value >>= mxStatusIndicator;
}
if(mbWriterFilter || mbCalcFilter)
return implExportWriterOrCalc(xOStm);
return implExportImpressOrDraw(xOStm);
}
bool SVGFilter::implExportImpressOrDraw( const Reference< XOutputStream >& rxOStm)
{
const Reference< XComponentContext >& xContext( ::comphelper::getProcessComponentContext() ) ;
bool bRet = false;
// Instead of writing to rxOStm directly, we write here in case we need
// to compress the output later
SvMemoryStream aTempStm;
if( rxOStm.is() )
{
if (!mSelectedPages.empty())
{
::rtl::Reference< ::utl::OStreamWrapper > aTempStmWrapper = new ::utl::OStreamWrapper( aTempStm );
Reference< XDocumentHandler > xDocHandler = implCreateExportDocumentHandler( aTempStmWrapper );
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;
}
}
}
if ( bRet )
{
const sal_Int8* aDataPtr = static_cast< const sal_Int8* >( aTempStm.GetData() );
sal_uInt32 aDataSize = aTempStm.GetSize();
SvMemoryStream aCompressedStm;
if ( mbShouldCompress )
{
sal_uInt32 nUncompressedCRC32
= rtl_crc32( 0, aTempStm.GetData(), aTempStm.GetSize() );
ZCodec aCodec;
aTempStm.Seek( 0 );
aCodec.BeginCompression( ZCODEC_DEFAULT_COMPRESSION, /*gzLib*/true );
// the inner modify time/filename doesn't really matter in this context because
// compressed graphic formats are meant to be opened as is - not to be extracted
aCodec.SetCompressionMetadata( "inner"_ostr, 0, nUncompressedCRC32 );
aCodec.Compress( aTempStm, aCompressedStm );
sal_uInt32 nTotalIn = static_cast< sal_uInt32 >( aCodec.EndCompression() );
if ( aCompressedStm.GetError() || nTotalIn != aDataSize )
{
bRet = false;
return bRet;
}
else
{
aDataPtr = static_cast< const sal_Int8* >( aCompressedStm.GetData() );
aDataSize = aCompressedStm.GetSize();
}
}
Sequence< sal_Int8 > aTempSeq( aDataPtr, aDataSize );
rxOStm->writeBytes( aTempSeq );
}
return bRet;
}
bool SVGFilter::implExportWriterOrCalc( const Reference< XOutputStream >& rxOStm )
{
const 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(u"com.sun.star.text.TextGraphicObject"_ustr))
return true;
uno::Reference<beans::XPropertySet> xPropertySet(xSelection, uno::UNO_QUERY);
uno::Reference<graphic::XGraphic> xOriginalGraphic;
xPropertySet->getPropertyValue(u"Graphic"_ustr) >>= xOriginalGraphic;
const Graphic aOriginalGraphic(xOriginalGraphic);
uno::Reference<graphic::XGraphic> xTransformedGraphic;
xPropertySet->getPropertyValue(
mbIsPreview ? u"GraphicPreview"_ustr : u"TransformedGraphic"_ustr)
>>= 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;
SdrModel& rModel = pSvxDrawPage->GetSdrPage()->getSdrModelFromSdrPage();
const bool bUndoEnable = rModel.IsUndoEnabled();
if (bUndoEnable)
rModel.EnableUndo(false);
comphelper::ScopeGuard guard([bUndoEnable, &rModel]() {
// restore when leaving
if (bUndoEnable)
rModel.EnableUndo(false);
});
rtl::Reference<SdrGrafObj> pGraphicObj = new SdrGrafObj(rModel, aGraphic, tools::Rectangle( aPos, aSize ));
uno::Reference< drawing::XShape > xShape = GetXShapeForSdrObject(pGraphicObj.get());
uno::Reference< XPropertySet > xShapePropSet(xShape, uno::UNO_QUERY);
xShapePropSet->setPropertyValue(u"Graphic"_ustr, 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( u"Visible"_ustr ) >>= 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())
{
sal_Int32 nWidth = 0;
sal_Int32 nHeight = 0;
if (xDefaultPagePropertySet->getPropertyValue(u"Width"_ustr) >>= nWidth)
nDocWidth = nWidth;
if (xDefaultPagePropertySet->getPropertyValue(u"Height"_ustr) >>= nHeight)
nDocHeight = nHeight;
}
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( u"BoundRect"_ustr ) >>= 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 && !mMasterPageTargets.empty())
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, u"version"_ustr, u"1.2"_ustr );
if( mpSVGExport->IsUseTinyProfile() )
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"baseProfile"_ustr, u"tiny"_ustr );
// 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, u"width"_ustr, aAttr );
aAttr = OUString::number( nDocHeight * 0.01 ) + "mm";
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"height"_ustr, 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, u"viewBox"_ustr, aAttr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"preserveAspectRatio"_ustr, u"xMidYMid"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"fill-rule"_ustr, u"evenodd"_ustr );
// standard line width is based on 1 pixel on a 90 DPI device (0.28222mmm)
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"stroke-width"_ustr, OUString::number( 28.222 ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"stroke-linejoin"_ustr, u"round"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xmlns"_ustr, constSvgNamespace );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xmlns:ooo"_ustr, u"http://xml.openoffice.org/svg/export"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xmlns:xlink"_ustr, u"http://www.w3.org/1999/xlink"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xmlns:presentation"_ustr, u"http://sun.com/xmlns/staroffice/presentation"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xmlns:smil"_ustr, u"http://www.w3.org/2001/SMIL20/"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xmlns:anim"_ustr, u"urn:oasis:names:tc:opendocument:xmlns:animation:1.0"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xmlns:svg"_ustr, u"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xml:space"_ustr, u"preserve"_ustr );
mpSVGDoc = new SvXMLElementExport( *mpSVGExport, XML_NAMESPACE_NONE, u"svg"_ustr, 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, u"class"_ustr, u"ClipPathGroup"_ustr );
SvXMLElementExport aDefsElem( *mpSVGExport, XML_NAMESPACE_NONE, u"defs"_ustr, true, true );
{
msClipPathId = "presentation_clip_path";
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"id"_ustr, msClipPathId );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"clipPathUnits"_ustr, u"userSpaceOnUse"_ustr );
SvXMLElementExport aClipPathElem( *mpSVGExport, XML_NAMESPACE_NONE, u"clipPath"_ustr, true, true );
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"x"_ustr, OUString::number( nDocX ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"y"_ustr, OUString::number( nDocY ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"width"_ustr, OUString::number( nDocWidth ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"height"_ustr, OUString::number( nDocHeight ) );
SvXMLElementExport aRectElem( *mpSVGExport, XML_NAMESPACE_NONE, u"rect"_ustr, 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, u"id"_ustr, u"presentation_clip_path_shrink"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"clipPathUnits"_ustr, u"userSpaceOnUse"_ustr );
SvXMLElementExport aClipPathElem( *mpSVGExport, XML_NAMESPACE_NONE, u"clipPath"_ustr, true, true );
{
sal_Int32 nDocWidthExt = nDocWidth / 500;
sal_Int32 nDocHeightExt = nDocHeight / 500;
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"x"_ustr, OUString::number( nDocX + nDocWidthExt / 2 ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"y"_ustr, OUString::number( nDocY + nDocHeightExt / 2) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"width"_ustr, OUString::number( nDocWidth - nDocWidthExt ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"height"_ustr, OUString::number( nDocHeight - nDocHeightExt ) );
SvXMLElementExport aRectElem( *mpSVGExport, XML_NAMESPACE_NONE, u"rect"_ustr, true, true );
}
}
}
void SVGFilter::implExportDocumentHeaderWriterOrCalc(sal_Int32 nDocX, sal_Int32 nDocY,
sal_Int32 nDocWidth, sal_Int32 nDocHeight)
{
OUString aAttr;
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"version"_ustr, u"1.2"_ustr );
aAttr = OUString::number( nDocWidth * 0.01 ) + "mm";
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"width"_ustr, aAttr );
aAttr = OUString::number( nDocHeight * 0.01 ) + "mm";
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"height"_ustr, aAttr );
aAttr = OUString::number(nDocX) + " " + OUString::number(nDocY) + " " +
OUString::number(nDocWidth) + " " + OUString::number(nDocHeight);
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"viewBox"_ustr, aAttr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"preserveAspectRatio"_ustr, u"xMidYMid"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"fill-rule"_ustr, u"evenodd"_ustr );
// standard line width is based on 1 pixel on a 90 DPI device (0.28222mmm)
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"stroke-width"_ustr, OUString::number( 28.222 ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"stroke-linejoin"_ustr, u"round"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xmlns"_ustr, constSvgNamespace );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xmlns:ooo"_ustr, u"http://xml.openoffice.org/svg/export"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xmlns:xlink"_ustr, u"http://www.w3.org/1999/xlink"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xmlns:office"_ustr, u"urn:oasis:names:tc:opendocument:xmlns:office:1.0"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xmlns:smil"_ustr, u"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xml:space"_ustr, u"preserve"_ustr );
mpSVGDoc = new SvXMLElementExport( *mpSVGExport, XML_NAMESPACE_NONE, u"svg"_ustr, 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, u"defs"_ustr, true, true );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"id"_ustr, NSPREFIX "meta_slides"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "number-of-slides"_ustr, OUString::number( nCount ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "start-slide-number"_ustr, OUString::number( mnVisiblePage ) );
if( mpSVGExport->IsUsePositionedCharacters() )
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "use-positioned-chars"_ustr, u"true"_ustr );
}
// 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"_ustr, sNumberingType );
}
{
SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, true, true );
const OUString aId( NSPREFIX "meta_slide"_ustr );
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, u"id"_ustr, NSPREFIX "meta_dummy_slide"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrSlide, u"dummy-slide"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrMaster, u"dummy-master-page"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrBackgroundVisibility, u"hidden"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrMasterObjectsVisibility, u"hidden"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrHasTransition, u"false"_ustr );
SvXMLElementExport aMetaDummySlideElem( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, 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, u"id"_ustr, 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(u"LinkDisplayName"_ustr) >>= 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( u"Background"_ustr ) >>= xBackground;
if( xBackground.is() )
{
drawing::FillStyle aFillStyle;
bool assigned = ( xBackground->getPropertyValue( u"FillStyle"_ustr ) >>= aFillStyle );
// has a custom background ?
if( assigned && aFillStyle != drawing::FillStyle_NONE )
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrHasCustomBackground, u"true"_ustr );
}
xPropSet->getPropertyValue( u"IsBackgroundVisible"_ustr ) >>= 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, u"hidden"_ustr );
// Page Number, DateTime, Footer and Header Fields are regarded as background objects.
// So bBackgroundObjectsVisibility overrides visibility of master page text fields.
xPropSet->getPropertyValue( u"IsBackgroundObjectsVisible"_ustr ) >>= 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( u"IsPageNumberVisible"_ustr ) >>= bPageNumberVisibility;
bPageNumberVisibility = bPageNumberVisibility && ( nPageNumberingType != css::style::NumberingType::NUMBER_NONE );
if( bPageNumberVisibility ) // visibility default value: 'hidden'
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "page-number-visibility"_ustr, u"visible"_ustr );
}
// DateTime Field
bool bDateTimeFixed = true; // default: fixed
xPropSet->getPropertyValue( u"IsDateTimeFixed"_ustr ) >>= bDateTimeFixed;
if( bDateTimeFixed ) // we are interested only in the field text not in the date/time format
{
xPropSet->getPropertyValue( u"DateTimeText"_ustr ) >>= 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( u"DateTimeFormat"_ustr ) >>= aVariableDateTimeField.format;
OUString sFieldId = implGenerateFieldId( aFieldSet, aVariableDateTimeField, aElemTextFieldId, xMasterPage );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrDateTimeField, sFieldId );
}
xPropSet->getPropertyValue( u"IsDateTimeVisible"_ustr ) >>= bDateTimeVisibility;
if( !bDateTimeVisibility ) // visibility default value: 'visible'
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "date-time-visibility"_ustr, u"hidden"_ustr );
}
// Footer Field
xPropSet->getPropertyValue( u"FooterText"_ustr ) >>= aFooterField.text;
if( !aFooterField.text.isEmpty() )
{
OUString sFieldId = implGenerateFieldId( aFieldSet, aFooterField, aElemTextFieldId, xMasterPage );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrFooterField, sFieldId );
}
xPropSet->getPropertyValue( u"IsFooterVisible"_ustr ) >>= bFooterVisibility;
if( !bFooterVisibility ) // visibility default value: 'visible'
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, NSPREFIX "footer-visibility"_ustr, u"hidden"_ustr );
}
}
else
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrMasterObjectsVisibility, u"hidden"_ustr );
}
sal_Int32 nChange(0);
if( xPropSet->getPropertySetInfo()->hasPropertyByName( u"Change"_ustr ) &&
(xPropSet->getPropertyValue( u"Change"_ustr ) >>= nChange ) && nChange == 1 )
{
double fSlideDuration(0);
if( xPropSet->getPropertySetInfo()->hasPropertyByName( u"HighResDuration"_ustr ) &&
(xPropSet->getPropertyValue( u"HighResDuration"_ustr ) >>= 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( u"TransitionType"_ustr ) &&
(xPropSet->getPropertyValue( u"TransitionType"_ustr ) >>= nTransitionType) )
{
sal_Int16 nTransitionSubType(0);
if( xPropSet->getPropertyValue( u"TransitionSubtype"_ustr ) >>= nTransitionSubType )
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrHasTransition, u"true"_ustr );
}
}
}
}
{
SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, 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, u"id"_ustr, 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, u"id"_ustr, u"presentation-animations"_ustr );
SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, u"defs"_ustr, true, true );
for(const uno::Reference<drawing::XDrawPage> & mSelectedPage : mSelectedPages)
{
Reference< XPropertySet > xProps( mSelectedPage, UNO_QUERY );
if( xProps.is() && xProps->getPropertySetInfo()->hasPropertyByName( u"TransitionType"_ustr ) )
{
sal_Int16 nTransition = 0;
xProps->getPropertyValue( u"TransitionType"_ustr ) >>= 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, u"id"_ustr, sId );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"Animations"_ustr );
SvXMLElementExport aDefsElem( *mpSVGExport, XML_NAMESPACE_NONE, u"defs"_ustr, 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, u"class"_ustr, u"TextShapeIndex"_ustr );
SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, u"defs"_ustr, 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.contains(xDrawPage) )
{
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"_ustr, sTextShapeIdList );
SvXMLElementExport aGElem( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, true, true );
}
}
}
}
void SVGFilter::implEmbedBulletGlyphs()
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"EmbeddedBulletChars"_ustr );
SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, u"defs"_ustr, true, true );
OUString sPathData = u"M 580,1141 L 1163,571 580,0 -4,571 580,1141 Z"_ustr;
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, u"id"_ustr, sId );
OUString sFactor = OUString::number( 1.0 / 2048 );
OUString sTransform = "scale(" + sFactor + ",-" + sFactor + ")";
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"transform"_ustr, sTransform );
SvXMLElementExport aGElem( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, true, true );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"d"_ustr, sPathData );
SvXMLElementExport aPathElem( *mpSVGExport, XML_NAMESPACE_NONE, u"path"_ustr, true, true );
mpSVGExport->SetEmbeddedBulletGlyph(cBullet);
}
void SVGFilter::implExportBackgroundBitmaps()
{
if (maBitmapActionMap.empty())
return;
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"BackgroundBitmaps"_ustr );
SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, u"defs"_ustr, 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, u"id"_ustr, 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, u"class"_ustr, u"BackgroundPatterns"_ustr );
SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, u"defs"_ustr, 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, u"id"_ustr, sPatternId );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"x"_ustr, OUString::number( rData.aPos.X() ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"y"_ustr, OUString::number( rData.aPos.Y() ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"width"_ustr, OUString::number( rData.aSize.Width() ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"height"_ustr, OUString::number( rData.aSize.Height() ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"patternUnits"_ustr, u"userSpaceOnUse"_ustr );
SvXMLElementExport aPatternElem( *mpSVGExport, XML_NAMESPACE_NONE, u"pattern"_ustr, 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, u"transform"_ustr, sTransform );
// referenced bitmap
OUString sRefId = "#bitmap(" + OUString::number( rData.aBitmapChecksum ) + ")";
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xlink:href"_ustr, sRefId );
SvXMLElementExport aUseElem( *mpSVGExport, XML_NAMESPACE_NONE, u"use"_ustr, true, true );
} // </use> </pattern>
// <g> <rect>
{
// group
const OUString sBgId = getIdForTiledBackground( rSlideId, rData.aBitmapChecksum );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"id"_ustr, sBgId );
SvXMLElementExport aGroupElem( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, true, true );
// rectangle
const OUString sUrl = "url(#" + sPatternId + ")";
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"x"_ustr, u"0"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"y"_ustr, u"0"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"width"_ustr, OUString::number( rData.aSlideSize.Width() ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"height"_ustr, OUString::number( rData.aSlideSize.Height() ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"stroke"_ustr, u"none"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"fill"_ustr, sUrl );
SvXMLElementExport aRectElem( *mpSVGExport, XML_NAMESPACE_NONE, u"rect"_ustr, 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, u"class"_ustr, u"TextEmbeddedBitmaps"_ustr );
SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, u"defs"_ustr, 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, u"id"_ustr, sId );
const Reference< XInterface >& rxShape = embeddedBitmapAction.GetObject();
Reference< XPropertySet > xShapePropSet( rxShape, UNO_QUERY );
css::awt::Rectangle aBoundRect;
if( xShapePropSet.is() && ( xShapePropSet->getPropertyValue( u"BoundRect"_ustr ) >>= 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, u"type"_ustr, u"text/ecmascript"_ustr );
{
SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, u"script"_ustr, 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( u"IsBackgroundVisible"_ustr, xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsBackgroundVisible;
implSafeGetPagePropSet( u"IsBackgroundObjectsVisible"_ustr, xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bAreBackgroundObjectsVisible;
implSafeGetPagePropSet( u"IsPageNumberVisible"_ustr, xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsPageNumberFieldVisible;
implSafeGetPagePropSet( u"IsHeaderVisible"_ustr, xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsHeaderFieldVisible;
implSafeGetPagePropSet( u"IsFooterVisible"_ustr, xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsFooterFieldVisible;
implSafeGetPagePropSet( u"IsDateTimeVisible"_ustr, xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsDateTimeFieldVisible;
implSafeGetPagePropSet( u"IsDateTimeFixed"_ustr, xPropSet, xPropSetInfo ) >>= mVisiblePagePropSet.bIsDateTimeFieldFixed;
sal_Int32 nTmp;
if (implSafeGetPagePropSet( u"DateTimeFormat"_ustr, 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) ? u"g"_ustr : u"defs"_ustr;
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, u"id"_ustr, u"dummy-master-page"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrName, u"dummy-master-page"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"Master_Slide"_ustr );
SvXMLElementExport aMasterSlideElem( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, true, true );
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"id"_ustr, u"bg-dummy-master-page"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"Background"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"visibility"_ustr, u"hidden"_ustr );
SvXMLElementExport aBackgroundElem( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, true, true );
}
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"id"_ustr, u"bo-dummy-master-page"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"BackgroundObjects"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"visibility"_ustr, u"hidden"_ustr );
SvXMLElementExport aBackgroundObjectElem( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, 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, u"id"_ustr, 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, u"class"_ustr, u"DummySlide"_ustr );
SvXMLElementExport aDummySlideElement( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, true, true );
{
SvXMLElementExport aGElement( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, true, true );
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"id"_ustr, u"dummy-slide"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"Slide"_ustr );
OUString sClipPathAttrValue = "url(#" + msClipPathId + ")";
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"clip-path"_ustr, sClipPathAttrValue );
SvXMLElementExport aSlideElement( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, true, true );
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, aOOOAttrName, u"dummy-page"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"Page"_ustr );
SvXMLElementExport aPageElement( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, true, true );
}
}
}
}
if (!mbExportShapeSelection)
{
// We wrap all slide in a group element with class name "SlideGroup".
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"SlideGroup"_ustr );
SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, true, true );
if (mxStatusIndicator)
mxStatusIndicator->start(FilterResId(STR_FILTER_DOC_SAVING), nLastPage - nFirstPage + 1);
for( sal_Int32 i = nFirstPage; i <= nLastPage; ++i )
{
if (mxStatusIndicator.is())
mxStatusIndicator->setValue(i - nFirstPage);
Reference<css::drawing::XShapes> 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, u"visibility"_ustr, u"hidden"_ustr );
}
SvXMLElementExport aGElement( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, 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, u"id"_ustr, sContainerId );
SvXMLElementExport aContainerExp( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, true, true );
{
// add id attribute
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"id"_ustr, sPageId );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"Slide"_ustr );
// 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, u"clip-path"_ustr, sClipPathAttrValue );
SvXMLElementExport aSlideElement( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, 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
}
if (mxStatusIndicator)
mxStatusIndicator->end();
}
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, u"class"_ustr, aDesc );
}
}
// insert the <g> open tag related to the DrawPage/MasterPage
SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, 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, u"class"_ustr, u"SlideBackground"_ustr );
xDefsExp.reset( new SvXMLElementExport( *mpSVGExport, XML_NAMESPACE_NONE, u"defs"_ustr, true, true ) );
}
{
// background id = "bg-" + page id
OUString sBackgroundId = OUString::Concat("bg-") + sPageId;
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"id"_ustr, 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, u"visibility"_ustr, u"hidden"_ustr );
}
}
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"Background"_ustr );
// insert the <g> open tag related to the Background
SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, 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, u"id"_ustr, sBackgroundObjectsId );
if( !mbPresentation )
{
if( !mVisiblePagePropSet.bAreBackgroundObjectsVisible )
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"visibility"_ustr, u"hidden"_ustr );
}
}
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"BackgroundObjects"_ustr );
// insert the <g> open tag related to the Background Objects
SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, 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( u"IsEmptyPresentationObject"_ustr ) >>= 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, u"class"_ustr, u"Group"_ustr );
const OUString& rShapeId = implGetValidIDFromInterface( Reference<XInterface>(rxShape, UNO_QUERY) );
if( !rShapeId.isEmpty() )
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"id"_ustr, rShapeId );
}
SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, 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( u"BoundRect"_ustr ) >>= 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 == "PageNumber" );
bool bIsFooter = ( aShapeClass == "Footer" );
bool bIsDateTime = ( aShapeClass == "DateTime" );
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, u"visibility"_ustr, u"hidden"_ustr );
}
if( bTextField || ( aShapeClass == "TextShape" ) )
{
sal_uInt16 nTextAdjust = sal_uInt16(ParagraphAdjust_LEFT);
OUString sTextAdjust;
xShapePropSet->getPropertyValue( u"ParaAdjust"_ustr ) >>= 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"_ustr, sTextAdjust );
}
}
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, aShapeClass );
SvXMLElementExport aExp( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, true, true );
Reference< XExtendedDocumentHandler > xExtDocHandler( mpSVGExport->GetDocHandler(), UNO_QUERY );
OUString aTitle;
xShapePropSet->getPropertyValue( u"Title"_ustr ) >>= aTitle;
if( !aTitle.isEmpty() )
{
SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, u"title"_ustr, true, true );
xExtDocHandler->characters( aTitle );
}
OUString aDescription;
xShapePropSet->getPropertyValue( u"Description"_ustr ) >>= aDescription;
if( !aDescription.isEmpty() )
{
SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, u"desc"_ustr, true, true );
xExtDocHandler->characters( aDescription );
}
const OUString& rShapeId = implGetValidIDFromInterface( Reference<XInterface>(rxShape, UNO_QUERY) );
if( !rShapeId.isEmpty() )
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"id"_ustr, 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(u"Bookmark"_ustr))
{
xShapePropSet->getPropertyValue( u"Bookmark"_ustr ) >>= aBookmark;
}
SvXMLElementExport aExp2( *mpSVGExport, XML_NAMESPACE_NONE, u"g"_ustr, true, true );
// export the shape bounding box
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"BoundingBox"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"stroke"_ustr, u"none"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"fill"_ustr, u"none"_ustr );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"x"_ustr, OUString::number( aBoundRect.X ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"y"_ustr, OUString::number( aBoundRect.Y ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"width"_ustr, OUString::number( aBoundRect.Width ) );
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"height"_ustr, OUString::number( aBoundRect.Height ) );
SvXMLElementExport aBB( *mpSVGExport, XML_NAMESPACE_NONE, u"rect"_ustr, true, true );
}
if( !aBookmark.isEmpty() )
{
INetURLObject aINetURLObject(aBookmark);
if (!aINetURLObject.HasError()
&& aINetURLObject.GetProtocol() != INetProtocol::Javascript)
{
mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, u"xlink:href"_ustr, aBookmark);
SvXMLElementExport alinkA( *mpSVGExport, XML_NAMESPACE_NONE, u"a"_ustr, 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;
if (xPropSet->getPropertySetInfo()->hasPropertyByName(u"Background"_ustr))
xPropSet->getPropertyValue( u"Background"_ustr ) >>= xBackground;
if( xBackground.is() )
{
drawing::FillStyle aFillStyle;
bool assigned = ( xBackground->getPropertyValue( u"FillStyle"_ustr ) >>= 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 )
{
// tdf#155479 need to signal SVG export
Graphic aGraphic(SdrExchangeView::GetObjGraphic(*pObj, true));
// 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(u"Graphic"_ustr) >>= 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( u"IsEmptyPresentationObject"_ustr ) >>= 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::TempFileFast aFile;
SvStream* pStream = aFile.GetStream(StreamMode::READWRITE);
Sequence< PropertyValue > aDescriptor{
comphelper::makePropertyValue(u"FilterName"_ustr, u"SVM"_ustr),
comphelper::makePropertyValue(u"OutputStream"_ustr, uno::Reference<XOutputStream>(new utl::OOutputStreamWrapper(*pStream))),
comphelper::makePropertyValue(u"ExportOnlyBackground"_ustr, true)
};
xExporter->setSourceDocument( Reference< XComponent >( rxDrawPage, UNO_QUERY ) );
xExporter->filter( aDescriptor );
pStream->Seek(0);
SvmReader aReader( *pStream );
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( u"Background"_ustr ) >>= xBackground;
if( xBackground.is() )
{
drawing::FillStyle aFillStyle;
if( xBackground->getPropertyValue( u"FillStyle"_ustr ) >>= aFillStyle )
{
if( aFillStyle == drawing::FillStyle::FillStyle_BITMAP )
{
bIsBitmap = true;
xBackground->getPropertyValue( u"FillBitmapTile"_ustr ) >>= bIsTiled;
// we do not handle tiled background with a row or column offset
sal_Int32 nFillBitmapOffsetX = 0, nFillBitmapOffsetY = 0;
xBackground->getPropertyValue( u"FillBitmapOffsetX"_ustr ) >>= nFillBitmapOffsetX;
xBackground->getPropertyValue( u"FillBitmapOffsetY"_ustr ) >>= 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( u"Width"_ustr ) >>= nSlideWidth;
xPropSet->getPropertyValue( u"Height"_ustr ) >>= 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 );
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 = "DateTime";
else if( aShapeType.lastIndexOf( "presentation.SlideNumberShape" ) != -1 )
aRet = "PageNumber";
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 OUString aHeaderId( NSPREFIX "header-field"_ustr );
static constexpr OUString aFooterId( aOOOAttrFooterField );
static constexpr OUString 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, u"width"_ustr, aAttr );
aAttr = OUString::number( aSize.Height() ) + "mm";
AddAttribute( XML_NAMESPACE_NONE, u"height"_ustr, aAttr );
aAttr = "0 0 " + OUString::number( aSize.Width() * 100 ) + " " +
OUString::number( aSize.Height() * 100 );
AddAttribute( XML_NAMESPACE_NONE, u"viewBox"_ustr, aAttr );
AddAttribute( XML_NAMESPACE_NONE, u"version"_ustr, u"1.1"_ustr );
if( IsUseTinyProfile() )
AddAttribute( XML_NAMESPACE_NONE, u"baseProfile"_ustr, u"tiny"_ustr );
AddAttribute( XML_NAMESPACE_NONE, u"xmlns"_ustr, constSvgNamespace );
// For <image xlink:href="...">.
AddAttribute(XML_NAMESPACE_XMLNS, u"xlink"_ustr, u"http://www.w3.org/1999/xlink"_ustr);
AddAttribute( XML_NAMESPACE_NONE, u"stroke-width"_ustr, OUString::number( 28.222 ) );
AddAttribute( XML_NAMESPACE_NONE, u"stroke-linejoin"_ustr, u"round"_ustr );
AddAttribute( XML_NAMESPACE_NONE, u"xml:space"_ustr, u"preserve"_ustr );
{
SvXMLElementExport aSVG( *this, XML_NAMESPACE_NONE, u"svg"_ustr, 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: */
↑ V530 The return value of function 'registerReference' is required to be utilized.
↑ V1023 A pointer without owner is added to the 'aFieldSet' container by the 'emplace_back' method. A memory leak will occur in case of an exception.
↑ V1051 Consider checking for misprints. It's possible that the 'nLastPage' should be checked here.
↑ V1077 The 'SVGExport' constructor contains potentially uninitialized members. Inspect the following: mbIsEmbedFonts.