diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /vcl/source/gdi/gdimetafiletools.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.tar.xz libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | vcl/source/gdi/gdimetafiletools.cxx | 1086 |
1 files changed, 1086 insertions, 0 deletions
diff --git a/vcl/source/gdi/gdimetafiletools.cxx b/vcl/source/gdi/gdimetafiletools.cxx new file mode 100644 index 000000000..6a74c4d69 --- /dev/null +++ b/vcl/source/gdi/gdimetafiletools.cxx @@ -0,0 +1,1086 @@ +/* -*- 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 <vcl/gdimetafiletools.hxx> +#include <vcl/metaact.hxx> +#include <vcl/canvastools.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <vcl/virdev.hxx> +#include <vcl/svapp.hxx> +#include <vcl/graphictools.hxx> +#include <osl/diagnose.h> +#include <tools/stream.hxx> + +// helpers + +namespace +{ + bool handleGeometricContent( + const basegfx::B2DPolyPolygon& rClip, + const basegfx::B2DPolyPolygon& rSource, + GDIMetaFile& rTarget, + bool bStroke) + { + if(rSource.count() && rClip.count()) + { + const basegfx::B2DPolyPolygon aResult( + basegfx::utils::clipPolyPolygonOnPolyPolygon( + rSource, + rClip, + true, // inside + bStroke)); + + if(aResult.count()) + { + if(aResult == rSource) + { + // not clipped, but inside. Add original + return false; + } + else + { + // add clipped geometry + if(bStroke) + { + for(auto const& rB2DPolygon : aResult) + { + rTarget.AddAction( + new MetaPolyLineAction( + tools::Polygon(rB2DPolygon))); + } + } + else + { + rTarget.AddAction( + new MetaPolyPolygonAction( + tools::PolyPolygon(aResult))); + } + } + } + } + + return true; + } + + bool handleGradientContent( + const basegfx::B2DPolyPolygon& rClip, + const basegfx::B2DPolyPolygon& rSource, + const Gradient& rGradient, + GDIMetaFile& rTarget) + { + if(rSource.count() && rClip.count()) + { + const basegfx::B2DPolyPolygon aResult( + basegfx::utils::clipPolyPolygonOnPolyPolygon( + rSource, + rClip, + true, // inside + false)); // stroke + + if(aResult.count()) + { + if(aResult == rSource) + { + // not clipped, but inside. Add original + return false; + } + else + { + // add clipped geometry + rTarget.AddAction( + new MetaGradientExAction( + tools::PolyPolygon(aResult), + rGradient)); + } + } + } + + return true; + } + + bool handleBitmapContent( + const basegfx::B2DPolyPolygon& rClip, + const Point& rPoint, + const Size& rSize, + const BitmapEx& rBitmapEx, + GDIMetaFile& rTarget) + { + if(!rSize.Width() || !rSize.Height() || rBitmapEx.IsEmpty()) + { + // bitmap or size is empty + return true; + } + + const basegfx::B2DRange aLogicBitmapRange( + rPoint.X(), rPoint.Y(), + rPoint.X() + rSize.Width(), rPoint.Y() + rSize.Height()); + const basegfx::B2DPolyPolygon aClipOfBitmap( + basegfx::utils::clipPolyPolygonOnRange( + rClip, + aLogicBitmapRange, + true, + false)); // stroke + + if(!aClipOfBitmap.count()) + { + // outside clip region + return true; + } + + // inside or overlapping. Use area to find out if it is completely + // covering (inside) or overlapping + const double fClipArea(basegfx::utils::getArea(aClipOfBitmap)); + const double fBitmapArea( + aLogicBitmapRange.getWidth() * aLogicBitmapRange.getWidth() + + aLogicBitmapRange.getHeight() * aLogicBitmapRange.getHeight()); + const double fFactor(fClipArea / fBitmapArea); + + if(basegfx::fTools::more(fFactor, 1.0 - 0.001)) + { + // completely covering (with 0.1% tolerance) + return false; + } + + // needs clipping (with 0.1% tolerance). Prepare VirtualDevice + // in pixel mode for alpha channel painting (black is transparent, + // white to paint 100% opacity) + const Size aSizePixel(rBitmapEx.GetSizePixel()); + ScopedVclPtrInstance< VirtualDevice > aVDev; + + aVDev->SetOutputSizePixel(aSizePixel); + aVDev->EnableMapMode(false); + aVDev->SetFillColor( COL_WHITE); + aVDev->SetLineColor(); + + if(rBitmapEx.IsTransparent()) + { + // use given alpha channel + aVDev->DrawBitmap(Point(0, 0), rBitmapEx.GetAlpha().GetBitmap()); + } + else + { + // reset alpha channel + aVDev->SetBackground(Wallpaper(COL_BLACK)); + aVDev->Erase(); + } + + // transform polygon from clipping to pixel coordinates + basegfx::B2DPolyPolygon aPixelPoly(aClipOfBitmap); + basegfx::B2DHomMatrix aTransform; + + aTransform.translate(-aLogicBitmapRange.getMinX(), -aLogicBitmapRange.getMinY()); + aTransform.scale( + static_cast< double >(aSizePixel.Width()) / aLogicBitmapRange.getWidth(), + static_cast< double >(aSizePixel.Height()) / aLogicBitmapRange.getHeight()); + aPixelPoly.transform(aTransform); + + // to fill the non-covered parts, use the Xor fill rule of + // tools::PolyPolygon painting. Start with an all-covering polygon and + // add the clip polygon one + basegfx::B2DPolyPolygon aInvertPixelPoly; + + aInvertPixelPoly.append( + basegfx::utils::createPolygonFromRect( + basegfx::B2DRange( + 0.0, 0.0, + aSizePixel.Width(), aSizePixel.Height()))); + aInvertPixelPoly.append(aPixelPoly); + + // paint as alpha + aVDev->DrawPolyPolygon(aInvertPixelPoly); + + // get created alpha mask and set defaults + AlphaMask aAlpha( + aVDev->GetBitmap( + Point(0, 0), + aSizePixel)); + + aAlpha.SetPrefSize(rBitmapEx.GetPrefSize()); + aAlpha.SetPrefMapMode(rBitmapEx.GetPrefMapMode()); + + // add new action replacing the old one + rTarget.AddAction( + new MetaBmpExScaleAction( + Point( + basegfx::fround(aLogicBitmapRange.getMinX()), + basegfx::fround(aLogicBitmapRange.getMinY())), + Size( + basegfx::fround(aLogicBitmapRange.getWidth()), + basegfx::fround(aLogicBitmapRange.getHeight())), + BitmapEx(rBitmapEx.GetBitmap(), aAlpha))); + + return true; + } + + void addSvtGraphicStroke(const SvtGraphicStroke& rStroke, GDIMetaFile& rTarget) + { + // write SvtGraphicFill + SvMemoryStream aMemStm; + WriteSvtGraphicStroke( aMemStm, rStroke ); + rTarget.AddAction( + new MetaCommentAction( + "XPATHSTROKE_SEQ_BEGIN", + 0, + static_cast< const sal_uInt8* >(aMemStm.GetData()), + aMemStm.TellEnd())); + } + + void addSvtGraphicFill(const SvtGraphicFill &rFilling, GDIMetaFile& rTarget) + { + // write SvtGraphicFill + SvMemoryStream aMemStm; + WriteSvtGraphicFill( aMemStm, rFilling ); + rTarget.AddAction( + new MetaCommentAction( + "XPATHFILL_SEQ_BEGIN", + 0, + static_cast< const sal_uInt8* >(aMemStm.GetData()), + aMemStm.TellEnd())); + } +} // end of anonymous namespace + +// #i121267# Tooling to internally clip geometry against internal clip regions + +void clipMetafileContentAgainstOwnRegions(GDIMetaFile& rSource) +{ + const sal_uLong nObjCount(rSource.GetActionSize()); + + if(!nObjCount) + { + return; + } + + // prepare target data container and push/pop stack data + GDIMetaFile aTarget; + bool bChanged(false); + std::vector< basegfx::B2DPolyPolygon > aClips; + std::vector< PushFlags > aPushFlags; + std::vector< MapMode > aMapModes; + + // start with empty region + aClips.emplace_back(); + + // start with default MapMode (MapUnit::MapPixel) + aMapModes.emplace_back(); + + for(sal_uLong i(0); i < nObjCount; ++i) + { + const MetaAction* pAction(rSource.GetAction(i)); + const MetaActionType nType(pAction->GetType()); + bool bDone(false); + + // basic operation takes care of clipregion actions (four) and push/pop of these + // to steer the currently set clip region. There *is* an active + // clip region when (aClips.size() && aClips.back().count()), see + // below + switch(nType) + { + case MetaActionType::CLIPREGION : + { + const MetaClipRegionAction* pA = static_cast< const MetaClipRegionAction* >(pAction); + + if(pA->IsClipping()) + { + const vcl::Region& rRegion = pA->GetRegion(); + const basegfx::B2DPolyPolygon aNewClip(rRegion.GetAsB2DPolyPolygon()); + + aClips.back() = aNewClip; + } + else + { + aClips.back() = basegfx::B2DPolyPolygon(); + } + + break; + } + + case MetaActionType::ISECTRECTCLIPREGION : + { + const MetaISectRectClipRegionAction* pA = static_cast< const MetaISectRectClipRegionAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(!rRect.IsEmpty() && !aClips.empty() && aClips.back().count()) + { + const basegfx::B2DRange aClipRange(vcl::unotools::b2DRectangleFromRectangle(rRect)); + + aClips.back() = basegfx::utils::clipPolyPolygonOnRange( + aClips.back(), + aClipRange, + true, // inside + false); // stroke + } + break; + } + + case MetaActionType::ISECTREGIONCLIPREGION : + { + const MetaISectRegionClipRegionAction* pA = static_cast< const MetaISectRegionClipRegionAction* >(pAction); + const vcl::Region& rRegion = pA->GetRegion(); + + if(!rRegion.IsEmpty() && !aClips.empty() && aClips.back().count()) + { + const basegfx::B2DPolyPolygon aNewClip(rRegion.GetAsB2DPolyPolygon()); + + aClips.back() = basegfx::utils::clipPolyPolygonOnPolyPolygon( + aClips.back(), + aNewClip, + true, // inside + false); // stroke + } + break; + } + + case MetaActionType::MOVECLIPREGION : + { + const MetaMoveClipRegionAction* pA = static_cast< const MetaMoveClipRegionAction* >(pAction); + const long aHorMove(pA->GetHorzMove()); + const long aVerMove(pA->GetVertMove()); + + if((aHorMove || aVerMove) && !aClips.empty() && aClips.back().count()) + { + aClips.back().transform( + basegfx::utils::createTranslateB2DHomMatrix( + aHorMove, + aVerMove)); + } + break; + } + + case MetaActionType::PUSH : + { + const MetaPushAction* pA = static_cast< const MetaPushAction* >(pAction); + const PushFlags nFlags(pA->GetFlags()); + + aPushFlags.push_back(nFlags); + + if(nFlags & PushFlags::CLIPREGION) + { + aClips.push_back(aClips.back()); + } + + if(nFlags & PushFlags::MAPMODE) + { + aMapModes.push_back(aMapModes.back()); + } + break; + } + + case MetaActionType::POP : + { + + if(!aPushFlags.empty()) + { + const PushFlags nFlags(aPushFlags.back()); + aPushFlags.pop_back(); + + if(nFlags & PushFlags::CLIPREGION) + { + if(aClips.size() > 1) + { + aClips.pop_back(); + } + else + { + OSL_ENSURE(false, "Wrong POP() in ClipRegions (!)"); + } + } + + if(nFlags & PushFlags::MAPMODE) + { + if(aMapModes.size() > 1) + { + aMapModes.pop_back(); + } + else + { + OSL_ENSURE(false, "Wrong POP() in MapModes (!)"); + } + } + } + else + { + OSL_ENSURE(false, "Invalid pop() without push() (!)"); + } + + break; + } + + case MetaActionType::MAPMODE : + { + const MetaMapModeAction* pA = static_cast< const MetaMapModeAction* >(pAction); + + aMapModes.back() = pA->GetMapMode(); + break; + } + + default: + { + break; + } + } + + // this area contains all actions which could potentially be clipped. Since + // this tooling is only a fallback (see comments in header), only the needed + // actions will be implemented. Extend using the pattern for the already + // implemented actions. + if(!aClips.empty() && aClips.back().count()) + { + switch(nType) + { + + // pixel actions, just check on inside + + case MetaActionType::PIXEL : + { + const MetaPixelAction* pA = static_cast< const MetaPixelAction* >(pAction); + const Point& rPoint = pA->GetPoint(); + + if(!basegfx::utils::isInside( + aClips.back(), + basegfx::B2DPoint(rPoint.X(), rPoint.Y()))) + { + // when not inside, do not add original + bDone = true; + } + break; + } + + case MetaActionType::POINT : + { + const MetaPointAction* pA = static_cast< const MetaPointAction* >(pAction); + const Point& rPoint = pA->GetPoint(); + + if(!basegfx::utils::isInside( + aClips.back(), + basegfx::B2DPoint(rPoint.X(), rPoint.Y()))) + { + // when not inside, do not add original + bDone = true; + } + break; + } + + // geometry actions + + case MetaActionType::LINE : + { + const MetaLineAction* pA = static_cast< const MetaLineAction* >(pAction); + const Point& rStart(pA->GetStartPoint()); + const Point& rEnd(pA->GetEndPoint()); + basegfx::B2DPolygon aLine; + + aLine.append(basegfx::B2DPoint(rStart.X(), rStart.Y())); + aLine.append(basegfx::B2DPoint(rEnd.X(), rEnd.Y())); + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon(aLine), + aTarget, + true); // stroke + break; + } + + case MetaActionType::RECT : + { + const MetaRectAction* pA = static_cast< const MetaRectAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(rRect.IsEmpty()) + { + bDone = true; + } + else + { + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(rRect))), + aTarget, + false); // stroke + } + break; + } + + case MetaActionType::ROUNDRECT : + { + const MetaRoundRectAction* pA = static_cast< const MetaRoundRectAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(rRect.IsEmpty()) + { + bDone = true; + } + else + { + const sal_uInt32 nHor(pA->GetHorzRound()); + const sal_uInt32 nVer(pA->GetVertRound()); + const basegfx::B2DRange aRange(vcl::unotools::b2DRectangleFromRectangle(rRect)); + basegfx::B2DPolygon aOutline; + + if(nHor || nVer) + { + double fRadiusX((nHor * 2.0) / (aRange.getWidth() > 0.0 ? aRange.getWidth() : 1.0)); + double fRadiusY((nVer * 2.0) / (aRange.getHeight() > 0.0 ? aRange.getHeight() : 1.0)); + fRadiusX = std::max(0.0, std::min(1.0, fRadiusX)); + fRadiusY = std::max(0.0, std::min(1.0, fRadiusY)); + + aOutline = basegfx::utils::createPolygonFromRect(aRange, fRadiusX, fRadiusY); + } + else + { + aOutline = basegfx::utils::createPolygonFromRect(aRange); + } + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon(aOutline), + aTarget, + false); // stroke + } + break; + } + + case MetaActionType::ELLIPSE : + { + const MetaEllipseAction* pA = static_cast< const MetaEllipseAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(rRect.IsEmpty()) + { + bDone = true; + } + else + { + const basegfx::B2DRange aRange(vcl::unotools::b2DRectangleFromRectangle(rRect)); + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromEllipse( + aRange.getCenter(), + aRange.getWidth() * 0.5, + aRange.getHeight() * 0.5)), + aTarget, + false); // stroke + } + break; + } + + case MetaActionType::ARC : + { + const MetaArcAction* pA = static_cast< const MetaArcAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(rRect.IsEmpty()) + { + bDone = true; + } + else + { + const tools::Polygon aToolsPoly( + rRect, + pA->GetStartPoint(), + pA->GetEndPoint(), + PolyStyle::Arc); + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()), + aTarget, + true); // stroke + } + break; + } + + case MetaActionType::PIE : + { + const MetaPieAction* pA = static_cast< const MetaPieAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(rRect.IsEmpty()) + { + bDone = true; + } + else + { + const tools::Polygon aToolsPoly( + rRect, + pA->GetStartPoint(), + pA->GetEndPoint(), + PolyStyle::Pie); + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()), + aTarget, + false); // stroke + } + break; + } + + case MetaActionType::CHORD : + { + const MetaChordAction* pA = static_cast< const MetaChordAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(rRect.IsEmpty()) + { + bDone = true; + } + else + { + const tools::Polygon aToolsPoly( + rRect, + pA->GetStartPoint(), + pA->GetEndPoint(), + PolyStyle::Chord); + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon(aToolsPoly.getB2DPolygon()), + aTarget, + false); // stroke + } + break; + } + + case MetaActionType::POLYLINE : + { + const MetaPolyLineAction* pA = static_cast< const MetaPolyLineAction* >(pAction); + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon(pA->GetPolygon().getB2DPolygon()), + aTarget, + true); // stroke + break; + } + + case MetaActionType::POLYGON : + { + const MetaPolygonAction* pA = static_cast< const MetaPolygonAction* >(pAction); + + bDone = handleGeometricContent( + aClips.back(), + basegfx::B2DPolyPolygon(pA->GetPolygon().getB2DPolygon()), + aTarget, + false); // stroke + break; + } + + case MetaActionType::POLYPOLYGON : + { + const MetaPolyPolygonAction* pA = static_cast< const MetaPolyPolygonAction* >(pAction); + const tools::PolyPolygon& rPoly = pA->GetPolyPolygon(); + + bDone = handleGeometricContent( + aClips.back(), + rPoly.getB2DPolyPolygon(), + aTarget, + false); // stroke + break; + } + + // bitmap actions, create BitmapEx with alpha channel derived + // from clipping + + case MetaActionType::BMPEX : + { + const MetaBmpExAction* pA = static_cast< const MetaBmpExAction* >(pAction); + const BitmapEx& rBitmapEx = pA->GetBitmapEx(); + + // the logical size depends on the PrefSize of the given bitmap in + // combination with the current MapMode + Size aLogicalSize(rBitmapEx.GetPrefSize()); + + if(MapUnit::MapPixel == rBitmapEx.GetPrefMapMode().GetMapUnit()) + { + aLogicalSize = Application::GetDefaultDevice()->PixelToLogic(aLogicalSize, aMapModes.back()); + } + else + { + aLogicalSize = OutputDevice::LogicToLogic(aLogicalSize, rBitmapEx.GetPrefMapMode(), aMapModes.back()); + } + + bDone = handleBitmapContent( + aClips.back(), + pA->GetPoint(), + aLogicalSize, + rBitmapEx, + aTarget); + break; + } + + case MetaActionType::BMP : + { + const MetaBmpAction* pA = static_cast< const MetaBmpAction* >(pAction); + const Bitmap& rBitmap = pA->GetBitmap(); + + // the logical size depends on the PrefSize of the given bitmap in + // combination with the current MapMode + Size aLogicalSize(rBitmap.GetPrefSize()); + + if(MapUnit::MapPixel == rBitmap.GetPrefMapMode().GetMapUnit()) + { + aLogicalSize = Application::GetDefaultDevice()->PixelToLogic(aLogicalSize, aMapModes.back()); + } + else + { + aLogicalSize = OutputDevice::LogicToLogic(aLogicalSize, rBitmap.GetPrefMapMode(), aMapModes.back()); + } + + bDone = handleBitmapContent( + aClips.back(), + pA->GetPoint(), + aLogicalSize, + BitmapEx(rBitmap), + aTarget); + break; + } + + case MetaActionType::BMPEXSCALE : + { + const MetaBmpExScaleAction* pA = static_cast< const MetaBmpExScaleAction* >(pAction); + + bDone = handleBitmapContent( + aClips.back(), + pA->GetPoint(), + pA->GetSize(), + pA->GetBitmapEx(), + aTarget); + break; + } + + case MetaActionType::BMPSCALE : + { + const MetaBmpScaleAction* pA = static_cast< const MetaBmpScaleAction* >(pAction); + + bDone = handleBitmapContent( + aClips.back(), + pA->GetPoint(), + pA->GetSize(), + BitmapEx(pA->GetBitmap()), + aTarget); + break; + } + + case MetaActionType::BMPEXSCALEPART : + { + const MetaBmpExScalePartAction* pA = static_cast< const MetaBmpExScalePartAction* >(pAction); + const BitmapEx& rBitmapEx = pA->GetBitmapEx(); + + if(rBitmapEx.IsEmpty()) + { + // empty content + bDone = true; + } + else + { + BitmapEx aCroppedBitmapEx(rBitmapEx); + const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize()); + + if(aCropRectangle.IsEmpty()) + { + // empty content + bDone = true; + } + else + { + aCroppedBitmapEx.Crop(aCropRectangle); + bDone = handleBitmapContent( + aClips.back(), + pA->GetDestPoint(), + pA->GetDestSize(), + aCroppedBitmapEx, + aTarget); + } + } + break; + } + + case MetaActionType::BMPSCALEPART : + { + const MetaBmpScalePartAction* pA = static_cast< const MetaBmpScalePartAction* >(pAction); + const Bitmap& rBitmap = pA->GetBitmap(); + + if(rBitmap.IsEmpty()) + { + // empty content + bDone = true; + } + else + { + Bitmap aCroppedBitmap(rBitmap); + const tools::Rectangle aCropRectangle(pA->GetSrcPoint(), pA->GetSrcSize()); + + if(aCropRectangle.IsEmpty()) + { + // empty content + bDone = true; + } + else + { + aCroppedBitmap.Crop(aCropRectangle); + bDone = handleBitmapContent( + aClips.back(), + pA->GetDestPoint(), + pA->GetDestSize(), + BitmapEx(aCroppedBitmap), + aTarget); + } + } + break; + } + + // need to handle all those 'hacks' which hide data in comments + + case MetaActionType::COMMENT : + { + const MetaCommentAction* pA = static_cast< const MetaCommentAction* >(pAction); + const OString& rComment = pA->GetComment(); + + if(rComment.equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN")) + { + // nothing to do; this just means that between here and XGRAD_SEQ_END + // exists a MetaActionType::GRADIENTEX mixed with Xor-tricked painting + // commands. This comment is used to scan over these and filter for + // the gradient action. It is needed to support MetaActionType::GRADIENTEX + // in this processor to solve usages. + } + else if(rComment.equalsIgnoreAsciiCase("XPATHFILL_SEQ_BEGIN")) + { + SvtGraphicFill aFilling; + tools::PolyPolygon aPath; + + { // read SvtGraphicFill + SvMemoryStream aMemStm(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(),StreamMode::READ); + ReadSvtGraphicFill( aMemStm, aFilling ); + } + + aFilling.getPath(aPath); + + if(aPath.Count()) + { + const basegfx::B2DPolyPolygon aSource(aPath.getB2DPolyPolygon()); + const basegfx::B2DPolyPolygon aResult( + basegfx::utils::clipPolyPolygonOnPolyPolygon( + aSource, + aClips.back(), + true, // inside + false)); // stroke + + if(aResult.count()) + { + if(aResult != aSource) + { + // add clipped geometry + aFilling.setPath(tools::PolyPolygon(aResult)); + addSvtGraphicFill(aFilling, aTarget); + bDone = true; + } + } + else + { + // exchange with empty polygon + aFilling.setPath(tools::PolyPolygon()); + addSvtGraphicFill(aFilling, aTarget); + bDone = true; + } + } + } + else if(rComment.equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_BEGIN")) + { + SvtGraphicStroke aStroke; + tools::Polygon aPath; + + { // read SvtGraphicFill + SvMemoryStream aMemStm(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(),StreamMode::READ); + ReadSvtGraphicStroke( aMemStm, aStroke ); + } + + aStroke.getPath(aPath); + + if(aPath.GetSize()) + { + const basegfx::B2DPolygon aSource(aPath.getB2DPolygon()); + const basegfx::B2DPolyPolygon aResult( + basegfx::utils::clipPolygonOnPolyPolygon( + aSource, + aClips.back(), + true, // inside + true)); // stroke + + if(aResult.count()) + { + if(aResult.count() > 1 || aResult.getB2DPolygon(0) != aSource) + { + // add clipped geometry + for(auto const& rB2DPolygon : aResult) + { + aStroke.setPath(tools::Polygon(rB2DPolygon)); + addSvtGraphicStroke(aStroke, aTarget); + } + + bDone = true; + } + } + else + { + // exchange with empty polygon + aStroke.setPath(tools::Polygon()); + addSvtGraphicStroke(aStroke, aTarget); + bDone = true; + } + + } + } + break; + } + + // need to handle gradient fills (hopefully only unrotated ones) + + case MetaActionType::GRADIENT : + { + const MetaGradientAction* pA = static_cast< const MetaGradientAction* >(pAction); + const tools::Rectangle& rRect = pA->GetRect(); + + if(rRect.IsEmpty()) + { + bDone = true; + } + else + { + bDone = handleGradientContent( + aClips.back(), + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(rRect))), + pA->GetGradient(), + aTarget); + } + + break; + } + + case MetaActionType::GRADIENTEX : + { + const MetaGradientExAction* pA = static_cast< const MetaGradientExAction* >(pAction); + const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon(); + + bDone = handleGradientContent( + aClips.back(), + rPolyPoly.getB2DPolyPolygon(), + pA->GetGradient(), + aTarget); + break; + } + + // not (yet) supported actions + + // MetaActionType::NONE + // MetaActionType::TEXT + // MetaActionType::TEXTARRAY + // MetaActionType::STRETCHTEXT + // MetaActionType::TEXTRECT + // MetaActionType::MASK + // MetaActionType::MASKSCALE + // MetaActionType::MASKSCALEPART + // MetaActionType::HATCH + // MetaActionType::WALLPAPER + // MetaActionType::FILLCOLOR + // MetaActionType::TEXTCOLOR + // MetaActionType::TEXTFILLCOLOR + // MetaActionType::TEXTALIGN + // MetaActionType::MAPMODE + // MetaActionType::FONT + // MetaActionType::Transparent + // MetaActionType::EPS + // MetaActionType::REFPOINT + // MetaActionType::TEXTLINECOLOR + // MetaActionType::TEXTLINE + // MetaActionType::FLOATTRANSPARENT + // MetaActionType::LAYOUTMODE + // MetaActionType::TEXTLANGUAGE + // MetaActionType::OVERLINECOLOR + + // if an action is not handled at all, it will simply get copied to the + // target (see below). This is the default for all non-implemented actions + default: + { + break; + } + } + } + + if(bDone) + { + bChanged = true; + } + else + { + aTarget.AddAction(const_cast< MetaAction* >(pAction)); + } + } + + if(bChanged) + { + // when changed, copy back and do not forget to set MapMode + // and PrefSize + aTarget.SetPrefMapMode(rSource.GetPrefMapMode()); + aTarget.SetPrefSize(rSource.GetPrefSize()); + rSource = aTarget; + } +} + +bool usesClipActions(const GDIMetaFile& rSource) +{ + const sal_uLong nObjCount(rSource.GetActionSize()); + + for(sal_uLong i(0); i < nObjCount; ++i) + { + const MetaAction* pAction(rSource.GetAction(i)); + const MetaActionType nType(pAction->GetType()); + + switch(nType) + { + case MetaActionType::CLIPREGION : + case MetaActionType::ISECTRECTCLIPREGION : + case MetaActionType::ISECTREGIONCLIPREGION : + case MetaActionType::MOVECLIPREGION : + { + return true; + } + + default: break; + } + } + + return false; +} + +MetafileAccessor::~MetafileAccessor() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |