diff options
Diffstat (limited to 'vcl/source/gdi/gdimtf.cxx')
-rw-r--r-- | vcl/source/gdi/gdimtf.cxx | 2357 |
1 files changed, 2357 insertions, 0 deletions
diff --git a/vcl/source/gdi/gdimtf.cxx b/vcl/source/gdi/gdimtf.cxx new file mode 100644 index 0000000000..fb4ff37dfa --- /dev/null +++ b/vcl/source/gdi/gdimtf.cxx @@ -0,0 +1,2357 @@ +/* -*- 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 <cstdlib> +#include <memory> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> +#include <tools/helpers.hxx> +#include <tools/stream.hxx> +#include <tools/vcompat.hxx> +#include <tools/fract.hxx> +#include <vcl/BitmapPalette.hxx> +#include <vcl/metaact.hxx> +#include <vcl/outdev.hxx> +#include <vcl/window.hxx> +#include <vcl/virdev.hxx> +#include <vcl/svapp.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/graphictools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/mtfxmldump.hxx> + +#include <vcl/TypeSerializer.hxx> + +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <com/sun/star/rendering/MtfRenderer.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <comphelper/processfactory.hxx> + +using namespace com::sun::star; + +namespace { + +struct ImplColAdjustParam +{ + std::unique_ptr<sal_uInt8[]> pMapR; + std::unique_ptr<sal_uInt8[]> pMapG; + std::unique_ptr<sal_uInt8[]> pMapB; +}; + +struct ImplBmpAdjustParam +{ + short nLuminancePercent; + short nContrastPercent; + short nChannelRPercent; + short nChannelGPercent; + short nChannelBPercent; + double fGamma; + bool bInvert; +}; + +struct ImplColConvertParam +{ + MtfConversion eConversion; +}; + +struct ImplBmpConvertParam +{ + BmpConversion eConversion; +}; + +struct ImplColMonoParam +{ + Color aColor; +}; + +struct ImplBmpMonoParam +{ + Color aColor; +}; + +struct ImplColReplaceParam +{ + std::unique_ptr<sal_uLong[]> pMinR; + std::unique_ptr<sal_uLong[]> pMaxR; + std::unique_ptr<sal_uLong[]> pMinG; + std::unique_ptr<sal_uLong[]> pMaxG; + std::unique_ptr<sal_uLong[]> pMinB; + std::unique_ptr<sal_uLong[]> pMaxB; + const Color * pDstCols; + sal_uLong nCount; +}; + +struct ImplBmpReplaceParam +{ + const Color* pSrcCols; + const Color* pDstCols; + sal_uLong nCount; +}; + +} + +GDIMetaFile::GDIMetaFile() : + m_nCurrentActionElement( 0 ), + m_aPrefSize ( 1, 1 ), + m_pPrev ( nullptr ), + m_pNext ( nullptr ), + m_pOutDev ( nullptr ), + m_bPause ( false ), + m_bRecord ( false ), + m_bUseCanvas ( false ), + m_bSVG ( false ) +{ +} + +GDIMetaFile::GDIMetaFile( const GDIMetaFile& rMtf ) : + m_nCurrentActionElement( rMtf.m_nCurrentActionElement ), + m_aPrefMapMode ( rMtf.m_aPrefMapMode ), + m_aPrefSize ( rMtf.m_aPrefSize ), + m_pPrev ( rMtf.m_pPrev ), + m_pNext ( rMtf.m_pNext ), + m_pOutDev ( nullptr ), + m_bPause ( false ), + m_bRecord ( false ), + m_bUseCanvas ( rMtf.m_bUseCanvas ), + m_bSVG ( rMtf.m_bSVG ) +{ + for( size_t i = 0, n = rMtf.GetActionSize(); i < n; ++i ) + { + m_aList.push_back( rMtf.GetAction( i ) ); + } + + if( rMtf.m_bRecord ) + { + Record( rMtf.m_pOutDev ); + + if ( rMtf.m_bPause ) + Pause( true ); + } +} + +GDIMetaFile::~GDIMetaFile() +{ + Clear(); +} + +bool GDIMetaFile::HasTransparentActions() const +{ + MetaAction* pCurrAct; + + // watch for transparent drawing actions + for(pCurrAct = const_cast<GDIMetaFile*>(this)->FirstAction(); + pCurrAct; + pCurrAct = const_cast<GDIMetaFile*>(this)->NextAction()) + { + // #i10613# determine if the action is transparency capable + + // #107169# Also examine metafiles with masked bitmaps in + // detail. Further down, this is optimized in such a way + // that there's no unnecessary painting of masked bitmaps + // (which are _always_ subdivided into rectangular regions + // of uniform opacity): if a masked bitmap is printed over + // empty background, we convert to a plain bitmap with + // white background. + if (pCurrAct->IsTransparent()) + return true; + } + + return false; +} + +size_t GDIMetaFile::GetActionSize() const +{ + return m_aList.size(); +} + +MetaAction* GDIMetaFile::GetAction( size_t nAction ) const +{ + return (nAction < m_aList.size()) ? m_aList[ nAction ].get() : nullptr; +} + +MetaAction* GDIMetaFile::FirstAction() +{ + m_nCurrentActionElement = 0; + return m_aList.empty() ? nullptr : m_aList[ 0 ].get(); +} + +MetaAction* GDIMetaFile::NextAction() +{ + return ( m_nCurrentActionElement + 1 < m_aList.size() ) ? m_aList[ ++m_nCurrentActionElement ].get() : nullptr; +} + +void GDIMetaFile::ReplaceAction( rtl::Reference<MetaAction> pAction, size_t nAction ) +{ + if ( nAction >= m_aList.size() ) + { + return; + } + //fdo#39995 This doesn't increment the incoming action ref-count nor does it + //decrement the outgoing action ref-count + std::swap(pAction, m_aList[nAction]); +} + +GDIMetaFile& GDIMetaFile::operator=( const GDIMetaFile& rMtf ) +{ + if( this != &rMtf ) + { + Clear(); + + // Increment RefCount of MetaActions + for( size_t i = 0, n = rMtf.GetActionSize(); i < n; ++i ) + { + m_aList.push_back( rMtf.GetAction( i ) ); + } + + m_aPrefMapMode = rMtf.m_aPrefMapMode; + m_aPrefSize = rMtf.m_aPrefSize; + m_pPrev = rMtf.m_pPrev; + m_pNext = rMtf.m_pNext; + m_pOutDev = nullptr; + m_bPause = false; + m_bRecord = false; + m_bUseCanvas = rMtf.m_bUseCanvas; + m_bSVG = rMtf.m_bSVG; + + if( rMtf.m_bRecord ) + { + Record( rMtf.m_pOutDev ); + + if( rMtf.m_bPause ) + Pause( true ); + } + } + + return *this; +} + +bool GDIMetaFile::operator==( const GDIMetaFile& rMtf ) const +{ + const size_t nObjCount = m_aList.size(); + bool bRet = false; + + if( this == &rMtf ) + bRet = true; + else if( rMtf.GetActionSize() == nObjCount && + rMtf.GetPrefSize() == m_aPrefSize && + rMtf.GetPrefMapMode() == m_aPrefMapMode ) + { + bRet = true; + + for( size_t n = 0; n < nObjCount; n++ ) + { + if( m_aList[ n ] != rMtf.GetAction( n ) ) + { + bRet = false; + break; + } + } + } + + return bRet; +} + +void GDIMetaFile::Clear() +{ + if( m_bRecord ) + Stop(); + + m_aList.clear(); +} + +void GDIMetaFile::Linker( OutputDevice* pOut, bool bLink ) +{ + if( bLink ) + { + m_pNext = nullptr; + m_pPrev = pOut->GetConnectMetaFile(); + pOut->SetConnectMetaFile( this ); + + if( m_pPrev ) + m_pPrev->m_pNext = this; + } + else + { + if( m_pNext ) + { + m_pNext->m_pPrev = m_pPrev; + + if( m_pPrev ) + m_pPrev->m_pNext = m_pNext; + } + else + { + if( m_pPrev ) + m_pPrev->m_pNext = nullptr; + + pOut->SetConnectMetaFile( m_pPrev ); + } + + m_pPrev = nullptr; + m_pNext = nullptr; + } +} + +void GDIMetaFile::Record( OutputDevice* pOut ) +{ + if( m_bRecord ) + Stop(); + + m_nCurrentActionElement = m_aList.empty() ? 0 : (m_aList.size() - 1); + m_pOutDev = pOut; + m_bRecord = true; + Linker( pOut, true ); +} + +void GDIMetaFile::Play( GDIMetaFile& rMtf ) +{ + if (m_bRecord || rMtf.m_bRecord) + return; + + MetaAction* pAction = GetCurAction(); + const size_t nObjCount = m_aList.size(); + + rMtf.UseCanvas( rMtf.GetUseCanvas() || m_bUseCanvas ); + rMtf.setSVG( rMtf.getSVG() || m_bSVG ); + + for( size_t nCurPos = m_nCurrentActionElement; nCurPos < nObjCount; nCurPos++ ) + { + if( pAction ) + { + rMtf.AddAction( pAction ); + } + + pAction = NextAction(); + } +} + +void GDIMetaFile::Play(OutputDevice& rOut, size_t nPos) +{ + if( m_bRecord ) + return; + + MetaAction* pAction = GetCurAction(); + const size_t nObjCount = m_aList.size(); + size_t nSyncCount = rOut.GetSyncCount(); + + if( nPos > nObjCount ) + nPos = nObjCount; + + // #i23407# Set backwards-compatible text language and layout mode + // This is necessary, since old metafiles don't even know of these + // recent add-ons. Newer metafiles must of course explicitly set + // those states. + rOut.Push(vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE); + rOut.SetLayoutMode(vcl::text::ComplexTextLayoutFlags::Default); + rOut.SetDigitLanguage(LANGUAGE_SYSTEM); + + SAL_INFO( "vcl.gdi", "GDIMetaFile::Play on device of size: " << rOut.GetOutputSizePixel().Width() << " " << rOut.GetOutputSizePixel().Height()); + + if (!ImplPlayWithRenderer(rOut, Point(0,0), rOut.GetOutputSize())) { + size_t i = 0; + for( size_t nCurPos = m_nCurrentActionElement; nCurPos < nPos; nCurPos++ ) + { + if( pAction ) + { + pAction->Execute(&rOut); + + // flush output from time to time + if( i++ > nSyncCount ) + { + rOut.Flush(); + i = 0; + } + } + + pAction = NextAction(); + } + } + rOut.Pop(); +} + +bool GDIMetaFile::ImplPlayWithRenderer(OutputDevice& rOut, const Point& rPos, Size rLogicDestSize) +{ + if (!m_bUseCanvas) + return false; + + Size rDestSize(rOut.LogicToPixel(rLogicDestSize)); + + const vcl::Window* win = rOut.GetOwnerWindow(); + + if (!win) + win = Application::GetActiveTopWindow(); + if (!win) + win = Application::GetFirstTopLevelWindow(); + + if (!win) + return false; + + try + { + uno::Reference<rendering::XCanvas> xCanvas = win->GetOutDev()->GetCanvas (); + + if (!xCanvas.is()) + return false; + + Size aSize (rDestSize.Width () + 1, rDestSize.Height () + 1); + uno::Reference<rendering::XBitmap> xBitmap = xCanvas->getDevice ()->createCompatibleAlphaBitmap (vcl::unotools::integerSize2DFromSize( aSize)); + if( xBitmap.is () ) + { + uno::Reference< rendering::XBitmapCanvas > xBitmapCanvas( xBitmap, uno::UNO_QUERY ); + if( xBitmapCanvas.is() ) + { + uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); + uno::Reference< rendering::XMtfRenderer > xMtfRenderer = rendering::MtfRenderer::createWithBitmapCanvas( xContext, xBitmapCanvas ); + + xBitmapCanvas->clear(); + uno::Reference< beans::XFastPropertySet > xMtfFastPropertySet( xMtfRenderer, uno::UNO_QUERY ); + if( xMtfFastPropertySet.is() ) + // set this metafile to the renderer to + // speedup things (instead of copying data to + // sequence of bytes passed to renderer) + xMtfFastPropertySet->setFastPropertyValue( 0, uno::Any( reinterpret_cast<sal_Int64>( this ) ) ); + + xMtfRenderer->draw( rDestSize.Width(), rDestSize.Height() ); + + BitmapEx aBitmapEx; + if( aBitmapEx.Create( xBitmapCanvas, aSize ) ) + { + if (rOut.GetMapMode().GetMapUnit() == MapUnit::MapPixel) + rOut.DrawBitmapEx( rPos, aBitmapEx ); + else + rOut.DrawBitmapEx( rPos, rLogicDestSize, aBitmapEx ); + return true; + } + } + } + } + catch (const uno::RuntimeException& ) + { + throw; // runtime errors are fatal + } + catch (const uno::Exception&) + { + // ignore errors, no way of reporting them here + TOOLS_WARN_EXCEPTION("vcl.gdi", "GDIMetaFile::ImplPlayWithRenderer"); + } + + return false; +} + +void GDIMetaFile::Play(OutputDevice& rOut, const Point& rPos, + const Size& rSize) +{ + MapMode aDrawMap( GetPrefMapMode() ); + Size aDestSize(rOut.LogicToPixel(rSize)); + + if (aDestSize.Width() <= 0 || aDestSize.Height() <= 0) + return; + + if (aDestSize.Width() > std::numeric_limits<sal_Int32>::max() || + aDestSize.Height() > std::numeric_limits<sal_Int32>::max()) + return; + + GDIMetaFile* pMtf = rOut.GetConnectMetaFile(); + + if (ImplPlayWithRenderer(rOut, rPos, rSize)) + return; + + Size aTmpPrefSize(rOut.LogicToPixel(GetPrefSize(), aDrawMap)); + + if( !aTmpPrefSize.Width() ) + aTmpPrefSize.setWidth( aDestSize.Width() ); + + if( !aTmpPrefSize.Height() ) + aTmpPrefSize.setHeight( aDestSize.Height() ); + + Fraction aScaleX( aDestSize.Width(), aTmpPrefSize.Width() ); + Fraction aScaleY( aDestSize.Height(), aTmpPrefSize.Height() ); + + aScaleX *= aDrawMap.GetScaleX(); + aScaleY *= aDrawMap.GetScaleY(); + // try reducing inaccurary first and abandon if the scaling + // still cannot be achieved + if (TooLargeScaleForMapMode(aScaleX, rOut.GetDPIX())) + aScaleX.ReduceInaccurate(10); + if (TooLargeScaleForMapMode(aScaleY, rOut.GetDPIY())) + aScaleY.ReduceInaccurate(10); + if (TooLargeScaleForMapMode(aScaleX, rOut.GetDPIX()) || + TooLargeScaleForMapMode(aScaleY, rOut.GetDPIY())) + { + SAL_WARN("vcl", "GDIMetaFile Scaling is too high"); + return; + } + + aDrawMap.SetScaleX(aScaleX); + aDrawMap.SetScaleY(aScaleY); + + // #i47260# Convert logical output position to offset within + // the metafile's mapmode. Therefore, disable pixel offset on + // outdev, it's inverse mnOutOffLogicX/Y is calculated for a + // different mapmode (the one currently set on rOut, that is) + // - thus, aDrawMap's origin would generally be wrong. And + // even _if_ aDrawMap is similar to pOutDev's current mapmode, + // it's _still_ undesirable to have pixel offset unequal zero, + // because one would still get round-off errors (the + // round-trip error for LogicToPixel( PixelToLogic() ) was the + // reason for having pixel offset in the first place). + const Size& rOldOffset(rOut.GetPixelOffset()); + const Size aEmptySize; + rOut.SetPixelOffset(aEmptySize); + aDrawMap.SetOrigin(rOut.PixelToLogic(rOut.LogicToPixel(rPos), aDrawMap)); + rOut.SetPixelOffset(rOldOffset); + + rOut.Push(); + + bool bIsRecord = (pMtf && pMtf->IsRecord()); + rOut.SetMetafileMapMode(aDrawMap, bIsRecord); + + // #i23407# Set backwards-compatible text language and layout mode + // This is necessary, since old metafiles don't even know of these + // recent add-ons. Newer metafiles must of course explicitly set + // those states. + rOut.SetLayoutMode(vcl::text::ComplexTextLayoutFlags::Default); + rOut.SetDigitLanguage(LANGUAGE_SYSTEM); + + Play(rOut); + + rOut.Pop(); +} + +void GDIMetaFile::Pause( bool _bPause ) +{ + if( !m_bRecord ) + return; + + if( _bPause ) + { + if( !m_bPause ) + Linker( m_pOutDev, false ); + } + else + { + if( m_bPause ) + Linker( m_pOutDev, true ); + } + + m_bPause = _bPause; +} + +void GDIMetaFile::Stop() +{ + if( m_bRecord ) + { + m_bRecord = false; + + if( !m_bPause ) + Linker( m_pOutDev, false ); + else + m_bPause = false; + } +} + +void GDIMetaFile::WindStart() +{ + if( !m_bRecord ) + m_nCurrentActionElement = 0; +} + +void GDIMetaFile::WindPrev() +{ + if( !m_bRecord ) + if ( m_nCurrentActionElement > 0 ) + --m_nCurrentActionElement; +} + +void GDIMetaFile::AddAction(const rtl::Reference<MetaAction>& pAction) +{ + m_aList.push_back( pAction ); + + if( m_pPrev ) + { + m_pPrev->AddAction( pAction ); + } +} + +void GDIMetaFile::AddAction(const rtl::Reference<MetaAction>& pAction, size_t nPos) +{ + if ( nPos < m_aList.size() ) + { + m_aList.insert( m_aList.begin() + nPos, pAction ); + } + else + { + m_aList.push_back( pAction ); + } + + if( m_pPrev ) + { + m_pPrev->AddAction( pAction, nPos ); + } +} + +void GDIMetaFile::push_back(const rtl::Reference<MetaAction>& pAction) +{ + m_aList.push_back( pAction ); +} + +void GDIMetaFile::Mirror( BmpMirrorFlags nMirrorFlags ) +{ + const Size aOldPrefSize( GetPrefSize() ); + tools::Long nMoveX, nMoveY; + double fScaleX, fScaleY; + + if( nMirrorFlags & BmpMirrorFlags::Horizontal ) + { + nMoveX = std::abs( aOldPrefSize.Width() ) - 1; + fScaleX = -1.0; + } + else + { + nMoveX = 0; + fScaleX = 1.0; + } + + if( nMirrorFlags & BmpMirrorFlags::Vertical ) + { + nMoveY = std::abs( aOldPrefSize.Height() ) - 1; + fScaleY = -1.0; + } + else + { + nMoveY = 0; + fScaleY = 1.0; + } + + if( ( fScaleX != 1.0 ) || ( fScaleY != 1.0 ) ) + { + Scale( fScaleX, fScaleY ); + Move( nMoveX, nMoveY ); + SetPrefSize( aOldPrefSize ); + } +} + +void GDIMetaFile::Move( tools::Long nX, tools::Long nY ) +{ + const Size aBaseOffset( nX, nY ); + Size aOffset( aBaseOffset ); + ScopedVclPtrInstance< VirtualDevice > aMapVDev; + + aMapVDev->EnableOutput( false ); + aMapVDev->SetMapMode( GetPrefMapMode() ); + + for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() ) + { + const MetaActionType nType = pAct->GetType(); + MetaAction* pModAct; + + if( pAct->GetRefCount() > 1 ) + { + m_aList[ m_nCurrentActionElement ] = pAct->Clone(); + pModAct = m_aList[ m_nCurrentActionElement ].get(); + } + else + pModAct = pAct; + + if( ( MetaActionType::MAPMODE == nType ) || + ( MetaActionType::PUSH == nType ) || + ( MetaActionType::POP == nType ) ) + { + pModAct->Execute( aMapVDev.get() ); + aOffset = OutputDevice::LogicToLogic( aBaseOffset, GetPrefMapMode(), aMapVDev->GetMapMode() ); + } + + pModAct->Move( aOffset.Width(), aOffset.Height() ); + } +} + +void GDIMetaFile::Move( tools::Long nX, tools::Long nY, tools::Long nDPIX, tools::Long nDPIY ) +{ + const Size aBaseOffset( nX, nY ); + Size aOffset( aBaseOffset ); + ScopedVclPtrInstance< VirtualDevice > aMapVDev; + + aMapVDev->EnableOutput( false ); + aMapVDev->SetReferenceDevice( nDPIX, nDPIY ); + aMapVDev->SetMapMode( GetPrefMapMode() ); + + for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() ) + { + const MetaActionType nType = pAct->GetType(); + MetaAction* pModAct; + + if( pAct->GetRefCount() > 1 ) + { + m_aList[ m_nCurrentActionElement ] = pAct->Clone(); + pModAct = m_aList[ m_nCurrentActionElement ].get(); + } + else + pModAct = pAct; + + if( ( MetaActionType::MAPMODE == nType ) || + ( MetaActionType::PUSH == nType ) || + ( MetaActionType::POP == nType ) ) + { + pModAct->Execute( aMapVDev.get() ); + if( aMapVDev->GetMapMode().GetMapUnit() == MapUnit::MapPixel ) + { + aOffset = aMapVDev->LogicToPixel( aBaseOffset, GetPrefMapMode() ); + MapMode aMap( aMapVDev->GetMapMode() ); + aOffset.setWidth( static_cast<tools::Long>(aOffset.Width() * static_cast<double>(aMap.GetScaleX())) ); + aOffset.setHeight( static_cast<tools::Long>(aOffset.Height() * static_cast<double>(aMap.GetScaleY())) ); + } + else + aOffset = OutputDevice::LogicToLogic( aBaseOffset, GetPrefMapMode(), aMapVDev->GetMapMode() ); + } + + pModAct->Move( aOffset.Width(), aOffset.Height() ); + } +} + +void GDIMetaFile::ScaleActions(double const fScaleX, double const fScaleY) +{ + for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() ) + { + MetaAction* pModAct; + + if( pAct->GetRefCount() > 1 ) + { + m_aList[ m_nCurrentActionElement ] = pAct->Clone(); + pModAct = m_aList[ m_nCurrentActionElement ].get(); + } + else + pModAct = pAct; + + pModAct->Scale( fScaleX, fScaleY ); + } +} + +void GDIMetaFile::Scale( double fScaleX, double fScaleY ) +{ + ScaleActions(fScaleX, fScaleY); + + m_aPrefSize.setWidth( FRound( m_aPrefSize.Width() * fScaleX ) ); + m_aPrefSize.setHeight( FRound( m_aPrefSize.Height() * fScaleY ) ); +} + +void GDIMetaFile::Scale( const Fraction& rScaleX, const Fraction& rScaleY ) +{ + Scale( static_cast<double>(rScaleX), static_cast<double>(rScaleY) ); +} + +void GDIMetaFile::Clip( const tools::Rectangle& i_rClipRect ) +{ + tools::Rectangle aCurRect( i_rClipRect ); + ScopedVclPtrInstance< VirtualDevice > aMapVDev; + + aMapVDev->EnableOutput( false ); + aMapVDev->SetMapMode( GetPrefMapMode() ); + + for( MetaAction* pAct = FirstAction(); pAct; pAct = NextAction() ) + { + const MetaActionType nType = pAct->GetType(); + + if( ( MetaActionType::MAPMODE == nType ) || + ( MetaActionType::PUSH == nType ) || + ( MetaActionType::POP == nType ) ) + { + pAct->Execute( aMapVDev.get() ); + aCurRect = OutputDevice::LogicToLogic( i_rClipRect, GetPrefMapMode(), aMapVDev->GetMapMode() ); + } + else if( nType == MetaActionType::CLIPREGION ) + { + MetaClipRegionAction* pOldAct = static_cast<MetaClipRegionAction*>(pAct); + vcl::Region aNewReg( aCurRect ); + if( pOldAct->IsClipping() ) + aNewReg.Intersect( pOldAct->GetRegion() ); + MetaClipRegionAction* pNewAct = new MetaClipRegionAction( std::move(aNewReg), true ); + m_aList[ m_nCurrentActionElement ] = pNewAct; + } + } +} + +Point GDIMetaFile::ImplGetRotatedPoint( const Point& rPt, const Point& rRotatePt, + const Size& rOffset, double fSin, double fCos ) +{ + const tools::Long nX = rPt.X() - rRotatePt.X(); + const tools::Long nY = rPt.Y() - rRotatePt.Y(); + + return Point( FRound( fCos * nX + fSin * nY ) + rRotatePt.X() + rOffset.Width(), + -FRound( fSin * nX - fCos * nY ) + rRotatePt.Y() + rOffset.Height() ); +} + +tools::Polygon GDIMetaFile::ImplGetRotatedPolygon( const tools::Polygon& rPoly, const Point& rRotatePt, + const Size& rOffset, double fSin, double fCos ) +{ + tools::Polygon aRet( rPoly ); + + aRet.Rotate( rRotatePt, fSin, fCos ); + aRet.Move( rOffset.Width(), rOffset.Height() ); + + return aRet; +} + +tools::PolyPolygon GDIMetaFile::ImplGetRotatedPolyPolygon( const tools::PolyPolygon& rPolyPoly, const Point& rRotatePt, + const Size& rOffset, double fSin, double fCos ) +{ + tools::PolyPolygon aRet( rPolyPoly ); + + aRet.Rotate( rRotatePt, fSin, fCos ); + aRet.Move( rOffset.Width(), rOffset.Height() ); + + return aRet; +} + +void GDIMetaFile::ImplAddGradientEx( GDIMetaFile& rMtf, + const OutputDevice& rMapDev, + const tools::PolyPolygon& rPolyPoly, + const Gradient& rGrad ) +{ + // Generate comment, GradientEx and Gradient actions (within DrawGradient) + ScopedVclPtrInstance< VirtualDevice > aVDev(rMapDev, DeviceFormat::WITHOUT_ALPHA); + aVDev->EnableOutput( false ); + GDIMetaFile aGradMtf; + + aGradMtf.Record( aVDev.get() ); + aVDev->DrawGradient( rPolyPoly, rGrad ); + aGradMtf.Stop(); + + size_t i, nAct( aGradMtf.GetActionSize() ); + for( i=0; i < nAct; ++i ) + { + MetaAction* pMetaAct = aGradMtf.GetAction( i ); + rMtf.AddAction( pMetaAct ); + } +} + +void GDIMetaFile::Rotate( Degree10 nAngle10 ) +{ + nAngle10 %= 3600_deg10; + nAngle10 = ( nAngle10 < 0_deg10 ) ? ( Degree10(3599) + nAngle10 ) : nAngle10; + + if( !nAngle10 ) + return; + + GDIMetaFile aMtf; + ScopedVclPtrInstance< VirtualDevice > aMapVDev; + const double fAngle = toRadians(nAngle10); + const double fSin = sin( fAngle ); + const double fCos = cos( fAngle ); + tools::Rectangle aRect( Point(), GetPrefSize() ); + tools::Polygon aPoly( aRect ); + + aPoly.Rotate( Point(), fSin, fCos ); + + aMapVDev->EnableOutput( false ); + aMapVDev->SetMapMode( GetPrefMapMode() ); + + const tools::Rectangle aNewBound( aPoly.GetBoundRect() ); + + const Point aOrigin( GetPrefMapMode().GetOrigin().X(), GetPrefMapMode().GetOrigin().Y() ); + const Size aOffset( -aNewBound.Left(), -aNewBound.Top() ); + + Point aRotAnchor( aOrigin ); + Size aRotOffset( aOffset ); + + for( MetaAction* pAction = FirstAction(); pAction; pAction = NextAction() ) + { + const MetaActionType nActionType = pAction->GetType(); + + switch( nActionType ) + { + case MetaActionType::PIXEL: + { + MetaPixelAction* pAct = static_cast<MetaPixelAction*>(pAction); + aMtf.AddAction( new MetaPixelAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetColor() ) ); + } + break; + + case MetaActionType::POINT: + { + MetaPointAction* pAct = static_cast<MetaPointAction*>(pAction); + aMtf.AddAction( new MetaPointAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::LINE: + { + MetaLineAction* pAct = static_cast<MetaLineAction*>(pAction); + aMtf.AddAction( new MetaLineAction( ImplGetRotatedPoint( pAct->GetStartPoint(), aRotAnchor, aRotOffset, fSin, fCos ), + ImplGetRotatedPoint( pAct->GetEndPoint(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetLineInfo() ) ); + } + break; + + case MetaActionType::RECT: + { + MetaRectAction* pAct = static_cast<MetaRectAction*>(pAction); + aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( tools::Polygon(pAct->GetRect()), aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::ROUNDRECT: + { + MetaRoundRectAction* pAct = static_cast<MetaRoundRectAction*>(pAction); + const tools::Polygon aRoundRectPoly( pAct->GetRect(), pAct->GetHorzRound(), pAct->GetVertRound() ); + + aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aRoundRectPoly, aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::ELLIPSE: + { + MetaEllipseAction* pAct = static_cast<MetaEllipseAction*>(pAction); + const tools::Polygon aEllipsePoly( pAct->GetRect().Center(), pAct->GetRect().GetWidth() >> 1, pAct->GetRect().GetHeight() >> 1 ); + + aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aEllipsePoly, aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::ARC: + { + MetaArcAction* pAct = static_cast<MetaArcAction*>(pAction); + const tools::Polygon aArcPoly( pAct->GetRect(), pAct->GetStartPoint(), pAct->GetEndPoint(), PolyStyle::Arc ); + + aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aArcPoly, aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::PIE: + { + MetaPieAction* pAct = static_cast<MetaPieAction*>(pAction); + const tools::Polygon aPiePoly( pAct->GetRect(), pAct->GetStartPoint(), pAct->GetEndPoint(), PolyStyle::Pie ); + + aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aPiePoly, aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::CHORD: + { + MetaChordAction* pAct = static_cast<MetaChordAction*>(pAction); + const tools::Polygon aChordPoly( pAct->GetRect(), pAct->GetStartPoint(), pAct->GetEndPoint(), PolyStyle::Chord ); + + aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( aChordPoly, aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::POLYLINE: + { + MetaPolyLineAction* pAct = static_cast<MetaPolyLineAction*>(pAction); + aMtf.AddAction( new MetaPolyLineAction( ImplGetRotatedPolygon( pAct->GetPolygon(), aRotAnchor, aRotOffset, fSin, fCos ), pAct->GetLineInfo() ) ); + } + break; + + case MetaActionType::POLYGON: + { + MetaPolygonAction* pAct = static_cast<MetaPolygonAction*>(pAction); + aMtf.AddAction( new MetaPolygonAction( ImplGetRotatedPolygon( pAct->GetPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::POLYPOLYGON: + { + MetaPolyPolygonAction* pAct = static_cast<MetaPolyPolygonAction*>(pAction); + aMtf.AddAction( new MetaPolyPolygonAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ) ); + } + break; + + case MetaActionType::TEXT: + { + MetaTextAction* pAct = static_cast<MetaTextAction*>(pAction); + aMtf.AddAction( new MetaTextAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetText(), pAct->GetIndex(), pAct->GetLen() ) ); + } + break; + + case MetaActionType::TEXTARRAY: + { + MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pAction); + aMtf.AddAction( new MetaTextArrayAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetText(), pAct->GetDXArray(), pAct->GetKashidaArray(), pAct->GetIndex(), pAct->GetLen() ) ); + } + break; + + case MetaActionType::STRETCHTEXT: + { + MetaStretchTextAction* pAct = static_cast<MetaStretchTextAction*>(pAction); + aMtf.AddAction( new MetaStretchTextAction( ImplGetRotatedPoint( pAct->GetPoint(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetWidth(), pAct->GetText(), pAct->GetIndex(), pAct->GetLen() ) ); + } + break; + + case MetaActionType::TEXTLINE: + { + MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pAction); + aMtf.AddAction( new MetaTextLineAction( ImplGetRotatedPoint( pAct->GetStartPoint(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetWidth(), pAct->GetStrikeout(), pAct->GetUnderline(), pAct->GetOverline() ) ); + } + break; + + case MetaActionType::BMPSCALE: + { + MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction); + tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetPoint(), pAct->GetSize() )), aRotAnchor, aRotOffset, fSin, fCos ) ); + tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() ); + BitmapEx aBmpEx( pAct->GetBitmap() ); + + aBmpEx.Rotate( nAngle10, COL_TRANSPARENT ); + aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), + aBmpEx ) ); + } + break; + + case MetaActionType::BMPSCALEPART: + { + MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction); + tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetDestPoint(), pAct->GetDestSize() )), aRotAnchor, aRotOffset, fSin, fCos ) ); + tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() ); + BitmapEx aBmpEx( pAct->GetBitmap() ); + + aBmpEx.Crop( tools::Rectangle( pAct->GetSrcPoint(), pAct->GetSrcSize() ) ); + aBmpEx.Rotate( nAngle10, COL_TRANSPARENT ); + + aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), aBmpEx ) ); + } + break; + + case MetaActionType::BMPEXSCALE: + { + MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction); + tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetPoint(), pAct->GetSize() )), aRotAnchor, aRotOffset, fSin, fCos ) ); + tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() ); + BitmapEx aBmpEx( pAct->GetBitmapEx() ); + + aBmpEx.Rotate( nAngle10, COL_TRANSPARENT ); + + aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), aBmpEx ) ); + } + break; + + case MetaActionType::BMPEXSCALEPART: + { + MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction); + tools::Polygon aBmpPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetDestPoint(), pAct->GetDestSize() )), aRotAnchor, aRotOffset, fSin, fCos ) ); + tools::Rectangle aBmpRect( aBmpPoly.GetBoundRect() ); + BitmapEx aBmpEx( pAct->GetBitmapEx() ); + + aBmpEx.Crop( tools::Rectangle( pAct->GetSrcPoint(), pAct->GetSrcSize() ) ); + aBmpEx.Rotate( nAngle10, COL_TRANSPARENT ); + + aMtf.AddAction( new MetaBmpExScaleAction( aBmpRect.TopLeft(), aBmpRect.GetSize(), aBmpEx ) ); + } + break; + + case MetaActionType::GRADIENT: + { + MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction); + + ImplAddGradientEx( aMtf, *aMapVDev, + tools::PolyPolygon(ImplGetRotatedPolygon( tools::Polygon(pAct->GetRect()), aRotAnchor, aRotOffset, fSin, fCos )), + pAct->GetGradient() ); + } + break; + + case MetaActionType::GRADIENTEX: + { + MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction); + aMtf.AddAction( new MetaGradientExAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetGradient() ) ); + } + break; + + // Handle gradientex comment block correctly + case MetaActionType::COMMENT: + { + MetaCommentAction* pCommentAct = static_cast<MetaCommentAction*>(pAction); + if( pCommentAct->GetComment() == "XGRAD_SEQ_BEGIN" ) + { + int nBeginComments( 1 ); + pAction = NextAction(); + + // skip everything, except gradientex action + while( pAction ) + { + const MetaActionType nType = pAction->GetType(); + + if( MetaActionType::GRADIENTEX == nType ) + { + // Add rotated gradientex + MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction); + ImplAddGradientEx( aMtf, *aMapVDev, + ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetGradient() ); + } + else if( MetaActionType::COMMENT == nType) + { + MetaCommentAction* pAct = static_cast<MetaCommentAction*>(pAction); + if( pAct->GetComment() == "XGRAD_SEQ_END" ) + { + // handle nested blocks + --nBeginComments; + + // gradientex comment block: end reached, done. + if( !nBeginComments ) + break; + } + else if( pAct->GetComment() == "XGRAD_SEQ_BEGIN" ) + { + // handle nested blocks + ++nBeginComments; + } + + } + + pAction =NextAction(); + } + } + else + { + bool bPathStroke = (pCommentAct->GetComment() == "XPATHSTROKE_SEQ_BEGIN"); + if ( bPathStroke || pCommentAct->GetComment() == "XPATHFILL_SEQ_BEGIN" ) + { + if ( pCommentAct->GetDataSize() ) + { + SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pCommentAct->GetData()), pCommentAct->GetDataSize(), StreamMode::READ ); + SvMemoryStream aDest; + if ( bPathStroke ) + { + SvtGraphicStroke aStroke; + ReadSvtGraphicStroke( aMemStm, aStroke ); + tools::Polygon aPath; + aStroke.getPath( aPath ); + aStroke.setPath( ImplGetRotatedPolygon( aPath, aRotAnchor, aRotOffset, fSin, fCos ) ); + WriteSvtGraphicStroke( aDest, aStroke ); + aMtf.AddAction( new MetaCommentAction( "XPATHSTROKE_SEQ_BEGIN"_ostr, 0, + static_cast<const sal_uInt8*>( aDest.GetData()), aDest.Tell() ) ); + } + else + { + SvtGraphicFill aFill; + ReadSvtGraphicFill( aMemStm, aFill ); + tools::PolyPolygon aPath; + aFill.getPath( aPath ); + aFill.setPath( ImplGetRotatedPolyPolygon( aPath, aRotAnchor, aRotOffset, fSin, fCos ) ); + WriteSvtGraphicFill( aDest, aFill ); + aMtf.AddAction( new MetaCommentAction( "XPATHFILL_SEQ_BEGIN"_ostr, 0, + static_cast<const sal_uInt8*>( aDest.GetData()), aDest.Tell() ) ); + } + } + } + else if ( pCommentAct->GetComment() == "XPATHSTROKE_SEQ_END" + || pCommentAct->GetComment() == "XPATHFILL_SEQ_END" ) + { + pAction->Execute( aMapVDev.get() ); + aMtf.AddAction( pAction ); + } + } + } + break; + + case MetaActionType::HATCH: + { + MetaHatchAction* pAct = static_cast<MetaHatchAction*>(pAction); + Hatch aHatch( pAct->GetHatch() ); + + aHatch.SetAngle( aHatch.GetAngle() + nAngle10 ); + aMtf.AddAction( new MetaHatchAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ), + aHatch ) ); + } + break; + + case MetaActionType::Transparent: + { + MetaTransparentAction* pAct = static_cast<MetaTransparentAction*>(pAction); + aMtf.AddAction( new MetaTransparentAction( ImplGetRotatedPolyPolygon( pAct->GetPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ), + pAct->GetTransparence() ) ); + } + break; + + case MetaActionType::FLOATTRANSPARENT: + { + MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pAction); + GDIMetaFile aTransMtf( pAct->GetGDIMetaFile() ); + tools::Polygon aMtfPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetPoint(), pAct->GetSize() )), aRotAnchor, aRotOffset, fSin, fCos ) ); + tools::Rectangle aMtfRect( aMtfPoly.GetBoundRect() ); + + aTransMtf.Rotate( nAngle10 ); + aMtf.AddAction( new MetaFloatTransparentAction( aTransMtf, aMtfRect.TopLeft(), aMtfRect.GetSize(), + pAct->GetGradient() ) ); + } + break; + + case MetaActionType::EPS: + { + MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction); + GDIMetaFile aEPSMtf( pAct->GetSubstitute() ); + tools::Polygon aEPSPoly( ImplGetRotatedPolygon( tools::Polygon(tools::Rectangle( pAct->GetPoint(), pAct->GetSize() )), aRotAnchor, aRotOffset, fSin, fCos ) ); + tools::Rectangle aEPSRect( aEPSPoly.GetBoundRect() ); + + aEPSMtf.Rotate( nAngle10 ); + aMtf.AddAction( new MetaEPSAction( aEPSRect.TopLeft(), aEPSRect.GetSize(), + pAct->GetLink(), aEPSMtf ) ); + } + break; + + case MetaActionType::CLIPREGION: + { + MetaClipRegionAction* pAct = static_cast<MetaClipRegionAction*>(pAction); + + if( pAct->IsClipping() && pAct->GetRegion().HasPolyPolygonOrB2DPolyPolygon() ) + aMtf.AddAction( new MetaClipRegionAction( vcl::Region( ImplGetRotatedPolyPolygon( pAct->GetRegion().GetAsPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ), true ) ); + else + { + aMtf.AddAction( pAction ); + } + } + break; + + case MetaActionType::ISECTRECTCLIPREGION: + { + MetaISectRectClipRegionAction* pAct = static_cast<MetaISectRectClipRegionAction*>(pAction); + aMtf.AddAction( new MetaISectRegionClipRegionAction(vcl::Region( + ImplGetRotatedPolygon( tools::Polygon(pAct->GetRect()), aRotAnchor, + aRotOffset, fSin, fCos )) ) ); + } + break; + + case MetaActionType::ISECTREGIONCLIPREGION: + { + MetaISectRegionClipRegionAction* pAct = static_cast<MetaISectRegionClipRegionAction*>(pAction); + const vcl::Region& rRegion = pAct->GetRegion(); + + if( rRegion.HasPolyPolygonOrB2DPolyPolygon() ) + aMtf.AddAction( new MetaISectRegionClipRegionAction( vcl::Region( ImplGetRotatedPolyPolygon( rRegion.GetAsPolyPolygon(), aRotAnchor, aRotOffset, fSin, fCos ) ) ) ); + else + { + aMtf.AddAction( pAction ); + } + } + break; + + case MetaActionType::REFPOINT: + { + MetaRefPointAction* pAct = static_cast<MetaRefPointAction*>(pAction); + aMtf.AddAction( new MetaRefPointAction( ImplGetRotatedPoint( pAct->GetRefPoint(), aRotAnchor, aRotOffset, fSin, fCos ), pAct->IsSetting() ) ); + } + break; + + case MetaActionType::FONT: + { + MetaFontAction* pAct = static_cast<MetaFontAction*>(pAction); + vcl::Font aFont( pAct->GetFont() ); + + aFont.SetOrientation( aFont.GetOrientation() + nAngle10 ); + aMtf.AddAction( new MetaFontAction( std::move(aFont) ) ); + } + break; + + case MetaActionType::BMP: + case MetaActionType::BMPEX: + case MetaActionType::MASK: + case MetaActionType::MASKSCALE: + case MetaActionType::MASKSCALEPART: + case MetaActionType::WALLPAPER: + case MetaActionType::TEXTRECT: + case MetaActionType::MOVECLIPREGION: + { + OSL_FAIL( "GDIMetaFile::Rotate(): unsupported action" ); + } + break; + + default: + { + pAction->Execute( aMapVDev.get() ); + aMtf.AddAction( pAction ); + + // update rotation point and offset, if necessary + if( ( MetaActionType::MAPMODE == nActionType ) || + ( MetaActionType::PUSH == nActionType ) || + ( MetaActionType::POP == nActionType ) ) + { + aRotAnchor = OutputDevice::LogicToLogic( aOrigin, m_aPrefMapMode, aMapVDev->GetMapMode() ); + aRotOffset = OutputDevice::LogicToLogic( aOffset, m_aPrefMapMode, aMapVDev->GetMapMode() ); + } + } + break; + } + } + + aMtf.m_aPrefMapMode = m_aPrefMapMode; + aMtf.m_aPrefSize = aNewBound.GetSize(); + + *this = aMtf; + +} + +static void ImplActionBounds( tools::Rectangle& o_rOutBounds, + const tools::Rectangle& i_rInBounds, + const std::vector<tools::Rectangle>& i_rClipStack ) +{ + tools::Rectangle aBounds( i_rInBounds ); + if( ! i_rInBounds.IsEmpty() && ! i_rClipStack.empty() && ! i_rClipStack.back().IsEmpty() ) + aBounds.Intersection( i_rClipStack.back() ); + if( aBounds.IsEmpty() ) + return; + + if( ! o_rOutBounds.IsEmpty() ) + o_rOutBounds.Union( aBounds ); + else + o_rOutBounds = aBounds; +} + +tools::Rectangle GDIMetaFile::GetBoundRect( OutputDevice& i_rReference ) const +{ + ScopedVclPtrInstance< VirtualDevice > aMapVDev( i_rReference ); + + aMapVDev->EnableOutput( false ); + aMapVDev->SetMapMode( GetPrefMapMode() ); + + std::vector<tools::Rectangle> aClipStack( 1, tools::Rectangle() ); + std::vector<vcl::PushFlags> aPushFlagStack; + + tools::Rectangle aBound; + const sal_uLong nCount(GetActionSize()); + + for(sal_uLong a(0); a < nCount; a++) + { + MetaAction* pAction = GetAction(a); + const MetaActionType nActionType = pAction->GetType(); + + switch( nActionType ) + { + case MetaActionType::PIXEL: + { + MetaPixelAction* pAct = static_cast<MetaPixelAction*>(pAction); + ImplActionBounds( aBound, + tools::Rectangle( OutputDevice::LogicToLogic( pAct->GetPoint(), aMapVDev->GetMapMode(), GetPrefMapMode() ), + aMapVDev->PixelToLogic( Size( 1, 1 ), GetPrefMapMode() ) ), + aClipStack ); + } + break; + + case MetaActionType::POINT: + { + MetaPointAction* pAct = static_cast<MetaPointAction*>(pAction); + ImplActionBounds( aBound, + tools::Rectangle( OutputDevice::LogicToLogic( pAct->GetPoint(), aMapVDev->GetMapMode(), GetPrefMapMode() ), + aMapVDev->PixelToLogic( Size( 1, 1 ), GetPrefMapMode() ) ), + aClipStack ); + } + break; + + case MetaActionType::LINE: + { + MetaLineAction* pAct = static_cast<MetaLineAction*>(pAction); + Point aP1( pAct->GetStartPoint() ), aP2( pAct->GetEndPoint() ); + tools::Rectangle aRect( aP1, aP2 ); + aRect.Normalize(); + + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::RECT: + { + MetaRectAction* pAct = static_cast<MetaRectAction*>(pAction); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::ROUNDRECT: + { + MetaRoundRectAction* pAct = static_cast<MetaRoundRectAction*>(pAction); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::ELLIPSE: + { + MetaEllipseAction* pAct = static_cast<MetaEllipseAction*>(pAction); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::ARC: + { + MetaArcAction* pAct = static_cast<MetaArcAction*>(pAction); + // FIXME: this is imprecise + // e.g. for small arcs the whole rectangle is WAY too large + ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::PIE: + { + MetaPieAction* pAct = static_cast<MetaPieAction*>(pAction); + // FIXME: this is imprecise + // e.g. for small arcs the whole rectangle is WAY too large + ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::CHORD: + { + MetaChordAction* pAct = static_cast<MetaChordAction*>(pAction); + // FIXME: this is imprecise + // e.g. for small arcs the whole rectangle is WAY too large + ImplActionBounds( aBound, OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::POLYLINE: + { + MetaPolyLineAction* pAct = static_cast<MetaPolyLineAction*>(pAction); + tools::Rectangle aRect( pAct->GetPolygon().GetBoundRect() ); + + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::POLYGON: + { + MetaPolygonAction* pAct = static_cast<MetaPolygonAction*>(pAction); + tools::Rectangle aRect( pAct->GetPolygon().GetBoundRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::POLYPOLYGON: + { + MetaPolyPolygonAction* pAct = static_cast<MetaPolyPolygonAction*>(pAction); + tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::TEXT: + { + MetaTextAction* pAct = static_cast<MetaTextAction*>(pAction); + tools::Rectangle aRect; + // hdu said base = index + aMapVDev->GetTextBoundRect( aRect, pAct->GetText(), pAct->GetIndex(), pAct->GetIndex(), pAct->GetLen() ); + Point aPt( pAct->GetPoint() ); + aRect.Move( aPt.X(), aPt.Y() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::TEXTARRAY: + { + MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pAction); + tools::Rectangle aRect; + // hdu said base = index + aMapVDev->GetTextBoundRect( aRect, pAct->GetText(), pAct->GetIndex(), pAct->GetIndex(), pAct->GetLen(), + 0, pAct->GetDXArray(), pAct->GetKashidaArray() ); + Point aPt( pAct->GetPoint() ); + aRect.Move( aPt.X(), aPt.Y() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::STRETCHTEXT: + { + MetaStretchTextAction* pAct = static_cast<MetaStretchTextAction*>(pAction); + tools::Rectangle aRect; + // hdu said base = index + aMapVDev->GetTextBoundRect( aRect, pAct->GetText(), pAct->GetIndex(), pAct->GetIndex(), pAct->GetLen(), + pAct->GetWidth() ); + Point aPt( pAct->GetPoint() ); + aRect.Move( aPt.X(), aPt.Y() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::TEXTLINE: + { + MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pAction); + // measure a test string to get ascend and descent right + static constexpr OUStringLiteral pStr = u"\u00c4g"; + OUString aStr( pStr ); + + tools::Rectangle aRect; + aMapVDev->GetTextBoundRect( aRect, aStr, 0, 0, aStr.getLength() ); + Point aPt( pAct->GetStartPoint() ); + aRect.Move( aPt.X(), aPt.Y() ); + aRect.SetRight( aRect.Left() + pAct->GetWidth() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::BMPSCALE: + { + MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction); + tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::BMPSCALEPART: + { + MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction); + tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::BMPEXSCALE: + { + MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction); + tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::BMPEXSCALEPART: + { + MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction); + tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::GRADIENT: + { + MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction); + tools::Rectangle aRect( pAct->GetRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::GRADIENTEX: + { + MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction); + tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::COMMENT: + { + // nothing to do + }; + break; + + case MetaActionType::HATCH: + { + MetaHatchAction* pAct = static_cast<MetaHatchAction*>(pAction); + tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::Transparent: + { + MetaTransparentAction* pAct = static_cast<MetaTransparentAction*>(pAction); + tools::Rectangle aRect( pAct->GetPolyPolygon().GetBoundRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::FLOATTRANSPARENT: + { + MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pAction); + // MetaFloatTransparentAction is defined limiting its content Metafile + // to its geometry definition(Point, Size), so use these directly + const tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::EPS: + { + MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction); + tools::Rectangle aRect( pAct->GetPoint(), pAct->GetSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::CLIPREGION: + { + MetaClipRegionAction* pAct = static_cast<MetaClipRegionAction*>(pAction); + if( pAct->IsClipping() ) + aClipStack.back() = OutputDevice::LogicToLogic( pAct->GetRegion().GetBoundRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ); + else + aClipStack.back() = tools::Rectangle(); + } + break; + + case MetaActionType::ISECTRECTCLIPREGION: + { + MetaISectRectClipRegionAction* pAct = static_cast<MetaISectRectClipRegionAction*>(pAction); + tools::Rectangle aRect( OutputDevice::LogicToLogic( pAct->GetRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ) ); + if( aClipStack.back().IsEmpty() ) + aClipStack.back() = aRect; + else + aClipStack.back().Intersection( aRect ); + } + break; + + case MetaActionType::ISECTREGIONCLIPREGION: + { + MetaISectRegionClipRegionAction* pAct = static_cast<MetaISectRegionClipRegionAction*>(pAction); + tools::Rectangle aRect( OutputDevice::LogicToLogic( pAct->GetRegion().GetBoundRect(), aMapVDev->GetMapMode(), GetPrefMapMode() ) ); + if( aClipStack.back().IsEmpty() ) + aClipStack.back() = aRect; + else + aClipStack.back().Intersection( aRect ); + } + break; + + case MetaActionType::BMP: + { + MetaBmpAction* pAct = static_cast<MetaBmpAction*>(pAction); + tools::Rectangle aRect( pAct->GetPoint(), aMapVDev->PixelToLogic( pAct->GetBitmap().GetSizePixel() ) ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::BMPEX: + { + MetaBmpExAction* pAct = static_cast<MetaBmpExAction*>(pAction); + tools::Rectangle aRect( pAct->GetPoint(), aMapVDev->PixelToLogic( pAct->GetBitmapEx().GetSizePixel() ) ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::MASK: + { + MetaMaskAction* pAct = static_cast<MetaMaskAction*>(pAction); + tools::Rectangle aRect( pAct->GetPoint(), aMapVDev->PixelToLogic( pAct->GetBitmap().GetSizePixel() ) ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::MASKSCALE: + { + MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction); + tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::MASKSCALEPART: + { + MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction); + tools::Rectangle aRect( pAct->GetDestPoint(), pAct->GetDestSize() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::WALLPAPER: + { + MetaWallpaperAction* pAct = static_cast<MetaWallpaperAction*>(pAction); + tools::Rectangle aRect( pAct->GetRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::TEXTRECT: + { + MetaTextRectAction* pAct = static_cast<MetaTextRectAction*>(pAction); + tools::Rectangle aRect( pAct->GetRect() ); + ImplActionBounds( aBound, OutputDevice::LogicToLogic( aRect, aMapVDev->GetMapMode(), GetPrefMapMode() ), aClipStack ); + } + break; + + case MetaActionType::MOVECLIPREGION: + { + MetaMoveClipRegionAction* pAct = static_cast<MetaMoveClipRegionAction*>(pAction); + if( ! aClipStack.back().IsEmpty() ) + { + Size aDelta( pAct->GetHorzMove(), pAct->GetVertMove() ); + aDelta = OutputDevice::LogicToLogic( aDelta, aMapVDev->GetMapMode(), GetPrefMapMode() ); + aClipStack.back().Move( aDelta.Width(), aDelta.Width() ); + } + } + break; + + default: + { + pAction->Execute( aMapVDev.get() ); + + if( nActionType == MetaActionType::PUSH ) + { + MetaPushAction* pAct = static_cast<MetaPushAction*>(pAction); + aPushFlagStack.push_back( pAct->GetFlags() ); + if( aPushFlagStack.back() & vcl::PushFlags::CLIPREGION ) + { + tools::Rectangle aRect( aClipStack.back() ); + aClipStack.push_back( aRect ); + } + } + else if( nActionType == MetaActionType::POP ) + { + // sanity check + if( ! aPushFlagStack.empty() ) + { + if( aPushFlagStack.back() & vcl::PushFlags::CLIPREGION ) + { + if( aClipStack.size() > 1 ) + aClipStack.pop_back(); + } + aPushFlagStack.pop_back(); + } + } + } + break; + } + } + return aBound; +} + +Color GDIMetaFile::ImplColAdjustFnc( const Color& rColor, const void* pColParam ) +{ + return Color( ColorAlpha, rColor.GetAlpha(), + static_cast<const ImplColAdjustParam*>(pColParam)->pMapR[ rColor.GetRed() ], + static_cast<const ImplColAdjustParam*>(pColParam)->pMapG[ rColor.GetGreen() ], + static_cast<const ImplColAdjustParam*>(pColParam)->pMapB[ rColor.GetBlue() ] ); + +} + +BitmapEx GDIMetaFile::ImplBmpAdjustFnc( const BitmapEx& rBmpEx, const void* pBmpParam ) +{ + const ImplBmpAdjustParam* p = static_cast<const ImplBmpAdjustParam*>(pBmpParam); + BitmapEx aRet( rBmpEx ); + + aRet.Adjust( p->nLuminancePercent, p->nContrastPercent, + p->nChannelRPercent, p->nChannelGPercent, p->nChannelBPercent, + p->fGamma, p->bInvert ); + + return aRet; +} + +Color GDIMetaFile::ImplColConvertFnc( const Color& rColor, const void* pColParam ) +{ + sal_uInt8 cLum = rColor.GetLuminance(); + + if( MtfConversion::N1BitThreshold == static_cast<const ImplColConvertParam*>(pColParam)->eConversion ) + cLum = ( cLum < 128 ) ? 0 : 255; + + return Color( ColorAlpha, rColor.GetAlpha(), cLum, cLum, cLum ); +} + +BitmapEx GDIMetaFile::ImplBmpConvertFnc( const BitmapEx& rBmpEx, const void* pBmpParam ) +{ + BitmapEx aRet( rBmpEx ); + + aRet.Convert( static_cast<const ImplBmpConvertParam*>(pBmpParam)->eConversion ); + + return aRet; +} + +Color GDIMetaFile::ImplColMonoFnc( const Color&, const void* pColParam ) +{ + return static_cast<const ImplColMonoParam*>(pColParam)->aColor; +} + +BitmapEx GDIMetaFile::ImplBmpMonoFnc( const BitmapEx& rBmpEx, const void* pBmpParam ) +{ + BitmapPalette aPal( 3 ); + aPal[ 0 ] = COL_BLACK; + aPal[ 1 ] = COL_WHITE; + aPal[ 2 ] = static_cast<const ImplBmpMonoParam*>(pBmpParam)->aColor; + + Bitmap aBmp(rBmpEx.GetSizePixel(), vcl::PixelFormat::N8_BPP, &aPal); + aBmp.Erase( static_cast<const ImplBmpMonoParam*>(pBmpParam)->aColor ); + + if( rBmpEx.IsAlpha() ) + return BitmapEx( aBmp, rBmpEx.GetAlphaMask() ); + else + return BitmapEx( aBmp ); +} + +Color GDIMetaFile::ImplColReplaceFnc( const Color& rColor, const void* pColParam ) +{ + const sal_uLong nR = rColor.GetRed(), nG = rColor.GetGreen(), nB = rColor.GetBlue(); + + for( sal_uLong i = 0; i < static_cast<const ImplColReplaceParam*>(pColParam)->nCount; i++ ) + { + if( ( static_cast<const ImplColReplaceParam*>(pColParam)->pMinR[ i ] <= nR ) && + ( static_cast<const ImplColReplaceParam*>(pColParam)->pMaxR[ i ] >= nR ) && + ( static_cast<const ImplColReplaceParam*>(pColParam)->pMinG[ i ] <= nG ) && + ( static_cast<const ImplColReplaceParam*>(pColParam)->pMaxG[ i ] >= nG ) && + ( static_cast<const ImplColReplaceParam*>(pColParam)->pMinB[ i ] <= nB ) && + ( static_cast<const ImplColReplaceParam*>(pColParam)->pMaxB[ i ] >= nB ) ) + { + return static_cast<const ImplColReplaceParam*>(pColParam)->pDstCols[ i ]; + } + } + + return rColor; +} + +BitmapEx GDIMetaFile::ImplBmpReplaceFnc( const BitmapEx& rBmpEx, const void* pBmpParam ) +{ + const ImplBmpReplaceParam* p = static_cast<const ImplBmpReplaceParam*>(pBmpParam); + BitmapEx aRet( rBmpEx ); + + aRet.Replace( p->pSrcCols, p->pDstCols, p->nCount ); + + return aRet; +} + +void GDIMetaFile::ImplExchangeColors( ColorExchangeFnc pFncCol, const void* pColParam, + BmpExchangeFnc pFncBmp, const void* pBmpParam ) +{ + GDIMetaFile aMtf; + + aMtf.m_aPrefSize = m_aPrefSize; + aMtf.m_aPrefMapMode = m_aPrefMapMode; + aMtf.m_bUseCanvas = m_bUseCanvas; + aMtf.m_bSVG = m_bSVG; + + for( MetaAction* pAction = FirstAction(); pAction; pAction = NextAction() ) + { + const MetaActionType nType = pAction->GetType(); + + switch( nType ) + { + case MetaActionType::PIXEL: + { + MetaPixelAction* pAct = static_cast<MetaPixelAction*>(pAction); + aMtf.push_back( new MetaPixelAction( pAct->GetPoint(), pFncCol( pAct->GetColor(), pColParam ) ) ); + } + break; + + case MetaActionType::LINECOLOR: + { + MetaLineColorAction* pAct = static_cast<MetaLineColorAction*>(pAction); + + if( pAct->IsSetting() ) + pAct = new MetaLineColorAction( pFncCol( pAct->GetColor(), pColParam ), true ); + + aMtf.push_back( pAct ); + } + break; + + case MetaActionType::FILLCOLOR: + { + MetaFillColorAction* pAct = static_cast<MetaFillColorAction*>(pAction); + + if( pAct->IsSetting() ) + pAct = new MetaFillColorAction( pFncCol( pAct->GetColor(), pColParam ), true ); + + aMtf.push_back( pAct ); + } + break; + + case MetaActionType::TEXTCOLOR: + { + MetaTextColorAction* pAct = static_cast<MetaTextColorAction*>(pAction); + aMtf.push_back( new MetaTextColorAction( pFncCol( pAct->GetColor(), pColParam ) ) ); + } + break; + + case MetaActionType::TEXTFILLCOLOR: + { + MetaTextFillColorAction* pAct = static_cast<MetaTextFillColorAction*>(pAction); + + if( pAct->IsSetting() ) + pAct = new MetaTextFillColorAction( pFncCol( pAct->GetColor(), pColParam ), true ); + + aMtf.push_back( pAct ); + } + break; + + case MetaActionType::TEXTLINECOLOR: + { + MetaTextLineColorAction* pAct = static_cast<MetaTextLineColorAction*>(pAction); + + if( pAct->IsSetting() ) + pAct = new MetaTextLineColorAction( pFncCol( pAct->GetColor(), pColParam ), true ); + + aMtf.push_back( pAct ); + } + break; + + case MetaActionType::OVERLINECOLOR: + { + MetaOverlineColorAction* pAct = static_cast<MetaOverlineColorAction*>(pAction); + + if( pAct->IsSetting() ) + pAct = new MetaOverlineColorAction( pFncCol( pAct->GetColor(), pColParam ), true ); + + aMtf.push_back( pAct ); + } + break; + + case MetaActionType::FONT: + { + MetaFontAction* pAct = static_cast<MetaFontAction*>(pAction); + vcl::Font aFont( pAct->GetFont() ); + + aFont.SetColor( pFncCol( aFont.GetColor(), pColParam ) ); + aFont.SetFillColor( pFncCol( aFont.GetFillColor(), pColParam ) ); + aMtf.push_back( new MetaFontAction( std::move(aFont) ) ); + } + break; + + case MetaActionType::WALLPAPER: + { + MetaWallpaperAction* pAct = static_cast<MetaWallpaperAction*>(pAction); + Wallpaper aWall( pAct->GetWallpaper() ); + const tools::Rectangle& rRect = pAct->GetRect(); + + aWall.SetColor( pFncCol( aWall.GetColor(), pColParam ) ); + + if( aWall.IsBitmap() ) + aWall.SetBitmap( pFncBmp( aWall.GetBitmap(), pBmpParam ) ); + + if( aWall.IsGradient() ) + { + Gradient aGradient( aWall.GetGradient() ); + + aGradient.SetStartColor( pFncCol( aGradient.GetStartColor(), pColParam ) ); + aGradient.SetEndColor( pFncCol( aGradient.GetEndColor(), pColParam ) ); + aWall.SetGradient( aGradient ); + } + + aMtf.push_back( new MetaWallpaperAction( rRect, std::move(aWall) ) ); + } + break; + + case MetaActionType::BMP: + case MetaActionType::BMPEX: + case MetaActionType::MASK: + { + OSL_FAIL( "Don't use bitmap actions of this type in metafiles!" ); + } + break; + + case MetaActionType::BMPSCALE: + { + MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pAction); + aMtf.push_back( new MetaBmpScaleAction( pAct->GetPoint(), pAct->GetSize(), + pFncBmp( BitmapEx(pAct->GetBitmap()), pBmpParam ).GetBitmap() ) ); + } + break; + + case MetaActionType::BMPSCALEPART: + { + MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pAction); + aMtf.push_back( new MetaBmpScalePartAction( pAct->GetDestPoint(), pAct->GetDestSize(), + pAct->GetSrcPoint(), pAct->GetSrcSize(), + pFncBmp( BitmapEx(pAct->GetBitmap()), pBmpParam ).GetBitmap() ) + ); + } + break; + + case MetaActionType::BMPEXSCALE: + { + MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pAction); + aMtf.push_back( new MetaBmpExScaleAction( pAct->GetPoint(), pAct->GetSize(), + pFncBmp( pAct->GetBitmapEx(), pBmpParam ) ) + ); + } + break; + + case MetaActionType::BMPEXSCALEPART: + { + MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pAction); + aMtf.push_back( new MetaBmpExScalePartAction( pAct->GetDestPoint(), pAct->GetDestSize(), + pAct->GetSrcPoint(), pAct->GetSrcSize(), + pFncBmp( pAct->GetBitmapEx(), pBmpParam ) ) + ); + } + break; + + case MetaActionType::MASKSCALE: + { + MetaMaskScaleAction* pAct = static_cast<MetaMaskScaleAction*>(pAction); + aMtf.push_back( new MetaMaskScaleAction( pAct->GetPoint(), pAct->GetSize(), + pAct->GetBitmap(), + pFncCol( pAct->GetColor(), pColParam ) ) + ); + } + break; + + case MetaActionType::MASKSCALEPART: + { + MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pAction); + aMtf.push_back( new MetaMaskScalePartAction( pAct->GetDestPoint(), pAct->GetDestSize(), + pAct->GetSrcPoint(), pAct->GetSrcSize(), + pAct->GetBitmap(), + pFncCol( pAct->GetColor(), pColParam ) ) + ); + } + break; + + case MetaActionType::GRADIENT: + { + MetaGradientAction* pAct = static_cast<MetaGradientAction*>(pAction); + Gradient aGradient( pAct->GetGradient() ); + + aGradient.SetStartColor( pFncCol( aGradient.GetStartColor(), pColParam ) ); + aGradient.SetEndColor( pFncCol( aGradient.GetEndColor(), pColParam ) ); + aMtf.push_back( new MetaGradientAction( pAct->GetRect(), std::move(aGradient) ) ); + } + break; + + case MetaActionType::GRADIENTEX: + { + MetaGradientExAction* pAct = static_cast<MetaGradientExAction*>(pAction); + Gradient aGradient( pAct->GetGradient() ); + + aGradient.SetStartColor( pFncCol( aGradient.GetStartColor(), pColParam ) ); + aGradient.SetEndColor( pFncCol( aGradient.GetEndColor(), pColParam ) ); + aMtf.push_back( new MetaGradientExAction( pAct->GetPolyPolygon(), std::move(aGradient) ) ); + } + break; + + case MetaActionType::HATCH: + { + MetaHatchAction* pAct = static_cast<MetaHatchAction*>(pAction); + Hatch aHatch( pAct->GetHatch() ); + + aHatch.SetColor( pFncCol( aHatch.GetColor(), pColParam ) ); + aMtf.push_back( new MetaHatchAction( pAct->GetPolyPolygon(), aHatch ) ); + } + break; + + case MetaActionType::FLOATTRANSPARENT: + { + MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pAction); + GDIMetaFile aTransMtf( pAct->GetGDIMetaFile() ); + + aTransMtf.ImplExchangeColors( pFncCol, pColParam, pFncBmp, pBmpParam ); + aMtf.push_back( new MetaFloatTransparentAction( aTransMtf, + pAct->GetPoint(), pAct->GetSize(), + pAct->GetGradient() ) + ); + } + break; + + case MetaActionType::EPS: + { + MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pAction); + GDIMetaFile aSubst( pAct->GetSubstitute() ); + + aSubst.ImplExchangeColors( pFncCol, pColParam, pFncBmp, pBmpParam ); + aMtf.push_back( new MetaEPSAction( pAct->GetPoint(), pAct->GetSize(), + pAct->GetLink(), aSubst ) + ); + } + break; + + default: + { + aMtf.push_back( pAction ); + } + break; + } + } + + *this = aMtf; +} + +void GDIMetaFile::Adjust( short nLuminancePercent, short nContrastPercent, + short nChannelRPercent, short nChannelGPercent, + short nChannelBPercent, double fGamma, bool bInvert, bool msoBrightness ) +{ + // nothing to do? => return quickly + if( !(nLuminancePercent || nContrastPercent || + nChannelRPercent || nChannelGPercent || nChannelBPercent || + ( fGamma != 1.0 ) || bInvert) ) + return; + + double fM, fROff, fGOff, fBOff, fOff; + ImplColAdjustParam aColParam; + ImplBmpAdjustParam aBmpParam; + + aColParam.pMapR.reset(new sal_uInt8[ 256 ]); + aColParam.pMapG.reset(new sal_uInt8[ 256 ]); + aColParam.pMapB.reset(new sal_uInt8[ 256 ]); + + // calculate slope + if( nContrastPercent >= 0 ) + fM = 128.0 / ( 128.0 - 1.27 * std::clamp( nContrastPercent, short(0), short(100) ) ); + else + fM = ( 128.0 + 1.27 * std::clamp( nContrastPercent, short(-100), short(0) ) ) / 128.0; + + if(!msoBrightness) + // total offset = luminance offset + contrast offset + fOff = std::clamp( nLuminancePercent, short(-100), short(100) ) * 2.55 + 128.0 - fM * 128.0; + else + fOff = std::clamp( nLuminancePercent, short(-100), short(100) ) * 2.55; + + // channel offset = channel offset + total offset + fROff = nChannelRPercent * 2.55 + fOff; + fGOff = nChannelGPercent * 2.55 + fOff; + fBOff = nChannelBPercent * 2.55 + fOff; + + // calculate gamma value + fGamma = ( fGamma <= 0.0 || fGamma > 10.0 ) ? 1.0 : ( 1.0 / fGamma ); + const bool bGamma = ( fGamma != 1.0 ); + + // create mapping table + for( tools::Long nX = 0; nX < 256; nX++ ) + { + if(!msoBrightness) + { + aColParam.pMapR[ nX ] = FRound(std::clamp( nX * fM + fROff, 0.0, 255.0 )); + aColParam.pMapG[ nX ] = FRound(std::clamp( nX * fM + fGOff, 0.0, 255.0 )); + aColParam.pMapB[ nX ] = FRound(std::clamp( nX * fM + fBOff, 0.0, 255.0 )); + } + else + { + aColParam.pMapR[ nX ] = FRound(std::clamp( (nX+fROff/2-128) * fM + 128 + fROff/2, 0.0, 255.0 )); + aColParam.pMapG[ nX ] = FRound(std::clamp( (nX+fGOff/2-128) * fM + 128 + fGOff/2, 0.0, 255.0 )); + aColParam.pMapB[ nX ] = FRound(std::clamp( (nX+fBOff/2-128) * fM + 128 + fBOff/2, 0.0, 255.0 )); + } + if( bGamma ) + { + aColParam.pMapR[ nX ] = GAMMA( aColParam.pMapR[ nX ], fGamma ); + aColParam.pMapG[ nX ] = GAMMA( aColParam.pMapG[ nX ], fGamma ); + aColParam.pMapB[ nX ] = GAMMA( aColParam.pMapB[ nX ], fGamma ); + } + + if( bInvert ) + { + aColParam.pMapR[ nX ] = ~aColParam.pMapR[ nX ]; + aColParam.pMapG[ nX ] = ~aColParam.pMapG[ nX ]; + aColParam.pMapB[ nX ] = ~aColParam.pMapB[ nX ]; + } + } + + aBmpParam.nLuminancePercent = nLuminancePercent; + aBmpParam.nContrastPercent = nContrastPercent; + aBmpParam.nChannelRPercent = nChannelRPercent; + aBmpParam.nChannelGPercent = nChannelGPercent; + aBmpParam.nChannelBPercent = nChannelBPercent; + aBmpParam.fGamma = fGamma; + aBmpParam.bInvert = bInvert; + + // do color adjustment + ImplExchangeColors( ImplColAdjustFnc, &aColParam, ImplBmpAdjustFnc, &aBmpParam ); +} + +void GDIMetaFile::Convert( MtfConversion eConversion ) +{ + ImplColConvertParam aColParam; + ImplBmpConvertParam aBmpParam; + + aColParam.eConversion = eConversion; + aBmpParam.eConversion = ( MtfConversion::N1BitThreshold == eConversion ) ? BmpConversion::N1BitThreshold : BmpConversion::N8BitGreys; + + ImplExchangeColors( ImplColConvertFnc, &aColParam, ImplBmpConvertFnc, &aBmpParam ); +} + +void GDIMetaFile::ReplaceColors( const Color* pSearchColors, const Color* pReplaceColors, sal_uLong nColorCount ) +{ + ImplColReplaceParam aColParam; + ImplBmpReplaceParam aBmpParam; + + aColParam.pMinR.reset(new sal_uLong[ nColorCount ]); + aColParam.pMaxR.reset(new sal_uLong[ nColorCount ]); + aColParam.pMinG.reset(new sal_uLong[ nColorCount ]); + aColParam.pMaxG.reset(new sal_uLong[ nColorCount ]); + aColParam.pMinB.reset(new sal_uLong[ nColorCount ]); + aColParam.pMaxB.reset(new sal_uLong[ nColorCount ]); + + for( sal_uLong i = 0; i < nColorCount; i++ ) + { + tools::Long nVal; + + nVal = pSearchColors[ i ].GetRed(); + aColParam.pMinR[ i ] = static_cast<sal_uLong>(std::max( nVal, tools::Long(0) )); + aColParam.pMaxR[ i ] = static_cast<sal_uLong>(std::min( nVal, tools::Long(255) )); + + nVal = pSearchColors[ i ].GetGreen(); + aColParam.pMinG[ i ] = static_cast<sal_uLong>(std::max( nVal, tools::Long(0) )); + aColParam.pMaxG[ i ] = static_cast<sal_uLong>(std::min( nVal, tools::Long(255) )); + + nVal = pSearchColors[ i ].GetBlue(); + aColParam.pMinB[ i ] = static_cast<sal_uLong>(std::max( nVal, tools::Long(0) )); + aColParam.pMaxB[ i ] = static_cast<sal_uLong>(std::min( nVal, tools::Long(255) )); + } + + aColParam.pDstCols = pReplaceColors; + aColParam.nCount = nColorCount; + + aBmpParam.pSrcCols = pSearchColors; + aBmpParam.pDstCols = pReplaceColors; + aBmpParam.nCount = nColorCount; + + ImplExchangeColors( ImplColReplaceFnc, &aColParam, ImplBmpReplaceFnc, &aBmpParam ); +}; + +GDIMetaFile GDIMetaFile::GetMonochromeMtf( const Color& rColor ) const +{ + GDIMetaFile aRet( *this ); + + ImplColMonoParam aColParam; + ImplBmpMonoParam aBmpParam; + + aColParam.aColor = rColor; + aBmpParam.aColor = rColor; + + aRet.ImplExchangeColors( ImplColMonoFnc, &aColParam, ImplBmpMonoFnc, &aBmpParam ); + + return aRet; +} + +sal_uLong GDIMetaFile::GetSizeBytes() const +{ + sal_uLong nSizeBytes = 0; + + for( size_t i = 0, nObjCount = GetActionSize(); i < nObjCount; ++i ) + { + MetaAction* pAction = GetAction( i ); + + // default action size is set to 32 (=> not the exact value) + nSizeBytes += 32; + + // add sizes for large action content + switch( pAction->GetType() ) + { + case MetaActionType::BMP: nSizeBytes += static_cast<MetaBmpAction*>( pAction )->GetBitmap().GetSizeBytes(); break; + case MetaActionType::BMPSCALE: nSizeBytes += static_cast<MetaBmpScaleAction*>( pAction )->GetBitmap().GetSizeBytes(); break; + case MetaActionType::BMPSCALEPART: nSizeBytes += static_cast<MetaBmpScalePartAction*>( pAction )->GetBitmap().GetSizeBytes(); break; + + case MetaActionType::BMPEX: nSizeBytes += static_cast<MetaBmpExAction*>( pAction )->GetBitmapEx().GetSizeBytes(); break; + case MetaActionType::BMPEXSCALE: nSizeBytes += static_cast<MetaBmpExScaleAction*>( pAction )->GetBitmapEx().GetSizeBytes(); break; + case MetaActionType::BMPEXSCALEPART: nSizeBytes += static_cast<MetaBmpExScalePartAction*>( pAction )->GetBitmapEx().GetSizeBytes(); break; + + case MetaActionType::MASK: nSizeBytes += static_cast<MetaMaskAction*>( pAction )->GetBitmap().GetSizeBytes(); break; + case MetaActionType::MASKSCALE: nSizeBytes += static_cast<MetaMaskScaleAction*>( pAction )->GetBitmap().GetSizeBytes(); break; + case MetaActionType::MASKSCALEPART: nSizeBytes += static_cast<MetaMaskScalePartAction*>( pAction )->GetBitmap().GetSizeBytes(); break; + + case MetaActionType::POLYLINE: nSizeBytes += static_cast<MetaPolyLineAction*>( pAction )->GetPolygon().GetSize() * sizeof( Point ); break; + case MetaActionType::POLYGON: nSizeBytes += static_cast<MetaPolygonAction*>( pAction )->GetPolygon().GetSize() * sizeof( Point ); break; + case MetaActionType::POLYPOLYGON: + { + const tools::PolyPolygon& rPolyPoly = static_cast<MetaPolyPolygonAction*>( pAction )->GetPolyPolygon(); + + for( sal_uInt16 n = 0; n < rPolyPoly.Count(); ++n ) + nSizeBytes += ( rPolyPoly[ n ].GetSize() * sizeof( Point ) ); + } + break; + + case MetaActionType::TEXT: nSizeBytes += static_cast<MetaTextAction*>( pAction )->GetText().getLength() * sizeof( sal_Unicode ); break; + case MetaActionType::STRETCHTEXT: nSizeBytes += static_cast<MetaStretchTextAction*>( pAction )->GetText().getLength() * sizeof( sal_Unicode ); break; + case MetaActionType::TEXTRECT: nSizeBytes += static_cast<MetaTextRectAction*>( pAction )->GetText().getLength() * sizeof( sal_Unicode ); break; + case MetaActionType::TEXTARRAY: + { + MetaTextArrayAction* pTextArrayAction = static_cast<MetaTextArrayAction*>(pAction); + + nSizeBytes += ( pTextArrayAction->GetText().getLength() * sizeof( sal_Unicode ) ); + + if( !pTextArrayAction->GetDXArray().empty() ) + nSizeBytes += ( pTextArrayAction->GetLen() << 2 ); + } + break; + default: break; + } + } + + return nSizeBytes; +} + +bool GDIMetaFile::CreateThumbnail(BitmapEx& rBitmapEx, BmpConversion eColorConversion, BmpScaleFlag nScaleFlag) const +{ + // initialization seems to be complicated but is used to avoid rounding errors + ScopedVclPtrInstance< VirtualDevice > aVDev; + // set Enable to tease the rendering down the code paths which use B2DPolygon and + // avoid integer overflows on scaling tools::Polygon, e.g. moz1545040-1.svg + // note: this is similar to DocumentToGraphicRenderer::renderToGraphic + aVDev->SetAntialiasing(AntialiasingFlags::Enable | aVDev->GetAntialiasing()); + const Point aNullPt; + const Point aTLPix( aVDev->LogicToPixel( aNullPt, GetPrefMapMode() ) ); + const Point aBRPix( aVDev->LogicToPixel( Point( GetPrefSize().Width() - 1, GetPrefSize().Height() - 1 ), GetPrefMapMode() ) ); + Size aDrawSize( aVDev->LogicToPixel( GetPrefSize(), GetPrefMapMode() ) ); + Size aSizePix( std::abs( aBRPix.X() - aTLPix.X() ) + 1, std::abs( aBRPix.Y() - aTLPix.Y() ) + 1 ); + sal_uInt32 nMaximumExtent = 512; + + if (!rBitmapEx.IsEmpty()) + rBitmapEx.SetEmpty(); + + // determine size that has the same aspect ratio as image size and + // fits into the rectangle determined by nMaximumExtent + if ( aSizePix.Width() && aSizePix.Height() + && ( sal::static_int_cast< tools::ULong >(aSizePix.Width()) > + nMaximumExtent || + sal::static_int_cast< tools::ULong >(aSizePix.Height()) > + nMaximumExtent ) ) + { + const Size aOldSizePix( aSizePix ); + double fWH = static_cast< double >( aSizePix.Width() ) / aSizePix.Height(); + + if ( fWH <= 1.0 ) + { + aSizePix.setWidth( FRound( nMaximumExtent * fWH ) ); + aSizePix.setHeight( nMaximumExtent ); + } + else + { + aSizePix.setWidth( nMaximumExtent ); + aSizePix.setHeight( FRound( nMaximumExtent / fWH ) ); + } + + aDrawSize.setWidth( FRound( ( static_cast< double >( aDrawSize.Width() ) * aSizePix.Width() ) / aOldSizePix.Width() ) ); + aDrawSize.setHeight( FRound( ( static_cast< double >( aDrawSize.Height() ) * aSizePix.Height() ) / aOldSizePix.Height() ) ); + } + + // draw image(s) into VDev and get resulting image + // do it 4x larger to be able to scale it down & get beautiful antialias + Size aAntialiasSize(aSizePix.Width() * 4, aSizePix.Height() * 4); + if (aVDev->SetOutputSizePixel(aAntialiasSize)) + { + // antialias: provide 4x larger size, and then scale down the result + Size aAntialias(aDrawSize.Width() * 4, aDrawSize.Height() * 4); + + // draw metafile into VDev + const_cast<GDIMetaFile *>(this)->WindStart(); + const_cast<GDIMetaFile *>(this)->Play(*aVDev, Point(), aAntialias); + + // get paint bitmap + BitmapEx aBitmap( aVDev->GetBitmapEx( aNullPt, aVDev->GetOutputSizePixel() ) ); + + // scale down the image to the desired size - use the input scaler for the scaling operation + aBitmap.Scale(aDrawSize, nScaleFlag); + + // convert to desired bitmap color format + Size aSize(aBitmap.GetSizePixel()); + if (aSize.Width() && aSize.Height()) + aBitmap.Convert(eColorConversion); + + rBitmapEx = aBitmap; + } + + return !rBitmapEx.IsEmpty(); +} + +void GDIMetaFile::UseCanvas( bool _bUseCanvas ) +{ + m_bUseCanvas = _bUseCanvas; +} + +void GDIMetaFile::dumpAsXml(const char* pFileName) const +{ + SvFileStream aStream(pFileName ? OUString::fromUtf8(pFileName) : OUString("file:///tmp/metafile.xml"), + StreamMode::STD_READWRITE | StreamMode::TRUNC); + assert(aStream.good()); + MetafileXmlDump aDumper; + aDumper.dump(*this, aStream); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |