summaryrefslogtreecommitdiffstats
path: root/vcl/source/outdev
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /vcl/source/outdev
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/outdev')
-rw-r--r--vcl/source/outdev/background.cxx77
-rw-r--r--vcl/source/outdev/bitmap.cxx993
-rw-r--r--vcl/source/outdev/bitmapex.cxx680
-rw-r--r--vcl/source/outdev/clipping.cxx224
-rw-r--r--vcl/source/outdev/curvedshapes.cxx212
-rw-r--r--vcl/source/outdev/eps.cxx84
-rw-r--r--vcl/source/outdev/fill.cxx99
-rw-r--r--vcl/source/outdev/font.cxx1293
-rw-r--r--vcl/source/outdev/gradient.cxx624
-rw-r--r--vcl/source/outdev/hatch.cxx439
-rw-r--r--vcl/source/outdev/line.cxx370
-rw-r--r--vcl/source/outdev/map.cxx1868
-rw-r--r--vcl/source/outdev/mask.cxx162
-rw-r--r--vcl/source/outdev/nativecontrols.cxx326
-rw-r--r--vcl/source/outdev/outdev.cxx822
-rw-r--r--vcl/source/outdev/pixel.cxx118
-rw-r--r--vcl/source/outdev/polygon.cxx510
-rw-r--r--vcl/source/outdev/polyline.cxx404
-rw-r--r--vcl/source/outdev/rect.cxx438
-rw-r--r--vcl/source/outdev/stack.cxx205
-rw-r--r--vcl/source/outdev/text.cxx2106
-rw-r--r--vcl/source/outdev/textline.cxx1126
-rw-r--r--vcl/source/outdev/transparent.cxx1935
-rw-r--r--vcl/source/outdev/vclreferencebase.cxx44
-rw-r--r--vcl/source/outdev/wallpaper.cxx396
25 files changed, 15555 insertions, 0 deletions
diff --git a/vcl/source/outdev/background.cxx b/vcl/source/outdev/background.cxx
new file mode 100644
index 0000000000..7c07367a82
--- /dev/null
+++ b/vcl/source/outdev/background.cxx
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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/virdev.hxx>
+
+Color OutputDevice::GetBackgroundColor() const
+{
+ return GetBackground().GetColor();
+}
+
+void OutputDevice::SetBackground()
+{
+
+ maBackground = Wallpaper();
+ mbBackground = false;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetBackground();
+}
+
+void OutputDevice::SetBackground( const Wallpaper& rBackground )
+{
+
+ maBackground = rBackground;
+
+ if( rBackground.GetStyle() == WallpaperStyle::NONE )
+ mbBackground = false;
+ else
+ mbBackground = true;
+
+ if( !mpAlphaVDev )
+ return;
+
+ // Some of these are probably wrong (e.g. if the gradient has transparency),
+ // but hopefully nobody uses that. If you do, feel free to implement it properly.
+ if( rBackground.GetStyle() == WallpaperStyle::NONE )
+ {
+ mpAlphaVDev->SetBackground( rBackground );
+ }
+ else if( rBackground.IsBitmap())
+ {
+ BitmapEx bitmap = rBackground.GetBitmap();
+ if( bitmap.IsAlpha())
+ mpAlphaVDev->SetBackground( Wallpaper( BitmapEx( bitmap.GetAlphaMask().GetBitmap() ) ) );
+ else
+ mpAlphaVDev->SetBackground( Wallpaper( COL_ALPHA_OPAQUE ));
+ }
+ else if( rBackground.IsGradient())
+ {
+ mpAlphaVDev->SetBackground( Wallpaper( COL_ALPHA_OPAQUE ));
+ }
+ else
+ {
+ // Color background.
+ int alpha = rBackground.GetColor().GetAlpha();
+ mpAlphaVDev->SetBackground( Wallpaper( Color( alpha, alpha, alpha )));
+ }
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/outdev/bitmap.cxx b/vcl/source/outdev/bitmap.cxx
new file mode 100644
index 0000000000..86ac231375
--- /dev/null
+++ b/vcl/source/outdev/bitmap.cxx
@@ -0,0 +1,993 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <config_features.h>
+
+#include <osl/diagnose.h>
+#include <tools/debug.hxx>
+#include <tools/helpers.hxx>
+
+#include <vcl/image.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/skia/SkiaHelper.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+
+#include <bitmap/bmpfast.hxx>
+#include <drawmode.hxx>
+#include <salbmp.hxx>
+#include <salgdi.hxx>
+
+void OutputDevice::DrawBitmap( const Point& rDestPt, const Bitmap& rBitmap )
+{
+ assert(!is_double_buffered_window());
+
+ const Size aSizePix( rBitmap.GetSizePixel() );
+ DrawBitmap( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmap, MetaActionType::BMP );
+}
+
+void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize, const Bitmap& rBitmap )
+{
+ assert(!is_double_buffered_window());
+
+ DrawBitmap( rDestPt, rDestSize, Point(), rBitmap.GetSizePixel(), rBitmap, MetaActionType::BMPSCALE );
+}
+
+void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ const Bitmap& rBitmap)
+{
+ assert(!is_double_buffered_window());
+
+ DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmap, MetaActionType::BMPSCALEPART );
+}
+
+void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ const Bitmap& rBitmap, const MetaActionType nAction )
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if ( RasterOp::Invert == meRasterOp )
+ {
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ return;
+ }
+
+ Bitmap aBmp( rBitmap );
+
+ if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap |
+ DrawModeFlags::GrayBitmap ) )
+ {
+ if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap ) )
+ {
+ sal_uInt8 cCmpVal;
+
+ if ( mnDrawMode & DrawModeFlags::BlackBitmap )
+ cCmpVal = 0;
+ else
+ cCmpVal = 255;
+
+ Color aCol( cCmpVal, cCmpVal, cCmpVal );
+ Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
+ SetLineColor( aCol );
+ SetFillColor( aCol );
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ Pop();
+ return;
+ }
+ else if( !aBmp.IsEmpty() )
+ {
+ if ( mnDrawMode & DrawModeFlags::GrayBitmap )
+ aBmp.Convert( BmpConversion::N8BitGreys );
+ }
+ }
+
+ if ( mpMetaFile )
+ {
+ switch( nAction )
+ {
+ case MetaActionType::BMP:
+ mpMetaFile->AddAction( new MetaBmpAction( rDestPt, aBmp ) );
+ break;
+
+ case MetaActionType::BMPSCALE:
+ mpMetaFile->AddAction( new MetaBmpScaleAction( rDestPt, rDestSize, aBmp ) );
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ mpMetaFile->AddAction( new MetaBmpScalePartAction(
+ rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmp ) );
+ break;
+
+ default: break;
+ }
+ }
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ if (!mpGraphics && !AcquireGraphics())
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if( !aBmp.IsEmpty() )
+ {
+ SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(),
+ ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()),
+ ImplLogicWidthToDevicePixel(rDestSize.Width()),
+ ImplLogicHeightToDevicePixel(rDestSize.Height()));
+
+ if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight )
+ {
+ const BmpMirrorFlags nMirrFlags = AdjustTwoRect( aPosAry, aBmp.GetSizePixel() );
+
+ if ( nMirrFlags != BmpMirrorFlags::NONE )
+ aBmp.Mirror( nMirrFlags );
+
+ if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight )
+ {
+ if (nAction == MetaActionType::BMPSCALE && CanSubsampleBitmap())
+ {
+ double nScaleX = aPosAry.mnDestWidth / static_cast<double>(aPosAry.mnSrcWidth);
+ double nScaleY = aPosAry.mnDestHeight / static_cast<double>(aPosAry.mnSrcHeight);
+
+ // If subsampling, use Bitmap::Scale() for subsampling of better quality.
+
+ // but hidpi surfaces like the cairo one have their own scale, so don't downscale
+ // past the surface scaling which can retain the extra detail
+ double fScale(1.0);
+ if (mpGraphics->ShouldDownscaleIconsAtSurface(&fScale))
+ {
+ nScaleX *= fScale;
+ nScaleY *= fScale;
+ }
+
+ if ( nScaleX < 1.0 || nScaleY < 1.0 )
+ {
+ aBmp.Scale(nScaleX, nScaleY);
+ aPosAry.mnSrcWidth = aPosAry.mnDestWidth * fScale;
+ aPosAry.mnSrcHeight = aPosAry.mnDestHeight * fScale;
+ }
+ }
+
+ mpGraphics->DrawBitmap( aPosAry, *aBmp.ImplGetSalBitmap(), *this );
+ }
+ }
+ }
+
+ if( mpAlphaVDev )
+ {
+ // #i32109#: Make bitmap area opaque
+ mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) );
+ }
+}
+
+Bitmap OutputDevice::GetBitmap( const Point& rSrcPt, const Size& rSize ) const
+{
+ if ( !mpGraphics && !AcquireGraphics() )
+ return Bitmap();
+
+ assert(mpGraphics);
+
+ tools::Long nX = ImplLogicXToDevicePixel( rSrcPt.X() );
+ tools::Long nY = ImplLogicYToDevicePixel( rSrcPt.Y() );
+ tools::Long nWidth = ImplLogicWidthToDevicePixel( rSize.Width() );
+ tools::Long nHeight = ImplLogicHeightToDevicePixel( rSize.Height() );
+ if ( nWidth <= 0 || nHeight <= 0 || nX > (mnOutWidth + mnOutOffX) || nY > (mnOutHeight + mnOutOffY))
+ return Bitmap();
+
+ Bitmap aBmp;
+ tools::Rectangle aRect( Point( nX, nY ), Size( nWidth, nHeight ) );
+ bool bClipped = false;
+
+ // X-Coordinate outside of draw area?
+ if ( nX < mnOutOffX )
+ {
+ nWidth -= ( mnOutOffX - nX );
+ nX = mnOutOffX;
+ bClipped = true;
+ }
+
+ // Y-Coordinate outside of draw area?
+ if ( nY < mnOutOffY )
+ {
+ nHeight -= ( mnOutOffY - nY );
+ nY = mnOutOffY;
+ bClipped = true;
+ }
+
+ // Width outside of draw area?
+ if ( (nWidth + nX) > (mnOutWidth + mnOutOffX) )
+ {
+ nWidth = mnOutOffX + mnOutWidth - nX;
+ bClipped = true;
+ }
+
+ // Height outside of draw area?
+ if ( (nHeight + nY) > (mnOutHeight + mnOutOffY) )
+ {
+ nHeight = mnOutOffY + mnOutHeight - nY;
+ bClipped = true;
+ }
+
+ if ( bClipped )
+ {
+ // If the visible part has been clipped, we have to create a
+ // Bitmap with the correct size in which we copy the clipped
+ // Bitmap to the correct position.
+ ScopedVclPtrInstance< VirtualDevice > aVDev( *this );
+
+ if ( aVDev->SetOutputSizePixel( aRect.GetSize() ) )
+ {
+ if ( aVDev->mpGraphics || aVDev->AcquireGraphics() )
+ {
+ if ( (nWidth > 0) && (nHeight > 0) )
+ {
+ SalTwoRect aPosAry(nX, nY, nWidth, nHeight,
+ (aRect.Left() < mnOutOffX) ? (mnOutOffX - aRect.Left()) : 0L,
+ (aRect.Top() < mnOutOffY) ? (mnOutOffY - aRect.Top()) : 0L,
+ nWidth, nHeight);
+ aVDev->mpGraphics->CopyBits(aPosAry, *mpGraphics, *this, *this);
+ }
+ else
+ {
+ OSL_ENSURE(false, "CopyBits with zero or negative width or height");
+ }
+
+ aBmp = aVDev->GetBitmap( Point(), aVDev->GetOutputSizePixel() );
+ }
+ else
+ bClipped = false;
+ }
+ else
+ bClipped = false;
+ }
+
+ if ( !bClipped )
+ {
+ std::shared_ptr<SalBitmap> pSalBmp = mpGraphics->GetBitmap( nX, nY, nWidth, nHeight, *this );
+
+ if( pSalBmp )
+ {
+ aBmp.ImplSetSalBitmap(pSalBmp);
+ }
+ }
+
+ return aBmp;
+}
+
+void OutputDevice::DrawDeviceAlphaBitmap( const Bitmap& rBmp, const AlphaMask& rAlpha,
+ const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel )
+{
+ assert(!is_double_buffered_window());
+
+ Point aOutPt(LogicToPixel(rDestPt));
+ Size aOutSz(LogicToPixel(rDestSize));
+ tools::Rectangle aDstRect(Point(), GetOutputSizePixel());
+
+ const bool bHMirr = aOutSz.Width() < 0;
+ const bool bVMirr = aOutSz.Height() < 0;
+
+ ClipToPaintRegion(aDstRect);
+
+ BmpMirrorFlags mirrorFlags = BmpMirrorFlags::NONE;
+ if (bHMirr)
+ {
+ aOutSz.setWidth( -aOutSz.Width() );
+ aOutPt.AdjustX( -(aOutSz.Width() - 1) );
+ mirrorFlags |= BmpMirrorFlags::Horizontal;
+ }
+
+ if (bVMirr)
+ {
+ aOutSz.setHeight( -aOutSz.Height() );
+ aOutPt.AdjustY( -(aOutSz.Height() - 1) );
+ mirrorFlags |= BmpMirrorFlags::Vertical;
+ }
+
+ if (aDstRect.Intersection(tools::Rectangle(aOutPt, aOutSz)).IsEmpty())
+ return;
+
+ {
+ Point aRelPt = aOutPt + Point(mnOutOffX, mnOutOffY);
+ SalTwoRect aTR(
+ rSrcPtPixel.X(), rSrcPtPixel.Y(),
+ rSrcSizePixel.Width(), rSrcSizePixel.Height(),
+ aRelPt.X(), aRelPt.Y(),
+ aOutSz.Width(), aOutSz.Height());
+
+ Bitmap bitmap(rBmp);
+ AlphaMask alpha(rAlpha);
+ if(bHMirr || bVMirr)
+ {
+ bitmap.Mirror(mirrorFlags);
+ alpha.Mirror(mirrorFlags);
+ }
+ SalBitmap* pSalSrcBmp = bitmap.ImplGetSalBitmap().get();
+ SalBitmap* pSalAlphaBmp = alpha.GetBitmap().ImplGetSalBitmap().get();
+
+ // #i83087# Naturally, system alpha blending (SalGraphics::DrawAlphaBitmap) cannot work
+ // with separate alpha VDev
+
+ // try to blend the alpha bitmap with the alpha virtual device
+ if (mpAlphaVDev)
+ {
+ if (ImplLogicToDevicePixel(aOutSz).IsEmpty()) // nothing to draw
+ return;
+ Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aRelPt, aOutSz ) );
+ if (SalBitmap* pSalAlphaBmp2 = aAlphaBitmap.ImplGetSalBitmap().get())
+ {
+ if (mpGraphics->BlendAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, *pSalAlphaBmp2, *this))
+ {
+ mpAlphaVDev->BlendBitmap(aTR, rAlpha.GetBitmap());
+ return;
+ }
+ }
+ }
+ else
+ {
+ if (mpGraphics->DrawAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, *this))
+ return;
+ }
+
+ // we need to make sure Skia never reaches this slow code path
+ // (but do not fail in no-op cases)
+ assert(!SkiaHelper::isVCLSkiaEnabled() || !SkiaHelper::isAlphaMaskBlendingEnabled()
+ || tools::Rectangle(Point(), rBmp.GetSizePixel())
+ .Intersection(tools::Rectangle(rSrcPtPixel, rSrcSizePixel)).IsEmpty()
+ || mpAlphaVDev->LogicToPixel(mpAlphaVDev->GetOutputSizePixel()).IsEmpty());
+ }
+
+ tools::Rectangle aBmpRect(Point(), rBmp.GetSizePixel());
+ if (aBmpRect.Intersection(tools::Rectangle(rSrcPtPixel, rSrcSizePixel)).IsEmpty())
+ return;
+
+ Point auxOutPt(LogicToPixel(rDestPt));
+ Size auxOutSz(LogicToPixel(rDestSize));
+
+ // HACK: The function is broken with alpha vdev and mirroring, mirror here.
+ Bitmap bitmap(rBmp);
+ AlphaMask alpha(rAlpha);
+ if(mpAlphaVDev && (bHMirr || bVMirr))
+ {
+ bitmap.Mirror(mirrorFlags);
+ alpha.Mirror(mirrorFlags);
+ auxOutPt = aOutPt;
+ auxOutSz = aOutSz;
+ }
+ DrawDeviceAlphaBitmapSlowPath(bitmap, alpha, aDstRect, aBmpRect, auxOutSz, auxOutPt);
+}
+
+namespace
+{
+
+struct LinearScaleContext
+{
+ std::unique_ptr<sal_Int32[]> mpMapX;
+ std::unique_ptr<sal_Int32[]> mpMapY;
+
+ std::unique_ptr<sal_Int32[]> mpMapXOffset;
+ std::unique_ptr<sal_Int32[]> mpMapYOffset;
+
+ LinearScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect,
+ Size const & aOutSize, tools::Long nOffX, tools::Long nOffY)
+
+ : mpMapX(new sal_Int32[aDstRect.GetWidth()])
+ , mpMapY(new sal_Int32[aDstRect.GetHeight()])
+ , mpMapXOffset(new sal_Int32[aDstRect.GetWidth()])
+ , mpMapYOffset(new sal_Int32[aDstRect.GetHeight()])
+ {
+ const tools::Long nSrcWidth = aBitmapRect.GetWidth();
+ const tools::Long nSrcHeight = aBitmapRect.GetHeight();
+
+ generateSimpleMap(
+ nSrcWidth, aDstRect.GetWidth(), aBitmapRect.Left(),
+ aOutSize.Width(), nOffX, mpMapX.get(), mpMapXOffset.get());
+
+ generateSimpleMap(
+ nSrcHeight, aDstRect.GetHeight(), aBitmapRect.Top(),
+ aOutSize.Height(), nOffY, mpMapY.get(), mpMapYOffset.get());
+ }
+
+private:
+
+ static void generateSimpleMap(tools::Long nSrcDimension, tools::Long nDstDimension, tools::Long nDstLocation,
+ tools::Long nOutDimension, tools::Long nOffset, sal_Int32* pMap, sal_Int32* pMapOffset)
+ {
+
+ const double fReverseScale = (std::abs(nOutDimension) > 1) ? (nSrcDimension - 1) / double(std::abs(nOutDimension) - 1) : 0.0;
+
+ tools::Long nSampleRange = std::max(tools::Long(0), nSrcDimension - 2);
+
+ for (tools::Long i = 0; i < nDstDimension; i++)
+ {
+ double fTemp = std::abs((nOffset + i) * fReverseScale);
+
+ pMap[i] = std::clamp(nDstLocation + tools::Long(fTemp), tools::Long(0), nSampleRange);
+ pMapOffset[i] = static_cast<tools::Long>((fTemp - pMap[i]) * 128.0);
+ }
+ }
+
+public:
+ bool blendBitmap(
+ const BitmapWriteAccess* pDestination,
+ const BitmapReadAccess* pSource,
+ const BitmapReadAccess* pSourceAlpha,
+ const tools::Long nDstWidth,
+ const tools::Long nDstHeight)
+ {
+ if (!pSource || !pSourceAlpha || !pDestination)
+ return false;
+
+ ScanlineFormat nSourceFormat = pSource->GetScanlineFormat();
+ ScanlineFormat nDestinationFormat = pDestination->GetScanlineFormat();
+
+ switch (nSourceFormat)
+ {
+ case ScanlineFormat::N24BitTcRgb:
+ case ScanlineFormat::N24BitTcBgr:
+ {
+ if ( (nSourceFormat == ScanlineFormat::N24BitTcBgr && nDestinationFormat == ScanlineFormat::N32BitTcBgra)
+ || (nSourceFormat == ScanlineFormat::N24BitTcRgb && nDestinationFormat == ScanlineFormat::N32BitTcRgba))
+ {
+ blendBitmap24(pDestination, pSource, pSourceAlpha, nDstWidth, nDstHeight);
+ return true;
+ }
+ }
+ break;
+ default: break;
+ }
+ return false;
+ }
+
+ void blendBitmap24(
+ const BitmapWriteAccess* pDestination,
+ const BitmapReadAccess* pSource,
+ const BitmapReadAccess* pSourceAlpha,
+ const tools::Long nDstWidth,
+ const tools::Long nDstHeight)
+ {
+ Scanline pLine0, pLine1;
+ Scanline pLineAlpha0, pLineAlpha1;
+ Scanline pColorSample1, pColorSample2;
+ Scanline pDestScanline;
+
+ tools::Long nColor1Line1, nColor2Line1, nColor3Line1;
+ tools::Long nColor1Line2, nColor2Line2, nColor3Line2;
+ tools::Long nAlphaLine1, nAlphaLine2;
+
+ sal_uInt8 nColor1, nColor2, nColor3, nAlpha;
+
+ for (tools::Long nY = 0; nY < nDstHeight; nY++)
+ {
+ const tools::Long nMapY = mpMapY[nY];
+ const tools::Long nMapFY = mpMapYOffset[nY];
+
+ pLine0 = pSource->GetScanline(nMapY);
+ // tdf#95481 guard nMapY + 1 to be within bounds
+ pLine1 = (nMapY + 1 < pSource->Height()) ? pSource->GetScanline(nMapY + 1) : pLine0;
+
+ pLineAlpha0 = pSourceAlpha->GetScanline(nMapY);
+ // tdf#95481 guard nMapY + 1 to be within bounds
+ pLineAlpha1 = (nMapY + 1 < pSourceAlpha->Height()) ? pSourceAlpha->GetScanline(nMapY + 1) : pLineAlpha0;
+
+ pDestScanline = pDestination->GetScanline(nY);
+
+ for (tools::Long nX = 0; nX < nDstWidth; nX++)
+ {
+ const tools::Long nMapX = mpMapX[nX];
+ const tools::Long nMapFX = mpMapXOffset[nX];
+
+ pColorSample1 = pLine0 + 3 * nMapX;
+ pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1;
+ nColor1Line1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor2Line1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor3Line1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1 = pLine1 + 3 * nMapX;
+ pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1;
+ nColor1Line2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor2Line2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor3Line2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1 = pLineAlpha0 + nMapX;
+ pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1;
+ nAlphaLine1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1 = pLineAlpha1 + nMapX;
+ pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1;
+ nAlphaLine2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
+
+ nColor1 = (nColor1Line1 + nMapFY * ((nColor1Line2 >> 7) - (nColor1Line1 >> 7))) >> 7;
+ nColor2 = (nColor2Line1 + nMapFY * ((nColor2Line2 >> 7) - (nColor2Line1 >> 7))) >> 7;
+ nColor3 = (nColor3Line1 + nMapFY * ((nColor3Line2 >> 7) - (nColor3Line1 >> 7))) >> 7;
+
+ nAlpha = (nAlphaLine1 + nMapFY * ((nAlphaLine2 >> 7) - (nAlphaLine1 >> 7))) >> 7;
+
+ *pDestScanline = color::ColorChannelMerge(*pDestScanline, nColor1, nAlpha);
+ pDestScanline++;
+ *pDestScanline = color::ColorChannelMerge(*pDestScanline, nColor2, nAlpha);
+ pDestScanline++;
+ *pDestScanline = color::ColorChannelMerge(*pDestScanline, nColor3, nAlpha);
+ pDestScanline++;
+ pDestScanline++;
+ }
+ }
+ }
+};
+
+struct TradScaleContext
+{
+ std::unique_ptr<sal_Int32[]> mpMapX;
+ std::unique_ptr<sal_Int32[]> mpMapY;
+
+ TradScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect,
+ Size const & aOutSize, tools::Long nOffX, tools::Long nOffY)
+
+ : mpMapX(new sal_Int32[aDstRect.GetWidth()])
+ , mpMapY(new sal_Int32[aDstRect.GetHeight()])
+ {
+ const tools::Long nSrcWidth = aBitmapRect.GetWidth();
+ const tools::Long nSrcHeight = aBitmapRect.GetHeight();
+
+ const bool bHMirr = aOutSize.Width() < 0;
+ const bool bVMirr = aOutSize.Height() < 0;
+
+ generateSimpleMap(
+ nSrcWidth, aDstRect.GetWidth(), aBitmapRect.Left(),
+ aOutSize.Width(), nOffX, bHMirr, mpMapX.get());
+
+ generateSimpleMap(
+ nSrcHeight, aDstRect.GetHeight(), aBitmapRect.Top(),
+ aOutSize.Height(), nOffY, bVMirr, mpMapY.get());
+ }
+
+private:
+
+ static void generateSimpleMap(tools::Long nSrcDimension, tools::Long nDstDimension, tools::Long nDstLocation,
+ tools::Long nOutDimension, tools::Long nOffset, bool bMirror, sal_Int32* pMap)
+ {
+ tools::Long nMirrorOffset = 0;
+
+ if (bMirror)
+ nMirrorOffset = (nDstLocation << 1) + nSrcDimension - 1;
+
+ for (tools::Long i = 0; i < nDstDimension; ++i, ++nOffset)
+ {
+ pMap[i] = nDstLocation + nOffset * nSrcDimension / nOutDimension;
+ if (bMirror)
+ pMap[i] = nMirrorOffset - pMap[i];
+ }
+ }
+};
+
+
+} // end anonymous namespace
+
+void OutputDevice::DrawDeviceAlphaBitmapSlowPath(const Bitmap& rBitmap,
+ const AlphaMask& rAlpha, tools::Rectangle aDstRect, tools::Rectangle aBmpRect, Size const & aOutSize, Point const & aOutPoint)
+{
+ assert(!is_double_buffered_window());
+
+ VirtualDevice* pOldVDev = mpAlphaVDev;
+
+ const bool bHMirr = aOutSize.Width() < 0;
+ const bool bVMirr = aOutSize.Height() < 0;
+
+ // The scaling in this code path produces really ugly results - it
+ // does the most trivial scaling with no smoothing.
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ const bool bOldMap = mbMap;
+
+ mpMetaFile = nullptr; // fdo#55044 reset before GetBitmap!
+ mbMap = false;
+
+ Bitmap aBmp(GetBitmap(aDstRect.TopLeft(), aDstRect.GetSize()));
+
+ // #109044# The generated bitmap need not necessarily be
+ // of aDstRect dimensions, it's internally clipped to
+ // window bounds. Thus, we correct the dest size here,
+ // since we later use it (in nDstWidth/Height) for pixel
+ // access)
+ // #i38887# reading from screen may sometimes fail
+ if (aBmp.ImplGetSalBitmap())
+ {
+ aDstRect.SetSize(aBmp.GetSizePixel());
+ }
+
+ const tools::Long nDstWidth = aDstRect.GetWidth();
+ const tools::Long nDstHeight = aDstRect.GetHeight();
+
+ // calculate offset in original bitmap
+ // in RTL case this is a little more complicated since the contents of the
+ // bitmap is not mirrored (it never is), however the paint region and bmp region
+ // are in mirrored coordinates, so the intersection of (aOutPt,aOutSz) with these
+ // is content wise somewhere else and needs to take mirroring into account
+ const tools::Long nOffX = IsRTLEnabled()
+ ? aOutSize.Width() - aDstRect.GetWidth() - (aDstRect.Left() - aOutPoint.X())
+ : aDstRect.Left() - aOutPoint.X();
+
+ const tools::Long nOffY = aDstRect.Top() - aOutPoint.Y();
+
+ TradScaleContext aTradContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY);
+
+ BitmapScopedReadAccess pBitmapReadAccess(rBitmap);
+ BitmapScopedReadAccess pAlphaReadAccess(rAlpha);
+
+ SAL_WARN_IF(pAlphaReadAccess->GetScanlineFormat() != ScanlineFormat::N8BitPal, "vcl.gdi", "non-8bit alpha no longer supported!");
+ assert(pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal);
+
+ // #i38887# reading from screen may sometimes fail
+ if (aBmp.ImplGetSalBitmap())
+ {
+ Bitmap aNewBitmap;
+
+ if (mpAlphaVDev)
+ {
+ aNewBitmap = BlendBitmapWithAlpha(
+ aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(),
+ aDstRect,
+ nOffY, nDstHeight,
+ nOffX, nDstWidth,
+ aTradContext.mpMapX.get(), aTradContext.mpMapY.get() );
+ }
+ else
+ {
+ LinearScaleContext aLinearContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY);
+
+ if (aLinearContext.blendBitmap( BitmapScopedWriteAccess(aBmp).get(), pBitmapReadAccess.get(), pAlphaReadAccess.get(),
+ nDstWidth, nDstHeight))
+ {
+ aNewBitmap = aBmp;
+ }
+ else
+ {
+ aNewBitmap = BlendBitmap(
+ aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(),
+ nOffY, nDstHeight,
+ nOffX, nDstWidth,
+ aBmpRect, aOutSize,
+ bHMirr, bVMirr,
+ aTradContext.mpMapX.get(), aTradContext.mpMapY.get() );
+ }
+ }
+
+ // #110958# Disable alpha VDev, we're doing the necessary
+ // stuff explicitly further below
+ if (mpAlphaVDev)
+ mpAlphaVDev = nullptr;
+
+ DrawBitmap(aDstRect.TopLeft(), aNewBitmap);
+
+ // #110958# Enable alpha VDev again
+ mpAlphaVDev = pOldVDev;
+ }
+
+ mbMap = bOldMap;
+ mpMetaFile = pOldMetaFile;
+}
+
+bool OutputDevice::HasFastDrawTransformedBitmap() const
+{
+ if( ImplIsRecordLayout() )
+ return false;
+
+ if (!mpGraphics && !AcquireGraphics())
+ return false;
+ assert(mpGraphics);
+
+ return mpGraphics->HasFastDrawTransformedBitmap();
+}
+
+void OutputDevice::DrawImage( const Point& rPos, const Image& rImage, DrawImageFlags nStyle )
+{
+ assert(!is_double_buffered_window());
+
+ DrawImage( rPos, Size(), rImage, nStyle );
+}
+
+void OutputDevice::DrawImage( const Point& rPos, const Size& rSize,
+ const Image& rImage, DrawImageFlags nStyle )
+{
+ assert(!is_double_buffered_window());
+
+ bool bIsSizeValid = !rSize.IsEmpty();
+
+ if (!ImplIsRecordLayout())
+ {
+ Image& rNonConstImage = const_cast<Image&>(rImage);
+ if (bIsSizeValid)
+ rNonConstImage.Draw(this, rPos, nStyle, &rSize);
+ else
+ rNonConstImage.Draw(this, rPos, nStyle);
+ }
+}
+
+namespace
+{
+ // Co = Cs + Cd*(1-As) premultiplied alpha -or-
+ // Co = (AsCs + AdCd*(1-As)) / Ao
+ sal_uInt8 CalcColor( const sal_uInt8 nSourceColor, const sal_uInt8 nSourceAlpha,
+ const sal_uInt8 nDstAlpha, const sal_uInt8 nResAlpha, const sal_uInt8 nDestColor )
+ {
+ int c = nResAlpha ? ( static_cast<int>(nSourceAlpha)*nSourceColor + static_cast<int>(nDstAlpha)*nDestColor -
+ static_cast<int>(nDstAlpha)*nDestColor*nSourceAlpha/255 ) / static_cast<int>(nResAlpha) : 0;
+ return sal_uInt8( c );
+ }
+
+ BitmapColor AlphaBlend( int nX, int nY,
+ const tools::Long nMapX,
+ const tools::Long nMapY,
+ BitmapReadAccess const * pP,
+ BitmapReadAccess const * pA,
+ BitmapReadAccess const * pB,
+ BitmapWriteAccess const * pAlphaW,
+ sal_uInt8& nResAlpha )
+ {
+ BitmapColor aDstCol,aSrcCol;
+ aSrcCol = pP->GetColor( nMapY, nMapX );
+ aDstCol = pB->GetColor( nY, nX );
+
+ const sal_uInt8 nSrcAlpha = pA->GetPixelIndex( nMapY, nMapX );
+ const sal_uInt8 nDstAlpha = pAlphaW->GetPixelIndex( nY, nX );
+
+ // Perform porter-duff compositing 'over' operation
+
+ // Co = Cs + Cd*(1-As)
+ // Ad = As + Ad*(1-As)
+ nResAlpha = static_cast<int>(nSrcAlpha) + static_cast<int>(nDstAlpha) - static_cast<int>(nDstAlpha)*nSrcAlpha/255;
+
+ aDstCol.SetRed( CalcColor( aSrcCol.GetRed(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetRed() ) );
+ aDstCol.SetBlue( CalcColor( aSrcCol.GetBlue(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetBlue() ) );
+ aDstCol.SetGreen( CalcColor( aSrcCol.GetGreen(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetGreen() ) );
+
+ return aDstCol;
+ }
+}
+
+void OutputDevice::BlendBitmap(
+ const SalTwoRect& rPosAry,
+ const Bitmap& rBmp )
+{
+ mpGraphics->BlendBitmap( rPosAry, *rBmp.ImplGetSalBitmap(), *this );
+}
+
+Bitmap OutputDevice::BlendBitmapWithAlpha(
+ Bitmap& aBmp,
+ BitmapReadAccess const * pP,
+ BitmapReadAccess const * pA,
+ const tools::Rectangle& aDstRect,
+ const sal_Int32 nOffY,
+ const sal_Int32 nDstHeight,
+ const sal_Int32 nOffX,
+ const sal_Int32 nDstWidth,
+ const sal_Int32* pMapX,
+ const sal_Int32* pMapY )
+
+{
+ BitmapColor aDstCol;
+ Bitmap res;
+ int nX, nY;
+ sal_uInt8 nResAlpha;
+
+ SAL_WARN_IF( !mpAlphaVDev, "vcl.gdi", "BlendBitmapWithAlpha(): call me only with valid alpha VirtualDevice!" );
+
+ bool bOldMapMode( mpAlphaVDev->IsMapModeEnabled() );
+ mpAlphaVDev->EnableMapMode(false);
+
+ Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aDstRect.TopLeft(), aDstRect.GetSize() ) );
+ BitmapScopedWriteAccess pAlphaW(aAlphaBitmap);
+
+ if( GetBitCount() <= 8 )
+ {
+ Bitmap aDither(aBmp.GetSizePixel(), vcl::PixelFormat::N8_BPP);
+ BitmapColor aIndex( 0 );
+ BitmapScopedReadAccess pB(aBmp);
+ BitmapScopedWriteAccess pW(aDither);
+
+ if (pB && pP && pA && pW && pAlphaW)
+ {
+ int nOutY;
+
+ for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ )
+ {
+ const tools::Long nMapY = pMapY[ nY ];
+ const tools::Long nModY = ( nOutY & 0x0FL ) << 4;
+ int nOutX;
+
+ Scanline pScanline = pW->GetScanline(nY);
+ Scanline pScanlineAlpha = pAlphaW->GetScanline(nY);
+ for( nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ )
+ {
+ const tools::Long nMapX = pMapX[ nX ];
+ const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ];
+
+ aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha );
+
+ aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] +
+ nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] +
+ nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) );
+ pW->SetPixelOnData( pScanline, nX, aIndex );
+
+ aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ nResAlpha ] + nD ) >> 16 ] +
+ nVCLGLut[ ( nVCLLut[ nResAlpha ] + nD ) >> 16 ] +
+ nVCLBLut[ ( nVCLLut[ nResAlpha ] + nD ) >> 16 ] ) );
+ pAlphaW->SetPixelOnData( pScanlineAlpha, nX, aIndex );
+ }
+ }
+ }
+ pB.reset();
+ pW.reset();
+ res = aDither;
+ }
+ else
+ {
+ BitmapScopedWriteAccess pB(aBmp);
+ if (pB && pP && pA && pAlphaW)
+ {
+ for( nY = 0; nY < nDstHeight; nY++ )
+ {
+ const tools::Long nMapY = pMapY[ nY ];
+ Scanline pScanlineB = pB->GetScanline(nY);
+ Scanline pScanlineAlpha = pAlphaW->GetScanline(nY);
+
+ for( nX = 0; nX < nDstWidth; nX++ )
+ {
+ const tools::Long nMapX = pMapX[ nX ];
+ aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha );
+
+ pB->SetPixelOnData(pScanlineB, nX, pB->GetBestMatchingColor(aDstCol));
+ pAlphaW->SetPixelOnData(pScanlineAlpha, nX, pB->GetBestMatchingColor(Color(nResAlpha, nResAlpha, nResAlpha)));
+ }
+ }
+ }
+ pB.reset();
+ res = aBmp;
+ }
+
+ pAlphaW.reset();
+ mpAlphaVDev->DrawBitmap( aDstRect.TopLeft(), aAlphaBitmap );
+ mpAlphaVDev->EnableMapMode( bOldMapMode );
+
+ return res;
+}
+
+Bitmap OutputDevice::BlendBitmap(
+ Bitmap& aBmp,
+ BitmapReadAccess const * pP,
+ BitmapReadAccess const * pA,
+ const sal_Int32 nOffY,
+ const sal_Int32 nDstHeight,
+ const sal_Int32 nOffX,
+ const sal_Int32 nDstWidth,
+ const tools::Rectangle& aBmpRect,
+ const Size& aOutSz,
+ const bool bHMirr,
+ const bool bVMirr,
+ const sal_Int32* pMapX,
+ const sal_Int32* pMapY )
+{
+ if( !pP || !pA )
+ return aBmp;
+
+ if( GetBitCount() <= 8 )
+ {
+ Bitmap aDither(aBmp.GetSizePixel(), vcl::PixelFormat::N8_BPP);
+ BitmapColor aIndex( 0 );
+ BitmapScopedReadAccess pB(aBmp);
+ BitmapScopedWriteAccess pW(aDither);
+
+ for( int nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ )
+ {
+ tools::Long nMapY = pMapY[ nY ];
+ if (bVMirr)
+ {
+ nMapY = aBmpRect.Bottom() - nMapY;
+ }
+ const tools::Long nModY = ( nOutY & 0x0FL ) << 4;
+
+ Scanline pScanline = pW->GetScanline(nY);
+ Scanline pScanlineAlpha = pA->GetScanline(nMapY);
+ for( int nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ )
+ {
+ tools::Long nMapX = pMapX[ nX ];
+ if (bHMirr)
+ {
+ nMapX = aBmpRect.Right() - nMapX;
+ }
+ const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ];
+
+ BitmapColor aDstCol = pB->GetColor( nY, nX );
+ aDstCol.Merge( pP->GetColor( nMapY, nMapX ), 255 - pA->GetIndexFromData( pScanlineAlpha, nMapX ) );
+ aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] +
+ nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] +
+ nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) );
+ pW->SetPixelOnData( pScanline, nX, aIndex );
+ }
+ }
+
+ pB.reset();
+ pW.reset();
+ return aDither;
+ }
+
+ BitmapScopedWriteAccess pB(aBmp);
+
+ bool bFastBlend = false;
+ if (!bHMirr && !bVMirr)
+ {
+ SalTwoRect aTR(aBmpRect.Left(), aBmpRect.Top(), aBmpRect.GetWidth(), aBmpRect.GetHeight(),
+ nOffX, nOffY, aOutSz.Width(), aOutSz.Height());
+
+ bFastBlend = ImplFastBitmapBlending(*pB, *pP, *pA, aTR);
+ }
+
+ if (!bFastBlend)
+ {
+ for (int nY = 0; nY < nDstHeight; nY++)
+ {
+ tools::Long nMapY = pMapY[nY];
+
+ if (bVMirr)
+ nMapY = aBmpRect.Bottom() - nMapY;
+
+ Scanline pAScan = pA->GetScanline(nMapY);
+ Scanline pBScan = pB->GetScanline(nY);
+ for(int nX = 0; nX < nDstWidth; nX++)
+ {
+ tools::Long nMapX = pMapX[nX];
+
+ if (bHMirr)
+ nMapX = aBmpRect.Right() - nMapX;
+
+ BitmapColor aDstCol = pB->GetPixelFromData(pBScan, nX);
+ aDstCol.Merge(pP->GetColor(nMapY, nMapX), pAScan[nMapX]);
+ pB->SetPixelOnData(pBScan, nX, aDstCol);
+ }
+ }
+ }
+
+ pB.reset();
+ return aBmp;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/bitmapex.cxx b/vcl/source/outdev/bitmapex.cxx
new file mode 100644
index 0000000000..04a50d5545
--- /dev/null
+++ b/vcl/source/outdev/bitmapex.cxx
@@ -0,0 +1,680 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <config_features.h>
+
+#include <rtl/math.hxx>
+#include <comphelper/lok.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <vcl/canvastools.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+
+void OutputDevice::DrawBitmapEx( const Point& rDestPt,
+ const BitmapEx& rBitmapEx )
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if( !rBitmapEx.IsAlpha() )
+ {
+ DrawBitmap( rDestPt, rBitmapEx.GetBitmap() );
+ }
+ else
+ {
+ const Size aSizePix( rBitmapEx.GetSizePixel() );
+ DrawBitmapEx( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmapEx, MetaActionType::BMPEX );
+ }
+}
+
+void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize,
+ const BitmapEx& rBitmapEx )
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if ( !rBitmapEx.IsAlpha() )
+ {
+ DrawBitmap( rDestPt, rDestSize, rBitmapEx.GetBitmap() );
+ }
+ else
+ {
+ DrawBitmapEx( rDestPt, rDestSize, Point(), rBitmapEx.GetSizePixel(), rBitmapEx, MetaActionType::BMPEXSCALE );
+ }
+}
+
+void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ const BitmapEx& rBitmapEx)
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if ( !rBitmapEx.IsAlpha() )
+ {
+ DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx.GetBitmap() );
+ }
+ else
+ {
+ DrawBitmapEx( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx, MetaActionType::BMPEXSCALEPART );
+ }
+}
+
+void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ const BitmapEx& rBitmapEx, const MetaActionType nAction )
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if( !rBitmapEx.IsAlpha() )
+ {
+ DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx.GetBitmap() );
+ }
+ else
+ {
+ if ( RasterOp::Invert == meRasterOp )
+ {
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ return;
+ }
+
+ BitmapEx aBmpEx(vcl::drawmode::GetBitmapEx(rBitmapEx, GetDrawMode()));
+
+ if ( mpMetaFile )
+ {
+ switch( nAction )
+ {
+ case MetaActionType::BMPEX:
+ mpMetaFile->AddAction( new MetaBmpExAction( rDestPt, aBmpEx ) );
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ mpMetaFile->AddAction( new MetaBmpExScaleAction( rDestPt, rDestSize, aBmpEx ) );
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ mpMetaFile->AddAction( new MetaBmpExScalePartAction( rDestPt, rDestSize,
+ rSrcPtPixel, rSrcSizePixel, aBmpEx ) );
+ break;
+
+ default: break;
+ }
+ }
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ DrawDeviceBitmapEx( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmpEx );
+ }
+}
+
+BitmapEx OutputDevice::GetBitmapEx( const Point& rSrcPt, const Size& rSize ) const
+{
+
+ // #110958# Extract alpha value from VDev, if any
+ if( mpAlphaVDev )
+ {
+ Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( rSrcPt, rSize ) );
+
+ // ensure 8 bit alpha
+ if (aAlphaBitmap.getPixelFormat() > vcl::PixelFormat::N8_BPP)
+ aAlphaBitmap.Convert( BmpConversion::N8BitNoConversion );
+
+ return BitmapEx(GetBitmap( rSrcPt, rSize ), AlphaMask( aAlphaBitmap ) );
+ }
+
+ return BitmapEx(GetBitmap( rSrcPt, rSize ));
+}
+
+void OutputDevice::DrawDeviceBitmapEx( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ BitmapEx& rBitmapEx )
+{
+ assert(!is_double_buffered_window());
+
+ if (rBitmapEx.IsAlpha())
+ {
+ DrawDeviceAlphaBitmap(rBitmapEx.GetBitmap(), rBitmapEx.GetAlphaMask(), rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel);
+ }
+ else if (!rBitmapEx.IsEmpty())
+ {
+ SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(),
+ ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()),
+ ImplLogicWidthToDevicePixel(rDestSize.Width()),
+ ImplLogicHeightToDevicePixel(rDestSize.Height()));
+
+ const BmpMirrorFlags nMirrFlags = AdjustTwoRect(aPosAry, rBitmapEx.GetSizePixel());
+
+ if (aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight)
+ {
+
+ if (nMirrFlags != BmpMirrorFlags::NONE)
+ rBitmapEx.Mirror(nMirrFlags);
+
+ const SalBitmap* pSalSrcBmp = rBitmapEx.ImplGetBitmapSalBitmap().get();
+ std::shared_ptr<SalBitmap> xMaskBmp = rBitmapEx.maAlphaMask.GetBitmap().ImplGetSalBitmap();
+
+ if (xMaskBmp)
+ {
+ bool bTryDirectPaint(pSalSrcBmp);
+
+ if (bTryDirectPaint && mpGraphics->DrawAlphaBitmap(aPosAry, *pSalSrcBmp, *xMaskBmp, *this))
+ {
+ // tried to paint as alpha directly. If this worked, we are done (except
+ // alpha, see below)
+ }
+ else
+ {
+ // #4919452# reduce operation area to bounds of
+ // cliprect. since masked transparency involves
+ // creation of a large vdev and copying the screen
+ // content into that (slooow read from framebuffer),
+ // that should considerably increase performance for
+ // large bitmaps and small clippings.
+
+ // Note that this optimization is a workaround for a
+ // Writer peculiarity, namely, to decompose background
+ // graphics into myriads of disjunct, tiny
+ // rectangles. That otherwise kills us here, since for
+ // transparent output, SAL always prepares the whole
+ // bitmap, if aPosAry contains the whole bitmap (and
+ // it's _not_ to blame for that).
+
+ // Note the call to ImplPixelToDevicePixel(), since
+ // aPosAry already contains the mnOutOff-offsets, they
+ // also have to be applied to the region
+ tools::Rectangle aClipRegionBounds( ImplPixelToDevicePixel(maRegion).GetBoundRect() );
+
+ // TODO: Also respect scaling (that's a bit tricky,
+ // since the source points have to move fractional
+ // amounts (which is not possible, thus has to be
+ // emulated by increases copy area)
+ // const double nScaleX( aPosAry.mnDestWidth / aPosAry.mnSrcWidth );
+ // const double nScaleY( aPosAry.mnDestHeight / aPosAry.mnSrcHeight );
+
+ // for now, only identity scales allowed
+ if (!aClipRegionBounds.IsEmpty() &&
+ aPosAry.mnDestWidth == aPosAry.mnSrcWidth &&
+ aPosAry.mnDestHeight == aPosAry.mnSrcHeight)
+ {
+ // now intersect dest rect with clip region
+ aClipRegionBounds.Intersection(tools::Rectangle(aPosAry.mnDestX,
+ aPosAry.mnDestY,
+ aPosAry.mnDestX + aPosAry.mnDestWidth - 1,
+ aPosAry.mnDestY + aPosAry.mnDestHeight - 1));
+
+ // Note: I could theoretically optimize away the
+ // DrawBitmap below, if the region is empty
+ // here. Unfortunately, cannot rule out that
+ // somebody relies on the side effects.
+ if (!aClipRegionBounds.IsEmpty())
+ {
+ aPosAry.mnSrcX += aClipRegionBounds.Left() - aPosAry.mnDestX;
+ aPosAry.mnSrcY += aClipRegionBounds.Top() - aPosAry.mnDestY;
+ aPosAry.mnSrcWidth = aClipRegionBounds.GetWidth();
+ aPosAry.mnSrcHeight = aClipRegionBounds.GetHeight();
+
+ aPosAry.mnDestX = aClipRegionBounds.Left();
+ aPosAry.mnDestY = aClipRegionBounds.Top();
+ aPosAry.mnDestWidth = aClipRegionBounds.GetWidth();
+ aPosAry.mnDestHeight = aClipRegionBounds.GetHeight();
+ }
+ }
+
+ mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, *xMaskBmp, *this);
+ }
+
+ // #110958# Paint mask to alpha channel. Luckily, the
+ // black and white representation of the mask maps to
+ // the alpha channel
+
+ // #i25167# Restrict mask painting to _opaque_ areas
+ // of the mask, otherwise we spoil areas where no
+ // bitmap content was ever visible. Interestingly
+ // enough, this can be achieved by taking the mask as
+ // the transparency mask of itself
+ if (mpAlphaVDev)
+ mpAlphaVDev->DrawBitmapEx(rDestPt,
+ rDestSize,
+ BitmapEx(rBitmapEx.GetAlphaMask().GetBitmap(),
+ rBitmapEx.GetAlphaMask()));
+ }
+ else
+ {
+ mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, *this);
+
+ if (mpAlphaVDev)
+ {
+ // #i32109#: Make bitmap area opaque
+ mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) );
+ }
+ }
+ }
+ }
+}
+
+bool OutputDevice::DrawTransformBitmapExDirect(
+ const basegfx::B2DHomMatrix& aFullTransform,
+ const BitmapEx& rBitmapEx,
+ double fAlpha)
+{
+ assert(!is_double_buffered_window());
+
+ bool bDone = false;
+
+ // try to paint directly
+ const basegfx::B2DPoint aNull(aFullTransform * basegfx::B2DPoint(0.0, 0.0));
+ const basegfx::B2DPoint aTopX(aFullTransform * basegfx::B2DPoint(1.0, 0.0));
+ const basegfx::B2DPoint aTopY(aFullTransform * basegfx::B2DPoint(0.0, 1.0));
+ SalBitmap* pSalSrcBmp = rBitmapEx.GetBitmap().ImplGetSalBitmap().get();
+ AlphaMask aAlphaBitmap;
+
+ if(rBitmapEx.IsAlpha())
+ {
+ aAlphaBitmap = rBitmapEx.GetAlphaMask();
+ }
+ else if (mpAlphaVDev)
+ {
+ aAlphaBitmap = AlphaMask(rBitmapEx.GetSizePixel());
+ aAlphaBitmap.Erase(0); // opaque
+ }
+
+ SalBitmap* pSalAlphaBmp = aAlphaBitmap.GetBitmap().ImplGetSalBitmap().get();
+
+ bDone = mpGraphics->DrawTransformedBitmap(
+ aNull,
+ aTopX,
+ aTopY,
+ *pSalSrcBmp,
+ pSalAlphaBmp,
+ fAlpha,
+ *this);
+
+ if (mpAlphaVDev)
+ {
+ // Merge bitmap alpha to alpha device
+ AlphaMask aAlpha(rBitmapEx.GetSizePixel());
+ aAlpha.Erase( ( 1 - fAlpha ) * 255 );
+ mpAlphaVDev->DrawTransformBitmapExDirect(aFullTransform, BitmapEx(aAlpha.GetBitmap(), aAlphaBitmap));
+ }
+
+ return bDone;
+};
+
+bool OutputDevice::TransformAndReduceBitmapExToTargetRange(
+ const basegfx::B2DHomMatrix& aFullTransform,
+ basegfx::B2DRange &aVisibleRange,
+ double &fMaximumArea)
+{
+ // limit TargetRange to existing pixels (if pixel device)
+ // first get discrete range of object
+ basegfx::B2DRange aFullPixelRange(aVisibleRange);
+
+ aFullPixelRange.transform(aFullTransform);
+
+ if(basegfx::fTools::equalZero(aFullPixelRange.getWidth()) || basegfx::fTools::equalZero(aFullPixelRange.getHeight()))
+ {
+ // object is outside of visible area
+ return false;
+ }
+
+ // now get discrete target pixels; start with OutDev pixel size and evtl.
+ // intersect with active clipping area
+ basegfx::B2DRange aOutPixel(
+ 0.0,
+ 0.0,
+ GetOutputSizePixel().Width(),
+ GetOutputSizePixel().Height());
+
+ if(IsClipRegion())
+ {
+ tools::Rectangle aRegionRectangle(GetActiveClipRegion().GetBoundRect());
+
+ // caution! Range from rectangle, one too much (!)
+ aRegionRectangle.AdjustRight(-1);
+ aRegionRectangle.AdjustBottom(-1);
+ aOutPixel.intersect( vcl::unotools::b2DRectangleFromRectangle(aRegionRectangle) );
+ }
+
+ if(aOutPixel.isEmpty())
+ {
+ // no active output area
+ return false;
+ }
+
+ // if aFullPixelRange is not completely inside of aOutPixel,
+ // reduction of target pixels is possible
+ basegfx::B2DRange aVisiblePixelRange(aFullPixelRange);
+
+ if(!aOutPixel.isInside(aFullPixelRange))
+ {
+ aVisiblePixelRange.intersect(aOutPixel);
+
+ if(aVisiblePixelRange.isEmpty())
+ {
+ // nothing in visible part, reduces to nothing
+ return false;
+ }
+
+ // aVisiblePixelRange contains the reduced output area in
+ // discrete coordinates. To make it useful everywhere, make it relative to
+ // the object range
+ basegfx::B2DHomMatrix aMakeVisibleRangeRelative;
+
+ aVisibleRange = aVisiblePixelRange;
+ aMakeVisibleRangeRelative.translate(
+ -aFullPixelRange.getMinX(),
+ -aFullPixelRange.getMinY());
+ aMakeVisibleRangeRelative.scale(
+ 1.0 / aFullPixelRange.getWidth(),
+ 1.0 / aFullPixelRange.getHeight());
+ aVisibleRange.transform(aMakeVisibleRangeRelative);
+ }
+
+ // for pixel devices, do *not* limit size, else OutputDevice::DrawDeviceAlphaBitmap
+ // will create another, badly scaled bitmap to do the job. Nonetheless, do a
+ // maximum clipping of something big (1600x1280x2). Add 1.0 to avoid rounding
+ // errors in rough estimations
+ const double fNewMaxArea(aVisiblePixelRange.getWidth() * aVisiblePixelRange.getHeight());
+
+ fMaximumArea = std::min(4096000.0, fNewMaxArea + 1.0);
+
+ return true;
+}
+
+// MM02 add some test class to get a simple timer-based output to be able
+// to check if it gets faster - and how much. Uncomment next line or set
+// DO_TIME_TEST for compile time if you want to use it
+// #define DO_TIME_TEST
+#ifdef DO_TIME_TEST
+#include <tools/time.hxx>
+struct LocalTimeTest
+{
+ const sal_uInt64 nStartTime;
+ LocalTimeTest() : nStartTime(tools::Time::GetSystemTicks()) {}
+ ~LocalTimeTest()
+ {
+ const sal_uInt64 nEndTime(tools::Time::GetSystemTicks());
+ const sal_uInt64 nDiffTime(nEndTime - nStartTime);
+
+ if(nDiffTime > 0)
+ {
+ OStringBuffer aOutput("Time: ");
+ OString aNumber(OString::number(nDiffTime));
+ aOutput.append(aNumber);
+ OSL_FAIL(aOutput.getStr());
+ }
+ }
+};
+#endif
+
+void OutputDevice::DrawTransformedBitmapEx(
+ const basegfx::B2DHomMatrix& rTransformation,
+ const BitmapEx& rBitmapEx,
+ double fAlpha)
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if(rBitmapEx.IsEmpty())
+ return;
+
+ if(rtl::math::approxEqual( fAlpha, 0.0 ))
+ return;
+
+ // MM02 compared to other public methods of OutputDevice
+ // this test was missing and led to zero-ptr-accesses
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ const bool bMetafile(nullptr != mpMetaFile);
+ /*
+ tdf#135325 typically in these OutputDevice methods, for the in
+ record-to-metafile case the MetaFile is already written to before the
+ test against mbOutputClipped to determine that output to the current
+ device would result in no visual output. In this case the metafile is
+ written after the test, so we must continue past mbOutputClipped if
+ recording to a metafile. It's typical to record with a device of nominal
+ size and play back later against something of a totally different size.
+ */
+ if (mbOutputClipped && !bMetafile)
+ return;
+
+#ifdef DO_TIME_TEST
+ // MM02 start time test when some data (not for trivial stuff). Will
+ // trigger and show data when leaving this method by destructing helper
+ static const char* pEnableBitmapDrawTimerTimer(getenv("SAL_ENABLE_TIMER_BITMAPDRAW"));
+ static bool bUseTimer(nullptr != pEnableBitmapDrawTimerTimer);
+ std::unique_ptr<LocalTimeTest> aTimeTest(
+ bUseTimer && rBitmapEx.GetSizeBytes() > 10000
+ ? new LocalTimeTest()
+ : nullptr);
+#endif
+
+ BitmapEx bitmapEx = rBitmapEx;
+
+ const bool bInvert(RasterOp::Invert == meRasterOp);
+ const bool bBitmapChangedColor(mnDrawMode & (DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | DrawModeFlags::GrayBitmap ));
+ const bool bTryDirectPaint(!bInvert && !bBitmapChangedColor && !bMetafile);
+ // tdf#130768 CAUTION(!) using GetViewTransformation() is *not* enough here, it may
+ // be that mnOutOffX/mnOutOffY is used - see AOO bug 75163, mentioned at
+ // ImplGetDeviceTransformation declaration
+ basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rTransformation);
+
+ // First try to handle additional alpha blending, either directly, or modify the bitmap.
+ if(!rtl::math::approxEqual( fAlpha, 1.0 ))
+ {
+ if(bTryDirectPaint)
+ {
+ if(DrawTransformBitmapExDirect(aFullTransform, bitmapEx, fAlpha))
+ {
+ // we are done
+ return;
+ }
+ }
+ // Apply the alpha manually.
+ sal_uInt8 nTransparency( static_cast<sal_uInt8>( ::basegfx::fround( 255.0*(1.0 - fAlpha) + .5) ) );
+ AlphaMask aAlpha( bitmapEx.GetSizePixel(), &nTransparency );
+ if( bitmapEx.IsAlpha())
+ aAlpha.BlendWith( bitmapEx.GetAlphaMask());
+ bitmapEx = BitmapEx( bitmapEx.GetBitmap(), aAlpha );
+ }
+
+ // If the backend's implementation is known to not need any optimizations here, pass to it directly.
+ // With most backends it's more performant to try to simplify to DrawBitmapEx() first.
+ if(bTryDirectPaint && mpGraphics->HasFastDrawTransformedBitmap() && DrawTransformBitmapExDirect(aFullTransform, bitmapEx))
+ return;
+
+ // decompose matrix to check rotation and shear
+ basegfx::B2DVector aScale, aTranslate;
+ double fRotate, fShearX;
+ rTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
+ const bool bRotated(!basegfx::fTools::equalZero(fRotate));
+ const bool bSheared(!basegfx::fTools::equalZero(fShearX));
+ const bool bMirroredX(basegfx::fTools::less(aScale.getX(), 0.0));
+ const bool bMirroredY(basegfx::fTools::less(aScale.getY(), 0.0));
+
+ if(!bRotated && !bSheared && !bMirroredX && !bMirroredY)
+ {
+ // with no rotation, shear or mirroring it can be mapped to DrawBitmapEx
+ // do *not* execute the mirroring here, it's done in the fallback
+ // #i124580# the correct DestSize needs to be calculated based on MaxXY values
+ Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY()));
+ const Size aDestSize(
+ basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(),
+ basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y());
+ const Point aOrigin = GetMapMode().GetOrigin();
+ if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel)
+ {
+ aDestPt.Move(aOrigin.getX(), aOrigin.getY());
+ EnableMapMode(false);
+ }
+
+ DrawBitmapEx(aDestPt, aDestSize, bitmapEx);
+ if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel)
+ {
+ EnableMapMode();
+ aDestPt.Move(-aOrigin.getX(), -aOrigin.getY());
+ }
+ return;
+ }
+
+ // Try the backend's implementation before resorting to the slower fallback here.
+ if(bTryDirectPaint && DrawTransformBitmapExDirect(aFullTransform, bitmapEx))
+ return;
+
+ // take the fallback when no rotate and shear, but mirror (else we would have done this above)
+ if(!bRotated && !bSheared)
+ {
+ // with no rotation or shear it can be mapped to DrawBitmapEx
+ // do *not* execute the mirroring here, it's done in the fallback
+ // #i124580# the correct DestSize needs to be calculated based on MaxXY values
+ const Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY()));
+ const Size aDestSize(
+ basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(),
+ basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y());
+
+ DrawBitmapEx(aDestPt, aDestSize, bitmapEx);
+ return;
+ }
+
+ // at this point we are either sheared or rotated or both
+ assert(bSheared || bRotated);
+
+ // fallback; create transformed bitmap the hard way (back-transform
+ // the pixels) and paint
+ basegfx::B2DRange aVisibleRange(0.0, 0.0, 1.0, 1.0);
+
+ // limit maximum area to something looking good for non-pixel-based targets (metafile, printer)
+ // by using a fixed minimum (allow at least, but no need to utilize) for good smoothing and an area
+ // dependent of original size for good quality when e.g. rotated/sheared. Still, limit to a maximum
+ // to avoid crashes/resource problems (ca. 1500x3000 here)
+ const Size& rOriginalSizePixel(bitmapEx.GetSizePixel());
+ const double fOrigArea(rOriginalSizePixel.Width() * rOriginalSizePixel.Height() * 0.5);
+ const double fOrigAreaScaled(fOrigArea * 1.44);
+ double fMaximumArea(std::clamp(fOrigAreaScaled, 1000000.0, 4500000.0));
+
+ if(!bMetafile)
+ {
+ if ( !TransformAndReduceBitmapExToTargetRange( aFullTransform, aVisibleRange, fMaximumArea ) )
+ return;
+ }
+
+ if(aVisibleRange.isEmpty())
+ return;
+
+ BitmapEx aTransformed(bitmapEx);
+
+ // #122923# when the result needs an alpha channel due to being rotated or sheared
+ // and thus uncovering areas, add these channels so that the own transformer (used
+ // in getTransformed) also creates a transformed alpha channel
+ if(!aTransformed.IsAlpha() && (bSheared || bRotated))
+ {
+ // parts will be uncovered, extend aTransformed with a mask bitmap
+ const Bitmap aContent(aTransformed.GetBitmap());
+
+ AlphaMask aMaskBmp(aContent.GetSizePixel());
+ aMaskBmp.Erase(0);
+
+ aTransformed = BitmapEx(aContent, aMaskBmp);
+ }
+
+ basegfx::B2DVector aFullScale, aFullTranslate;
+ double fFullRotate, fFullShearX;
+ aFullTransform.decompose(aFullScale, aFullTranslate, fFullRotate, fFullShearX);
+
+ double fSourceRatio = 1.0;
+ if (rOriginalSizePixel.getHeight() != 0)
+ {
+ fSourceRatio = rOriginalSizePixel.getWidth() / rOriginalSizePixel.getHeight();
+ }
+ double fTargetRatio = 1.0;
+ if (aFullScale.getY() != 0)
+ {
+ fTargetRatio = aFullScale.getX() / aFullScale.getY();
+ }
+ bool bAspectRatioKept = rtl::math::approxEqual(fSourceRatio, fTargetRatio);
+ if (bSheared || !bAspectRatioKept)
+ {
+ // Not only rotation, or scaling does not keep aspect ratio.
+ aTransformed = aTransformed.getTransformed(
+ aFullTransform,
+ aVisibleRange,
+ fMaximumArea);
+ }
+ else
+ {
+ // Just rotation, can do that directly.
+ fFullRotate = fmod(fFullRotate * -1, 2 * M_PI);
+ if (fFullRotate < 0)
+ {
+ fFullRotate += 2 * M_PI;
+ }
+ Degree10 nAngle10(basegfx::fround(basegfx::rad2deg<10>(fFullRotate)));
+ aTransformed.Rotate(nAngle10, COL_TRANSPARENT);
+ }
+ basegfx::B2DRange aTargetRange(0.0, 0.0, 1.0, 1.0);
+
+ // get logic object target range
+ aTargetRange.transform(rTransformation);
+
+ // get from unified/relative VisibleRange to logoc one
+ aVisibleRange.transform(
+ basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aTargetRange.getRange(),
+ aTargetRange.getMinimum()));
+
+ // extract point and size; do not remove size, the bitmap may have been prepared reduced by purpose
+ // #i124580# the correct DestSize needs to be calculated based on MaxXY values
+ const Point aDestPt(basegfx::fround(aVisibleRange.getMinX()), basegfx::fround(aVisibleRange.getMinY()));
+ const Size aDestSize(
+ basegfx::fround(aVisibleRange.getMaxX()) - aDestPt.X(),
+ basegfx::fround(aVisibleRange.getMaxY()) - aDestPt.Y());
+
+ DrawBitmapEx(aDestPt, aDestSize, aTransformed);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/outdev/clipping.cxx b/vcl/source/outdev/clipping.cxx
new file mode 100644
index 0000000000..6efc9df9d0
--- /dev/null
+++ b/vcl/source/outdev/clipping.cxx
@@ -0,0 +1,224 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <osl/diagnose.h>
+#include <tools/debug.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+void OutputDevice::SaveBackground(VirtualDevice& rSaveDevice,
+ const Point& rPos, const Size& rSize, const Size& rBackgroundSize) const
+{
+ rSaveDevice.DrawOutDev(Point(), rBackgroundSize, rPos, rSize, *this);
+}
+
+vcl::Region OutputDevice::GetClipRegion() const
+{
+
+ return PixelToLogic( maRegion );
+}
+
+void OutputDevice::SetClipRegion()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaClipRegionAction( vcl::Region(), false ) );
+
+ SetDeviceClipRegion( nullptr );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetClipRegion();
+}
+
+void OutputDevice::SetClipRegion( const vcl::Region& rRegion )
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaClipRegionAction( rRegion, true ) );
+
+ if ( rRegion.IsNull() )
+ {
+ SetDeviceClipRegion( nullptr );
+ }
+ else
+ {
+ vcl::Region aRegion = LogicToPixel( rRegion );
+ SetDeviceClipRegion( &aRegion );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetClipRegion( rRegion );
+}
+
+bool OutputDevice::SelectClipRegion( const vcl::Region& rRegion, SalGraphics* pGraphics )
+{
+ DBG_TESTSOLARMUTEX();
+
+ if( !pGraphics )
+ {
+ if( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+ pGraphics = mpGraphics;
+ }
+
+ pGraphics->SetClipRegion( rRegion, *this );
+ return true;
+}
+
+void OutputDevice::MoveClipRegion( tools::Long nHorzMove, tools::Long nVertMove )
+{
+
+ if ( mbClipRegion )
+ {
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaMoveClipRegionAction( nHorzMove, nVertMove ) );
+
+ maRegion.Move( ImplLogicWidthToDevicePixel( nHorzMove ),
+ ImplLogicHeightToDevicePixel( nVertMove ) );
+ mbInitClipRegion = true;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->MoveClipRegion( nHorzMove, nVertMove );
+}
+
+void OutputDevice::IntersectClipRegion( const tools::Rectangle& rRect )
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaISectRectClipRegionAction( rRect ) );
+
+ tools::Rectangle aRect = LogicToPixel( rRect );
+ maRegion.Intersect( aRect );
+ mbClipRegion = true;
+ mbInitClipRegion = true;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->IntersectClipRegion( rRect );
+}
+
+void OutputDevice::IntersectClipRegion( const vcl::Region& rRegion )
+{
+
+ if(!rRegion.IsNull())
+ {
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaISectRegionClipRegionAction( rRegion ) );
+
+ vcl::Region aRegion = LogicToPixel( rRegion );
+ maRegion.Intersect( aRegion );
+ mbClipRegion = true;
+ mbInitClipRegion = true;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->IntersectClipRegion( rRegion );
+}
+
+void OutputDevice::InitClipRegion()
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( mbClipRegion )
+ {
+ if ( maRegion.IsEmpty() )
+ mbOutputClipped = true;
+ else
+ {
+ mbOutputClipped = false;
+
+ // #102532# Respect output offset also for clip region
+ vcl::Region aRegion = ClipToDeviceBounds(ImplPixelToDevicePixel(maRegion));
+
+ if ( aRegion.IsEmpty() )
+ {
+ mbOutputClipped = true;
+ }
+ else
+ {
+ mbOutputClipped = false;
+ SelectClipRegion( aRegion );
+ }
+ }
+
+ mbClipRegionSet = true;
+ }
+ else
+ {
+ if ( mbClipRegionSet )
+ {
+ if (mpGraphics)
+ mpGraphics->ResetClipRegion();
+ mbClipRegionSet = false;
+ }
+
+ mbOutputClipped = false;
+ }
+
+ mbInitClipRegion = false;
+}
+
+vcl::Region OutputDevice::ClipToDeviceBounds(vcl::Region aRegion) const
+{
+ aRegion.Intersect(tools::Rectangle{mnOutOffX,
+ mnOutOffY,
+ mnOutOffX + GetOutputWidthPixel() - 1,
+ mnOutOffY + GetOutputHeightPixel() - 1
+ });
+ return aRegion;
+}
+
+vcl::Region OutputDevice::GetActiveClipRegion() const
+{
+ return GetClipRegion();
+}
+
+void OutputDevice::ClipToPaintRegion(tools::Rectangle& /*rDstRect*/)
+{
+ // this is only used in Window, but we still need it as it's called
+ // on in other clipping functions
+}
+
+void OutputDevice::SetDeviceClipRegion( const vcl::Region* pRegion )
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( !pRegion )
+ {
+ if ( mbClipRegion )
+ {
+ maRegion = vcl::Region(true);
+ mbClipRegion = false;
+ mbInitClipRegion = true;
+ }
+ }
+ else
+ {
+ maRegion = *pRegion;
+ mbClipRegion = true;
+ mbInitClipRegion = true;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/curvedshapes.cxx b/vcl/source/outdev/curvedshapes.cxx
new file mode 100644
index 0000000000..b5a13fb721
--- /dev/null
+++ b/vcl/source/outdev/curvedshapes.cxx
@@ -0,0 +1,212 @@
+/* -*- 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/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+#include <cassert>
+
+void OutputDevice::DrawEllipse( const tools::Rectangle& rRect )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaEllipseAction( rRect ) );
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() )
+ return;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+ if ( aRect.IsEmpty() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ tools::Polygon aRectPoly( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 );
+ if ( aRectPoly.GetSize() >= 2 )
+ {
+ Point* pPtAry = aRectPoly.GetPointAry();
+ if ( !mbFillColor )
+ mpGraphics->DrawPolyLine( aRectPoly.GetSize(), pPtAry, *this );
+ else
+ {
+ if ( mbInitFillColor )
+ InitFillColor();
+ mpGraphics->DrawPolygon( aRectPoly.GetSize(), pPtAry, *this );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawEllipse( rRect );
+}
+
+void OutputDevice::DrawArc( const tools::Rectangle& rRect,
+ const Point& rStartPt, const Point& rEndPt )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaArcAction( rRect, rStartPt, rEndPt ) );
+
+ if ( !IsDeviceOutputNecessary() || !mbLineColor || ImplIsRecordLayout() )
+ return;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+ if ( aRect.IsEmpty() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ const Point aStart( ImplLogicToDevicePixel( rStartPt ) );
+ const Point aEnd( ImplLogicToDevicePixel( rEndPt ) );
+ tools::Polygon aArcPoly( aRect, aStart, aEnd, PolyStyle::Arc );
+
+ if ( aArcPoly.GetSize() >= 2 )
+ {
+ Point* pPtAry = aArcPoly.GetPointAry();
+ mpGraphics->DrawPolyLine( aArcPoly.GetSize(), pPtAry, *this );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawArc( rRect, rStartPt, rEndPt );
+}
+
+void OutputDevice::DrawPie( const tools::Rectangle& rRect,
+ const Point& rStartPt, const Point& rEndPt )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPieAction( rRect, rStartPt, rEndPt ) );
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() )
+ return;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+ if ( aRect.IsEmpty() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ const Point aStart( ImplLogicToDevicePixel( rStartPt ) );
+ const Point aEnd( ImplLogicToDevicePixel( rEndPt ) );
+ tools::Polygon aPiePoly( aRect, aStart, aEnd, PolyStyle::Pie );
+
+ if ( aPiePoly.GetSize() >= 2 )
+ {
+ Point* pPtAry = aPiePoly.GetPointAry();
+ if ( !mbFillColor )
+ mpGraphics->DrawPolyLine( aPiePoly.GetSize(), pPtAry, *this );
+ else
+ {
+ if ( mbInitFillColor )
+ InitFillColor();
+ mpGraphics->DrawPolygon( aPiePoly.GetSize(), pPtAry, *this );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPie( rRect, rStartPt, rEndPt );
+}
+
+void OutputDevice::DrawChord( const tools::Rectangle& rRect,
+ const Point& rStartPt, const Point& rEndPt )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaChordAction( rRect, rStartPt, rEndPt ) );
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() )
+ return;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+ if ( aRect.IsEmpty() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ const Point aStart( ImplLogicToDevicePixel( rStartPt ) );
+ const Point aEnd( ImplLogicToDevicePixel( rEndPt ) );
+ tools::Polygon aChordPoly( aRect, aStart, aEnd, PolyStyle::Chord );
+
+ if ( aChordPoly.GetSize() >= 2 )
+ {
+ Point* pPtAry = aChordPoly.GetPointAry();
+ if ( !mbFillColor )
+ mpGraphics->DrawPolyLine( aChordPoly.GetSize(), pPtAry, *this );
+ else
+ {
+ if ( mbInitFillColor )
+ InitFillColor();
+ mpGraphics->DrawPolygon( aChordPoly.GetSize(), pPtAry, *this );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawChord( rRect, rStartPt, rEndPt );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/eps.cxx b/vcl/source/outdev/eps.cxx
new file mode 100644
index 0000000000..3f436ee327
--- /dev/null
+++ b/vcl/source/outdev/eps.cxx
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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/gfxlink.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+bool OutputDevice::DrawEPS( const Point& rPoint, const Size& rSize,
+ const GfxLink& rGfxLink, GDIMetaFile* pSubst )
+{
+ if ( mpMetaFile )
+ {
+ GDIMetaFile aSubst;
+
+ if( pSubst )
+ aSubst = *pSubst;
+
+ mpMetaFile->AddAction( new MetaEPSAction( rPoint, rSize, rGfxLink, aSubst ) );
+ }
+
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return true;
+
+ if( mbOutputClipped )
+ return true;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( tools::Rectangle( rPoint, rSize ) ) );
+
+ bool bDrawn = true;
+
+ if( !aRect.IsEmpty() )
+ {
+ // draw the real EPS graphics
+ if( rGfxLink.GetData() && rGfxLink.GetDataSize() )
+ {
+ if( !mpGraphics && !AcquireGraphics() )
+ return bDrawn;
+ assert(mpGraphics);
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ aRect.Normalize();
+ bDrawn = mpGraphics->DrawEPS( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(),
+ const_cast<sal_uInt8*>(rGfxLink.GetData()), rGfxLink.GetDataSize(), *this );
+ }
+
+ // else draw the substitution graphics
+ if( !bDrawn && pSubst )
+ {
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+
+ mpMetaFile = nullptr;
+ Graphic(*pSubst).Draw(*this, rPoint, rSize);
+ mpMetaFile = pOldMetaFile;
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawEPS( rPoint, rSize, rGfxLink, pSubst );
+
+ return bDrawn;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/outdev/fill.cxx b/vcl/source/outdev/fill.cxx
new file mode 100644
index 0000000000..c684595fb2
--- /dev/null
+++ b/vcl/source/outdev/fill.cxx
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 <tools/debug.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+
+void OutputDevice::SetFillColor()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaFillColorAction( Color(), false ) );
+
+ if ( mbFillColor )
+ {
+ mbInitFillColor = true;
+ mbFillColor = false;
+ maFillColor = COL_TRANSPARENT;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetFillColor();
+}
+
+void OutputDevice::SetFillColor( const Color& rColor )
+{
+ Color aColor(vcl::drawmode::GetFillColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaFillColorAction( aColor, true ) );
+
+ if ( aColor.IsTransparent() )
+ {
+ if ( mbFillColor )
+ {
+ mbInitFillColor = true;
+ mbFillColor = false;
+ maFillColor = COL_TRANSPARENT;
+ }
+ }
+ else
+ {
+ if ( maFillColor != aColor )
+ {
+ mbInitFillColor = true;
+ mbFillColor = true;
+ maFillColor = aColor;
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetFillColor( COL_ALPHA_OPAQUE );
+}
+
+void OutputDevice::InitFillColor()
+{
+ DBG_TESTSOLARMUTEX();
+
+ if( mbFillColor )
+ {
+ if( RasterOp::N0 == meRasterOp )
+ mpGraphics->SetROPFillColor( SalROPColor::N0 );
+ else if( RasterOp::N1 == meRasterOp )
+ mpGraphics->SetROPFillColor( SalROPColor::N1 );
+ else if( RasterOp::Invert == meRasterOp )
+ mpGraphics->SetROPFillColor( SalROPColor::Invert );
+ else
+ mpGraphics->SetFillColor( maFillColor );
+ }
+ else
+ {
+ mpGraphics->SetFillColor();
+ }
+
+ mbInitFillColor = false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/outdev/font.cxx b/vcl/source/outdev/font.cxx
new file mode 100644
index 0000000000..2086db7f63
--- /dev/null
+++ b/vcl/source/outdev/font.cxx
@@ -0,0 +1,1293 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <i18nlangtag/lang.h>
+#include <unotools/configmgr.hxx>
+
+#include <vcl/event.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/print.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/virdev.hxx>
+
+#include <window.h>
+#include <font/EmphasisMark.hxx>
+
+#include <ImplLayoutArgs.hxx>
+#include <drawmode.hxx>
+#include <impfontcache.hxx>
+#include <font/DirectFontSubstitution.hxx>
+#include <font/PhysicalFontFaceCollection.hxx>
+#include <font/PhysicalFontCollection.hxx>
+#include <font/FeatureCollector.hxx>
+#include <impglyphitem.hxx>
+#include <sallayout.hxx>
+#include <salgdi.hxx>
+#include <svdata.hxx>
+
+#include <unicode/uchar.h>
+
+#include <strings.hrc>
+
+void OutputDevice::SetFont( const vcl::Font& rNewFont )
+{
+ vcl::Font aFont = vcl::drawmode::GetFont(rNewFont, GetDrawMode(), GetSettings().GetStyleSettings());
+
+ if ( mpMetaFile )
+ {
+ mpMetaFile->AddAction( new MetaFontAction( aFont ) );
+ // the color and alignment actions don't belong here
+ // TODO: get rid of them without breaking anything...
+ mpMetaFile->AddAction( new MetaTextAlignAction( aFont.GetAlignment() ) );
+ mpMetaFile->AddAction( new MetaTextFillColorAction( aFont.GetFillColor(), !aFont.IsTransparent() ) );
+ }
+
+ if ( maFont.IsSameInstance( aFont ) )
+ return;
+
+ // Optimization MT/HDU: COL_TRANSPARENT means SetFont should ignore the font color,
+ // because SetTextColor() is used for this.
+ // #i28759# maTextColor might have been changed behind our back, commit then, too.
+ if( aFont.GetColor() != COL_TRANSPARENT
+ && (aFont.GetColor() != maFont.GetColor() || aFont.GetColor() != maTextColor ) )
+ {
+ maTextColor = aFont.GetColor();
+ mbInitTextColor = true;
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextColorAction( aFont.GetColor() ) );
+ }
+ maFont = aFont;
+ mbNewFont = true;
+
+ if( !mpAlphaVDev )
+ return;
+
+ // #i30463#
+ // Since SetFont might change the text color, apply that only
+ // selectively to alpha vdev (which normally paints opaque text
+ // with COL_BLACK)
+ if( aFont.GetColor() != COL_TRANSPARENT )
+ {
+ mpAlphaVDev->SetTextColor( COL_ALPHA_OPAQUE );
+ aFont.SetColor( COL_TRANSPARENT );
+ }
+
+ mpAlphaVDev->SetFont( aFont );
+}
+
+FontMetric OutputDevice::GetFontMetricFromCollection(int nDevFontIndex) const
+{
+ ImplInitFontList();
+
+ if (nDevFontIndex < GetFontFaceCollectionCount())
+ return FontMetric(*mpFontFaceCollection->Get(nDevFontIndex));
+
+ return FontMetric();
+}
+
+int OutputDevice::GetFontFaceCollectionCount() const
+{
+ if( !mpFontFaceCollection )
+ {
+ if (!mxFontCollection)
+ {
+ return 0;
+ }
+
+ mpFontFaceCollection = mxFontCollection->GetFontFaceCollection();
+
+ if (!mpFontFaceCollection->Count())
+ {
+ mpFontFaceCollection.reset();
+ return 0;
+ }
+ }
+ return mpFontFaceCollection->Count();
+}
+
+bool OutputDevice::IsFontAvailable( std::u16string_view rFontName ) const
+{
+ ImplInitFontList();
+ vcl::font::PhysicalFontFamily* pFound = mxFontCollection->FindFontFamily( rFontName );
+ return (pFound != nullptr);
+}
+
+bool OutputDevice::AddTempDevFont( const OUString& rFileURL, const OUString& rFontName )
+{
+ ImplInitFontList();
+
+ if( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+
+ bool bRC = mpGraphics->AddTempDevFont( mxFontCollection.get(), rFileURL, rFontName );
+ if( !bRC )
+ return false;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->AddTempDevFont( rFileURL, rFontName );
+
+ return true;
+}
+
+bool OutputDevice::GetFontFeatures(std::vector<vcl::font::Feature>& rFontFeatures) const
+{
+ if (!ImplNewFont())
+ return false;
+
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ if (!pFontInstance)
+ return false;
+
+ const LanguageTag& rOfficeLanguage = Application::GetSettings().GetUILanguageTag();
+
+ vcl::font::FeatureCollector aFeatureCollector(pFontInstance->GetFontFace(), rFontFeatures, rOfficeLanguage);
+ aFeatureCollector.collect();
+
+ return true;
+}
+
+FontMetric OutputDevice::GetFontMetric() const
+{
+ FontMetric aMetric;
+ if (!ImplNewFont())
+ return aMetric;
+
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ FontMetricDataRef xFontMetric = pFontInstance->mxFontMetric;
+
+ // prepare metric
+ aMetric = maFont;
+
+ // set aMetric with info from font
+ aMetric.SetFamilyName( maFont.GetFamilyName() );
+ aMetric.SetStyleName( xFontMetric->GetStyleName() );
+ aMetric.SetFontSize( PixelToLogic( Size( xFontMetric->GetWidth(), xFontMetric->GetAscent() + xFontMetric->GetDescent() - xFontMetric->GetInternalLeading() ) ) );
+ aMetric.SetCharSet( xFontMetric->IsMicrosoftSymbolEncoded() ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE );
+ aMetric.SetFamily( xFontMetric->GetFamilyType() );
+ aMetric.SetPitch( xFontMetric->GetPitch() );
+ aMetric.SetWeight( xFontMetric->GetWeight() );
+ aMetric.SetItalic( xFontMetric->GetItalic() );
+ aMetric.SetAlignment( TextAlign::ALIGN_TOP );
+ aMetric.SetWidthType( xFontMetric->GetWidthType() );
+ if ( pFontInstance->mnOwnOrientation )
+ aMetric.SetOrientation( pFontInstance->mnOwnOrientation );
+ else
+ aMetric.SetOrientation( xFontMetric->GetOrientation() );
+
+ // set remaining metric fields
+ aMetric.SetFullstopCenteredFlag( xFontMetric->IsFullstopCentered() );
+ aMetric.SetBulletOffset( xFontMetric->GetBulletOffset() );
+ aMetric.SetAscent( ImplDevicePixelToLogicHeight( xFontMetric->GetAscent() + mnEmphasisAscent ) );
+ aMetric.SetDescent( ImplDevicePixelToLogicHeight( xFontMetric->GetDescent() + mnEmphasisDescent ) );
+ aMetric.SetInternalLeading( ImplDevicePixelToLogicHeight( xFontMetric->GetInternalLeading() + mnEmphasisAscent ) );
+ // OutputDevice has its own external leading function due to #i60945#
+ aMetric.SetExternalLeading( ImplDevicePixelToLogicHeight( GetFontExtLeading() ) );
+ aMetric.SetLineHeight( ImplDevicePixelToLogicHeight( xFontMetric->GetAscent() + xFontMetric->GetDescent() + mnEmphasisAscent + mnEmphasisDescent ) );
+ aMetric.SetSlant( ImplDevicePixelToLogicHeight( xFontMetric->GetSlant() ) );
+ aMetric.SetHangingBaseline( ImplDevicePixelToLogicHeight( xFontMetric->GetHangingBaseline() ) );
+
+ // get miscellaneous data
+ aMetric.SetQuality( xFontMetric->GetQuality() );
+
+ SAL_INFO("vcl.gdi.fontmetric", "OutputDevice::GetFontMetric:" << aMetric);
+
+ xFontMetric = nullptr;
+
+ return aMetric;
+}
+
+FontMetric OutputDevice::GetFontMetric( const vcl::Font& rFont ) const
+{
+ // select font, query metrics, select original font again
+ vcl::Font aOldFont = GetFont();
+ const_cast<OutputDevice*>(this)->SetFont( rFont );
+ FontMetric aMetric( GetFontMetric() );
+ const_cast<OutputDevice*>(this)->SetFont( aOldFont );
+ return aMetric;
+}
+
+bool OutputDevice::GetFontCharMap( FontCharMapRef& rxFontCharMap ) const
+{
+ if (!InitFont())
+ return false;
+
+ FontCharMapRef xFontCharMap ( mpGraphics->GetFontCharMap() );
+ if (!xFontCharMap.is())
+ {
+ FontCharMapRef xDefaultMap( new FontCharMap() );
+ rxFontCharMap = xDefaultMap;
+ }
+ else
+ rxFontCharMap = xFontCharMap;
+
+ return !rxFontCharMap->IsDefaultMap();
+}
+
+bool OutputDevice::GetFontCapabilities( vcl::FontCapabilities& rFontCapabilities ) const
+{
+ if (!InitFont())
+ return false;
+ return mpGraphics->GetFontCapabilities(rFontCapabilities);
+}
+
+tools::Long OutputDevice::GetFontExtLeading() const
+{
+ return mpFontInstance->mxFontMetric->GetExternalLeading();
+}
+
+void OutputDevice::ImplClearFontData( const bool bNewFontLists )
+{
+ // the currently selected logical font is no longer needed
+ mpFontInstance.clear();
+
+ mbInitFont = true;
+ mbNewFont = true;
+
+ if ( bNewFontLists )
+ {
+ mpFontFaceCollection.reset();
+
+ // release all physically selected fonts on this device
+ if( AcquireGraphics() )
+ mpGraphics->ReleaseFonts();
+ }
+
+ ImplSVData* pSVData = ImplGetSVData();
+
+ if (mxFontCache && mxFontCache != pSVData->maGDIData.mxScreenFontCache)
+ mxFontCache->Invalidate();
+
+ if (bNewFontLists && AcquireGraphics())
+ {
+ if (mxFontCollection && mxFontCollection != pSVData->maGDIData.mxScreenFontList)
+ mxFontCollection->Clear();
+ }
+}
+
+void OutputDevice::RefreshFontData( const bool bNewFontLists )
+{
+ ImplRefreshFontData( bNewFontLists );
+}
+
+void OutputDevice::ImplRefreshFontData( const bool bNewFontLists )
+{
+ if (bNewFontLists && AcquireGraphics())
+ mpGraphics->GetDevFontList( mxFontCollection.get() );
+}
+
+void OutputDevice::ImplUpdateFontData()
+{
+ ImplClearFontData( true/*bNewFontLists*/ );
+ ImplRefreshFontData( true/*bNewFontLists*/ );
+}
+
+void OutputDevice::ImplClearAllFontData(bool bNewFontLists)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+
+ ImplUpdateFontDataForAllFrames( &OutputDevice::ImplClearFontData, bNewFontLists );
+
+ // clear global font lists to have them updated
+ pSVData->maGDIData.mxScreenFontCache->Invalidate();
+ if ( !bNewFontLists )
+ return;
+
+ pSVData->maGDIData.mxScreenFontList->Clear();
+ vcl::Window * pFrame = pSVData->maFrameData.mpFirstFrame;
+ if ( pFrame )
+ {
+ if ( pFrame->GetOutDev()->AcquireGraphics() )
+ {
+ OutputDevice *pDevice = pFrame->GetOutDev();
+ pDevice->mpGraphics->ClearDevFontCache();
+ pDevice->mpGraphics->GetDevFontList(pFrame->mpWindowImpl->mpFrameData->mxFontCollection.get());
+ }
+ }
+}
+
+void OutputDevice::ImplRefreshAllFontData(bool bNewFontLists)
+{
+ ImplUpdateFontDataForAllFrames( &OutputDevice::ImplRefreshFontData, bNewFontLists );
+}
+
+void OutputDevice::ImplUpdateAllFontData(bool bNewFontLists)
+{
+ OutputDevice::ImplClearAllFontData(bNewFontLists);
+ OutputDevice::ImplRefreshAllFontData(bNewFontLists);
+}
+
+void OutputDevice::ImplUpdateFontDataForAllFrames( const FontUpdateHandler_t pHdl, const bool bNewFontLists )
+{
+ ImplSVData* const pSVData = ImplGetSVData();
+
+ // update all windows
+ vcl::Window* pFrame = pSVData->maFrameData.mpFirstFrame;
+ while ( pFrame )
+ {
+ ( pFrame->GetOutDev()->*pHdl )( bNewFontLists );
+
+ vcl::Window* pSysWin = pFrame->mpWindowImpl->mpFrameData->mpFirstOverlap;
+ while ( pSysWin )
+ {
+ ( pSysWin->GetOutDev()->*pHdl )( bNewFontLists );
+ pSysWin = pSysWin->mpWindowImpl->mpNextOverlap;
+ }
+
+ pFrame = pFrame->mpWindowImpl->mpFrameData->mpNextFrame;
+ }
+
+ // update all virtual devices
+ VirtualDevice* pVirDev = pSVData->maGDIData.mpFirstVirDev;
+ while ( pVirDev )
+ {
+ ( pVirDev->*pHdl )( bNewFontLists );
+ pVirDev = pVirDev->mpNext;
+ }
+
+ // update all printers
+ Printer* pPrinter = pSVData->maGDIData.mpFirstPrinter;
+ while ( pPrinter )
+ {
+ ( pPrinter->*pHdl )( bNewFontLists );
+ pPrinter = pPrinter->mpNext;
+ }
+}
+
+void OutputDevice::BeginFontSubstitution()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maGDIData.mbFontSubChanged = false;
+}
+
+void OutputDevice::EndFontSubstitution()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( pSVData->maGDIData.mbFontSubChanged )
+ {
+ ImplUpdateAllFontData( false );
+
+ DataChangedEvent aDCEvt( DataChangedEventType::FONTSUBSTITUTION );
+ Application::ImplCallEventListenersApplicationDataChanged(&aDCEvt);
+ Application::NotifyAllWindows( aDCEvt );
+ pSVData->maGDIData.mbFontSubChanged = false;
+ }
+}
+
+void OutputDevice::AddFontSubstitute( const OUString& rFontName,
+ const OUString& rReplaceFontName,
+ AddFontSubstituteFlags nFlags )
+{
+ vcl::font::DirectFontSubstitution*& rpSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst;
+ if( !rpSubst )
+ rpSubst = new vcl::font::DirectFontSubstitution;
+ rpSubst->AddFontSubstitute( rFontName, rReplaceFontName, nFlags );
+ ImplGetSVData()->maGDIData.mbFontSubChanged = true;
+}
+
+void OutputDevice::RemoveFontsSubstitute()
+{
+ vcl::font::DirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst;
+ if( pSubst )
+ pSubst->RemoveFontsSubstitute();
+}
+
+//hidpi TODO: This routine has hard-coded font-sizes that break places such as DialControl
+vcl::Font OutputDevice::GetDefaultFont( DefaultFontType nType, LanguageType eLang,
+ GetDefaultFontFlags nFlags, const OutputDevice* pOutDev )
+{
+ static bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ static bool bAbortOnFontSubstitute = [] {
+ const char* pEnv = getenv("SAL_NON_APPLICATION_FONT_USE");
+ return pEnv && strcmp(pEnv, "abort") == 0;
+ }();
+
+ if (!pOutDev && !bFuzzing) // default is NULL
+ pOutDev = Application::GetDefaultDevice();
+
+ OUString aSearch;
+ if (!bFuzzing)
+ {
+ LanguageTag aLanguageTag(
+ ( eLang == LANGUAGE_NONE || eLang == LANGUAGE_SYSTEM || eLang == LANGUAGE_DONTKNOW ) ?
+ Application::GetSettings().GetUILanguageTag() :
+ LanguageTag( eLang ));
+
+ utl::DefaultFontConfiguration& rDefaults = utl::DefaultFontConfiguration::get();
+ OUString aDefault = rDefaults.getDefaultFont( aLanguageTag, nType );
+
+ if( !aDefault.isEmpty() )
+ aSearch = aDefault;
+ else
+ aSearch = rDefaults.getUserInterfaceFont( aLanguageTag ); // use the UI font as a fallback
+
+ // during cppunit tests with SAL_NON_APPLICATION_FONT_USE set we don't have any bundled fonts
+ // that support the default CTL and CJK languages of Hindi and Chinese, so just pick something
+ // (unsuitable) that does exist, if they get used with SAL_NON_APPLICATION_FONT_USE=abort then
+ // glyph fallback will trigger std::abort
+ if (bAbortOnFontSubstitute)
+ {
+ if (eLang == LANGUAGE_HINDI || eLang == LANGUAGE_CHINESE_SIMPLIFIED)
+ aSearch = "DejaVu Sans";
+ }
+ }
+ else
+ aSearch = "Liberation Serif";
+
+ vcl::Font aFont;
+ aFont.SetPitch( PITCH_VARIABLE );
+
+ switch ( nType )
+ {
+ case DefaultFontType::SANS_UNICODE:
+ case DefaultFontType::UI_SANS:
+ aFont.SetFamily( FAMILY_SWISS );
+ break;
+
+ case DefaultFontType::SANS:
+ case DefaultFontType::LATIN_HEADING:
+ case DefaultFontType::LATIN_SPREADSHEET:
+ case DefaultFontType::LATIN_DISPLAY:
+ aFont.SetFamily( FAMILY_SWISS );
+ break;
+
+ case DefaultFontType::SERIF:
+ case DefaultFontType::LATIN_TEXT:
+ case DefaultFontType::LATIN_PRESENTATION:
+ aFont.SetFamily( FAMILY_ROMAN );
+ break;
+
+ case DefaultFontType::FIXED:
+ case DefaultFontType::LATIN_FIXED:
+ case DefaultFontType::UI_FIXED:
+ aFont.SetPitch( PITCH_FIXED );
+ aFont.SetFamily( FAMILY_MODERN );
+ break;
+
+ case DefaultFontType::SYMBOL:
+ aFont.SetCharSet( RTL_TEXTENCODING_SYMBOL );
+ break;
+
+ case DefaultFontType::CJK_TEXT:
+ case DefaultFontType::CJK_PRESENTATION:
+ case DefaultFontType::CJK_SPREADSHEET:
+ case DefaultFontType::CJK_HEADING:
+ case DefaultFontType::CJK_DISPLAY:
+ aFont.SetFamily( FAMILY_SYSTEM ); // don't care, but don't use font subst config later...
+ break;
+
+ case DefaultFontType::CTL_TEXT:
+ case DefaultFontType::CTL_PRESENTATION:
+ case DefaultFontType::CTL_SPREADSHEET:
+ case DefaultFontType::CTL_HEADING:
+ case DefaultFontType::CTL_DISPLAY:
+ aFont.SetFamily( FAMILY_SYSTEM ); // don't care, but don't use font subst config later...
+ break;
+ }
+
+ if ( !aSearch.isEmpty() )
+ {
+ aFont.SetFontHeight( 12 ); // corresponds to nDefaultHeight
+ aFont.SetWeight( WEIGHT_NORMAL );
+ aFont.SetLanguage( eLang );
+
+ if ( aFont.GetCharSet() == RTL_TEXTENCODING_DONTKNOW )
+ aFont.SetCharSet( osl_getThreadTextEncoding() );
+
+ // Should we only return available fonts on the given device
+ if ( pOutDev )
+ {
+ pOutDev->ImplInitFontList();
+
+ // Search Font in the FontList
+ OUString aName;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ vcl::font::PhysicalFontFamily* pFontFamily = pOutDev->mxFontCollection->FindFontFamily( GetNextFontToken( aSearch, nIndex ) );
+ if( pFontFamily )
+ {
+ AddTokenFontName( aName, pFontFamily->GetFamilyName() );
+ if( nFlags & GetDefaultFontFlags::OnlyOne )
+ break;
+ }
+ }
+ while ( nIndex != -1 );
+ aFont.SetFamilyName( aName );
+ }
+
+ // No Name, then set all names
+ if ( aFont.GetFamilyName().isEmpty() )
+ {
+ if ( nFlags & GetDefaultFontFlags::OnlyOne )
+ {
+ if( !pOutDev )
+ {
+ SAL_WARN_IF(!utl::ConfigManager::IsFuzzing(), "vcl.gdi", "No default window has been set for the application - we really shouldn't be able to get here");
+ aFont.SetFamilyName( aSearch.getToken( 0, ';' ) );
+ }
+ else
+ {
+ pOutDev->ImplInitFontList();
+
+ aFont.SetFamilyName( aSearch );
+
+ // convert to pixel height
+ Size aSize = pOutDev->ImplLogicToDevicePixel( aFont.GetFontSize() );
+ if ( !aSize.Height() )
+ {
+ // use default pixel height only when logical height is zero
+ if ( aFont.GetFontHeight() )
+ aSize.setHeight( 1 );
+ else
+ aSize.setHeight( (12*pOutDev->mnDPIY)/72 );
+ }
+
+ // use default width only when logical width is zero
+ if( (0 == aSize.Width()) && (0 != aFont.GetFontSize().Width()) )
+ aSize.setWidth( 1 );
+
+ // get the name of the first available font
+ float fExactHeight = static_cast<float>(aSize.Height());
+ rtl::Reference<LogicalFontInstance> pFontInstance = pOutDev->mxFontCache->GetFontInstance( pOutDev->mxFontCollection.get(), aFont, aSize, fExactHeight );
+ if (pFontInstance)
+ {
+ assert(pFontInstance->GetFontFace());
+ aFont.SetFamilyName(pFontInstance->GetFontFace()->GetFamilyName());
+ }
+ }
+ }
+ else
+ aFont.SetFamilyName( aSearch );
+ }
+ }
+
+#if OSL_DEBUG_LEVEL > 2
+ const char* s = "SANS_UNKNOWN";
+ switch ( nType )
+ {
+ case DefaultFontType::SANS_UNICODE: s = "SANS_UNICODE"; break;
+ case DefaultFontType::UI_SANS: s = "UI_SANS"; break;
+
+ case DefaultFontType::SANS: s = "SANS"; break;
+ case DefaultFontType::LATIN_HEADING: s = "LATIN_HEADING"; break;
+ case DefaultFontType::LATIN_SPREADSHEET: s = "LATIN_SPREADSHEET"; break;
+ case DefaultFontType::LATIN_DISPLAY: s = "LATIN_DISPLAY"; break;
+
+ case DefaultFontType::SERIF: s = "SERIF"; break;
+ case DefaultFontType::LATIN_TEXT: s = "LATIN_TEXT"; break;
+ case DefaultFontType::LATIN_PRESENTATION: s = "LATIN_PRESENTATION"; break;
+
+ case DefaultFontType::FIXED: s = "FIXED"; break;
+ case DefaultFontType::LATIN_FIXED: s = "LATIN_FIXED"; break;
+ case DefaultFontType::UI_FIXED: s = "UI_FIXED"; break;
+
+ case DefaultFontType::SYMBOL: s = "SYMBOL"; break;
+
+ case DefaultFontType::CJK_TEXT: s = "CJK_TEXT"; break;
+ case DefaultFontType::CJK_PRESENTATION: s = "CJK_PRESENTATION"; break;
+ case DefaultFontType::CJK_SPREADSHEET: s = "CJK_SPREADSHEET"; break;
+ case DefaultFontType::CJK_HEADING: s = "CJK_HEADING"; break;
+ case DefaultFontType::CJK_DISPLAY: s = "CJK_DISPLAY"; break;
+
+ case DefaultFontType::CTL_TEXT: s = "CTL_TEXT"; break;
+ case DefaultFontType::CTL_PRESENTATION: s = "CTL_PRESENTATION"; break;
+ case DefaultFontType::CTL_SPREADSHEET: s = "CTL_SPREADSHEET"; break;
+ case DefaultFontType::CTL_HEADING: s = "CTL_HEADING"; break;
+ case DefaultFontType::CTL_DISPLAY: s = "CTL_DISPLAY"; break;
+ }
+ SAL_INFO("vcl.gdi",
+ "OutputDevice::GetDefaultFont() Type=" << s
+ << " lang=" << eLang
+ << " flags=" << static_cast<int>(nFlags)
+ << " family=\"" << aFont.GetFamilyName() << "\"");
+#endif
+
+ return aFont;
+}
+
+void OutputDevice::ImplInitFontList() const
+{
+ if( mxFontCollection->Count() )
+ return;
+
+ if( !(mpGraphics || AcquireGraphics()) )
+ return;
+ assert(mpGraphics);
+
+ SAL_INFO( "vcl.gdi", "OutputDevice::ImplInitFontList()" );
+ mpGraphics->GetDevFontList(mxFontCollection.get());
+
+ // There is absolutely no way there should be no fonts available on the device
+ if( !mxFontCollection->Count() )
+ {
+ OUString aError( "Application error: no fonts and no vcl resource found on your system" );
+ OUString aResStr(VclResId(SV_ACCESSERROR_NO_FONTS));
+ if (!aResStr.isEmpty())
+ aError = aResStr;
+ Application::Abort(aError);
+ }
+}
+
+bool OutputDevice::InitFont() const
+{
+ DBG_TESTSOLARMUTEX();
+
+ if (!ImplNewFont())
+ return false;
+ if (!mpFontInstance)
+ return false;
+ if (!mpGraphics)
+ {
+ if (!AcquireGraphics())
+ return false;
+ }
+ else if (!mbInitFont)
+ return true;
+
+ assert(mpGraphics);
+ mpGraphics->SetFont(mpFontInstance.get(), 0);
+ mbInitFont = false;
+ return true;
+}
+
+const LogicalFontInstance* OutputDevice::GetFontInstance() const
+{
+ if (!InitFont())
+ return nullptr;
+ return mpFontInstance.get();
+}
+
+bool OutputDevice::ImplNewFont() const
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( !mbNewFont )
+ return true;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ {
+ SAL_WARN("vcl.gdi", "OutputDevice::ImplNewFont(): no Graphics, no Font");
+ return false;
+ }
+ assert(mpGraphics);
+
+ ImplInitFontList();
+
+ // convert to pixel height
+ // TODO: replace integer based aSize completely with subpixel accurate type
+ float fExactHeight = ImplLogicHeightToDeviceSubPixel(maFont.GetFontHeight());
+ Size aSize = ImplLogicToDevicePixel( maFont.GetFontSize() );
+ if ( !aSize.Height() )
+ {
+ // use default pixel height only when logical height is zero
+ if ( maFont.GetFontSize().Height() )
+ aSize.setHeight( 1 );
+ else
+ aSize.setHeight( (12*mnDPIY)/72 );
+ fExactHeight = static_cast<float>(aSize.Height());
+ }
+
+ // select the default width only when logical width is zero
+ if( (0 == aSize.Width()) && (0 != maFont.GetFontSize().Width()) )
+ aSize.setWidth( 1 );
+
+ // decide if antialiasing is appropriate
+ bool bNonAntialiased(GetAntialiasing() & AntialiasingFlags::DisableText);
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ bNonAntialiased |= bool(rStyleSettings.GetDisplayOptions() & DisplayOptions::AADisable);
+ bNonAntialiased |= (int(rStyleSettings.GetAntialiasingMinPixelHeight()) > maFont.GetFontSize().Height());
+ }
+
+ // get font entry
+ rtl::Reference<LogicalFontInstance> pOldFontInstance = mpFontInstance;
+ mpFontInstance = mxFontCache->GetFontInstance(mxFontCollection.get(), maFont, aSize, fExactHeight, bNonAntialiased);
+ const bool bNewFontInstance = pOldFontInstance.get() != mpFontInstance.get();
+ pOldFontInstance.clear();
+
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+
+ if (!pFontInstance)
+ {
+ SAL_WARN("vcl.gdi", "OutputDevice::ImplNewFont(): no LogicalFontInstance, no Font");
+ return false;
+ }
+
+ // mark when lower layers need to get involved
+ mbNewFont = false;
+ if( bNewFontInstance )
+ mbInitFont = true;
+
+ // select font when it has not been initialized yet
+ if (!pFontInstance->mbInit && InitFont())
+ {
+ // get metric data from device layers
+ pFontInstance->mbInit = true;
+
+ pFontInstance->mxFontMetric->SetOrientation( mpFontInstance->GetFontSelectPattern().mnOrientation );
+ mpGraphics->GetFontMetric( pFontInstance->mxFontMetric, 0 );
+
+ pFontInstance->mxFontMetric->ImplInitTextLineSize( this );
+ pFontInstance->mxFontMetric->ImplInitAboveTextLineSize( this );
+ pFontInstance->mxFontMetric->ImplInitFlags( this );
+
+ pFontInstance->mnLineHeight = pFontInstance->mxFontMetric->GetAscent() + pFontInstance->mxFontMetric->GetDescent();
+
+ SetFontOrientation( pFontInstance );
+ }
+
+ // calculate EmphasisArea
+ mnEmphasisAscent = 0;
+ mnEmphasisDescent = 0;
+ if ( maFont.GetEmphasisMark() & FontEmphasisMark::Style )
+ {
+ FontEmphasisMark nEmphasisMark = maFont.GetEmphasisMarkStyle();
+ tools::Long nEmphasisHeight = (pFontInstance->mnLineHeight*250)/1000;
+ if ( nEmphasisHeight < 1 )
+ nEmphasisHeight = 1;
+ if ( nEmphasisMark & FontEmphasisMark::PosBelow )
+ mnEmphasisDescent = nEmphasisHeight;
+ else
+ mnEmphasisAscent = nEmphasisHeight;
+ }
+
+ // calculate text offset depending on TextAlignment
+ TextAlign eAlign = maFont.GetAlignment();
+ if ( eAlign == ALIGN_BASELINE )
+ {
+ mnTextOffX = 0;
+ mnTextOffY = 0;
+ }
+ else if ( eAlign == ALIGN_TOP )
+ {
+ mnTextOffX = 0;
+ mnTextOffY = +pFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;
+ if ( pFontInstance->mnOrientation )
+ {
+ Point aOriginPt(0, 0);
+ aOriginPt.RotateAround( mnTextOffX, mnTextOffY, pFontInstance->mnOrientation );
+ }
+ }
+ else // eAlign == ALIGN_BOTTOM
+ {
+ mnTextOffX = 0;
+ mnTextOffY = -pFontInstance->mxFontMetric->GetDescent() + mnEmphasisDescent;
+ if ( pFontInstance->mnOrientation )
+ {
+ Point aOriginPt(0, 0);
+ aOriginPt.RotateAround( mnTextOffX, mnTextOffY, pFontInstance->mnOrientation );
+ }
+ }
+
+ mbTextLines = ((maFont.GetUnderline() != LINESTYLE_NONE) && (maFont.GetUnderline() != LINESTYLE_DONTKNOW)) ||
+ ((maFont.GetOverline() != LINESTYLE_NONE) && (maFont.GetOverline() != LINESTYLE_DONTKNOW)) ||
+ ((maFont.GetStrikeout() != STRIKEOUT_NONE) && (maFont.GetStrikeout() != STRIKEOUT_DONTKNOW));
+ mbTextSpecial = maFont.IsShadow() || maFont.IsOutline() ||
+ (maFont.GetRelief() != FontRelief::NONE);
+
+
+ bool bRet = true;
+
+ // #95414# fix for OLE objects which use scale factors very creatively
+ if (mbMap && !aSize.Width())
+ bRet = AttemptOLEFontScaleFix(const_cast<vcl::Font&>(maFont), aSize.Height());
+
+ return bRet;
+}
+
+bool OutputDevice::AttemptOLEFontScaleFix(vcl::Font& rFont, tools::Long nHeight) const
+{
+ const float fDenominator = static_cast<float>(maMapRes.mnMapScNumY) * maMapRes.mnMapScDenomX;
+ if (fDenominator == 0.0)
+ return false;
+ const float fNumerator = static_cast<float>(maMapRes.mnMapScNumX) * maMapRes.mnMapScDenomY;
+ float fStretch = fNumerator / fDenominator;
+ int nOrigWidth = mpFontInstance->mxFontMetric->GetWidth();
+ int nNewWidth = static_cast<int>(nOrigWidth * fStretch + 0.5);
+ bool bRet = true;
+ if (nNewWidth != nOrigWidth && nNewWidth != 0)
+ {
+ Size aOrigSize = rFont.GetFontSize();
+ rFont.SetFontSize(Size(nNewWidth, nHeight));
+ mbMap = false;
+ mbNewFont = true;
+ bRet = ImplNewFont(); // recurse once using stretched width
+ mbMap = true;
+ rFont.SetFontSize(aOrigSize);
+ }
+ return bRet;
+}
+
+void OutputDevice::SetFontOrientation( LogicalFontInstance* const pFontInstance ) const
+{
+ if( pFontInstance->GetFontSelectPattern().mnOrientation && !pFontInstance->mxFontMetric->GetOrientation() )
+ {
+ pFontInstance->mnOwnOrientation = pFontInstance->GetFontSelectPattern().mnOrientation;
+ pFontInstance->mnOrientation = pFontInstance->mnOwnOrientation;
+ }
+ else
+ {
+ pFontInstance->mnOrientation = pFontInstance->mxFontMetric->GetOrientation();
+ }
+}
+
+void OutputDevice::ImplDrawEmphasisMark( tools::Long nBaseX, tools::Long nX, tools::Long nY,
+ const tools::PolyPolygon& rPolyPoly, bool bPolyLine,
+ const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 )
+{
+ if( IsRTLEnabled() )
+ nX = nBaseX - (nX - nBaseX - 1);
+
+ nX -= mnOutOffX;
+ nY -= mnOutOffY;
+
+ if ( rPolyPoly.Count() )
+ {
+ if ( bPolyLine )
+ {
+ tools::Polygon aPoly = rPolyPoly.GetObject( 0 );
+ aPoly.Move( nX, nY );
+ DrawPolyLine( aPoly );
+ }
+ else
+ {
+ tools::PolyPolygon aPolyPoly = rPolyPoly;
+ aPolyPoly.Move( nX, nY );
+ DrawPolyPolygon( aPolyPoly );
+ }
+ }
+
+ if ( !rRect1.IsEmpty() )
+ {
+ tools::Rectangle aRect( Point( nX+rRect1.Left(),
+ nY+rRect1.Top() ), rRect1.GetSize() );
+ DrawRect( aRect );
+ }
+
+ if ( !rRect2.IsEmpty() )
+ {
+ tools::Rectangle aRect( Point( nX+rRect2.Left(),
+ nY+rRect2.Top() ), rRect2.GetSize() );
+
+ DrawRect( aRect );
+ }
+}
+
+void OutputDevice::ImplDrawEmphasisMarks( SalLayout& rSalLayout )
+{
+ Color aOldLineColor = GetLineColor();
+ Color aOldFillColor = GetFillColor();
+ bool bOldMap = mbMap;
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ mpMetaFile = nullptr;
+ EnableMapMode( false );
+
+ FontEmphasisMark nEmphasisMark = maFont.GetEmphasisMarkStyle();
+ tools::Long nEmphasisHeight;
+
+ if ( nEmphasisMark & FontEmphasisMark::PosBelow )
+ nEmphasisHeight = mnEmphasisDescent;
+ else
+ nEmphasisHeight = mnEmphasisAscent;
+
+ vcl::font::EmphasisMark aEmphasisMark(nEmphasisMark, nEmphasisHeight, GetDPIY());
+
+ if (aEmphasisMark.IsShapePolyLine())
+ {
+ SetLineColor( GetTextColor() );
+ SetFillColor();
+ }
+ else
+ {
+ SetLineColor();
+ SetFillColor( GetTextColor() );
+ }
+
+ Point aOffset(0,0);
+ Point aOffsetVert(0,0);
+
+ if ( nEmphasisMark & FontEmphasisMark::PosBelow )
+ {
+ aOffset.AdjustY(mpFontInstance->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset());
+ aOffsetVert = aOffset;
+ }
+ else
+ {
+ aOffset.AdjustY(-(mpFontInstance->mxFontMetric->GetAscent() + aEmphasisMark.GetYOffset()));
+ // Todo: use ideographic em-box or ideographic character face information.
+ aOffsetVert.AdjustY(-(mpFontInstance->mxFontMetric->GetAscent() +
+ mpFontInstance->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset()));
+ }
+
+ tools::Long nEmphasisWidth2 = aEmphasisMark.GetWidth() / 2;
+ tools::Long nEmphasisHeight2 = nEmphasisHeight / 2;
+ aOffset += Point( nEmphasisWidth2, nEmphasisHeight2 );
+
+ basegfx::B2DPoint aOutPoint;
+ tools::Rectangle aRectangle;
+ const GlyphItem* pGlyph;
+ const LogicalFontInstance* pGlyphFont;
+ int nStart = 0;
+ while (rSalLayout.GetNextGlyph(&pGlyph, aOutPoint, nStart, &pGlyphFont))
+ {
+ if (!pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle))
+ continue;
+
+ if (!pGlyph->IsSpacing())
+ {
+ Point aAdjPoint;
+ if (pGlyph->IsVertical())
+ {
+ aAdjPoint = aOffsetVert;
+ aAdjPoint.AdjustX((-pGlyph->origWidth() + aEmphasisMark.GetWidth()) / 2);
+ }
+ else
+ {
+ aAdjPoint = aOffset;
+ aAdjPoint.AdjustX(aRectangle.Left() + (aRectangle.GetWidth() - aEmphasisMark.GetWidth()) / 2 );
+ }
+
+ if ( mpFontInstance->mnOrientation )
+ {
+ Point aOriginPt(0, 0);
+ aOriginPt.RotateAround( aAdjPoint, mpFontInstance->mnOrientation );
+ }
+ aOutPoint.adjustX(aAdjPoint.X() - nEmphasisWidth2);
+ aOutPoint.adjustY(aAdjPoint.Y() - nEmphasisHeight2);
+ ImplDrawEmphasisMark( rSalLayout.DrawBase().getX(),
+ aOutPoint.getX(), aOutPoint.getY(),
+ aEmphasisMark.GetShape(), aEmphasisMark.IsShapePolyLine(),
+ aEmphasisMark.GetRect1(), aEmphasisMark.GetRect2() );
+ }
+ }
+
+ SetLineColor( aOldLineColor );
+ SetFillColor( aOldFillColor );
+ EnableMapMode( bOldMap );
+ mpMetaFile = pOldMetaFile;
+}
+
+std::unique_ptr<SalLayout> OutputDevice::getFallbackLayout(
+ LogicalFontInstance* pLogicalFont, int nFallbackLevel,
+ vcl::text::ImplLayoutArgs& rLayoutArgs, const SalLayoutGlyphs* pGlyphs) const
+{
+ // we need a graphics
+ if (!mpGraphics && !AcquireGraphics())
+ return nullptr;
+
+ assert(mpGraphics != nullptr);
+ mpGraphics->SetFont( pLogicalFont, nFallbackLevel );
+
+ rLayoutArgs.ResetPos();
+ std::unique_ptr<GenericSalLayout> pFallback = mpGraphics->GetTextLayout(nFallbackLevel);
+
+ if (!pFallback)
+ return nullptr;
+
+ if (!pFallback->LayoutText(rLayoutArgs, pGlyphs ? pGlyphs->Impl(nFallbackLevel) : nullptr))
+ {
+ // there is no need for a font that couldn't resolve anything
+ return nullptr;
+ }
+
+ return pFallback;
+}
+
+bool OutputDevice::ForceFallbackFont(vcl::Font const& rFallbackFont)
+{
+ vcl::Font aOldFont = GetFont();
+ SetFont(rFallbackFont);
+ if (!InitFont())
+ return false;
+
+ mpForcedFallbackInstance = mpFontInstance;
+ SetFont(aOldFont);
+ if (!InitFont())
+ return false;
+
+ if (mpForcedFallbackInstance)
+ return true;
+
+ return false;
+}
+
+std::unique_ptr<SalLayout> OutputDevice::ImplGlyphFallbackLayout( std::unique_ptr<SalLayout> pSalLayout,
+ vcl::text::ImplLayoutArgs& rLayoutArgs, const SalLayoutGlyphs* pGlyphs ) const
+{
+ // This function relies on a valid mpFontInstance, if it doesn't exist bail out
+ // - we'd have crashed later on anyway. At least here we can catch the error in debug
+ // mode.
+ if ( !mpFontInstance )
+ {
+ SAL_WARN ("vcl.gdi", "No font entry set in OutputDevice");
+ assert(mpFontInstance);
+ return nullptr;
+ }
+
+ // prepare multi level glyph fallback
+ std::unique_ptr<MultiSalLayout> pMultiSalLayout;
+ ImplLayoutRuns aLayoutRuns = rLayoutArgs.maRuns;
+ rLayoutArgs.PrepareFallback(nullptr);
+ rLayoutArgs.mnFlags |= SalLayoutFlags::ForFallback;
+
+ // get list of code units that need glyph fallback
+ bool bRTL;
+ int nMinRunPos, nEndRunPos;
+ OUStringBuffer aMissingCodeBuf(512);
+ while (rLayoutArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL))
+ aMissingCodeBuf.append(rLayoutArgs.mrStr.subView(nMinRunPos, nEndRunPos - nMinRunPos));
+ rLayoutArgs.ResetPos();
+ OUString aMissingCodes = aMissingCodeBuf.makeStringAndClear();
+
+ vcl::font::FontSelectPattern aFontSelData(mpFontInstance->GetFontSelectPattern());
+ SalLayoutGlyphsImpl* pGlyphsImpl = pGlyphs ? pGlyphs->Impl(1) : nullptr;
+
+ bool bHasUsedFallback = false;
+
+ // try if fallback fonts support the missing code units
+ for( int nFallbackLevel = 1; nFallbackLevel < MAX_FALLBACK; ++nFallbackLevel )
+ {
+ rtl::Reference<LogicalFontInstance> pFallbackFont;
+ if (!bHasUsedFallback && mpForcedFallbackInstance)
+ {
+ pFallbackFont = mpForcedFallbackInstance;
+ bHasUsedFallback = true;
+ }
+ else if(pGlyphsImpl != nullptr)
+ {
+ pFallbackFont = pGlyphsImpl->GetFont();
+ }
+
+ // find a font family suited for glyph fallback
+ // GetGlyphFallbackFont() needs a valid FontInstance
+ // if the system-specific glyph fallback is active
+ OUString oldMissingCodes = aMissingCodes;
+ if( !pFallbackFont )
+ pFallbackFont = mxFontCache->GetGlyphFallbackFont( mxFontCollection.get(),
+ aFontSelData, mpFontInstance.get(), nFallbackLevel, aMissingCodes );
+ if( !pFallbackFont )
+ break;
+
+ SAL_INFO("vcl", "Fallback font (level " << nFallbackLevel << "): family: " << pFallbackFont->GetFontFace()->GetFamilyName()
+ << ", style: " << pFallbackFont->GetFontFace()->GetStyleName());
+
+ if( nFallbackLevel < MAX_FALLBACK-1)
+ {
+ // ignore fallback font if it is the same as the original font
+ // TODO: This seems broken. Either the font does not provide any of the missing
+ // codes, in which case the fallback should not select it. Or it does provide
+ // some of the missing codes, and then why weren't they used the first time?
+ // This will just loop repeatedly finding the same font (it used to remove
+ // the found font from mxFontCache, but doesn't do that anymore and I don't
+ // see how doing that would remove the font from consideration for fallback).
+ if( mpFontInstance->GetFontFace() == pFallbackFont->GetFontFace())
+ {
+ if(aMissingCodes != oldMissingCodes)
+ {
+ SAL_INFO("vcl.gdi", "Font fallback to the same font, but has missing codes");
+ // Restore the missing codes if we're not going to use this font.
+ aMissingCodes = oldMissingCodes;
+ }
+ continue;
+ }
+ }
+
+ // create and add glyph fallback layout to multilayout
+ std::unique_ptr<SalLayout> pFallback = getFallbackLayout(pFallbackFont.get(),
+ nFallbackLevel, rLayoutArgs, pGlyphs);
+ if (pFallback)
+ {
+ if( !pMultiSalLayout )
+ pMultiSalLayout.reset( new MultiSalLayout( std::move(pSalLayout) ) );
+ pMultiSalLayout->AddFallback(std::move(pFallback), rLayoutArgs.maRuns);
+ if (nFallbackLevel == MAX_FALLBACK-1)
+ pMultiSalLayout->SetIncomplete(true);
+ }
+
+ if (pGlyphs != nullptr)
+ pGlyphsImpl = pGlyphs->Impl(nFallbackLevel + 1);
+
+ // break when this fallback was sufficient
+ if( !rLayoutArgs.PrepareFallback(pGlyphsImpl) )
+ break;
+ }
+
+ if (pMultiSalLayout) // due to missing glyphs, multilevel layout fallback attempted
+ {
+ // if it works, use that Layout
+ if (pMultiSalLayout->LayoutText(rLayoutArgs, nullptr))
+ pSalLayout = std::move(pMultiSalLayout);
+ else
+ {
+ // if it doesn't, give up and restore ownership of the pSalLayout
+ // back to its original state
+ pSalLayout = pMultiSalLayout->ReleaseBaseLayout();
+ }
+ }
+
+ // restore orig font settings
+ pSalLayout->InitFont();
+ rLayoutArgs.maRuns = aLayoutRuns;
+
+ return pSalLayout;
+}
+
+tools::Long OutputDevice::GetMinKashida() const
+{
+ if (!ImplNewFont())
+ return 0;
+
+ auto nKashidaWidth = mpFontInstance->mxFontMetric->GetMinKashida();
+ if (!mbMap)
+ nKashidaWidth = std::ceil(nKashidaWidth);
+
+ return ImplDevicePixelToLogicWidth(nKashidaWidth);
+}
+
+sal_Int32 OutputDevice::ValidateKashidas ( const OUString& rTxt,
+ sal_Int32 nIdx, sal_Int32 nLen,
+ sal_Int32 nKashCount,
+ const sal_Int32* pKashidaPos,
+ sal_Int32* pKashidaPosDropped ) const
+{
+ // do layout
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rTxt, nIdx, nLen );
+ if( !pSalLayout )
+ return 0;
+
+ auto nEnd = nIdx + nLen - 1;
+ sal_Int32 nDropped = 0;
+ for( int i = 0; i < nKashCount; ++i )
+ {
+ auto nPos = pKashidaPos[i];
+ auto nNextPos = nPos + 1;
+
+ // Skip combining marks to find the next character after this position.
+ while (nNextPos <= nEnd &&
+ u_getIntPropertyValue(rTxt[nNextPos], UCHAR_JOINING_TYPE) == U_JT_TRANSPARENT)
+ nNextPos++;
+
+ // The next position is past end of the layout, it would happen if we
+ // changed the text styling in the middle of a word. Since we don’t do
+ // apply OpenType features across different layouts, this can’t be an
+ // invalid place to insert Kashida.
+ if (nNextPos > nEnd)
+ continue;
+
+ if (!pSalLayout->IsKashidaPosValid(nPos, nNextPos))
+ pKashidaPosDropped[nDropped++] = nPos;
+ }
+ return nDropped;
+}
+
+bool OutputDevice::GetGlyphBoundRects( const Point& rOrigin, const OUString& rStr,
+ int nIndex, int nLen, std::vector< tools::Rectangle >& rVector ) const
+{
+ rVector.clear();
+
+ if( nIndex >= rStr.getLength() )
+ return false;
+
+ if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ tools::Rectangle aRect;
+ for( int i = 0; i < nLen; i++ )
+ {
+ if( !GetTextBoundRect( aRect, rStr, nIndex, nIndex + i, 1 ) )
+ break;
+ aRect.Move( rOrigin.X(), rOrigin.Y() );
+ rVector.push_back( aRect );
+ }
+
+ return (nLen == static_cast<int>(rVector.size()));
+}
+
+sal_Int32 OutputDevice::HasGlyphs( const vcl::Font& rTempFont, std::u16string_view rStr,
+ sal_Int32 nIndex, sal_Int32 nLen ) const
+{
+ if( nIndex >= static_cast<sal_Int32>(rStr.size()) )
+ return nIndex;
+ sal_Int32 nEnd;
+ if( nLen == -1 )
+ nEnd = rStr.size();
+ else
+ nEnd = std::min<sal_Int32>( rStr.size(), nIndex + nLen );
+
+ SAL_WARN_IF( nIndex >= nEnd, "vcl.gdi", "StartPos >= EndPos?" );
+ SAL_WARN_IF( nEnd > static_cast<sal_Int32>(rStr.size()), "vcl.gdi", "String too short" );
+
+ // to get the map temporarily set font
+ const vcl::Font aOrigFont = GetFont();
+ const_cast<OutputDevice&>(*this).SetFont( rTempFont );
+ FontCharMapRef xFontCharMap;
+ bool bRet = GetFontCharMap( xFontCharMap );
+ const_cast<OutputDevice&>(*this).SetFont( aOrigFont );
+
+ // if fontmap is unknown assume it doesn't have the glyphs
+ if( !bRet )
+ return nIndex;
+
+ for( sal_Int32 i = nIndex; nIndex < nEnd; ++i, ++nIndex )
+ if( ! xFontCharMap->HasChar( rStr[i] ) )
+ return nIndex;
+
+ return -1;
+}
+
+void OutputDevice::ReleaseFontCache() { mxFontCache.reset(); }
+
+void OutputDevice::ReleaseFontCollection() { mxFontCollection.reset(); }
+
+void OutputDevice::SetFontCollectionFromSVData()
+{
+ mxFontCollection = ImplGetSVData()->maGDIData.mxScreenFontList->Clone();
+}
+
+void OutputDevice::ResetNewFontCache()
+{
+ mxFontCache = std::make_shared<ImplFontCache>();
+}
+
+void OutputDevice::ImplReleaseFonts()
+{
+ mpGraphics->ReleaseFonts();
+
+ mbNewFont = true;
+ mbInitFont = true;
+
+ mpFontInstance.clear();
+ mpForcedFallbackInstance.clear();
+ mpFontFaceCollection.reset();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/outdev/gradient.cxx b/vcl/source/outdev/gradient.cxx
new file mode 100644
index 0000000000..e3803d660d
--- /dev/null
+++ b/vcl/source/outdev/gradient.cxx
@@ -0,0 +1,624 @@
+/* -*- 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 <tools/poly.hxx>
+
+#include <vcl/gradient.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+
+#include <salgdi.hxx>
+
+#include <cassert>
+#include <memory>
+
+#define GRADIENT_DEFAULT_STEPCOUNT 0
+
+void OutputDevice::DrawGradient( const tools::Rectangle& rRect,
+ const Gradient& rGradient )
+{
+ assert(!is_double_buffered_window());
+
+ // Convert rectangle to a tools::PolyPolygon by first converting to a Polygon
+ tools::Polygon aPolygon ( rRect );
+ tools::PolyPolygon aPolyPoly ( aPolygon );
+
+ DrawGradient ( aPolyPoly, rGradient );
+}
+
+void OutputDevice::DrawGradient( const tools::PolyPolygon& rPolyPoly,
+ const Gradient& rGradient )
+{
+ assert(!is_double_buffered_window());
+
+ if (mbInitClipRegion)
+ InitClipRegion();
+ // don't return on mbOutputClipped here, as we may need to draw the clipped metafile, even if the output is clipped
+
+ if ( rPolyPoly.Count() && rPolyPoly[ 0 ].GetSize() )
+ {
+ if ( mnDrawMode & ( DrawModeFlags::BlackGradient | DrawModeFlags::WhiteGradient | DrawModeFlags::SettingsGradient) )
+ {
+ Color aColor = GetSingleColorGradientFill();
+
+ Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR );
+ SetLineColor( aColor );
+ SetFillColor( aColor );
+ DrawPolyPolygon( rPolyPoly );
+ Pop();
+ return;
+ }
+
+ Gradient aGradient( rGradient );
+
+ if ( mnDrawMode & DrawModeFlags::GrayGradient )
+ aGradient.MakeGrayscale();
+
+ DrawGradientToMetafile( rPolyPoly, rGradient );
+
+ if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ // Clip and then draw the gradient
+ if( !tools::Rectangle( PixelToLogic( Point() ), GetOutputSize() ).IsEmpty() )
+ {
+ const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
+
+ // convert rectangle to pixels
+ tools::Rectangle aRect( ImplLogicToDevicePixel( aBoundRect ) );
+ aRect.Normalize();
+
+ // do nothing if the rectangle is empty
+ if ( !aRect.IsEmpty() )
+ {
+ tools::PolyPolygon aClixPolyPoly( ImplLogicToDevicePixel( rPolyPoly ) );
+ bool bDrawn = false;
+
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ // secure clip region
+ Push( vcl::PushFlags::CLIPREGION );
+ IntersectClipRegion( aBoundRect );
+
+ if (mbInitClipRegion)
+ InitClipRegion();
+
+ // try to draw gradient natively
+ if (!mbOutputClipped)
+ bDrawn = mpGraphics->DrawGradient( aClixPolyPoly, aGradient, *this );
+
+ if (!bDrawn && !mbOutputClipped)
+ {
+ // draw gradients without border
+ if( mbLineColor || mbInitLineColor )
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+
+ mbInitFillColor = true;
+
+ // calculate step count if necessary
+ if ( !aGradient.GetSteps() )
+ aGradient.SetSteps( GRADIENT_DEFAULT_STEPCOUNT );
+
+ if ( rPolyPoly.IsRect() )
+ {
+ // because we draw with no border line, we have to expand gradient
+ // rect to avoid missing lines on the right and bottom edge
+ aRect.AdjustLeft( -1 );
+ aRect.AdjustTop( -1 );
+ aRect.AdjustRight( 1 );
+ aRect.AdjustBottom( 1 );
+ }
+
+ // if the clipping polypolygon is a rectangle, then it's the same size as the bounding of the
+ // polypolygon, so pass in a NULL for the clipping parameter
+ if( aGradient.GetStyle() == css::awt::GradientStyle_LINEAR || rGradient.GetStyle() == css::awt::GradientStyle_AXIAL )
+ DrawLinearGradient( aRect, aGradient, aClixPolyPoly.IsRect() ? nullptr : &aClixPolyPoly );
+ else
+ DrawComplexGradient( aRect, aGradient, aClixPolyPoly.IsRect() ? nullptr : &aClixPolyPoly );
+ }
+
+ Pop();
+ }
+ }
+ }
+
+ if( mpAlphaVDev )
+ {
+ const Color aFillCol( mpAlphaVDev->GetFillColor() );
+ mpAlphaVDev->SetFillColor( COL_ALPHA_OPAQUE );
+ mpAlphaVDev->DrawPolyPolygon( rPolyPoly );
+ mpAlphaVDev->SetFillColor( aFillCol );
+ }
+}
+
+void OutputDevice::ClipAndDrawGradientMetafile ( const Gradient &rGradient, const tools::PolyPolygon &rPolyPoly )
+{
+ const bool bOldOutput = IsOutputEnabled();
+ EnableOutput( false );
+
+ Push( vcl::PushFlags::CLIPREGION );
+ SetClipRegion( vcl::Region( rPolyPoly ) );
+ DrawGradient( rPolyPoly.GetBoundRect(), rGradient );
+ Pop();
+
+ EnableOutput( bOldOutput );
+}
+
+void OutputDevice::DrawGradientToMetafile ( const tools::PolyPolygon& rPolyPoly,
+ const Gradient& rGradient )
+{
+ assert(!is_double_buffered_window());
+
+ if ( !mpMetaFile )
+ return;
+
+ if ( !(rPolyPoly.Count() && rPolyPoly[ 0 ].GetSize()) )
+ return;
+
+ const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
+
+ if (aBoundRect.IsEmpty())
+ return;
+
+ Gradient aGradient( rGradient );
+
+ if (mnDrawMode & DrawModeFlags::GrayGradient)
+ aGradient.MakeGrayscale();
+
+ if ( rPolyPoly.IsRect() )
+ {
+ mpMetaFile->AddAction( new MetaGradientAction( aBoundRect, std::move(aGradient) ) );
+ }
+ else
+ {
+ mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_BEGIN"_ostr ) );
+ mpMetaFile->AddAction( new MetaGradientExAction( rPolyPoly, rGradient ) );
+
+ ClipAndDrawGradientMetafile ( rGradient, rPolyPoly );
+
+ mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_END"_ostr ) );
+ }
+}
+
+namespace
+{
+ sal_uInt8 GetGradientColorValue( tools::Long nValue )
+ {
+ if ( nValue < 0 )
+ return 0;
+ else if ( nValue > 0xFF )
+ return 0xFF;
+ else
+ return static_cast<sal_uInt8>(nValue);
+ }
+}
+
+void OutputDevice::DrawLinearGradient( const tools::Rectangle& rRect,
+ const Gradient& rGradient,
+ const tools::PolyPolygon* pClixPolyPoly )
+{
+ assert(!is_double_buffered_window());
+
+ // get BoundRect of rotated rectangle
+ tools::Rectangle aRect;
+ Point aCenter;
+ Degree10 nAngle = rGradient.GetAngle() % 3600_deg10;
+
+ rGradient.GetBoundRect( rRect, aRect, aCenter );
+
+ bool bLinear = (rGradient.GetStyle() == css::awt::GradientStyle_LINEAR);
+ double fBorder = rGradient.GetBorder() * aRect.GetHeight() / 100.0;
+ if ( !bLinear )
+ {
+ fBorder /= 2.0;
+ }
+ tools::Rectangle aMirrorRect = aRect; // used in style axial
+ aMirrorRect.SetTop( ( aRect.Top() + aRect.Bottom() ) / 2 );
+ if ( !bLinear )
+ {
+ aRect.SetBottom( aMirrorRect.Top() );
+ }
+
+ // colour-intensities of start- and finish; change if needed
+ tools::Long nFactor;
+ Color aStartCol = rGradient.GetStartColor();
+ Color aEndCol = rGradient.GetEndColor();
+ tools::Long nStartRed = aStartCol.GetRed();
+ tools::Long nStartGreen = aStartCol.GetGreen();
+ tools::Long nStartBlue = aStartCol.GetBlue();
+ tools::Long nEndRed = aEndCol.GetRed();
+ tools::Long nEndGreen = aEndCol.GetGreen();
+ tools::Long nEndBlue = aEndCol.GetBlue();
+ nFactor = rGradient.GetStartIntensity();
+ nStartRed = (nStartRed * nFactor) / 100;
+ nStartGreen = (nStartGreen * nFactor) / 100;
+ nStartBlue = (nStartBlue * nFactor) / 100;
+ nFactor = rGradient.GetEndIntensity();
+ nEndRed = (nEndRed * nFactor) / 100;
+ nEndGreen = (nEndGreen * nFactor) / 100;
+ nEndBlue = (nEndBlue * nFactor) / 100;
+
+ // gradient style axial has exchanged start and end colors
+ if ( !bLinear)
+ {
+ std::swap( nStartRed, nEndRed );
+ std::swap( nStartGreen, nEndGreen );
+ std::swap( nStartBlue, nEndBlue );
+ }
+
+ sal_uInt8 nRed;
+ sal_uInt8 nGreen;
+ sal_uInt8 nBlue;
+
+ // Create border
+ tools::Rectangle aBorderRect = aRect;
+ tools::Polygon aPoly( 4 );
+ if (fBorder > 0.0)
+ {
+ nRed = static_cast<sal_uInt8>(nStartRed);
+ nGreen = static_cast<sal_uInt8>(nStartGreen);
+ nBlue = static_cast<sal_uInt8>(nStartBlue);
+
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+
+ aBorderRect.SetBottom( static_cast<tools::Long>( aBorderRect.Top() + fBorder ) );
+ aRect.SetTop( aBorderRect.Bottom() );
+ aPoly[0] = aBorderRect.TopLeft();
+ aPoly[1] = aBorderRect.TopRight();
+ aPoly[2] = aBorderRect.BottomRight();
+ aPoly[3] = aBorderRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+
+ if ( !bLinear)
+ {
+ aBorderRect = aMirrorRect;
+ aBorderRect.SetTop( static_cast<tools::Long>( aBorderRect.Bottom() - fBorder ) );
+ aMirrorRect.SetBottom( aBorderRect.Top() );
+ aPoly[0] = aBorderRect.TopLeft();
+ aPoly[1] = aBorderRect.TopRight();
+ aPoly[2] = aBorderRect.BottomRight();
+ aPoly[3] = aBorderRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+ }
+ }
+
+ // calculate step count
+ tools::Long nStepCount = GetGradientSteps(rGradient, aRect);
+
+ // minimal three steps and maximal as max color steps
+ tools::Long nAbsRedSteps = std::abs( nEndRed - nStartRed );
+ tools::Long nAbsGreenSteps = std::abs( nEndGreen - nStartGreen );
+ tools::Long nAbsBlueSteps = std::abs( nEndBlue - nStartBlue );
+ tools::Long nMaxColorSteps = std::max( nAbsRedSteps , nAbsGreenSteps );
+ nMaxColorSteps = std::max( nMaxColorSteps, nAbsBlueSteps );
+ tools::Long nSteps = std::min( nStepCount, nMaxColorSteps );
+ if ( nSteps < 3)
+ {
+ nSteps = 3;
+ }
+
+ double fScanInc = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps);
+ double fGradientLine = static_cast<double>(aRect.Top());
+ double fMirrorGradientLine = static_cast<double>(aMirrorRect.Bottom());
+
+ const double fStepsMinus1 = static_cast<double>(nSteps) - 1.0;
+ if ( !bLinear)
+ {
+ nSteps -= 1; // draw middle polygons as one polygon after loop to avoid gap
+ }
+
+ for ( tools::Long i = 0; i < nSteps; i++ )
+ {
+ // linear interpolation of color
+ const double fAlpha = static_cast<double>(i) / fStepsMinus1;
+ double fTempColor = static_cast<double>(nStartRed) * (1.0-fAlpha) + static_cast<double>(nEndRed) * fAlpha;
+ nRed = GetGradientColorValue(static_cast<tools::Long>(fTempColor));
+ fTempColor = static_cast<double>(nStartGreen) * (1.0-fAlpha) + static_cast<double>(nEndGreen) * fAlpha;
+ nGreen = GetGradientColorValue(static_cast<tools::Long>(fTempColor));
+ fTempColor = static_cast<double>(nStartBlue) * (1.0-fAlpha) + static_cast<double>(nEndBlue) * fAlpha;
+ nBlue = GetGradientColorValue(static_cast<tools::Long>(fTempColor));
+
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+
+ // Polygon for this color step
+ aRect.SetTop( static_cast<tools::Long>( fGradientLine + static_cast<double>(i) * fScanInc ) );
+ aRect.SetBottom( static_cast<tools::Long>( fGradientLine + ( static_cast<double>(i) + 1.0 ) * fScanInc ) );
+ aPoly[0] = aRect.TopLeft();
+ aPoly[1] = aRect.TopRight();
+ aPoly[2] = aRect.BottomRight();
+ aPoly[3] = aRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+
+ if ( !bLinear )
+ {
+ aMirrorRect.SetBottom( static_cast<tools::Long>( fMirrorGradientLine - static_cast<double>(i) * fScanInc ) );
+ aMirrorRect.SetTop( static_cast<tools::Long>( fMirrorGradientLine - (static_cast<double>(i) + 1.0)* fScanInc ) );
+ aPoly[0] = aMirrorRect.TopLeft();
+ aPoly[1] = aMirrorRect.TopRight();
+ aPoly[2] = aMirrorRect.BottomRight();
+ aPoly[3] = aMirrorRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+ }
+ }
+ if ( bLinear)
+ return;
+
+ // draw middle polygon with end color
+ nRed = GetGradientColorValue(nEndRed);
+ nGreen = GetGradientColorValue(nEndGreen);
+ nBlue = GetGradientColorValue(nEndBlue);
+
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+
+ aRect.SetTop( static_cast<tools::Long>( fGradientLine + static_cast<double>(nSteps) * fScanInc ) );
+ aRect.SetBottom( static_cast<tools::Long>( fMirrorGradientLine - static_cast<double>(nSteps) * fScanInc ) );
+ aPoly[0] = aRect.TopLeft();
+ aPoly[1] = aRect.TopRight();
+ aPoly[2] = aRect.BottomRight();
+ aPoly[3] = aRect.BottomLeft();
+ aPoly.Rotate( aCenter, nAngle );
+
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+
+}
+
+bool OutputDevice::is_double_buffered_window() const
+{
+ auto pOwnerWindow = GetOwnerWindow();
+ return pOwnerWindow && pOwnerWindow->SupportsDoubleBuffering();
+}
+
+void OutputDevice::DrawComplexGradient( const tools::Rectangle& rRect,
+ const Gradient& rGradient,
+ const tools::PolyPolygon* pClixPolyPoly )
+{
+ assert(!is_double_buffered_window());
+
+ // Determine if we output via Polygon or PolyPolygon
+ // For all rasteroperations other than Overpaint always use PolyPolygon,
+ // as we will get wrong results if we output multiple times on top of each other.
+ // Also for printers always use PolyPolygon, as not all printers
+ // can print polygons on top of each other.
+
+ std::optional<tools::PolyPolygon> xPolyPoly;
+ tools::Rectangle aRect;
+ Point aCenter;
+ Color aStartCol( rGradient.GetStartColor() );
+ Color aEndCol( rGradient.GetEndColor() );
+ tools::Long nStartRed = ( static_cast<tools::Long>(aStartCol.GetRed()) * rGradient.GetStartIntensity() ) / 100;
+ tools::Long nStartGreen = ( static_cast<tools::Long>(aStartCol.GetGreen()) * rGradient.GetStartIntensity() ) / 100;
+ tools::Long nStartBlue = ( static_cast<tools::Long>(aStartCol.GetBlue()) * rGradient.GetStartIntensity() ) / 100;
+ tools::Long nEndRed = ( static_cast<tools::Long>(aEndCol.GetRed()) * rGradient.GetEndIntensity() ) / 100;
+ tools::Long nEndGreen = ( static_cast<tools::Long>(aEndCol.GetGreen()) * rGradient.GetEndIntensity() ) / 100;
+ tools::Long nEndBlue = ( static_cast<tools::Long>(aEndCol.GetBlue()) * rGradient.GetEndIntensity() ) / 100;
+ tools::Long nRedSteps = nEndRed - nStartRed;
+ tools::Long nGreenSteps = nEndGreen - nStartGreen;
+ tools::Long nBlueSteps = nEndBlue - nStartBlue;
+ Degree10 nAngle = rGradient.GetAngle() % 3600_deg10;
+
+ rGradient.GetBoundRect( rRect, aRect, aCenter );
+
+ if ( UsePolyPolygonForComplexGradient() )
+ xPolyPoly = tools::PolyPolygon( 2 );
+
+ tools::Long nStepCount = GetGradientSteps(rGradient, rRect);
+
+ // at least three steps and at most the number of colour differences
+ tools::Long nSteps = std::max( nStepCount, tools::Long(2) );
+ tools::Long nCalcSteps = std::abs( nRedSteps );
+ tools::Long nTempSteps = std::abs( nGreenSteps );
+ if ( nTempSteps > nCalcSteps )
+ nCalcSteps = nTempSteps;
+ nTempSteps = std::abs( nBlueSteps );
+ if ( nTempSteps > nCalcSteps )
+ nCalcSteps = nTempSteps;
+ if ( nCalcSteps < nSteps )
+ nSteps = nCalcSteps;
+ if ( !nSteps )
+ nSteps = 1;
+
+ // determine output limits and stepsizes for all directions
+ tools::Polygon aPoly;
+ double fScanLeft = aRect.Left();
+ double fScanTop = aRect.Top();
+ double fScanRight = aRect.Right();
+ double fScanBottom = aRect.Bottom();
+ double fScanIncX = static_cast<double>(aRect.GetWidth()) / static_cast<double>(nSteps) * 0.5;
+ double fScanIncY = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps) * 0.5;
+
+ // all gradients are rendered as nested rectangles which shrink
+ // equally in each dimension - except for 'square' gradients
+ // which shrink to a central vertex but are not per-se square.
+ if( rGradient.GetStyle() != css::awt::GradientStyle_SQUARE )
+ {
+ fScanIncY = std::min( fScanIncY, fScanIncX );
+ fScanIncX = fScanIncY;
+ }
+ sal_uInt8 nRed = static_cast<sal_uInt8>(nStartRed), nGreen = static_cast<sal_uInt8>(nStartGreen), nBlue = static_cast<sal_uInt8>(nStartBlue);
+ bool bPaintLastPolygon( false ); // #107349# Paint last polygon only if loop has generated any output
+
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+
+ if( xPolyPoly )
+ {
+ aPoly = tools::Polygon(rRect);
+ xPolyPoly->Insert( aPoly );
+ xPolyPoly->Insert( aPoly );
+ }
+ else
+ {
+ // extend rect, to avoid missing bounding line
+ tools::Rectangle aExtRect( rRect );
+
+ aExtRect.AdjustLeft( -1 );
+ aExtRect.AdjustTop( -1 );
+ aExtRect.AdjustRight(1 );
+ aExtRect.AdjustBottom(1 );
+
+ aPoly = tools::Polygon(aExtRect);
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+ }
+
+ // loop to output Polygon/PolyPolygon sequentially
+ for( tools::Long i = 1; i < nSteps; i++ )
+ {
+ // calculate new Polygon
+ fScanLeft += fScanIncX;
+ aRect.SetLeft( static_cast<tools::Long>( fScanLeft ) );
+ fScanTop += fScanIncY;
+ aRect.SetTop( static_cast<tools::Long>( fScanTop ) );
+ fScanRight -= fScanIncX;
+ aRect.SetRight( static_cast<tools::Long>( fScanRight ) );
+ fScanBottom -= fScanIncY;
+ aRect.SetBottom( static_cast<tools::Long>( fScanBottom ) );
+
+ if( ( aRect.GetWidth() < 2 ) || ( aRect.GetHeight() < 2 ) )
+ break;
+
+ if( rGradient.GetStyle() == css::awt::GradientStyle_RADIAL || rGradient.GetStyle() == css::awt::GradientStyle_ELLIPTICAL )
+ aPoly = tools::Polygon( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 );
+ else
+ aPoly = tools::Polygon( aRect );
+
+ aPoly.Rotate( aCenter, nAngle );
+
+ // adapt colour accordingly
+ const tools::Long nStepIndex = ( xPolyPoly ? i : ( i + 1 ) );
+ nRed = GetGradientColorValue( nStartRed + ( ( nRedSteps * nStepIndex ) / nSteps ) );
+ nGreen = GetGradientColorValue( nStartGreen + ( ( nGreenSteps * nStepIndex ) / nSteps ) );
+ nBlue = GetGradientColorValue( nStartBlue + ( ( nBlueSteps * nStepIndex ) / nSteps ) );
+
+ // either slow tools::PolyPolygon output or fast Polygon-Painting
+ if( xPolyPoly )
+ {
+ bPaintLastPolygon = true; // #107349# Paint last polygon only if loop has generated any output
+
+ xPolyPoly->Replace( xPolyPoly->GetObject( 1 ), 0 );
+ xPolyPoly->Replace( aPoly, 1 );
+
+ ImplDrawPolyPolygon( *xPolyPoly, pClixPolyPoly );
+
+ // #107349# Set fill color _after_ geometry painting:
+ // xPolyPoly's geometry is the band from last iteration's
+ // aPoly to current iteration's aPoly. The window outdev
+ // path (see else below), on the other hand, paints the
+ // full aPoly. Thus, here, we're painting the band before
+ // the one painted in the window outdev path below. To get
+ // matching colors, have to delay color setting here.
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+ }
+ else
+ {
+ // #107349# Set fill color _before_ geometry painting
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+ }
+ }
+
+ // we should draw last inner Polygon if we output PolyPolygon
+ if( !xPolyPoly )
+ return;
+
+ const tools::Polygon& rPoly = xPolyPoly->GetObject( 1 );
+
+ if( rPoly.GetBoundRect().IsEmpty() )
+ return;
+
+ // #107349# Paint last polygon with end color only if loop
+ // has generated output. Otherwise, the current
+ // (i.e. start) color is taken, to generate _any_ output.
+ if( bPaintLastPolygon )
+ {
+ nRed = GetGradientColorValue( nEndRed );
+ nGreen = GetGradientColorValue( nEndGreen );
+ nBlue = GetGradientColorValue( nEndBlue );
+ }
+
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+ ImplDrawPolygon( rPoly, pClixPolyPoly );
+}
+
+tools::Long OutputDevice::GetGradientStepCount( tools::Long nMinRect )
+{
+ tools::Long nInc = (nMinRect < 50) ? 2 : 4;
+
+ return nInc;
+}
+
+tools::Long OutputDevice::GetGradientSteps(Gradient const& rGradient, tools::Rectangle const& rRect)
+{
+ // calculate step count
+ tools::Long nStepCount = rGradient.GetSteps();
+
+ if (nStepCount)
+ return nStepCount;
+
+ tools::Long nMinRect = 0;
+
+ if (rGradient.GetStyle() == css::awt::GradientStyle_LINEAR || rGradient.GetStyle() == css::awt::GradientStyle_AXIAL)
+ nMinRect = rRect.GetHeight();
+ else
+ nMinRect = std::min(rRect.GetWidth(), rRect.GetHeight());
+
+ tools::Long nInc = GetGradientStepCount(nMinRect);
+
+ if (!nInc)
+ nInc = 1;
+
+ return nMinRect / nInc;
+}
+
+Color OutputDevice::GetSingleColorGradientFill()
+{
+ Color aColor;
+
+ // we should never call on this function if any of these aren't set!
+ assert( mnDrawMode & ( DrawModeFlags::BlackGradient | DrawModeFlags::WhiteGradient | DrawModeFlags::SettingsGradient) );
+
+ if ( mnDrawMode & DrawModeFlags::BlackGradient )
+ aColor = COL_BLACK;
+ else if ( mnDrawMode & DrawModeFlags::WhiteGradient )
+ aColor = COL_WHITE;
+ else if ( mnDrawMode & DrawModeFlags::SettingsGradient )
+ {
+ if (mnDrawMode & DrawModeFlags::SettingsForSelection)
+ aColor = GetSettings().GetStyleSettings().GetHighlightColor();
+ else
+ aColor = GetSettings().GetStyleSettings().GetWindowColor();
+ }
+
+ return aColor;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/hatch.cxx b/vcl/source/outdev/hatch.cxx
new file mode 100644
index 0000000000..0fc755864a
--- /dev/null
+++ b/vcl/source/outdev/hatch.cxx
@@ -0,0 +1,439 @@
+/* -*- 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 <osl/diagnose.h>
+#include <tools/line.hxx>
+#include <tools/helpers.hxx>
+#include <unotools/configmgr.hxx>
+
+#include <vcl/hatch.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+
+#include <cassert>
+#include <cstdlib>
+#include <memory>
+
+#define HATCH_MAXPOINTS 1024
+
+extern "C" {
+
+static int HatchCmpFnc( const void* p1, const void* p2 )
+{
+ const tools::Long nX1 = static_cast<Point const *>(p1)->X();
+ const tools::Long nX2 = static_cast<Point const *>(p2)->X();
+ const tools::Long nY1 = static_cast<Point const *>(p1)->Y();
+ const tools::Long nY2 = static_cast<Point const *>(p2)->Y();
+
+ return ( nX1 > nX2 ? 1 : nX1 == nX2 ? nY1 > nY2 ? 1: nY1 == nY2 ? 0 : -1 : -1 );
+}
+
+}
+
+void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
+{
+ assert(!is_double_buffered_window());
+
+ Hatch aHatch( rHatch );
+ aHatch.SetColor(vcl::drawmode::GetHatchColor(rHatch.GetColor(), GetDrawMode(), GetSettings().GetStyleSettings()));
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaHatchAction( rPolyPoly, aHatch ) );
+
+ if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ if( rPolyPoly.Count() )
+ {
+ tools::PolyPolygon aPolyPoly( LogicToPixel( rPolyPoly ) );
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ bool bOldMap = mbMap;
+
+ aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
+ aHatch.SetDistance( ImplLogicWidthToDevicePixel( aHatch.GetDistance() ) );
+
+ mpMetaFile = nullptr;
+ EnableMapMode( false );
+ Push( vcl::PushFlags::LINECOLOR );
+ SetLineColor( aHatch.GetColor() );
+ InitLineColor();
+ DrawHatch( aPolyPoly, aHatch, false );
+ Pop();
+ EnableMapMode( bOldMap );
+ mpMetaFile = pOldMetaFile;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawHatch( rPolyPoly, rHatch );
+}
+
+void OutputDevice::AddHatchActions( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch,
+ GDIMetaFile& rMtf )
+{
+
+ tools::PolyPolygon aPolyPoly( rPolyPoly );
+ aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME | PolyOptimizeFlags::CLOSE );
+
+ if( aPolyPoly.Count() )
+ {
+ GDIMetaFile* pOldMtf = mpMetaFile;
+
+ mpMetaFile = &rMtf;
+ mpMetaFile->AddAction( new MetaPushAction( vcl::PushFlags::ALL ) );
+ mpMetaFile->AddAction( new MetaLineColorAction( rHatch.GetColor(), true ) );
+ DrawHatch( aPolyPoly, rHatch, true );
+ mpMetaFile->AddAction( new MetaPopAction() );
+ mpMetaFile = pOldMtf;
+ }
+}
+
+static bool HasSaneNSteps(const Point& rPt1, const Point& rEndPt1, const Size& rInc)
+{
+ tools::Long nVertSteps = -1;
+ if (rInc.Height())
+ {
+ bool bFail = o3tl::checked_sub(rEndPt1.Y(), rPt1.Y(), nVertSteps);
+ if (bFail)
+ nVertSteps = std::numeric_limits<tools::Long>::max();
+ else
+ nVertSteps = nVertSteps / rInc.Height();
+ }
+ tools::Long nHorzSteps = -1;
+ if (rInc.Width())
+ {
+ bool bFail = o3tl::checked_sub(rEndPt1.X(), rPt1.X(), nHorzSteps);
+ if (bFail)
+ nHorzSteps = std::numeric_limits<tools::Long>::max();
+ else
+ nHorzSteps = nHorzSteps / rInc.Width();
+ }
+ auto nSteps = std::max(nVertSteps, nHorzSteps);
+ if (nSteps > 1024)
+ {
+ SAL_WARN("vcl.gdi", "skipping slow hatch with " << nSteps << " steps");
+ return false;
+ }
+ return true;
+}
+
+void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch, bool bMtf )
+{
+ assert(!is_double_buffered_window());
+
+ if(!rPolyPoly.Count())
+ return;
+
+ // #i115630# DrawHatch does not work with beziers included in the polypolygon, take care of that
+ bool bIsCurve(false);
+
+ for(sal_uInt16 a(0); !bIsCurve && a < rPolyPoly.Count(); a++)
+ {
+ if(rPolyPoly[a].HasFlags())
+ {
+ bIsCurve = true;
+ }
+ }
+
+ if(bIsCurve)
+ {
+ OSL_ENSURE(false, "DrawHatch does *not* support curves, falling back to AdaptiveSubdivide()...");
+ tools::PolyPolygon aPolyPoly;
+
+ rPolyPoly.AdaptiveSubdivide(aPolyPoly);
+ DrawHatch(aPolyPoly, rHatch, bMtf);
+ }
+ else
+ {
+ tools::Rectangle aRect( rPolyPoly.GetBoundRect() );
+ const tools::Long nLogPixelWidth = ImplDevicePixelToLogicWidth( 1 );
+ const tools::Long nWidth = ImplDevicePixelToLogicWidth( std::max( ImplLogicWidthToDevicePixel( rHatch.GetDistance() ), tools::Long(3) ) );
+ std::unique_ptr<Point[]> pPtBuffer(new Point[ HATCH_MAXPOINTS ]);
+ Point aPt1, aPt2, aEndPt1;
+ Size aInc;
+
+ // Single hatch
+ aRect.AdjustLeft( -nLogPixelWidth ); aRect.AdjustTop( -nLogPixelWidth ); aRect.AdjustRight(nLogPixelWidth ); aRect.AdjustBottom(nLogPixelWidth );
+ CalcHatchValues( aRect, nWidth, rHatch.GetAngle(), aPt1, aPt2, aInc, aEndPt1 );
+ if (utl::ConfigManager::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc))
+ return;
+
+ if (aInc.Width() <= 0 && aInc.Height() <= 0)
+ SAL_WARN("vcl.gdi", "invalid increment");
+ else
+ {
+ do
+ {
+ DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf );
+ aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() );
+ aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() );
+ }
+ while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) );
+ }
+
+ if( ( rHatch.GetStyle() == HatchStyle::Double ) || ( rHatch.GetStyle() == HatchStyle::Triple ) )
+ {
+ // Double hatch
+ CalcHatchValues( aRect, nWidth, rHatch.GetAngle() + 900_deg10, aPt1, aPt2, aInc, aEndPt1 );
+ if (utl::ConfigManager::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc))
+ return;
+
+ do
+ {
+ DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf );
+ aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() );
+ aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() );
+ }
+ while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) );
+
+ if( rHatch.GetStyle() == HatchStyle::Triple )
+ {
+ // Triple hatch
+ CalcHatchValues( aRect, nWidth, rHatch.GetAngle() + 450_deg10, aPt1, aPt2, aInc, aEndPt1 );
+ if (utl::ConfigManager::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc))
+ return;
+
+ do
+ {
+ DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf );
+ aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() );
+ aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() );
+ }
+ while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) );
+ }
+ }
+ }
+}
+
+void OutputDevice::CalcHatchValues( const tools::Rectangle& rRect, tools::Long nDist, Degree10 nAngle10,
+ Point& rPt1, Point& rPt2, Size& rInc, Point& rEndPt1 )
+{
+ Point aRef;
+ Degree10 nAngle = nAngle10 % 1800_deg10;
+ tools::Long nOffset = 0;
+
+ if( nAngle > 900_deg10 )
+ nAngle -= 1800_deg10;
+
+ aRef = ( !IsRefPoint() ? rRect.TopLeft() : GetRefPoint() );
+
+ if( 0_deg10 == nAngle )
+ {
+ rInc = Size( 0, nDist );
+ rPt1 = rRect.TopLeft();
+ rPt2 = rRect.TopRight();
+ rEndPt1 = rRect.BottomLeft();
+
+ if( aRef.Y() <= rRect.Top() )
+ nOffset = ( ( rRect.Top() - aRef.Y() ) % nDist );
+ else
+ nOffset = ( nDist - ( ( aRef.Y() - rRect.Top() ) % nDist ) );
+
+ rPt1.AdjustY( -nOffset );
+ rPt2.AdjustY( -nOffset );
+ }
+ else if( 900_deg10 == nAngle )
+ {
+ rInc = Size( nDist, 0 );
+ rPt1 = rRect.TopLeft();
+ rPt2 = rRect.BottomLeft();
+ rEndPt1 = rRect.TopRight();
+
+ if( aRef.X() <= rRect.Left() )
+ nOffset = ( rRect.Left() - aRef.X() ) % nDist;
+ else
+ nOffset = nDist - ( ( aRef.X() - rRect.Left() ) % nDist );
+
+ rPt1.AdjustX( -nOffset );
+ rPt2.AdjustX( -nOffset );
+ }
+ else if( nAngle >= Degree10(-450) && nAngle <= 450_deg10 )
+ {
+ const double fAngle = std::abs( toRadians(nAngle) );
+ const double fTan = tan( fAngle );
+ const tools::Long nYOff = FRound( ( rRect.Right() - rRect.Left() ) * fTan );
+ tools::Long nPY;
+
+ nDist = FRound( nDist / cos( fAngle ) );
+ rInc = Size( 0, nDist );
+
+ if( nAngle > 0_deg10 )
+ {
+ rPt1 = rRect.TopLeft();
+ rPt2 = Point( rRect.Right(), rRect.Top() - nYOff );
+ rEndPt1 = Point( rRect.Left(), rRect.Bottom() + nYOff );
+ nPY = FRound( aRef.Y() - ( ( rPt1.X() - aRef.X() ) * fTan ) );
+ }
+ else
+ {
+ rPt1 = rRect.TopRight();
+ rPt2 = Point( rRect.Left(), rRect.Top() - nYOff );
+ rEndPt1 = Point( rRect.Right(), rRect.Bottom() + nYOff );
+ nPY = FRound( aRef.Y() + ( ( rPt1.X() - aRef.X() ) * fTan ) );
+ }
+
+ if( nPY <= rPt1.Y() )
+ nOffset = ( rPt1.Y() - nPY ) % nDist;
+ else
+ nOffset = nDist - ( ( nPY - rPt1.Y() ) % nDist );
+
+ rPt1.AdjustY( -nOffset );
+ rPt2.AdjustY( -nOffset );
+ }
+ else
+ {
+ const double fAngle = std::abs( toRadians(nAngle) );
+ const double fTan = tan( fAngle );
+ const tools::Long nXOff = FRound( (static_cast<double>(rRect.Bottom()) - rRect.Top()) / fTan );
+ tools::Long nPX;
+
+ nDist = FRound( nDist / sin( fAngle ) );
+ rInc = Size( nDist, 0 );
+
+ if( nAngle > 0_deg10 )
+ {
+ rPt1 = rRect.TopLeft();
+ rPt2 = Point( rRect.Left() - nXOff, rRect.Bottom() );
+ rEndPt1 = Point( rRect.Right() + nXOff, rRect.Top() );
+ nPX = FRound( aRef.X() - ( (static_cast<double>(rPt1.Y()) - aRef.Y()) / fTan ) );
+ }
+ else
+ {
+ rPt1 = rRect.BottomLeft();
+ rPt2 = Point( rRect.Left() - nXOff, rRect.Top() );
+ rEndPt1 = Point( rRect.Right() + nXOff, rRect.Bottom() );
+ nPX = FRound( aRef.X() + ( (static_cast<double>(rPt1.Y()) - aRef.Y()) / fTan ) );
+ }
+
+ if( nPX <= rPt1.X() )
+ nOffset = ( rPt1.X() - nPX ) % nDist;
+ else
+ nOffset = nDist - ( ( nPX - rPt1.X() ) % nDist );
+
+ rPt1.AdjustX( -nOffset );
+ rPt2.AdjustX( -nOffset );
+ }
+}
+
+void OutputDevice::DrawHatchLine( const tools::Line& rLine, const tools::PolyPolygon& rPolyPoly,
+ Point* pPtBuffer, bool bMtf )
+{
+ assert(!is_double_buffered_window());
+
+ double fX, fY;
+ tools::Long nAdd, nPCounter = 0;
+
+ for( tools::Long nPoly = 0, nPolyCount = rPolyPoly.Count(); nPoly < nPolyCount; nPoly++ )
+ {
+ const tools::Polygon& rPoly = rPolyPoly[ static_cast<sal_uInt16>(nPoly) ];
+
+ if( rPoly.GetSize() > 1 )
+ {
+ tools::Line aCurSegment( rPoly[ 0 ], Point() );
+
+ for( tools::Long i = 1, nCount = rPoly.GetSize(); i <= nCount; i++ )
+ {
+ aCurSegment.SetEnd( rPoly[ static_cast<sal_uInt16>( i % nCount ) ] );
+ nAdd = 0;
+
+ if( rLine.Intersection( aCurSegment, fX, fY ) )
+ {
+ if( ( fabs( fX - aCurSegment.GetStart().X() ) <= 0.0000001 ) &&
+ ( fabs( fY - aCurSegment.GetStart().Y() ) <= 0.0000001 ) )
+ {
+ const tools::Line aPrevSegment( rPoly[ static_cast<sal_uInt16>( ( i > 1 ) ? ( i - 2 ) : ( nCount - 1 ) ) ], aCurSegment.GetStart() );
+ const double fPrevDistance = rLine.GetDistance( aPrevSegment.GetStart() );
+ const double fCurDistance = rLine.GetDistance( aCurSegment.GetEnd() );
+
+ if( ( fPrevDistance <= 0.0 && fCurDistance > 0.0 ) ||
+ ( fPrevDistance > 0.0 && fCurDistance < 0.0 ) )
+ {
+ nAdd = 1;
+ }
+ }
+ else if( ( fabs( fX - aCurSegment.GetEnd().X() ) <= 0.0000001 ) &&
+ ( fabs( fY - aCurSegment.GetEnd().Y() ) <= 0.0000001 ) )
+ {
+ const tools::Line aNextSegment( aCurSegment.GetEnd(), rPoly[ static_cast<sal_uInt16>( ( i + 1 ) % nCount ) ] );
+
+ if( ( fabs( rLine.GetDistance( aNextSegment.GetEnd() ) ) <= 0.0000001 ) &&
+ ( rLine.GetDistance( aCurSegment.GetStart() ) > 0.0 ) )
+ {
+ nAdd = 1;
+ }
+ }
+ else
+ nAdd = 1;
+
+ if( nAdd )
+ {
+ if (nPCounter == HATCH_MAXPOINTS)
+ {
+ SAL_WARN("vcl.gdi", "too many hatch points");
+ return;
+ }
+ pPtBuffer[ nPCounter++ ] = Point( FRound( fX ), FRound( fY ) );
+ }
+ }
+
+ aCurSegment.SetStart( aCurSegment.GetEnd() );
+ }
+ }
+ }
+
+ if( nPCounter <= 1 )
+ return;
+
+ qsort( pPtBuffer, nPCounter, sizeof( Point ), HatchCmpFnc );
+
+ if( nPCounter & 1 )
+ nPCounter--;
+
+ if( bMtf )
+ {
+ for( tools::Long i = 0; i < nPCounter; i += 2 )
+ mpMetaFile->AddAction( new MetaLineAction( pPtBuffer[ i ], pPtBuffer[ i + 1 ] ) );
+ }
+ else
+ {
+ for( tools::Long i = 0; i < nPCounter; i += 2 )
+ DrawHatchLine_DrawLine(pPtBuffer[i], pPtBuffer[i+1]);
+ }
+}
+
+void OutputDevice::DrawHatchLine_DrawLine(const Point& rStartPoint, const Point& rEndPoint)
+{
+ Point aPt1{ImplLogicToDevicePixel(rStartPoint)}, aPt2{ImplLogicToDevicePixel(rEndPoint)};
+ mpGraphics->DrawLine(aPt1.X(), aPt1.Y(), aPt2.X(), aPt2.Y(), *this);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/line.cxx b/vcl/source/outdev/line.cxx
new file mode 100644
index 0000000000..6bf59455da
--- /dev/null
+++ b/vcl/source/outdev/line.cxx
@@ -0,0 +1,370 @@
+/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+#include <tools/debug.hxx>
+#include <unotools/configmgr.hxx>
+
+#include <vcl/lineinfo.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+
+#include <cassert>
+#include <numeric>
+
+void OutputDevice::SetLineColor()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaLineColorAction( Color(), false ) );
+
+ if ( mbLineColor )
+ {
+ mbInitLineColor = true;
+ mbLineColor = false;
+ maLineColor = COL_TRANSPARENT;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetLineColor();
+}
+
+void OutputDevice::SetLineColor( const Color& rColor )
+{
+
+ Color aColor = vcl::drawmode::GetLineColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings());
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaLineColorAction( aColor, true ) );
+
+ if( aColor.IsTransparent() )
+ {
+ if ( mbLineColor )
+ {
+ mbInitLineColor = true;
+ mbLineColor = false;
+ maLineColor = COL_TRANSPARENT;
+ }
+ }
+ else
+ {
+ if( maLineColor != aColor )
+ {
+ mbInitLineColor = true;
+ mbLineColor = true;
+ maLineColor = aColor;
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetLineColor( COL_ALPHA_OPAQUE );
+}
+
+void OutputDevice::InitLineColor()
+{
+ DBG_TESTSOLARMUTEX();
+
+ if( mbLineColor )
+ {
+ if( RasterOp::N0 == meRasterOp )
+ mpGraphics->SetROPLineColor( SalROPColor::N0 );
+ else if( RasterOp::N1 == meRasterOp )
+ mpGraphics->SetROPLineColor( SalROPColor::N1 );
+ else if( RasterOp::Invert == meRasterOp )
+ mpGraphics->SetROPLineColor( SalROPColor::Invert );
+ else
+ mpGraphics->SetLineColor( maLineColor );
+ }
+ else
+ {
+ mpGraphics->SetLineColor();
+ }
+
+ mbInitLineColor = false;
+}
+
+void OutputDevice::DrawLine( const Point& rStartPt, const Point& rEndPt,
+ const LineInfo& rLineInfo )
+{
+ assert(!is_double_buffered_window());
+
+ if ( rLineInfo.IsDefault() )
+ {
+ DrawLine( rStartPt, rEndPt );
+ return;
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaLineAction( rStartPt, rEndPt, rLineInfo ) );
+
+ if ( !IsDeviceOutputNecessary() || !mbLineColor || ( LineStyle::NONE == rLineInfo.GetStyle() ) || ImplIsRecordLayout() )
+ return;
+
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ const Point aStartPt( ImplLogicToDevicePixel( rStartPt ) );
+ const Point aEndPt( ImplLogicToDevicePixel( rEndPt ) );
+ const LineInfo aInfo( ImplLogicToDevicePixel( rLineInfo ) );
+ const bool bDashUsed(LineStyle::Dash == aInfo.GetStyle());
+ const bool bLineWidthUsed(aInfo.GetWidth() > 1);
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if(bDashUsed || bLineWidthUsed)
+ {
+ basegfx::B2DPolygon aLinePolygon;
+ aLinePolygon.append(basegfx::B2DPoint(aStartPt.X(), aStartPt.Y()));
+ aLinePolygon.append(basegfx::B2DPoint(aEndPt.X(), aEndPt.Y()));
+
+ drawLine( basegfx::B2DPolyPolygon(aLinePolygon), aInfo );
+ }
+ else
+ {
+ mpGraphics->DrawLine( aStartPt.X(), aStartPt.Y(), aEndPt.X(), aEndPt.Y(), *this );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawLine( rStartPt, rEndPt, rLineInfo );
+}
+
+void OutputDevice::DrawLine( const Point& rStartPt, const Point& rEndPt )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaLineAction( rStartPt, rEndPt ) );
+
+ if ( !IsDeviceOutputNecessary() || !mbLineColor || ImplIsRecordLayout() )
+ return;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ bool bDrawn = false;
+
+ // #i101598# support AA and snap for lines, too
+ if (RasterOp::OverPaint == GetRasterOp() && IsLineColor())
+ {
+ // at least transform with double precision to device coordinates; this will
+ // avoid pixel snap of single, appended lines
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolygon aB2DPolyLine;
+
+ aB2DPolyLine.append(basegfx::B2DPoint(rStartPt.X(), rStartPt.Y()));
+ aB2DPolyLine.append(basegfx::B2DPoint(rEndPt.X(), rEndPt.Y()));
+ aB2DPolyLine.transform( aTransform );
+
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ bDrawn = mpGraphics->DrawPolyLine(
+ basegfx::B2DHomMatrix(),
+ aB2DPolyLine,
+ 0.0,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ *this);
+ }
+ if(!bDrawn)
+ {
+ const Point aStartPt(ImplLogicToDevicePixel(rStartPt));
+ const Point aEndPt(ImplLogicToDevicePixel(rEndPt));
+ mpGraphics->DrawLine( aStartPt.X(), aStartPt.Y(), aEndPt.X(), aEndPt.Y(), *this );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawLine( rStartPt, rEndPt );
+}
+
+void OutputDevice::drawLine( basegfx::B2DPolyPolygon aLinePolyPolygon, const LineInfo& rInfo )
+{
+ static const bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ const bool bTryB2d(RasterOp::OverPaint == GetRasterOp() && IsLineColor());
+ basegfx::B2DPolyPolygon aFillPolyPolygon;
+ const bool bDashUsed(LineStyle::Dash == rInfo.GetStyle());
+ const bool bLineWidthUsed(rInfo.GetWidth() > 1);
+
+ if (!bFuzzing && bDashUsed && aLinePolyPolygon.count())
+ {
+ ::std::vector< double > fDotDashArray = rInfo.GetDotDashArray();
+ const double fAccumulated(::std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0));
+
+ if(fAccumulated > 0.0)
+ {
+ basegfx::B2DPolyPolygon aResult;
+
+ for(auto const& rPolygon : std::as_const(aLinePolyPolygon))
+ {
+ basegfx::B2DPolyPolygon aLineTarget;
+ basegfx::utils::applyLineDashing(
+ rPolygon,
+ fDotDashArray,
+ &aLineTarget);
+ aResult.append(aLineTarget);
+ }
+
+ aLinePolyPolygon = aResult;
+ }
+ }
+
+ if(bLineWidthUsed && aLinePolyPolygon.count())
+ {
+ const double fHalfLineWidth((rInfo.GetWidth() * 0.5) + 0.5);
+
+ if(aLinePolyPolygon.areControlPointsUsed())
+ {
+ // #i110768# When area geometry has to be created, do not
+ // use the fallback bezier decomposition inside createAreaGeometry,
+ // but one that is at least as good as ImplSubdivideBezier was.
+ // There, Polygon::AdaptiveSubdivide was used with default parameter
+ // 1.0 as quality index.
+ static int nRecurseLimit = utl::ConfigManager::IsFuzzing() ? 10 : 30;
+ aLinePolyPolygon = basegfx::utils::adaptiveSubdivideByDistance(aLinePolyPolygon, 1.0, nRecurseLimit);
+ }
+
+ for(auto const& rPolygon : std::as_const(aLinePolyPolygon))
+ {
+ aFillPolyPolygon.append(basegfx::utils::createAreaGeometry(
+ rPolygon,
+ fHalfLineWidth,
+ rInfo.GetLineJoin(),
+ rInfo.GetLineCap()));
+ }
+
+ aLinePolyPolygon.clear();
+ }
+
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ mpMetaFile = nullptr;
+
+ if(aLinePolyPolygon.count())
+ {
+ for(auto const& rB2DPolygon : std::as_const(aLinePolyPolygon))
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+ bool bDone(false);
+
+ if(bTryB2d)
+ {
+ bDone = mpGraphics->DrawPolyLine(
+ basegfx::B2DHomMatrix(),
+ rB2DPolygon,
+ 0.0,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ *this);
+ }
+
+ if(!bDone)
+ {
+ tools::Polygon aPolygon(rB2DPolygon);
+ mpGraphics->DrawPolyLine(
+ aPolygon.GetSize(),
+ aPolygon.GetPointAry(),
+ *this);
+ }
+ }
+ }
+
+ if(aFillPolyPolygon.count())
+ {
+ const Color aOldLineColor( maLineColor );
+ const Color aOldFillColor( maFillColor );
+
+ SetLineColor();
+ InitLineColor();
+ SetFillColor( aOldLineColor );
+ InitFillColor();
+
+ bool bDone(false);
+
+ if (bFuzzing)
+ {
+ const basegfx::B2DRange aRange(basegfx::utils::getRange(aFillPolyPolygon));
+ if (aRange.getMaxX() - aRange.getMinX() > 0x10000000
+ || aRange.getMaxY() - aRange.getMinY() > 0x10000000)
+ {
+ SAL_WARN("vcl.gdi", "drawLine, skipping suspicious range of: "
+ << aRange << " for fuzzing performance");
+ bDone = true;
+ }
+ }
+
+ if (bTryB2d && !bDone)
+ {
+ mpGraphics->DrawPolyPolygon(
+ basegfx::B2DHomMatrix(),
+ aFillPolyPolygon,
+ 0.0,
+ *this);
+ bDone = true;
+ }
+
+ if(!bDone)
+ {
+ for(auto const& rB2DPolygon : std::as_const(aFillPolyPolygon))
+ {
+ tools::Polygon aPolygon(rB2DPolygon);
+
+ // need to subdivide, mpGraphics->DrawPolygon ignores curves
+ aPolygon.AdaptiveSubdivide(aPolygon);
+ mpGraphics->DrawPolygon(aPolygon.GetSize(), aPolygon.GetConstPointAry(), *this);
+ }
+ }
+
+ SetFillColor( aOldFillColor );
+ SetLineColor( aOldLineColor );
+ }
+
+ mpMetaFile = pOldMetaFile;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/map.cxx b/vcl/source/outdev/map.cxx
new file mode 100644
index 0000000000..23c68a2385
--- /dev/null
+++ b/vcl/source/outdev/map.cxx
@@ -0,0 +1,1868 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <tools/bigint.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/cursor.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/wrkwin.hxx>
+
+#include <ImplOutDevData.hxx>
+#include <svdata.hxx>
+#include <window.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <tools/UnitConversion.hxx>
+
+static auto setMapRes(ImplMapRes& rMapRes, const o3tl::Length eUnit)
+{
+ const auto [nNum, nDen] = o3tl::getConversionMulDiv(eUnit, o3tl::Length::in);
+ rMapRes.mnMapScNumX = rMapRes.mnMapScNumY = nNum;
+ rMapRes.mnMapScDenomX = rMapRes.mnMapScDenomY = nDen;
+};
+
+static void ImplCalcMapResolution( const MapMode& rMapMode,
+ tools::Long nDPIX, tools::Long nDPIY, ImplMapRes& rMapRes )
+{
+ switch ( rMapMode.GetMapUnit() )
+ {
+ case MapUnit::MapRelative:
+ break;
+ case MapUnit::Map100thMM:
+ setMapRes(rMapRes, o3tl::Length::mm100);
+ break;
+ case MapUnit::Map10thMM:
+ setMapRes(rMapRes, o3tl::Length::mm10);
+ break;
+ case MapUnit::MapMM:
+ setMapRes(rMapRes, o3tl::Length::mm);
+ break;
+ case MapUnit::MapCM:
+ setMapRes(rMapRes, o3tl::Length::cm);
+ break;
+ case MapUnit::Map1000thInch:
+ setMapRes(rMapRes, o3tl::Length::in1000);
+ break;
+ case MapUnit::Map100thInch:
+ setMapRes(rMapRes, o3tl::Length::in100);
+ break;
+ case MapUnit::Map10thInch:
+ setMapRes(rMapRes, o3tl::Length::in10);
+ break;
+ case MapUnit::MapInch:
+ setMapRes(rMapRes, o3tl::Length::in);
+ break;
+ case MapUnit::MapPoint:
+ setMapRes(rMapRes, o3tl::Length::pt);
+ break;
+ case MapUnit::MapTwip:
+ setMapRes(rMapRes, o3tl::Length::twip);
+ break;
+ case MapUnit::MapPixel:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = nDPIX;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = nDPIY;
+ break;
+ case MapUnit::MapSysFont:
+ case MapUnit::MapAppFont:
+ {
+ ImplSVData* pSVData = ImplGetSVData();
+ if ( !pSVData->maGDIData.mnAppFontX )
+ {
+ if (pSVData->maFrameData.mpFirstFrame)
+ vcl::Window::ImplInitAppFontData(pSVData->maFrameData.mpFirstFrame);
+ else
+ {
+ ScopedVclPtrInstance<WorkWindow> pWin( nullptr, 0 );
+ vcl::Window::ImplInitAppFontData( pWin );
+ }
+ }
+ rMapRes.mnMapScNumX = pSVData->maGDIData.mnAppFontX;
+ rMapRes.mnMapScDenomX = nDPIX * 40;
+ rMapRes.mnMapScNumY = pSVData->maGDIData.mnAppFontY;
+ rMapRes.mnMapScDenomY = nDPIY * 80;
+ }
+ break;
+ default:
+ OSL_FAIL( "unhandled MapUnit" );
+ break;
+ }
+
+ const Fraction& aScaleX = rMapMode.GetScaleX();
+ const Fraction& aScaleY = rMapMode.GetScaleY();
+
+ // set offset according to MapMode
+ Point aOrigin = rMapMode.GetOrigin();
+ if ( rMapMode.GetMapUnit() != MapUnit::MapRelative )
+ {
+ rMapRes.mnMapOfsX = aOrigin.X();
+ rMapRes.mnMapOfsY = aOrigin.Y();
+ }
+ else
+ {
+ auto funcCalcOffset = [](const Fraction& rScale, tools::Long& rnMapOffset, tools::Long nOrigin)
+ {
+ auto nNumerator = rScale.GetNumerator();
+ assert(nNumerator != 0);
+
+ BigInt aX( rnMapOffset );
+ aX *= BigInt( rScale.GetDenominator() );
+ if ( rnMapOffset >= 0 )
+ {
+ if (nNumerator >= 0)
+ aX += BigInt(nNumerator / 2);
+ else
+ aX -= BigInt((nNumerator + 1) / 2);
+ }
+ else
+ {
+ if (nNumerator >= 0 )
+ aX -= BigInt((nNumerator - 1) / 2);
+ else
+ aX += BigInt(nNumerator / 2);
+ }
+ aX /= BigInt(nNumerator);
+ rnMapOffset = static_cast<tools::Long>(aX) + nOrigin;
+ };
+
+ funcCalcOffset(aScaleX, rMapRes.mnMapOfsX, aOrigin.X());
+ funcCalcOffset(aScaleY, rMapRes.mnMapOfsY, aOrigin.Y());
+ }
+
+ // calculate scaling factor according to MapMode
+ // aTemp? = rMapRes.mnMapSc? * aScale?
+ Fraction aTempX = Fraction::MakeFraction( rMapRes.mnMapScNumX,
+ aScaleX.GetNumerator(),
+ rMapRes.mnMapScDenomX,
+ aScaleX.GetDenominator() );
+ Fraction aTempY = Fraction::MakeFraction( rMapRes.mnMapScNumY,
+ aScaleY.GetNumerator(),
+ rMapRes.mnMapScDenomY,
+ aScaleY.GetDenominator() );
+ rMapRes.mnMapScNumX = aTempX.GetNumerator();
+ rMapRes.mnMapScDenomX = aTempX.GetDenominator();
+ rMapRes.mnMapScNumY = aTempY.GetNumerator();
+ rMapRes.mnMapScDenomY = aTempY.GetDenominator();
+}
+
+// #i75163#
+void OutputDevice::ImplInvalidateViewTransform()
+{
+ if(!mpOutDevData)
+ return;
+
+ if(mpOutDevData->mpViewTransform)
+ {
+ delete mpOutDevData->mpViewTransform;
+ mpOutDevData->mpViewTransform = nullptr;
+ }
+
+ if(mpOutDevData->mpInverseViewTransform)
+ {
+ delete mpOutDevData->mpInverseViewTransform;
+ mpOutDevData->mpInverseViewTransform = nullptr;
+ }
+}
+
+static tools::Long ImplLogicToPixel(tools::Long n, tools::Long nDPI, tools::Long nMapNum,
+ tools::Long nMapDenom)
+{
+ assert(nDPI > 0);
+ assert(nMapDenom != 0);
+ if constexpr (sizeof(tools::Long) >= 8)
+ {
+ assert(nMapNum >= 0);
+ //detect overflows
+ assert(nMapNum == 0
+ || std::abs(n) < std::numeric_limits<tools::Long>::max() / nMapNum / nDPI);
+ }
+ sal_Int64 n64 = n;
+ n64 *= nMapNum;
+ n64 *= nDPI;
+ if (nMapDenom == 1)
+ n = static_cast<tools::Long>(n64);
+ else
+ {
+ n64 = 2 * n64 / nMapDenom;
+ if (n64 < 0)
+ --n64;
+ else
+ ++n64;
+ n = static_cast<tools::Long>(n64 / 2);
+ }
+ return n;
+}
+
+static double ImplLogicToSubPixel(tools::Long n, tools::Long nDPI, tools::Long nMapNum,
+ tools::Long nMapDenom)
+{
+ assert(nDPI > 0);
+ assert(nMapDenom != 0);
+ return static_cast<double>(n) * nMapNum * nDPI / nMapDenom;
+}
+
+static tools::Long ImplSubPixelToLogic(double n, tools::Long nDPI, tools::Long nMapNum,
+ tools::Long nMapDenom)
+{
+ assert(nDPI > 0);
+ assert(nMapNum != 0);
+
+ return std::round(n * nMapDenom / nMapNum / nDPI);
+}
+
+static tools::Long ImplPixelToLogic(tools::Long n, tools::Long nDPI, tools::Long nMapNum,
+ tools::Long nMapDenom)
+{
+ assert(nDPI > 0);
+ if (nMapNum == 0)
+ return 0;
+ sal_Int64 nDenom = nDPI;
+ nDenom *= nMapNum;
+
+ sal_Int64 n64 = n;
+ n64 *= nMapDenom;
+ if (nDenom == 1)
+ n = static_cast<tools::Long>(n64);
+ else
+ {
+ n64 = 2 * n64 / nDenom;
+ if (n64 < 0)
+ --n64;
+ else
+ ++n64;
+ n = static_cast<tools::Long>(n64 / 2);
+ }
+ return n;
+}
+
+tools::Long OutputDevice::ImplLogicXToDevicePixel( tools::Long nX ) const
+{
+ if ( !mbMap )
+ return nX+mnOutOffX;
+
+ return ImplLogicToPixel( nX + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX;
+}
+
+tools::Long OutputDevice::ImplLogicYToDevicePixel( tools::Long nY ) const
+{
+ if ( !mbMap )
+ return nY+mnOutOffY;
+
+ return ImplLogicToPixel( nY + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY;
+}
+
+tools::Long OutputDevice::ImplLogicWidthToDevicePixel( tools::Long nWidth ) const
+{
+ if ( !mbMap )
+ return nWidth;
+
+ return ImplLogicToPixel(nWidth, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX);
+}
+
+tools::Long OutputDevice::ImplLogicHeightToDevicePixel( tools::Long nHeight ) const
+{
+ if ( !mbMap )
+ return nHeight;
+
+ return ImplLogicToPixel(nHeight, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY);
+}
+
+tools::Long OutputDevice::ImplDevicePixelToLogicWidth( tools::Long nWidth ) const
+{
+ if ( !mbMap )
+ return nWidth;
+
+ return ImplPixelToLogic(nWidth, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX);
+}
+
+tools::Long OutputDevice::ImplDevicePixelToLogicHeight( tools::Long nHeight ) const
+{
+ if ( !mbMap )
+ return nHeight;
+
+ return ImplPixelToLogic(nHeight, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY);
+}
+
+Point OutputDevice::ImplLogicToDevicePixel( const Point& rLogicPt ) const
+{
+ if ( !mbMap )
+ return Point( rLogicPt.X()+mnOutOffX, rLogicPt.Y()+mnOutOffY );
+
+ return Point( ImplLogicToPixel( rLogicPt.X() + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicPt.Y() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY );
+}
+
+Size OutputDevice::ImplLogicToDevicePixel( const Size& rLogicSize ) const
+{
+ if ( !mbMap )
+ return rLogicSize;
+
+ return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ),
+ ImplLogicToPixel( rLogicSize.Height(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) );
+}
+
+tools::Rectangle OutputDevice::ImplLogicToDevicePixel( const tools::Rectangle& rLogicRect ) const
+{
+ // tdf#141761 IsEmpty() removed
+ // Even if rLogicRect.IsEmpty(), transform of the Position contained
+ // in the Rectangle is necessary. Due to Rectangle::Right() returning
+ // Left() when IsEmpty(), the code *could* stay unchanged (same for Bottom),
+ // but:
+ // The Rectangle constructor used with the four tools::Long values does not
+ // check for IsEmpty(), so to keep that state correct there are two possibilities:
+ // (1) Add a test to the Rectangle constructor in question
+ // (2) Do it handish here
+ // I have tried (1) first, but test Test::test_rectangle() claims that for
+ // tools::Rectangle aRect(1, 1, 1, 1);
+ // tools::Long(1) == aRect.GetWidth()
+ // tools::Long(0) == aRect.getWidth()
+ // (remember: this means Left == Right == 1 -> GetWidth => 1, getWidth == 0)
+ // so indeed the 1's have to go uncommented/unchecked into the data body
+ // of rectangle. Switching to (2) *is* needed, doing so
+ tools::Rectangle aRetval;
+
+ if ( !mbMap )
+ {
+ aRetval = tools::Rectangle(
+ rLogicRect.Left()+mnOutOffX,
+ rLogicRect.Top()+mnOutOffY,
+ rLogicRect.IsWidthEmpty() ? 0 : rLogicRect.Right()+mnOutOffX,
+ rLogicRect.IsHeightEmpty() ? 0 : rLogicRect.Bottom()+mnOutOffY );
+ }
+ else
+ {
+ aRetval = tools::Rectangle(
+ ImplLogicToPixel( rLogicRect.Left()+maMapRes.mnMapOfsX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Top()+maMapRes.mnMapOfsY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY,
+ rLogicRect.IsWidthEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Right()+maMapRes.mnMapOfsX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX,
+ rLogicRect.IsHeightEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Bottom()+maMapRes.mnMapOfsY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY );
+ }
+
+ if(rLogicRect.IsWidthEmpty())
+ aRetval.SetWidthEmpty();
+
+ if(rLogicRect.IsHeightEmpty())
+ aRetval.SetHeightEmpty();
+
+ return aRetval;
+}
+
+tools::Polygon OutputDevice::ImplLogicToDevicePixel( const tools::Polygon& rLogicPoly ) const
+{
+ if ( !mbMap && !mnOutOffX && !mnOutOffY )
+ return rLogicPoly;
+
+ sal_uInt16 i;
+ sal_uInt16 nPoints = rLogicPoly.GetSize();
+ tools::Polygon aPoly( rLogicPoly );
+
+ // get pointer to Point-array (copy data)
+ const Point* pPointAry = aPoly.GetConstPointAry();
+
+ if ( mbMap )
+ {
+ for ( i = 0; i < nPoints; i++ )
+ {
+ const Point& rPt = pPointAry[i];
+ Point aPt(ImplLogicToPixel( rPt.X()+maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( rPt.Y()+maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY);
+ aPoly[i] = aPt;
+ }
+ }
+ else
+ {
+ for ( i = 0; i < nPoints; i++ )
+ {
+ Point aPt = pPointAry[i];
+ aPt.AdjustX(mnOutOffX );
+ aPt.AdjustY(mnOutOffY );
+ aPoly[i] = aPt;
+ }
+ }
+
+ return aPoly;
+}
+
+basegfx::B2DPolygon OutputDevice::ImplLogicToDevicePixel(const basegfx::B2DPolygon& rLogicPoly) const
+{
+ if (!mbMap && !mnOutOffX && !mnOutOffY)
+ return rLogicPoly;
+
+ sal_uInt32 nPoints = rLogicPoly.count();
+ basegfx::B2DPolygon aPoly(rLogicPoly);
+
+ basegfx::B2DPoint aC1;
+ basegfx::B2DPoint aC2;
+
+ if (mbMap)
+ {
+ for (sal_uInt32 i = 0; i < nPoints; ++i)
+ {
+ const basegfx::B2DPoint& rPt = aPoly.getB2DPoint(i);
+ basegfx::B2DPoint aPt(ImplLogicToPixel( rPt.getX()+maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( rPt.getY()+maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY);
+
+ const bool bC1 = aPoly.isPrevControlPointUsed(i);
+ if (bC1)
+ {
+ const basegfx::B2DPoint aB2DC1(aPoly.getPrevControlPoint(i));
+
+ aC1 = basegfx::B2DPoint(ImplLogicToPixel( aB2DC1.getX()+maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( aB2DC1.getY()+maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY);
+ }
+
+ const bool bC2 = aPoly.isNextControlPointUsed(i);
+ if (bC2)
+ {
+ const basegfx::B2DPoint aB2DC2(aPoly.getNextControlPoint(i));
+
+ aC2 = basegfx::B2DPoint(ImplLogicToPixel( aB2DC2.getX()+maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( aB2DC2.getY()+maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY);
+ }
+
+ aPoly.setB2DPoint(i, aPt);
+
+ if (bC1)
+ aPoly.setPrevControlPoint(i, aC1);
+
+ if (bC2)
+ aPoly.setNextControlPoint(i, aC2);
+ }
+ }
+ else
+ {
+ for (sal_uInt32 i = 0; i < nPoints; ++i)
+ {
+ const basegfx::B2DPoint& rPt = aPoly.getB2DPoint(i);
+ basegfx::B2DPoint aPt(rPt.getX() + mnOutOffX, rPt.getY() + mnOutOffY);
+
+ const bool bC1 = aPoly.isPrevControlPointUsed(i);
+ if (bC1)
+ {
+ const basegfx::B2DPoint aB2DC1(aPoly.getPrevControlPoint(i));
+
+ aC1 = basegfx::B2DPoint(aB2DC1.getX() + mnOutOffX, aB2DC1.getY() + mnOutOffY);
+ }
+
+ const bool bC2 = aPoly.isNextControlPointUsed(i);
+ if (bC2)
+ {
+ const basegfx::B2DPoint aB2DC2(aPoly.getNextControlPoint(i));
+
+ aC1 = basegfx::B2DPoint(aB2DC2.getX() + mnOutOffX, aB2DC2.getY() + mnOutOffY);
+ }
+
+ aPoly.setB2DPoint(i, aPt);
+
+ if (bC1)
+ aPoly.setPrevControlPoint(i, aC1);
+
+ if (bC2)
+ aPoly.setNextControlPoint(i, aC2);
+ }
+ }
+
+ return aPoly;
+}
+
+tools::PolyPolygon OutputDevice::ImplLogicToDevicePixel( const tools::PolyPolygon& rLogicPolyPoly ) const
+{
+ if ( !mbMap && !mnOutOffX && !mnOutOffY )
+ return rLogicPolyPoly;
+
+ tools::PolyPolygon aPolyPoly( rLogicPolyPoly );
+ sal_uInt16 nPoly = aPolyPoly.Count();
+ for( sal_uInt16 i = 0; i < nPoly; i++ )
+ {
+ tools::Polygon& rPoly = aPolyPoly[i];
+ rPoly = ImplLogicToDevicePixel( rPoly );
+ }
+ return aPolyPoly;
+}
+
+LineInfo OutputDevice::ImplLogicToDevicePixel( const LineInfo& rLineInfo ) const
+{
+ LineInfo aInfo( rLineInfo );
+
+ if( aInfo.GetStyle() == LineStyle::Dash )
+ {
+ if( aInfo.GetDotCount() && aInfo.GetDotLen() )
+ aInfo.SetDotLen( std::max( ImplLogicWidthToDevicePixel( aInfo.GetDotLen() ), tools::Long(1) ) );
+ else
+ aInfo.SetDotCount( 0 );
+
+ if( aInfo.GetDashCount() && aInfo.GetDashLen() )
+ aInfo.SetDashLen( std::max( ImplLogicWidthToDevicePixel( aInfo.GetDashLen() ), tools::Long(1) ) );
+ else
+ aInfo.SetDashCount( 0 );
+
+ aInfo.SetDistance( ImplLogicWidthToDevicePixel( aInfo.GetDistance() ) );
+
+ if( ( !aInfo.GetDashCount() && !aInfo.GetDotCount() ) || !aInfo.GetDistance() )
+ aInfo.SetStyle( LineStyle::Solid );
+ }
+
+ aInfo.SetWidth( ImplLogicWidthToDevicePixel( aInfo.GetWidth() ) );
+
+ return aInfo;
+}
+
+tools::Rectangle OutputDevice::ImplDevicePixelToLogic( const tools::Rectangle& rPixelRect ) const
+{
+ // tdf#141761 see comments above, IsEmpty() removed
+ tools::Rectangle aRetval;
+
+ if ( !mbMap )
+ {
+ aRetval = tools::Rectangle(
+ rPixelRect.Left()-mnOutOffX,
+ rPixelRect.Top()-mnOutOffY,
+ rPixelRect.IsWidthEmpty() ? 0 : rPixelRect.Right()-mnOutOffX,
+ rPixelRect.IsHeightEmpty() ? 0 : rPixelRect.Bottom()-mnOutOffY );
+ }
+ else
+ {
+ aRetval = tools::Rectangle(
+ ImplPixelToLogic( rPixelRect.Left()-mnOutOffX-mnOutOffOrigX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )-maMapRes.mnMapOfsX,
+ ImplPixelToLogic( rPixelRect.Top()-mnOutOffY-mnOutOffOrigY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )-maMapRes.mnMapOfsY,
+ rPixelRect.IsWidthEmpty() ? 0 : ImplPixelToLogic( rPixelRect.Right()-mnOutOffX-mnOutOffOrigX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )-maMapRes.mnMapOfsX,
+ rPixelRect.IsHeightEmpty() ? 0 : ImplPixelToLogic( rPixelRect.Bottom()-mnOutOffY-mnOutOffOrigY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )-maMapRes.mnMapOfsY );
+ }
+
+ if(rPixelRect.IsWidthEmpty())
+ aRetval.SetWidthEmpty();
+
+ if(rPixelRect.IsHeightEmpty())
+ aRetval.SetHeightEmpty();
+
+ return aRetval;
+}
+
+vcl::Region OutputDevice::ImplPixelToDevicePixel( const vcl::Region& rRegion ) const
+{
+ if ( !mnOutOffX && !mnOutOffY )
+ return rRegion;
+
+ vcl::Region aRegion( rRegion );
+ aRegion.Move( mnOutOffX+mnOutOffOrigX, mnOutOffY+mnOutOffOrigY );
+ return aRegion;
+}
+
+void OutputDevice::EnableMapMode( bool bEnable )
+{
+ mbMap = bEnable;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->EnableMapMode( bEnable );
+}
+
+void OutputDevice::SetMapMode()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaMapModeAction( MapMode() ) );
+
+ if ( mbMap || !maMapMode.IsDefault() )
+ {
+ mbMap = false;
+ maMapMode = MapMode();
+
+ // create new objects (clip region are not re-scaled)
+ mbNewFont = true;
+ mbInitFont = true;
+ ImplInitMapModeObjects();
+
+ // #106426# Adapt logical offset when changing mapmode
+ mnOutOffLogicX = mnOutOffOrigX; // no mapping -> equal offsets
+ mnOutOffLogicY = mnOutOffOrigY;
+
+ // #i75163#
+ ImplInvalidateViewTransform();
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetMapMode();
+}
+
+void OutputDevice::SetMapMode( const MapMode& rNewMapMode )
+{
+
+ bool bRelMap = (rNewMapMode.GetMapUnit() == MapUnit::MapRelative);
+
+ if ( mpMetaFile )
+ {
+ mpMetaFile->AddAction( new MetaMapModeAction( rNewMapMode ) );
+ }
+
+ // do nothing if MapMode was not changed
+ if ( maMapMode == rNewMapMode )
+ return;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetMapMode( rNewMapMode );
+
+ // if default MapMode calculate nothing
+ bool bOldMap = mbMap;
+ mbMap = !rNewMapMode.IsDefault();
+ if ( mbMap )
+ {
+ // if only the origin is converted, do not scale new
+ if ( (rNewMapMode.GetMapUnit() == maMapMode.GetMapUnit()) &&
+ (rNewMapMode.GetScaleX() == maMapMode.GetScaleX()) &&
+ (rNewMapMode.GetScaleY() == maMapMode.GetScaleY()) &&
+ (bOldMap == mbMap) )
+ {
+ // set offset
+ Point aOrigin = rNewMapMode.GetOrigin();
+ maMapRes.mnMapOfsX = aOrigin.X();
+ maMapRes.mnMapOfsY = aOrigin.Y();
+ maMapMode = rNewMapMode;
+
+ // #i75163#
+ ImplInvalidateViewTransform();
+
+ return;
+ }
+ if ( !bOldMap && bRelMap )
+ {
+ maMapRes.mnMapScNumX = 1;
+ maMapRes.mnMapScNumY = 1;
+ maMapRes.mnMapScDenomX = mnDPIX;
+ maMapRes.mnMapScDenomY = mnDPIY;
+ maMapRes.mnMapOfsX = 0;
+ maMapRes.mnMapOfsY = 0;
+ }
+
+ // calculate new MapMode-resolution
+ ImplCalcMapResolution(rNewMapMode, mnDPIX, mnDPIY, maMapRes);
+ }
+
+ // set new MapMode
+ if (bRelMap)
+ {
+ maMapMode.SetScaleX(Fraction::MakeFraction(
+ maMapMode.GetScaleX().GetNumerator(), rNewMapMode.GetScaleX().GetNumerator(),
+ maMapMode.GetScaleX().GetDenominator(), rNewMapMode.GetScaleX().GetDenominator()));
+
+ maMapMode.SetScaleY(Fraction::MakeFraction(
+ maMapMode.GetScaleY().GetNumerator(), rNewMapMode.GetScaleY().GetNumerator(),
+ maMapMode.GetScaleY().GetDenominator(), rNewMapMode.GetScaleY().GetDenominator()));
+
+ maMapMode.SetOrigin(Point(maMapRes.mnMapOfsX, maMapRes.mnMapOfsY));
+ }
+ else
+ {
+ maMapMode = rNewMapMode;
+ }
+
+ // create new objects (clip region are not re-scaled)
+ mbNewFont = true;
+ mbInitFont = true;
+ ImplInitMapModeObjects();
+
+ // #106426# Adapt logical offset when changing mapmode
+ mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX );
+ mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY );
+
+ // #i75163#
+ ImplInvalidateViewTransform();
+}
+
+void OutputDevice::SetMetafileMapMode(const MapMode& rNewMapMode, bool bIsRecord)
+{
+ if (bIsRecord)
+ SetRelativeMapMode(rNewMapMode);
+ else
+ SetMapMode(rNewMapMode);
+}
+
+void OutputDevice::ImplInitMapModeObjects() {}
+
+void OutputDevice::SetRelativeMapMode( const MapMode& rNewMapMode )
+{
+ // do nothing if MapMode did not change
+ if ( maMapMode == rNewMapMode )
+ return;
+
+ MapUnit eOld = maMapMode.GetMapUnit();
+ MapUnit eNew = rNewMapMode.GetMapUnit();
+
+ // a?F = rNewMapMode.GetScale?() / maMapMode.GetScale?()
+ Fraction aXF = Fraction::MakeFraction( rNewMapMode.GetScaleX().GetNumerator(),
+ maMapMode.GetScaleX().GetDenominator(),
+ rNewMapMode.GetScaleX().GetDenominator(),
+ maMapMode.GetScaleX().GetNumerator() );
+ Fraction aYF = Fraction::MakeFraction( rNewMapMode.GetScaleY().GetNumerator(),
+ maMapMode.GetScaleY().GetDenominator(),
+ rNewMapMode.GetScaleY().GetDenominator(),
+ maMapMode.GetScaleY().GetNumerator() );
+
+ Point aPt( LogicToLogic( Point(), nullptr, &rNewMapMode ) );
+ if ( eNew != eOld )
+ {
+ if ( eOld > MapUnit::MapPixel )
+ {
+ SAL_WARN( "vcl.gdi", "Not implemented MapUnit" );
+ }
+ else if ( eNew > MapUnit::MapPixel )
+ {
+ SAL_WARN( "vcl.gdi", "Not implemented MapUnit" );
+ }
+ else
+ {
+ const auto eFrom = MapToO3tlLength(eOld, o3tl::Length::in);
+ const auto eTo = MapToO3tlLength(eNew, o3tl::Length::in);
+ const auto& [mul, div] = o3tl::getConversionMulDiv(eFrom, eTo);
+ Fraction aF(div, mul);
+
+ // a?F = a?F * aF
+ aXF = Fraction::MakeFraction( aXF.GetNumerator(), aF.GetNumerator(),
+ aXF.GetDenominator(), aF.GetDenominator() );
+ aYF = Fraction::MakeFraction( aYF.GetNumerator(), aF.GetNumerator(),
+ aYF.GetDenominator(), aF.GetDenominator() );
+ if ( eOld == MapUnit::MapPixel )
+ {
+ aXF *= Fraction( mnDPIX, 1 );
+ aYF *= Fraction( mnDPIY, 1 );
+ }
+ else if ( eNew == MapUnit::MapPixel )
+ {
+ aXF *= Fraction( 1, mnDPIX );
+ aYF *= Fraction( 1, mnDPIY );
+ }
+ }
+ }
+
+ MapMode aNewMapMode( MapUnit::MapRelative, Point( -aPt.X(), -aPt.Y() ), aXF, aYF );
+ SetMapMode( aNewMapMode );
+
+ if ( eNew != eOld )
+ maMapMode = rNewMapMode;
+
+ // #106426# Adapt logical offset when changing MapMode
+ mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX );
+ mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetRelativeMapMode( rNewMapMode );
+}
+
+// #i75163#
+basegfx::B2DHomMatrix OutputDevice::GetViewTransformation() const
+{
+ if(mbMap && mpOutDevData)
+ {
+ if(!mpOutDevData->mpViewTransform)
+ {
+ mpOutDevData->mpViewTransform = new basegfx::B2DHomMatrix;
+
+ const double fScaleFactorX(static_cast<double>(mnDPIX) * static_cast<double>(maMapRes.mnMapScNumX) / static_cast<double>(maMapRes.mnMapScDenomX));
+ const double fScaleFactorY(static_cast<double>(mnDPIY) * static_cast<double>(maMapRes.mnMapScNumY) / static_cast<double>(maMapRes.mnMapScDenomY));
+ const double fZeroPointX((static_cast<double>(maMapRes.mnMapOfsX) * fScaleFactorX) + static_cast<double>(mnOutOffOrigX));
+ const double fZeroPointY((static_cast<double>(maMapRes.mnMapOfsY) * fScaleFactorY) + static_cast<double>(mnOutOffOrigY));
+
+ mpOutDevData->mpViewTransform->set(0, 0, fScaleFactorX);
+ mpOutDevData->mpViewTransform->set(1, 1, fScaleFactorY);
+ mpOutDevData->mpViewTransform->set(0, 2, fZeroPointX);
+ mpOutDevData->mpViewTransform->set(1, 2, fZeroPointY);
+ }
+
+ return *mpOutDevData->mpViewTransform;
+ }
+ else
+ {
+ return basegfx::B2DHomMatrix();
+ }
+}
+
+// #i75163#
+basegfx::B2DHomMatrix OutputDevice::GetInverseViewTransformation() const
+{
+ if(mbMap && mpOutDevData)
+ {
+ if(!mpOutDevData->mpInverseViewTransform)
+ {
+ GetViewTransformation();
+ mpOutDevData->mpInverseViewTransform = new basegfx::B2DHomMatrix(*mpOutDevData->mpViewTransform);
+ mpOutDevData->mpInverseViewTransform->invert();
+ }
+
+ return *mpOutDevData->mpInverseViewTransform;
+ }
+ else
+ {
+ return basegfx::B2DHomMatrix();
+ }
+}
+
+// #i75163#
+basegfx::B2DHomMatrix OutputDevice::GetViewTransformation( const MapMode& rMapMode ) const
+{
+ // #i82615#
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ basegfx::B2DHomMatrix aTransform;
+
+ const double fScaleFactorX(static_cast<double>(mnDPIX) * static_cast<double>(aMapRes.mnMapScNumX) / static_cast<double>(aMapRes.mnMapScDenomX));
+ const double fScaleFactorY(static_cast<double>(mnDPIY) * static_cast<double>(aMapRes.mnMapScNumY) / static_cast<double>(aMapRes.mnMapScDenomY));
+ const double fZeroPointX((static_cast<double>(aMapRes.mnMapOfsX) * fScaleFactorX) + static_cast<double>(mnOutOffOrigX));
+ const double fZeroPointY((static_cast<double>(aMapRes.mnMapOfsY) * fScaleFactorY) + static_cast<double>(mnOutOffOrigY));
+
+ aTransform.set(0, 0, fScaleFactorX);
+ aTransform.set(1, 1, fScaleFactorY);
+ aTransform.set(0, 2, fZeroPointX);
+ aTransform.set(1, 2, fZeroPointY);
+
+ return aTransform;
+}
+
+// #i75163#
+basegfx::B2DHomMatrix OutputDevice::GetInverseViewTransformation( const MapMode& rMapMode ) const
+{
+ basegfx::B2DHomMatrix aMatrix( GetViewTransformation( rMapMode ) );
+ aMatrix.invert();
+ return aMatrix;
+}
+
+basegfx::B2DHomMatrix OutputDevice::ImplGetDeviceTransformation() const
+{
+ basegfx::B2DHomMatrix aTransformation = GetViewTransformation();
+ // TODO: is it worth to cache the transformed result?
+ if( mnOutOffX || mnOutOffY )
+ aTransformation.translate( mnOutOffX, mnOutOffY );
+ return aTransformation;
+}
+
+Point OutputDevice::LogicToPixel( const Point& rLogicPt ) const
+{
+
+ if ( !mbMap )
+ return rLogicPt;
+
+ return Point( ImplLogicToPixel( rLogicPt.X() + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicPt.Y() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffOrigY );
+}
+
+Size OutputDevice::LogicToPixel( const Size& rLogicSize ) const
+{
+
+ if ( !mbMap )
+ return rLogicSize;
+
+ return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ),
+ ImplLogicToPixel( rLogicSize.Height(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) );
+}
+
+tools::Rectangle OutputDevice::LogicToPixel( const tools::Rectangle& rLogicRect ) const
+{
+ // tdf#141761 see comments above, IsEmpty() removed
+ if ( !mbMap )
+ return rLogicRect;
+
+ tools::Rectangle aRetval(
+ ImplLogicToPixel( rLogicRect.Left() + maMapRes.mnMapOfsX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Top() + maMapRes.mnMapOfsY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffOrigY,
+ rLogicRect.IsWidthEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Right() + maMapRes.mnMapOfsX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffOrigX,
+ rLogicRect.IsHeightEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Bottom() + maMapRes.mnMapOfsY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffOrigY );
+
+ if(rLogicRect.IsWidthEmpty())
+ aRetval.SetWidthEmpty();
+
+ if(rLogicRect.IsHeightEmpty())
+ aRetval.SetHeightEmpty();
+
+ return aRetval;
+}
+
+tools::Polygon OutputDevice::LogicToPixel( const tools::Polygon& rLogicPoly ) const
+{
+
+ if ( !mbMap )
+ return rLogicPoly;
+
+ sal_uInt16 i;
+ sal_uInt16 nPoints = rLogicPoly.GetSize();
+ tools::Polygon aPoly( rLogicPoly );
+
+ // get pointer to Point-array (copy data)
+ const Point* pPointAry = aPoly.GetConstPointAry();
+
+ for ( i = 0; i < nPoints; i++ )
+ {
+ const Point* pPt = &(pPointAry[i]);
+ Point aPt;
+ aPt.setX( ImplLogicToPixel( pPt->X() + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffOrigX );
+ aPt.setY( ImplLogicToPixel( pPt->Y() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffOrigY );
+ aPoly[i] = aPt;
+ }
+
+ return aPoly;
+}
+
+tools::PolyPolygon OutputDevice::LogicToPixel( const tools::PolyPolygon& rLogicPolyPoly ) const
+{
+
+ if ( !mbMap )
+ return rLogicPolyPoly;
+
+ tools::PolyPolygon aPolyPoly( rLogicPolyPoly );
+ sal_uInt16 nPoly = aPolyPoly.Count();
+ for( sal_uInt16 i = 0; i < nPoly; i++ )
+ {
+ tools::Polygon& rPoly = aPolyPoly[i];
+ rPoly = LogicToPixel( rPoly );
+ }
+ return aPolyPoly;
+}
+
+basegfx::B2DPolyPolygon OutputDevice::LogicToPixel( const basegfx::B2DPolyPolygon& rLogicPolyPoly ) const
+{
+ basegfx::B2DPolyPolygon aTransformedPoly = rLogicPolyPoly;
+ const basegfx::B2DHomMatrix& rTransformationMatrix = GetViewTransformation();
+ aTransformedPoly.transform( rTransformationMatrix );
+ return aTransformedPoly;
+}
+
+vcl::Region OutputDevice::LogicToPixel( const vcl::Region& rLogicRegion ) const
+{
+
+ if(!mbMap || rLogicRegion.IsNull() || rLogicRegion.IsEmpty())
+ {
+ return rLogicRegion;
+ }
+
+ vcl::Region aRegion;
+
+ if(rLogicRegion.getB2DPolyPolygon())
+ {
+ aRegion = vcl::Region(LogicToPixel(*rLogicRegion.getB2DPolyPolygon()));
+ }
+ else if(rLogicRegion.getPolyPolygon())
+ {
+ aRegion = vcl::Region(LogicToPixel(*rLogicRegion.getPolyPolygon()));
+ }
+ else if(rLogicRegion.getRegionBand())
+ {
+ RectangleVector aRectangles;
+ rLogicRegion.GetRegionRectangles(aRectangles);
+ const RectangleVector& rRectangles(aRectangles); // needed to make the '!=' work
+
+ // make reverse run to fill new region bottom-up, this will speed it up due to the used data structuring
+ for(RectangleVector::const_reverse_iterator aRectIter(rRectangles.rbegin()); aRectIter != rRectangles.rend(); ++aRectIter)
+ {
+ aRegion.Union(LogicToPixel(*aRectIter));
+ }
+ }
+
+ return aRegion;
+}
+
+Point OutputDevice::LogicToPixel( const Point& rLogicPt,
+ const MapMode& rMapMode ) const
+{
+
+ if ( rMapMode.IsDefault() )
+ return rLogicPt;
+
+ // convert MapMode resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ return Point( ImplLogicToPixel( rLogicPt.X() + aMapRes.mnMapOfsX, mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicPt.Y() + aMapRes.mnMapOfsY, mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY )+mnOutOffOrigY );
+}
+
+Size OutputDevice::LogicToPixel( const Size& rLogicSize,
+ const MapMode& rMapMode ) const
+{
+
+ if ( rMapMode.IsDefault() )
+ return rLogicSize;
+
+ // convert MapMode resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ),
+ ImplLogicToPixel( rLogicSize.Height(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) );
+}
+
+tools::Rectangle OutputDevice::LogicToPixel( const tools::Rectangle& rLogicRect,
+ const MapMode& rMapMode ) const
+{
+ // tdf#141761 see comments above, IsEmpty() removed
+ if ( rMapMode.IsDefault() )
+ return rLogicRect;
+
+ // convert MapMode resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ tools::Rectangle aRetval(
+ ImplLogicToPixel( rLogicRect.Left() + aMapRes.mnMapOfsX, mnDPIX, aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Top() + aMapRes.mnMapOfsY, mnDPIY, aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY )+mnOutOffOrigY,
+ rLogicRect.IsWidthEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Right() + aMapRes.mnMapOfsX, mnDPIX, aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX )+mnOutOffOrigX,
+ rLogicRect.IsHeightEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Bottom() + aMapRes.mnMapOfsY, mnDPIY, aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY )+mnOutOffOrigY );
+
+ if(rLogicRect.IsWidthEmpty())
+ aRetval.SetWidthEmpty();
+
+ if(rLogicRect.IsHeightEmpty())
+ aRetval.SetHeightEmpty();
+
+ return aRetval;
+}
+
+tools::Polygon OutputDevice::LogicToPixel( const tools::Polygon& rLogicPoly,
+ const MapMode& rMapMode ) const
+{
+
+ if ( rMapMode.IsDefault() )
+ return rLogicPoly;
+
+ // convert MapMode resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ sal_uInt16 i;
+ sal_uInt16 nPoints = rLogicPoly.GetSize();
+ tools::Polygon aPoly( rLogicPoly );
+
+ // get pointer to Point-array (copy data)
+ const Point* pPointAry = aPoly.GetConstPointAry();
+
+ for ( i = 0; i < nPoints; i++ )
+ {
+ const Point* pPt = &(pPointAry[i]);
+ Point aPt;
+ aPt.setX( ImplLogicToPixel( pPt->X() + aMapRes.mnMapOfsX, mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX )+mnOutOffOrigX );
+ aPt.setY( ImplLogicToPixel( pPt->Y() + aMapRes.mnMapOfsY, mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY )+mnOutOffOrigY );
+ aPoly[i] = aPt;
+ }
+
+ return aPoly;
+}
+
+basegfx::B2DPolyPolygon OutputDevice::LogicToPixel( const basegfx::B2DPolyPolygon& rLogicPolyPoly,
+ const MapMode& rMapMode ) const
+{
+ basegfx::B2DPolyPolygon aTransformedPoly = rLogicPolyPoly;
+ const basegfx::B2DHomMatrix& rTransformationMatrix = GetViewTransformation( rMapMode );
+ aTransformedPoly.transform( rTransformationMatrix );
+ return aTransformedPoly;
+}
+
+Point OutputDevice::PixelToLogic( const Point& rDevicePt ) const
+{
+
+ if ( !mbMap )
+ return rDevicePt;
+
+ return Point( ImplPixelToLogic( rDevicePt.X(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ) - maMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDevicePt.Y(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) - maMapRes.mnMapOfsY - mnOutOffLogicY );
+}
+
+Point OutputDevice::SubPixelToLogic(const basegfx::B2DPoint& rDevicePt) const
+{
+ if (!mbMap)
+ {
+ assert(floor(rDevicePt.getX() == rDevicePt.getX()) && floor(rDevicePt.getY() == rDevicePt.getY()));
+ return Point(rDevicePt.getX(), rDevicePt.getY());
+ }
+
+ return Point(ImplSubPixelToLogic(rDevicePt.getX(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX) - maMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplSubPixelToLogic(rDevicePt.getY(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY) - maMapRes.mnMapOfsY - mnOutOffLogicY);
+}
+
+Size OutputDevice::PixelToLogic( const Size& rDeviceSize ) const
+{
+
+ if ( !mbMap )
+ return rDeviceSize;
+
+ return Size( ImplPixelToLogic( rDeviceSize.Width(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ),
+ ImplPixelToLogic( rDeviceSize.Height(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) );
+}
+
+tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect ) const
+{
+ // tdf#141761 see comments above, IsEmpty() removed
+ if ( !mbMap )
+ return rDeviceRect;
+
+ tools::Rectangle aRetval(
+ ImplPixelToLogic( rDeviceRect.Left(), mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ) - maMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDeviceRect.Top(), mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) - maMapRes.mnMapOfsY - mnOutOffLogicY,
+ rDeviceRect.IsWidthEmpty() ? 0 : ImplPixelToLogic( rDeviceRect.Right(), mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ) - maMapRes.mnMapOfsX - mnOutOffLogicX,
+ rDeviceRect.IsHeightEmpty() ? 0 : ImplPixelToLogic( rDeviceRect.Bottom(), mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) - maMapRes.mnMapOfsY - mnOutOffLogicY );
+
+ if(rDeviceRect.IsWidthEmpty())
+ aRetval.SetWidthEmpty();
+
+ if(rDeviceRect.IsHeightEmpty())
+ aRetval.SetHeightEmpty();
+
+ return aRetval;
+}
+
+tools::Polygon OutputDevice::PixelToLogic( const tools::Polygon& rDevicePoly ) const
+{
+
+ if ( !mbMap )
+ return rDevicePoly;
+
+ sal_uInt16 i;
+ sal_uInt16 nPoints = rDevicePoly.GetSize();
+ tools::Polygon aPoly( rDevicePoly );
+
+ // get pointer to Point-array (copy data)
+ const Point* pPointAry = aPoly.GetConstPointAry();
+
+ for ( i = 0; i < nPoints; i++ )
+ {
+ const Point* pPt = &(pPointAry[i]);
+ Point aPt;
+ aPt.setX( ImplPixelToLogic( pPt->X(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ) - maMapRes.mnMapOfsX - mnOutOffLogicX );
+ aPt.setY( ImplPixelToLogic( pPt->Y(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) - maMapRes.mnMapOfsY - mnOutOffLogicY );
+ aPoly[i] = aPt;
+ }
+
+ return aPoly;
+}
+
+tools::PolyPolygon OutputDevice::PixelToLogic( const tools::PolyPolygon& rDevicePolyPoly ) const
+{
+
+ if ( !mbMap )
+ return rDevicePolyPoly;
+
+ tools::PolyPolygon aPolyPoly( rDevicePolyPoly );
+ sal_uInt16 nPoly = aPolyPoly.Count();
+ for( sal_uInt16 i = 0; i < nPoly; i++ )
+ {
+ tools::Polygon& rPoly = aPolyPoly[i];
+ rPoly = PixelToLogic( rPoly );
+ }
+ return aPolyPoly;
+}
+
+basegfx::B2DPolyPolygon OutputDevice::PixelToLogic( const basegfx::B2DPolyPolygon& rPixelPolyPoly ) const
+{
+ basegfx::B2DPolyPolygon aTransformedPoly = rPixelPolyPoly;
+ const basegfx::B2DHomMatrix& rTransformationMatrix = GetInverseViewTransformation();
+ aTransformedPoly.transform( rTransformationMatrix );
+ return aTransformedPoly;
+}
+
+vcl::Region OutputDevice::PixelToLogic( const vcl::Region& rDeviceRegion ) const
+{
+
+ if(!mbMap || rDeviceRegion.IsNull() || rDeviceRegion.IsEmpty())
+ {
+ return rDeviceRegion;
+ }
+
+ vcl::Region aRegion;
+
+ if(rDeviceRegion.getB2DPolyPolygon())
+ {
+ aRegion = vcl::Region(PixelToLogic(*rDeviceRegion.getB2DPolyPolygon()));
+ }
+ else if(rDeviceRegion.getPolyPolygon())
+ {
+ aRegion = vcl::Region(PixelToLogic(*rDeviceRegion.getPolyPolygon()));
+ }
+ else if(rDeviceRegion.getRegionBand())
+ {
+ RectangleVector aRectangles;
+ rDeviceRegion.GetRegionRectangles(aRectangles);
+ const RectangleVector& rRectangles(aRectangles); // needed to make the '!=' work
+
+ // make reverse run to fill new region bottom-up, this will speed it up due to the used data structuring
+ for(RectangleVector::const_reverse_iterator aRectIter(rRectangles.rbegin()); aRectIter != rRectangles.rend(); ++aRectIter)
+ {
+ aRegion.Union(PixelToLogic(*aRectIter));
+ }
+ }
+
+ return aRegion;
+}
+
+Point OutputDevice::PixelToLogic( const Point& rDevicePt,
+ const MapMode& rMapMode ) const
+{
+
+ // calculate nothing if default-MapMode
+ if ( rMapMode.IsDefault() )
+ return rDevicePt;
+
+ // calculate MapMode-resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ return Point( ImplPixelToLogic( rDevicePt.X(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ) - aMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDevicePt.Y(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) - aMapRes.mnMapOfsY - mnOutOffLogicY );
+}
+
+Size OutputDevice::PixelToLogic( const Size& rDeviceSize,
+ const MapMode& rMapMode ) const
+{
+
+ // calculate nothing if default-MapMode
+ if ( rMapMode.IsDefault() )
+ return rDeviceSize;
+
+ // calculate MapMode-resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ return Size( ImplPixelToLogic( rDeviceSize.Width(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ),
+ ImplPixelToLogic( rDeviceSize.Height(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) );
+}
+
+tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect,
+ const MapMode& rMapMode ) const
+{
+ // calculate nothing if default-MapMode
+ // tdf#141761 see comments above, IsEmpty() removed
+ if ( rMapMode.IsDefault() )
+ return rDeviceRect;
+
+ // calculate MapMode-resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ tools::Rectangle aRetval(
+ ImplPixelToLogic( rDeviceRect.Left(), mnDPIX, aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ) - aMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDeviceRect.Top(), mnDPIY, aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) - aMapRes.mnMapOfsY - mnOutOffLogicY,
+ rDeviceRect.IsWidthEmpty() ? 0 : ImplPixelToLogic( rDeviceRect.Right(), mnDPIX, aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ) - aMapRes.mnMapOfsX - mnOutOffLogicX,
+ rDeviceRect.IsHeightEmpty() ? 0 : ImplPixelToLogic( rDeviceRect.Bottom(), mnDPIY, aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) - aMapRes.mnMapOfsY - mnOutOffLogicY );
+
+ if(rDeviceRect.IsWidthEmpty())
+ aRetval.SetWidthEmpty();
+
+ if(rDeviceRect.IsHeightEmpty())
+ aRetval.SetHeightEmpty();
+
+ return aRetval;
+}
+
+tools::Polygon OutputDevice::PixelToLogic( const tools::Polygon& rDevicePoly,
+ const MapMode& rMapMode ) const
+{
+
+ // calculate nothing if default-MapMode
+ if ( rMapMode.IsDefault() )
+ return rDevicePoly;
+
+ // calculate MapMode-resolution and convert
+ ImplMapRes aMapRes;
+ ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes);
+
+ sal_uInt16 i;
+ sal_uInt16 nPoints = rDevicePoly.GetSize();
+ tools::Polygon aPoly( rDevicePoly );
+
+ // get pointer to Point-array (copy data)
+ const Point* pPointAry = aPoly.GetConstPointAry();
+
+ for ( i = 0; i < nPoints; i++ )
+ {
+ const Point* pPt = &(pPointAry[i]);
+ Point aPt;
+ aPt.setX( ImplPixelToLogic( pPt->X(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ) - aMapRes.mnMapOfsX - mnOutOffLogicX );
+ aPt.setY( ImplPixelToLogic( pPt->Y(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) - aMapRes.mnMapOfsY - mnOutOffLogicY );
+ aPoly[i] = aPt;
+ }
+
+ return aPoly;
+}
+
+basegfx::B2DPolygon OutputDevice::PixelToLogic( const basegfx::B2DPolygon& rPixelPoly,
+ const MapMode& rMapMode ) const
+{
+ basegfx::B2DPolygon aTransformedPoly = rPixelPoly;
+ const basegfx::B2DHomMatrix& rTransformationMatrix = GetInverseViewTransformation( rMapMode );
+ aTransformedPoly.transform( rTransformationMatrix );
+ return aTransformedPoly;
+}
+
+basegfx::B2DPolyPolygon OutputDevice::PixelToLogic( const basegfx::B2DPolyPolygon& rPixelPolyPoly,
+ const MapMode& rMapMode ) const
+{
+ basegfx::B2DPolyPolygon aTransformedPoly = rPixelPolyPoly;
+ const basegfx::B2DHomMatrix& rTransformationMatrix = GetInverseViewTransformation( rMapMode );
+ aTransformedPoly.transform( rTransformationMatrix );
+ return aTransformedPoly;
+}
+
+#define ENTER1( rSource, pMapModeSource, pMapModeDest ) \
+ if ( !pMapModeSource ) \
+ pMapModeSource = &maMapMode; \
+ if ( !pMapModeDest ) \
+ pMapModeDest = &maMapMode; \
+ if ( *pMapModeSource == *pMapModeDest ) \
+ return rSource; \
+ \
+ ImplMapRes aMapResSource; \
+ ImplMapRes aMapResDest; \
+ \
+ if ( !mbMap || pMapModeSource != &maMapMode ) \
+ { \
+ if ( pMapModeSource->GetMapUnit() == MapUnit::MapRelative ) \
+ aMapResSource = maMapRes; \
+ ImplCalcMapResolution( *pMapModeSource, \
+ mnDPIX, mnDPIY, aMapResSource ); \
+ } \
+ else \
+ aMapResSource = maMapRes; \
+ if ( !mbMap || pMapModeDest != &maMapMode ) \
+ { \
+ if ( pMapModeDest->GetMapUnit() == MapUnit::MapRelative ) \
+ aMapResDest = maMapRes; \
+ ImplCalcMapResolution( *pMapModeDest, \
+ mnDPIX, mnDPIY, aMapResDest ); \
+ } \
+ else \
+ aMapResDest = maMapRes
+
+static void verifyUnitSourceDest( MapUnit eUnitSource, MapUnit eUnitDest )
+{
+ DBG_ASSERT( eUnitSource != MapUnit::MapSysFont
+ && eUnitSource != MapUnit::MapAppFont
+ && eUnitSource != MapUnit::MapRelative,
+ "Source MapUnit is not permitted" );
+ DBG_ASSERT( eUnitDest != MapUnit::MapSysFont
+ && eUnitDest != MapUnit::MapAppFont
+ && eUnitDest != MapUnit::MapRelative,
+ "Destination MapUnit is not permitted" );
+}
+
+namespace
+{
+auto getCorrectedUnit(MapUnit eMapSrc, MapUnit eMapDst)
+{
+ o3tl::Length eSrc = o3tl::Length::invalid;
+ o3tl::Length eDst = o3tl::Length::invalid;
+ if (eMapSrc > MapUnit::MapPixel)
+ SAL_WARN("vcl.gdi", "Invalid source map unit");
+ else if (eMapDst > MapUnit::MapPixel)
+ SAL_WARN("vcl.gdi", "Invalid destination map unit");
+ else if (eMapSrc != eMapDst)
+ {
+ // Here 72 PPI is assumed for MapPixel
+ eSrc = MapToO3tlLength(eMapSrc, o3tl::Length::pt);
+ eDst = MapToO3tlLength(eMapDst, o3tl::Length::pt);
+ }
+ return std::make_pair(eSrc, eDst);
+}
+
+std::pair<ImplMapRes, ImplMapRes> ENTER4(const MapMode& rMMSource, const MapMode& rMMDest)
+{
+ std::pair<ImplMapRes, ImplMapRes> result;
+ ImplCalcMapResolution(rMMSource, 72, 72, result.first);
+ ImplCalcMapResolution(rMMDest, 72, 72, result.second);
+ return result;
+}
+}
+
+// return (n1 * n2 * n3) / (n4 * n5)
+static tools::Long fn5( const tools::Long n1,
+ const tools::Long n2,
+ const tools::Long n3,
+ const tools::Long n4,
+ const tools::Long n5 )
+{
+ if ( n1 == 0 || n2 == 0 || n3 == 0 || n4 == 0 || n5 == 0 )
+ return 0;
+ if (std::numeric_limits<tools::Long>::max() / std::abs(n2) < std::abs(n3))
+ {
+ // a6 is skipped
+ BigInt a7 = n2;
+ a7 *= n3;
+ a7 *= n1;
+
+ if (std::numeric_limits<tools::Long>::max() / std::abs(n4) < std::abs(n5))
+ {
+ BigInt a8 = n4;
+ a8 *= n5;
+
+ BigInt a9 = a8;
+ a9 /= 2;
+ if ( a7.IsNeg() )
+ a7 -= a9;
+ else
+ a7 += a9;
+
+ a7 /= a8;
+ } // of if
+ else
+ {
+ tools::Long n8 = n4 * n5;
+
+ if ( a7.IsNeg() )
+ a7 -= n8 / 2;
+ else
+ a7 += n8 / 2;
+
+ a7 /= n8;
+ } // of else
+ return static_cast<tools::Long>(a7);
+ } // of if
+ else
+ {
+ tools::Long n6 = n2 * n3;
+
+ if (std::numeric_limits<tools::Long>::max() / std::abs(n1) < std::abs(n6))
+ {
+ BigInt a7 = n1;
+ a7 *= n6;
+
+ if (std::numeric_limits<tools::Long>::max() / std::abs(n4) < std::abs(n5))
+ {
+ BigInt a8 = n4;
+ a8 *= n5;
+
+ BigInt a9 = a8;
+ a9 /= 2;
+ if ( a7.IsNeg() )
+ a7 -= a9;
+ else
+ a7 += a9;
+
+ a7 /= a8;
+ } // of if
+ else
+ {
+ tools::Long n8 = n4 * n5;
+
+ if ( a7.IsNeg() )
+ a7 -= n8 / 2;
+ else
+ a7 += n8 / 2;
+
+ a7 /= n8;
+ } // of else
+ return static_cast<tools::Long>(a7);
+ } // of if
+ else
+ {
+ tools::Long n7 = n1 * n6;
+
+ if (std::numeric_limits<tools::Long>::max() / std::abs(n4) < std::abs(n5))
+ {
+ BigInt a7 = n7;
+ BigInt a8 = n4;
+ a8 *= n5;
+
+ BigInt a9 = a8;
+ a9 /= 2;
+ if ( a7.IsNeg() )
+ a7 -= a9;
+ else
+ a7 += a9;
+
+ a7 /= a8;
+ return static_cast<tools::Long>(a7);
+ } // of if
+ else
+ {
+ const tools::Long n8 = n4 * n5;
+ const tools::Long n8_2 = n8 / 2;
+
+ if( n7 < 0 )
+ {
+ if ((n7 - std::numeric_limits<tools::Long>::min()) >= n8_2)
+ n7 -= n8_2;
+ }
+ else if ((std::numeric_limits<tools::Long>::max() - n7) >= n8_2)
+ n7 += n8_2;
+
+ return n7 / n8;
+ } // of else
+ } // of else
+ } // of else
+}
+
+static tools::Long fn3(const tools::Long n1, const o3tl::Length eFrom, const o3tl::Length eTo)
+{
+ if (n1 == 0 || eFrom == o3tl::Length::invalid || eTo == o3tl::Length::invalid)
+ return 0;
+ bool bOverflow;
+ const auto nResult = o3tl::convert(n1, eFrom, eTo, bOverflow);
+ if (bOverflow)
+ {
+ const auto& [n2, n3] = o3tl::getConversionMulDiv(eFrom, eTo);
+ BigInt a4 = n1;
+ a4 *= n2;
+
+ if ( a4.IsNeg() )
+ a4 -= n3 / 2;
+ else
+ a4 += n3 / 2;
+
+ a4 /= n3;
+ return static_cast<tools::Long>(a4);
+ } // of if
+ else
+ return nResult;
+}
+
+Point OutputDevice::LogicToLogic( const Point& rPtSource,
+ const MapMode* pMapModeSource,
+ const MapMode* pMapModeDest ) const
+{
+ ENTER1( rPtSource, pMapModeSource, pMapModeDest );
+
+ return Point( fn5( rPtSource.X() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX,
+ fn5( rPtSource.Y() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY );
+}
+
+Size OutputDevice::LogicToLogic( const Size& rSzSource,
+ const MapMode* pMapModeSource,
+ const MapMode* pMapModeDest ) const
+{
+ ENTER1( rSzSource, pMapModeSource, pMapModeDest );
+
+ return Size( fn5( rSzSource.Width(),
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ),
+ fn5( rSzSource.Height(),
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) );
+}
+
+tools::Rectangle OutputDevice::LogicToLogic( const tools::Rectangle& rRectSource,
+ const MapMode* pMapModeSource,
+ const MapMode* pMapModeDest ) const
+{
+ ENTER1( rRectSource, pMapModeSource, pMapModeDest );
+
+ return tools::Rectangle( fn5( rRectSource.Left() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX,
+ fn5( rRectSource.Top() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY,
+ fn5( rRectSource.Right() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX,
+ fn5( rRectSource.Bottom() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY );
+}
+
+Point OutputDevice::LogicToLogic( const Point& rPtSource,
+ const MapMode& rMapModeSource,
+ const MapMode& rMapModeDest )
+{
+ if ( rMapModeSource == rMapModeDest )
+ return rPtSource;
+
+ MapUnit eUnitSource = rMapModeSource.GetMapUnit();
+ MapUnit eUnitDest = rMapModeDest.GetMapUnit();
+ verifyUnitSourceDest( eUnitSource, eUnitDest );
+
+ if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple())
+ {
+ const auto& [eFrom, eTo] = getCorrectedUnit(eUnitSource, eUnitDest);
+ return Point(fn3(rPtSource.X(), eFrom, eTo), fn3(rPtSource.Y(), eFrom, eTo));
+ }
+ else
+ {
+ const auto& [aMapResSource, aMapResDest] = ENTER4( rMapModeSource, rMapModeDest );
+
+ return Point( fn5( rPtSource.X() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX,
+ fn5( rPtSource.Y() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY );
+ }
+}
+
+Size OutputDevice::LogicToLogic( const Size& rSzSource,
+ const MapMode& rMapModeSource,
+ const MapMode& rMapModeDest )
+{
+ if ( rMapModeSource == rMapModeDest )
+ return rSzSource;
+
+ MapUnit eUnitSource = rMapModeSource.GetMapUnit();
+ MapUnit eUnitDest = rMapModeDest.GetMapUnit();
+ verifyUnitSourceDest( eUnitSource, eUnitDest );
+
+ if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple())
+ {
+ const auto& [eFrom, eTo] = getCorrectedUnit(eUnitSource, eUnitDest);
+ return Size(fn3(rSzSource.Width(), eFrom, eTo), fn3(rSzSource.Height(), eFrom, eTo));
+ }
+ else
+ {
+ const auto& [aMapResSource, aMapResDest] = ENTER4( rMapModeSource, rMapModeDest );
+
+ return Size( fn5( rSzSource.Width(),
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ),
+ fn5( rSzSource.Height(),
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) );
+ }
+}
+
+basegfx::B2DPolygon OutputDevice::LogicToLogic( const basegfx::B2DPolygon& rPolySource,
+ const MapMode& rMapModeSource,
+ const MapMode& rMapModeDest )
+{
+ if(rMapModeSource == rMapModeDest)
+ {
+ return rPolySource;
+ }
+
+ const basegfx::B2DHomMatrix aTransform(LogicToLogic(rMapModeSource, rMapModeDest));
+ basegfx::B2DPolygon aPoly(rPolySource);
+
+ aPoly.transform(aTransform);
+ return aPoly;
+}
+
+basegfx::B2DHomMatrix OutputDevice::LogicToLogic(const MapMode& rMapModeSource, const MapMode& rMapModeDest)
+{
+ basegfx::B2DHomMatrix aTransform;
+
+ if(rMapModeSource == rMapModeDest)
+ {
+ return aTransform;
+ }
+
+ MapUnit eUnitSource = rMapModeSource.GetMapUnit();
+ MapUnit eUnitDest = rMapModeDest.GetMapUnit();
+ verifyUnitSourceDest(eUnitSource, eUnitDest);
+
+ if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple())
+ {
+ const auto& [eFrom, eTo] = getCorrectedUnit(eUnitSource, eUnitDest);
+ const double fScaleFactor(eFrom == o3tl::Length::invalid || eTo == o3tl::Length::invalid
+ ? std::numeric_limits<double>::quiet_NaN()
+ : o3tl::convert(1.0, eFrom, eTo));
+ aTransform.set(0, 0, fScaleFactor);
+ aTransform.set(1, 1, fScaleFactor);
+ }
+ else
+ {
+ const auto& [aMapResSource, aMapResDest] = ENTER4(rMapModeSource, rMapModeDest);
+
+ const double fScaleFactorX((double(aMapResSource.mnMapScNumX) * double(aMapResDest.mnMapScDenomX)) / (double(aMapResSource.mnMapScDenomX) * double(aMapResDest.mnMapScNumX)));
+ const double fScaleFactorY((double(aMapResSource.mnMapScNumY) * double(aMapResDest.mnMapScDenomY)) / (double(aMapResSource.mnMapScDenomY) * double(aMapResDest.mnMapScNumY)));
+ const double fZeroPointX(double(aMapResSource.mnMapOfsX) * fScaleFactorX - double(aMapResDest.mnMapOfsX));
+ const double fZeroPointY(double(aMapResSource.mnMapOfsY) * fScaleFactorY - double(aMapResDest.mnMapOfsY));
+
+ aTransform.set(0, 0, fScaleFactorX);
+ aTransform.set(1, 1, fScaleFactorY);
+ aTransform.set(0, 2, fZeroPointX);
+ aTransform.set(1, 2, fZeroPointY);
+ }
+
+ return aTransform;
+}
+
+tools::Rectangle OutputDevice::LogicToLogic( const tools::Rectangle& rRectSource,
+ const MapMode& rMapModeSource,
+ const MapMode& rMapModeDest )
+{
+ if ( rMapModeSource == rMapModeDest )
+ return rRectSource;
+
+ MapUnit eUnitSource = rMapModeSource.GetMapUnit();
+ MapUnit eUnitDest = rMapModeDest.GetMapUnit();
+ verifyUnitSourceDest( eUnitSource, eUnitDest );
+
+ tools::Rectangle aRetval;
+
+ if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple())
+ {
+ const auto& [eFrom, eTo] = getCorrectedUnit(eUnitSource, eUnitDest);
+
+ auto left = fn3(rRectSource.Left(), eFrom, eTo);
+ auto top = fn3(rRectSource.Top(), eFrom, eTo);
+
+ // tdf#141761 see comments above, IsEmpty() removed
+ auto right = rRectSource.IsWidthEmpty() ? 0 : fn3(rRectSource.Right(), eFrom, eTo);
+ auto bottom = rRectSource.IsHeightEmpty() ? 0 : fn3(rRectSource.Bottom(), eFrom, eTo);
+
+ aRetval = tools::Rectangle(left, top, right, bottom);
+ }
+ else
+ {
+ const auto& [aMapResSource, aMapResDest] = ENTER4( rMapModeSource, rMapModeDest );
+
+ auto left = fn5( rRectSource.Left() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX;
+ auto top = fn5( rRectSource.Top() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY;
+
+ // tdf#141761 see comments above, IsEmpty() removed
+ auto right = rRectSource.IsWidthEmpty() ? 0 : fn5( rRectSource.Right() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX;
+ auto bottom = rRectSource.IsHeightEmpty() ? 0 : fn5( rRectSource.Bottom() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY;
+
+ aRetval = tools::Rectangle(left, top, right, bottom);
+ }
+
+ if(rRectSource.IsWidthEmpty())
+ aRetval.SetWidthEmpty();
+
+ if(rRectSource.IsHeightEmpty())
+ aRetval.SetHeightEmpty();
+
+ return aRetval;
+}
+
+tools::Long OutputDevice::LogicToLogic( tools::Long nLongSource,
+ MapUnit eUnitSource, MapUnit eUnitDest )
+{
+ if ( eUnitSource == eUnitDest )
+ return nLongSource;
+
+ verifyUnitSourceDest( eUnitSource, eUnitDest );
+ const auto& [eFrom, eTo] = getCorrectedUnit(eUnitSource, eUnitDest);
+ return fn3(nLongSource, eFrom, eTo);
+}
+
+void OutputDevice::SetPixelOffset( const Size& rOffset )
+{
+ mnOutOffOrigX = rOffset.Width();
+ mnOutOffOrigY = rOffset.Height();
+
+ mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX );
+ mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetPixelOffset( rOffset );
+}
+
+
+double OutputDevice::ImplLogicWidthToDeviceSubPixel(tools::Long nWidth) const
+{
+ if (!mbMap)
+ return nWidth;
+
+ return ImplLogicToSubPixel(nWidth, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX);
+}
+
+double OutputDevice::ImplLogicHeightToDeviceSubPixel(tools::Long nHeight) const
+{
+ if (!mbMap)
+ return nHeight;
+
+ return ImplLogicToSubPixel(nHeight, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY);
+}
+
+basegfx::B2DPoint OutputDevice::ImplLogicToDeviceSubPixel(const Point& rPoint) const
+{
+ if (!mbMap)
+ return basegfx::B2DPoint(rPoint.X() + mnOutOffX, rPoint.Y() + mnOutOffY);
+
+ return basegfx::B2DPoint(ImplLogicToSubPixel(rPoint.X() + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX)
+ + mnOutOffX + mnOutOffOrigX,
+ ImplLogicToSubPixel(rPoint.Y() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY)
+ + mnOutOffY + mnOutOffOrigY);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/mask.cxx b/vcl/source/outdev/mask.cxx
new file mode 100644
index 0000000000..004b248785
--- /dev/null
+++ b/vcl/source/outdev/mask.cxx
@@ -0,0 +1,162 @@
+/* -*- 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/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+#include <salbmp.hxx>
+
+#include <cassert>
+
+void OutputDevice::DrawMask( const Point& rDestPt,
+ const Bitmap& rBitmap, const Color& rMaskColor )
+{
+ assert(!is_double_buffered_window());
+
+ const Size aSizePix( rBitmap.GetSizePixel() );
+ DrawMask( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmap, rMaskColor, MetaActionType::MASK );
+}
+
+void OutputDevice::DrawMask( const Point& rDestPt, const Size& rDestSize,
+ const Bitmap& rBitmap, const Color& rMaskColor )
+{
+ assert(!is_double_buffered_window());
+
+ DrawMask( rDestPt, rDestSize, Point(), rBitmap.GetSizePixel(), rBitmap, rMaskColor, MetaActionType::MASKSCALE );
+}
+
+void OutputDevice::DrawMask( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ const Bitmap& rBitmap, const Color& rMaskColor)
+{
+
+ assert(!is_double_buffered_window());
+
+ DrawMask( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmap, rMaskColor, MetaActionType::MASKSCALEPART );
+}
+
+void OutputDevice::DrawMask( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel,
+ const Bitmap& rBitmap, const Color& rMaskColor,
+ const MetaActionType nAction )
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if( RasterOp::Invert == meRasterOp )
+ {
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ return;
+ }
+
+ if ( mpMetaFile )
+ {
+ switch( nAction )
+ {
+ case MetaActionType::MASK:
+ mpMetaFile->AddAction( new MetaMaskAction( rDestPt,
+ rBitmap, rMaskColor ) );
+ break;
+
+ case MetaActionType::MASKSCALE:
+ mpMetaFile->AddAction( new MetaMaskScaleAction( rDestPt,
+ rDestSize, rBitmap, rMaskColor ) );
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ mpMetaFile->AddAction( new MetaMaskScalePartAction( rDestPt, rDestSize,
+ rSrcPtPixel, rSrcSizePixel, rBitmap, rMaskColor ) );
+ break;
+
+ default: break;
+ }
+ }
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ DrawDeviceMask( rBitmap, rMaskColor, rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel );
+
+}
+
+void OutputDevice::DrawDeviceMask( const Bitmap& rMask, const Color& rMaskColor,
+ const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel )
+{
+ assert(!is_double_buffered_window());
+
+ const std::shared_ptr<SalBitmap>& xImpBmp = rMask.ImplGetSalBitmap();
+ if (xImpBmp)
+ {
+ SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(),
+ ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()),
+ ImplLogicWidthToDevicePixel(rDestSize.Width()),
+ ImplLogicHeightToDevicePixel(rDestSize.Height()));
+
+ // we don't want to mirror via coordinates
+ const BmpMirrorFlags nMirrFlags = AdjustTwoRect( aPosAry, xImpBmp->GetSize() );
+
+ // check if output is necessary
+ if( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight )
+ {
+
+ if( nMirrFlags != BmpMirrorFlags::NONE )
+ {
+ Bitmap aTmp( rMask );
+ aTmp.Mirror( nMirrFlags );
+ mpGraphics->DrawMask( aPosAry, *aTmp.ImplGetSalBitmap(),
+ rMaskColor, *this);
+ }
+ else
+ mpGraphics->DrawMask( aPosAry, *xImpBmp, rMaskColor, *this );
+
+ }
+ }
+
+ // TODO: Use mask here
+ if( !mpAlphaVDev )
+ return;
+
+ const Bitmap& rAlphaMask( rMask.CreateMask( rMaskColor ) );
+
+ // #i25167# Restrict mask painting to _opaque_ areas
+ // of the mask, otherwise we spoil areas where no
+ // bitmap content was ever visible. Interestingly
+ // enough, this can be achieved by taking the mask as
+ // the transparency mask of itself
+ mpAlphaVDev->DrawBitmapEx( rDestPt,
+ rDestSize,
+ rSrcPtPixel,
+ rSrcSizePixel,
+ BitmapEx( rAlphaMask, rMask ) );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/nativecontrols.cxx b/vcl/source/outdev/nativecontrols.cxx
new file mode 100644
index 0000000000..1b035c72bd
--- /dev/null
+++ b/vcl/source/outdev/nativecontrols.cxx
@@ -0,0 +1,326 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/log.hxx>
+
+#include <vcl/pdfextoutdevdata.hxx>
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+
+#include <salgdi.hxx>
+#include <toolbarvalue.hxx>
+#include <menubarvalue.hxx>
+
+#include <cassert>
+
+ImplControlValue::~ImplControlValue()
+{
+}
+
+ImplControlValue* ImplControlValue::clone() const
+{
+ assert( typeid( const ImplControlValue ) == typeid( *this ));
+ return new ImplControlValue( *this );
+}
+
+ScrollbarValue::~ScrollbarValue()
+{
+}
+
+ScrollbarValue* ScrollbarValue::clone() const
+{
+ assert( typeid( const ScrollbarValue ) == typeid( *this ));
+ return new ScrollbarValue( *this );
+}
+
+SliderValue::~SliderValue()
+{
+}
+
+SliderValue* SliderValue::clone() const
+{
+ assert( typeid( const SliderValue ) == typeid( *this ));
+ return new SliderValue( *this );
+}
+
+int TabPaneValue::m_nOverlap = 0;
+
+TabPaneValue* TabPaneValue::clone() const
+{
+ assert(typeid(const TabPaneValue) == typeid(*this));
+ return new TabPaneValue(*this);
+}
+
+TabitemValue::~TabitemValue()
+{
+}
+
+TabitemValue* TabitemValue::clone() const
+{
+ assert( typeid( const TabitemValue ) == typeid( *this ));
+ return new TabitemValue( *this );
+}
+
+SpinbuttonValue::~SpinbuttonValue()
+{
+}
+
+SpinbuttonValue* SpinbuttonValue::clone() const
+{
+ assert( typeid( const SpinbuttonValue ) == typeid( *this ));
+ return new SpinbuttonValue( *this );
+}
+
+ToolbarValue::~ToolbarValue()
+{
+}
+
+ToolbarValue* ToolbarValue::clone() const
+{
+ assert( typeid( const ToolbarValue ) == typeid( *this ));
+ return new ToolbarValue( *this );
+}
+
+MenubarValue::~MenubarValue()
+{
+}
+
+MenubarValue* MenubarValue::clone() const
+{
+ assert( typeid( const MenubarValue ) == typeid( *this ));
+ return new MenubarValue( *this );
+}
+
+MenupopupValue::~MenupopupValue()
+{
+}
+
+MenupopupValue* MenupopupValue::clone() const
+{
+ assert( typeid( const MenupopupValue ) == typeid( *this ));
+ return new MenupopupValue( *this );
+}
+
+PushButtonValue::~PushButtonValue()
+{
+}
+
+PushButtonValue* PushButtonValue::clone() const
+{
+ assert( typeid( const PushButtonValue ) == typeid( *this ));
+ return new PushButtonValue( *this );
+}
+
+// These functions are mainly passthrough functions that allow access to
+// the SalFrame behind a Window object for native widget rendering purposes.
+
+bool OutputDevice::IsNativeControlSupported( ControlType nType, ControlPart nPart ) const
+{
+ if( !CanEnableNativeWidget() )
+ return false;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+
+ return mpGraphics->IsNativeControlSupported(nType, nPart);
+}
+
+bool OutputDevice::HitTestNativeScrollbar(
+ ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ const Point& aPos,
+ bool& rIsInside ) const
+{
+ if( !CanEnableNativeWidget() )
+ return false;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+
+ Point aWinOffs( mnOutOffX, mnOutOffY );
+ tools::Rectangle screenRegion( rControlRegion );
+ screenRegion.Move( aWinOffs.X(), aWinOffs.Y());
+
+ return mpGraphics->HitTestNativeScrollbar( nPart, screenRegion, Point( aPos.X() + mnOutOffX, aPos.Y() + mnOutOffY ),
+ rIsInside, *this );
+}
+
+static std::unique_ptr< ImplControlValue > TransformControlValue( const ImplControlValue& rVal, const OutputDevice& rDev )
+{
+ std::unique_ptr< ImplControlValue > aResult;
+ switch( rVal.getType() )
+ {
+ case ControlType::Slider:
+ {
+ const SliderValue* pSlVal = static_cast<const SliderValue*>(&rVal);
+ SliderValue* pNew = new SliderValue( *pSlVal );
+ aResult.reset( pNew );
+ pNew->maThumbRect = rDev.ImplLogicToDevicePixel( pSlVal->maThumbRect );
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ const ScrollbarValue* pScVal = static_cast<const ScrollbarValue*>(&rVal);
+ ScrollbarValue* pNew = new ScrollbarValue( *pScVal );
+ aResult.reset( pNew );
+ pNew->maThumbRect = rDev.ImplLogicToDevicePixel( pScVal->maThumbRect );
+ pNew->maButton1Rect = rDev.ImplLogicToDevicePixel( pScVal->maButton1Rect );
+ pNew->maButton2Rect = rDev.ImplLogicToDevicePixel( pScVal->maButton2Rect );
+ }
+ break;
+ case ControlType::SpinButtons:
+ {
+ const SpinbuttonValue* pSpVal = static_cast<const SpinbuttonValue*>(&rVal);
+ SpinbuttonValue* pNew = new SpinbuttonValue( *pSpVal );
+ aResult.reset( pNew );
+ pNew->maUpperRect = rDev.ImplLogicToDevicePixel( pSpVal->maUpperRect );
+ pNew->maLowerRect = rDev.ImplLogicToDevicePixel( pSpVal->maLowerRect );
+ }
+ break;
+ case ControlType::Toolbar:
+ {
+ const ToolbarValue* pTVal = static_cast<const ToolbarValue*>(&rVal);
+ ToolbarValue* pNew = new ToolbarValue( *pTVal );
+ aResult.reset( pNew );
+ pNew->maGripRect = rDev.ImplLogicToDevicePixel( pTVal->maGripRect );
+ }
+ break;
+ case ControlType::TabPane:
+ {
+ const TabPaneValue* pTIVal = static_cast<const TabPaneValue*>(&rVal);
+ TabPaneValue* pNew = new TabPaneValue(*pTIVal);
+ pNew->m_aTabHeaderRect = rDev.ImplLogicToDevicePixel(pTIVal->m_aTabHeaderRect);
+ pNew->m_aSelectedTabRect = rDev.ImplLogicToDevicePixel(pTIVal->m_aSelectedTabRect);
+ aResult.reset(pNew);
+ }
+ break;
+ case ControlType::TabItem:
+ {
+ const TabitemValue* pTIVal = static_cast<const TabitemValue*>(&rVal);
+ TabitemValue* pNew = new TabitemValue( *pTIVal );
+ pNew->maContentRect = rDev.ImplLogicToDevicePixel(pTIVal->maContentRect);
+ aResult.reset( pNew );
+ }
+ break;
+ case ControlType::Menubar:
+ {
+ const MenubarValue* pMVal = static_cast<const MenubarValue*>(&rVal);
+ MenubarValue* pNew = new MenubarValue( *pMVal );
+ aResult.reset( pNew );
+ }
+ break;
+ case ControlType::Pushbutton:
+ {
+ const PushButtonValue* pBVal = static_cast<const PushButtonValue*>(&rVal);
+ PushButtonValue* pNew = new PushButtonValue( *pBVal );
+ aResult.reset( pNew );
+ }
+ break;
+ case ControlType::Generic:
+ aResult = std::make_unique<ImplControlValue>( rVal );
+ break;
+ case ControlType::MenuPopup:
+ {
+ const MenupopupValue* pMVal = static_cast<const MenupopupValue*>(&rVal);
+ MenupopupValue* pNew = new MenupopupValue( *pMVal );
+ pNew->maItemRect = rDev.ImplLogicToDevicePixel( pMVal->maItemRect );
+ aResult.reset( pNew );
+ }
+ break;
+ default:
+ std::abort();
+ break;
+ }
+ return aResult;
+}
+bool OutputDevice::DrawNativeControl( ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ ControlState nState,
+ const ImplControlValue& aValue,
+ const OUString& aCaption,
+ const Color& rBackgroundColor )
+{
+ assert(!is_double_buffered_window());
+
+ if( !CanEnableNativeWidget() )
+ return false;
+
+ // make sure the current clip region is initialized correctly
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+ if ( mbOutputClipped )
+ return true;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ // Convert the coordinates from relative to Window-absolute, so we draw
+ // in the correct place in platform code
+ std::unique_ptr< ImplControlValue > aScreenCtrlValue( TransformControlValue( aValue, *this ) );
+ tools::Rectangle screenRegion( ImplLogicToDevicePixel( rControlRegion ) );
+
+ bool bRet = mpGraphics->DrawNativeControl(nType, nPart, screenRegion, nState, *aScreenCtrlValue, aCaption, *this, rBackgroundColor);
+
+ return bRet;
+}
+
+bool OutputDevice::GetNativeControlRegion( ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ ControlState nState,
+ const ImplControlValue& aValue,
+ tools::Rectangle &rNativeBoundingRegion,
+ tools::Rectangle &rNativeContentRegion ) const
+{
+ if( !CanEnableNativeWidget() )
+ return false;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+
+ // Convert the coordinates from relative to Window-absolute, so we draw
+ // in the correct place in platform code
+ std::unique_ptr< ImplControlValue > aScreenCtrlValue( TransformControlValue( aValue, *this ) );
+ tools::Rectangle screenRegion( ImplLogicToDevicePixel( rControlRegion ) );
+
+ bool bRet = mpGraphics->GetNativeControlRegion(nType, nPart, screenRegion, nState, *aScreenCtrlValue,
+ rNativeBoundingRegion,
+ rNativeContentRegion, *this );
+ if( bRet )
+ {
+ // transform back native regions
+ rNativeBoundingRegion = ImplDevicePixelToLogic( rNativeBoundingRegion );
+ rNativeContentRegion = ImplDevicePixelToLogic( rNativeContentRegion );
+ }
+
+ return bRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/outdev.cxx b/vcl/source/outdev/outdev.cxx
new file mode 100644
index 0000000000..828a121cca
--- /dev/null
+++ b/vcl/source/outdev/outdev.cxx
@@ -0,0 +1,822 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <sal/log.hxx>
+#include <comphelper/processfactory.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/graph.hxx>
+#include <vcl/lazydelete.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/virdev.hxx>
+
+#include <ImplOutDevData.hxx>
+#include <font/PhysicalFontFaceCollection.hxx>
+#include <salgdi.hxx>
+#include <window.h>
+
+#include <com/sun/star/awt/DeviceCapability.hpp>
+#include <com/sun/star/awt/XWindow.hpp>
+#include <com/sun/star/rendering/CanvasFactory.hpp>
+#include <com/sun/star/rendering/XSpriteCanvas.hpp>
+
+#ifdef DISABLE_DYNLOADING
+// Linking all needed LO code into one .so/executable, these already
+// exist in the tools library, so put them in the anonymous namespace
+// here to avoid clash...
+namespace {
+#endif
+#ifdef DISABLE_DYNLOADING
+}
+#endif
+
+using namespace ::com::sun::star::uno;
+
+// Begin initializer and accessor public functions
+
+OutputDevice::OutputDevice(OutDevType eOutDevType) :
+ meOutDevType(eOutDevType),
+ maRegion(true),
+ maFillColor( COL_WHITE ),
+ maTextLineColor( COL_TRANSPARENT ),
+ moSettings( Application::GetSettings() )
+{
+ mpGraphics = nullptr;
+ mpUnoGraphicsList = nullptr;
+ mpPrevGraphics = nullptr;
+ mpNextGraphics = nullptr;
+ mpMetaFile = nullptr;
+ mpFontInstance = nullptr;
+ mpForcedFallbackInstance = nullptr;
+ mpFontFaceCollection = nullptr;
+ mpAlphaVDev = nullptr;
+ mpExtOutDevData = nullptr;
+ mnOutOffX = 0;
+ mnOutOffY = 0;
+ mnOutWidth = 0;
+ mnOutHeight = 0;
+ mnDPIX = 0;
+ mnDPIY = 0;
+ mnDPIScalePercentage = 100;
+ mnTextOffX = 0;
+ mnTextOffY = 0;
+ mnOutOffOrigX = 0;
+ mnOutOffLogicX = 0;
+ mnOutOffOrigY = 0;
+ mnOutOffLogicY = 0;
+ mnEmphasisAscent = 0;
+ mnEmphasisDescent = 0;
+ mnDrawMode = DrawModeFlags::Default;
+ mnTextLayoutMode = vcl::text::ComplexTextLayoutFlags::Default;
+
+ if( AllSettings::GetLayoutRTL() ) //#i84553# tip BiDi preference to RTL
+ mnTextLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
+
+ meOutDevViewType = OutDevViewType::DontKnow;
+ mbMap = false;
+ mbClipRegion = false;
+ mbBackground = false;
+ mbOutput = true;
+ mbDevOutput = false;
+ mbOutputClipped = false;
+ maTextColor = COL_BLACK;
+ maOverlineColor = COL_TRANSPARENT;
+ meRasterOp = RasterOp::OverPaint;
+ mnAntialiasing = AntialiasingFlags::NONE;
+ meTextLanguage = LANGUAGE_SYSTEM; // TODO: get default from configuration?
+ mbLineColor = true;
+ mbFillColor = true;
+ mbInitLineColor = true;
+ mbInitFillColor = true;
+ mbInitFont = true;
+ mbInitTextColor = true;
+ mbInitClipRegion = true;
+ mbClipRegionSet = false;
+ mbNewFont = true;
+ mbTextLines = false;
+ mbTextSpecial = false;
+ mbRefPoint = false;
+ mbEnableRTL = false; // mirroring must be explicitly allowed (typically for windows only)
+
+ // struct ImplMapRes
+ maMapRes.mnMapOfsX = 0;
+ maMapRes.mnMapOfsY = 0;
+ maMapRes.mnMapScNumX = 1;
+ maMapRes.mnMapScNumY = 1;
+ maMapRes.mnMapScDenomX = 1;
+ maMapRes.mnMapScDenomY = 1;
+
+ // struct ImplOutDevData- see #i82615#
+ mpOutDevData.reset(new ImplOutDevData);
+ mpOutDevData->mpRotateDev = nullptr;
+ mpOutDevData->mpRecordLayout = nullptr;
+
+ // #i75163#
+ mpOutDevData->mpViewTransform = nullptr;
+ mpOutDevData->mpInverseViewTransform = nullptr;
+}
+
+OutputDevice::~OutputDevice()
+{
+ disposeOnce();
+}
+
+void OutputDevice::dispose()
+{
+ if ( GetUnoGraphicsList() )
+ {
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper( false );
+ if ( pWrapper )
+ pWrapper->ReleaseAllGraphics( this );
+ delete mpUnoGraphicsList;
+ mpUnoGraphicsList = nullptr;
+ }
+
+ mpOutDevData->mpRotateDev.disposeAndClear();
+
+ // #i75163#
+ ImplInvalidateViewTransform();
+
+ mpOutDevData.reset();
+
+ // for some reason, we haven't removed state from the stack properly
+ if ( !maOutDevStateStack.empty() )
+ SAL_WARN( "vcl.gdi", "OutputDevice::~OutputDevice(): OutputDevice::Push() calls != OutputDevice::Pop() calls" );
+ maOutDevStateStack.clear();
+
+ // release the active font instance
+ mpFontInstance.clear();
+ mpForcedFallbackInstance.clear();
+
+ // remove cached results of GetDevFontList/GetDevSizeList
+ mpFontFaceCollection.reset();
+
+ // release ImplFontCache specific to this OutputDevice
+ mxFontCache.reset();
+
+ // release ImplFontList specific to this OutputDevice
+ mxFontCollection.reset();
+
+ mpAlphaVDev.disposeAndClear();
+ mpPrevGraphics.clear();
+ mpNextGraphics.clear();
+ VclReferenceBase::dispose();
+}
+
+bool OutputDevice::IsVirtual() const
+{
+ return false;
+}
+
+SalGraphics* OutputDevice::GetGraphics()
+{
+ DBG_TESTSOLARMUTEX();
+
+ if (!mpGraphics && !AcquireGraphics())
+ SAL_WARN("vcl.gdi", "No mpGraphics set");
+
+ return mpGraphics;
+}
+
+SalGraphics const *OutputDevice::GetGraphics() const
+{
+ DBG_TESTSOLARMUTEX();
+
+ if (!mpGraphics && !AcquireGraphics())
+ SAL_WARN("vcl.gdi", "No mpGraphics set");
+
+ return mpGraphics;
+}
+
+void OutputDevice::SetConnectMetaFile( GDIMetaFile* pMtf )
+{
+ mpMetaFile = pMtf;
+}
+
+void OutputDevice::SetSettings( const AllSettings& rSettings )
+{
+ *moSettings = rSettings;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetSettings( rSettings );
+}
+
+SystemGraphicsData OutputDevice::GetSystemGfxData() const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return SystemGraphicsData();
+ assert(mpGraphics);
+
+ return mpGraphics->GetGraphicsData();
+}
+
+OUString OutputDevice::GetRenderBackendName() const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return {};
+ assert(mpGraphics);
+
+ return mpGraphics->getRenderBackendName();
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool OutputDevice::SupportsCairo() const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return false;
+ assert(mpGraphics);
+
+ return mpGraphics->SupportsCairo();
+}
+
+cairo::SurfaceSharedPtr OutputDevice::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return cairo::SurfaceSharedPtr();
+ assert(mpGraphics);
+ return mpGraphics->CreateSurface(rSurface);
+}
+
+cairo::SurfaceSharedPtr OutputDevice::CreateSurface(int x, int y, int width, int height) const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return cairo::SurfaceSharedPtr();
+ assert(mpGraphics);
+ return mpGraphics->CreateSurface(*this, x, y, width, height);
+}
+
+cairo::SurfaceSharedPtr OutputDevice::CreateBitmapSurface(const BitmapSystemData& rData, const Size& rSize) const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return cairo::SurfaceSharedPtr();
+ assert(mpGraphics);
+ return mpGraphics->CreateBitmapSurface(*this, rData, rSize);
+}
+
+css::uno::Any OutputDevice::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& rSurface, const basegfx::B2ISize& rSize) const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return css::uno::Any();
+ assert(mpGraphics);
+ return mpGraphics->GetNativeSurfaceHandle(rSurface, rSize);
+}
+
+#endif // ENABLE_CAIRO_CANVAS
+
+css::uno::Any OutputDevice::GetSystemGfxDataAny() const
+{
+ const SystemGraphicsData aSysData = GetSystemGfxData();
+ css::uno::Sequence< sal_Int8 > aSeq( reinterpret_cast<sal_Int8 const *>(&aSysData),
+ aSysData.nSize );
+
+ return css::uno::Any(aSeq);
+}
+
+void OutputDevice::SetRefPoint()
+{
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaRefPointAction( Point(), false ) );
+
+ mbRefPoint = false;
+ maRefPoint.setX(0);
+ maRefPoint.setY(0);
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetRefPoint();
+}
+
+void OutputDevice::SetRefPoint( const Point& rRefPoint )
+{
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaRefPointAction( rRefPoint, true ) );
+
+ mbRefPoint = true;
+ maRefPoint = rRefPoint;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetRefPoint( rRefPoint );
+}
+
+void OutputDevice::SetRasterOp( RasterOp eRasterOp )
+{
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaRasterOpAction( eRasterOp ) );
+
+ if ( meRasterOp != eRasterOp )
+ {
+ meRasterOp = eRasterOp;
+ mbInitLineColor = mbInitFillColor = true;
+
+ if( mpGraphics || AcquireGraphics() )
+ {
+ assert(mpGraphics);
+ mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp), RasterOp::Invert == meRasterOp );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetRasterOp( eRasterOp );
+}
+
+void OutputDevice::EnableOutput( bool bEnable )
+{
+ mbOutput = bEnable;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->EnableOutput( bEnable );
+}
+
+void OutputDevice::SetAntialiasing( AntialiasingFlags nMode )
+{
+ if ( mnAntialiasing != nMode )
+ {
+ mnAntialiasing = nMode;
+ mbInitFont = true;
+
+ if (mpGraphics)
+ mpGraphics->setAntiAlias(bool(mnAntialiasing & AntialiasingFlags::Enable));
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetAntialiasing( nMode );
+}
+
+void OutputDevice::SetDrawMode(DrawModeFlags nDrawMode)
+{
+ mnDrawMode = nDrawMode;
+
+ if (mpAlphaVDev)
+ mpAlphaVDev->SetDrawMode(nDrawMode);
+}
+
+sal_uInt16 OutputDevice::GetBitCount() const
+{
+ // we need a graphics instance
+ if ( !mpGraphics && !AcquireGraphics() )
+ return 0;
+ assert(mpGraphics);
+
+ return mpGraphics->GetBitCount();
+}
+
+void OutputDevice::SetOutOffXPixel(tools::Long nOutOffX)
+{
+ mnOutOffX = nOutOffX;
+}
+
+void OutputDevice::SetOutOffYPixel(tools::Long nOutOffY)
+{
+ mnOutOffY = nOutOffY;
+}
+
+css::uno::Reference< css::awt::XGraphics > OutputDevice::CreateUnoGraphics()
+{
+ UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper();
+ return pWrapper ? pWrapper->CreateGraphics( this ) : css::uno::Reference< css::awt::XGraphics >();
+}
+
+std::vector< VCLXGraphics* > *OutputDevice::CreateUnoGraphicsList()
+{
+ mpUnoGraphicsList = new std::vector< VCLXGraphics* >;
+ return mpUnoGraphicsList;
+}
+
+// Helper public function
+
+bool OutputDevice::SupportsOperation( OutDevSupportType eType ) const
+{
+ if( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+ const bool bHasSupport = mpGraphics->supportsOperation( eType );
+ return bHasSupport;
+}
+
+// Direct OutputDevice drawing public functions
+
+void OutputDevice::DrawOutDev( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPt, const Size& rSrcSize )
+{
+ if( ImplIsRecordLayout() )
+ return;
+
+ if ( RasterOp::Invert == meRasterOp )
+ {
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ return;
+ }
+
+ if ( mpMetaFile )
+ {
+ const Bitmap aBmp( GetBitmap( rSrcPt, rSrcSize ) );
+ mpMetaFile->AddAction( new MetaBmpScaleAction( rDestPt, rDestSize, aBmp ) );
+ }
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ tools::Long nSrcWidth = ImplLogicWidthToDevicePixel( rSrcSize.Width() );
+ tools::Long nSrcHeight = ImplLogicHeightToDevicePixel( rSrcSize.Height() );
+ tools::Long nDestWidth = ImplLogicWidthToDevicePixel( rDestSize.Width() );
+ tools::Long nDestHeight = ImplLogicHeightToDevicePixel( rDestSize.Height() );
+
+ if (nSrcWidth && nSrcHeight && nDestWidth && nDestHeight)
+ {
+ SalTwoRect aPosAry(ImplLogicXToDevicePixel(rSrcPt.X()), ImplLogicYToDevicePixel(rSrcPt.Y()),
+ nSrcWidth, nSrcHeight,
+ ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()),
+ nDestWidth, nDestHeight);
+
+ AdjustTwoRect( aPosAry, GetOutputRectPixel() );
+
+ if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight )
+ mpGraphics->CopyBits(aPosAry, *this);
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawOutDev( rDestPt, rDestSize, rSrcPt, rSrcSize );
+}
+
+void OutputDevice::DrawOutDev( const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPt, const Size& rSrcSize,
+ const OutputDevice& rOutDev )
+{
+ if ( ImplIsRecordLayout() )
+ return;
+
+ if ( RasterOp::Invert == meRasterOp )
+ {
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ return;
+ }
+
+ if ( mpMetaFile )
+ {
+ if (rOutDev.mpAlphaVDev)
+ {
+ const BitmapEx aBmpEx(rOutDev.GetBitmapEx(rSrcPt, rSrcSize));
+ mpMetaFile->AddAction(new MetaBmpExScaleAction(rDestPt, rDestSize, aBmpEx));
+ }
+ else
+ {
+ const Bitmap aBmp(rOutDev.GetBitmap(rSrcPt, rSrcSize));
+ mpMetaFile->AddAction(new MetaBmpScaleAction(rDestPt, rDestSize, aBmp));
+ }
+ }
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if (rOutDev.mpAlphaVDev)
+ {
+ // alpha-blend source over destination
+ DrawBitmapEx(rDestPt, rDestSize, rOutDev.GetBitmapEx(rSrcPt, rSrcSize));
+ }
+ else
+ {
+ SalTwoRect aPosAry(rOutDev.ImplLogicXToDevicePixel(rSrcPt.X()),
+ rOutDev.ImplLogicYToDevicePixel(rSrcPt.Y()),
+ rOutDev.ImplLogicWidthToDevicePixel(rSrcSize.Width()),
+ rOutDev.ImplLogicHeightToDevicePixel(rSrcSize.Height()),
+ ImplLogicXToDevicePixel(rDestPt.X()),
+ ImplLogicYToDevicePixel(rDestPt.Y()),
+ ImplLogicWidthToDevicePixel(rDestSize.Width()),
+ ImplLogicHeightToDevicePixel(rDestSize.Height()));
+
+ drawOutDevDirect(rOutDev, aPosAry);
+
+ // #i32109#: make destination rectangle opaque - source has no alpha
+ if (mpAlphaVDev)
+ mpAlphaVDev->ImplFillOpaqueRectangle(tools::Rectangle(rDestPt, rDestSize));
+ }
+}
+
+void OutputDevice::CopyArea( const Point& rDestPt,
+ const Point& rSrcPt, const Size& rSrcSize,
+ bool bWindowInvalidate )
+{
+ if ( ImplIsRecordLayout() )
+ return;
+
+ RasterOp eOldRop = GetRasterOp();
+ SetRasterOp( RasterOp::OverPaint );
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ tools::Long nSrcWidth = ImplLogicWidthToDevicePixel( rSrcSize.Width() );
+ tools::Long nSrcHeight = ImplLogicHeightToDevicePixel( rSrcSize.Height() );
+ if (nSrcWidth && nSrcHeight)
+ {
+ SalTwoRect aPosAry(ImplLogicXToDevicePixel(rSrcPt.X()), ImplLogicYToDevicePixel(rSrcPt.Y()),
+ nSrcWidth, nSrcHeight,
+ ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()),
+ nSrcWidth, nSrcHeight);
+
+ AdjustTwoRect( aPosAry, GetOutputRectPixel() );
+
+ CopyDeviceArea( aPosAry, bWindowInvalidate );
+ }
+
+ SetRasterOp( eOldRop );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->CopyArea( rDestPt, rSrcPt, rSrcSize, bWindowInvalidate );
+}
+
+// Direct OutputDevice drawing protected function
+
+void OutputDevice::CopyDeviceArea( SalTwoRect& aPosAry, bool /*bWindowInvalidate*/)
+{
+ if (aPosAry.mnSrcWidth == 0 || aPosAry.mnSrcHeight == 0 || aPosAry.mnDestWidth == 0 || aPosAry.mnDestHeight == 0)
+ return;
+
+ aPosAry.mnDestWidth = aPosAry.mnSrcWidth;
+ aPosAry.mnDestHeight = aPosAry.mnSrcHeight;
+ mpGraphics->CopyBits(aPosAry, *this);
+}
+
+// Direct OutputDevice drawing private function
+void OutputDevice::drawOutDevDirect(const OutputDevice& rSrcDev, SalTwoRect& rPosAry)
+{
+ SalGraphics* pSrcGraphics;
+ if (const OutputDevice* pCheckedSrc = DrawOutDevDirectCheck(rSrcDev))
+ {
+ if (!pCheckedSrc->mpGraphics && !pCheckedSrc->AcquireGraphics())
+ return;
+ pSrcGraphics = pCheckedSrc->mpGraphics;
+ }
+ else
+ pSrcGraphics = nullptr;
+
+ if (!mpGraphics && !AcquireGraphics())
+ return;
+ assert(mpGraphics);
+
+ // #102532# Offset only has to be pseudo window offset
+
+ AdjustTwoRect( rPosAry, rSrcDev.GetOutputRectPixel() );
+
+ if ( rPosAry.mnSrcWidth && rPosAry.mnSrcHeight && rPosAry.mnDestWidth && rPosAry.mnDestHeight )
+ {
+ // if this is no window, but rSrcDev is a window
+ // mirroring may be required
+ // because only windows have a SalGraphicsLayout
+ // mirroring is performed here
+ DrawOutDevDirectProcess(rSrcDev, rPosAry, pSrcGraphics);
+ }
+}
+
+const OutputDevice* OutputDevice::DrawOutDevDirectCheck(const OutputDevice& rSrcDev) const
+{
+ return this == &rSrcDev ? nullptr : &rSrcDev;
+}
+
+void OutputDevice::DrawOutDevDirectProcess(const OutputDevice& rSrcDev, SalTwoRect& rPosAry, SalGraphics* pSrcGraphics)
+{
+ if( pSrcGraphics && (pSrcGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ pSrcGraphics->mirror( aPosAry2.mnSrcX, aPosAry2.mnSrcWidth, rSrcDev );
+ mpGraphics->CopyBits( aPosAry2, *pSrcGraphics, *this, rSrcDev );
+ return;
+ }
+ if (pSrcGraphics)
+ mpGraphics->CopyBits( rPosAry, *pSrcGraphics, *this, rSrcDev );
+ else
+ mpGraphics->CopyBits( rPosAry, *this );
+}
+
+tools::Rectangle OutputDevice::GetBackgroundComponentBounds() const
+{
+ return tools::Rectangle( Point( 0, 0 ), GetOutputSizePixel() );
+}
+
+// Layout public functions
+
+void OutputDevice::EnableRTL( bool bEnable )
+{
+ mbEnableRTL = bEnable;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->EnableRTL( bEnable );
+}
+
+bool OutputDevice::ImplIsAntiparallel() const
+{
+ bool bRet = false;
+ if( AcquireGraphics() )
+ {
+ if( ( (mpGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) && ! IsRTLEnabled() ) ||
+ ( ! (mpGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) && IsRTLEnabled() ) )
+ {
+ bRet = true;
+ }
+ }
+ return bRet;
+}
+
+// note: the coordinates to be remirrored are in frame coordinates !
+
+void OutputDevice::ReMirror( Point &rPoint ) const
+{
+ rPoint.setX( mnOutOffX + mnOutWidth - 1 - rPoint.X() + mnOutOffX );
+}
+void OutputDevice::ReMirror( tools::Rectangle &rRect ) const
+{
+ tools::Long nWidth = rRect.Right() - rRect.Left();
+
+ //long lc_x = rRect.nLeft - mnOutOffX; // normalize
+ //lc_x = mnOutWidth - nWidth - 1 - lc_x; // mirror
+ //rRect.nLeft = lc_x + mnOutOffX; // re-normalize
+
+ rRect.SetLeft( mnOutOffX + mnOutWidth - nWidth - 1 - rRect.Left() + mnOutOffX );
+ rRect.SetRight( rRect.Left() + nWidth );
+}
+
+void OutputDevice::ReMirror( vcl::Region &rRegion ) const
+{
+ RectangleVector aRectangles;
+ rRegion.GetRegionRectangles(aRectangles);
+ vcl::Region aMirroredRegion;
+
+ for (auto & rectangle : aRectangles)
+ {
+ ReMirror(rectangle);
+ aMirroredRegion.Union(rectangle);
+ }
+
+ rRegion = aMirroredRegion;
+
+}
+
+bool OutputDevice::HasMirroredGraphics() const
+{
+ return ( AcquireGraphics() && (mpGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) );
+}
+
+bool OutputDevice::ImplIsRecordLayout() const
+{
+ if (!mpOutDevData)
+ return false;
+
+ return mpOutDevData->mpRecordLayout;
+}
+
+css::awt::DeviceInfo OutputDevice::GetCommonDeviceInfo(Size const& rDevSz) const
+{
+ css::awt::DeviceInfo aInfo;
+
+ aInfo.Width = rDevSz.Width();
+ aInfo.Height = rDevSz.Height();
+
+ Size aTmpSz = LogicToPixel(Size(1000, 1000), MapMode(MapUnit::MapMM));
+ aInfo.PixelPerMeterX = aTmpSz.Width();
+ aInfo.PixelPerMeterY = aTmpSz.Height();
+ aInfo.BitsPerPixel = GetBitCount();
+
+ aInfo.Capabilities = css::awt::DeviceCapability::RASTEROPERATIONS |
+ css::awt::DeviceCapability::GETBITS;
+
+ return aInfo;
+}
+
+css::awt::DeviceInfo OutputDevice::GetDeviceInfo() const
+{
+ css::awt::DeviceInfo aInfo = GetCommonDeviceInfo(GetOutputSizePixel());
+
+ aInfo.LeftInset = 0;
+ aInfo.TopInset = 0;
+ aInfo.RightInset = 0;
+ aInfo.BottomInset = 0;
+
+ return aInfo;
+}
+
+Reference< css::rendering::XCanvas > OutputDevice::GetCanvas() const
+{
+ // try to retrieve hard reference from weak member
+ Reference< css::rendering::XCanvas > xCanvas( mxCanvas );
+ // canvas still valid? Then we're done.
+ if( xCanvas.is() )
+ return xCanvas;
+ xCanvas = ImplGetCanvas( false );
+ mxCanvas = xCanvas;
+ return xCanvas;
+}
+
+Reference< css::rendering::XSpriteCanvas > OutputDevice::GetSpriteCanvas() const
+{
+ Reference< css::rendering::XCanvas > xCanvas( mxCanvas );
+ Reference< css::rendering::XSpriteCanvas > xSpriteCanvas( xCanvas, UNO_QUERY );
+ if( xSpriteCanvas.is() )
+ return xSpriteCanvas;
+ xCanvas = ImplGetCanvas( true );
+ mxCanvas = xCanvas;
+ return Reference< css::rendering::XSpriteCanvas >( xCanvas, UNO_QUERY );
+}
+
+// Generic implementation, Window will override.
+com::sun::star::uno::Reference< css::rendering::XCanvas > OutputDevice::ImplGetCanvas( bool bSpriteCanvas ) const
+{
+ /* Arguments:
+ 0: ptr to creating instance (Window or VirtualDevice)
+ 1: current bounds of creating instance
+ 2: bool, denoting always on top state for Window (always false for VirtualDevice)
+ 3: XWindow for creating Window (or empty for VirtualDevice)
+ 4: SystemGraphicsData as a streamed Any
+ */
+ Sequence< Any > aArg{
+ Any(reinterpret_cast<sal_Int64>(this)),
+ Any(css::awt::Rectangle( mnOutOffX, mnOutOffY, mnOutWidth, mnOutHeight )),
+ Any(false),
+ Any(Reference< css::awt::XWindow >()),
+ GetSystemGfxDataAny()
+ };
+
+ Reference< XComponentContext > xContext = comphelper::getProcessComponentContext();
+
+ static vcl::DeleteUnoReferenceOnDeinit<css::lang::XMultiComponentFactory> xStaticCanvasFactory(
+ css::rendering::CanvasFactory::create( xContext ) );
+ Reference<css::lang::XMultiComponentFactory> xCanvasFactory(xStaticCanvasFactory.get());
+ Reference< css::rendering::XCanvas > xCanvas;
+
+ if(xCanvasFactory.is())
+ {
+ xCanvas.set( xCanvasFactory->createInstanceWithArgumentsAndContext(
+ bSpriteCanvas ?
+ OUString( "com.sun.star.rendering.SpriteCanvas" ) :
+ OUString( "com.sun.star.rendering.Canvas" ),
+ aArg,
+ xContext ),
+ UNO_QUERY );
+ }
+
+ // no factory??? Empty reference, then.
+ return xCanvas;
+}
+
+void OutputDevice::ImplDisposeCanvas()
+{
+ css::uno::Reference< css::rendering::XCanvas > xCanvas( mxCanvas );
+ if( xCanvas.is() )
+ {
+ css::uno::Reference< css::lang::XComponent > xCanvasComponent( xCanvas, css::uno::UNO_QUERY );
+ if( xCanvasComponent.is() )
+ xCanvasComponent->dispose();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/vcl/source/outdev/pixel.cxx b/vcl/source/outdev/pixel.cxx
new file mode 100644
index 0000000000..8c97aeb432
--- /dev/null
+++ b/vcl/source/outdev/pixel.cxx
@@ -0,0 +1,118 @@
+/* -*- 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/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+
+#include <cassert>
+
+Color OutputDevice::GetPixel(const Point& rPoint) const
+{
+ Color aColor;
+
+ if (mpGraphics || AcquireGraphics())
+ {
+ assert(mpGraphics);
+ if (mbInitClipRegion)
+ const_cast<OutputDevice*>(this)->InitClipRegion();
+
+ if (!mbOutputClipped)
+ {
+ const tools::Long nX = ImplLogicXToDevicePixel(rPoint.X());
+ const tools::Long nY = ImplLogicYToDevicePixel(rPoint.Y());
+ aColor = mpGraphics->GetPixel(nX, nY, *this);
+
+ if (mpAlphaVDev)
+ {
+ Color aAlphaColor = mpAlphaVDev->GetPixel(rPoint);
+ aColor.SetAlpha(aAlphaColor.GetBlue());
+ }
+ }
+ }
+ return aColor;
+}
+
+void OutputDevice::DrawPixel( const Point& rPt )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPointAction( rPt ) );
+
+ if ( !IsDeviceOutputNecessary() || !mbLineColor || ImplIsRecordLayout() )
+ return;
+
+ Point aPt = ImplLogicToDevicePixel( rPt );
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ mpGraphics->DrawPixel( aPt.X(), aPt.Y(), *this );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPixel( rPt );
+}
+
+void OutputDevice::DrawPixel( const Point& rPt, const Color& rColor )
+{
+ assert(!is_double_buffered_window());
+
+ Color aColor = vcl::drawmode::GetLineColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPixelAction( rPt, aColor ) );
+
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ Point aPt = ImplLogicToDevicePixel( rPt );
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ mpGraphics->DrawPixel( aPt.X(), aPt.Y(), aColor, *this );
+
+ if (mpAlphaVDev)
+ {
+ Color aAlphaColor(rColor.GetAlpha(), rColor.GetAlpha(), rColor.GetAlpha());
+ mpAlphaVDev->DrawPixel(rPt, aAlphaColor);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/polygon.cxx b/vcl/source/outdev/polygon.cxx
new file mode 100644
index 0000000000..e764e6b66d
--- /dev/null
+++ b/vcl/source/outdev/polygon.cxx
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/types.h>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <tools/poly.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+#include <cassert>
+#include <memory>
+
+#define OUTDEV_POLYPOLY_STACKBUF 32
+
+void OutputDevice::DrawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
+{
+ assert(!is_double_buffered_window());
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPolyPolygonAction( rPolyPoly ) );
+
+ sal_uInt16 nPoly = rPolyPoly.Count();
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || !nPoly || ImplIsRecordLayout() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ // use b2dpolygon drawing if possible
+ if (RasterOp::OverPaint == GetRasterOp() && (IsLineColor() || IsFillColor()))
+ {
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon());
+
+ // ensure closed - may be asserted, will prevent buffering
+ if(!aB2DPolyPolygon.isClosed())
+ {
+ aB2DPolyPolygon.setClosed(true);
+ }
+
+ if (IsFillColor())
+ {
+ mpGraphics->DrawPolyPolygon(
+ aTransform,
+ aB2DPolyPolygon,
+ 0.0,
+ *this);
+ }
+
+ bool bSuccess(true);
+ if (IsLineColor())
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : std::as_const(aB2DPolyPolygon))
+ {
+ bSuccess = mpGraphics->DrawPolyLine(
+ aTransform,
+ rPolygon,
+ 0.0,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ *this);
+ if (!bSuccess)
+ break;
+ }
+ }
+
+ if(bSuccess)
+ {
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPolyPolygon( rPolyPoly );
+ return;
+ }
+ }
+
+ if ( nPoly == 1 )
+ {
+ // #100127# Map to DrawPolygon
+ const tools::Polygon& aPoly = rPolyPoly.GetObject( 0 );
+ if( aPoly.GetSize() >= 2 )
+ {
+ GDIMetaFile* pOldMF = mpMetaFile;
+ mpMetaFile = nullptr;
+
+ DrawPolygon( aPoly );
+
+ mpMetaFile = pOldMF;
+ }
+ }
+ else
+ {
+ // #100127# moved real tools::PolyPolygon draw to separate method,
+ // have to call recursively, avoiding duplicate
+ // ImplLogicToDevicePixel calls
+ ImplDrawPolyPolygon( nPoly, ImplLogicToDevicePixel( rPolyPoly ) );
+ }
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPolyPolygon( rPolyPoly );
+}
+
+void OutputDevice::DrawPolygon( const basegfx::B2DPolygon& rB2DPolygon)
+{
+ assert(!is_double_buffered_window());
+
+ // AW: Do NOT paint empty polygons
+ if(rB2DPolygon.count())
+ {
+ basegfx::B2DPolyPolygon aPP( rB2DPolygon );
+ DrawPolyPolygon( aPP );
+ }
+}
+
+void OutputDevice::DrawPolygon( const tools::Polygon& rPoly )
+{
+ assert(!is_double_buffered_window());
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPolygonAction( rPoly ) );
+
+ sal_uInt16 nPoints = rPoly.GetSize();
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || (nPoints < 2) || ImplIsRecordLayout() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ // use b2dpolygon drawing if possible
+ if (RasterOp::OverPaint == GetRasterOp() && (IsLineColor() || IsFillColor()))
+ {
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolygon aB2DPolygon(rPoly.getB2DPolygon());
+
+ // ensure closed - maybe assert, hinders buffering
+ if(!aB2DPolygon.isClosed())
+ {
+ aB2DPolygon.setClosed(true);
+ }
+
+ if (IsFillColor())
+ {
+ mpGraphics->DrawPolyPolygon(
+ aTransform,
+ basegfx::B2DPolyPolygon(aB2DPolygon),
+ 0.0,
+ *this);
+ }
+
+ bool bSuccess(true);
+ if (IsLineColor())
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ bSuccess = mpGraphics->DrawPolyLine(
+ aTransform,
+ aB2DPolygon,
+ 0.0,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ *this);
+ }
+
+ if(bSuccess)
+ {
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPolygon( rPoly );
+ return;
+ }
+ }
+
+ tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly );
+ const Point* pPtAry = aPoly.GetConstPointAry();
+
+ // #100127# Forward beziers to sal, if any
+ if( aPoly.HasFlags() )
+ {
+ const PolyFlags* pFlgAry = aPoly.GetConstFlagAry();
+ if( !mpGraphics->DrawPolygonBezier( nPoints, pPtAry, pFlgAry, *this ) )
+ {
+ aPoly = tools::Polygon::SubdivideBezier(aPoly);
+ pPtAry = aPoly.GetConstPointAry();
+ mpGraphics->DrawPolygon( aPoly.GetSize(), pPtAry, *this );
+ }
+ }
+ else
+ {
+ mpGraphics->DrawPolygon( nPoints, pPtAry, *this );
+ }
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPolygon( rPoly );
+}
+
+// Caution: This method is nearly the same as
+// OutputDevice::DrawTransparent( const basegfx::B2DPolyPolygon& rB2DPolyPoly, double fTransparency),
+// so when changes are made here do not forget to make changes there, too
+
+void OutputDevice::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rB2DPolyPoly )
+{
+ assert(!is_double_buffered_window());
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPolyPolygonAction( tools::PolyPolygon( rB2DPolyPoly ) ) );
+
+ // call helper
+ ImplDrawPolyPolygonWithB2DPolyPolygon(rB2DPolyPoly);
+}
+
+void OutputDevice::ImplDrawPolyPolygonWithB2DPolyPolygon(const basegfx::B2DPolyPolygon& rB2DPolyPoly)
+{
+ // Do not paint empty PolyPolygons
+ if(!rB2DPolyPoly.count() || !IsDeviceOutputNecessary())
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ if( mbInitFillColor )
+ InitFillColor();
+
+ bool bSuccess(false);
+
+ if (RasterOp::OverPaint == GetRasterOp() && (IsLineColor() || IsFillColor()))
+ {
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(rB2DPolyPoly);
+ bSuccess = true;
+
+ // ensure closed - maybe assert, hinders buffering
+ if(!aB2DPolyPolygon.isClosed())
+ {
+ aB2DPolyPolygon.setClosed(true);
+ }
+
+ if (IsFillColor())
+ {
+ mpGraphics->DrawPolyPolygon(
+ aTransform,
+ aB2DPolyPolygon,
+ 0.0,
+ *this);
+ }
+
+ if (IsLineColor())
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : std::as_const(aB2DPolyPolygon))
+ {
+ bSuccess = mpGraphics->DrawPolyLine(
+ aTransform,
+ rPolygon,
+ 0.0,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ *this);
+ if (!bSuccess)
+ break;
+ }
+ }
+ }
+
+ if (!bSuccess)
+ {
+ // fallback to old polygon drawing if needed
+ const tools::PolyPolygon aToolsPolyPolygon(rB2DPolyPoly);
+ const tools::PolyPolygon aPixelPolyPolygon = ImplLogicToDevicePixel(aToolsPolyPolygon);
+ ImplDrawPolyPolygon(aPixelPolyPolygon.Count(), aPixelPolyPolygon);
+ }
+
+ if (mpAlphaVDev)
+ mpAlphaVDev->ImplDrawPolyPolygonWithB2DPolyPolygon(rB2DPolyPoly);
+}
+
+// #100127# Extracted from OutputDevice::DrawPolyPolygon()
+void OutputDevice::ImplDrawPolyPolygon( sal_uInt16 nPoly, const tools::PolyPolygon& rPolyPoly )
+{
+ // AW: This crashes on empty PolyPolygons, avoid that
+ if(!nPoly)
+ return;
+
+ sal_uInt32 aStackAry1[OUTDEV_POLYPOLY_STACKBUF];
+ const Point* aStackAry2[OUTDEV_POLYPOLY_STACKBUF];
+ PolyFlags* aStackAry3[OUTDEV_POLYPOLY_STACKBUF];
+ sal_uInt32* pPointAry;
+ const Point** pPointAryAry;
+ const PolyFlags** pFlagAryAry;
+ sal_uInt16 i = 0;
+ sal_uInt16 j = 0;
+ sal_uInt16 last = 0;
+ bool bHaveBezier = false;
+ if ( nPoly > OUTDEV_POLYPOLY_STACKBUF )
+ {
+ pPointAry = new sal_uInt32[nPoly];
+ pPointAryAry = new const Point*[nPoly];
+ pFlagAryAry = new const PolyFlags*[nPoly];
+ }
+ else
+ {
+ pPointAry = aStackAry1;
+ pPointAryAry = aStackAry2;
+ pFlagAryAry = const_cast<const PolyFlags**>(aStackAry3);
+ }
+
+ do
+ {
+ const tools::Polygon& rPoly = rPolyPoly.GetObject( i );
+ sal_uInt16 nSize = rPoly.GetSize();
+ if ( nSize )
+ {
+ pPointAry[j] = nSize;
+ pPointAryAry[j] = rPoly.GetConstPointAry();
+ pFlagAryAry[j] = rPoly.GetConstFlagAry();
+ last = i;
+
+ if( pFlagAryAry[j] )
+ bHaveBezier = true;
+
+ ++j;
+ }
+ ++i;
+ }
+ while ( i < nPoly );
+
+ if ( j == 1 )
+ {
+ // #100127# Forward beziers to sal, if any
+ if( bHaveBezier )
+ {
+ if( !mpGraphics->DrawPolygonBezier( *pPointAry, *pPointAryAry, *pFlagAryAry, *this ) )
+ {
+ tools::Polygon aPoly = tools::Polygon::SubdivideBezier( rPolyPoly.GetObject( last ) );
+ mpGraphics->DrawPolygon( aPoly.GetSize(), aPoly.GetConstPointAry(), *this );
+ }
+ }
+ else
+ {
+ mpGraphics->DrawPolygon( *pPointAry, *pPointAryAry, *this );
+ }
+ }
+ else
+ {
+ // #100127# Forward beziers to sal, if any
+ if( bHaveBezier )
+ {
+ if (!mpGraphics->DrawPolyPolygonBezier(j, pPointAry, pPointAryAry, pFlagAryAry, *this))
+ {
+ tools::PolyPolygon aPolyPoly = tools::PolyPolygon::SubdivideBezier( rPolyPoly );
+ ImplDrawPolyPolygon( aPolyPoly.Count(), aPolyPoly );
+ }
+ }
+ else
+ {
+ mpGraphics->DrawPolyPolygon( j, pPointAry, pPointAryAry, *this );
+ }
+ }
+
+ if ( pPointAry != aStackAry1 )
+ {
+ delete[] pPointAry;
+ delete[] pPointAryAry;
+ delete[] pFlagAryAry;
+ }
+}
+
+void OutputDevice::ImplDrawPolygon( const tools::Polygon& rPoly, const tools::PolyPolygon* pClipPolyPoly )
+{
+ if( pClipPolyPoly )
+ {
+ ImplDrawPolyPolygon( tools::PolyPolygon(rPoly), pClipPolyPoly );
+ }
+ else
+ {
+ sal_uInt16 nPoints = rPoly.GetSize();
+
+ if ( nPoints < 2 )
+ return;
+
+ const Point* pPtAry = rPoly.GetConstPointAry();
+ mpGraphics->DrawPolygon( nPoints, pPtAry, *this );
+ }
+}
+
+void OutputDevice::ImplDrawPolyPolygon( const tools::PolyPolygon& rPolyPoly, const tools::PolyPolygon* pClipPolyPoly )
+{
+ tools::PolyPolygon* pPolyPoly;
+
+ if( pClipPolyPoly )
+ {
+ pPolyPoly = new tools::PolyPolygon;
+ rPolyPoly.GetIntersection( *pClipPolyPoly, *pPolyPoly );
+ }
+ else
+ {
+ pPolyPoly = const_cast<tools::PolyPolygon*>(&rPolyPoly);
+ }
+ if( pPolyPoly->Count() == 1 )
+ {
+ const tools::Polygon& rPoly = pPolyPoly->GetObject( 0 );
+ sal_uInt16 nSize = rPoly.GetSize();
+
+ if( nSize >= 2 )
+ {
+ const Point* pPtAry = rPoly.GetConstPointAry();
+ mpGraphics->DrawPolygon( nSize, pPtAry, *this );
+ }
+ }
+ else if( pPolyPoly->Count() )
+ {
+ sal_uInt16 nCount = pPolyPoly->Count();
+ std::unique_ptr<sal_uInt32[]> pPointAry(new sal_uInt32[nCount]);
+ std::unique_ptr<const Point*[]> pPointAryAry(new const Point*[nCount]);
+ sal_uInt16 i = 0;
+ do
+ {
+ const tools::Polygon& rPoly = pPolyPoly->GetObject( i );
+ sal_uInt16 nSize = rPoly.GetSize();
+ if ( nSize )
+ {
+ pPointAry[i] = nSize;
+ pPointAryAry[i] = rPoly.GetConstPointAry();
+ i++;
+ }
+ else
+ nCount--;
+ }
+ while( i < nCount );
+
+ if( nCount == 1 )
+ mpGraphics->DrawPolygon( pPointAry[0], pPointAryAry[0], *this );
+ else
+ mpGraphics->DrawPolyPolygon( nCount, pPointAry.get(), pPointAryAry.get(), *this );
+ }
+
+ if( pClipPolyPoly )
+ delete pPolyPoly;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/polyline.cxx b/vcl/source/outdev/polyline.cxx
new file mode 100644
index 0000000000..75e8252f4a
--- /dev/null
+++ b/vcl/source/outdev/polyline.cxx
@@ -0,0 +1,404 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/types.h>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+#include <cassert>
+
+void OutputDevice::DrawPolyLine( const tools::Polygon& rPoly )
+{
+ assert(!is_double_buffered_window());
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPolyLineAction( rPoly ) );
+
+ sal_uInt16 nPoints = rPoly.GetSize();
+
+ if ( !IsDeviceOutputNecessary() || !mbLineColor || (nPoints < 2) || ImplIsRecordLayout() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ // use b2dpolygon drawing if possible
+ if(DrawPolyLineDirectInternal(
+ basegfx::B2DHomMatrix(),
+ rPoly.getB2DPolygon()))
+ {
+ return;
+ }
+
+ const basegfx::B2DPolygon aB2DPolyLine(rPoly.getB2DPolygon());
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ bool bDrawn = mpGraphics->DrawPolyLine(
+ aTransform,
+ aB2DPolyLine,
+ 0.0,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0) /*default fMiterMinimumAngle, not used*/,
+ bPixelSnapHairline,
+ *this);
+
+ if(!bDrawn)
+ {
+ tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly );
+ Point* pPtAry = aPoly.GetPointAry();
+
+ // #100127# Forward beziers to sal, if any
+ if( aPoly.HasFlags() )
+ {
+ const PolyFlags* pFlgAry = aPoly.GetConstFlagAry();
+ if( !mpGraphics->DrawPolyLineBezier( nPoints, pPtAry, pFlgAry, *this ) )
+ {
+ aPoly = tools::Polygon::SubdivideBezier(aPoly);
+ pPtAry = aPoly.GetPointAry();
+ mpGraphics->DrawPolyLine( aPoly.GetSize(), pPtAry, *this );
+ }
+ }
+ else
+ {
+ mpGraphics->DrawPolyLine( nPoints, pPtAry, *this );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPolyLine( rPoly );
+}
+
+void OutputDevice::DrawPolyLine( const tools::Polygon& rPoly, const LineInfo& rLineInfo )
+{
+ assert(!is_double_buffered_window());
+
+ if ( rLineInfo.IsDefault() )
+ {
+ DrawPolyLine( rPoly );
+ return;
+ }
+
+ if (IsDeviceOutputNecessary())
+ {
+ auto eLineStyle = rLineInfo.GetStyle();
+ switch (eLineStyle)
+ {
+ case LineStyle::NONE:
+ case LineStyle::Dash:
+ // use drawPolyLine for these
+ break;
+ case LineStyle::Solid:
+ // #i101491# Try direct Fallback to B2D-Version of DrawPolyLine
+ DrawPolyLine(
+ rPoly.getB2DPolygon(),
+ rLineInfo.GetWidth(),
+ rLineInfo.GetLineJoin(),
+ rLineInfo.GetLineCap(),
+ basegfx::deg2rad(15.0) /* default fMiterMinimumAngle, value not available in LineInfo */);
+ return;
+ default:
+ SAL_WARN("vcl.gdi", "Unknown LineStyle: " << static_cast<int>(eLineStyle));
+ return;
+ }
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPolyLineAction( rPoly, rLineInfo ) );
+
+ drawPolyLine(rPoly, rLineInfo);
+}
+
+void OutputDevice::DrawPolyLine( const basegfx::B2DPolygon& rB2DPolygon,
+ double fLineWidth,
+ basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle)
+{
+ assert(!is_double_buffered_window());
+
+ if( mpMetaFile )
+ {
+ LineInfo aLineInfo;
+ if( fLineWidth != 0.0 )
+ aLineInfo.SetWidth( fLineWidth );
+
+ aLineInfo.SetLineJoin(eLineJoin);
+ aLineInfo.SetLineCap(eLineCap);
+
+ tools::Polygon aToolsPolygon( rB2DPolygon );
+ mpMetaFile->AddAction( new MetaPolyLineAction( std::move(aToolsPolygon), std::move(aLineInfo) ) );
+ }
+
+ // Do not paint empty PolyPolygons
+ if(!rB2DPolygon.count() || !IsDeviceOutputNecessary())
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ // use b2dpolygon drawing if possible
+ if(DrawPolyLineDirectInternal(
+ basegfx::B2DHomMatrix(),
+ rB2DPolygon,
+ fLineWidth,
+ 0.0,
+ nullptr, // MM01
+ eLineJoin,
+ eLineCap,
+ fMiterMinimumAngle))
+ {
+ return;
+ }
+
+ // #i101491#
+ // no output yet; fallback to geometry decomposition and use filled polygon paint
+ // when line is fat and not too complex. ImplDrawPolyPolygonWithB2DPolyPolygon
+ // will do internal needed AA checks etc.
+ if(fLineWidth >= 2.5 &&
+ rB2DPolygon.count() &&
+ rB2DPolygon.count() <= 1000)
+ {
+ const double fHalfLineWidth((fLineWidth * 0.5) + 0.5);
+ const basegfx::B2DPolyPolygon aAreaPolyPolygon(
+ basegfx::utils::createAreaGeometry( rB2DPolygon,
+ fHalfLineWidth,
+ eLineJoin,
+ eLineCap,
+ fMiterMinimumAngle));
+ const Color aOldLineColor(maLineColor);
+ const Color aOldFillColor(maFillColor);
+
+ SetLineColor();
+ InitLineColor();
+ SetFillColor(aOldLineColor);
+ InitFillColor();
+
+ // draw using a loop; else the topology will paint a PolyPolygon
+ for(auto const& rPolygon : aAreaPolyPolygon)
+ {
+ ImplDrawPolyPolygonWithB2DPolyPolygon(
+ basegfx::B2DPolyPolygon(rPolygon));
+ }
+
+ SetLineColor(aOldLineColor);
+ InitLineColor();
+ SetFillColor(aOldFillColor);
+ InitFillColor();
+
+ // when AA it is necessary to also paint the filled polygon's outline
+ // to avoid optical gaps
+ for(auto const& rPolygon : aAreaPolyPolygon)
+ {
+ (void)DrawPolyLineDirectInternal(
+ basegfx::B2DHomMatrix(),
+ rPolygon);
+ }
+ }
+ else
+ {
+ // fallback to old polygon drawing if needed
+ const tools::Polygon aToolsPolygon( rB2DPolygon );
+ LineInfo aLineInfo;
+ if( fLineWidth != 0.0 )
+ aLineInfo.SetWidth( fLineWidth );
+
+ drawPolyLine( aToolsPolygon, aLineInfo );
+ }
+}
+
+void OutputDevice::drawPolyLine(const tools::Polygon& rPoly, const LineInfo& rLineInfo)
+{
+ sal_uInt16 nPoints(rPoly.GetSize());
+
+ if ( !IsDeviceOutputNecessary() || !mbLineColor || ( nPoints < 2 ) || ( LineStyle::NONE == rLineInfo.GetStyle() ) || ImplIsRecordLayout() )
+ return;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ const LineInfo aInfo( ImplLogicToDevicePixel( rLineInfo ) );
+ const bool bDashUsed(LineStyle::Dash == aInfo.GetStyle());
+ const bool bLineWidthUsed(aInfo.GetWidth() > 1);
+
+ if (bDashUsed || bLineWidthUsed)
+ {
+ basegfx::B2DPolygon aPoly = ImplLogicToDevicePixel(rPoly.getB2DPolygon());
+ drawLine(basegfx::B2DPolyPolygon(aPoly), aInfo);
+ }
+ else
+ {
+ tools::Polygon aPoly = ImplLogicToDevicePixel(rPoly);
+
+ // #100127# the subdivision HAS to be done here since only a pointer
+ // to an array of points is given to the DrawPolyLine method, there is
+ // NO way to find out there that it's a curve.
+ if( aPoly.HasFlags() )
+ {
+ aPoly = tools::Polygon::SubdivideBezier( aPoly );
+ nPoints = aPoly.GetSize();
+ }
+
+ mpGraphics->DrawPolyLine(nPoints, aPoly.GetPointAry(), *this);
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPolyLine( rPoly, rLineInfo );
+}
+
+bool OutputDevice::DrawPolyLineDirect(
+ const basegfx::B2DHomMatrix& rObjectTransform,
+ const basegfx::B2DPolygon& rB2DPolygon,
+ double fLineWidth,
+ double fTransparency,
+ const std::vector< double >* pStroke, // MM01
+ basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle)
+{
+ if(DrawPolyLineDirectInternal(rObjectTransform, rB2DPolygon, fLineWidth, fTransparency,
+ pStroke, eLineJoin, eLineCap, fMiterMinimumAngle))
+ {
+ // Worked, add metafile action (if recorded). This is done only here,
+ // because this function is public, other OutDev functions already add metafile
+ // actions, so they call the internal function directly.
+ if( mpMetaFile )
+ {
+ LineInfo aLineInfo;
+ if( fLineWidth != 0.0 )
+ aLineInfo.SetWidth( fLineWidth );
+ // Transport known information, might be needed
+ aLineInfo.SetLineJoin(eLineJoin);
+ aLineInfo.SetLineCap(eLineCap);
+ // MiterMinimumAngle does not exist yet in LineInfo
+ tools::Polygon aToolsPolygon( rB2DPolygon );
+ mpMetaFile->AddAction( new MetaPolyLineAction( std::move(aToolsPolygon), std::move(aLineInfo) ) );
+ }
+ return true;
+ }
+ return false;
+}
+
+bool OutputDevice::DrawPolyLineDirectInternal(
+ const basegfx::B2DHomMatrix& rObjectTransform,
+ const basegfx::B2DPolygon& rB2DPolygon,
+ double fLineWidth,
+ double fTransparency,
+ const std::vector< double >* pStroke, // MM01
+ basegfx::B2DLineJoin eLineJoin,
+ css::drawing::LineCap eLineCap,
+ double fMiterMinimumAngle)
+{
+ assert(!is_double_buffered_window());
+
+ // AW: Do NOT paint empty PolyPolygons
+ if(!rB2DPolygon.count())
+ return true;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return false;
+ assert(mpGraphics);
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return true;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ const bool bTryB2d(RasterOp::OverPaint == GetRasterOp() && IsLineColor());
+
+ if(bTryB2d)
+ {
+ // combine rObjectTransform with WorldToDevice
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation() * rObjectTransform);
+ const bool bPixelSnapHairline((mnAntialiasing & AntialiasingFlags::PixelSnapHairline) && rB2DPolygon.count() < 1000);
+
+ const double fAdjustedTransparency = mpAlphaVDev ? 0 : fTransparency;
+ // draw the polyline
+ bool bDrawSuccess = mpGraphics->DrawPolyLine(
+ aTransform,
+ rB2DPolygon,
+ fAdjustedTransparency,
+ fLineWidth, // tdf#124848 use LineWidth direct, do not try to solve for zero-case (aka hairline)
+ pStroke, // MM01
+ eLineJoin,
+ eLineCap,
+ fMiterMinimumAngle,
+ bPixelSnapHairline,
+ *this);
+
+ if( bDrawSuccess )
+ {
+ if (mpAlphaVDev)
+ mpAlphaVDev->DrawPolyLineDirect(rObjectTransform, rB2DPolygon, fLineWidth,
+ fTransparency, pStroke, eLineJoin, eLineCap,
+ fMiterMinimumAngle);
+
+ return true;
+ }
+ }
+ return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/rect.cxx b/vcl/source/outdev/rect.cxx
new file mode 100644
index 0000000000..63ec16c62c
--- /dev/null
+++ b/vcl/source/outdev/rect.cxx
@@ -0,0 +1,438 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/types.h>
+#include <tools/poly.hxx>
+#include <tools/helpers.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+#include <cassert>
+
+void OutputDevice::DrawBorder(tools::Rectangle aBorderRect)
+{
+ sal_uInt16 nPixel = static_cast<sal_uInt16>(PixelToLogic(Size(1, 1)).Width());
+
+ aBorderRect.AdjustLeft(nPixel);
+ aBorderRect.AdjustTop(nPixel);
+
+ SetLineColor(COL_LIGHTGRAY);
+ DrawRect(aBorderRect);
+
+ aBorderRect.AdjustLeft(-nPixel);
+ aBorderRect.AdjustTop(-nPixel);
+ aBorderRect.AdjustRight(-nPixel);
+ aBorderRect.AdjustBottom(-nPixel);
+ SetLineColor(COL_GRAY);
+
+ DrawRect(aBorderRect);
+}
+
+void OutputDevice::DrawRect( const tools::Rectangle& rRect )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaRectAction( rRect ) );
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() )
+ return;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+
+ if ( aRect.IsEmpty() )
+ return;
+
+ aRect.Normalize();
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ mpGraphics->DrawRect( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), *this );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawRect( rRect );
+}
+
+void OutputDevice::DrawRect( const tools::Rectangle& rRect,
+ sal_uLong nHorzRound, sal_uLong nVertRound )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaRoundRectAction( rRect, nHorzRound, nVertRound ) );
+
+ if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() )
+ return;
+
+ const tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+
+ if ( aRect.IsEmpty() )
+ return;
+
+ nHorzRound = ImplLogicWidthToDevicePixel( nHorzRound );
+ nVertRound = ImplLogicHeightToDevicePixel( nVertRound );
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ if ( !nHorzRound && !nVertRound )
+ {
+ mpGraphics->DrawRect( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), *this );
+ }
+ else
+ {
+ tools::Polygon aRoundRectPoly( aRect, nHorzRound, nVertRound );
+
+ if ( aRoundRectPoly.GetSize() >= 2 )
+ {
+ Point* pPtAry = aRoundRectPoly.GetPointAry();
+
+ if ( !mbFillColor )
+ mpGraphics->DrawPolyLine( aRoundRectPoly.GetSize(), pPtAry, *this );
+ else
+ mpGraphics->DrawPolygon( aRoundRectPoly.GetSize(), pPtAry, *this );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawRect( rRect, nHorzRound, nVertRound );
+}
+
+void OutputDevice::Invert( const tools::Rectangle& rRect, InvertFlags nFlags )
+{
+ assert(!is_double_buffered_window());
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) );
+
+ if ( aRect.IsEmpty() )
+ return;
+ aRect.Normalize();
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ SalInvert nSalFlags = SalInvert::NONE;
+ if ( nFlags & InvertFlags::N50 )
+ nSalFlags |= SalInvert::N50;
+ if ( nFlags & InvertFlags::TrackFrame )
+ nSalFlags |= SalInvert::TrackFrame;
+ mpGraphics->Invert( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), nSalFlags, *this );
+}
+
+void OutputDevice::Invert( const tools::Polygon& rPoly, InvertFlags nFlags )
+{
+ assert(!is_double_buffered_window());
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ sal_uInt16 nPoints = rPoly.GetSize();
+
+ if ( nPoints < 2 )
+ return;
+
+ tools::Polygon aPoly( ImplLogicToDevicePixel( rPoly ) );
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ SalInvert nSalFlags = SalInvert::NONE;
+ if ( nFlags & InvertFlags::N50 )
+ nSalFlags |= SalInvert::N50;
+ if ( nFlags & InvertFlags::TrackFrame )
+ nSalFlags |= SalInvert::TrackFrame;
+ const Point* pPtAry = aPoly.GetConstPointAry();
+ mpGraphics->Invert( nPoints, pPtAry, nSalFlags, *this );
+}
+
+void OutputDevice::DrawCheckered(const Point& rPos, const Size& rSize, sal_uInt32 nLen, Color aStart, Color aEnd)
+{
+ assert(!is_double_buffered_window());
+
+ const sal_uInt32 nMaxX(rPos.X() + rSize.Width());
+ const sal_uInt32 nMaxY(rPos.Y() + rSize.Height());
+
+ Push(vcl::PushFlags::LINECOLOR|vcl::PushFlags::FILLCOLOR);
+ SetLineColor();
+
+ for(sal_uInt32 x(0), nX(rPos.X()); nX < nMaxX; x++, nX += nLen)
+ {
+ const sal_uInt32 nRight(std::min(nMaxX, nX + nLen));
+
+ for(sal_uInt32 y(0), nY(rPos.Y()); nY < nMaxY; y++, nY += nLen)
+ {
+ const sal_uInt32 nBottom(std::min(nMaxY, nY + nLen));
+
+ SetFillColor(((x & 0x0001) ^ (y & 0x0001)) ? aStart : aEnd);
+ DrawRect(tools::Rectangle(nX, nY, nRight, nBottom));
+ }
+ }
+
+ Pop();
+}
+
+void OutputDevice::DrawGrid( const tools::Rectangle& rRect, const Size& rDist, DrawGridFlags nFlags )
+{
+ assert(!is_double_buffered_window());
+
+ tools::Rectangle aDstRect( PixelToLogic( Point() ), GetOutputSize() );
+ aDstRect.Intersection( rRect );
+
+ if( aDstRect.IsEmpty() || ImplIsRecordLayout() )
+ return;
+
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ const tools::Long nDistX = std::max( rDist.Width(), tools::Long(1) );
+ const tools::Long nDistY = std::max( rDist.Height(), tools::Long(1) );
+ tools::Long nX = ( rRect.Left() >= aDstRect.Left() ) ? rRect.Left() : ( rRect.Left() + ( ( aDstRect.Left() - rRect.Left() ) / nDistX ) * nDistX );
+ tools::Long nY = ( rRect.Top() >= aDstRect.Top() ) ? rRect.Top() : ( rRect.Top() + ( ( aDstRect.Top() - rRect.Top() ) / nDistY ) * nDistY );
+ const tools::Long nRight = aDstRect.Right();
+ const tools::Long nBottom = aDstRect.Bottom();
+ const tools::Long nStartX = ImplLogicXToDevicePixel( nX );
+ const tools::Long nEndX = ImplLogicXToDevicePixel( nRight );
+ const tools::Long nStartY = ImplLogicYToDevicePixel( nY );
+ const tools::Long nEndY = ImplLogicYToDevicePixel( nBottom );
+ tools::Long nHorzCount = 0;
+ tools::Long nVertCount = 0;
+
+ std::vector< sal_Int32 > aVertBuf;
+ std::vector< sal_Int32 > aHorzBuf;
+
+ if( ( nFlags & DrawGridFlags::Dots ) || ( nFlags & DrawGridFlags::HorzLines ) )
+ {
+ aVertBuf.resize( aDstRect.GetHeight() / nDistY + 2 );
+ aVertBuf[ nVertCount++ ] = nStartY;
+ while( ( nY += nDistY ) <= nBottom )
+ {
+ aVertBuf[ nVertCount++ ] = ImplLogicYToDevicePixel( nY );
+ }
+ }
+
+ if( ( nFlags & DrawGridFlags::Dots ) || ( nFlags & DrawGridFlags::VertLines ) )
+ {
+ aHorzBuf.resize( aDstRect.GetWidth() / nDistX + 2 );
+ aHorzBuf[ nHorzCount++ ] = nStartX;
+ while( ( nX += nDistX ) <= nRight )
+ {
+ aHorzBuf[ nHorzCount++ ] = ImplLogicXToDevicePixel( nX );
+ }
+ }
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ if( mbInitFillColor )
+ InitFillColor();
+
+ const bool bOldMap = mbMap;
+ EnableMapMode( false );
+
+ if( nFlags & DrawGridFlags::Dots )
+ {
+ for( tools::Long i = 0; i < nVertCount; i++ )
+ {
+ for( tools::Long j = 0, Y = aVertBuf[ i ]; j < nHorzCount; j++ )
+ {
+ mpGraphics->DrawPixel( aHorzBuf[ j ], Y, *this );
+ }
+ }
+ }
+ else
+ {
+ if( nFlags & DrawGridFlags::HorzLines )
+ {
+ for( tools::Long i = 0; i < nVertCount; i++ )
+ {
+ nY = aVertBuf[ i ];
+ mpGraphics->DrawLine( nStartX, nY, nEndX, nY, *this );
+ }
+ }
+
+ if( nFlags & DrawGridFlags::VertLines )
+ {
+ for( tools::Long i = 0; i < nHorzCount; i++ )
+ {
+ nX = aHorzBuf[ i ];
+ mpGraphics->DrawLine( nX, nStartY, nX, nEndY, *this );
+ }
+ }
+ }
+
+ EnableMapMode( bOldMap );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawGrid( rRect, rDist, nFlags );
+}
+
+BmpMirrorFlags AdjustTwoRect( SalTwoRect& rTwoRect, const Size& rSizePix )
+{
+ BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE;
+
+ if ( rTwoRect.mnDestWidth < 0 )
+ {
+ rTwoRect.mnSrcX = rSizePix.Width() - rTwoRect.mnSrcX - rTwoRect.mnSrcWidth;
+ rTwoRect.mnDestWidth = -rTwoRect.mnDestWidth;
+ rTwoRect.mnDestX -= rTwoRect.mnDestWidth-1;
+ nMirrFlags |= BmpMirrorFlags::Horizontal;
+ }
+
+ if ( rTwoRect.mnDestHeight < 0 )
+ {
+ rTwoRect.mnSrcY = rSizePix.Height() - rTwoRect.mnSrcY - rTwoRect.mnSrcHeight;
+ rTwoRect.mnDestHeight = -rTwoRect.mnDestHeight;
+ rTwoRect.mnDestY -= rTwoRect.mnDestHeight-1;
+ nMirrFlags |= BmpMirrorFlags::Vertical;
+ }
+
+ if( ( rTwoRect.mnSrcX < 0 ) || ( rTwoRect.mnSrcX >= rSizePix.Width() ) ||
+ ( rTwoRect.mnSrcY < 0 ) || ( rTwoRect.mnSrcY >= rSizePix.Height() ) ||
+ ( ( rTwoRect.mnSrcX + rTwoRect.mnSrcWidth ) > rSizePix.Width() ) ||
+ ( ( rTwoRect.mnSrcY + rTwoRect.mnSrcHeight ) > rSizePix.Height() ) )
+ {
+ const tools::Rectangle aSourceRect( Point( rTwoRect.mnSrcX, rTwoRect.mnSrcY ),
+ Size( rTwoRect.mnSrcWidth, rTwoRect.mnSrcHeight ) );
+ tools::Rectangle aCropRect( aSourceRect );
+
+ aCropRect.Intersection( tools::Rectangle( Point(), rSizePix ) );
+
+ if( aCropRect.IsEmpty() )
+ {
+ rTwoRect.mnSrcWidth = rTwoRect.mnSrcHeight = rTwoRect.mnDestWidth = rTwoRect.mnDestHeight = 0;
+ }
+ else
+ {
+ const double fFactorX = ( rTwoRect.mnSrcWidth > 1 ) ? static_cast<double>( rTwoRect.mnDestWidth - 1 ) / ( rTwoRect.mnSrcWidth - 1 ) : 0.0;
+ const double fFactorY = ( rTwoRect.mnSrcHeight > 1 ) ? static_cast<double>( rTwoRect.mnDestHeight - 1 ) / ( rTwoRect.mnSrcHeight - 1 ) : 0.0;
+
+ const tools::Long nDstX1 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Left() - rTwoRect.mnSrcX ) );
+ const tools::Long nDstY1 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Top() - rTwoRect.mnSrcY ) );
+ const tools::Long nDstX2 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Right() - rTwoRect.mnSrcX ) );
+ const tools::Long nDstY2 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Bottom() - rTwoRect.mnSrcY ) );
+
+ rTwoRect.mnSrcX = aCropRect.Left();
+ rTwoRect.mnSrcY = aCropRect.Top();
+ rTwoRect.mnSrcWidth = aCropRect.GetWidth();
+ rTwoRect.mnSrcHeight = aCropRect.GetHeight();
+ rTwoRect.mnDestX = nDstX1;
+ rTwoRect.mnDestY = nDstY1;
+ rTwoRect.mnDestWidth = nDstX2 - nDstX1 + 1;
+ rTwoRect.mnDestHeight = nDstY2 - nDstY1 + 1;
+ }
+ }
+
+ return nMirrFlags;
+}
+
+void AdjustTwoRect( SalTwoRect& rTwoRect, const tools::Rectangle& rValidSrcRect )
+{
+ if( !(( rTwoRect.mnSrcX < rValidSrcRect.Left() ) || ( rTwoRect.mnSrcX >= rValidSrcRect.Right() ) ||
+ ( rTwoRect.mnSrcY < rValidSrcRect.Top() ) || ( rTwoRect.mnSrcY >= rValidSrcRect.Bottom() ) ||
+ ( ( rTwoRect.mnSrcX + rTwoRect.mnSrcWidth ) > rValidSrcRect.Right() ) ||
+ ( ( rTwoRect.mnSrcY + rTwoRect.mnSrcHeight ) > rValidSrcRect.Bottom() )) )
+ return;
+
+ const tools::Rectangle aSourceRect( Point( rTwoRect.mnSrcX, rTwoRect.mnSrcY ),
+ Size( rTwoRect.mnSrcWidth, rTwoRect.mnSrcHeight ) );
+ tools::Rectangle aCropRect( aSourceRect );
+
+ aCropRect.Intersection( rValidSrcRect );
+
+ if( aCropRect.IsEmpty() )
+ {
+ rTwoRect.mnSrcWidth = rTwoRect.mnSrcHeight = rTwoRect.mnDestWidth = rTwoRect.mnDestHeight = 0;
+ }
+ else
+ {
+ const double fFactorX = ( rTwoRect.mnSrcWidth > 1 ) ? static_cast<double>( rTwoRect.mnDestWidth - 1 ) / ( rTwoRect.mnSrcWidth - 1 ) : 0.0;
+ const double fFactorY = ( rTwoRect.mnSrcHeight > 1 ) ? static_cast<double>( rTwoRect.mnDestHeight - 1 ) / ( rTwoRect.mnSrcHeight - 1 ) : 0.0;
+
+ const tools::Long nDstX1 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Left() - rTwoRect.mnSrcX ) );
+ const tools::Long nDstY1 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Top() - rTwoRect.mnSrcY ) );
+ const tools::Long nDstX2 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Right() - rTwoRect.mnSrcX ) );
+ const tools::Long nDstY2 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Bottom() - rTwoRect.mnSrcY ) );
+
+ rTwoRect.mnSrcX = aCropRect.Left();
+ rTwoRect.mnSrcY = aCropRect.Top();
+ rTwoRect.mnSrcWidth = aCropRect.GetWidth();
+ rTwoRect.mnSrcHeight = aCropRect.GetHeight();
+ rTwoRect.mnDestX = nDstX1;
+ rTwoRect.mnDestY = nDstY1;
+ rTwoRect.mnDestWidth = nDstX2 - nDstX1 + 1;
+ rTwoRect.mnDestHeight = nDstY2 - nDstY1 + 1;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/stack.cxx b/vcl/source/outdev/stack.cxx
new file mode 100644
index 0000000000..129348051e
--- /dev/null
+++ b/vcl/source/outdev/stack.cxx
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+#include <vcl/metaact.hxx>
+#include <vcl/rendercontext/State.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/settings.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+
+void OutputDevice::Push(vcl::PushFlags nFlags)
+{
+ if (mpMetaFile)
+ mpMetaFile->AddAction(new MetaPushAction(nFlags));
+
+ maOutDevStateStack.emplace_back();
+ vcl::State& rState = maOutDevStateStack.back();
+
+ rState.mnFlags = nFlags;
+
+ if (nFlags & vcl::PushFlags::LINECOLOR && mbLineColor)
+ rState.mpLineColor = maLineColor;
+
+ if (nFlags & vcl::PushFlags::FILLCOLOR && mbFillColor)
+ rState.mpFillColor = maFillColor;
+
+ if (nFlags & vcl::PushFlags::FONT)
+ rState.mpFont = maFont;
+
+ if (nFlags & vcl::PushFlags::TEXTCOLOR)
+ rState.mpTextColor = GetTextColor();
+
+ if (nFlags & vcl::PushFlags::TEXTFILLCOLOR && IsTextFillColor())
+ rState.mpTextFillColor = GetTextFillColor();
+
+ if (nFlags & vcl::PushFlags::TEXTLINECOLOR && IsTextLineColor())
+ rState.mpTextLineColor = GetTextLineColor();
+
+ if (nFlags & vcl::PushFlags::OVERLINECOLOR && IsOverlineColor())
+ rState.mpOverlineColor = GetOverlineColor();
+
+ if (nFlags & vcl::PushFlags::TEXTALIGN)
+ rState.meTextAlign = GetTextAlign();
+
+ if (nFlags & vcl::PushFlags::TEXTLAYOUTMODE)
+ rState.mnTextLayoutMode = GetLayoutMode();
+
+ if (nFlags & vcl::PushFlags::TEXTLANGUAGE)
+ rState.meTextLanguage = GetDigitLanguage();
+
+ if (nFlags & vcl::PushFlags::RASTEROP)
+ rState.meRasterOp = GetRasterOp();
+
+ if (nFlags & vcl::PushFlags::MAPMODE)
+ {
+ rState.mpMapMode = maMapMode;
+ rState.mbMapActive = mbMap;
+ }
+
+ if (nFlags & vcl::PushFlags::CLIPREGION && mbClipRegion)
+ rState.mpClipRegion.reset(new vcl::Region(maRegion));
+
+ if (nFlags & vcl::PushFlags::REFPOINT && mbRefPoint)
+ rState.mpRefPoint = maRefPoint;
+
+ if (nFlags & vcl::PushFlags::RTLENABLED)
+ rState.mbRTLEnabled = IsRTLEnabled();
+
+ if (mpAlphaVDev)
+ mpAlphaVDev->Push();
+}
+
+void OutputDevice::Pop()
+{
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPopAction() );
+
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ mpMetaFile = nullptr;
+
+ if ( maOutDevStateStack.empty() )
+ {
+ SAL_WARN( "vcl.gdi", "OutputDevice::Pop() without OutputDevice::Push()" );
+ return;
+ }
+ const vcl::State& rState = maOutDevStateStack.back();
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->Pop();
+
+ if ( rState.mnFlags & vcl::PushFlags::LINECOLOR )
+ {
+ if ( rState.mpLineColor )
+ SetLineColor( *rState.mpLineColor );
+ else
+ SetLineColor();
+ }
+
+ if ( rState.mnFlags & vcl::PushFlags::FILLCOLOR )
+ {
+ if ( rState.mpFillColor )
+ SetFillColor( *rState.mpFillColor );
+ else
+ SetFillColor();
+ }
+
+ if ( rState.mnFlags & vcl::PushFlags::FONT )
+ SetFont( *rState.mpFont );
+
+ if ( rState.mnFlags & vcl::PushFlags::TEXTCOLOR )
+ SetTextColor( *rState.mpTextColor );
+
+ if ( rState.mnFlags & vcl::PushFlags::TEXTFILLCOLOR )
+ {
+ if ( rState.mpTextFillColor )
+ SetTextFillColor( *rState.mpTextFillColor );
+ else
+ SetTextFillColor();
+ }
+
+ if ( rState.mnFlags & vcl::PushFlags::TEXTLINECOLOR )
+ {
+ if ( rState.mpTextLineColor )
+ SetTextLineColor( *rState.mpTextLineColor );
+ else
+ SetTextLineColor();
+ }
+
+ if ( rState.mnFlags & vcl::PushFlags::OVERLINECOLOR )
+ {
+ if ( rState.mpOverlineColor )
+ SetOverlineColor( *rState.mpOverlineColor );
+ else
+ SetOverlineColor();
+ }
+
+ if ( rState.mnFlags & vcl::PushFlags::TEXTALIGN )
+ SetTextAlign( rState.meTextAlign );
+
+ if( rState.mnFlags & vcl::PushFlags::TEXTLAYOUTMODE )
+ SetLayoutMode( rState.mnTextLayoutMode );
+
+ if( rState.mnFlags & vcl::PushFlags::TEXTLANGUAGE )
+ SetDigitLanguage( rState.meTextLanguage );
+
+ if ( rState.mnFlags & vcl::PushFlags::RASTEROP )
+ SetRasterOp( rState.meRasterOp );
+
+ if ( rState.mnFlags & vcl::PushFlags::MAPMODE )
+ {
+ if ( rState.mpMapMode )
+ SetMapMode( *rState.mpMapMode );
+ else
+ SetMapMode();
+ mbMap = rState.mbMapActive;
+ }
+
+ if ( rState.mnFlags & vcl::PushFlags::CLIPREGION )
+ SetDeviceClipRegion( rState.mpClipRegion.get() );
+
+ if ( rState.mnFlags & vcl::PushFlags::REFPOINT )
+ {
+ if ( rState.mpRefPoint )
+ SetRefPoint( *rState.mpRefPoint );
+ else
+ SetRefPoint();
+ }
+
+ if ( rState.mnFlags & vcl::PushFlags::RTLENABLED )
+ EnableRTL( rState.mbRTLEnabled );
+
+ maOutDevStateStack.pop_back();
+
+ mpMetaFile = pOldMetaFile;
+}
+
+void OutputDevice::ClearStack()
+{
+ sal_uInt32 nCount = maOutDevStateStack.size();
+ while( nCount-- )
+ Pop();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/text.cxx b/vcl/source/outdev/text.cxx
new file mode 100644
index 0000000000..1b40a1b2de
--- /dev/null
+++ b/vcl/source/outdev/text.cxx
@@ -0,0 +1,2106 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <sal/log.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <tools/lineend.hxx>
+#include <tools/debug.hxx>
+#include <unotools/configmgr.hxx>
+
+#include <vcl/ctrl.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/mnemonic.hxx>
+#include <vcl/textrectinfo.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <ImplLayoutArgs.hxx>
+#include <ImplOutDevData.hxx>
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+#include <svdata.hxx>
+#include <textlayout.hxx>
+#include <textlineinfo.hxx>
+#include <impglyphitem.hxx>
+#include <TextLayoutCache.hxx>
+#include <font/PhysicalFontFace.hxx>
+
+#include <memory>
+#include <optional>
+
+#define TEXT_DRAW_ELLIPSIS (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis)
+
+void OutputDevice::SetLayoutMode( vcl::text::ComplexTextLayoutFlags nTextLayoutMode )
+{
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaLayoutModeAction( nTextLayoutMode ) );
+
+ mnTextLayoutMode = nTextLayoutMode;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetLayoutMode( nTextLayoutMode );
+}
+
+void OutputDevice::SetDigitLanguage( LanguageType eTextLanguage )
+{
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextLanguageAction( eTextLanguage ) );
+
+ meTextLanguage = eTextLanguage;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetDigitLanguage( eTextLanguage );
+}
+
+void OutputDevice::ImplInitTextColor()
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( mbInitTextColor )
+ {
+ mpGraphics->SetTextColor( GetTextColor() );
+ mbInitTextColor = false;
+ }
+}
+
+OUString OutputDevice::GetEllipsisString( const OUString& rOrigStr, tools::Long nMaxWidth,
+ DrawTextFlags nStyle ) const
+{
+ vcl::DefaultTextLayout aTextLayout(*const_cast< OutputDevice* >(this));
+ return aTextLayout.GetEllipsisString(rOrigStr, nMaxWidth, nStyle);
+}
+
+void OutputDevice::ImplDrawTextRect( tools::Long nBaseX, tools::Long nBaseY,
+ tools::Long nDistX, tools::Long nDistY, tools::Long nWidth, tools::Long nHeight )
+{
+ tools::Long nX = nDistX;
+ tools::Long nY = nDistY;
+
+ Degree10 nOrientation = mpFontInstance->mnOrientation;
+ if ( nOrientation )
+ {
+ // Rotate rect without rounding problems for 90 degree rotations
+ if ( !(nOrientation % 900_deg10) )
+ {
+ if ( nOrientation == 900_deg10 )
+ {
+ tools::Long nTemp = nX;
+ nX = nY;
+ nY = -nTemp;
+ nTemp = nWidth;
+ nWidth = nHeight;
+ nHeight = nTemp;
+ nY -= nHeight;
+ }
+ else if ( nOrientation == 1800_deg10 )
+ {
+ nX = -nX;
+ nY = -nY;
+ nX -= nWidth;
+ nY -= nHeight;
+ }
+ else /* ( nOrientation == 2700 ) */
+ {
+ tools::Long nTemp = nX;
+ nX = -nY;
+ nY = nTemp;
+ nTemp = nWidth;
+ nWidth = nHeight;
+ nHeight = nTemp;
+ nX -= nWidth;
+ }
+ }
+ else
+ {
+ nX += nBaseX;
+ nY += nBaseY;
+ // inflate because polygons are drawn smaller
+ tools::Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) );
+ tools::Polygon aPoly( aRect );
+ aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontInstance->mnOrientation );
+ ImplDrawPolygon( aPoly );
+ return;
+ }
+ }
+
+ nX += nBaseX;
+ nY += nBaseY;
+ mpGraphics->DrawRect( nX, nY, nWidth, nHeight, *this ); // original code
+
+}
+
+void OutputDevice::ImplDrawTextBackground( const SalLayout& rSalLayout )
+{
+ const double nWidth = rSalLayout.GetTextWidth();
+ const basegfx::B2DPoint aBase = rSalLayout.DrawBase();
+ const tools::Long nX = aBase.getX();
+ const tools::Long nY = aBase.getY();
+
+ if ( mbLineColor || mbInitLineColor )
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+ mpGraphics->SetFillColor( GetTextFillColor() );
+ mbInitFillColor = true;
+
+ ImplDrawTextRect( nX, nY, 0, -(mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent),
+ nWidth,
+ mpFontInstance->mnLineHeight+mnEmphasisAscent+mnEmphasisDescent );
+}
+
+tools::Rectangle OutputDevice::ImplGetTextBoundRect( const SalLayout& rSalLayout ) const
+{
+ basegfx::B2DPoint aPoint = rSalLayout.GetDrawPosition();
+ tools::Long nX = aPoint.getX();
+ tools::Long nY = aPoint.getY();
+
+ double nWidth = rSalLayout.GetTextWidth();
+ tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
+
+ nY -= mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;
+
+ if ( mpFontInstance->mnOrientation )
+ {
+ tools::Long nBaseX = nX, nBaseY = nY;
+ if ( !(mpFontInstance->mnOrientation % 900_deg10) )
+ {
+ tools::Long nX2 = nX+nWidth;
+ tools::Long nY2 = nY+nHeight;
+
+ Point aBasePt( nBaseX, nBaseY );
+ aBasePt.RotateAround( nX, nY, mpFontInstance->mnOrientation );
+ aBasePt.RotateAround( nX2, nY2, mpFontInstance->mnOrientation );
+ nWidth = nX2-nX;
+ nHeight = nY2-nY;
+ }
+ else
+ {
+ // inflate by +1+1 because polygons are drawn smaller
+ tools::Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) );
+ tools::Polygon aPoly( aRect );
+ aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontInstance->mnOrientation );
+ return aPoly.GetBoundRect();
+ }
+ }
+
+ return tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) );
+}
+
+bool OutputDevice::ImplDrawRotateText( SalLayout& rSalLayout )
+{
+ tools::Long nX = rSalLayout.DrawBase().getX();
+ tools::Long nY = rSalLayout.DrawBase().getY();
+
+ tools::Rectangle aBoundRect;
+ rSalLayout.DrawBase() = basegfx::B2DPoint( 0, 0 );
+ rSalLayout.DrawOffset() = Point( 0, 0 );
+ if (!rSalLayout.GetBoundRect(aBoundRect))
+ {
+ // guess vertical text extents if GetBoundRect failed
+ double nRight = rSalLayout.GetTextWidth();
+ tools::Long nTop = mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;
+ tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
+ aBoundRect = tools::Rectangle( 0, -nTop, nRight, nHeight - nTop );
+ }
+
+ // cache virtual device for rotation
+ if (!mpOutDevData->mpRotateDev)
+ mpOutDevData->mpRotateDev = VclPtr<VirtualDevice>::Create(*this);
+ VirtualDevice* pVDev = mpOutDevData->mpRotateDev;
+
+ // size it accordingly
+ if( !pVDev->SetOutputSizePixel( aBoundRect.GetSize() ) )
+ return false;
+
+ const vcl::font::FontSelectPattern& rPattern = mpFontInstance->GetFontSelectPattern();
+ vcl::Font aFont( GetFont() );
+ aFont.SetOrientation( 0_deg10 );
+ aFont.SetFontSize( Size( rPattern.mnWidth, rPattern.mnHeight ) );
+ pVDev->SetFont( aFont );
+ pVDev->SetTextColor( COL_BLACK );
+ pVDev->SetTextFillColor();
+ if (!pVDev->InitFont())
+ return false;
+ pVDev->ImplInitTextColor();
+
+ // draw text into upper left corner
+ rSalLayout.DrawBase().adjustX(-aBoundRect.Left());
+ rSalLayout.DrawBase().adjustY(-aBoundRect.Top());
+ rSalLayout.DrawText( *pVDev->mpGraphics );
+
+ Bitmap aBmp = pVDev->GetBitmap( Point(), aBoundRect.GetSize() );
+ if ( aBmp.IsEmpty() || !aBmp.Rotate( mpFontInstance->mnOwnOrientation, COL_WHITE ) )
+ return false;
+
+ // calculate rotation offset
+ tools::Polygon aPoly( aBoundRect );
+ aPoly.Rotate( Point(), mpFontInstance->mnOwnOrientation );
+ Point aPoint = aPoly.GetBoundRect().TopLeft();
+ aPoint += Point( nX, nY );
+
+ // mask output with text colored bitmap
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ tools::Long nOldOffX = mnOutOffX;
+ tools::Long nOldOffY = mnOutOffY;
+ bool bOldMap = mbMap;
+
+ mnOutOffX = 0;
+ mnOutOffY = 0;
+ mpMetaFile = nullptr;
+ EnableMapMode( false );
+
+ DrawMask( aPoint, aBmp, GetTextColor() );
+
+ EnableMapMode( bOldMap );
+ mnOutOffX = nOldOffX;
+ mnOutOffY = nOldOffY;
+ mpMetaFile = pOldMetaFile;
+
+ return true;
+}
+
+void OutputDevice::ImplDrawTextDirect( SalLayout& rSalLayout,
+ bool bTextLines)
+{
+ if( mpFontInstance->mnOwnOrientation )
+ if( ImplDrawRotateText( rSalLayout ) )
+ return;
+
+ auto nOldX = rSalLayout.DrawBase().getX();
+ if( HasMirroredGraphics() )
+ {
+ tools::Long w = IsVirtual() ? mnOutWidth : mpGraphics->GetGraphicsWidth();
+ auto x = rSalLayout.DrawBase().getX();
+ rSalLayout.DrawBase().setX( w - 1 - x );
+ if( !IsRTLEnabled() )
+ {
+ OutputDevice *pOutDevRef = this;
+ // mirror this window back
+ tools::Long devX = w-pOutDevRef->mnOutWidth-pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX
+ rSalLayout.DrawBase().setX( devX + ( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().getX() - devX) ) ) ;
+ }
+ }
+ else if( IsRTLEnabled() )
+ {
+ OutputDevice *pOutDevRef = this;
+
+ // mirror this window back
+ tools::Long devX = pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX
+ rSalLayout.DrawBase().setX( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().getX() - devX) + devX );
+ }
+
+ rSalLayout.DrawText( *mpGraphics );
+ rSalLayout.DrawBase().setX( nOldX );
+
+ if( bTextLines )
+ ImplDrawTextLines( rSalLayout,
+ maFont.GetStrikeout(), maFont.GetUnderline(), maFont.GetOverline(),
+ maFont.IsWordLineMode(), maFont.IsUnderlineAbove() );
+
+ // emphasis marks
+ if( maFont.GetEmphasisMark() & FontEmphasisMark::Style )
+ ImplDrawEmphasisMarks( rSalLayout );
+}
+
+void OutputDevice::ImplDrawSpecialText( SalLayout& rSalLayout )
+{
+ Color aOldColor = GetTextColor();
+ Color aOldTextLineColor = GetTextLineColor();
+ Color aOldOverlineColor = GetOverlineColor();
+ FontRelief eRelief = maFont.GetRelief();
+
+ basegfx::B2DPoint aOrigPos = rSalLayout.DrawBase();
+ if ( eRelief != FontRelief::NONE )
+ {
+ Color aReliefColor( COL_LIGHTGRAY );
+ Color aTextColor( aOldColor );
+
+ Color aTextLineColor( aOldTextLineColor );
+ Color aOverlineColor( aOldOverlineColor );
+
+ // we don't have an automatic color, so black is always drawn on white
+ if ( aTextColor == COL_BLACK )
+ aTextColor = COL_WHITE;
+ if ( aTextLineColor == COL_BLACK )
+ aTextLineColor = COL_WHITE;
+ if ( aOverlineColor == COL_BLACK )
+ aOverlineColor = COL_WHITE;
+
+ // relief-color is black for white text, in all other cases
+ // we set this to LightGray
+ // coverity[copy_paste_error: FALSE] - this is intentional
+ if ( aTextColor == COL_WHITE )
+ aReliefColor = COL_BLACK;
+ SetTextLineColor( aReliefColor );
+ SetOverlineColor( aReliefColor );
+ SetTextColor( aReliefColor );
+ ImplInitTextColor();
+
+ // calculate offset - for high resolution printers the offset
+ // should be greater so that the effect is visible
+ tools::Long nOff = 1;
+ nOff += mnDPIX/300;
+
+ if ( eRelief == FontRelief::Engraved )
+ nOff = -nOff;
+ rSalLayout.DrawOffset() += Point( nOff, nOff);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawOffset() -= Point( nOff, nOff);
+
+ SetTextLineColor( aTextLineColor );
+ SetOverlineColor( aOverlineColor );
+ SetTextColor( aTextColor );
+ ImplInitTextColor();
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+
+ SetTextLineColor( aOldTextLineColor );
+ SetOverlineColor( aOldOverlineColor );
+
+ if ( aTextColor != aOldColor )
+ {
+ SetTextColor( aOldColor );
+ ImplInitTextColor();
+ }
+ }
+ else
+ {
+ if ( maFont.IsShadow() )
+ {
+ tools::Long nOff = 1 + ((mpFontInstance->mnLineHeight-24)/24);
+ if ( maFont.IsOutline() )
+ nOff++;
+ SetTextLineColor();
+ SetOverlineColor();
+ if ( (GetTextColor() == COL_BLACK)
+ || (GetTextColor().GetLuminance() < 8) )
+ SetTextColor( COL_LIGHTGRAY );
+ else
+ SetTextColor( COL_BLACK );
+ ImplInitTextColor();
+ rSalLayout.DrawBase() += basegfx::B2DPoint( nOff, nOff );
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() -= basegfx::B2DPoint( nOff, nOff );
+ SetTextColor( aOldColor );
+ SetTextLineColor( aOldTextLineColor );
+ SetOverlineColor( aOldOverlineColor );
+ ImplInitTextColor();
+
+ if ( !maFont.IsOutline() )
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ }
+
+ if ( maFont.IsOutline() )
+ {
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(-1,-1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+1,+1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(-1,+0);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(-1,+1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+0,+1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+0,-1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+1,-1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+1,+0);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos;
+
+ SetTextColor( COL_WHITE );
+ SetTextLineColor( COL_WHITE );
+ SetOverlineColor( COL_WHITE );
+ ImplInitTextColor();
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ SetTextColor( aOldColor );
+ SetTextLineColor( aOldTextLineColor );
+ SetOverlineColor( aOldOverlineColor );
+ ImplInitTextColor();
+ }
+ }
+}
+
+void OutputDevice::ImplDrawText( SalLayout& rSalLayout )
+{
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+ if( mbOutputClipped )
+ return;
+ if( mbInitTextColor )
+ ImplInitTextColor();
+
+ rSalLayout.DrawBase() += basegfx::B2DPoint(mnTextOffX, mnTextOffY);
+
+ if( IsTextFillColor() )
+ ImplDrawTextBackground( rSalLayout );
+
+ if( mbTextSpecial )
+ ImplDrawSpecialText( rSalLayout );
+ else
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+}
+
+void OutputDevice::SetTextColor( const Color& rColor )
+{
+
+ Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextColorAction( aColor ) );
+
+ if ( maTextColor != aColor )
+ {
+ maTextColor = aColor;
+ mbInitTextColor = true;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextColor( COL_ALPHA_OPAQUE );
+}
+
+void OutputDevice::SetTextFillColor()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextFillColorAction( Color(), false ) );
+
+ if ( maFont.GetColor() != COL_TRANSPARENT ) {
+ maFont.SetFillColor( COL_TRANSPARENT );
+ }
+ if ( !maFont.IsTransparent() )
+ maFont.SetTransparent( true );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextFillColor();
+}
+
+void OutputDevice::SetTextFillColor( const Color& rColor )
+{
+ Color aColor(vcl::drawmode::GetFillColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextFillColorAction( aColor, true ) );
+
+ if ( maFont.GetFillColor() != aColor )
+ maFont.SetFillColor( aColor );
+ if ( maFont.IsTransparent() != rColor.IsTransparent() )
+ maFont.SetTransparent( rColor.IsTransparent() );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextFillColor( COL_ALPHA_OPAQUE );
+}
+
+Color OutputDevice::GetTextFillColor() const
+{
+ if ( maFont.IsTransparent() )
+ return COL_TRANSPARENT;
+ else
+ return maFont.GetFillColor();
+}
+
+void OutputDevice::SetTextAlign( TextAlign eAlign )
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextAlignAction( eAlign ) );
+
+ if ( maFont.GetAlignment() != eAlign )
+ {
+ maFont.SetAlignment( eAlign );
+ mbNewFont = true;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextAlign( eAlign );
+}
+
+vcl::Region OutputDevice::GetOutputBoundsClipRegion() const
+{
+ return GetClipRegion();
+}
+
+const SalLayoutFlags eDefaultLayout = SalLayoutFlags::NONE;
+
+void OutputDevice::DrawText( const Point& rStartPt, const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
+ const SalLayoutGlyphs* pLayoutCache
+ )
+{
+ assert(!is_double_buffered_window());
+
+ if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ if (mpOutDevData->mpRecordLayout)
+ {
+ pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects;
+ pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText;
+ }
+
+#if OSL_DEBUG_LEVEL > 2
+ SAL_INFO("vcl.gdi", "OutputDevice::DrawText(\"" << rStr << "\")");
+#endif
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextAction( rStartPt, rStr, nIndex, nLen ) );
+ if( pVector )
+ {
+ vcl::Region aClip(GetOutputBoundsClipRegion());
+
+ if (mpOutDevData->mpRecordLayout)
+ {
+ mpOutDevData->mpRecordLayout->m_aLineIndices.push_back( mpOutDevData->mpRecordLayout->m_aDisplayText.getLength() );
+ aClip.Intersect( mpOutDevData->maRecordRect );
+ }
+ if( ! aClip.IsNull() )
+ {
+ std::vector< tools::Rectangle > aTmp;
+ GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, aTmp );
+
+ bool bInserted = false;
+ for( std::vector< tools::Rectangle >::const_iterator it = aTmp.begin(); it != aTmp.end(); ++it, nIndex++ )
+ {
+ bool bAppend = false;
+
+ if( aClip.Overlaps( *it ) )
+ bAppend = true;
+ else if( rStr[ nIndex ] == ' ' && bInserted )
+ {
+ std::vector< tools::Rectangle >::const_iterator next = it;
+ ++next;
+ if( next != aTmp.end() && aClip.Overlaps( *next ) )
+ bAppend = true;
+ }
+
+ if( bAppend )
+ {
+ pVector->push_back( *it );
+ if( pDisplayText )
+ *pDisplayText += OUStringChar(rStr[ nIndex ]);
+ bInserted = true;
+ }
+ }
+ }
+ else
+ {
+ GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, *pVector );
+ if( pDisplayText )
+ *pDisplayText += rStr.subView( nIndex, nLen );
+ }
+ }
+
+ if ( !IsDeviceOutputNecessary() || pVector )
+ return;
+
+ if(mpFontInstance)
+ // do not use cache with modified string
+ if(mpFontInstance->mpConversion)
+ pLayoutCache = nullptr;
+
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, {}, {}, eDefaultLayout, nullptr, pLayoutCache);
+ if(pSalLayout)
+ {
+ ImplDrawText( *pSalLayout );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawText( rStartPt, rStr, nIndex, nLen, pVector, pDisplayText );
+}
+
+tools::Long OutputDevice::GetTextWidth( const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen,
+ vcl::text::TextLayoutCache const*const pLayoutCache,
+ SalLayoutGlyphs const*const pSalLayoutCache) const
+{
+
+ tools::Long nWidth = GetTextArray( rStr, nullptr, nIndex,
+ nLen, false, pLayoutCache, pSalLayoutCache );
+
+ return nWidth;
+}
+
+tools::Long OutputDevice::GetTextHeight() const
+{
+ if (!InitFont())
+ return 0;
+
+ tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
+
+ if ( mbMap )
+ nHeight = ImplDevicePixelToLogicHeight( nHeight );
+
+ return nHeight;
+}
+
+float OutputDevice::approximate_char_width() const
+{
+ //note pango uses "The quick brown fox jumps over the lazy dog." for english
+ //and has a bunch of per-language strings which corresponds somewhat with
+ //makeRepresentativeText in include/svtools/sampletext.hxx
+ return GetTextWidth("aemnnxEM") / 8.0;
+}
+
+float OutputDevice::approximate_digit_width() const
+{
+ return GetTextWidth("0123456789") / 10.0;
+}
+
+void OutputDevice::DrawTextArray( const Point& rStartPt, const OUString& rStr,
+ KernArraySpan pDXAry,
+ std::span<const sal_Bool> pKashidaAry,
+ sal_Int32 nIndex, sal_Int32 nLen, SalLayoutFlags flags,
+ const SalLayoutGlyphs* pSalLayoutCache )
+{
+ assert(!is_double_buffered_window());
+
+ if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen ) );
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+ if( mbInitClipRegion )
+ InitClipRegion();
+ if( mbOutputClipped )
+ return;
+
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry, pKashidaAry, flags, nullptr, pSalLayoutCache);
+ if( pSalLayout )
+ {
+ ImplDrawText( *pSalLayout );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawTextArray( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen, flags );
+}
+
+tools::Long OutputDevice::GetTextArray( const OUString& rStr, KernArray* pKernArray,
+ sal_Int32 nIndex, sal_Int32 nLen, bool bCaret,
+ vcl::text::TextLayoutCache const*const pLayoutCache,
+ SalLayoutGlyphs const*const pSalLayoutCache) const
+{
+ if( nIndex >= rStr.getLength() )
+ return 0; // TODO: this looks like a buggy caller?
+
+ if( nLen < 0 || nIndex + nLen >= rStr.getLength() )
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ std::vector<sal_Int32>* pDXAry = pKernArray ? &pKernArray->get_subunit_array() : nullptr;
+
+ // do layout
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen,
+ Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pSalLayoutCache);
+ if( !pSalLayout )
+ {
+ // The caller expects this to init the elements of pDXAry.
+ // Adapting all the callers to check that GetTextArray succeeded seems
+ // too much work.
+ // Init here to 0 only in the (rare) error case, so that any missing
+ // element init in the happy case will still be found by tools,
+ // and hope that is sufficient.
+ if (pDXAry)
+ {
+ pDXAry->resize(nLen);
+ std::fill(pDXAry->begin(), pDXAry->end(), 0);
+ }
+ return 0;
+ }
+
+ std::unique_ptr<std::vector<double>> xDXPixelArray;
+ if(pDXAry)
+ {
+ xDXPixelArray.reset(new std::vector<double>(nLen));
+ }
+ std::vector<double>* pDXPixelArray = xDXPixelArray.get();
+ double nWidth = pSalLayout->FillDXArray(pDXPixelArray, bCaret ? rStr : OUString());
+
+ // convert virtual char widths to virtual absolute positions
+ if( pDXPixelArray )
+ {
+ for( int i = 1; i < nLen; ++i )
+ {
+ (*pDXPixelArray)[i] += (*pDXPixelArray)[i - 1];
+ }
+ }
+
+ // convert from font units to logical units
+ if (pDXPixelArray)
+ {
+ int nSubPixelFactor = pKernArray->get_factor();
+ if (mbMap)
+ {
+ for (int i = 0; i < nLen; ++i)
+ (*pDXPixelArray)[i] = ImplDevicePixelToLogicWidth((*pDXPixelArray)[i] * nSubPixelFactor);
+ }
+ else if (nSubPixelFactor)
+ {
+ for (int i = 0; i < nLen; ++i)
+ (*pDXPixelArray)[i] *= nSubPixelFactor;
+ }
+ }
+
+ if (pDXAry)
+ {
+ pDXAry->resize(nLen);
+ for (int i = 0; i < nLen; ++i)
+ (*pDXAry)[i] = basegfx::fround((*pDXPixelArray)[i]);
+ }
+
+ if (mbMap)
+ nWidth = ImplDevicePixelToLogicWidth( nWidth );
+
+ return basegfx::fround(nWidth);
+}
+
+void OutputDevice::GetCaretPositions( const OUString& rStr, KernArray& rCaretXArray,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ const SalLayoutGlyphs* pGlyphs ) const
+{
+
+ if( nIndex >= rStr.getLength() )
+ return;
+ if( nIndex+nLen >= rStr.getLength() )
+ nLen = rStr.getLength() - nIndex;
+
+ sal_Int32 nCaretPos = nLen * 2;
+ std::vector<sal_Int32>& rCaretPos = rCaretXArray.get_subunit_array();
+ rCaretPos.resize(nCaretPos);
+
+ // do layout
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, Point(0, 0), 0, {}, {},
+ eDefaultLayout, nullptr, pGlyphs);
+ if( !pSalLayout )
+ {
+ std::fill(rCaretPos.begin(), rCaretPos.end(), -1);
+ return;
+ }
+
+ std::vector<double> aCaretPixelPos;
+ pSalLayout->GetCaretPositions(aCaretPixelPos, rStr);
+
+ // fixup unknown caret positions
+ int i;
+ for (i = 0; i < nCaretPos; ++i)
+ if (aCaretPixelPos[i] >= 0)
+ break;
+ tools::Long nXPos = (i < nCaretPos) ? aCaretPixelPos[i] : -1;
+ for (i = 0; i < nCaretPos; ++i)
+ {
+ if (aCaretPixelPos[i] >= 0)
+ nXPos = aCaretPixelPos[i];
+ else
+ aCaretPixelPos[i] = nXPos;
+ }
+
+ // handle window mirroring
+ if( IsRTLEnabled() )
+ {
+ double nWidth = pSalLayout->GetTextWidth();
+ for (i = 0; i < nCaretPos; ++i)
+ aCaretPixelPos[i] = nWidth - aCaretPixelPos[i] - 1;
+ }
+
+ int nSubPixelFactor = rCaretXArray.get_factor();
+ // convert from font units to logical units
+ if( mbMap )
+ {
+ for (i = 0; i < nCaretPos; ++i)
+ aCaretPixelPos[i] = ImplDevicePixelToLogicWidth(aCaretPixelPos[i] * nSubPixelFactor);
+ }
+ else if (nSubPixelFactor)
+ {
+ for (i = 0; i < nCaretPos; ++i)
+ aCaretPixelPos[i] *= nSubPixelFactor;
+ }
+
+ for (i = 0; i < nCaretPos; ++i)
+ rCaretPos[i] = basegfx::fround(aCaretPixelPos[i]);
+}
+
+void OutputDevice::DrawStretchText( const Point& rStartPt, sal_Int32 nWidth,
+ const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen)
+{
+ assert(!is_double_buffered_window());
+
+ if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaStretchTextAction( rStartPt, nWidth, rStr, nIndex, nLen ) );
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, nWidth);
+ if( pSalLayout )
+ {
+ ImplDrawText( *pSalLayout );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawStretchText( rStartPt, nWidth, rStr, nIndex, nLen );
+}
+
+vcl::text::ImplLayoutArgs OutputDevice::ImplPrepareLayoutArgs( OUString& rStr,
+ const sal_Int32 nMinIndex, const sal_Int32 nLen,
+ double nPixelWidth,
+ SalLayoutFlags nLayoutFlags,
+ vcl::text::TextLayoutCache const*const pLayoutCache) const
+{
+ assert(nMinIndex >= 0);
+ assert(nLen >= 0);
+
+ // get string length for calculating extents
+ sal_Int32 nEndIndex = rStr.getLength();
+ if( nMinIndex + nLen < nEndIndex )
+ nEndIndex = nMinIndex + nLen;
+
+ // don't bother if there is nothing to do
+ if( nEndIndex < nMinIndex )
+ nEndIndex = nMinIndex;
+
+ nLayoutFlags |= GetBiDiLayoutFlags( rStr, nMinIndex, nEndIndex );
+
+ if( !maFont.IsKerning() )
+ nLayoutFlags |= SalLayoutFlags::DisableKerning;
+ if( maFont.GetKerning() & FontKerning::Asian )
+ nLayoutFlags |= SalLayoutFlags::KerningAsian;
+ if( maFont.IsVertical() )
+ nLayoutFlags |= SalLayoutFlags::Vertical;
+ if( maFont.IsFixKerning() ||
+ ( mpFontInstance && mpFontInstance->GetFontSelectPattern().GetPitch() == PITCH_FIXED ) )
+ nLayoutFlags |= SalLayoutFlags::DisableLigatures;
+
+ if( meTextLanguage ) //TODO: (mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::SubstituteDigits)
+ {
+ // disable character localization when no digits used
+ const sal_Unicode* pBase = rStr.getStr();
+ const sal_Unicode* pStr = pBase + nMinIndex;
+ const sal_Unicode* pEnd = pBase + nEndIndex;
+ std::optional<OUStringBuffer> xTmpStr;
+ for( ; pStr < pEnd; ++pStr )
+ {
+ // TODO: are there non-digit localizations?
+ if( (*pStr >= '0') && (*pStr <= '9') )
+ {
+ // translate characters to local preference
+ sal_UCS4 cChar = GetLocalizedChar( *pStr, meTextLanguage );
+ if( cChar != *pStr )
+ {
+ if (!xTmpStr)
+ xTmpStr = OUStringBuffer(rStr);
+ // TODO: are the localized digit surrogates?
+ (*xTmpStr)[pStr - pBase] = cChar;
+ }
+ }
+ }
+ if (xTmpStr)
+ rStr = (*xTmpStr).makeStringAndClear();
+ }
+
+ // right align for RTL text, DRAWPOS_REVERSED, RTL window style
+ bool bRightAlign = bool(mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl);
+ if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginLeft )
+ bRightAlign = false;
+ else if ( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginRight )
+ bRightAlign = true;
+ // SSA: hack for western office, ie text get right aligned
+ // for debugging purposes of mirrored UI
+ bool bRTLWindow = IsRTLEnabled();
+ bRightAlign ^= bRTLWindow;
+ if( bRightAlign )
+ nLayoutFlags |= SalLayoutFlags::RightAlign;
+
+ // set layout options
+ vcl::text::ImplLayoutArgs aLayoutArgs(rStr, nMinIndex, nEndIndex, nLayoutFlags, maFont.GetLanguageTag(), pLayoutCache);
+
+ Degree10 nOrientation = mpFontInstance ? mpFontInstance->mnOrientation : 0_deg10;
+ aLayoutArgs.SetOrientation( nOrientation );
+
+ aLayoutArgs.SetLayoutWidth( nPixelWidth );
+
+ return aLayoutArgs;
+}
+
+SalLayoutFlags OutputDevice::GetBiDiLayoutFlags( std::u16string_view rStr,
+ const sal_Int32 nMinIndex,
+ const sal_Int32 nEndIndex ) const
+{
+ SalLayoutFlags nLayoutFlags = SalLayoutFlags::NONE;
+ if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl )
+ nLayoutFlags |= SalLayoutFlags::BiDiRtl;
+ if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiStrong )
+ nLayoutFlags |= SalLayoutFlags::BiDiStrong;
+ else if( !(mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl) )
+ {
+ // Disable Bidi if no RTL hint and only known LTR codes used.
+ bool bAllLtr = true;
+ for (sal_Int32 i = nMinIndex; i < nEndIndex; i++)
+ {
+ // [0x0000, 0x052F] are Latin, Greek and Cyrillic.
+ // [0x0370, 0x03FF] has a few holes as if Unicode 10.0.0, but
+ // hopefully no RTL character will be encoded there.
+ if (rStr[i] > 0x052F)
+ {
+ bAllLtr = false;
+ break;
+ }
+ }
+ if (bAllLtr)
+ nLayoutFlags |= SalLayoutFlags::BiDiStrong;
+ }
+ return nLayoutFlags;
+}
+
+static OutputDevice::FontMappingUseData* fontMappingUseData = nullptr;
+
+static inline bool IsTrackingFontMappingUse()
+{
+ return fontMappingUseData != nullptr;
+}
+
+static void TrackFontMappingUse( const vcl::Font& originalFont, const SalLayout* salLayout)
+{
+ assert(fontMappingUseData);
+ OUString originalName = originalFont.GetStyleName().isEmpty()
+ ? originalFont.GetFamilyName()
+ : originalFont.GetFamilyName() + "/" + originalFont.GetStyleName();
+ std::vector<OUString> usedFontNames;
+ SalLayoutGlyphs glyphs = salLayout->GetGlyphs(); // includes all font fallbacks
+ int level = 0;
+ while( const SalLayoutGlyphsImpl* impl = glyphs.Impl(level++))
+ {
+ const vcl::font::PhysicalFontFace* face = impl->GetFont()->GetFontFace();
+ OUString name = face->GetStyleName().isEmpty()
+ ? face->GetFamilyName()
+ : face->GetFamilyName() + "/" + face->GetStyleName();
+ usedFontNames.push_back( name );
+ }
+ for( OutputDevice::FontMappingUseItem& item : *fontMappingUseData )
+ {
+ if( item.mOriginalFont == originalName && item.mUsedFonts == usedFontNames )
+ {
+ ++item.mCount;
+ return;
+ }
+ }
+ fontMappingUseData->push_back( { originalName, usedFontNames, 1 } );
+}
+
+void OutputDevice::StartTrackingFontMappingUse()
+{
+ delete fontMappingUseData;
+ fontMappingUseData = new FontMappingUseData;
+}
+
+OutputDevice::FontMappingUseData OutputDevice::FinishTrackingFontMappingUse()
+{
+ if(!fontMappingUseData)
+ return {};
+ FontMappingUseData ret = std::move( *fontMappingUseData );
+ delete fontMappingUseData;
+ fontMappingUseData = nullptr;
+ return ret;
+}
+
+std::unique_ptr<SalLayout> OutputDevice::ImplLayout(const OUString& rOrigStr,
+ sal_Int32 nMinIndex, sal_Int32 nLen,
+ const Point& rLogicalPos, tools::Long nLogicalWidth,
+ KernArraySpan pDXArray,
+ std::span<const sal_Bool> pKashidaArray,
+ SalLayoutFlags flags,
+ vcl::text::TextLayoutCache const* pLayoutCache,
+ const SalLayoutGlyphs* pGlyphs) const
+{
+ if (pGlyphs && !pGlyphs->IsValid())
+ {
+ SAL_WARN("vcl", "Trying to setup invalid cached glyphs - falling back to relayout!");
+ pGlyphs = nullptr;
+ }
+#ifdef DBG_UTIL
+ if (pGlyphs)
+ {
+ for( int level = 0;; ++level )
+ {
+ SalLayoutGlyphsImpl* glyphsImpl = pGlyphs->Impl(level);
+ if(glyphsImpl == nullptr)
+ break;
+ // It is allowed to reuse only glyphs created with SalLayoutFlags::GlyphItemsOnly.
+ // If the glyphs have already been used, the AdjustLayout() call below might have
+ // altered them (MultiSalLayout::ImplAdjustMultiLayout() drops glyphs that need
+ // fallback from the base layout, but then GenericSalLayout::LayoutText()
+ // would not know to call SetNeedFallback()).
+ assert(glyphsImpl->GetFlags() & SalLayoutFlags::GlyphItemsOnly);
+ }
+ }
+#endif
+
+ if (!InitFont())
+ return nullptr;
+
+ // check string index and length
+ if( -1 == nLen || nMinIndex + nLen > rOrigStr.getLength() )
+ {
+ const sal_Int32 nNewLen = rOrigStr.getLength() - nMinIndex;
+ if( nNewLen <= 0 )
+ return nullptr;
+ nLen = nNewLen;
+ }
+
+ OUString aStr = rOrigStr;
+
+ // recode string if needed
+ if( mpFontInstance->mpConversion ) {
+ mpFontInstance->mpConversion->RecodeString( aStr, 0, aStr.getLength() );
+ pLayoutCache = nullptr; // don't use cache with modified string!
+ pGlyphs = nullptr;
+ }
+
+ double nPixelWidth = nLogicalWidth;
+ if( nLogicalWidth && mbMap )
+ {
+ // convert from logical units to physical units
+ nPixelWidth = ImplLogicWidthToDeviceSubPixel(nLogicalWidth);
+ }
+
+ vcl::text::ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nMinIndex, nLen,
+ nPixelWidth, flags, pLayoutCache);
+
+ double nEndGlyphCoord(0);
+ std::unique_ptr<double[]> xDXPixelArray;
+ if( !pDXArray.empty() )
+ {
+ xDXPixelArray.reset(new double[nLen]);
+
+ if (mbMap)
+ {
+ // convert from logical units to font units without rounding,
+ // keeping accuracy for lower levels
+ int nSubPixels = pDXArray.get_factor();
+ for (int i = 0; i < nLen; ++i)
+ xDXPixelArray[i] = ImplLogicWidthToDeviceSubPixel(pDXArray.get_subunit(i)) / nSubPixels;
+ nEndGlyphCoord = xDXPixelArray[nLen - 1];
+ }
+ else
+ {
+ for(int i = 0; i < nLen; ++i)
+ xDXPixelArray[i] = pDXArray.get(i);
+ nEndGlyphCoord = std::lround(xDXPixelArray[nLen - 1]);
+ }
+
+ aLayoutArgs.SetDXArray(xDXPixelArray.get());
+ }
+
+ if (!pKashidaArray.empty())
+ aLayoutArgs.SetKashidaArray(pKashidaArray.data());
+
+ // get matching layout object for base font
+ std::unique_ptr<SalLayout> pSalLayout = mpGraphics->GetTextLayout(0);
+
+ if (pSalLayout)
+ pSalLayout->SetSubpixelPositioning(mbMap);
+
+ // layout text
+ if( pSalLayout && !pSalLayout->LayoutText( aLayoutArgs, pGlyphs ? pGlyphs->Impl(0) : nullptr ) )
+ {
+ pSalLayout.reset();
+ }
+
+ if( !pSalLayout )
+ return nullptr;
+
+ // do glyph fallback if needed
+ // #105768# avoid fallback for very small font sizes
+ if (aLayoutArgs.HasFallbackRun() && mpFontInstance->GetFontSelectPattern().mnHeight >= 3)
+ pSalLayout = ImplGlyphFallbackLayout(std::move(pSalLayout), aLayoutArgs, pGlyphs);
+
+ if (flags & SalLayoutFlags::GlyphItemsOnly)
+ // Return glyph items only after fallback handling. Otherwise they may
+ // contain invalid glyph IDs.
+ return pSalLayout;
+
+ // position, justify, etc. the layout
+ pSalLayout->AdjustLayout( aLayoutArgs );
+
+ // default to on for pdf export, which uses SubPixelToLogic to convert back to
+ // the logical coord space, of if we are scaling/mapping
+ if (mbMap || meOutDevType == OUTDEV_PDF)
+ pSalLayout->DrawBase() = ImplLogicToDeviceSubPixel(rLogicalPos);
+ else
+ {
+ Point aDevicePos = ImplLogicToDevicePixel(rLogicalPos);
+ pSalLayout->DrawBase() = basegfx::B2DPoint(aDevicePos.X(), aDevicePos.Y());
+ }
+
+ // adjust to right alignment if necessary
+ if( aLayoutArgs.mnFlags & SalLayoutFlags::RightAlign )
+ {
+ double nRTLOffset;
+ if (!pDXArray.empty())
+ nRTLOffset = nEndGlyphCoord;
+ else if( nPixelWidth )
+ nRTLOffset = nPixelWidth;
+ else
+ nRTLOffset = pSalLayout->GetTextWidth();
+ pSalLayout->DrawOffset().setX( 1 - nRTLOffset );
+ }
+
+ if(IsTrackingFontMappingUse())
+ TrackFontMappingUse(GetFont(), pSalLayout.get());
+
+ return pSalLayout;
+}
+
+std::shared_ptr<const vcl::text::TextLayoutCache> OutputDevice::CreateTextLayoutCache(
+ OUString const& rString)
+{
+ return vcl::text::TextLayoutCache::Create(rString);
+}
+
+bool OutputDevice::GetTextIsRTL( const OUString& rString, sal_Int32 nIndex, sal_Int32 nLen ) const
+{
+ OUString aStr( rString );
+ vcl::text::ImplLayoutArgs aArgs = ImplPrepareLayoutArgs(aStr, nIndex, nLen, 0);
+ bool bRTL = false;
+ int nCharPos = -1;
+ if (!aArgs.GetNextPos(&nCharPos, &bRTL))
+ return false;
+ return (nCharPos != nIndex);
+}
+
+sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, tools::Long nTextWidth,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ tools::Long nCharExtra,
+ vcl::text::TextLayoutCache const*const pLayoutCache,
+ const SalLayoutGlyphs* pGlyphs) const
+{
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
+ Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pGlyphs);
+ sal_Int32 nRetVal = -1;
+ if( pSalLayout )
+ {
+ // convert logical widths into layout units
+ // NOTE: be very careful to avoid rounding errors for nCharExtra case
+ // problem with rounding errors especially for small nCharExtras
+ // TODO: remove when layout units have subpixel granularity
+ tools::Long nSubPixelFactor = 1;
+ if (!mbMap)
+ nSubPixelFactor = 64;
+ double nTextPixelWidth = ImplLogicWidthToDeviceSubPixel(nTextWidth * nSubPixelFactor);
+ double nExtraPixelWidth = 0;
+ if( nCharExtra != 0 )
+ nExtraPixelWidth = ImplLogicWidthToDeviceSubPixel(nCharExtra * nSubPixelFactor);
+ nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor );
+ }
+
+ return nRetVal;
+}
+
+sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, tools::Long nTextWidth,
+ sal_Unicode nHyphenChar, sal_Int32& rHyphenPos,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ tools::Long nCharExtra,
+ vcl::text::TextLayoutCache const*const pLayoutCache,
+ const SalLayoutGlyphs* pGlyphs) const
+{
+ rHyphenPos = -1;
+
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
+ Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pGlyphs);
+ sal_Int32 nRetVal = -1;
+ if( pSalLayout )
+ {
+ // convert logical widths into layout units
+ // NOTE: be very careful to avoid rounding errors for nCharExtra case
+ // problem with rounding errors especially for small nCharExtras
+ // TODO: remove when layout units have subpixel granularity
+ tools::Long nSubPixelFactor = 1;
+ if (!mbMap)
+ nSubPixelFactor = 64;
+
+ double nTextPixelWidth = ImplLogicWidthToDeviceSubPixel(nTextWidth * nSubPixelFactor);
+ double nExtraPixelWidth = 0;
+ if( nCharExtra != 0 )
+ nExtraPixelWidth = ImplLogicWidthToDeviceSubPixel(nCharExtra * nSubPixelFactor);
+
+ // calculate un-hyphenated break position
+ nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor );
+
+ // calculate hyphenated break position
+ OUString aHyphenStr(nHyphenChar);
+ std::unique_ptr<SalLayout> pHyphenLayout = ImplLayout( aHyphenStr, 0, 1 );
+ if( pHyphenLayout )
+ {
+ // calculate subpixel width of hyphenation character
+ double nHyphenPixelWidth = pHyphenLayout->GetTextWidth() * nSubPixelFactor;
+
+ // calculate hyphenated break position
+ nTextPixelWidth -= nHyphenPixelWidth;
+ if( nExtraPixelWidth > 0 )
+ nTextPixelWidth -= nExtraPixelWidth;
+
+ rHyphenPos = pSalLayout->GetTextBreak(nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor);
+
+ if( rHyphenPos > nRetVal )
+ rHyphenPos = nRetVal;
+ }
+ }
+
+ return nRetVal;
+}
+
+void OutputDevice::ImplDrawText( OutputDevice& rTargetDevice, const tools::Rectangle& rRect,
+ const OUString& rOrigStr, DrawTextFlags nStyle,
+ std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
+ vcl::TextLayoutCommon& _rLayout )
+{
+
+ Color aOldTextColor;
+ Color aOldTextFillColor;
+ bool bRestoreFillColor = false;
+ if ( (nStyle & DrawTextFlags::Disable) && ! pVector )
+ {
+ bool bHighContrastBlack = false;
+ bool bHighContrastWhite = false;
+ const StyleSettings& rStyleSettings( rTargetDevice.GetSettings().GetStyleSettings() );
+ if( rStyleSettings.GetHighContrastMode() )
+ {
+ Color aCol;
+ if( rTargetDevice.IsBackground() )
+ aCol = rTargetDevice.GetBackground().GetColor();
+ else
+ // best guess is the face color here
+ // but it may be totally wrong. the background color
+ // was typically already reset
+ aCol = rStyleSettings.GetFaceColor();
+
+ bHighContrastBlack = aCol.IsDark();
+ bHighContrastWhite = aCol.IsBright();
+ }
+
+ aOldTextColor = rTargetDevice.GetTextColor();
+ if ( rTargetDevice.IsTextFillColor() )
+ {
+ bRestoreFillColor = true;
+ aOldTextFillColor = rTargetDevice.GetTextFillColor();
+ }
+ if( bHighContrastBlack )
+ rTargetDevice.SetTextColor( COL_GREEN );
+ else if( bHighContrastWhite )
+ rTargetDevice.SetTextColor( COL_LIGHTGREEN );
+ else
+ {
+ // draw disabled text always without shadow
+ // as it fits better with native look
+ rTargetDevice.SetTextColor( rTargetDevice.GetSettings().GetStyleSettings().GetDisableColor() );
+ }
+ }
+
+ tools::Long nWidth = rRect.GetWidth();
+ tools::Long nHeight = rRect.GetHeight();
+
+ if (nWidth <= 0 || nHeight <= 0)
+ {
+ if (nStyle & DrawTextFlags::Clip)
+ return;
+ static bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ SAL_WARN_IF(bFuzzing, "vcl", "skipping negative rectangle of: " << nWidth << " x " << nHeight);
+ if (bFuzzing)
+ return;
+ }
+
+ Point aPos = rRect.TopLeft();
+
+ tools::Long nTextHeight = rTargetDevice.GetTextHeight();
+ TextAlign eAlign = rTargetDevice.GetTextAlign();
+ sal_Int32 nMnemonicPos = -1;
+
+ OUString aStr = rOrigStr;
+ if ( nStyle & DrawTextFlags::Mnemonic )
+ aStr = removeMnemonicFromString( aStr, nMnemonicPos );
+
+ const bool bDrawMnemonics = !(rTargetDevice.GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector;
+
+ // We treat multiline text differently
+ if ( nStyle & DrawTextFlags::MultiLine )
+ {
+
+ ImplMultiTextLineInfo aMultiLineInfo;
+ sal_Int32 i;
+ sal_Int32 nFormatLines;
+
+ if ( nTextHeight )
+ {
+ tools::Long nMaxTextWidth = _rLayout.GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle);
+ sal_Int32 nLines = static_cast<sal_Int32>(nHeight/nTextHeight);
+ OUString aLastLine;
+ nFormatLines = aMultiLineInfo.Count();
+ if (nLines <= 0)
+ nLines = 1;
+ if ( nFormatLines > nLines )
+ {
+ if ( nStyle & DrawTextFlags::EndEllipsis )
+ {
+ // Create last line and shorten it
+ nFormatLines = nLines-1;
+
+ ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines );
+ aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF);
+ // Replace all LineFeeds with Spaces
+ OUStringBuffer aLastLineBuffer(aLastLine);
+ sal_Int32 nLastLineLen = aLastLineBuffer.getLength();
+ for ( i = 0; i < nLastLineLen; i++ )
+ {
+ if ( aLastLineBuffer[ i ] == '\n' )
+ aLastLineBuffer[ i ] = ' ';
+ }
+ aLastLine = aLastLineBuffer.makeStringAndClear();
+ aLastLine = _rLayout.GetEllipsisString(aLastLine, nWidth, nStyle);
+ nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
+ nStyle |= DrawTextFlags::Top;
+ }
+ }
+ else
+ {
+ if ( nMaxTextWidth <= nWidth )
+ nStyle &= ~DrawTextFlags::Clip;
+ }
+
+ // Do we need to clip the height?
+ if ( nFormatLines*nTextHeight > nHeight )
+ nStyle |= DrawTextFlags::Clip;
+
+ // Set clipping
+ if ( nStyle & DrawTextFlags::Clip )
+ {
+ rTargetDevice.Push( vcl::PushFlags::CLIPREGION );
+ rTargetDevice.IntersectClipRegion( rRect );
+ }
+
+ // Vertical alignment
+ if ( nStyle & DrawTextFlags::Bottom )
+ aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) );
+ else if ( nStyle & DrawTextFlags::VCenter )
+ aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 );
+
+ // Font alignment
+ if ( eAlign == ALIGN_BOTTOM )
+ aPos.AdjustY(nTextHeight );
+ else if ( eAlign == ALIGN_BASELINE )
+ aPos.AdjustY(rTargetDevice.GetFontMetric().GetAscent() );
+
+ // Output all lines except for the last one
+ for ( i = 0; i < nFormatLines; i++ )
+ {
+ ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
+ if ( nStyle & DrawTextFlags::Right )
+ aPos.AdjustX(nWidth-rLineInfo.GetWidth() );
+ else if ( nStyle & DrawTextFlags::Center )
+ aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 );
+ sal_Int32 nIndex = rLineInfo.GetIndex();
+ sal_Int32 nLineLen = rLineInfo.GetLen();
+ _rLayout.DrawText( aPos, aStr, nIndex, nLineLen, pVector, pDisplayText );
+ if ( bDrawMnemonics )
+ {
+ if ( (nMnemonicPos >= nIndex) && (nMnemonicPos < nIndex+nLineLen) )
+ {
+ tools::Long nMnemonicX;
+ tools::Long nMnemonicY;
+
+ KernArray aDXArray;
+ _rLayout.GetTextArray(aStr, &aDXArray, nIndex, nLineLen, true);
+ sal_Int32 nPos = nMnemonicPos - nIndex;
+ sal_Int32 lc_x1 = nPos ? aDXArray[nPos - 1] : 0;
+ sal_Int32 lc_x2 = aDXArray[nPos];
+ double nMnemonicWidth = rTargetDevice.ImplLogicWidthToDeviceSubPixel(std::abs(lc_x1 - lc_x2));
+
+ Point aTempPos = rTargetDevice.LogicToPixel( aPos );
+ nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min( lc_x1, lc_x2 ) );
+ nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() );
+ rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
+ }
+ }
+ aPos.AdjustY(nTextHeight );
+ aPos.setX( rRect.Left() );
+ }
+
+ // If there still is a last line, we output it left-aligned as the line would be clipped
+ if ( !aLastLine.isEmpty() )
+ _rLayout.DrawText( aPos, aLastLine, 0, aLastLine.getLength(), pVector, pDisplayText );
+
+ // Reset clipping
+ if ( nStyle & DrawTextFlags::Clip )
+ rTargetDevice.Pop();
+ }
+ }
+ else
+ {
+ tools::Long nTextWidth = _rLayout.GetTextWidth( aStr, 0, -1 );
+
+ // Clip text if needed
+ if ( nTextWidth > nWidth )
+ {
+ if ( nStyle & TEXT_DRAW_ELLIPSIS )
+ {
+ aStr = _rLayout.GetEllipsisString(aStr, nWidth, nStyle);
+ nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
+ nStyle |= DrawTextFlags::Left;
+ nTextWidth = _rLayout.GetTextWidth( aStr, 0, aStr.getLength() );
+ }
+ }
+ else
+ {
+ if ( nTextHeight <= nHeight )
+ nStyle &= ~DrawTextFlags::Clip;
+ }
+
+ // horizontal text alignment
+ if ( nStyle & DrawTextFlags::Right )
+ aPos.AdjustX(nWidth-nTextWidth );
+ else if ( nStyle & DrawTextFlags::Center )
+ aPos.AdjustX((nWidth-nTextWidth)/2 );
+
+ // vertical font alignment
+ if ( eAlign == ALIGN_BOTTOM )
+ aPos.AdjustY(nTextHeight );
+ else if ( eAlign == ALIGN_BASELINE )
+ aPos.AdjustY(rTargetDevice.GetFontMetric().GetAscent() );
+
+ if ( nStyle & DrawTextFlags::Bottom )
+ aPos.AdjustY(nHeight-nTextHeight );
+ else if ( nStyle & DrawTextFlags::VCenter )
+ aPos.AdjustY((nHeight-nTextHeight)/2 );
+
+ tools::Long nMnemonicX = 0;
+ tools::Long nMnemonicY = 0;
+ double nMnemonicWidth = 0;
+ if (nMnemonicPos != -1 && nMnemonicPos < aStr.getLength())
+ {
+ KernArray aDXArray;
+ _rLayout.GetTextArray(aStr, &aDXArray, 0, aStr.getLength(), true);
+ tools::Long lc_x1 = nMnemonicPos? aDXArray[nMnemonicPos - 1] : 0;
+ tools::Long lc_x2 = aDXArray[nMnemonicPos];
+ nMnemonicWidth = rTargetDevice.ImplLogicWidthToDeviceSubPixel(std::abs(lc_x1 - lc_x2));
+
+ Point aTempPos = rTargetDevice.LogicToPixel( aPos );
+ nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min(lc_x1, lc_x2) );
+ nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() );
+ }
+
+ if ( nStyle & DrawTextFlags::Clip )
+ {
+ rTargetDevice.Push( vcl::PushFlags::CLIPREGION );
+ rTargetDevice.IntersectClipRegion( rRect );
+ _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText );
+ if ( bDrawMnemonics && nMnemonicPos != -1 )
+ rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
+ rTargetDevice.Pop();
+ }
+ else
+ {
+ _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText );
+ if ( bDrawMnemonics && nMnemonicPos != -1 )
+ rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
+ }
+ }
+
+ if ( nStyle & DrawTextFlags::Disable && !pVector )
+ {
+ rTargetDevice.SetTextColor( aOldTextColor );
+ if ( bRestoreFillColor )
+ rTargetDevice.SetTextFillColor( aOldTextFillColor );
+ }
+}
+
+void OutputDevice::AddTextRectActions( const tools::Rectangle& rRect,
+ const OUString& rOrigStr,
+ DrawTextFlags nStyle,
+ GDIMetaFile& rMtf )
+{
+
+ if ( rOrigStr.isEmpty() || rRect.IsEmpty() )
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ // temporarily swap in passed mtf for action generation, and
+ // disable output generation.
+ const bool bOutputEnabled( IsOutputEnabled() );
+ GDIMetaFile* pMtf = mpMetaFile;
+
+ mpMetaFile = &rMtf;
+ EnableOutput( false );
+
+ // #i47157# Factored out to ImplDrawTextRect(), to be shared
+ // between us and DrawText()
+ vcl::DefaultTextLayout aLayout( *this );
+ ImplDrawText( *this, rRect, rOrigStr, nStyle, nullptr, nullptr, aLayout );
+
+ // and restore again
+ EnableOutput( bOutputEnabled );
+ mpMetaFile = pMtf;
+}
+
+void OutputDevice::DrawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle,
+ std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
+ vcl::TextLayoutCommon* _pTextLayout )
+{
+ assert(!is_double_buffered_window());
+
+ if (mpOutDevData->mpRecordLayout)
+ {
+ pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects;
+ pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText;
+ }
+
+ bool bDecomposeTextRectAction = ( _pTextLayout != nullptr ) && _pTextLayout->DecomposeTextRectAction();
+ if ( mpMetaFile && !bDecomposeTextRectAction )
+ mpMetaFile->AddAction( new MetaTextRectAction( rRect, rOrigStr, nStyle ) );
+
+ if ( ( !IsDeviceOutputNecessary() && !pVector && !bDecomposeTextRectAction ) || rOrigStr.isEmpty() || rRect.IsEmpty() )
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+ if( mbInitClipRegion )
+ InitClipRegion();
+ if (mbOutputClipped && !bDecomposeTextRectAction && !pDisplayText)
+ return;
+
+ // temporarily disable mtf action generation (ImplDrawText _does_
+ // create MetaActionType::TEXTs otherwise)
+ GDIMetaFile* pMtf = mpMetaFile;
+ if ( !bDecomposeTextRectAction )
+ mpMetaFile = nullptr;
+
+ // #i47157# Factored out to ImplDrawText(), to be used also
+ // from AddTextRectActions()
+ vcl::DefaultTextLayout aDefaultLayout( *this );
+ ImplDrawText( *this, rRect, rOrigStr, nStyle, pVector, pDisplayText, _pTextLayout ? *_pTextLayout : aDefaultLayout );
+
+ // and enable again
+ mpMetaFile = pMtf;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawText( rRect, rOrigStr, nStyle, pVector, pDisplayText );
+}
+
+tools::Rectangle OutputDevice::GetTextRect( const tools::Rectangle& rRect,
+ const OUString& rStr, DrawTextFlags nStyle,
+ TextRectInfo* pInfo,
+ const vcl::TextLayoutCommon* _pTextLayout ) const
+{
+
+ tools::Rectangle aRect = rRect;
+ sal_Int32 nLines;
+ tools::Long nWidth = rRect.GetWidth();
+ tools::Long nMaxWidth;
+ tools::Long nTextHeight = GetTextHeight();
+
+ OUString aStr = rStr;
+ if ( nStyle & DrawTextFlags::Mnemonic )
+ aStr = removeMnemonicFromString( aStr );
+
+ if ( nStyle & DrawTextFlags::MultiLine )
+ {
+ ImplMultiTextLineInfo aMultiLineInfo;
+ sal_Int32 nFormatLines;
+ sal_Int32 i;
+
+ nMaxWidth = 0;
+ vcl::DefaultTextLayout aDefaultLayout( *const_cast< OutputDevice* >( this ) );
+
+ if (_pTextLayout)
+ const_cast<vcl::TextLayoutCommon*>(_pTextLayout)->GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle);
+ else
+ aDefaultLayout.GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle);
+
+ nFormatLines = aMultiLineInfo.Count();
+ if ( !nTextHeight )
+ nTextHeight = 1;
+ nLines = static_cast<sal_uInt16>(aRect.GetHeight()/nTextHeight);
+ if ( pInfo )
+ pInfo->mnLineCount = nFormatLines;
+ if ( !nLines )
+ nLines = 1;
+ if ( nFormatLines <= nLines )
+ nLines = nFormatLines;
+ else
+ {
+ if ( !(nStyle & DrawTextFlags::EndEllipsis) )
+ nLines = nFormatLines;
+ else
+ {
+ if ( pInfo )
+ pInfo->mbEllipsis = true;
+ nMaxWidth = nWidth;
+ }
+ }
+ if ( pInfo )
+ {
+ bool bMaxWidth = nMaxWidth == 0;
+ pInfo->mnMaxWidth = 0;
+ for ( i = 0; i < nLines; i++ )
+ {
+ ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
+ if ( bMaxWidth && (rLineInfo.GetWidth() > nMaxWidth) )
+ nMaxWidth = rLineInfo.GetWidth();
+ if ( rLineInfo.GetWidth() > pInfo->mnMaxWidth )
+ pInfo->mnMaxWidth = rLineInfo.GetWidth();
+ }
+ }
+ else if ( !nMaxWidth )
+ {
+ for ( i = 0; i < nLines; i++ )
+ {
+ ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
+ if ( rLineInfo.GetWidth() > nMaxWidth )
+ nMaxWidth = rLineInfo.GetWidth();
+ }
+ }
+ }
+ else
+ {
+ nLines = 1;
+ nMaxWidth = _pTextLayout ? _pTextLayout->GetTextWidth( aStr, 0, aStr.getLength() ) : GetTextWidth( aStr );
+
+ if ( pInfo )
+ {
+ pInfo->mnLineCount = 1;
+ pInfo->mnMaxWidth = nMaxWidth;
+ }
+
+ if ( (nMaxWidth > nWidth) && (nStyle & TEXT_DRAW_ELLIPSIS) )
+ {
+ if ( pInfo )
+ pInfo->mbEllipsis = true;
+ nMaxWidth = nWidth;
+ }
+ }
+
+ if ( nStyle & DrawTextFlags::Right )
+ aRect.SetLeft( aRect.Right()-nMaxWidth+1 );
+ else if ( nStyle & DrawTextFlags::Center )
+ {
+ aRect.AdjustLeft((nWidth-nMaxWidth)/2 );
+ aRect.SetRight( aRect.Left()+nMaxWidth-1 );
+ }
+ else
+ aRect.SetRight( aRect.Left()+nMaxWidth-1 );
+
+ if ( nStyle & DrawTextFlags::Bottom )
+ aRect.SetTop( aRect.Bottom()-(nTextHeight*nLines)+1 );
+ else if ( nStyle & DrawTextFlags::VCenter )
+ {
+ aRect.AdjustTop((aRect.GetHeight()-(nTextHeight*nLines))/2 );
+ aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 );
+ }
+ else
+ aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 );
+
+ // #99188# get rid of rounding problems when using this rect later
+ if (nStyle & DrawTextFlags::Right)
+ aRect.AdjustLeft( -1 );
+ else
+ aRect.AdjustRight( 1 );
+
+ if (maFont.GetOrientation() != 0_deg10)
+ {
+ tools::Polygon aRotatedPolygon(aRect);
+ aRotatedPolygon.Rotate(Point(aRect.GetWidth() / 2, aRect.GetHeight() / 2), maFont.GetOrientation());
+ return aRotatedPolygon.GetBoundRect();
+ }
+
+ return aRect;
+}
+
+void OutputDevice::DrawCtrlText( const Point& rPos, const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ DrawTextFlags nStyle, std::vector< tools::Rectangle >* pVector, OUString* pDisplayText,
+ const SalLayoutGlyphs* pGlyphs )
+{
+ assert(!is_double_buffered_window());
+
+ if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ if ( !IsDeviceOutputNecessary() || (nIndex >= rStr.getLength()) )
+ return;
+
+ // better get graphics here because ImplDrawMnemonicLine() will not
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+ if( mbInitClipRegion )
+ InitClipRegion();
+ if ( mbOutputClipped )
+ return;
+
+ if( nIndex >= rStr.getLength() )
+ return;
+
+ if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+ OUString aStr = rStr;
+ sal_Int32 nMnemonicPos = -1;
+
+ tools::Long nMnemonicX = 0;
+ tools::Long nMnemonicY = 0;
+ tools::Long nMnemonicWidth = 0;
+ if ( (nStyle & DrawTextFlags::Mnemonic) && nLen > 1 )
+ {
+ aStr = removeMnemonicFromString( aStr, nMnemonicPos );
+ if ( nMnemonicPos != -1 )
+ {
+ if( nMnemonicPos < nIndex )
+ {
+ --nIndex;
+ }
+ else
+ {
+ if( nMnemonicPos < (nIndex+nLen) )
+ --nLen;
+ SAL_WARN_IF( nMnemonicPos >= (nIndex+nLen), "vcl", "Mnemonic underline marker after last character" );
+ }
+ bool bInvalidPos = false;
+
+ if( nMnemonicPos >= nLen )
+ {
+ // may occur in BiDi-Strings: the '~' is sometimes found behind the last char
+ // due to some strange BiDi text editors
+ // -> place the underline behind the string to indicate a failure
+ bInvalidPos = true;
+ nMnemonicPos = nLen-1;
+ }
+
+ KernArray aDXArray;
+ GetTextArray(aStr, &aDXArray, nIndex, nLen, true, nullptr, pGlyphs);
+ sal_Int32 nPos = nMnemonicPos - nIndex;
+ sal_Int32 lc_x1 = nPos ? aDXArray[nPos - 1] : 0;
+ sal_Int32 lc_x2 = aDXArray[nPos];
+ nMnemonicWidth = std::abs(lc_x1 - lc_x2);
+
+ Point aTempPos( std::min(lc_x1,lc_x2), GetFontMetric().GetAscent() );
+ if( bInvalidPos ) // #106952#, place behind the (last) character
+ aTempPos = Point( std::max(lc_x1,lc_x2), GetFontMetric().GetAscent() );
+
+ aTempPos += rPos;
+ aTempPos = LogicToPixel( aTempPos );
+ nMnemonicX = mnOutOffX + aTempPos.X();
+ nMnemonicY = mnOutOffY + aTempPos.Y();
+ }
+ }
+
+ if ( nStyle & DrawTextFlags::Disable && ! pVector )
+ {
+ Color aOldTextColor;
+ Color aOldTextFillColor;
+ bool bRestoreFillColor;
+ bool bHighContrastBlack = false;
+ bool bHighContrastWhite = false;
+ const StyleSettings& rStyleSettings( GetSettings().GetStyleSettings() );
+ if( rStyleSettings.GetHighContrastMode() )
+ {
+ if( IsBackground() )
+ {
+ Wallpaper aWall = GetBackground();
+ Color aCol = aWall.GetColor();
+ bHighContrastBlack = aCol.IsDark();
+ bHighContrastWhite = aCol.IsBright();
+ }
+ }
+
+ aOldTextColor = GetTextColor();
+ if ( IsTextFillColor() )
+ {
+ bRestoreFillColor = true;
+ aOldTextFillColor = GetTextFillColor();
+ }
+ else
+ bRestoreFillColor = false;
+
+ if( bHighContrastBlack )
+ SetTextColor( COL_GREEN );
+ else if( bHighContrastWhite )
+ SetTextColor( COL_LIGHTGREEN );
+ else
+ SetTextColor( GetSettings().GetStyleSettings().GetDisableColor() );
+
+ DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText );
+ if (!(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics))
+ {
+ if ( nMnemonicPos != -1 )
+ ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
+ }
+ SetTextColor( aOldTextColor );
+ if ( bRestoreFillColor )
+ SetTextFillColor( aOldTextFillColor );
+ }
+ else
+ {
+ DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText, pGlyphs );
+ if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector )
+ {
+ if ( nMnemonicPos != -1 )
+ ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawCtrlText( rPos, rStr, nIndex, nLen, nStyle, pVector, pDisplayText );
+}
+
+tools::Long OutputDevice::GetCtrlTextWidth( const OUString& rStr, const SalLayoutGlyphs* pGlyphs ) const
+{
+ sal_Int32 nLen = rStr.getLength();
+ sal_Int32 nIndex = 0;
+
+ sal_Int32 nMnemonicPos;
+ OUString aStr = removeMnemonicFromString( rStr, nMnemonicPos );
+ if ( nMnemonicPos != -1 )
+ {
+ if ( nMnemonicPos < nIndex )
+ nIndex--;
+ else if (static_cast<sal_uLong>(nMnemonicPos) < static_cast<sal_uLong>(nIndex+nLen))
+ nLen--;
+ }
+ return GetTextWidth( aStr, nIndex, nLen, nullptr, pGlyphs );
+}
+
+bool OutputDevice::GetTextBoundRect( tools::Rectangle& rRect,
+ const OUString& rStr, sal_Int32 nBase,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ sal_uLong nLayoutWidth, KernArraySpan pDXAry,
+ std::span<const sal_Bool> pKashidaAry,
+ const SalLayoutGlyphs* pGlyphs ) const
+{
+ bool bRet = false;
+ rRect.SetEmpty();
+
+ std::unique_ptr<SalLayout> pSalLayout;
+ const Point aPoint;
+ // calculate offset when nBase!=nIndex
+ double nXOffset = 0;
+ if( nBase != nIndex )
+ {
+ sal_Int32 nStart = std::min( nBase, nIndex );
+ sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart;
+ pSalLayout = ImplLayout( rStr, nStart, nOfsLen, aPoint, nLayoutWidth, pDXAry, pKashidaAry );
+ if( pSalLayout )
+ {
+ nXOffset = pSalLayout->GetTextWidth();
+ // TODO: fix offset calculation for Bidi case
+ if( nBase < nIndex)
+ nXOffset = -nXOffset;
+ }
+ }
+
+ pSalLayout = ImplLayout(rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry, pKashidaAry, eDefaultLayout,
+ nullptr, pGlyphs);
+ if( pSalLayout )
+ {
+ tools::Rectangle aPixelRect;
+ bRet = pSalLayout->GetBoundRect(aPixelRect);
+
+ if( bRet )
+ {
+ Point aRotatedOfs( mnTextOffX, mnTextOffY );
+ basegfx::B2DPoint aPos = pSalLayout->GetDrawPosition(basegfx::B2DPoint(nXOffset, 0));
+ aRotatedOfs -= Point(aPos.getX(), aPos.getY());
+ aPixelRect += aRotatedOfs;
+ rRect = PixelToLogic( aPixelRect );
+ if( mbMap )
+ rRect += Point( maMapRes.mnMapOfsX, maMapRes.mnMapOfsY );
+ }
+ }
+
+ return bRet;
+}
+
+bool OutputDevice::GetTextOutlines( basegfx::B2DPolyPolygonVector& rVector,
+ const OUString& rStr, sal_Int32 nBase,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ sal_uLong nLayoutWidth,
+ KernArraySpan pDXArray,
+ std::span<const sal_Bool> pKashidaArray ) const
+{
+ if (!InitFont())
+ return false;
+
+ bool bRet = false;
+ rVector.clear();
+ if( nLen < 0 )
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+ rVector.reserve( nLen );
+
+ // we want to get the Rectangle in logical units, so to
+ // avoid rounding errors we just size the font in logical units
+ bool bOldMap = mbMap;
+ if( bOldMap )
+ {
+ const_cast<OutputDevice&>(*this).mbMap = false;
+ const_cast<OutputDevice&>(*this).mbNewFont = true;
+ }
+
+ std::unique_ptr<SalLayout> pSalLayout;
+
+ // calculate offset when nBase!=nIndex
+ double nXOffset = 0;
+ if( nBase != nIndex )
+ {
+ sal_Int32 nStart = std::min( nBase, nIndex );
+ sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart;
+ pSalLayout = ImplLayout( rStr, nStart, nOfsLen, Point(0,0), nLayoutWidth, pDXArray, pKashidaArray);
+ if( pSalLayout )
+ {
+ nXOffset = pSalLayout->GetTextWidth();
+ pSalLayout.reset();
+ // TODO: fix offset calculation for Bidi case
+ if( nBase > nIndex)
+ nXOffset = -nXOffset;
+ }
+ }
+
+ pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), nLayoutWidth, pDXArray, pKashidaArray );
+ if( pSalLayout )
+ {
+ bRet = pSalLayout->GetOutline(rVector);
+ if( bRet )
+ {
+ // transform polygon to pixel units
+ basegfx::B2DHomMatrix aMatrix;
+
+ if (nXOffset || mnTextOffX || mnTextOffY)
+ {
+ basegfx::B2DPoint aRotatedOfs(mnTextOffX, mnTextOffY);
+ aRotatedOfs -= pSalLayout->GetDrawPosition(basegfx::B2DPoint(nXOffset, 0));
+ aMatrix.translate( aRotatedOfs.getX(), aRotatedOfs.getY() );
+ }
+
+ if( !aMatrix.isIdentity() )
+ {
+ for (auto & elem : rVector)
+ elem.transform( aMatrix );
+ }
+ }
+
+ pSalLayout.reset();
+ }
+
+ if( bOldMap )
+ {
+ // restore original font size and map mode
+ const_cast<OutputDevice&>(*this).mbMap = bOldMap;
+ const_cast<OutputDevice&>(*this).mbNewFont = true;
+ }
+
+ return bRet;
+}
+
+bool OutputDevice::GetTextOutlines( PolyPolyVector& rResultVector,
+ const OUString& rStr, sal_Int32 nBase,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ sal_uLong nLayoutWidth, KernArraySpan pDXArray,
+ std::span<const sal_Bool> pKashidaArray ) const
+{
+ rResultVector.clear();
+
+ // get the basegfx polypolygon vector
+ basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
+ if( !GetTextOutlines( aB2DPolyPolyVector, rStr, nBase, nIndex, nLen,
+ nLayoutWidth, pDXArray, pKashidaArray ) )
+ return false;
+
+ // convert to a tool polypolygon vector
+ rResultVector.reserve( aB2DPolyPolyVector.size() );
+ for (auto const& elem : aB2DPolyPolyVector)
+ rResultVector.emplace_back(elem); // #i76339#
+
+ return true;
+}
+
+bool OutputDevice::GetTextOutline( tools::PolyPolygon& rPolyPoly, const OUString& rStr ) const
+{
+ rPolyPoly.Clear();
+
+ // get the basegfx polypolygon vector
+ basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
+ if( !GetTextOutlines( aB2DPolyPolyVector, rStr, 0/*nBase*/, 0/*nIndex*/, /*nLen*/-1,
+ /*nLayoutWidth*/0, /*pDXArray*/{} ) )
+ return false;
+
+ // convert and merge into a tool polypolygon
+ for (auto const& elem : aB2DPolyPolyVector)
+ for(auto const& rB2DPolygon : elem)
+ rPolyPoly.Insert(tools::Polygon(rB2DPolygon)); // #i76339#
+
+ return true;
+}
+
+void OutputDevice::SetSystemTextColor(SystemTextColorFlags nFlags, bool bEnabled)
+{
+ if (nFlags & SystemTextColorFlags::Mono)
+ {
+ SetTextColor(COL_BLACK);
+ }
+ else
+ {
+ if (!bEnabled)
+ {
+ const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
+ SetTextColor(rStyleSettings.GetDisableColor());
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/textline.cxx b/vcl/source/outdev/textline.cxx
new file mode 100644
index 0000000000..84965e87b5
--- /dev/null
+++ b/vcl/source/outdev/textline.cxx
@@ -0,0 +1,1126 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/types.h>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/WaveLine.hxx>
+#include <tools/helpers.hxx>
+#include <o3tl/hash_combine.hxx>
+#include <o3tl/lru_map.hxx>
+#include <unotools/configmgr.hxx>
+#include <vcl/lazydelete.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+
+#include <drawmode.hxx>
+#include <salgdi.hxx>
+#include <impglyphitem.hxx>
+
+#include <cassert>
+
+#define UNDERLINE_LAST LINESTYLE_BOLDWAVE
+#define STRIKEOUT_LAST STRIKEOUT_X
+
+namespace {
+ struct WavyLineCache final
+ {
+ WavyLineCache () : m_aItems( 10 ) {}
+
+ bool find( Color aLineColor, size_t nLineWidth, size_t nWaveHeight, size_t nWordWidth, BitmapEx& rOutput )
+ {
+ Key aKey = { nWaveHeight, sal_uInt32(aLineColor) };
+ auto item = m_aItems.find( aKey );
+ if ( item == m_aItems.end() )
+ return false;
+ // needs update
+ if ( item->second.m_aLineWidth != nLineWidth || item->second.m_aWordWidth < nWordWidth )
+ {
+ return false;
+ }
+ rOutput = item->second.m_Bitmap;
+ return true;
+ }
+
+ void insert( const BitmapEx& aBitmap, const Color& aLineColor, const size_t nLineWidth, const size_t nWaveHeight, const size_t nWordWidth, BitmapEx& rOutput )
+ {
+ Key aKey = { nWaveHeight, sal_uInt32(aLineColor) };
+ m_aItems.insert( std::pair< Key, WavyLineCacheItem>( aKey, { nLineWidth, nWordWidth, aBitmap } ) );
+ rOutput = aBitmap;
+ }
+
+ private:
+ struct WavyLineCacheItem
+ {
+ size_t m_aLineWidth;
+ size_t m_aWordWidth;
+ BitmapEx m_Bitmap;
+ };
+
+ struct Key
+ {
+ size_t m_aFirst;
+ size_t m_aSecond;
+ bool operator ==( const Key& rOther ) const
+ {
+ return ( m_aFirst == rOther.m_aFirst && m_aSecond == rOther.m_aSecond );
+ }
+ };
+
+ struct Hash
+ {
+ size_t operator() ( const Key& rKey ) const
+ {
+ size_t aSeed = 0;
+ o3tl::hash_combine(aSeed, rKey.m_aFirst);
+ o3tl::hash_combine(aSeed, rKey.m_aSecond);
+ return aSeed;
+ }
+ };
+
+ o3tl::lru_map< Key, WavyLineCacheItem, Hash > m_aItems;
+ };
+}
+
+void OutputDevice::ImplInitTextLineSize()
+{
+ mpFontInstance->mxFontMetric->ImplInitTextLineSize( this );
+}
+
+void OutputDevice::ImplInitAboveTextLineSize()
+{
+ mpFontInstance->mxFontMetric->ImplInitAboveTextLineSize( this );
+}
+
+void OutputDevice::ImplDrawWavePixel( tools::Long nOriginX, tools::Long nOriginY,
+ tools::Long nCurX, tools::Long nCurY,
+ tools::Long nWidth,
+ Degree10 nOrientation,
+ SalGraphics* pGraphics,
+ const OutputDevice& rOutDev,
+ tools::Long nPixWidth, tools::Long nPixHeight )
+{
+ if (nOrientation)
+ {
+ Point aPoint( nOriginX, nOriginY );
+ aPoint.RotateAround( nCurX, nCurY, nOrientation );
+ }
+
+ if (shouldDrawWavePixelAsRect(nWidth))
+ {
+ pGraphics->DrawRect( nCurX, nCurY, nPixWidth, nPixHeight, rOutDev );
+ }
+ else
+ {
+ pGraphics->DrawPixel( nCurX, nCurY, rOutDev );
+ }
+}
+
+bool OutputDevice::shouldDrawWavePixelAsRect(tools::Long nLineWidth) const
+{
+ if (nLineWidth > 1)
+ return true;
+
+ return false;
+}
+
+void OutputDevice::SetWaveLineColors(Color const& rColor, tools::Long nLineWidth)
+{
+ // On printers that output pixel via DrawRect()
+ if (nLineWidth > 1)
+ {
+ if (mbLineColor || mbInitLineColor)
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+
+ mpGraphics->SetFillColor( rColor );
+ mbInitFillColor = true;
+ }
+ else
+ {
+ mpGraphics->SetLineColor( rColor );
+ mbInitLineColor = true;
+ }
+}
+
+Size OutputDevice::GetWaveLineSize(tools::Long nLineWidth) const
+{
+ if (nLineWidth > 1)
+ return Size(nLineWidth, ((nLineWidth*mnDPIX)+(mnDPIY/2))/mnDPIY);
+
+ return Size(1, 1);
+}
+
+void OutputDevice::ImplDrawWaveLine( tools::Long nBaseX, tools::Long nBaseY,
+ tools::Long nDistX, tools::Long nDistY,
+ tools::Long nWidth, tools::Long nHeight,
+ tools::Long nLineWidth, Degree10 nOrientation,
+ const Color& rColor )
+{
+ if ( !nHeight )
+ return;
+
+ tools::Long nStartX = nBaseX + nDistX;
+ tools::Long nStartY = nBaseY + nDistY;
+
+ // If the height is 1 pixel, it's enough output a line
+ if ( (nLineWidth == 1) && (nHeight == 1) )
+ {
+ mpGraphics->SetLineColor( rColor );
+ mbInitLineColor = true;
+
+ tools::Long nEndX = nStartX+nWidth;
+ tools::Long nEndY = nStartY;
+ if ( nOrientation )
+ {
+ Point aOriginPt( nBaseX, nBaseY );
+ aOriginPt.RotateAround( nStartX, nStartY, nOrientation );
+ aOriginPt.RotateAround( nEndX, nEndY, nOrientation );
+ }
+ mpGraphics->DrawLine( nStartX, nStartY, nEndX, nEndY, *this );
+ }
+ else
+ {
+ tools::Long nCurX = nStartX;
+ tools::Long nCurY = nStartY;
+ tools::Long nDiffX = 2;
+ tools::Long nDiffY = nHeight-1;
+ tools::Long nCount = nWidth;
+ tools::Long nOffY = -1;
+
+ SetWaveLineColors(rColor, nLineWidth);
+ Size aSize(GetWaveLineSize(nLineWidth));
+
+ tools::Long nPixWidth = aSize.Width();
+ tools::Long nPixHeight = aSize.Height();
+
+ if ( !nDiffY )
+ {
+ while ( nWidth )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
+ mpGraphics, *this,
+ nPixWidth, nPixHeight );
+ nCurX++;
+ nWidth--;
+ }
+ }
+ else
+ {
+ nCurY += nDiffY;
+ tools::Long nFreq = nCount / (nDiffX+nDiffY);
+ while ( nFreq-- )
+ {
+ for( tools::Long i = nDiffY; i; --i )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
+ mpGraphics, *this,
+ nPixWidth, nPixHeight );
+ nCurX++;
+ nCurY += nOffY;
+ }
+ for( tools::Long i = nDiffX; i; --i )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
+ mpGraphics, *this,
+ nPixWidth, nPixHeight );
+ nCurX++;
+ }
+ nOffY = -nOffY;
+ }
+ nFreq = nCount % (nDiffX+nDiffY);
+ if ( nFreq )
+ {
+ for( tools::Long i = nDiffY; i && nFreq; --i, --nFreq )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
+ mpGraphics, *this,
+ nPixWidth, nPixHeight );
+ nCurX++;
+ nCurY += nOffY;
+
+ }
+ for( tools::Long i = nDiffX; i && nFreq; --i, --nFreq )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
+ mpGraphics, *this,
+ nPixWidth, nPixHeight );
+ nCurX++;
+ }
+ }
+ }
+ }
+}
+
+void OutputDevice::ImplDrawWaveTextLine( tools::Long nBaseX, tools::Long nBaseY,
+ tools::Long nDistX, tools::Long nDistY, tools::Long nWidth,
+ FontLineStyle eTextLine,
+ Color aColor,
+ bool bIsAbove )
+{
+ static bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ if (bFuzzing && nWidth > 10000)
+ {
+ SAL_WARN("vcl.gdi", "drawLine, skipping suspicious WaveTextLine of length: "
+ << nWidth << " for fuzzing performance");
+ return;
+ }
+
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ tools::Long nLineHeight;
+ tools::Long nLinePos;
+
+ if ( bIsAbove )
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize();
+ nLinePos = pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset();
+ }
+ else
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetWavelineUnderlineSize();
+ nLinePos = pFontInstance->mxFontMetric->GetWavelineUnderlineOffset();
+ }
+ if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
+ nLineHeight = 3;
+
+ tools::Long nLineWidth = mnDPIX / 300;
+ if ( !nLineWidth )
+ nLineWidth = 1;
+
+ if ( eTextLine == LINESTYLE_BOLDWAVE )
+ nLineWidth *= 2;
+
+ nLinePos += nDistY - (nLineHeight / 2);
+
+ tools::Long nLineWidthHeight = ((nLineWidth * mnDPIX) + (mnDPIY / 2)) / mnDPIY;
+ if ( eTextLine == LINESTYLE_DOUBLEWAVE )
+ {
+ tools::Long nOrgLineHeight = nLineHeight;
+ nLineHeight /= 3;
+ if ( nLineHeight < 2 )
+ {
+ if ( nOrgLineHeight > 1 )
+ nLineHeight = 2;
+ else
+ nLineHeight = 1;
+ }
+
+ tools::Long nLineDY = nOrgLineHeight-(nLineHeight*2);
+ if ( nLineDY < nLineWidthHeight )
+ nLineDY = nLineWidthHeight;
+
+ tools::Long nLineDY2 = nLineDY/2;
+ if ( !nLineDY2 )
+ nLineDY2 = 1;
+
+ nLinePos -= nLineWidthHeight-nLineDY2;
+ ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight,
+ nLineWidth, mpFontInstance->mnOrientation, aColor );
+ nLinePos += nLineWidthHeight+nLineDY;
+ ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight,
+ nLineWidth, mpFontInstance->mnOrientation, aColor );
+ }
+ else
+ {
+ nLinePos -= nLineWidthHeight/2;
+ ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight,
+ nLineWidth, mpFontInstance->mnOrientation, aColor );
+ }
+}
+
+void OutputDevice::ImplDrawStraightTextLine( tools::Long nBaseX, tools::Long nBaseY,
+ tools::Long nDistX, tools::Long nDistY, tools::Long nWidth,
+ FontLineStyle eTextLine,
+ Color aColor,
+ bool bIsAbove )
+{
+ static bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ if (bFuzzing && nWidth > 100000)
+ {
+ SAL_WARN("vcl.gdi", "drawLine, skipping suspicious TextLine of length: "
+ << nWidth << " for fuzzing performance");
+ return;
+ }
+
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ tools::Long nLineHeight = 0;
+ tools::Long nLinePos = 0;
+ tools::Long nLinePos2 = 0;
+
+ const tools::Long nY = nDistY;
+
+ if ( eTextLine > UNDERLINE_LAST )
+ eTextLine = LINESTYLE_SINGLE;
+
+ switch ( eTextLine )
+ {
+ case LINESTYLE_SINGLE:
+ case LINESTYLE_DOTTED:
+ case LINESTYLE_DASH:
+ case LINESTYLE_LONGDASH:
+ case LINESTYLE_DASHDOT:
+ case LINESTYLE_DASHDOTDOT:
+ if ( bIsAbove )
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetAboveUnderlineSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetAboveUnderlineOffset();
+ }
+ else
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetUnderlineSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetUnderlineOffset();
+ }
+ break;
+ case LINESTYLE_BOLD:
+ case LINESTYLE_BOLDDOTTED:
+ case LINESTYLE_BOLDDASH:
+ case LINESTYLE_BOLDLONGDASH:
+ case LINESTYLE_BOLDDASHDOT:
+ case LINESTYLE_BOLDDASHDOTDOT:
+ if ( bIsAbove )
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset();
+ }
+ else
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetBoldUnderlineSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetBoldUnderlineOffset();
+ }
+ break;
+ case LINESTYLE_DOUBLE:
+ if ( bIsAbove )
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1();
+ nLinePos2 = nY + pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2();
+ }
+ else
+ {
+ nLineHeight = pFontInstance->mxFontMetric->GetDoubleUnderlineSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1();
+ nLinePos2 = nY + pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2();
+ }
+ break;
+ default:
+ break;
+ }
+
+ if ( !nLineHeight )
+ return;
+
+ if ( mbLineColor || mbInitLineColor )
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+ mpGraphics->SetFillColor( aColor );
+ mbInitFillColor = true;
+
+ tools::Long nLeft = nDistX;
+
+ switch ( eTextLine )
+ {
+ case LINESTYLE_SINGLE:
+ case LINESTYLE_BOLD:
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
+ break;
+ case LINESTYLE_DOUBLE:
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos2, nWidth, nLineHeight );
+ break;
+ case LINESTYLE_DOTTED:
+ case LINESTYLE_BOLDDOTTED:
+ {
+ tools::Long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ tools::Long nTempWidth = nDotWidth;
+ tools::Long nEnd = nLeft+nWidth;
+ while ( nLeft < nEnd )
+ {
+ if ( nLeft+nTempWidth > nEnd )
+ nTempWidth = nEnd-nLeft;
+
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempWidth, nLineHeight );
+ nLeft += nDotWidth*2;
+ }
+ }
+ break;
+ case LINESTYLE_DASH:
+ case LINESTYLE_LONGDASH:
+ case LINESTYLE_BOLDDASH:
+ case LINESTYLE_BOLDLONGDASH:
+ {
+ tools::Long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ tools::Long nMinDashWidth;
+ tools::Long nMinSpaceWidth;
+ tools::Long nSpaceWidth;
+ tools::Long nDashWidth;
+ if ( (eTextLine == LINESTYLE_LONGDASH) ||
+ (eTextLine == LINESTYLE_BOLDLONGDASH) )
+ {
+ nMinDashWidth = nDotWidth*6;
+ nMinSpaceWidth = nDotWidth*2;
+ nDashWidth = 200;
+ nSpaceWidth = 100;
+ }
+ else
+ {
+ nMinDashWidth = nDotWidth*4;
+ nMinSpaceWidth = (nDotWidth*150)/100;
+ nDashWidth = 100;
+ nSpaceWidth = 50;
+ }
+ nDashWidth = ((nDashWidth*mnDPIX)+1270)/2540;
+ nSpaceWidth = ((nSpaceWidth*mnDPIX)+1270)/2540;
+ // DashWidth will be increased if the line is getting too thick
+ // in proportion to the line's length
+ if ( nDashWidth < nMinDashWidth )
+ nDashWidth = nMinDashWidth;
+ if ( nSpaceWidth < nMinSpaceWidth )
+ nSpaceWidth = nMinSpaceWidth;
+
+ tools::Long nTempWidth = nDashWidth;
+ tools::Long nEnd = nLeft+nWidth;
+ while ( nLeft < nEnd )
+ {
+ if ( nLeft+nTempWidth > nEnd )
+ nTempWidth = nEnd-nLeft;
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempWidth, nLineHeight );
+ nLeft += nDashWidth+nSpaceWidth;
+ }
+ }
+ break;
+ case LINESTYLE_DASHDOT:
+ case LINESTYLE_BOLDDASHDOT:
+ {
+ tools::Long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ tools::Long nDashWidth = ((100*mnDPIX)+1270)/2540;
+ tools::Long nMinDashWidth = nDotWidth*4;
+ // DashWidth will be increased if the line is getting too thick
+ // in proportion to the line's length
+ if ( nDashWidth < nMinDashWidth )
+ nDashWidth = nMinDashWidth;
+
+ tools::Long nTempDotWidth = nDotWidth;
+ tools::Long nTempDashWidth = nDashWidth;
+ tools::Long nEnd = nLeft+nWidth;
+ while ( nLeft < nEnd )
+ {
+ if ( nLeft+nTempDotWidth > nEnd )
+ nTempDotWidth = nEnd-nLeft;
+
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight );
+ nLeft += nDotWidth*2;
+ if ( nLeft > nEnd )
+ break;
+
+ if ( nLeft+nTempDashWidth > nEnd )
+ nTempDashWidth = nEnd-nLeft;
+
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDashWidth, nLineHeight );
+ nLeft += nDashWidth+nDotWidth;
+ }
+ }
+ break;
+ case LINESTYLE_DASHDOTDOT:
+ case LINESTYLE_BOLDDASHDOTDOT:
+ {
+ tools::Long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ tools::Long nDashWidth = ((100*mnDPIX)+1270)/2540;
+ tools::Long nMinDashWidth = nDotWidth*4;
+ // DashWidth will be increased if the line is getting too thick
+ // in proportion to the line's length
+ if ( nDashWidth < nMinDashWidth )
+ nDashWidth = nMinDashWidth;
+
+ tools::Long nTempDotWidth = nDotWidth;
+ tools::Long nTempDashWidth = nDashWidth;
+ tools::Long nEnd = nLeft+nWidth;
+ while ( nLeft < nEnd )
+ {
+ if ( nLeft+nTempDotWidth > nEnd )
+ nTempDotWidth = nEnd-nLeft;
+
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight );
+ nLeft += nDotWidth*2;
+ if ( nLeft > nEnd )
+ break;
+
+ if ( nLeft+nTempDotWidth > nEnd )
+ nTempDotWidth = nEnd-nLeft;
+
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight );
+ nLeft += nDotWidth*2;
+ if ( nLeft > nEnd )
+ break;
+
+ if ( nLeft+nTempDashWidth > nEnd )
+ nTempDashWidth = nEnd-nLeft;
+
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDashWidth, nLineHeight );
+ nLeft += nDashWidth+nDotWidth;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void OutputDevice::ImplDrawStrikeoutLine( tools::Long nBaseX, tools::Long nBaseY,
+ tools::Long nDistX, tools::Long nDistY, tools::Long nWidth,
+ FontStrikeout eStrikeout,
+ Color aColor )
+{
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ tools::Long nLineHeight = 0;
+ tools::Long nLinePos = 0;
+ tools::Long nLinePos2 = 0;
+
+ tools::Long nY = nDistY;
+
+ if ( eStrikeout > STRIKEOUT_LAST )
+ eStrikeout = STRIKEOUT_SINGLE;
+
+ switch ( eStrikeout )
+ {
+ case STRIKEOUT_SINGLE:
+ nLineHeight = pFontInstance->mxFontMetric->GetStrikeoutSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetStrikeoutOffset();
+ break;
+ case STRIKEOUT_BOLD:
+ nLineHeight = pFontInstance->mxFontMetric->GetBoldStrikeoutSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetBoldStrikeoutOffset();
+ break;
+ case STRIKEOUT_DOUBLE:
+ nLineHeight = pFontInstance->mxFontMetric->GetDoubleStrikeoutSize();
+ nLinePos = nY + pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1();
+ nLinePos2 = nY + pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2();
+ break;
+ default:
+ break;
+ }
+
+ if ( !nLineHeight )
+ return;
+
+ if ( mbLineColor || mbInitLineColor )
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+ mpGraphics->SetFillColor( aColor );
+ mbInitFillColor = true;
+
+ const tools::Long& nLeft = nDistX;
+
+ switch ( eStrikeout )
+ {
+ case STRIKEOUT_SINGLE:
+ case STRIKEOUT_BOLD:
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
+ break;
+ case STRIKEOUT_DOUBLE:
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
+ ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos2, nWidth, nLineHeight );
+ break;
+ default:
+ break;
+ }
+}
+
+void OutputDevice::ImplDrawStrikeoutChar( tools::Long nBaseX, tools::Long nBaseY,
+ tools::Long nDistX, tools::Long nDistY, tools::Long nWidth,
+ FontStrikeout eStrikeout,
+ Color aColor )
+{
+ // See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
+ // to tweak this
+ if (!nWidth)
+ return;
+
+ // prepare string for strikeout measurement
+ const char cStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? '/' : 'X';
+ static const int nTestStrLen = 4;
+ static const int nMaxStrikeStrLen = 2048;
+ sal_Unicode aChars[nMaxStrikeStrLen+1]; // +1 for valgrind...
+
+ for( int i = 0; i < nTestStrLen; ++i)
+ aChars[i] = cStrikeoutChar;
+
+ const OUString aStrikeoutTest(aChars, nTestStrLen);
+
+ // calculate approximation of strikeout atom size
+ tools::Long nStrikeoutWidth = 0;
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( aStrikeoutTest, 0, nTestStrLen );
+ if( pLayout )
+ {
+ nStrikeoutWidth = pLayout->GetTextWidth() / nTestStrLen;
+ }
+ if( nStrikeoutWidth <= 0 ) // sanity check
+ return;
+
+ int nStrikeStrLen = (nWidth+(nStrikeoutWidth-1)) / nStrikeoutWidth;
+ if( nStrikeStrLen > nMaxStrikeStrLen )
+ nStrikeStrLen = nMaxStrikeStrLen;
+ else if (nStrikeStrLen < 0)
+ nStrikeStrLen = 0;
+
+ // build the strikeout string
+ for( int i = nTestStrLen; i < nStrikeStrLen; ++i)
+ aChars[i] = cStrikeoutChar;
+
+ const OUString aStrikeoutText(aChars, nStrikeStrLen);
+
+ if( mpFontInstance->mnOrientation )
+ {
+ Point aOriginPt(0, 0);
+ aOriginPt.RotateAround( nDistX, nDistY, mpFontInstance->mnOrientation );
+ }
+
+ nBaseX += nDistX;
+ nBaseY += nDistY;
+
+ // strikeout text has to be left aligned
+ vcl::text::ComplexTextLayoutFlags nOrigTLM = mnTextLayoutMode;
+ mnTextLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiStrong;
+ pLayout = ImplLayout( aStrikeoutText, 0, aStrikeoutText.getLength() );
+ mnTextLayoutMode = nOrigTLM;
+
+ if( !pLayout )
+ return;
+
+ // draw the strikeout text
+ const Color aOldColor = GetTextColor();
+ SetTextColor( aColor );
+ ImplInitTextColor();
+
+ pLayout->DrawBase() = basegfx::B2DPoint(nBaseX + mnTextOffX, nBaseY + mnTextOffY);
+
+ tools::Rectangle aPixelRect;
+ aPixelRect.SetLeft( nBaseX+mnTextOffX );
+ aPixelRect.SetRight( aPixelRect.Left()+nWidth );
+ aPixelRect.SetBottom( nBaseY+mpFontInstance->mxFontMetric->GetDescent() );
+ aPixelRect.SetTop( nBaseY-mpFontInstance->mxFontMetric->GetAscent() );
+
+ if (mpFontInstance->mnOrientation)
+ {
+ tools::Polygon aPoly( aPixelRect );
+ aPoly.Rotate( Point(nBaseX+mnTextOffX, nBaseY+mnTextOffY), mpFontInstance->mnOrientation);
+ aPixelRect = aPoly.GetBoundRect();
+ }
+
+ Push( vcl::PushFlags::CLIPREGION );
+ IntersectClipRegion( PixelToLogic(aPixelRect) );
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ pLayout->DrawText( *mpGraphics );
+
+ Pop();
+
+ SetTextColor( aOldColor );
+ ImplInitTextColor();
+}
+
+void OutputDevice::ImplDrawTextLine( tools::Long nX, tools::Long nY,
+ tools::Long nDistX, double nWidth,
+ FontStrikeout eStrikeout,
+ FontLineStyle eUnderline,
+ FontLineStyle eOverline,
+ bool bUnderlineAbove )
+{
+ if ( !nWidth )
+ return;
+
+ Color aStrikeoutColor = GetTextColor();
+ Color aUnderlineColor = GetTextLineColor();
+ Color aOverlineColor = GetOverlineColor();
+ bool bStrikeoutDone = false;
+ bool bUnderlineDone = false;
+ bool bOverlineDone = false;
+
+ if ( IsRTLEnabled() )
+ {
+ tools::Long nXAdd = nWidth - nDistX;
+ if( mpFontInstance->mnOrientation )
+ nXAdd = FRound( nXAdd * cos( toRadians(mpFontInstance->mnOrientation) ) );
+
+ nX += nXAdd - 1;
+ }
+
+ if ( !IsTextLineColor() )
+ aUnderlineColor = GetTextColor();
+
+ if ( !IsOverlineColor() )
+ aOverlineColor = GetTextColor();
+
+ if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
+ (eUnderline == LINESTYLE_WAVE) ||
+ (eUnderline == LINESTYLE_DOUBLEWAVE) ||
+ (eUnderline == LINESTYLE_BOLDWAVE) )
+ {
+ ImplDrawWaveTextLine( nX, nY, nDistX, 0, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
+ bUnderlineDone = true;
+ }
+ if ( (eOverline == LINESTYLE_SMALLWAVE) ||
+ (eOverline == LINESTYLE_WAVE) ||
+ (eOverline == LINESTYLE_DOUBLEWAVE) ||
+ (eOverline == LINESTYLE_BOLDWAVE) )
+ {
+ ImplDrawWaveTextLine( nX, nY, nDistX, 0, nWidth, eOverline, aOverlineColor, true );
+ bOverlineDone = true;
+ }
+
+ if ( (eStrikeout == STRIKEOUT_SLASH) ||
+ (eStrikeout == STRIKEOUT_X) )
+ {
+ ImplDrawStrikeoutChar( nX, nY, nDistX, 0, nWidth, eStrikeout, aStrikeoutColor );
+ bStrikeoutDone = true;
+ }
+
+ if ( !bUnderlineDone )
+ ImplDrawStraightTextLine( nX, nY, nDistX, 0, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
+
+ if ( !bOverlineDone )
+ ImplDrawStraightTextLine( nX, nY, nDistX, 0, nWidth, eOverline, aOverlineColor, true );
+
+ if ( !bStrikeoutDone )
+ ImplDrawStrikeoutLine( nX, nY, nDistX, 0, nWidth, eStrikeout, aStrikeoutColor );
+}
+
+void OutputDevice::ImplDrawTextLines( SalLayout& rSalLayout, FontStrikeout eStrikeout,
+ FontLineStyle eUnderline, FontLineStyle eOverline,
+ bool bWordLine, bool bUnderlineAbove )
+{
+ if( bWordLine )
+ {
+ // draw everything relative to the layout base point
+ const basegfx::B2DPoint aStartPt = rSalLayout.DrawBase();
+
+ // calculate distance of each word from the base point
+ basegfx::B2DPoint aPos;
+ double nDist = 0;
+ double nWidth = 0;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (rSalLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ // calculate the boundaries of each word
+ if (!pGlyph->IsSpacing())
+ {
+ if( !nWidth )
+ {
+ // get the distance to the base point (as projected to baseline)
+ nDist = aPos.getX() - aStartPt.getX();
+ if( mpFontInstance->mnOrientation )
+ {
+ const double nDY = aPos.getY() - aStartPt.getY();
+ const double fRad = toRadians(mpFontInstance->mnOrientation);
+ nDist = FRound( nDist*cos(fRad) - nDY*sin(fRad) );
+ }
+ }
+
+ // update the length of the textline
+ nWidth += pGlyph->newWidth();
+ }
+ else if( nWidth > 0 )
+ {
+ // draw the textline for each word
+ ImplDrawTextLine( aStartPt.getX(), aStartPt.getY(), nDist, nWidth,
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ nWidth = 0;
+ }
+ }
+
+ // draw textline for the last word
+ if( nWidth > 0 )
+ {
+ ImplDrawTextLine( aStartPt.getX(), aStartPt.getY(), nDist, nWidth,
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+ }
+ else
+ {
+ basegfx::B2DPoint aStartPt = rSalLayout.GetDrawPosition();
+ ImplDrawTextLine( aStartPt.getX(), aStartPt.getY(), 0,
+ rSalLayout.GetTextWidth(),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+}
+
+void OutputDevice::ImplDrawMnemonicLine( tools::Long nX, tools::Long nY, tools::Long nWidth )
+{
+ tools::Long nBaseX = nX;
+ if( /*HasMirroredGraphics() &&*/ IsRTLEnabled() )
+ {
+ // revert the hack that will be done later in ImplDrawTextLine
+ nX = nBaseX - nWidth - (nX - nBaseX - 1);
+ }
+
+ ImplDrawTextLine( nX, nY, 0, nWidth, STRIKEOUT_NONE, LINESTYLE_SINGLE, LINESTYLE_NONE, false );
+}
+
+void OutputDevice::SetTextLineColor()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextLineColorAction( Color(), false ) );
+
+ maTextLineColor = COL_TRANSPARENT;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextLineColor();
+}
+
+void OutputDevice::SetTextLineColor( const Color& rColor )
+{
+ Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextLineColorAction( aColor, true ) );
+
+ maTextLineColor = aColor;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextLineColor( COL_ALPHA_OPAQUE );
+}
+
+void OutputDevice::SetOverlineColor()
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaOverlineColorAction( Color(), false ) );
+
+ maOverlineColor = COL_TRANSPARENT;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetOverlineColor();
+}
+
+void OutputDevice::SetOverlineColor( const Color& rColor )
+{
+ Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaOverlineColorAction( aColor, true ) );
+
+ maOverlineColor = aColor;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetOverlineColor( COL_ALPHA_OPAQUE );
+}
+
+void OutputDevice::DrawTextLine( const Point& rPos, tools::Long nWidth,
+ FontStrikeout eStrikeout,
+ FontLineStyle eUnderline,
+ FontLineStyle eOverline,
+ bool bUnderlineAbove )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextLineAction( rPos, nWidth, eStrikeout, eUnderline, eOverline ) );
+
+ if ( ((eUnderline == LINESTYLE_NONE) || (eUnderline == LINESTYLE_DONTKNOW)) &&
+ ((eOverline == LINESTYLE_NONE) || (eOverline == LINESTYLE_DONTKNOW)) &&
+ ((eStrikeout == STRIKEOUT_NONE) || (eStrikeout == STRIKEOUT_DONTKNOW)) )
+ {
+ return;
+ }
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ // initialize font if needed to get text offsets
+ // TODO: only needed for mnTextOff!=(0,0)
+ if (!InitFont())
+ return;
+
+ Point aPos = ImplLogicToDevicePixel( rPos );
+ double fWidth = ImplLogicWidthToDeviceSubPixel(nWidth);
+ aPos += Point( mnTextOffX, mnTextOffY );
+ ImplDrawTextLine( aPos.X(), aPos.X(), 0, fWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawTextLine( rPos, nWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+}
+
+void OutputDevice::DrawWaveLine(const Point& rStartPos, const Point& rEndPos, tools::Long nLineWidth, tools::Long nWaveHeight)
+{
+ assert(!is_double_buffered_window());
+
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if (!InitFont())
+ return;
+
+ Point aStartPt = ImplLogicToDevicePixel(rStartPos);
+ Point aEndPt = ImplLogicToDevicePixel(rEndPos);
+
+ tools::Long nStartX = aStartPt.X();
+ tools::Long nStartY = aStartPt.Y();
+ tools::Long nEndX = aEndPt.X();
+ tools::Long nEndY = aEndPt.Y();
+ double fOrientation = 0.0;
+
+ // handle rotation
+ if (nStartY != nEndY || nStartX > nEndX)
+ {
+ fOrientation = basegfx::rad2deg(std::atan2(nStartY - nEndY, nEndX - nStartX));
+ // un-rotate the end point
+ aStartPt.RotateAround(nEndX, nEndY, Degree10(static_cast<sal_Int16>(-fOrientation * 10.0)));
+ }
+
+ // Handle HiDPI
+ float fScaleFactor = GetDPIScaleFactor();
+ if (fScaleFactor > 1.0f)
+ {
+ nWaveHeight *= fScaleFactor;
+
+ nStartY += fScaleFactor - 1; // Shift down additional pixel(s) to create more visual separation.
+
+ // odd heights look better than even
+ if (nWaveHeight % 2 == 0)
+ {
+ nWaveHeight--;
+ }
+ }
+
+ // #109280# make sure the waveline does not exceed the descent to avoid paint problems
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ if (nWaveHeight > pFontInstance->mxFontMetric->GetWavelineUnderlineSize())
+ {
+ nWaveHeight = pFontInstance->mxFontMetric->GetWavelineUnderlineSize();
+ // tdf#124848 hairline
+ nLineWidth = 0;
+ }
+
+ if ( fOrientation == 0.0 )
+ {
+ static vcl::DeleteOnDeinit< WavyLineCache > snLineCache {};
+ if ( !snLineCache.get() )
+ return;
+ WavyLineCache& rLineCache = *snLineCache.get();
+ BitmapEx aWavylinebmp;
+ if ( !rLineCache.find( GetLineColor(), nLineWidth, nWaveHeight, nEndX - nStartX, aWavylinebmp ) )
+ {
+ size_t nWordLength = nEndX - nStartX;
+ // start with something big to avoid updating it frequently
+ nWordLength = nWordLength < 1024 ? 1024 : nWordLength;
+ ScopedVclPtrInstance< VirtualDevice > pVirtDev( *this, DeviceFormat::WITH_ALPHA );
+ pVirtDev->SetOutputSizePixel( Size( nWordLength, nWaveHeight * 2 ), false );
+ pVirtDev->SetLineColor( GetLineColor() );
+ pVirtDev->SetBackground( Wallpaper( COL_TRANSPARENT ) );
+ pVirtDev->Erase();
+ pVirtDev->SetAntialiasing( AntialiasingFlags::Enable );
+ pVirtDev->ImplDrawWaveLineBezier( 0, 0, nWordLength, 0, nWaveHeight, fOrientation, nLineWidth );
+ BitmapEx aBitmapEx(pVirtDev->GetBitmapEx(Point(0, 0), pVirtDev->GetOutputSize()));
+
+ // Ideally we don't need this block, but in the split rgb surface + separate alpha surface
+ // with Antialiasing enabled and the svp/cairo backend we get both surfaces antialiased
+ // so their combination of aliases merge to overly wash-out the color. Hack it by taking just
+ // the alpha surface and use it to blend the original solid line color
+ Bitmap aSolidColor(aBitmapEx.GetBitmap());
+ aSolidColor.Erase(GetLineColor());
+ aBitmapEx = BitmapEx(aSolidColor, aBitmapEx.GetAlphaMask());
+
+ rLineCache.insert( aBitmapEx, GetLineColor(), nLineWidth, nWaveHeight, nWordLength, aWavylinebmp );
+ }
+ if ( aWavylinebmp.ImplGetBitmapSalBitmap() != nullptr )
+ {
+ Size _size( nEndX - nStartX, aWavylinebmp.GetSizePixel().Height() );
+ DrawBitmapEx(Point( rStartPos.X(), rStartPos.Y() ), PixelToLogic( _size ), Point(), _size, aWavylinebmp);
+ }
+ return;
+ }
+
+ ImplDrawWaveLineBezier( nStartX, nStartY, nEndX, nEndY, nWaveHeight, fOrientation, nLineWidth );
+}
+
+void OutputDevice::ImplDrawWaveLineBezier(tools::Long nStartX, tools::Long nStartY, tools::Long nEndX, tools::Long nEndY, tools::Long nWaveHeight, double fOrientation, tools::Long nLineWidth)
+{
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if (!InitFont())
+ return;
+
+ const basegfx::B2DRectangle aWaveLineRectangle(nStartX, nStartY, nEndX, nEndY + nWaveHeight);
+ const basegfx::B2DPolygon aWaveLinePolygon = basegfx::createWaveLinePolygon(aWaveLineRectangle);
+ const basegfx::B2DHomMatrix aRotationMatrix = basegfx::utils::createRotateAroundPoint(nStartX, nStartY, basegfx::deg2rad(-fOrientation));
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ mpGraphics->SetLineColor(GetLineColor());
+ mpGraphics->DrawPolyLine(
+ aRotationMatrix,
+ aWaveLinePolygon,
+ 0.0,
+ nLineWidth,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0),
+ bPixelSnapHairline,
+ *this);
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->ImplDrawWaveLineBezier(nStartX, nStartY, nEndX, nEndY, nWaveHeight, fOrientation, nLineWidth);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/transparent.cxx b/vcl/source/outdev/transparent.cxx
new file mode 100644
index 0000000000..7588dec556
--- /dev/null
+++ b/vcl/source/outdev/transparent.cxx
@@ -0,0 +1,1935 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/types.h>
+#include <osl/diagnose.h>
+#include <rtl/math.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <tools/helpers.hxx>
+#include <officecfg/Office/Common.hxx>
+
+#include <vcl/BitmapTools.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/print.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/BitmapWriteAccess.hxx>
+#include <pdf/pdfwriter_impl.hxx>
+#include <salgdi.hxx>
+
+#include <list>
+#include <memory>
+
+#define MAX_TILE_WIDTH 1024
+#define MAX_TILE_HEIGHT 1024
+
+namespace
+{
+ /**
+ * Perform a safe approximation of a polygon from double-precision
+ * coordinates to integer coordinates, to ensure that it has at least 2
+ * pixels in both X and Y directions.
+ */
+ tools::Polygon toPolygon( const basegfx::B2DPolygon& rPoly )
+ {
+ basegfx::B2DRange aRange = rPoly.getB2DRange();
+ double fW = aRange.getWidth(), fH = aRange.getHeight();
+ if (0.0 < fW && 0.0 < fH && (fW <= 1.0 || fH <= 1.0))
+ {
+ // This polygon not empty but is too small to display. Approximate it
+ // with a rectangle large enough to be displayed.
+ double nX = aRange.getMinX(), nY = aRange.getMinY();
+ double nW = std::max<double>(1.0, rtl::math::round(fW));
+ double nH = std::max<double>(1.0, rtl::math::round(fH));
+
+ tools::Polygon aTarget;
+ aTarget.Insert(0, Point(nX, nY));
+ aTarget.Insert(1, Point(nX+nW, nY));
+ aTarget.Insert(2, Point(nX+nW, nY+nH));
+ aTarget.Insert(3, Point(nX, nY+nH));
+ aTarget.Insert(4, Point(nX, nY));
+ return aTarget;
+ }
+ return tools::Polygon(rPoly);
+ }
+
+ tools::PolyPolygon toPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly )
+ {
+ tools::PolyPolygon aTarget;
+ for (auto const& rB2DPolygon : rPolyPoly)
+ aTarget.Insert(toPolygon(rB2DPolygon));
+
+ return aTarget;
+ }
+}
+
+// Caution: This method is nearly the same as
+// void OutputDevice::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rB2DPolyPoly )
+// so when changes are made here do not forget to make changes there, too
+
+void OutputDevice::DrawTransparent(
+ const basegfx::B2DHomMatrix& rObjectTransform,
+ const basegfx::B2DPolyPolygon& rB2DPolyPoly,
+ double fTransparency)
+{
+ assert(!is_double_buffered_window());
+
+ // AW: Do NOT paint empty PolyPolygons
+ if(!rB2DPolyPoly.count())
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ if( mbInitFillColor )
+ InitFillColor();
+
+ if (RasterOp::OverPaint == GetRasterOp())
+ {
+ // b2dpolygon support not implemented yet on non-UNX platforms
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(rB2DPolyPoly);
+
+ // ensure it is closed
+ if(!aB2DPolyPolygon.isClosed())
+ {
+ // maybe assert, prevents buffering due to making a copy
+ aB2DPolyPolygon.setClosed( true );
+ }
+
+ // create ObjectToDevice transformation
+ const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rObjectTransform);
+ // TODO: this must not drop transparency for mpAlphaVDev case, but instead use premultiplied
+ // alpha... but that requires using premultiplied alpha also for already drawn data
+ const double fAdjustedTransparency = mpAlphaVDev ? 0 : fTransparency;
+
+ if (IsFillColor())
+ {
+ mpGraphics->DrawPolyPolygon(
+ aFullTransform,
+ aB2DPolyPolygon,
+ fAdjustedTransparency,
+ *this);
+ }
+
+ if (IsLineColor())
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : std::as_const(aB2DPolyPolygon))
+ {
+ mpGraphics->DrawPolyLine(
+ aFullTransform,
+ rPolygon,
+ fAdjustedTransparency,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ *this );
+ }
+ }
+
+ if( mpMetaFile )
+ {
+ // tdf#119843 need transformed Polygon here
+ basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly);
+ aB2DPolyPoly.transform(rObjectTransform);
+ mpMetaFile->AddAction(
+ new MetaTransparentAction(
+ tools::PolyPolygon(aB2DPolyPoly),
+ static_cast< sal_uInt16 >(fTransparency * 100.0)));
+ }
+
+ if (mpAlphaVDev)
+ mpAlphaVDev->DrawTransparent(rObjectTransform, rB2DPolyPoly, fTransparency);
+
+ return;
+ }
+
+ // fallback to old polygon drawing if needed
+ // tdf#119843 need transformed Polygon here
+ basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly);
+ aB2DPolyPoly.transform(rObjectTransform);
+ DrawTransparent(
+ toPolyPolygon(aB2DPolyPoly),
+ static_cast<sal_uInt16>(fTransparency * 100.0));
+}
+
+bool OutputDevice::DrawTransparentNatively ( const tools::PolyPolygon& rPolyPoly,
+ sal_uInt16 nTransparencePercent )
+{
+ assert(!is_double_buffered_window());
+
+ bool bDrawn = false;
+
+ if (true
+#if defined UNX && ! defined MACOSX && ! defined IOS
+ && GetBitCount() > 8
+#endif
+#ifdef _WIN32
+ // workaround bad dithering on remote displaying when using GDI+ with toolbar button highlighting
+ && !rPolyPoly.IsRect()
+#endif
+ )
+ {
+ // prepare the graphics device
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return true;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ if( mbInitFillColor )
+ InitFillColor();
+
+ // get the polygon in device coordinates
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon());
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+
+ const double fTransparency = 0.01 * nTransparencePercent;
+ if( mbFillColor )
+ {
+ // #i121591#
+ // CAUTION: Only non printing (pixel-renderer) VCL commands from OutputDevices
+ // should be used when printing. Normally this is avoided by the printer being
+ // non-AAed and thus e.g. on WIN GdiPlus calls are not used. It may be necessary
+ // to figure out a way of moving this code to its own function that is
+ // overridden by the Print class, which will mean we deliberately override the
+ // functionality and we use the fallback some lines below (which is not very good,
+ // though. For now, WinSalGraphics::drawPolyPolygon will detect printer usage and
+ // correct the wrong mapping (see there for details)
+ mpGraphics->DrawPolyPolygon(
+ aTransform,
+ aB2DPolyPolygon,
+ fTransparency,
+ *this);
+ bDrawn = true;
+ }
+
+ if( mbLineColor )
+ {
+ // disable the fill color for now
+ mpGraphics->SetFillColor();
+
+ // draw the border line
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : std::as_const(aB2DPolyPolygon))
+ {
+ bDrawn = mpGraphics->DrawPolyLine(
+ aTransform,
+ rPolygon,
+ fTransparency,
+ 0.0, // tdf#124848 hairline
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+ bPixelSnapHairline,
+ *this );
+ }
+
+ // prepare to restore the fill color
+ mbInitFillColor = mbFillColor;
+ }
+ }
+
+ return bDrawn;
+}
+
+void OutputDevice::EmulateDrawTransparent ( const tools::PolyPolygon& rPolyPoly,
+ sal_uInt16 nTransparencePercent )
+{
+ // #110958# Disable alpha VDev, we perform the necessary
+ VirtualDevice* pOldAlphaVDev = mpAlphaVDev;
+
+ // operation explicitly further below.
+ if( mpAlphaVDev )
+ mpAlphaVDev = nullptr;
+
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ mpMetaFile = nullptr;
+
+ tools::PolyPolygon aPolyPoly( LogicToPixel( rPolyPoly ) );
+ tools::Rectangle aPolyRect( aPolyPoly.GetBoundRect() );
+ tools::Rectangle aDstRect( Point(), GetOutputSizePixel() );
+
+ aDstRect.Intersection( aPolyRect );
+
+ ClipToPaintRegion( aDstRect );
+
+ if( !aDstRect.IsEmpty() )
+ {
+ bool bDrawn = false;
+
+ // #i66849# Added fast path for exactly rectangular
+ // polygons
+ // #i83087# Naturally, system alpha blending cannot
+ // work with separate alpha VDev
+ if( !mpAlphaVDev && aPolyPoly.IsRect() )
+ {
+ // setup Graphics only here (other cases delegate
+ // to basic OutDev methods)
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ tools::Rectangle aLogicPolyRect( rPolyPoly.GetBoundRect() );
+ tools::Rectangle aPixelRect( ImplLogicToDevicePixel( aLogicPolyRect ) );
+
+ if( !mbOutputClipped )
+ {
+ bDrawn = mpGraphics->DrawAlphaRect( aPixelRect.Left(), aPixelRect.Top(),
+ // #i98405# use methods with small g, else one pixel too much will be painted.
+ // This is because the source is a polygon which when painted would not paint
+ // the rightmost and lowest pixel line(s), so use one pixel less for the
+ // rectangle, too.
+ aPixelRect.getOpenWidth(), aPixelRect.getOpenHeight(),
+ sal::static_int_cast<sal_uInt8>(nTransparencePercent),
+ *this );
+ }
+ else
+ {
+ bDrawn = true;
+ }
+ }
+
+ if( !bDrawn )
+ {
+ ScopedVclPtrInstance< VirtualDevice > aVDev(*this);
+ const Size aDstSz( aDstRect.GetSize() );
+ const sal_uInt8 cTrans = FRound( std::clamp( nTransparencePercent * 2.55, 0.0, 255.0 ) );
+
+ if( aDstRect.Left() || aDstRect.Top() )
+ aPolyPoly.Move( -aDstRect.Left(), -aDstRect.Top() );
+
+ if( aVDev->SetOutputSizePixel( aDstSz ) )
+ {
+ const bool bOldMap = mbMap;
+
+ EnableMapMode( false );
+
+ aVDev->SetLineColor( COL_BLACK );
+ aVDev->SetFillColor( COL_BLACK );
+ aVDev->DrawPolyPolygon( aPolyPoly );
+
+ Bitmap aPaint( GetBitmap( aDstRect.TopLeft(), aDstSz ) );
+ Bitmap aPolyMask( aVDev->GetBitmap( Point(), aDstSz ) );
+
+ // #107766# check for non-empty bitmaps before accessing them
+ if( !aPaint.IsEmpty() && !aPolyMask.IsEmpty() )
+ {
+ BitmapScopedWriteAccess pW(aPaint);
+ BitmapScopedReadAccess pR(aPolyMask);
+
+ if( pW && pR )
+ {
+ BitmapColor aPixCol;
+ const BitmapColor aFillCol( GetFillColor() );
+ const BitmapColor aBlack( pR->GetBestMatchingColor( COL_BLACK ) );
+ const tools::Long nWidth = pW->Width();
+ const tools::Long nHeight = pW->Height();
+ const tools::Long nR = aFillCol.GetRed();
+ const tools::Long nG = aFillCol.GetGreen();
+ const tools::Long nB = aFillCol.GetBlue();
+ tools::Long nX, nY;
+
+ if (vcl::isPalettePixelFormat(aPaint.getPixelFormat()))
+ {
+ const BitmapPalette& rPal = pW->GetPalette();
+ const sal_uInt16 nCount = rPal.GetEntryCount();
+ std::unique_ptr<sal_uInt8[]> xMap(new sal_uInt8[ nCount * sizeof( BitmapColor )]);
+ BitmapColor* pMap = reinterpret_cast<BitmapColor*>(xMap.get());
+
+ for( sal_uInt16 i = 0; i < nCount; i++ )
+ {
+ BitmapColor aCol( rPal[ i ] );
+ aCol.Merge( aFillCol, cTrans );
+ pMap[ i ] = BitmapColor( static_cast<sal_uInt8>(rPal.GetBestIndex( aCol )) );
+ }
+
+ if( pR->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal &&
+ pW->GetScanlineFormat() == ScanlineFormat::N8BitPal )
+ {
+ const sal_uInt8 cBlack = aBlack.GetIndex();
+
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pWScan = pW->GetScanline( nY );
+ Scanline pRScan = pR->GetScanline( nY );
+ sal_uInt8 cBit = 128;
+
+ for( nX = 0; nX < nWidth; nX++, cBit >>= 1, pWScan++ )
+ {
+ if( !cBit )
+ {
+ cBit = 128;
+ pRScan += 1;
+ }
+ if( ( *pRScan & cBit ) == cBlack )
+ {
+ *pWScan = pMap[ *pWScan ].GetIndex();
+ }
+ }
+ }
+ }
+ else
+ {
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pW->GetScanline(nY);
+ Scanline pScanlineRead = pR->GetScanline(nY);
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+ if( pR->GetPixelFromData( pScanlineRead, nX ) == aBlack )
+ {
+ pW->SetPixelOnData( pScanline, nX, pMap[ pW->GetIndexFromData( pScanline, nX ) ] );
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ if( pR->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal &&
+ pW->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr )
+ {
+ const sal_uInt8 cBlack = aBlack.GetIndex();
+
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pWScan = pW->GetScanline( nY );
+ Scanline pRScan = pR->GetScanline( nY );
+ sal_uInt8 cBit = 128;
+
+ for( nX = 0; nX < nWidth; nX++, cBit >>= 1, pWScan += 3 )
+ {
+ if( !cBit )
+ {
+ cBit = 128;
+ pRScan += 1;
+ }
+ if( ( *pRScan & cBit ) == cBlack )
+ {
+ pWScan[ 0 ] = color::ColorChannelMerge( pWScan[ 0 ], nB, cTrans );
+ pWScan[ 1 ] = color::ColorChannelMerge( pWScan[ 1 ], nG, cTrans );
+ pWScan[ 2 ] = color::ColorChannelMerge( pWScan[ 2 ], nR, cTrans );
+ }
+ }
+ }
+ }
+ else
+ {
+ for( nY = 0; nY < nHeight; nY++ )
+ {
+ Scanline pScanline = pW->GetScanline(nY);
+ Scanline pScanlineRead = pR->GetScanline(nY);
+ for( nX = 0; nX < nWidth; nX++ )
+ {
+ if( pR->GetPixelFromData( pScanlineRead, nX ) == aBlack )
+ {
+ aPixCol = pW->GetColor( nY, nX );
+ aPixCol.Merge(aFillCol, cTrans);
+ pW->SetPixelOnData(pScanline, nX, aPixCol);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ pR.reset();
+ pW.reset();
+
+ DrawBitmap( aDstRect.TopLeft(), aPaint );
+
+ EnableMapMode( bOldMap );
+
+ if( mbLineColor )
+ {
+ Push( vcl::PushFlags::FILLCOLOR );
+ SetFillColor();
+ DrawPolyPolygon( rPolyPoly );
+ Pop();
+ }
+ }
+ }
+ else
+ {
+ DrawPolyPolygon( rPolyPoly );
+ }
+ }
+ }
+
+ mpMetaFile = pOldMetaFile;
+
+ // #110958# Restore disabled alpha VDev
+ mpAlphaVDev = pOldAlphaVDev;
+}
+
+void OutputDevice::DrawTransparent( const tools::PolyPolygon& rPolyPoly,
+ sal_uInt16 nTransparencePercent )
+{
+ assert(!is_double_buffered_window());
+
+ // short circuit for drawing an opaque polygon
+ if( (nTransparencePercent < 1) || (mnDrawMode & DrawModeFlags::NoTransparency) )
+ {
+ DrawPolyPolygon( rPolyPoly );
+ return;
+ }
+
+ // short circuit for drawing an invisible polygon
+ if( (!mbFillColor && !mbLineColor) || (nTransparencePercent >= 100) )
+ return; // tdf#84294: do not record it in metafile
+
+ // handle metafile recording
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTransparentAction( rPolyPoly, nTransparencePercent ) );
+
+ bool bDrawn = !IsDeviceOutputNecessary() || ImplIsRecordLayout();
+ if( bDrawn )
+ return;
+
+ // get the device graphics as drawing target
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ assert(mpGraphics);
+
+ // try hard to draw it directly, because the emulation layers are slower
+ bDrawn = DrawTransparentNatively( rPolyPoly, nTransparencePercent );
+
+ if (!bDrawn)
+ EmulateDrawTransparent( rPolyPoly, nTransparencePercent );
+
+ // #110958# Apply alpha value also to VDev alpha channel
+ if( mpAlphaVDev )
+ {
+ const Color aFillCol( mpAlphaVDev->GetFillColor() );
+ sal_uInt8 nAlpha = 255 - sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100);
+ mpAlphaVDev->SetFillColor( Color(nAlpha, nAlpha, nAlpha) );
+
+ mpAlphaVDev->DrawTransparent( rPolyPoly, nTransparencePercent );
+
+ mpAlphaVDev->SetFillColor( aFillCol );
+ }
+}
+
+void OutputDevice::DrawTransparent( const GDIMetaFile& rMtf, const Point& rPos,
+ const Size& rSize, const Gradient& rTransparenceGradient )
+{
+ DrawTransparent( rMtf, rPos, rSize, rPos, rSize, rTransparenceGradient );
+}
+
+void OutputDevice::DrawTransparent( const GDIMetaFile& rMtf, const Point& rPos, const Size& rSize,
+ const Point& rMtfPos, const Size& rMtfSize,
+ const Gradient& rTransparenceGradient )
+{
+ assert(!is_double_buffered_window());
+
+ const Color aBlack( COL_BLACK );
+
+ if( mpMetaFile )
+ {
+ // missing here is to map the data using the DeviceTransformation
+ mpMetaFile->AddAction( new MetaFloatTransparentAction( rMtf, rPos, rSize, rTransparenceGradient ) );
+ }
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+
+ if( ( rTransparenceGradient.GetStartColor() == aBlack && rTransparenceGradient.GetEndColor() == aBlack ) ||
+ ( mnDrawMode & DrawModeFlags::NoTransparency ) )
+ {
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ const_cast<GDIMetaFile&>(rMtf).Play(*this, rMtfPos, rMtfSize);
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ }
+ else
+ {
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ tools::Rectangle aOutRect( LogicToPixel( tools::Rectangle(rPos, rSize) ) );
+ Point aPoint;
+ tools::Rectangle aDstRect( aPoint, GetOutputSizePixel() );
+
+ mpMetaFile = nullptr;
+ aDstRect.Intersection( aOutRect );
+
+ ClipToPaintRegion( aDstRect );
+
+ if( !aDstRect.IsEmpty() )
+ {
+ // Create transparent buffer
+ ScopedVclPtrInstance<VirtualDevice> xVDev(DeviceFormat::WITH_ALPHA);
+
+ xVDev->mnDPIX = mnDPIX;
+ xVDev->mnDPIY = mnDPIY;
+
+ if( xVDev->SetOutputSizePixel( aDstRect.GetSize(), true, true ) )
+ {
+ // tdf#150610 fix broken rendering of text meta actions
+ // Even when drawing to a VirtualDevice that has antialiasing
+ // disabled, text will still be drawn with some antialiased
+ // pixels on HiDPI displays. So, use the antialiasing enabled
+ // code to render if there are any text meta actions in the
+ // metafile.
+ if(GetAntialiasing() != AntialiasingFlags::NONE || rPos != rMtfPos || rSize != rMtfSize)
+ {
+ // #i102109#
+ // For MetaFile replay (see task) it may now be necessary to take
+ // into account that the content is AntiAlialiased and needs to be masked
+ // like that. Instead of masking, i will use a copy-modify-paste cycle
+ // here (as i already use in the VclPrimiziveRenderer with success)
+ xVDev->SetAntialiasing(GetAntialiasing());
+
+ // create MapMode for buffer (offset needed) and set
+ MapMode aMap(GetMapMode());
+ const Point aOutPos(PixelToLogic(aDstRect.TopLeft()));
+ aMap.SetOrigin(Point(-aOutPos.X(), -aOutPos.Y()));
+ xVDev->SetMapMode(aMap);
+
+ // copy MapMode state and disable for target
+ const bool bOrigMapModeEnabled(IsMapModeEnabled());
+ EnableMapMode(false);
+
+ // copy MapMode state and disable for buffer
+ const bool bBufferMapModeEnabled(xVDev->IsMapModeEnabled());
+ xVDev->EnableMapMode(false);
+
+ // copy content from original to buffer
+ xVDev->DrawOutDev( aPoint, xVDev->GetOutputSizePixel(), // dest
+ aDstRect.TopLeft(), xVDev->GetOutputSizePixel(), // source
+ *this);
+
+ // draw MetaFile to buffer
+ xVDev->EnableMapMode(bBufferMapModeEnabled);
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ const_cast<GDIMetaFile&>(rMtf).Play(*xVDev, rMtfPos, rMtfSize);
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+
+ // get content bitmap from buffer
+ xVDev->EnableMapMode(false);
+
+ const BitmapEx aPaint(xVDev->GetBitmapEx(aPoint, xVDev->GetOutputSizePixel()));
+
+ // create alpha mask from gradient and get as Bitmap
+ xVDev->EnableMapMode(bBufferMapModeEnabled);
+ xVDev->SetDrawMode(DrawModeFlags::GrayGradient);
+ // Related tdf#150610 draw gradient to VirtualDevice bounds
+ // If we are here and the metafile bounds differs from the
+ // VirtualDevice bounds so that we apply the transparency
+ // gradient to any pixels drawn outside of the metafile
+ // bounds.
+ xVDev->DrawGradient(tools::Rectangle(rPos, rSize), rTransparenceGradient);
+ xVDev->SetDrawMode(DrawModeFlags::Default);
+ xVDev->EnableMapMode(false);
+
+ AlphaMask aAlpha(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel()));
+ AlphaMask aPaintAlpha(aPaint.GetAlphaMask());
+ // The alpha mask is inverted from what
+ // is expected so invert it again
+ aAlpha.Invert(); // convert to alpha
+ aAlpha.BlendWith(aPaintAlpha);
+
+ xVDev.disposeAndClear();
+
+ // draw masked content to target and restore MapMode
+ DrawBitmapEx(aDstRect.TopLeft(), BitmapEx(aPaint.GetBitmap(), aAlpha));
+ EnableMapMode(bOrigMapModeEnabled);
+ }
+ else
+ {
+ MapMode aMap( GetMapMode() );
+ Point aOutPos( PixelToLogic( aDstRect.TopLeft() ) );
+ const bool bOldMap = mbMap;
+
+ aMap.SetOrigin( Point( -aOutPos.X(), -aOutPos.Y() ) );
+ xVDev->SetMapMode( aMap );
+ const bool bVDevOldMap = xVDev->IsMapModeEnabled();
+
+ // create paint bitmap
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ const_cast<GDIMetaFile&>(rMtf).Play(*xVDev, rMtfPos, rMtfSize);
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ xVDev->EnableMapMode( false );
+ BitmapEx aPaint = xVDev->GetBitmapEx(Point(), xVDev->GetOutputSizePixel());
+ xVDev->EnableMapMode( bVDevOldMap ); // #i35331#: MUST NOT use EnableMapMode( sal_True ) here!
+
+ // create alpha mask from gradient
+ xVDev->SetDrawMode( DrawModeFlags::GrayGradient );
+ xVDev->DrawGradient( tools::Rectangle( rMtfPos, rMtfSize ), rTransparenceGradient );
+ xVDev->SetDrawMode( DrawModeFlags::Default );
+ xVDev->EnableMapMode( false );
+
+ AlphaMask aAlpha(xVDev->GetBitmap(Point(), xVDev->GetOutputSizePixel()));
+ AlphaMask aPaintAlpha(aPaint.GetAlphaMask());
+ // The alpha mask is inverted from what
+ // is expected so invert it again
+ aAlpha.Invert(); // convert to alpha
+ aAlpha.BlendWith(aPaintAlpha);
+
+ xVDev.disposeAndClear();
+
+ EnableMapMode( false );
+ DrawBitmapEx(aDstRect.TopLeft(), BitmapEx(aPaint.GetBitmap(), aAlpha));
+ EnableMapMode( bOldMap );
+ }
+ }
+ }
+
+ mpMetaFile = pOldMetaFile;
+ }
+}
+
+typedef ::std::pair< MetaAction*, int > Component; // MetaAction plus index in metafile
+
+namespace {
+
+// List of (intersecting) actions, plus overall bounds
+struct ConnectedComponents
+{
+ ConnectedComponents() :
+ aComponentList(),
+ aBounds(),
+ aBgColor(COL_WHITE),
+ bIsSpecial(false),
+ bIsFullyTransparent(false)
+ {}
+
+ ::std::list< Component > aComponentList;
+ tools::Rectangle aBounds;
+ Color aBgColor;
+ bool bIsSpecial;
+ bool bIsFullyTransparent;
+};
+
+}
+
+namespace {
+
+/** Determines whether the action can handle transparency correctly
+ (i.e. when painted on white background, does the action still look
+ correct)?
+ */
+bool DoesActionHandleTransparency( const MetaAction& rAct )
+{
+ // MetaActionType::FLOATTRANSPARENT can contain a whole metafile,
+ // which is to be rendered with the given transparent gradient. We
+ // currently cannot emulate transparent painting on a white
+ // background reliably.
+
+ // the remainder can handle printing itself correctly on a uniform
+ // white background.
+ switch( rAct.GetType() )
+ {
+ case MetaActionType::Transparent:
+ case MetaActionType::BMPEX:
+ case MetaActionType::BMPEXSCALE:
+ case MetaActionType::BMPEXSCALEPART:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+bool doesRectCoverWithUniformColor(
+ tools::Rectangle const & rPrevRect,
+ tools::Rectangle const & rCurrRect,
+ OutputDevice const & rMapModeVDev)
+{
+ // shape needs to fully cover previous content, and have uniform
+ // color
+ return (rMapModeVDev.LogicToPixel(rCurrRect).Contains(rPrevRect) &&
+ rMapModeVDev.IsFillColor());
+}
+
+/** Check whether rCurrRect rectangle fully covers io_rPrevRect - if
+ yes, return true and update o_rBgColor
+ */
+bool checkRect( tools::Rectangle& io_rPrevRect,
+ Color& o_rBgColor,
+ const tools::Rectangle& rCurrRect,
+ OutputDevice const & rMapModeVDev )
+{
+ bool bRet = doesRectCoverWithUniformColor(io_rPrevRect, rCurrRect, rMapModeVDev);
+
+ if( bRet )
+ {
+ io_rPrevRect = rCurrRect;
+ o_rBgColor = rMapModeVDev.GetFillColor();
+ }
+
+ return bRet;
+}
+
+/** #107169# Convert BitmapEx to Bitmap with appropriately blended
+ color. Convert MetaTransparentAction to plain polygon,
+ appropriately colored
+
+ @param o_rMtf
+ Add converted actions to this metafile
+*/
+void ImplConvertTransparentAction( GDIMetaFile& o_rMtf,
+ const MetaAction& rAct,
+ const OutputDevice& rStateOutDev,
+ Color aBgColor )
+{
+ if (rAct.GetType() == MetaActionType::Transparent)
+ {
+ const MetaTransparentAction* pTransAct = static_cast<const MetaTransparentAction*>(&rAct);
+ sal_uInt16 nTransparency( pTransAct->GetTransparence() );
+
+ // #i10613# Respect transparency for draw color
+ if (nTransparency)
+ {
+ o_rMtf.AddAction(new MetaPushAction(vcl::PushFlags::LINECOLOR|vcl::PushFlags::FILLCOLOR));
+
+ // assume white background for alpha blending
+ Color aLineColor(rStateOutDev.GetLineColor());
+ aLineColor.SetRed(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency) * aLineColor.GetRed()) / 100));
+ aLineColor.SetGreen(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency) * aLineColor.GetGreen()) / 100));
+ aLineColor.SetBlue(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency) * aLineColor.GetBlue()) / 100));
+ o_rMtf.AddAction(new MetaLineColorAction(aLineColor, true));
+
+ Color aFillColor(rStateOutDev.GetFillColor());
+ aFillColor.SetRed(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency)*aFillColor.GetRed()) / 100));
+ aFillColor.SetGreen(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency)*aFillColor.GetGreen()) / 100));
+ aFillColor.SetBlue(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency)*aFillColor.GetBlue()) / 100));
+ o_rMtf.AddAction(new MetaFillColorAction(aFillColor, true));
+ }
+
+ o_rMtf.AddAction(new MetaPolyPolygonAction(pTransAct->GetPolyPolygon()));
+
+ if(nTransparency)
+ o_rMtf.AddAction(new MetaPopAction());
+ }
+ else
+ {
+ BitmapEx aBmpEx;
+
+ switch (rAct.GetType())
+ {
+ case MetaActionType::BMPEX:
+ aBmpEx = static_cast<const MetaBmpExAction&>(rAct).GetBitmapEx();
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ aBmpEx = static_cast<const MetaBmpExScaleAction&>(rAct).GetBitmapEx();
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ aBmpEx = static_cast<const MetaBmpExScaleAction&>(rAct).GetBitmapEx();
+ break;
+
+ case MetaActionType::Transparent:
+
+ default:
+ OSL_FAIL("Printer::GetPreparedMetafile impossible state reached");
+ break;
+ }
+
+ Bitmap aBmp(aBmpEx.GetBitmap());
+ if (aBmpEx.IsAlpha())
+ {
+ // blend with alpha channel
+ aBmp.Convert(BmpConversion::N24Bit);
+ aBmp.Blend(aBmpEx.GetAlphaMask(), aBgColor);
+ }
+
+ // add corresponding action
+ switch (rAct.GetType())
+ {
+ case MetaActionType::BMPEX:
+ o_rMtf.AddAction(new MetaBmpAction(
+ static_cast<const MetaBmpExAction&>(rAct).GetPoint(),
+ aBmp));
+ break;
+ case MetaActionType::BMPEXSCALE:
+ o_rMtf.AddAction(new MetaBmpScaleAction(
+ static_cast<const MetaBmpExScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaBmpExScaleAction&>(rAct).GetSize(),
+ aBmp));
+ break;
+ case MetaActionType::BMPEXSCALEPART:
+ o_rMtf.AddAction(new MetaBmpScalePartAction(
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestSize(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetSrcPoint(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetSrcSize(),
+ aBmp));
+ break;
+ default:
+ OSL_FAIL("Unexpected case");
+ break;
+ }
+ }
+}
+
+// #i10613# Extracted from ImplCheckRect::ImplCreate
+// Returns true, if given action creates visible (i.e. non-transparent) output
+bool ImplIsNotTransparent( const MetaAction& rAct, const OutputDevice& rOut )
+{
+ const bool bLineTransparency( !rOut.IsLineColor() || rOut.GetLineColor().IsFullyTransparent() );
+ const bool bFillTransparency( !rOut.IsFillColor() || rOut.GetFillColor().IsFullyTransparent() );
+ bool bRet( false );
+
+ switch( rAct.GetType() )
+ {
+ case MetaActionType::POINT:
+ if( !bLineTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::LINE:
+ if( !bLineTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::RECT:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::ELLIPSE:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::ARC:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::PIE:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::CHORD:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::POLYLINE:
+ if( !bLineTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::POLYGON:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ if( !bLineTransparency || !bFillTransparency )
+ bRet = true;
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction& rTextAct = static_cast<const MetaTextAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+ if (!aString.isEmpty())
+ bRet = true;
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction& rTextAct = static_cast<const MetaTextArrayAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+ if (!aString.isEmpty())
+ bRet = true;
+ }
+ break;
+
+ case MetaActionType::PIXEL:
+ case MetaActionType::BMP:
+ case MetaActionType::BMPSCALE:
+ case MetaActionType::BMPSCALEPART:
+ case MetaActionType::BMPEX:
+ case MetaActionType::BMPEXSCALE:
+ case MetaActionType::BMPEXSCALEPART:
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ case MetaActionType::MASKSCALEPART:
+ case MetaActionType::GRADIENT:
+ case MetaActionType::GRADIENTEX:
+ case MetaActionType::HATCH:
+ case MetaActionType::WALLPAPER:
+ case MetaActionType::Transparent:
+ case MetaActionType::FLOATTRANSPARENT:
+ case MetaActionType::EPS:
+ case MetaActionType::TEXTRECT:
+ case MetaActionType::STRETCHTEXT:
+ case MetaActionType::TEXTLINE:
+ // all other actions: generate non-transparent output
+ bRet = true;
+ break;
+
+ default:
+ break;
+ }
+
+ return bRet;
+}
+
+// #i10613# Extracted from ImplCheckRect::ImplCreate
+tools::Rectangle ImplCalcActionBounds( const MetaAction& rAct, const OutputDevice& rOut )
+{
+ tools::Rectangle aActionBounds;
+
+ switch( rAct.GetType() )
+ {
+ case MetaActionType::PIXEL:
+ aActionBounds = tools::Rectangle( static_cast<const MetaPixelAction&>(rAct).GetPoint(), Size( 1, 1 ) );
+ break;
+
+ case MetaActionType::POINT:
+ aActionBounds = tools::Rectangle( static_cast<const MetaPointAction&>(rAct).GetPoint(), Size( 1, 1 ) );
+ break;
+
+ case MetaActionType::LINE:
+ {
+ const MetaLineAction& rMetaLineAction = static_cast<const MetaLineAction&>(rAct);
+ aActionBounds = tools::Rectangle( rMetaLineAction.GetStartPoint(), rMetaLineAction.GetEndPoint() );
+ aActionBounds.Normalize();
+ const tools::Long nLineWidth(rMetaLineAction.GetLineInfo().GetWidth());
+ if(nLineWidth)
+ {
+ const tools::Long nHalfLineWidth((nLineWidth + 1) / 2);
+ aActionBounds.AdjustLeft( -nHalfLineWidth );
+ aActionBounds.AdjustTop( -nHalfLineWidth );
+ aActionBounds.AdjustRight(nHalfLineWidth );
+ aActionBounds.AdjustBottom(nHalfLineWidth );
+ }
+ break;
+ }
+
+ case MetaActionType::RECT:
+ aActionBounds = static_cast<const MetaRectAction&>(rAct).GetRect();
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ aActionBounds = tools::Polygon( static_cast<const MetaRoundRectAction&>(rAct).GetRect(),
+ static_cast<const MetaRoundRectAction&>(rAct).GetHorzRound(),
+ static_cast<const MetaRoundRectAction&>(rAct).GetVertRound() ).GetBoundRect();
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ const tools::Rectangle& rRect = static_cast<const MetaEllipseAction&>(rAct).GetRect();
+ aActionBounds = tools::Polygon( rRect.Center(),
+ rRect.GetWidth() >> 1,
+ rRect.GetHeight() >> 1 ).GetBoundRect();
+ break;
+ }
+
+ case MetaActionType::ARC:
+ aActionBounds = tools::Polygon( static_cast<const MetaArcAction&>(rAct).GetRect(),
+ static_cast<const MetaArcAction&>(rAct).GetStartPoint(),
+ static_cast<const MetaArcAction&>(rAct).GetEndPoint(), PolyStyle::Arc ).GetBoundRect();
+ break;
+
+ case MetaActionType::PIE:
+ aActionBounds = tools::Polygon( static_cast<const MetaPieAction&>(rAct).GetRect(),
+ static_cast<const MetaPieAction&>(rAct).GetStartPoint(),
+ static_cast<const MetaPieAction&>(rAct).GetEndPoint(), PolyStyle::Pie ).GetBoundRect();
+ break;
+
+ case MetaActionType::CHORD:
+ aActionBounds = tools::Polygon( static_cast<const MetaChordAction&>(rAct).GetRect(),
+ static_cast<const MetaChordAction&>(rAct).GetStartPoint(),
+ static_cast<const MetaChordAction&>(rAct).GetEndPoint(), PolyStyle::Chord ).GetBoundRect();
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ const MetaPolyLineAction& rMetaPolyLineAction = static_cast<const MetaPolyLineAction&>(rAct);
+ aActionBounds = rMetaPolyLineAction.GetPolygon().GetBoundRect();
+ const tools::Long nLineWidth(rMetaPolyLineAction.GetLineInfo().GetWidth());
+ if(nLineWidth)
+ {
+ const tools::Long nHalfLineWidth((nLineWidth + 1) / 2);
+ aActionBounds.AdjustLeft( -nHalfLineWidth );
+ aActionBounds.AdjustTop( -nHalfLineWidth );
+ aActionBounds.AdjustRight(nHalfLineWidth );
+ aActionBounds.AdjustBottom(nHalfLineWidth );
+ }
+ break;
+ }
+
+ case MetaActionType::POLYGON:
+ aActionBounds = static_cast<const MetaPolygonAction&>(rAct).GetPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ aActionBounds = static_cast<const MetaPolyPolygonAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::BMP:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpAction&>(rAct).GetPoint(),
+ rOut.PixelToLogic( static_cast<const MetaBmpAction&>(rAct).GetBitmap().GetSizePixel() ) );
+ break;
+
+ case MetaActionType::BMPSCALE:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaBmpScaleAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaBmpScalePartAction&>(rAct).GetDestSize() );
+ break;
+
+ case MetaActionType::BMPEX:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpExAction&>(rAct).GetPoint(),
+ rOut.PixelToLogic( static_cast<const MetaBmpExAction&>(rAct).GetBitmapEx().GetSizePixel() ) );
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpExScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaBmpExScaleAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ aActionBounds = tools::Rectangle( static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestSize() );
+ break;
+
+ case MetaActionType::MASK:
+ aActionBounds = tools::Rectangle( static_cast<const MetaMaskAction&>(rAct).GetPoint(),
+ rOut.PixelToLogic( static_cast<const MetaMaskAction&>(rAct).GetBitmap().GetSizePixel() ) );
+ break;
+
+ case MetaActionType::MASKSCALE:
+ aActionBounds = tools::Rectangle( static_cast<const MetaMaskScaleAction&>(rAct).GetPoint(),
+ static_cast<const MetaMaskScaleAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::MASKSCALEPART:
+ aActionBounds = tools::Rectangle( static_cast<const MetaMaskScalePartAction&>(rAct).GetDestPoint(),
+ static_cast<const MetaMaskScalePartAction&>(rAct).GetDestSize() );
+ break;
+
+ case MetaActionType::GRADIENT:
+ aActionBounds = static_cast<const MetaGradientAction&>(rAct).GetRect();
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ aActionBounds = static_cast<const MetaGradientExAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::HATCH:
+ aActionBounds = static_cast<const MetaHatchAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::WALLPAPER:
+ aActionBounds = static_cast<const MetaWallpaperAction&>(rAct).GetRect();
+ break;
+
+ case MetaActionType::Transparent:
+ aActionBounds = static_cast<const MetaTransparentAction&>(rAct).GetPolyPolygon().GetBoundRect();
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ aActionBounds = tools::Rectangle( static_cast<const MetaFloatTransparentAction&>(rAct).GetPoint(),
+ static_cast<const MetaFloatTransparentAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::EPS:
+ aActionBounds = tools::Rectangle( static_cast<const MetaEPSAction&>(rAct).GetPoint(),
+ static_cast<const MetaEPSAction&>(rAct).GetSize() );
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction& rTextAct = static_cast<const MetaTextAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+
+ if (!aString.isEmpty())
+ {
+ const Point aPtLog( rTextAct.GetPoint() );
+
+ // #105987# Use API method instead of Impl* methods
+ // #107490# Set base parameter equal to index parameter
+ rOut.GetTextBoundRect( aActionBounds, rTextAct.GetText(), rTextAct.GetIndex(),
+ rTextAct.GetIndex(), rTextAct.GetLen() );
+ aActionBounds.Move( aPtLog.X(), aPtLog.Y() );
+ }
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction& rTextAct = static_cast<const MetaTextArrayAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+
+ if( !aString.isEmpty() )
+ {
+ // #105987# ImplLayout takes everything in logical coordinates
+ std::unique_ptr<SalLayout> pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(),
+ rTextAct.GetLen(), rTextAct.GetPoint(),
+ 0, rTextAct.GetDXArray(), rTextAct.GetKashidaArray() );
+ if( pSalLayout )
+ {
+ tools::Rectangle aBoundRect( rOut.ImplGetTextBoundRect( *pSalLayout ) );
+ aActionBounds = rOut.PixelToLogic( aBoundRect );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ aActionBounds = static_cast<const MetaTextRectAction&>(rAct).GetRect();
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ const MetaStretchTextAction& rTextAct = static_cast<const MetaStretchTextAction&>(rAct);
+ const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) );
+
+ // #i16195# Literate copy from TextArray action, the
+ // semantics for the ImplLayout call are copied from the
+ // OutDev::DrawStretchText() code. Unfortunately, also in
+ // this case, public outdev methods such as GetTextWidth()
+ // don't provide enough info.
+ if( !aString.isEmpty() )
+ {
+ // #105987# ImplLayout takes everything in logical coordinates
+ std::unique_ptr<SalLayout> pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(),
+ rTextAct.GetLen(), rTextAct.GetPoint(),
+ rTextAct.GetWidth() );
+ if( pSalLayout )
+ {
+ tools::Rectangle aBoundRect( rOut.ImplGetTextBoundRect( *pSalLayout ) );
+ aActionBounds = rOut.PixelToLogic( aBoundRect );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::TEXTLINE:
+ OSL_FAIL("MetaActionType::TEXTLINE not supported");
+ break;
+
+ default:
+ break;
+ }
+
+ if( !aActionBounds.IsEmpty() )
+ {
+ // fdo#40421 limit current action's output to clipped area
+ if( rOut.IsClipRegion() )
+ return rOut.LogicToPixel(
+ rOut.GetClipRegion().GetBoundRect().Intersection( aActionBounds ) );
+ else
+ return rOut.LogicToPixel( aActionBounds );
+ }
+ else
+ return tools::Rectangle();
+}
+
+} // end anon namespace
+
+// TODO: this massive function operates on metafiles, so eventually it should probably
+// be shifted to the GDIMetaFile class
+bool OutputDevice::RemoveTransparenciesFromMetaFile( const GDIMetaFile& rInMtf, GDIMetaFile& rOutMtf,
+ tools::Long nMaxBmpDPIX, tools::Long nMaxBmpDPIY,
+ bool bReduceTransparency, bool bTransparencyAutoMode,
+ bool bDownsampleBitmaps,
+ const Color& rBackground
+ )
+{
+ MetaAction* pCurrAct;
+ bool bTransparent( false );
+
+ rOutMtf.Clear();
+
+ if(!bReduceTransparency || bTransparencyAutoMode)
+ bTransparent = rInMtf.HasTransparentActions();
+
+ // #i10613# Determine set of connected components containing transparent objects. These are
+ // then processed as bitmaps, the original actions are removed from the metafile.
+ if( !bTransparent )
+ {
+ // nothing transparent -> just copy
+ rOutMtf = rInMtf;
+ }
+ else
+ {
+ // #i10613#
+ // This works as follows: we want a number of distinct sets of
+ // connected components, where each set contains metafile
+ // actions that are intersecting (note: there are possibly
+ // more actions contained as are directly intersecting,
+ // because we can only produce rectangular bitmaps later
+ // on. Thus, each set of connected components is the smallest
+ // enclosing, axis-aligned rectangle that completely bounds a
+ // number of intersecting metafile actions, plus any action
+ // that would otherwise be cut in two). Therefore, we
+ // iteratively add metafile actions from the original metafile
+ // to this connected components list (aCCList), by checking
+ // each element's bounding box against intersection with the
+ // metaaction at hand.
+ // All those intersecting elements are removed from aCCList
+ // and collected in a temporary list (aCCMergeList). After all
+ // elements have been checked, the aCCMergeList elements are
+ // merged with the metaaction at hand into one resulting
+ // connected component, with one big bounding box, and
+ // inserted into aCCList again.
+ // The time complexity of this algorithm is O(n^3), where n is
+ // the number of metafile actions, and it finds all distinct
+ // regions of rectangle-bounded connected components. This
+ // algorithm was designed by AF.
+
+ // STAGE 1: Detect background
+
+ // Receives uniform background content, and is _not_ merged
+ // nor checked for intersection against other aCCList elements
+ ConnectedComponents aBackgroundComponent;
+
+ // Read the configuration value of minimal object area where transparency will be removed
+ double fReduceTransparencyMinArea = officecfg::Office::Common::VCL::ReduceTransparencyMinArea::get() / 100.0;
+ SAL_WARN_IF(fReduceTransparencyMinArea > 1.0, "vcl",
+ "Value of ReduceTransparencyMinArea config option is too high");
+ SAL_WARN_IF(fReduceTransparencyMinArea < 0.0, "vcl",
+ "Value of ReduceTransparencyMinArea config option is too low");
+ fReduceTransparencyMinArea = std::clamp(fReduceTransparencyMinArea, 0.0, 1.0);
+
+ // create an OutputDevice to record mapmode changes and the like
+ ScopedVclPtrInstance< VirtualDevice > aMapModeVDev;
+ aMapModeVDev->mnDPIX = mnDPIX;
+ aMapModeVDev->mnDPIY = mnDPIY;
+ aMapModeVDev->EnableOutput(false);
+
+ // weed out page-filling background objects (if they are
+ // uniformly coloured). Keeping them outside the other
+ // connected components often prevents whole-page bitmap
+ // generation.
+ bool bStillBackground=true; // true until first non-bg action
+ int nActionNum = 0, nLastBgAction = -1;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction();
+ if( rBackground != COL_TRANSPARENT )
+ {
+ aBackgroundComponent.aBgColor = rBackground;
+ aBackgroundComponent.aBounds = GetBackgroundComponentBounds();
+ }
+ while( pCurrAct && bStillBackground )
+ {
+ switch( pCurrAct->GetType() )
+ {
+ case MetaActionType::RECT:
+ {
+ if( !checkRect(
+ aBackgroundComponent.aBounds,
+ aBackgroundComponent.aBgColor,
+ static_cast<const MetaRectAction*>(pCurrAct)->GetRect(),
+ *aMapModeVDev) )
+ bStillBackground=false; // incomplete occlusion of background
+ else
+ nLastBgAction=nActionNum; // this _is_ background
+ break;
+ }
+ case MetaActionType::POLYGON:
+ {
+ const tools::Polygon aPoly(
+ static_cast<const MetaPolygonAction*>(pCurrAct)->GetPolygon());
+ if( !basegfx::utils::isRectangle(
+ aPoly.getB2DPolygon()) ||
+ !checkRect(
+ aBackgroundComponent.aBounds,
+ aBackgroundComponent.aBgColor,
+ aPoly.GetBoundRect(),
+ *aMapModeVDev) )
+ bStillBackground=false; // incomplete occlusion of background
+ else
+ nLastBgAction=nActionNum; // this _is_ background
+ break;
+ }
+ case MetaActionType::POLYPOLYGON:
+ {
+ const tools::PolyPolygon aPoly(
+ static_cast<const MetaPolyPolygonAction*>(pCurrAct)->GetPolyPolygon());
+ if( aPoly.Count() != 1 ||
+ !basegfx::utils::isRectangle(
+ aPoly[0].getB2DPolygon()) ||
+ !checkRect(
+ aBackgroundComponent.aBounds,
+ aBackgroundComponent.aBgColor,
+ aPoly.GetBoundRect(),
+ *aMapModeVDev) )
+ bStillBackground=false; // incomplete occlusion of background
+ else
+ nLastBgAction=nActionNum; // this _is_ background
+ break;
+ }
+ case MetaActionType::WALLPAPER:
+ {
+ if( !checkRect(
+ aBackgroundComponent.aBounds,
+ aBackgroundComponent.aBgColor,
+ static_cast<const MetaWallpaperAction*>(pCurrAct)->GetRect(),
+ *aMapModeVDev) )
+ bStillBackground=false; // incomplete occlusion of background
+ else
+ nLastBgAction=nActionNum; // this _is_ background
+ break;
+ }
+ default:
+ {
+ if( ImplIsNotTransparent( *pCurrAct,
+ *aMapModeVDev ) )
+ bStillBackground=false; // non-transparent action, possibly
+ // not uniform
+ else
+ // extend current bounds (next uniform action
+ // needs to fully cover this area)
+ aBackgroundComponent.aBounds.Union(
+ ImplCalcActionBounds(*pCurrAct, *aMapModeVDev) );
+ break;
+ }
+ }
+
+ // execute action to get correct MapModes etc.
+ pCurrAct->Execute( aMapModeVDev.get() );
+
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction();
+ ++nActionNum;
+ }
+
+ if (nLastBgAction != -1)
+ {
+ size_t nActionSize = rInMtf.GetActionSize();
+ // tdf#134736 move nLastBgAction to also include any trailing pops
+ for (size_t nPostLastBgAction = nLastBgAction + 1; nPostLastBgAction < nActionSize; ++nPostLastBgAction)
+ {
+ if (rInMtf.GetAction(nPostLastBgAction)->GetType() != MetaActionType::POP)
+ break;
+ nLastBgAction = nPostLastBgAction;
+ }
+ }
+
+ aMapModeVDev->ClearStack(); // clean up aMapModeVDev
+
+ // fast-forward until one after the last background action
+ // (need to reconstruct map mode vdev state)
+ nActionNum=0;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction();
+ while( pCurrAct && nActionNum<=nLastBgAction )
+ {
+ // up to and including last ink-generating background
+ // action go to background component
+ aBackgroundComponent.aComponentList.emplace_back(
+ pCurrAct, nActionNum );
+
+ // execute action to get correct MapModes etc.
+ pCurrAct->Execute( aMapModeVDev.get() );
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction();
+ ++nActionNum;
+ }
+
+ // STAGE 2: Generate connected components list
+
+ ::std::vector<ConnectedComponents> aCCList; // contains distinct sets of connected components as elements.
+
+ // iterate over all actions (start where background action
+ // search left off)
+ for( ;
+ pCurrAct;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum )
+ {
+ // execute action to get correct MapModes etc.
+ pCurrAct->Execute( aMapModeVDev.get() );
+
+ // cache bounds of current action
+ const tools::Rectangle aBBCurrAct( ImplCalcActionBounds(*pCurrAct, *aMapModeVDev) );
+
+ // accumulate collected bounds here, initialize with current action
+ tools::Rectangle aTotalBounds( aBBCurrAct ); // thus, aTotalComponents.aBounds is empty
+ // for non-output-generating actions
+ bool bTreatSpecial( false );
+ ConnectedComponents aTotalComponents;
+
+ // STAGE 2.1: Search for intersecting cc entries
+
+ // if aBBCurrAct is empty, it will intersect with no
+ // aCCList member. Thus, we can save the check.
+ // Furthermore, this ensures that non-output-generating
+ // actions get their own aCCList entry, which is necessary
+ // when copying them to the output metafile (see stage 4
+ // below).
+
+ // #107169# Wholly transparent objects need
+ // not be considered for connected components,
+ // too. Just put each of them into a separate
+ // component.
+ aTotalComponents.bIsFullyTransparent = !ImplIsNotTransparent(*pCurrAct, *aMapModeVDev);
+
+ if( !aBBCurrAct.IsEmpty() &&
+ !aTotalComponents.bIsFullyTransparent )
+ {
+ if( !aBackgroundComponent.aComponentList.empty() &&
+ !aBackgroundComponent.aBounds.Contains(aTotalBounds) )
+ {
+ // it seems the background is not large enough. to
+ // be on the safe side, combine with this component.
+ aTotalBounds.Union( aBackgroundComponent.aBounds );
+
+ // extract all aCurr actions to aTotalComponents
+ aTotalComponents.aComponentList.splice( aTotalComponents.aComponentList.end(),
+ aBackgroundComponent.aComponentList );
+
+ if( aBackgroundComponent.bIsSpecial )
+ bTreatSpecial = true;
+ }
+
+ bool bSomeComponentsChanged;
+
+ // now, this is unfortunate: since changing anyone of
+ // the aCCList elements (e.g. by merging or addition
+ // of an action) might generate new intersection with
+ // other aCCList elements, have to repeat the whole
+ // element scanning, until nothing changes anymore.
+ // Thus, this loop here makes us O(n^3) in the worst
+ // case.
+ do
+ {
+ // only loop here if 'intersects' branch below was hit
+ bSomeComponentsChanged = false;
+
+ // iterate over all current members of aCCList
+ for( auto aCurrCC=aCCList.begin(); aCurrCC != aCCList.end(); )
+ {
+ // first check if current element's bounds are
+ // empty. This ensures that empty actions are not
+ // merged into one component, as a matter of fact,
+ // they have no position.
+
+ // #107169# Wholly transparent objects need
+ // not be considered for connected components,
+ // too. Just put each of them into a separate
+ // component.
+ if( !aCurrCC->aBounds.IsEmpty() &&
+ !aCurrCC->bIsFullyTransparent &&
+ aCurrCC->aBounds.Overlaps( aTotalBounds ) )
+ {
+ // union the intersecting aCCList element into aTotalComponents
+
+ // calc union bounding box
+ aTotalBounds.Union( aCurrCC->aBounds );
+
+ // extract all aCurr actions to aTotalComponents
+ aTotalComponents.aComponentList.splice( aTotalComponents.aComponentList.end(),
+ aCurrCC->aComponentList );
+
+ if( aCurrCC->bIsSpecial )
+ bTreatSpecial = true;
+
+ // remove and delete aCurrCC element from list (we've now merged its content)
+ aCurrCC = aCCList.erase( aCurrCC );
+
+ // at least one component changed, need to rescan everything
+ bSomeComponentsChanged = true;
+ }
+ else
+ {
+ ++aCurrCC;
+ }
+ }
+ }
+ while( bSomeComponentsChanged );
+ }
+
+ // STAGE 2.2: Determine special state for cc element
+
+ // now test whether the whole connected component must be
+ // treated specially (i.e. rendered as a bitmap): if the
+ // added action is the very first action, or all actions
+ // before it are completely transparent, the connected
+ // component need not be treated specially, not even if
+ // the added action contains transparency. This is because
+ // painting of transparent objects on _white background_
+ // works without alpha compositing (you just calculate the
+ // color). Note that for the test "all objects before me
+ // are transparent" no sorting is necessary, since the
+ // added metaaction pCurrAct is always in the order the
+ // metafile is painted. Generally, the order of the
+ // metaactions in the ConnectedComponents are not
+ // guaranteed to be the same as in the metafile.
+ if( bTreatSpecial )
+ {
+ // prev component(s) special -> this one, too
+ aTotalComponents.bIsSpecial = true;
+ }
+ else if(!pCurrAct->IsTransparent())
+ {
+ // added action and none of prev components special ->
+ // this one normal, too
+ aTotalComponents.bIsSpecial = false;
+ }
+ else
+ {
+ // added action is special and none of prev components
+ // special -> do the detailed tests
+
+ // can the action handle transparency correctly
+ // (i.e. when painted on white background, does the
+ // action still look correct)?
+ if( !DoesActionHandleTransparency( *pCurrAct ) )
+ {
+ // no, action cannot handle its transparency on
+ // a printer device, render to bitmap
+ aTotalComponents.bIsSpecial = true;
+ }
+ else
+ {
+ // yes, action can handle its transparency, so
+ // check whether we're on white background
+ if( aTotalComponents.aComponentList.empty() )
+ {
+ // nothing between pCurrAct and page
+ // background -> don't be special
+ aTotalComponents.bIsSpecial = false;
+ }
+ else
+ {
+ // #107169# Fixes above now ensure that _no_
+ // object in the list is fully transparent. Thus,
+ // if the component list is not empty above, we
+ // must assume that we have to treat this
+ // component special.
+
+ // there are non-transparent objects between
+ // pCurrAct and the empty sheet of paper -> be
+ // special, then
+ aTotalComponents.bIsSpecial = true;
+ }
+ }
+ }
+
+ // STAGE 2.3: Add newly generated CC list element
+
+ // set new bounds and add action to list
+ aTotalComponents.aBounds = aTotalBounds;
+ aTotalComponents.aComponentList.emplace_back(
+ pCurrAct, nActionNum );
+
+ // add aTotalComponents as a new entry to aCCList
+ aCCList.push_back( aTotalComponents );
+
+ SAL_WARN_IF( aTotalComponents.aComponentList.empty(), "vcl",
+ "Printer::GetPreparedMetaFile empty component" );
+ SAL_WARN_IF( aTotalComponents.aBounds.IsEmpty() && (aTotalComponents.aComponentList.size() != 1), "vcl",
+ "Printer::GetPreparedMetaFile non-output generating actions must be solitary");
+ SAL_WARN_IF( aTotalComponents.bIsFullyTransparent && (aTotalComponents.aComponentList.size() != 1), "vcl",
+ "Printer::GetPreparedMetaFile fully transparent actions must be solitary");
+ }
+
+ // well now, we've got the list of disjunct connected
+ // components. Now we've got to create a map, which contains
+ // the corresponding aCCList element for every
+ // metaaction. Later on, we always process the complete
+ // metafile for each bitmap to be generated, but switch on
+ // output only for actions contained in the then current
+ // aCCList element. This ensures correct mapmode and attribute
+ // settings for all cases.
+
+ // maps mtf actions to CC list entries
+ ::std::vector< const ConnectedComponents* > aCCList_MemberMap( rInMtf.GetActionSize() );
+
+ // iterate over all aCCList members and their contained metaactions
+ for (auto const& currentItem : aCCList)
+ {
+ for (auto const& currentAction : currentItem.aComponentList)
+ {
+ // set pointer to aCCList element for corresponding index
+ aCCList_MemberMap[ currentAction.second ] = &currentItem;
+ }
+ }
+
+ // STAGE 3.1: Output background mtf actions (if there are any)
+
+ for (auto & component : aBackgroundComponent.aComponentList)
+ {
+ // simply add this action (above, we inserted the actions
+ // starting at index 0 up to and including nLastBgAction)
+ rOutMtf.AddAction( component.first );
+ }
+
+ // STAGE 3.2: Generate banded bitmaps for special regions
+
+ Point aPageOffset;
+ Size aTmpSize( GetOutputSizePixel() );
+ if( meOutDevType == OUTDEV_PDF )
+ {
+ auto pPdfWriter = static_cast<vcl::PDFWriterImpl*>(this);
+ aTmpSize = LogicToPixel(pPdfWriter->getCurPageSize(), MapMode(MapUnit::MapPoint));
+
+ // also add error code to PDFWriter
+ pPdfWriter->insertError(vcl::PDFWriter::Warning_Transparency_Converted);
+ }
+ else if( meOutDevType == OUTDEV_PRINTER )
+ {
+ Printer* pThis = dynamic_cast<Printer*>(this);
+ assert(pThis);
+ aPageOffset = pThis->GetPageOffsetPixel();
+ aPageOffset = Point( 0, 0 ) - aPageOffset;
+ aTmpSize = pThis->GetPaperSizePixel();
+ }
+ const tools::Rectangle aOutputRect( aPageOffset, aTmpSize );
+ bool bTiling = dynamic_cast<Printer*>(this) != nullptr;
+
+ // iterate over all aCCList members and generate bitmaps for the special ones
+ for (auto & currentItem : aCCList)
+ {
+ if( currentItem.bIsSpecial )
+ {
+ tools::Rectangle aBoundRect( currentItem.aBounds );
+ aBoundRect.Intersection( aOutputRect );
+
+ const double fBmpArea( static_cast<double>(aBoundRect.GetWidth()) * aBoundRect.GetHeight() );
+ const double fOutArea( static_cast<double>(aOutputRect.GetWidth()) * aOutputRect.GetHeight() );
+
+ // check if output doesn't exceed given size
+ if( bReduceTransparency && bTransparencyAutoMode && ( fBmpArea > ( fReduceTransparencyMinArea * fOutArea ) ) )
+ {
+ // output normally. Therefore, we simply clear the
+ // special attribute, as everything non-special is
+ // copied to rOutMtf further below.
+ currentItem.bIsSpecial = false;
+ }
+ else
+ {
+ // create new bitmap action first
+ if( aBoundRect.GetWidth() && aBoundRect.GetHeight() )
+ {
+ Point aDstPtPix( aBoundRect.TopLeft() );
+ Size aDstSzPix;
+
+ ScopedVclPtrInstance<VirtualDevice> aMapVDev; // here, we record only mapmode information
+ aMapVDev->EnableOutput(false);
+
+ ScopedVclPtrInstance<VirtualDevice> aPaintVDev; // into this one, we render.
+ aPaintVDev->SetBackground( aBackgroundComponent.aBgColor );
+
+ rOutMtf.AddAction( new MetaPushAction( vcl::PushFlags::MAPMODE ) );
+ rOutMtf.AddAction( new MetaMapModeAction() );
+
+ aPaintVDev->SetDrawMode( GetDrawMode() );
+
+ while( aDstPtPix.Y() <= aBoundRect.Bottom() )
+ {
+ aDstPtPix.setX( aBoundRect.Left() );
+ aDstSzPix = bTiling ? Size( MAX_TILE_WIDTH, MAX_TILE_HEIGHT ) : aBoundRect.GetSize();
+
+ if( ( aDstPtPix.Y() + aDstSzPix.Height() - 1 ) > aBoundRect.Bottom() )
+ aDstSzPix.setHeight( aBoundRect.Bottom() - aDstPtPix.Y() + 1 );
+
+ while( aDstPtPix.X() <= aBoundRect.Right() )
+ {
+ if( ( aDstPtPix.X() + aDstSzPix.Width() - 1 ) > aBoundRect.Right() )
+ aDstSzPix.setWidth( aBoundRect.Right() - aDstPtPix.X() + 1 );
+
+ if( !tools::Rectangle( aDstPtPix, aDstSzPix ).Intersection( aBoundRect ).IsEmpty() &&
+ aPaintVDev->SetOutputSizePixel( aDstSzPix ) )
+ {
+ aPaintVDev->Push();
+ aMapVDev->Push();
+
+ aMapVDev->mnDPIX = aPaintVDev->mnDPIX = mnDPIX;
+ aMapVDev->mnDPIY = aPaintVDev->mnDPIY = mnDPIY;
+
+ aPaintVDev->EnableOutput(false);
+
+ // iterate over all actions
+ for( pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(), nActionNum=0;
+ pCurrAct;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum )
+ {
+ // enable output only for
+ // actions that are members of
+ // the current aCCList element
+ // (currentItem)
+ if( aCCList_MemberMap[nActionNum] == &currentItem )
+ aPaintVDev->EnableOutput();
+
+ // but process every action
+ const MetaActionType nType( pCurrAct->GetType() );
+
+ if( MetaActionType::MAPMODE == nType )
+ {
+ pCurrAct->Execute( aMapVDev.get() );
+
+ MapMode aMtfMap( aMapVDev->GetMapMode() );
+ const Point aNewOrg( aMapVDev->PixelToLogic( aDstPtPix ) );
+
+ aMtfMap.SetOrigin( Point( -aNewOrg.X(), -aNewOrg.Y() ) );
+ aPaintVDev->SetMapMode( aMtfMap );
+ }
+ else if( ( MetaActionType::PUSH == nType ) || MetaActionType::POP == nType )
+ {
+ pCurrAct->Execute( aMapVDev.get() );
+ pCurrAct->Execute( aPaintVDev.get() );
+ }
+ else if( MetaActionType::GRADIENT == nType )
+ {
+ MetaGradientAction* pGradientAction = static_cast<MetaGradientAction*>(pCurrAct);
+ Printer* pPrinter = dynamic_cast< Printer* >(this);
+ if( pPrinter )
+ pPrinter->DrawGradientEx( aPaintVDev.get(), pGradientAction->GetRect(), pGradientAction->GetGradient() );
+ else
+ DrawGradient( pGradientAction->GetRect(), pGradientAction->GetGradient() );
+ }
+ else
+ {
+ pCurrAct->Execute( aPaintVDev.get() );
+ }
+
+ Application::Reschedule( true );
+ }
+
+ const bool bOldMap = mbMap;
+ mbMap = aPaintVDev->mbMap = false;
+
+ Bitmap aBandBmp( aPaintVDev->GetBitmap( Point(), aDstSzPix ) );
+
+ // scale down bitmap, if requested
+ if( bDownsampleBitmaps )
+ aBandBmp = vcl::bitmap::GetDownsampledBitmap(PixelToLogic(LogicToPixel(aDstSzPix), MapMode(MapUnit::MapTwip)),
+ Point(), aBandBmp.GetSizePixel(),
+ aBandBmp, nMaxBmpDPIX, nMaxBmpDPIY);
+
+ rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_BEGIN"_ostr ) );
+ rOutMtf.AddAction( new MetaBmpScaleAction( aDstPtPix, aDstSzPix, aBandBmp ) );
+ rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_END"_ostr ) );
+
+ aPaintVDev->mbMap = true;
+ mbMap = bOldMap;
+ aMapVDev->Pop();
+ aPaintVDev->Pop();
+ }
+
+ // overlapping bands to avoid missing lines (e.g. PostScript)
+ aDstPtPix.AdjustX(aDstSzPix.Width() );
+ }
+
+ // overlapping bands to avoid missing lines (e.g. PostScript)
+ aDstPtPix.AdjustY(aDstSzPix.Height() );
+ }
+
+ rOutMtf.AddAction( new MetaPopAction() );
+ }
+ }
+ }
+ }
+
+ aMapModeVDev->ClearStack(); // clean up aMapModeVDev
+
+ // STAGE 4: Copy actions to output metafile
+
+ // iterate over all actions and duplicate the ones not in a
+ // special aCCList member into rOutMtf
+ for( pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(), nActionNum=0;
+ pCurrAct;
+ pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum )
+ {
+ const ConnectedComponents* pCurrAssociatedComponent = aCCList_MemberMap[nActionNum];
+
+ // NOTE: This relies on the fact that map-mode or draw
+ // mode changing actions are solitary aCCList elements and
+ // have empty bounding boxes, see comment on stage 2.1
+ // above
+ if( pCurrAssociatedComponent &&
+ (pCurrAssociatedComponent->aBounds.IsEmpty() ||
+ !pCurrAssociatedComponent->bIsSpecial) )
+ {
+ // #107169# Treat transparent bitmaps special, if they
+ // are the first (or sole) action in their bounds
+ // list. Note that we previously ensured that no
+ // fully-transparent objects are before us here.
+ if( DoesActionHandleTransparency( *pCurrAct ) &&
+ pCurrAssociatedComponent->aComponentList.begin()->first == pCurrAct )
+ {
+ // convert actions, where masked-out parts are of
+ // given background color
+ ImplConvertTransparentAction(rOutMtf,
+ *pCurrAct,
+ *aMapModeVDev,
+ aBackgroundComponent.aBgColor);
+ }
+ else
+ {
+ // simply add this action
+ rOutMtf.AddAction( pCurrAct );
+ }
+
+ pCurrAct->Execute(aMapModeVDev.get());
+ }
+ }
+
+ rOutMtf.SetPrefMapMode( rInMtf.GetPrefMapMode() );
+ rOutMtf.SetPrefSize( rInMtf.GetPrefSize() );
+
+#if OSL_DEBUG_LEVEL > 1
+ // iterate over all aCCList members and generate rectangles for the bounding boxes
+ rOutMtf.AddAction( new MetaFillColorAction( COL_WHITE, false ) );
+ for(auto const& aCurr:aCCList)
+ {
+ if( aCurr.bIsSpecial )
+ rOutMtf.AddAction( new MetaLineColorAction( COL_RED, true) );
+ else
+ rOutMtf.AddAction( new MetaLineColorAction( COL_BLUE, true) );
+
+ rOutMtf.AddAction( new MetaRectAction( aMapModeVDev->PixelToLogic( aCurr.aBounds ) ) );
+ }
+#endif
+ }
+ return bTransparent;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/vclreferencebase.cxx b/vcl/source/outdev/vclreferencebase.cxx
new file mode 100644
index 0000000000..62dd0e67f1
--- /dev/null
+++ b/vcl/source/outdev/vclreferencebase.cxx
@@ -0,0 +1,44 @@
+/* -*- 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/vclreferencebase.hxx>
+
+VclReferenceBase::VclReferenceBase() :
+ mnRefCnt(1), // cf. VclPtrInstance and README.lifecycle
+ mbDisposed(false)
+{
+}
+
+VclReferenceBase::~VclReferenceBase()
+{
+ disposeOnce();
+}
+
+void VclReferenceBase::disposeOnce()
+{
+ if ( mbDisposed )
+ return;
+ mbDisposed = true;
+ dispose();
+}
+
+void VclReferenceBase::dispose()
+{
+}
+
diff --git a/vcl/source/outdev/wallpaper.cxx b/vcl/source/outdev/wallpaper.cxx
new file mode 100644
index 0000000000..2ae7cb2e5a
--- /dev/null
+++ b/vcl/source/outdev/wallpaper.cxx
@@ -0,0 +1,396 @@
+/* -*- 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/metaact.hxx>
+#include <vcl/virdev.hxx>
+
+#include <cassert>
+
+Color OutputDevice::GetReadableFontColor(const Color& rFontColor, const Color& rBgColor) const
+{
+ if (rBgColor.IsDark() && rFontColor.IsDark())
+ return COL_WHITE;
+ else if (rBgColor.IsBright() && rFontColor.IsBright())
+ return COL_BLACK;
+ else
+ return rFontColor;
+}
+
+void OutputDevice::DrawWallpaper( const tools::Rectangle& rRect,
+ const Wallpaper& rWallpaper )
+{
+ assert(!is_double_buffered_window());
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaWallpaperAction( rRect, rWallpaper ) );
+
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ if ( rWallpaper.GetStyle() != WallpaperStyle::NONE )
+ {
+ tools::Rectangle aRect = LogicToPixel( rRect );
+ aRect.Normalize();
+
+ if ( !aRect.IsEmpty() )
+ {
+ DrawWallpaper( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(),
+ rWallpaper );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawWallpaper( rRect, rWallpaper );
+}
+
+void OutputDevice::DrawWallpaper( tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ const Wallpaper& rWallpaper )
+{
+ assert(!is_double_buffered_window());
+
+ if( rWallpaper.IsBitmap() )
+ DrawBitmapWallpaper( nX, nY, nWidth, nHeight, rWallpaper );
+ else if( rWallpaper.IsGradient() )
+ DrawGradientWallpaper( nX, nY, nWidth, nHeight, rWallpaper );
+ else
+ DrawColorWallpaper( nX, nY, nWidth, nHeight, rWallpaper );
+}
+
+void OutputDevice::DrawColorWallpaper( tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ const Wallpaper& rWallpaper )
+{
+ assert(!is_double_buffered_window());
+
+ // draw wallpaper without border
+ Color aOldLineColor = GetLineColor();
+ Color aOldFillColor = GetFillColor();
+ SetLineColor();
+ SetFillColor( rWallpaper.GetColor() );
+
+ bool bMap = mbMap;
+ EnableMapMode( false );
+ DrawRect( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ) );
+ SetLineColor( aOldLineColor );
+ SetFillColor( aOldFillColor );
+ EnableMapMode( bMap );
+}
+
+void OutputDevice::Erase()
+{
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ if ( mbBackground )
+ {
+ RasterOp eRasterOp = GetRasterOp();
+ if ( eRasterOp != RasterOp::OverPaint )
+ SetRasterOp( RasterOp::OverPaint );
+ DrawWallpaper( 0, 0, mnOutWidth, mnOutHeight, maBackground );
+ if ( eRasterOp != RasterOp::OverPaint )
+ SetRasterOp( eRasterOp );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->Erase();
+}
+
+void OutputDevice::Erase(const tools::Rectangle& rRect)
+{
+ const RasterOp eRasterOp = GetRasterOp();
+ if ( eRasterOp != RasterOp::OverPaint )
+ SetRasterOp( RasterOp::OverPaint );
+ DrawWallpaper(rRect, GetBackground());
+ if ( eRasterOp != RasterOp::OverPaint )
+ SetRasterOp( eRasterOp );
+
+ if (mpAlphaVDev)
+ mpAlphaVDev->Erase(rRect);
+}
+
+void OutputDevice::DrawBitmapWallpaper( tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ const Wallpaper& rWallpaper )
+{
+ assert(!is_double_buffered_window());
+
+ const BitmapEx* pCached = rWallpaper.ImplGetCachedBitmap();
+
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ const bool bOldMap = mbMap;
+
+ BitmapEx aBmpEx;
+ if( pCached )
+ aBmpEx = *pCached;
+ else
+ aBmpEx = rWallpaper.GetBitmap();
+
+ const tools::Long nBmpWidth = aBmpEx.GetSizePixel().Width();
+ const tools::Long nBmpHeight = aBmpEx.GetSizePixel().Height();
+ const bool bTransparent = aBmpEx.IsAlpha();
+
+ const WallpaperStyle eStyle = rWallpaper.GetStyle();
+
+ bool bDrawGradientBackground = false;
+ bool bDrawColorBackground = false;
+
+ // draw background
+ if( bTransparent )
+ {
+ if( rWallpaper.IsGradient() )
+ bDrawGradientBackground = true;
+ else
+ {
+ if( !pCached && !rWallpaper.GetColor().IsTransparent() )
+ {
+ ScopedVclPtrInstance< VirtualDevice > aVDev( *this );
+ aVDev->SetBackground( rWallpaper.GetColor() );
+ aVDev->SetOutputSizePixel( Size( nBmpWidth, nBmpHeight ) );
+ aVDev->DrawBitmapEx( Point(), aBmpEx );
+ aBmpEx = aVDev->GetBitmapEx( Point(), aVDev->GetOutputSizePixel() );
+ }
+
+ bDrawColorBackground = true;
+ }
+ }
+ else if( eStyle != WallpaperStyle::Tile && eStyle != WallpaperStyle::Scale )
+ {
+ if( rWallpaper.IsGradient() )
+ bDrawGradientBackground = true;
+ else
+ bDrawColorBackground = true;
+ }
+
+ // background of bitmap?
+ if( bDrawGradientBackground )
+ {
+ DrawGradientWallpaper( nX, nY, nWidth, nHeight, rWallpaper );
+ }
+ else if( bDrawColorBackground && bTransparent )
+ {
+ DrawColorWallpaper( nX, nY, nWidth, nHeight, rWallpaper );
+ bDrawColorBackground = false;
+ }
+
+ Point aPos;
+ Size aSize;
+
+ // calc pos and size
+ if( rWallpaper.IsRect() )
+ {
+ const tools::Rectangle aBound( LogicToPixel( rWallpaper.GetRect() ) );
+ aPos = aBound.TopLeft();
+ aSize = aBound.GetSize();
+ }
+ else
+ {
+ aPos = Point( 0, 0 );
+ aSize = Size( nWidth, nHeight );
+ }
+
+ mpMetaFile = nullptr;
+ EnableMapMode( false );
+ Push( vcl::PushFlags::CLIPREGION );
+ IntersectClipRegion( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ) );
+
+ bool bDrawn = false;
+
+ switch( eStyle )
+ {
+ case WallpaperStyle::Scale:
+ if( !pCached || ( pCached->GetSizePixel() != aSize ) )
+ {
+ if( pCached )
+ rWallpaper.ImplReleaseCachedBitmap();
+
+ aBmpEx = rWallpaper.GetBitmap();
+ aBmpEx.Scale( aSize );
+ aBmpEx = BitmapEx( aBmpEx.GetBitmap().CreateDisplayBitmap( this ), aBmpEx.GetAlphaMask() );
+ }
+ break;
+
+ case WallpaperStyle::TopLeft:
+ break;
+
+ case WallpaperStyle::Top:
+ aPos.AdjustX(( aSize.Width() - nBmpWidth ) >> 1 );
+ break;
+
+ case WallpaperStyle::TopRight:
+ aPos.AdjustX( aSize.Width() - nBmpWidth);
+ break;
+
+ case WallpaperStyle::Left:
+ aPos.AdjustY(( aSize.Height() - nBmpHeight ) >> 1 );
+ break;
+
+ case WallpaperStyle::Center:
+ aPos.AdjustX(( aSize.Width() - nBmpWidth ) >> 1 );
+ aPos.AdjustY(( aSize.Height() - nBmpHeight ) >> 1 );
+ break;
+
+ case WallpaperStyle::Right:
+ aPos.AdjustX(aSize.Width() - nBmpWidth);
+ aPos.AdjustY(( aSize.Height() - nBmpHeight ) >> 1 );
+ break;
+
+ case WallpaperStyle::BottomLeft:
+ aPos.AdjustY( aSize.Height() - nBmpHeight );
+ break;
+
+ case WallpaperStyle::Bottom:
+ aPos.AdjustX(( aSize.Width() - nBmpWidth ) >> 1 );
+ aPos.AdjustY( aSize.Height() - nBmpHeight );
+ break;
+
+ case WallpaperStyle::BottomRight:
+ aPos.AdjustX( aSize.Width() - nBmpWidth );
+ aPos.AdjustY( aSize.Height() - nBmpHeight );
+ break;
+
+ default:
+ {
+ const tools::Long nRight = nX + nWidth - 1;
+ const tools::Long nBottom = nY + nHeight - 1;
+ tools::Long nFirstX;
+ tools::Long nFirstY;
+
+ if( eStyle == WallpaperStyle::Tile )
+ {
+ nFirstX = aPos.X();
+ nFirstY = aPos.Y();
+ }
+ else
+ {
+ nFirstX = aPos.X() + ( ( aSize.Width() - nBmpWidth ) >> 1 );
+ nFirstY = aPos.Y() + ( ( aSize.Height() - nBmpHeight ) >> 1 );
+ }
+
+ const tools::Long nOffX = ( nFirstX - nX ) % nBmpWidth;
+ const tools::Long nOffY = ( nFirstY - nY ) % nBmpHeight;
+ tools::Long nStartX = nX + nOffX;
+ tools::Long nStartY = nY + nOffY;
+
+ if( nOffX > 0 )
+ nStartX -= nBmpWidth;
+
+ if( nOffY > 0 )
+ nStartY -= nBmpHeight;
+
+ for( tools::Long nBmpY = nStartY; nBmpY <= nBottom; nBmpY += nBmpHeight )
+ {
+ for( tools::Long nBmpX = nStartX; nBmpX <= nRight; nBmpX += nBmpWidth )
+ {
+ DrawBitmapEx( Point( nBmpX, nBmpY ), aBmpEx );
+ }
+ }
+ bDrawn = true;
+ }
+ break;
+ }
+
+ if( !bDrawn )
+ {
+ // optimized for non-transparent bitmaps
+ if( bDrawColorBackground )
+ {
+ const Size aBmpSize( aBmpEx.GetSizePixel() );
+ const Point aTmpPoint;
+ const tools::Rectangle aOutRect( aTmpPoint, GetOutputSizePixel() );
+ const tools::Rectangle aColRect( Point( nX, nY ), Size( nWidth, nHeight ) );
+
+ tools::Rectangle aWorkRect( 0, 0, aOutRect.Right(), aPos.Y() - 1 );
+ aWorkRect.Normalize();
+ aWorkRect.Intersection( aColRect );
+ if( !aWorkRect.IsEmpty() )
+ {
+ DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(),
+ aWorkRect.GetWidth(), aWorkRect.GetHeight(),
+ rWallpaper );
+ }
+
+ aWorkRect = tools::Rectangle( 0, aPos.Y(), aPos.X() - 1, aPos.Y() + aBmpSize.Height() - 1 );
+ aWorkRect.Normalize();
+ aWorkRect.Intersection( aColRect );
+ if( !aWorkRect.IsEmpty() )
+ {
+ DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(),
+ aWorkRect.GetWidth(), aWorkRect.GetHeight(),
+ rWallpaper );
+ }
+
+ aWorkRect = tools::Rectangle( aPos.X() + aBmpSize.Width(), aPos.Y(),
+ aOutRect.Right(), aPos.Y() + aBmpSize.Height() - 1 );
+ aWorkRect.Normalize();
+ aWorkRect.Intersection( aColRect );
+ if( !aWorkRect.IsEmpty() )
+ {
+ DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(),
+ aWorkRect.GetWidth(), aWorkRect.GetHeight(),
+ rWallpaper );
+ }
+
+ aWorkRect = tools::Rectangle( 0, aPos.Y() + aBmpSize.Height(),
+ aOutRect.Right(), aOutRect.Bottom() );
+ aWorkRect.Normalize();
+ aWorkRect.Intersection( aColRect );
+ if( !aWorkRect.IsEmpty() )
+ {
+ DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(),
+ aWorkRect.GetWidth(), aWorkRect.GetHeight(),
+ rWallpaper );
+ }
+ }
+
+ DrawBitmapEx( aPos, aBmpEx );
+ }
+
+ rWallpaper.ImplSetCachedBitmap( aBmpEx );
+
+ Pop();
+ EnableMapMode( bOldMap );
+ mpMetaFile = pOldMetaFile;
+}
+
+void OutputDevice::DrawGradientWallpaper( tools::Long nX, tools::Long nY,
+ tools::Long nWidth, tools::Long nHeight,
+ const Wallpaper& rWallpaper )
+{
+ assert(!is_double_buffered_window());
+
+ tools::Rectangle aBound;
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ const bool bOldMap = mbMap;
+
+ aBound = tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) );
+
+ mpMetaFile = nullptr;
+ EnableMapMode( false );
+ Push( vcl::PushFlags::CLIPREGION );
+ IntersectClipRegion( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ) );
+
+ DrawGradient( aBound, rWallpaper.GetGradient() );
+
+ Pop();
+ EnableMapMode( bOldMap );
+ mpMetaFile = pOldMetaFile;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */