/* -*- 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 <sal/config.h>
#include <sal/log.hxx>
#include <comphelper/fileformat.h>
#include <o3tl/make_shared.hxx>
#include <tools/fract.hxx>
#include <tools/vcompat.hxx>
#include <tools/urlobj.hxx>
#include <tools/stream.hxx>
#include <unotools/ucbhelper.hxx>
#include <unotools/ucbstreamhelper.hxx>
#include <unotools/tempfile.hxx>
#include <utility>
#include <vcl/filter/SvmReader.hxx>
#include <vcl/filter/SvmWriter.hxx>
#include <vcl/outdev.hxx>
#include <vcl/graphicfilter.hxx>
#include <vcl/virdev.hxx>
#include <vcl/gfxlink.hxx>
#include <vcl/cvtgrf.hxx>
#include <vcl/graph.hxx>
#include <vcl/metaact.hxx>
#include <impgraph.hxx>
#include <com/sun/star/graphic/XPrimitive2D.hpp>
#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
#include <vcl/dibtools.hxx>
#include <map>
#include <memory>
#include <vcl/gdimetafiletools.hxx>
#include <vcl/TypeSerializer.hxx>
#include <vcl/pdfread.hxx>
#include <graphic/VectorGraphicLoader.hxx>
#define GRAPHIC_MTFTOBMP_MAXEXT 2048
#define GRAPHIC_STREAMBUFSIZE 8192UL
#define SWAP_FORMAT_ID COMPAT_FORMAT( 'S', 'W', 'A', 'P' )
using namespace com::sun::star;
class ImpSwapFile
{
private:
utl::TempFileFast maTempFile;
OUString maOriginURL;
public:
ImpSwapFile(OUString aOriginURL)
: maOriginURL(std::move(aOriginURL))
{
}
SvStream* getStream() { return maTempFile.GetStream(StreamMode::READWRITE); }
OUString const & getOriginURL() const { return maOriginURL; }
};
SvStream* ImpGraphic::getSwapFileStream() const
{
if (mpSwapFile)
return mpSwapFile->getStream();
return nullptr;
}
ImpGraphic::ImpGraphic(bool bDefault)
: MemoryManaged(false)
, meType(bDefault ? GraphicType::Default : GraphicType::NONE)
{
}
ImpGraphic::ImpGraphic(const ImpGraphic& rImpGraphic)
: MemoryManaged(rImpGraphic)
, maMetaFile(rImpGraphic.maMetaFile)
, maBitmapEx(rImpGraphic.maBitmapEx)
, maSwapInfo(rImpGraphic.maSwapInfo)
, mpSwapFile(rImpGraphic.mpSwapFile)
, mpGfxLink(rImpGraphic.mpGfxLink)
, maVectorGraphicData(rImpGraphic.maVectorGraphicData)
, meType(rImpGraphic.meType)
, mnSizeBytes(rImpGraphic.mnSizeBytes)
, mbSwapOut(rImpGraphic.mbSwapOut)
, mbDummyContext(rImpGraphic.mbDummyContext)
, maGraphicExternalLink(rImpGraphic.maGraphicExternalLink)
, mbPrepared(rImpGraphic.mbPrepared)
{
updateCurrentSizeInBytes(mnSizeBytes);
// Special case for animations
if (rImpGraphic.mpAnimation)
{
mpAnimation = std::make_unique<Animation>(*rImpGraphic.mpAnimation);
maBitmapEx = mpAnimation->GetBitmapEx();
}
}
ImpGraphic::ImpGraphic(ImpGraphic&& rImpGraphic) noexcept
: MemoryManaged(rImpGraphic)
, maMetaFile(std::move(rImpGraphic.maMetaFile))
, maBitmapEx(std::move(rImpGraphic.maBitmapEx))
, maSwapInfo(std::move(rImpGraphic.maSwapInfo))
, mpAnimation(std::move(rImpGraphic.mpAnimation))
, mpSwapFile(std::move(rImpGraphic.mpSwapFile))
, mpGfxLink(std::move(rImpGraphic.mpGfxLink))
, maVectorGraphicData(std::move(rImpGraphic.maVectorGraphicData))
, meType(rImpGraphic.meType)
, mnSizeBytes(rImpGraphic.mnSizeBytes)
, mbSwapOut(rImpGraphic.mbSwapOut)
, mbDummyContext(rImpGraphic.mbDummyContext)
, maGraphicExternalLink(rImpGraphic.maGraphicExternalLink)
, mbPrepared (rImpGraphic.mbPrepared)
{
updateCurrentSizeInBytes(mnSizeBytes);
rImpGraphic.clear();
rImpGraphic.mbDummyContext = false;
}
ImpGraphic::ImpGraphic(std::shared_ptr<GfxLink> xGfxLink, sal_Int32 nPageIndex)
: MemoryManaged(true)
, mpGfxLink(std::move(xGfxLink))
, meType(GraphicType::Bitmap)
, mbSwapOut(true)
{
maSwapInfo.mbIsTransparent = true;
maSwapInfo.mbIsAlpha = true;
maSwapInfo.mbIsEPS = false;
maSwapInfo.mbIsAnimated = false;
maSwapInfo.mnAnimationLoopCount = 0;
maSwapInfo.mnPageIndex = nPageIndex;
ensureCurrentSizeInBytes();
}
ImpGraphic::ImpGraphic(GraphicExternalLink aGraphicExternalLink)
: MemoryManaged(true)
, meType(GraphicType::Default)
, maGraphicExternalLink(std::move(aGraphicExternalLink))
{
ensureCurrentSizeInBytes();
}
ImpGraphic::ImpGraphic(const BitmapEx& rBitmapEx)
: MemoryManaged(!rBitmapEx.IsEmpty())
, maBitmapEx(rBitmapEx)
, meType(rBitmapEx.IsEmpty() ? GraphicType::NONE : GraphicType::Bitmap)
{
ensureCurrentSizeInBytes();
}
ImpGraphic::ImpGraphic(const std::shared_ptr<VectorGraphicData>& rVectorGraphicDataPtr)
: MemoryManaged(bool(rVectorGraphicDataPtr))
, maVectorGraphicData(rVectorGraphicDataPtr)
, meType(rVectorGraphicDataPtr ? GraphicType::Bitmap : GraphicType::NONE)
{
ensureCurrentSizeInBytes();
}
ImpGraphic::ImpGraphic(const Animation& rAnimation)
: MemoryManaged(true)
, maBitmapEx(rAnimation.GetBitmapEx())
, mpAnimation(std::make_unique<Animation>(rAnimation))
, meType(GraphicType::Bitmap)
{
ensureCurrentSizeInBytes();
}
ImpGraphic::ImpGraphic(const GDIMetaFile& rMetafile)
: MemoryManaged(true)
, maMetaFile(rMetafile)
, meType(GraphicType::GdiMetafile)
{
ensureCurrentSizeInBytes();
}
ImpGraphic::~ImpGraphic()
{
}
ImpGraphic& ImpGraphic::operator=( const ImpGraphic& rImpGraphic )
{
if( &rImpGraphic != this )
{
maMetaFile = rImpGraphic.maMetaFile;
meType = rImpGraphic.meType;
mnSizeBytes = rImpGraphic.mnSizeBytes;
updateCurrentSizeInBytes(mnSizeBytes);
maSwapInfo = rImpGraphic.maSwapInfo;
mbDummyContext = rImpGraphic.mbDummyContext;
maGraphicExternalLink = rImpGraphic.maGraphicExternalLink;
mpAnimation.reset();
if ( rImpGraphic.mpAnimation )
{
mpAnimation = std::make_unique<Animation>( *rImpGraphic.mpAnimation );
maBitmapEx = mpAnimation->GetBitmapEx();
}
else
{
maBitmapEx = rImpGraphic.maBitmapEx;
}
mbSwapOut = rImpGraphic.mbSwapOut;
mpSwapFile = rImpGraphic.mpSwapFile;
mbPrepared = rImpGraphic.mbPrepared;
mpGfxLink = rImpGraphic.mpGfxLink;
maVectorGraphicData = rImpGraphic.maVectorGraphicData;
resetLastUsed();
changeExisting(mnSizeBytes);
}
return *this;
}
ImpGraphic& ImpGraphic::operator=(ImpGraphic&& rImpGraphic)
{
maMetaFile = std::move(rImpGraphic.maMetaFile);
meType = rImpGraphic.meType;
mnSizeBytes = rImpGraphic.mnSizeBytes;
maSwapInfo = std::move(rImpGraphic.maSwapInfo);
mbDummyContext = rImpGraphic.mbDummyContext;
mpAnimation = std::move(rImpGraphic.mpAnimation);
maBitmapEx = std::move(rImpGraphic.maBitmapEx);
mbSwapOut = rImpGraphic.mbSwapOut;
mpSwapFile = std::move(rImpGraphic.mpSwapFile);
mpGfxLink = std::move(rImpGraphic.mpGfxLink);
maVectorGraphicData = std::move(rImpGraphic.maVectorGraphicData);
maGraphicExternalLink = rImpGraphic.maGraphicExternalLink;
mbPrepared = rImpGraphic.mbPrepared;
rImpGraphic.clear();
rImpGraphic.mbDummyContext = false;
resetLastUsed();
changeExisting(mnSizeBytes);
return *this;
}
bool ImpGraphic::operator==( const ImpGraphic& rOther ) const
{
if( this == &rOther )
return true;
if (mbPrepared && rOther.mbPrepared)
return (*mpGfxLink == *rOther.mpGfxLink);
if (!isAvailable() || !rOther.isAvailable())
return false;
if ( meType != rOther.meType )
return false;
bool bRet = false;
switch( meType )
{
case GraphicType::NONE:
case GraphicType::Default:
return true;
case GraphicType::GdiMetafile:
return ( rOther.maMetaFile == maMetaFile );
case GraphicType::Bitmap:
{
if(maVectorGraphicData)
{
if(maVectorGraphicData == rOther.maVectorGraphicData)
{
// equal instances
bRet = true;
}
else if(rOther.maVectorGraphicData)
{
// equal content
bRet = (*maVectorGraphicData) == (*rOther.maVectorGraphicData);
}
}
else if( mpAnimation )
{
if( rOther.mpAnimation && ( *rOther.mpAnimation == *mpAnimation ) )
bRet = true;
}
else if( !rOther.mpAnimation && ( rOther.maBitmapEx == maBitmapEx ) )
{
bRet = true;
}
}
break;
}
return bRet;
}
const std::shared_ptr<VectorGraphicData>& ImpGraphic::getVectorGraphicData() const
{
ensureAvailable();
return maVectorGraphicData;
}
void ImpGraphic::createSwapInfo()
{
if (isSwappedOut())
return;
if (!maBitmapEx.IsEmpty())
maSwapInfo.maSizePixel = maBitmapEx.GetSizePixel();
else
maSwapInfo.maSizePixel = Size();
maSwapInfo.maPrefMapMode = getPrefMapMode();
maSwapInfo.maPrefSize = getPrefSize();
maSwapInfo.mbIsAnimated = isAnimated();
maSwapInfo.mbIsEPS = isEPS();
maSwapInfo.mbIsTransparent = isTransparent();
maSwapInfo.mbIsAlpha = isAlpha();
maSwapInfo.mnAnimationLoopCount = getAnimationLoopCount();
maSwapInfo.mnPageIndex = getPageNumber();
}
void ImpGraphic::clearGraphics()
{
maBitmapEx.Clear();
maMetaFile.Clear();
mpAnimation.reset();
maVectorGraphicData.reset();
}
void ImpGraphic::setPrepared(bool bAnimated, const Size* pSizeHint)
{
mbPrepared = true;
mbSwapOut = true;
meType = GraphicType::Bitmap;
SvMemoryStream aMemoryStream(const_cast<sal_uInt8*>(mpGfxLink->GetData()), mpGfxLink->GetDataSize(), StreamMode::READ | StreamMode::WRITE);
if (pSizeHint)
{
maSwapInfo.maPrefSize = *pSizeHint;
maSwapInfo.maPrefMapMode = MapMode(MapUnit::Map100thMM);
}
GraphicDescriptor aDescriptor(aMemoryStream, nullptr);
if (aDescriptor.Detect(true))
{
if (!pSizeHint)
{
// If we have logic size, work with that, as later pixel -> logic
// conversion will work with the output device DPI, not the graphic
// DPI.
Size aLogSize = aDescriptor.GetSize_100TH_MM();
if (aDescriptor.GetPreferredLogSize() && aDescriptor.GetPreferredMapMode())
{
maSwapInfo.maPrefSize = *aDescriptor.GetPreferredLogSize();
maSwapInfo.maPrefMapMode = *aDescriptor.GetPreferredMapMode();
}
else if (aLogSize.getWidth() && aLogSize.getHeight())
{
maSwapInfo.maPrefSize = aLogSize;
maSwapInfo.maPrefMapMode = MapMode(MapUnit::Map100thMM);
}
else
{
maSwapInfo.maPrefSize = aDescriptor.GetSizePixel();
maSwapInfo.maPrefMapMode = MapMode(MapUnit::MapPixel);
}
}
maSwapInfo.maSizePixel = aDescriptor.GetSizePixel();
maSwapInfo.mbIsTransparent = aDescriptor.IsTransparent();
maSwapInfo.mbIsAlpha = aDescriptor.IsAlpha();
} else {
maSwapInfo.mbIsTransparent = false;
maSwapInfo.mbIsAlpha = false;
}
maSwapInfo.mnAnimationLoopCount = 0;
maSwapInfo.mbIsEPS = false;
maSwapInfo.mbIsAnimated = bAnimated;
if (maVectorGraphicData)
maSwapInfo.mnPageIndex = maVectorGraphicData->getPageIndex();
}
void ImpGraphic::clear()
{
mpSwapFile.reset();
mbSwapOut = false;
mbPrepared = false;
// cleanup
clearGraphics();
meType = GraphicType::NONE;
mnSizeBytes = 0;
changeExisting(mnSizeBytes);
maGraphicExternalLink.msURL.clear();
}
bool ImpGraphic::isSupportedGraphic() const
{
return meType != GraphicType::NONE;
}
bool ImpGraphic::isTransparent() const
{
bool bRet(true);
if (mbSwapOut)
{
bRet = maSwapInfo.mbIsTransparent;
}
else if (meType == GraphicType::Bitmap && !maVectorGraphicData)
{
bRet = mpAnimation ? mpAnimation->IsTransparent() : maBitmapEx.IsAlpha();
}
return bRet;
}
bool ImpGraphic::isAlpha() const
{
bool bRet(false);
if (mbSwapOut)
{
bRet = maSwapInfo.mbIsAlpha;
}
else if (maVectorGraphicData)
{
bRet = true;
}
else if (meType == GraphicType::Bitmap)
{
bRet = (nullptr == mpAnimation && maBitmapEx.IsAlpha());
}
return bRet;
}
bool ImpGraphic::isAnimated() const
{
return mbSwapOut ? maSwapInfo.mbIsAnimated : mpAnimation != nullptr;
}
bool ImpGraphic::isEPS() const
{
if (mbSwapOut)
return maSwapInfo.mbIsEPS;
return( ( meType == GraphicType::GdiMetafile ) &&
( maMetaFile.GetActionSize() > 0 ) &&
( maMetaFile.GetAction( 0 )->GetType() == MetaActionType::EPS ) );
}
bool ImpGraphic::isAvailable() const
{
return !mbPrepared && !mbSwapOut;
}
bool ImpGraphic::makeAvailable()
{
return ensureAvailable();
}
void ImpGraphic::updateBitmapFromVectorGraphic(const Size& pixelSize) const
{
assert (maVectorGraphicData);
// use maBitmapEx as local buffer for rendered vector image
if (pixelSize.Width() && pixelSize.Height())
{
if (maBitmapEx.IsEmpty() || maBitmapEx.GetSizePixel() != pixelSize)
const_cast<ImpGraphic*>(this)->maBitmapEx = maVectorGraphicData->getBitmap(pixelSize);
}
else // maVectorGraphicData caches the replacement, so updating unconditionally is cheap
{
const_cast<ImpGraphic*>(this)->maBitmapEx = maVectorGraphicData->getReplacement();
}
if (maExPrefSize.getWidth() && maExPrefSize.getHeight())
const_cast<ImpGraphic*>(this)->maBitmapEx.SetPrefSize(maExPrefSize);
}
Bitmap ImpGraphic::getBitmap(const GraphicConversionParameters& rParameters) const
{
Bitmap aRetBmp;
ensureAvailable();
if( meType == GraphicType::Bitmap )
{
if (!mpAnimation && maVectorGraphicData)
updateBitmapFromVectorGraphic(rParameters.getSizePixel());
const BitmapEx& rRetBmpEx = ( mpAnimation ? mpAnimation->GetBitmapEx() : maBitmapEx );
aRetBmp = rRetBmpEx.GetBitmap( COL_WHITE );
if(rParameters.getSizePixel().Width() || rParameters.getSizePixel().Height())
aRetBmp.Scale(rParameters.getSizePixel());
}
else if( ( meType != GraphicType::Default ) && isSupportedGraphic() )
{
if(maBitmapEx.IsEmpty())
{
// calculate size
ScopedVclPtrInstance< VirtualDevice > aVDev;
Size aDrawSize(aVDev->LogicToPixel(maMetaFile.GetPrefSize(), maMetaFile.GetPrefMapMode()));
if(rParameters.getSizePixel().Width() && rParameters.getSizePixel().Height())
{
// apply given size if exists
aDrawSize = rParameters.getSizePixel();
}
if(aDrawSize.Width() && aDrawSize.Height() && !rParameters.getUnlimitedSize()
&& (aDrawSize.Width() > GRAPHIC_MTFTOBMP_MAXEXT || aDrawSize.Height() > GRAPHIC_MTFTOBMP_MAXEXT))
{
// limit bitmap size to a maximum of GRAPHIC_MTFTOBMP_MAXEXT x GRAPHIC_MTFTOBMP_MAXEXT
double fWH(static_cast<double>(aDrawSize.Width()) / static_cast<double>(aDrawSize.Height()));
if(fWH <= 1.0)
{
aDrawSize.setWidth(basegfx::fround<tools::Long>(GRAPHIC_MTFTOBMP_MAXEXT * fWH));
aDrawSize.setHeight(GRAPHIC_MTFTOBMP_MAXEXT);
}
else
{
aDrawSize.setWidth(GRAPHIC_MTFTOBMP_MAXEXT);
aDrawSize.setHeight(basegfx::fround<tools::Long>(GRAPHIC_MTFTOBMP_MAXEXT / fWH));
}
}
// calculate pixel size. Normally, it's the same as aDrawSize, but may
// need to be extended when hairlines are on the right or bottom edge
Size aPixelSize(aDrawSize);
if(GraphicType::GdiMetafile == getType())
{
// tdf#126319 Removed correction based on hairline-at-the-extremes of
// the metafile. The task shows that this is no longer sufficient since
// less hairlines get used in general - what is good, but breaks that
// old fix. Anyways, hairlines are a left-over from non-AA times
// when it was not possible to paint lines taller than one pixel.
// This might need to be corrected further using primitives and
// the possibility to get better-quality ranges for correction. For
// now, always add that one pixel.
aPixelSize.setWidth(aPixelSize.getWidth() + 1);
aPixelSize.setHeight(aPixelSize.getHeight() + 1);
}
if(aVDev->SetOutputSizePixel(aPixelSize))
{
if(rParameters.getAntiAliase())
{
aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::Enable);
}
if(rParameters.getSnapHorVerLines())
{
aVDev->SetAntialiasing(aVDev->GetAntialiasing() | AntialiasingFlags::PixelSnapHairline);
}
draw(*aVDev, Point(), aDrawSize);
// use maBitmapEx as local buffer for rendered metafile
const_cast< ImpGraphic* >(this)->maBitmapEx = aVDev->GetBitmapEx( Point(), aVDev->GetOutputSizePixel() );
}
}
aRetBmp = maBitmapEx.GetBitmap();
}
if( !aRetBmp.IsEmpty() )
{
aRetBmp.SetPrefMapMode(getPrefMapMode());
aRetBmp.SetPrefSize(getPrefSize());
}
return aRetBmp;
}
BitmapEx ImpGraphic::getBitmapEx(const GraphicConversionParameters& rParameters) const
{
BitmapEx aRetBmpEx;
ensureAvailable();
if( meType == GraphicType::Bitmap )
{
if (!mpAnimation && maVectorGraphicData)
updateBitmapFromVectorGraphic(rParameters.getSizePixel());
aRetBmpEx = ( mpAnimation ? mpAnimation->GetBitmapEx() : maBitmapEx );
if(rParameters.getSizePixel().Width() || rParameters.getSizePixel().Height())
{
aRetBmpEx.Scale(
rParameters.getSizePixel(),
BmpScaleFlag::Fast);
}
}
else if( ( meType != GraphicType::Default ) && isSupportedGraphic() )
{
if(maBitmapEx.IsEmpty())
{
const ImpGraphic aMonoMask( maMetaFile.GetMonochromeMtf( COL_BLACK ) );
// use maBitmapEx as local buffer for rendered metafile
const_cast< ImpGraphic* >(this)->maBitmapEx = BitmapEx(getBitmap(rParameters), aMonoMask.getBitmap(rParameters));
}
aRetBmpEx = maBitmapEx;
}
return aRetBmpEx;
}
Animation ImpGraphic::getAnimation() const
{
Animation aAnimation;
ensureAvailable();
if( mpAnimation )
aAnimation = *mpAnimation;
return aAnimation;
}
const BitmapEx& ImpGraphic::getBitmapExRef() const
{
ensureAvailable();
return maBitmapEx;
}
const GDIMetaFile& ImpGraphic::getGDIMetaFile() const
{
ensureAvailable();
if (!maMetaFile.GetActionSize()
&& maVectorGraphicData
&& (VectorGraphicDataType::Emf == maVectorGraphicData->getType()
|| VectorGraphicDataType::Wmf == maVectorGraphicData->getType()))
{
// If we have a Emf/Wmf VectorGraphic object, we
// need a way to get the Metafile data out of the primitive
// representation. Use a strict virtual hook (MetafileAccessor)
// to access the MetafilePrimitive2D directly. Also see comments in
// XEmfParser about this.
const std::deque< css::uno::Reference< css::graphic::XPrimitive2D > > aSequence(maVectorGraphicData->getPrimitive2DSequence());
if (1 == aSequence.size())
{
// try to cast to MetafileAccessor implementation
const css::uno::Reference< css::graphic::XPrimitive2D >& xReference(aSequence[0]);
auto pUnoPrimitive = static_cast< const drawinglayer::primitive2d::UnoPrimitive2D* >(xReference.get());
if (pUnoPrimitive)
{
const MetafileAccessor* pMetafileAccessor = dynamic_cast< const MetafileAccessor* >(pUnoPrimitive->getBasePrimitive2D().get());
if (pMetafileAccessor)
{
// it is a MetafileAccessor implementation, get Metafile
pMetafileAccessor->accessMetafile(const_cast< ImpGraphic* >(this)->maMetaFile);
}
}
}
}
if (GraphicType::Bitmap == meType && !maMetaFile.GetActionSize())
{
if (maVectorGraphicData)
updateBitmapFromVectorGraphic();
// #i119735#
// Use the local maMetaFile as container for a metafile-representation
// of the bitmap graphic. This will be done only once, thus be buffered.
// I checked all usages of maMetaFile, it is only used when type is not
// GraphicType::Bitmap. In operator= it will get copied, thus buffering will
// survive copying (change this if not wanted)
ImpGraphic* pThat = const_cast< ImpGraphic* >(this);
// #123983# directly create a metafile with the same PrefSize and PrefMapMode
// the bitmap has, this will be an always correct metafile
if(maBitmapEx.IsAlpha())
{
pThat->maMetaFile.AddAction(new MetaBmpExScaleAction(Point(), maBitmapEx.GetPrefSize(), maBitmapEx));
}
else
{
pThat->maMetaFile.AddAction(new MetaBmpScaleAction(Point(), maBitmapEx.GetPrefSize(), maBitmapEx.GetBitmap()));
}
pThat->maMetaFile.Stop();
pThat->maMetaFile.WindStart();
pThat->maMetaFile.SetPrefSize(maBitmapEx.GetPrefSize());
pThat->maMetaFile.SetPrefMapMode(maBitmapEx.GetPrefMapMode());
}
return maMetaFile;
}
Size ImpGraphic::getSizePixel() const
{
Size aSize;
if (isSwappedOut())
aSize = maSwapInfo.maSizePixel;
else
aSize = getBitmapEx(GraphicConversionParameters()).GetSizePixel();
return aSize;
}
Size ImpGraphic::getPrefSize() const
{
Size aSize;
if (isSwappedOut())
{
aSize = maSwapInfo.maPrefSize;
}
else
{
switch (meType)
{
case GraphicType::Bitmap:
{
if (maVectorGraphicData && maBitmapEx.IsEmpty())
{
if (!maExPrefSize.getWidth() || !maExPrefSize.getHeight())
{
// svg not yet buffered in maBitmapEx, return size derived from range
const basegfx::B2DRange& rRange = maVectorGraphicData->getRange();
#ifdef MACOSX
// tdf#157680 scale down estimated size of embedded PDF
// For some unknown reason, the embedded PDF sizes
// are 20x larger than expected. This only occurs on
// macOS so possibly there is some special conversion
// from MapUnit::MapPoint to MapUnit::MapTwip elsewhere
// in the code.
if (maVectorGraphicData->getType() == VectorGraphicDataType::Pdf)
aSize = Size(basegfx::fround(rRange.getWidth() / 20.0f), basegfx::fround(rRange.getHeight() / 20.0f));
else
#endif
aSize = Size(basegfx::fround<tools::Long>(rRange.getWidth()), basegfx::fround<tools::Long>(rRange.getHeight()));
}
else
{
aSize = maExPrefSize;
}
}
else
{
aSize = maBitmapEx.GetPrefSize();
if( !aSize.Width() || !aSize.Height() )
{
aSize = maBitmapEx.GetSizePixel();
}
}
}
break;
case GraphicType::GdiMetafile:
{
aSize = maMetaFile.GetPrefSize();
}
break;
case GraphicType::NONE:
case GraphicType::Default:
break;
}
}
return aSize;
}
void ImpGraphic::setValuesForPrefSize(const Size& rPrefSize)
{
switch (meType)
{
case GraphicType::Bitmap:
{
// used when importing a writer FlyFrame with SVG as graphic, added conversion
// to allow setting the PrefSize at the BitmapEx to hold it
if (maVectorGraphicData)
{
maExPrefSize = rPrefSize;
}
// #108077# Push through pref size to animation object,
// will be lost on copy otherwise
if (mpAnimation)
{
const_cast< BitmapEx& >(mpAnimation->GetBitmapEx()).SetPrefSize(rPrefSize);
}
maBitmapEx.SetPrefSize(rPrefSize);
}
break;
case GraphicType::GdiMetafile:
{
if (isSupportedGraphic())
maMetaFile.SetPrefSize(rPrefSize);
}
break;
case GraphicType::NONE:
case GraphicType::Default:
break;
}
}
void ImpGraphic::setPrefSize(const Size& rPrefSize)
{
ensureAvailable();
setValuesForPrefSize(rPrefSize);
}
MapMode ImpGraphic::getPrefMapMode() const
{
MapMode aMapMode;
if (isSwappedOut())
{
aMapMode = maSwapInfo.maPrefMapMode;
}
else
{
switch (meType)
{
case GraphicType::Bitmap:
{
if (maVectorGraphicData && maBitmapEx.IsEmpty())
{
// svg not yet buffered in maBitmapEx, return default PrefMapMode
aMapMode = MapMode(MapUnit::Map100thMM);
}
else
{
const Size aSize(maBitmapEx.GetPrefSize());
if (aSize.Width() && aSize.Height())
aMapMode = maBitmapEx.GetPrefMapMode();
}
}
break;
case GraphicType::GdiMetafile:
{
return maMetaFile.GetPrefMapMode();
}
break;
case GraphicType::NONE:
case GraphicType::Default:
break;
}
}
return aMapMode;
}
void ImpGraphic::setValuesForPrefMapMod(const MapMode& rPrefMapMode)
{
switch (meType)
{
case GraphicType::Bitmap:
{
if (maVectorGraphicData)
{
// ignore for Vector Graphic Data. If this is really used (except the grfcache)
// it can be extended by using maBitmapEx as buffer for updateBitmapFromVectorGraphic()
}
else
{
// #108077# Push through pref mapmode to animation object,
// will be lost on copy otherwise
if (mpAnimation)
{
const_cast<BitmapEx&>(mpAnimation->GetBitmapEx()).SetPrefMapMode(rPrefMapMode);
}
maBitmapEx.SetPrefMapMode(rPrefMapMode);
}
}
break;
case GraphicType::GdiMetafile:
{
maMetaFile.SetPrefMapMode(rPrefMapMode);
}
break;
case GraphicType::NONE:
case GraphicType::Default:
break;
}
}
void ImpGraphic::setPrefMapMode(const MapMode& rPrefMapMode)
{
ensureAvailable();
setValuesForPrefMapMod(rPrefMapMode);
}
void ImpGraphic::ensureCurrentSizeInBytes()
{
if (isAvailable())
changeExisting(getSizeBytes());
else
changeExisting(0);
}
sal_uLong ImpGraphic::getSizeBytes() const
{
if (mnSizeBytes > 0)
return mnSizeBytes;
if (mbPrepared)
ensureAvailable();
switch (meType)
{
case GraphicType::Bitmap:
{
if (maVectorGraphicData)
{
std::pair<VectorGraphicData::State, size_t> aPair(maVectorGraphicData->getSizeBytes());
if (VectorGraphicData::State::UNPARSED == aPair.first)
{
return aPair.second; // don't cache it until Vector Graphic Data is parsed
}
mnSizeBytes = aPair.second;
}
else
{
mnSizeBytes = mpAnimation ? mpAnimation->GetSizeBytes() : maBitmapEx.GetSizeBytes();
}
}
break;
case GraphicType::GdiMetafile:
{
mnSizeBytes = maMetaFile.GetSizeBytes();
}
break;
case GraphicType::NONE:
case GraphicType::Default:
break;
}
return mnSizeBytes;
}
void ImpGraphic::draw(OutputDevice& rOutDev, const Point& rDestPt) const
{
ensureAvailable();
if (isSwappedOut())
return;
switch (meType)
{
case GraphicType::Bitmap:
{
if (mpAnimation)
{
mpAnimation->Draw(rOutDev, rDestPt);
}
else
{
if (maVectorGraphicData)
updateBitmapFromVectorGraphic();
maBitmapEx.Draw(&rOutDev, rDestPt);
}
}
break;
case GraphicType::GdiMetafile:
{
draw(rOutDev, rDestPt, maMetaFile.GetPrefSize());
}
break;
case GraphicType::Default:
case GraphicType::NONE:
break;
}
}
void ImpGraphic::draw(OutputDevice& rOutDev,
const Point& rDestPt, const Size& rDestSize) const
{
ensureAvailable();
if (isSwappedOut())
return;
switch (meType)
{
case GraphicType::Bitmap:
{
if (mpAnimation)
{
mpAnimation->Draw(rOutDev, rDestPt, rDestSize);
}
else
{
if (maVectorGraphicData)
updateBitmapFromVectorGraphic(rOutDev.LogicToPixel(rDestSize));
maBitmapEx.Draw(&rOutDev, rDestPt, rDestSize);
}
}
break;
case GraphicType::GdiMetafile:
{
const_cast<ImpGraphic*>(this)->maMetaFile.WindStart();
const_cast<ImpGraphic*>(this)->maMetaFile.Play(rOutDev, rDestPt, rDestSize);
const_cast<ImpGraphic*>(this)->maMetaFile.WindStart();
}
break;
case GraphicType::Default:
case GraphicType::NONE:
break;
}
}
void ImpGraphic::startAnimation(OutputDevice& rOutDev, const Point& rDestPt,
const Size& rDestSize, tools::Long nRendererId,
OutputDevice* pFirstFrameOutDev )
{
ensureAvailable();
if( isSupportedGraphic() && !isSwappedOut() && mpAnimation )
mpAnimation->Start(rOutDev, rDestPt, rDestSize, nRendererId, pFirstFrameOutDev);
}
void ImpGraphic::stopAnimation( const OutputDevice* pOutDev, tools::Long nRendererId )
{
ensureAvailable();
if( isSupportedGraphic() && !isSwappedOut() && mpAnimation )
mpAnimation->Stop( pOutDev, nRendererId );
}
void ImpGraphic::setAnimationNotifyHdl( const Link<Animation*,void>& rLink )
{
ensureAvailable();
if( mpAnimation )
mpAnimation->SetNotifyHdl( rLink );
}
Link<Animation*,void> ImpGraphic::getAnimationNotifyHdl() const
{
Link<Animation*,void> aLink;
ensureAvailable();
if( mpAnimation )
aLink = mpAnimation->GetNotifyHdl();
return aLink;
}
sal_uInt32 ImpGraphic::getAnimationLoopCount() const
{
if (mbSwapOut)
return maSwapInfo.mnAnimationLoopCount;
return mpAnimation ? mpAnimation->GetLoopCount() : 0;
}
bool ImpGraphic::swapInContent(SvStream& rStream)
{
bool bRet = false;
sal_uInt32 nId;
sal_Int32 nType;
sal_Int32 nLength;
rStream.ReadUInt32(nId);
// check version
if (SWAP_FORMAT_ID != nId)
{
SAL_WARN("vcl", "Incompatible swap file!");
return false;
}
rStream.ReadInt32(nType);
rStream.ReadInt32(nLength);
meType = static_cast<GraphicType>(nType);
if (meType == GraphicType::NONE || meType == GraphicType::Default)
{
return true;
}
else
{
bRet = swapInGraphic(rStream);
}
return bRet;
}
bool ImpGraphic::swapOutGraphic(SvStream& rStream)
{
if (rStream.GetError())
return false;
ensureAvailable();
if (isSwappedOut())
{
rStream.SetError(SVSTREAM_GENERALERROR);
return false;
}
switch (meType)
{
case GraphicType::GdiMetafile:
{
if(!rStream.GetError())
{
SvmWriter aWriter(rStream);
aWriter.Write(maMetaFile);
}
}
break;
case GraphicType::Bitmap:
{
if (maVectorGraphicData)
{
rStream.WriteInt32(sal_Int32(GraphicContentType::Vector));
// stream out Vector Graphic defining data (length, byte array and evtl. path)
// this is used e.g. in swapping out graphic data and in transporting it over UNO API
// as sequence of bytes, but AFAIK not written anywhere to any kind of file, so it should be
// no problem to extend it; only used at runtime
switch (maVectorGraphicData->getType())
{
case VectorGraphicDataType::Wmf:
{
rStream.WriteUInt32(constWmfMagic);
break;
}
case VectorGraphicDataType::Emf:
{
rStream.WriteUInt32(constEmfMagic);
break;
}
case VectorGraphicDataType::Svg:
{
rStream.WriteUInt32(constSvgMagic);
break;
}
case VectorGraphicDataType::Pdf:
{
rStream.WriteUInt32(constPdfMagic);
break;
}
}
rStream.WriteUInt32(maVectorGraphicData->getBinaryDataContainer().getSize());
maVectorGraphicData->getBinaryDataContainer().writeToStream(rStream);
}
else if (mpAnimation)
{
rStream.WriteInt32(sal_Int32(GraphicContentType::Animation));
WriteAnimation(rStream, *mpAnimation);
}
else
{
rStream.WriteInt32(sal_Int32(GraphicContentType::Bitmap));
WriteDIBBitmapEx(maBitmapEx, rStream);
}
}
break;
case GraphicType::NONE:
case GraphicType::Default:
break;
}
if (mpGfxLink)
mpGfxLink->getDataContainer().swapOut();
return true;
}
bool ImpGraphic::swapOutContent(SvStream& rStream)
{
ensureAvailable();
bool bRet = false;
if (meType == GraphicType::NONE || meType == GraphicType::Default || isSwappedOut())
return false;
sal_uLong nDataFieldPos;
// Write the SWAP ID
rStream.WriteUInt32(SWAP_FORMAT_ID);
rStream.WriteInt32(static_cast<sal_Int32>(meType));
// data size is updated later
nDataFieldPos = rStream.Tell();
rStream.WriteInt32(0);
// write data block
const sal_uInt64 nDataStart = rStream.Tell();
swapOutGraphic(rStream);
if (!rStream.GetError())
{
// Write the written length th the header
const sal_uInt64 nCurrentPosition = rStream.Tell();
rStream.Seek(nDataFieldPos);
rStream.WriteInt32(nCurrentPosition - nDataStart);
rStream.Seek(nCurrentPosition);
bRet = true;
}
return bRet;
}
bool ImpGraphic::swapOut()
{
if (isSwappedOut())
return false;
bool bResult = false;
// We have GfxLink so we have the source available
if (mpGfxLink && mpGfxLink->IsNative())
{
createSwapInfo();
clearGraphics();
// reset the swap file
mpSwapFile.reset();
mpGfxLink->getDataContainer().swapOut();
// mark as swapped out
mbSwapOut = true;
bResult = true;
}
else
{
// Create a swap file
auto pSwapFile = o3tl::make_shared<ImpSwapFile>(getOriginURL());
// Open a stream to write the swap file to
{
SvStream* pOutputStream = pSwapFile->getStream();
if (!pOutputStream)
return false;
// Write to stream
pOutputStream->SetVersion(SOFFICE_FILEFORMAT_50);
pOutputStream->SetCompressMode(SvStreamCompressFlags::NATIVE);
pOutputStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE);
if (!pOutputStream->GetError() && swapOutContent(*pOutputStream))
{
pOutputStream->FlushBuffer();
bResult = !pOutputStream->GetError();
}
}
// Check if writing was successful
if (bResult)
{
// We have swapped out, so can clean memory and prepare swap info
createSwapInfo();
clearGraphics();
mpSwapFile = std::move(pSwapFile);
mbSwapOut = true;
}
}
if (bResult)
{
// Signal to manager that we have swapped out
swappedOut(0);
}
return bResult;
}
bool ImpGraphic::ensureAvailable() const
{
bool bResult = true;
if (isSwappedOut())
{
auto pThis = const_cast<ImpGraphic*>(this);
pThis->registerIntoManager();
bResult = pThis->swapIn();
}
resetLastUsed();
return bResult;
}
void ImpGraphic::updateFromLoadedGraphic(const ImpGraphic* pGraphic)
{
if (mbPrepared)
{
GraphicExternalLink aLink = maGraphicExternalLink;
Size aPrefSize = maSwapInfo.maPrefSize;
MapMode aPrefMapMode = maSwapInfo.maPrefMapMode;
*this = *pGraphic;
if (aPrefSize.getWidth() && aPrefSize.getHeight() && aPrefMapMode == getPrefMapMode())
{
// Use custom preferred size if it was set when the graphic was still unloaded.
// Only set the size in case the unloaded and loaded unit matches.
setPrefSize(aPrefSize);
}
maGraphicExternalLink = std::move(aLink);
}
else
{
// Move over only graphic content
mpAnimation.reset();
if (pGraphic->mpAnimation)
{
mpAnimation = std::make_unique<Animation>(*pGraphic->mpAnimation);
maBitmapEx = mpAnimation->GetBitmapEx();
}
else
{
maBitmapEx = pGraphic->maBitmapEx;
}
maMetaFile = pGraphic->maMetaFile;
maVectorGraphicData = pGraphic->maVectorGraphicData;
// Set to 0, to force recalculation
mnSizeBytes = 0;
mnChecksum = 0;
restoreFromSwapInfo();
mbSwapOut = false;
}
}
void ImpGraphic::dumpState(rtl::OStringBuffer &rState)
{
if (meType == GraphicType::NONE && mnSizeBytes == 0)
return; // uninteresting.
rState.append("\n\t");
if (mbSwapOut)
rState.append("swapped\t");
else
rState.append("loaded\t");
rState.append(static_cast<sal_Int32>(meType));
rState.append("\tsize:\t");
rState.append(static_cast<sal_Int64>(mnSizeBytes));
rState.append("\tgfxl:\t");
rState.append(static_cast<sal_Int64>(mpGfxLink ? mpGfxLink->getSizeBytes() : -1));
rState.append("\t");
rState.append(static_cast<sal_Int32>(maSwapInfo.maSizePixel.Width()));
rState.append("x");
rState.append(static_cast<sal_Int32>(maSwapInfo.maSizePixel.Height()));
rState.append("\t");
rState.append(static_cast<sal_Int32>(maExPrefSize.Width()));
rState.append("x");
rState.append(static_cast<sal_Int32>(maExPrefSize.Height()));
}
void ImpGraphic::restoreFromSwapInfo()
{
setValuesForPrefMapMod(maSwapInfo.maPrefMapMode);
setValuesForPrefSize(maSwapInfo.maPrefSize);
if (maVectorGraphicData)
{
maVectorGraphicData->setPageIndex(maSwapInfo.mnPageIndex);
}
}
namespace
{
std::optional<VectorGraphicDataType> lclConvertToVectorGraphicType(GfxLink const & rLink)
{
switch(rLink.GetType())
{
case GfxLinkType::NativePdf:
return VectorGraphicDataType::Pdf;
case GfxLinkType::NativeWmf:
if (rLink.IsEMF())
return VectorGraphicDataType::Emf;
else
return VectorGraphicDataType::Wmf;
case GfxLinkType::NativeSvg:
return VectorGraphicDataType::Svg;
default:
break;
}
return std::optional<VectorGraphicDataType>();
}
} // end namespace
bool ImpGraphic::swapIn()
{
if (!isSwappedOut())
return false;
bool bReturn = false;
if (mbPrepared)
{
Graphic aGraphic;
if (!mpGfxLink->LoadNative(aGraphic))
return false;
updateFromLoadedGraphic(aGraphic.ImplGetImpGraphic());
resetLastUsed();
bReturn = true;
}
else if (mpGfxLink && mpGfxLink->IsNative())
{
std::optional<VectorGraphicDataType> oType = lclConvertToVectorGraphicType(*mpGfxLink);
if (oType)
{
maVectorGraphicData = vcl::loadVectorGraphic(mpGfxLink->getDataContainer(), *oType);
// Set to 0, to force recalculation
mnSizeBytes = 0;
mnChecksum = 0;
restoreFromSwapInfo();
mbSwapOut = false;
}
else
{
Graphic aGraphic;
if (!mpGfxLink->LoadNative(aGraphic))
return false;
ImpGraphic* pImpGraphic = aGraphic.ImplGetImpGraphic();
if (meType != pImpGraphic->meType)
return false;
updateFromLoadedGraphic(pImpGraphic);
}
resetLastUsed();
bReturn = true;
}
else
{
SvStream* pStream = nullptr;
if (mpSwapFile)
pStream = mpSwapFile->getStream();
if (pStream)
{
pStream->SetVersion(SOFFICE_FILEFORMAT_50);
pStream->SetCompressMode(SvStreamCompressFlags::NATIVE);
pStream->SetBufferSize(GRAPHIC_STREAMBUFSIZE);
pStream->Seek(STREAM_SEEK_TO_BEGIN);
bReturn = swapInFromStream(*pStream);
restoreFromSwapInfo();
setOriginURL(mpSwapFile->getOriginURL());
mpSwapFile.reset();
}
}
if (bReturn)
{
swappedIn(getSizeBytes());
}
return bReturn;
}
bool ImpGraphic::swapInFromStream(SvStream& rStream)
{
bool bRet = false;
if (rStream.GetError())
return false;
clearGraphics();
mnSizeBytes = 0;
mnChecksum = 0;
bRet = swapInContent(rStream);
if (!bRet)
{
//throw away swapfile, etc.
clear();
}
mbSwapOut = false;
return bRet;
}
bool ImpGraphic::swapInGraphic(SvStream& rStream)
{
bool bReturn = false;
if (rStream.GetError())
return bReturn;
if (meType == GraphicType::Bitmap)
{
sal_Int32 nContentType = -1;
rStream.ReadInt32(nContentType);
if (nContentType < 0)
return false;
auto eContentType = static_cast<GraphicContentType>(nContentType);
switch (eContentType)
{
case GraphicContentType::Bitmap:
{
BitmapEx aBitmapEx;
ReadDIBBitmapEx(aBitmapEx, rStream);
if (!rStream.GetError())
{
maBitmapEx = aBitmapEx;
bReturn = true;
}
}
break;
case GraphicContentType::Animation:
{
auto pAnimation = std::make_unique<Animation>();
ReadAnimation(rStream, *pAnimation);
if (!rStream.GetError())
{
mpAnimation = std::move(pAnimation);
maBitmapEx = mpAnimation->GetBitmapEx();
bReturn = true;
}
}
break;
case GraphicContentType::Vector:
{
// try to stream in Svg defining data (length, byte array and evtl. path)
// See below (operator<<) for more information
sal_uInt32 nMagic;
rStream.ReadUInt32(nMagic);
if (constSvgMagic == nMagic || constWmfMagic == nMagic || constEmfMagic == nMagic || constPdfMagic == nMagic)
{
sal_uInt32 nVectorGraphicDataSize(0);
rStream.ReadUInt32(nVectorGraphicDataSize);
if (nVectorGraphicDataSize)
{
BinaryDataContainer aDataContainer(rStream, nVectorGraphicDataSize);
if (rStream.GetError())
return false;
VectorGraphicDataType aDataType;
switch (nMagic)
{
case constSvgMagic:
aDataType = VectorGraphicDataType::Svg;
break;
case constWmfMagic:
aDataType = VectorGraphicDataType::Wmf;
break;
case constEmfMagic:
aDataType = VectorGraphicDataType::Emf;
break;
case constPdfMagic:
aDataType = VectorGraphicDataType::Pdf;
break;
default:
return false;
}
auto aVectorGraphicDataPtr = std::make_shared<VectorGraphicData>(aDataContainer, aDataType);
if (!rStream.GetError())
{
maVectorGraphicData = std::move(aVectorGraphicDataPtr);
bReturn = true;
}
}
}
}
break;
}
}
else if (meType == GraphicType::GdiMetafile)
{
GDIMetaFile aMetaFile;
SvmReader aReader(rStream);
aReader.Read(aMetaFile);
if (!rStream.GetError())
{
maMetaFile = aMetaFile;
bReturn = true;
}
}
return bReturn;
}
void ImpGraphic::setGfxLink(const std::shared_ptr<GfxLink>& rGfxLink)
{
ensureAvailable();
mpGfxLink = rGfxLink;
}
const std::shared_ptr<GfxLink> & ImpGraphic::getSharedGfxLink() const
{
return mpGfxLink;
}
GfxLink ImpGraphic::getGfxLink() const
{
ensureAvailable();
return( mpGfxLink ? *mpGfxLink : GfxLink() );
}
bool ImpGraphic::isGfxLink() const
{
return ( bool(mpGfxLink) );
}
BitmapChecksum ImpGraphic::getChecksum() const
{
if (mnChecksum != 0)
return mnChecksum;
ensureAvailable();
switch (meType)
{
case GraphicType::NONE:
case GraphicType::Default:
break;
case GraphicType::Bitmap:
{
if (maVectorGraphicData)
mnChecksum = maVectorGraphicData->GetChecksum();
else if (mpAnimation)
mnChecksum = mpAnimation->GetChecksum();
else
mnChecksum = maBitmapEx.GetChecksum();
}
break;
case GraphicType::GdiMetafile:
{
mnChecksum = SvmWriter::GetChecksum(maMetaFile);
}
break;
}
return mnChecksum;
}
sal_Int32 ImpGraphic::getPageNumber() const
{
if (isSwappedOut())
return maSwapInfo.mnPageIndex;
if (maVectorGraphicData)
return maVectorGraphicData->getPageIndex();
return -1;
}
bool ImpGraphic::canReduceMemory() const
{
return !isSwappedOut();
}
bool ImpGraphic::reduceMemory()
{
return swapOut();
}
std::chrono::high_resolution_clock::time_point ImpGraphic::getLastUsed() const
{
return maLastUsed;
}
void ImpGraphic::resetLastUsed() const
{
maLastUsed = std::chrono::high_resolution_clock::now();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V530 The return value of function 'append' is required to be utilized.
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: maSwapInfo.
↑ V730 It is possible that not all members of a class are initialized inside the constructor. Consider inspecting: maSwapInfo.
↑ V730 It is possible that not all members of a class are initialized inside the constructor. Consider inspecting: maSwapInfo.
↑ V730 It is possible that not all members of a class are initialized inside the constructor. Consider inspecting: maSwapInfo.
↑ V730 It is possible that not all members of a class are initialized inside the constructor. Consider inspecting: maSwapInfo.
↑ V730 It is possible that not all members of a class are initialized inside the constructor. Consider inspecting: maSwapInfo.