summaryrefslogtreecommitdiffstats
path: root/vcl/source/outdev
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /vcl/source/outdev
parentInitial commit. (diff)
downloadlibreoffice-upstream.tar.xz
libreoffice-upstream.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--vcl/source/outdev/bitmap.cxx1788
-rw-r--r--vcl/source/outdev/clipping.cxx226
-rw-r--r--vcl/source/outdev/curvedshapes.cxx210
-rw-r--r--vcl/source/outdev/font.cxx1435
-rw-r--r--vcl/source/outdev/gradient.cxx1045
-rw-r--r--vcl/source/outdev/hatch.cxx409
-rw-r--r--vcl/source/outdev/line.cxx304
-rw-r--r--vcl/source/outdev/map.cxx1936
-rw-r--r--vcl/source/outdev/mask.cxx154
-rw-r--r--vcl/source/outdev/nativecontrols.cxx356
-rw-r--r--vcl/source/outdev/outdev.cxx707
-rw-r--r--vcl/source/outdev/outdevstate.cxx609
-rw-r--r--vcl/source/outdev/pixel.cxx116
-rw-r--r--vcl/source/outdev/polygon.cxx518
-rw-r--r--vcl/source/outdev/polyline.cxx386
-rw-r--r--vcl/source/outdev/rect.cxx415
-rw-r--r--vcl/source/outdev/text.cxx2510
-rw-r--r--vcl/source/outdev/textline.cxx1026
-rw-r--r--vcl/source/outdev/transparent.cxx859
-rw-r--r--vcl/source/outdev/vclreferencebase.cxx44
-rw-r--r--vcl/source/outdev/wallpaper.cxx396
21 files changed, 15449 insertions, 0 deletions
diff --git a/vcl/source/outdev/bitmap.cxx b/vcl/source/outdev/bitmap.cxx
new file mode 100644
index 000000000..dcb9a6b1d
--- /dev/null
+++ b/vcl/source/outdev/bitmap.cxx
@@ -0,0 +1,1788 @@
+/* -*- 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 <cassert>
+
+#include <vcl/bitmap.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/BitmapFilterStackBlur.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <config_features.h>
+#if HAVE_FEATURE_OPENGL
+#include <vcl/opengl/OpenGLHelper.hxx>
+#endif
+#include <vcl/skia/SkiaHelper.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/image.hxx>
+#include <vcl/BitmapMonochromeFilter.hxx>
+
+#include <bmpfast.hxx>
+#include <salgdi.hxx>
+#include <salbmp.hxx>
+
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <memory>
+#include <comphelper/lok.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+#include <tools/helpers.hxx>
+#include <tools/debug.hxx>
+#include <rtl/math.hxx>
+
+#include <vcl/dibtools.hxx>
+#include <tools/stream.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, 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( PushFlags::LINECOLOR | PushFlags::FILLCOLOR );
+ SetLineColor( aCol );
+ SetFillColor( aCol );
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ Pop();
+ return;
+ }
+ else if( !!aBmp )
+ {
+ 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;
+
+ 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 )
+ ScaleBitmap (aBmp, aPosAry);
+
+ mpGraphics->DrawBitmap( aPosAry, *aBmp.ImplGetSalBitmap(), this );
+ }
+ }
+ }
+
+ if( mpAlphaVDev )
+ {
+ // #i32109#: Make bitmap area opaque
+ mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) );
+ }
+}
+
+Bitmap OutputDevice::GetDownsampledBitmap( const Size& rDstSz,
+ const Point& rSrcPt, const Size& rSrcSz,
+ const Bitmap& rBmp, long nMaxBmpDPIX, long nMaxBmpDPIY )
+{
+ Bitmap aBmp( rBmp );
+
+ if( !aBmp.IsEmpty() )
+ {
+ const tools::Rectangle aBmpRect( Point(), aBmp.GetSizePixel() );
+ tools::Rectangle aSrcRect( rSrcPt, rSrcSz );
+
+ // do cropping if necessary
+ if( aSrcRect.Intersection( aBmpRect ) != aBmpRect )
+ {
+ if( !aSrcRect.IsEmpty() )
+ aBmp.Crop( aSrcRect );
+ else
+ aBmp.SetEmpty();
+ }
+
+ if( !aBmp.IsEmpty() )
+ {
+ // do downsampling if necessary
+ Size aDstSizeTwip( PixelToLogic(LogicToPixel(rDstSz), MapMode(MapUnit::MapTwip)) );
+
+ // #103209# Normalize size (mirroring has to happen outside of this method)
+ aDstSizeTwip = Size( labs(aDstSizeTwip.Width()), labs(aDstSizeTwip.Height()) );
+
+ const Size aBmpSize( aBmp.GetSizePixel() );
+ const double fBmpPixelX = aBmpSize.Width();
+ const double fBmpPixelY = aBmpSize.Height();
+ const double fMaxPixelX = aDstSizeTwip.Width() * nMaxBmpDPIX / 1440.0;
+ const double fMaxPixelY = aDstSizeTwip.Height() * nMaxBmpDPIY / 1440.0;
+
+ // check, if the bitmap DPI exceeds the maximum DPI (allow 4 pixel rounding tolerance)
+ if( ( ( fBmpPixelX > ( fMaxPixelX + 4 ) ) ||
+ ( fBmpPixelY > ( fMaxPixelY + 4 ) ) ) &&
+ ( fBmpPixelY > 0.0 ) && ( fMaxPixelY > 0.0 ) )
+ {
+ // do scaling
+ Size aNewBmpSize;
+ const double fBmpWH = fBmpPixelX / fBmpPixelY;
+ const double fMaxWH = fMaxPixelX / fMaxPixelY;
+
+ if( fBmpWH < fMaxWH )
+ {
+ aNewBmpSize.setWidth( FRound( fMaxPixelY * fBmpWH ) );
+ aNewBmpSize.setHeight( FRound( fMaxPixelY ) );
+ }
+ else if( fBmpWH > 0.0 )
+ {
+ aNewBmpSize.setWidth( FRound( fMaxPixelX ) );
+ aNewBmpSize.setHeight( FRound( fMaxPixelX / fBmpWH) );
+ }
+
+ if( aNewBmpSize.Width() && aNewBmpSize.Height() )
+ aBmp.Scale( aNewBmpSize );
+ else
+ aBmp.SetEmpty();
+ }
+ }
+ }
+
+ return aBmp;
+}
+
+void OutputDevice::DrawBitmapEx( const Point& rDestPt,
+ const BitmapEx& rBitmapEx )
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if( TransparentType::NONE == rBitmapEx.GetTransparentType() )
+ {
+ 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 ( TransparentType::NONE == rBitmapEx.GetTransparentType() )
+ {
+ 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, const MetaActionType nAction )
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if( TransparentType::NONE == rBitmapEx.GetTransparentType() )
+ {
+ DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx.GetBitmap() );
+ }
+ else
+ {
+ if ( RasterOp::Invert == meRasterOp )
+ {
+ DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
+ return;
+ }
+
+ BitmapEx aBmpEx( rBitmapEx );
+
+ if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap |
+ DrawModeFlags::GrayBitmap ) )
+ {
+ if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap ) )
+ {
+ Bitmap aColorBmp( aBmpEx.GetSizePixel(), 1 );
+ sal_uInt8 cCmpVal;
+
+ if ( mnDrawMode & DrawModeFlags::BlackBitmap )
+ cCmpVal = 0;
+ else
+ cCmpVal = 255;
+
+ aColorBmp.Erase( Color( cCmpVal, cCmpVal, cCmpVal ) );
+
+ if( aBmpEx.IsAlpha() )
+ {
+ // Create one-bit mask out of alpha channel, by
+ // thresholding it at alpha=0.5. As
+ // DRAWMODE_BLACK/WHITEBITMAP requires monochrome
+ // output, having alpha-induced grey levels is not
+ // acceptable.
+ BitmapEx aMaskEx(aBmpEx.GetAlpha().GetBitmap());
+ BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(129));
+ aBmpEx = BitmapEx(aColorBmp, aMaskEx.GetBitmap());
+ }
+ else
+ {
+ aBmpEx = BitmapEx( aColorBmp, aBmpEx.GetMask() );
+ }
+ }
+ else if( !!aBmpEx )
+ {
+ if ( mnDrawMode & DrawModeFlags::GrayBitmap )
+ aBmpEx.Convert( BmpConversion::N8BitGreys );
+ }
+ }
+
+ 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;
+
+ DrawDeviceBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmpEx );
+ }
+}
+
+Bitmap OutputDevice::GetBitmap( const Point& rSrcPt, const Size& rSize ) const
+{
+ Bitmap aBmp;
+ long nX = ImplLogicXToDevicePixel( rSrcPt.X() );
+ long nY = ImplLogicYToDevicePixel( rSrcPt.Y() );
+ long nWidth = ImplLogicWidthToDevicePixel( rSize.Width() );
+ long nHeight = ImplLogicHeightToDevicePixel( rSize.Height() );
+
+ if ( mpGraphics || AcquireGraphics() )
+ {
+ if ( nWidth > 0 && nHeight > 0 && nX <= (mnOutWidth + mnOutOffX) && nY <= (mnOutHeight + mnOutOffY))
+ {
+ 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;
+}
+
+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.GetBitCount() > 8 )
+ aAlphaBitmap.Convert( BmpConversion::N8BitNoConversion );
+
+ return BitmapEx(GetBitmap( rSrcPt, rSize ), AlphaMask( aAlphaBitmap ) );
+ }
+ else
+ return BitmapEx(GetBitmap( rSrcPt, rSize ));
+}
+
+void OutputDevice::DrawDeviceBitmap( 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.GetAlpha(), rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel);
+ }
+ else if (!!rBitmapEx)
+ {
+ 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.ImplGetMaskSalBitmap();
+
+ 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.GetMask(),
+ rBitmapEx.GetMask()));
+ }
+ else
+ {
+ mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, this);
+
+ if (mpAlphaVDev)
+ {
+ // #i32109#: Make bitmap area opaque
+ mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) );
+ }
+ }
+ }
+ }
+}
+
+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);
+
+ if (bHMirr)
+ {
+ aOutSz.setWidth( -aOutSz.Width() );
+ aOutPt.AdjustX( -(aOutSz.Width() - 1) );
+ }
+
+ if (bVMirr)
+ {
+ aOutSz.setHeight( -aOutSz.Height() );
+ aOutPt.AdjustY( -(aOutSz.Height() - 1) );
+ }
+
+ if (!aDstRect.Intersection(tools::Rectangle(aOutPt, aOutSz)).IsEmpty())
+ {
+ static const char* pDisableNative = getenv( "SAL_DISABLE_NATIVE_ALPHA");
+ bool bTryDirectPaint(!pDisableNative && !bHMirr && !bVMirr);
+
+ if (bTryDirectPaint)
+ {
+ Point aRelPt = aOutPt + Point(mnOutOffX, mnOutOffY);
+ SalTwoRect aTR(
+ rSrcPtPixel.X(), rSrcPtPixel.Y(),
+ rSrcSizePixel.Width(), rSrcSizePixel.Height(),
+ aRelPt.X(), aRelPt.Y(),
+ aOutSz.Width(), aOutSz.Height());
+
+ SalBitmap* pSalSrcBmp = rBmp.ImplGetSalBitmap().get();
+ SalBitmap* pSalAlphaBmp = rAlpha.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)
+ {
+ Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aRelPt, aOutSz ) );
+ if (SalBitmap* pSalAlphaBmp2 = aAlphaBitmap.ImplGetSalBitmap().get())
+ {
+ if (mpGraphics->BlendAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, *pSalAlphaBmp2, this))
+ {
+ mpAlphaVDev->BlendBitmap(aTR, rAlpha);
+ return;
+ }
+ }
+ }
+ else
+ {
+ if (mpGraphics->DrawAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, this))
+ return;
+ }
+ }
+
+ // we need to make sure OpenGL never reaches this slow code path
+
+ assert(!SkiaHelper::isVCLSkiaEnabled());
+#if HAVE_FEATURE_OPENGL
+ assert(!OpenGLHelper::isVCLOpenGLEnabled());
+#endif
+ tools::Rectangle aBmpRect(Point(), rBmp.GetSizePixel());
+ if (!aBmpRect.Intersection(tools::Rectangle(rSrcPtPixel, rSrcSizePixel)).IsEmpty())
+ {
+ Point auxOutPt(LogicToPixel(rDestPt));
+ Size auxOutSz(LogicToPixel(rDestSize));
+
+ DrawDeviceAlphaBitmapSlowPath(rBmp, rAlpha, aDstRect, aBmpRect, auxOutSz, auxOutPt);
+ }
+ }
+}
+
+namespace
+{
+
+struct LinearScaleContext
+{
+ std::unique_ptr<long[]> mpMapX;
+ std::unique_ptr<long[]> mpMapY;
+
+ std::unique_ptr<long[]> mpMapXOffset;
+ std::unique_ptr<long[]> mpMapYOffset;
+
+ LinearScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect,
+ Size const & aOutSize, long nOffX, long nOffY)
+
+ : mpMapX(new long[aDstRect.GetWidth()])
+ , mpMapY(new long[aDstRect.GetHeight()])
+ , mpMapXOffset(new long[aDstRect.GetWidth()])
+ , mpMapYOffset(new long[aDstRect.GetHeight()])
+ {
+ const long nSrcWidth = aBitmapRect.GetWidth();
+ const 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(long nSrcDimension, long nDstDimension, long nDstLocation,
+ long nOutDimention, long nOffset, long* pMap, long* pMapOffset)
+ {
+
+ const double fReverseScale = (std::abs(nOutDimention) > 1) ? (nSrcDimension - 1) / double(std::abs(nOutDimention) - 1) : 0.0;
+
+ long nSampleRange = std::max(0L, nSrcDimension - 2);
+
+ for (long i = 0; i < nDstDimension; i++)
+ {
+ double fTemp = std::abs((nOffset + i) * fReverseScale);
+
+ pMap[i] = MinMax(nDstLocation + long(fTemp), 0, nSampleRange);
+ pMapOffset[i] = static_cast<long>((fTemp - pMap[i]) * 128.0);
+ }
+ }
+
+public:
+ bool blendBitmap(
+ const BitmapWriteAccess* pDestination,
+ const BitmapReadAccess* pSource,
+ const BitmapReadAccess* pSourceAlpha,
+ const long nDstWidth,
+ const long nDstHeight)
+ {
+ if (pSource && pSourceAlpha && pDestination)
+ {
+ 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 long nDstWidth,
+ const long nDstHeight)
+ {
+ Scanline pLine0, pLine1;
+ Scanline pLineAlpha0, pLineAlpha1;
+ Scanline pColorSample1, pColorSample2;
+ Scanline pDestScanline;
+
+ long nColor1Line1, nColor2Line1, nColor3Line1;
+ long nColor1Line2, nColor2Line2, nColor3Line2;
+ long nAlphaLine1, nAlphaLine2;
+
+ sal_uInt8 nColor1, nColor2, nColor3, nAlpha;
+
+ for (long nY = 0; nY < nDstHeight; nY++)
+ {
+ const long nMapY = mpMapY[nY];
+ const 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 (long nX = 0; nX < nDstWidth; nX++)
+ {
+ const long nMapX = mpMapX[nX];
+ const long nMapFX = mpMapXOffset[nX];
+
+ pColorSample1 = pLine0 + 3 * nMapX;
+ pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1;
+ nColor1Line1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor2Line1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor3Line1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1 = pLine1 + 3 * nMapX;
+ pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1;
+ nColor1Line2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor2Line2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1++;
+ pColorSample2++;
+ nColor3Line2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1 = pLineAlpha0 + nMapX;
+ pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1;
+ nAlphaLine1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
+
+ pColorSample1 = pLineAlpha1 + nMapX;
+ pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1;
+ nAlphaLine2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<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 = ColorChannelMerge(*pDestScanline, nColor1, nAlpha);
+ pDestScanline++;
+ *pDestScanline = ColorChannelMerge(*pDestScanline, nColor2, nAlpha);
+ pDestScanline++;
+ *pDestScanline = ColorChannelMerge(*pDestScanline, nColor3, nAlpha);
+ pDestScanline++;
+ pDestScanline++;
+ }
+ }
+ }
+};
+
+struct TradScaleContext
+{
+ std::unique_ptr<long[]> mpMapX;
+ std::unique_ptr<long[]> mpMapY;
+
+ TradScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect,
+ Size const & aOutSize, long nOffX, long nOffY)
+
+ : mpMapX(new long[aDstRect.GetWidth()])
+ , mpMapY(new long[aDstRect.GetHeight()])
+ {
+ const long nSrcWidth = aBitmapRect.GetWidth();
+ const 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(long nSrcDimension, long nDstDimension, long nDstLocation,
+ long nOutDimention, long nOffset, bool bMirror, long* pMap)
+ {
+ long nMirrorOffset = 0;
+
+ if (bMirror)
+ nMirrorOffset = (nDstLocation << 1) + nSrcDimension - 1;
+
+ for (long i = 0; i < nDstDimension; ++i, ++nOffset)
+ {
+ pMap[i] = nDstLocation + nOffset * nSrcDimension / nOutDimention;
+ 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 long nDstWidth = aDstRect.GetWidth();
+ const 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 long nOffX = IsRTLEnabled()
+ ? aOutSize.Width() - aDstRect.GetWidth() - (aDstRect.Left() - aOutPoint.X())
+ : aDstRect.Left() - aOutPoint.X();
+
+ const long nOffY = aDstRect.Top() - aOutPoint.Y();
+
+ TradScaleContext aTradContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY);
+
+ Bitmap::ScopedReadAccess pBitmapReadAccess(const_cast<Bitmap&>(rBitmap));
+ AlphaMask::ScopedReadAccess pAlphaReadAccess(const_cast<AlphaMask&>(rAlpha));
+
+ DBG_ASSERT( pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal ||
+ pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitTcMask,
+ "OutputDevice::ImplDrawAlpha(): non-8bit alpha no longer supported!" );
+
+ // #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;
+}
+
+void OutputDevice::ScaleBitmap (Bitmap &rBmp, SalTwoRect &rPosAry)
+{
+ const double nScaleX = rPosAry.mnDestWidth / static_cast<double>( rPosAry.mnSrcWidth );
+ const double nScaleY = rPosAry.mnDestHeight / static_cast<double>( rPosAry.mnSrcHeight );
+
+ // If subsampling, use Bitmap::Scale for subsampling for better quality.
+ if ( nScaleX < 1.0 || nScaleY < 1.0 )
+ {
+ rBmp.Scale ( nScaleX, nScaleY );
+ rPosAry.mnSrcWidth = rPosAry.mnDestWidth;
+ rPosAry.mnSrcHeight = rPosAry.mnDestHeight;
+ }
+}
+
+bool OutputDevice::DrawTransformBitmapExDirect(
+ const basegfx::B2DHomMatrix& aFullTransform,
+ const BitmapEx& rBitmapEx)
+{
+ 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();
+ Bitmap aAlphaBitmap;
+
+ if(rBitmapEx.IsTransparent())
+ {
+ if(rBitmapEx.IsAlpha())
+ {
+ aAlphaBitmap = rBitmapEx.GetAlpha();
+ }
+ else
+ {
+ aAlphaBitmap = rBitmapEx.GetMask();
+ }
+ }
+ else if (mpAlphaVDev)
+ {
+ aAlphaBitmap = Bitmap(rBitmapEx.GetSizePixel(), 1);
+ aAlphaBitmap.Erase(COL_BLACK);
+ }
+
+ SalBitmap* pSalAlphaBmp = aAlphaBitmap.ImplGetSalBitmap().get();
+
+ bDone = mpGraphics->DrawTransformedBitmap(
+ aNull,
+ aTopX,
+ aTopY,
+ *pSalSrcBmp,
+ pSalAlphaBmp,
+ this);
+
+ if (mpAlphaVDev)
+ {
+ // Merge bitmap alpha to alpha device
+ Bitmap aBlack(rBitmapEx.GetSizePixel(), 1);
+ aBlack.Erase(COL_BLACK);
+ mpAlphaVDev->DrawTransformBitmapExDirect(aFullTransform, BitmapEx(aBlack, 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)
+{
+ assert(!is_double_buffered_window());
+
+ if( ImplIsRecordLayout() )
+ return;
+
+ if(rBitmapEx.IsEmpty())
+ 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 the in
+ record-to-metafile case 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
+
+ // MM02 reorganize order: Prefer DrawTransformBitmapExDirect due
+ // to this having evolved and is improved on quite some systems.
+ // Check for exclusion parameters that may prevent using it
+ static bool bAllowPreferDirectPaint(true);
+ const bool bInvert(RasterOp::Invert == meRasterOp);
+ const bool bBitmapChangedColor(mnDrawMode & (DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | DrawModeFlags::GrayBitmap ));
+ const bool bTryDirectPaint(!bInvert && !bBitmapChangedColor && !bMetafile);
+
+ if(bAllowPreferDirectPaint && bTryDirectPaint)
+ {
+ // 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
+ const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rTransformation);
+
+ if(DrawTransformBitmapExDirect(aFullTransform, rBitmapEx))
+ {
+ // we are done
+ 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, rBitmapEx);
+ if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel)
+ {
+ EnableMapMode();
+ aDestPt.Move(-aOrigin.getX(), -aOrigin.getY());
+ }
+ return;
+ }
+
+ // MM02 bAllowPreferDirectPaint may have been false to allow
+ // to specify order of executions, so give bTryDirectPaint a call
+ if(bTryDirectPaint)
+ {
+ // 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
+ const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rTransformation);
+
+ if(DrawTransformBitmapExDirect(aFullTransform, rBitmapEx))
+ {
+ // we are done
+ 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, rBitmapEx);
+ 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(rBitmapEx.GetSizePixel());
+ const double fOrigArea(rOriginalSizePixel.Width() * rOriginalSizePixel.Height() * 0.5);
+ const double fOrigAreaScaled(fOrigArea * 1.44);
+ double fMaximumArea(std::min(4500000.0, std::max(1000000.0, fOrigAreaScaled)));
+ // 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);
+
+ if(!bMetafile)
+ {
+ if ( !TransformAndReduceBitmapExToTargetRange( aFullTransform, aVisibleRange, fMaximumArea ) )
+ return;
+ }
+
+ if(!aVisibleRange.isEmpty())
+ {
+ BitmapEx aTransformed(rBitmapEx);
+
+ // #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.IsTransparent() && (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);
+ }
+
+ // Remove scaling from aFulltransform: we transform due to shearing or rotation, scaling
+ // will happen according to aDestSize.
+ basegfx::B2DVector aFullScale, aFullTranslate;
+ double fFullRotate, fFullShearX;
+ aFullTransform.decompose(aFullScale, aFullTranslate, fFullRotate, fFullShearX);
+ // Require positive scaling, negative scaling would loose horizontal or vertical flip.
+ if (aFullScale.getX() > 0 && aFullScale.getY() > 0)
+ {
+ basegfx::B2DHomMatrix aTransform = basegfx::utils::createScaleB2DHomMatrix(
+ rOriginalSizePixel.getWidth() / aFullScale.getX(),
+ rOriginalSizePixel.getHeight() / aFullScale.getY());
+ aFullTransform *= aTransform;
+ }
+
+ 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, F_2PI);
+ if (fFullRotate < 0)
+ {
+ fFullRotate += F_2PI;
+ }
+ long nAngle10 = basegfx::fround(basegfx::rad2deg(fFullRotate) * 10);
+ 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);
+ }
+}
+
+void OutputDevice::DrawShadowBitmapEx(
+ const BitmapEx& rBitmapEx,
+ ::Color aShadowColor)
+{
+ Bitmap::ScopedReadAccess pReadAccess(const_cast<Bitmap&>(rBitmapEx.maBitmap));
+
+ if(!pReadAccess)
+ return;
+
+ for(long y(0); y < pReadAccess->Height(); y++)
+ {
+ for(long x(0); x < pReadAccess->Width(); x++)
+ {
+ const BitmapColor aColor = pReadAccess->GetColor(y, x);
+ sal_uInt16 nLuminance(static_cast<sal_uInt16>(aColor.GetLuminance()) + 1);
+ const Color aDestColor(
+ static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetRed())) >> 8),
+ static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetGreen())) >> 8),
+ static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetBlue())) >> 8));
+ DrawPixel(Point(x,y), aDestColor);
+ }
+ }
+}
+
+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 long nMapX,
+ const 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 );
+
+ // vcl stores transparency, not alpha - invert it
+ const sal_uInt8 nSrcAlpha = 255 - pA->GetPixelIndex( nMapY, nMapX );
+ const sal_uInt8 nDstAlpha = 255 - 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 long* pMapX,
+ const long* 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(), 8 );
+ BitmapColor aIndex( 0 );
+ Bitmap::ScopedReadAccess pB(aBmp);
+ BitmapScopedWriteAccess pW(aDither);
+
+ if (pB && pP && pA && pW && pAlphaW)
+ {
+ int nOutY;
+
+ for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ )
+ {
+ const long nMapY = pMapY[ nY ];
+ const 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 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[ 255-nResAlpha ] + nD ) >> 16 ] +
+ nVCLGLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] +
+ nVCLBLut[ ( nVCLLut[ 255-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 long nMapY = pMapY[ nY ];
+ Scanline pScanlineB = pB->GetScanline(nY);
+ Scanline pScanlineAlpha = pAlphaW->GetScanline(nY);
+
+ for( nX = 0; nX < nDstWidth; nX++ )
+ {
+ const 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(255L-nResAlpha, 255L-nResAlpha, 255L-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 long* pMapX,
+ const long* pMapY )
+{
+ BitmapColor aDstCol;
+ Bitmap res;
+ int nX, nY;
+
+ if( GetBitCount() <= 8 )
+ {
+ Bitmap aDither( aBmp.GetSizePixel(), 8 );
+ BitmapColor aIndex( 0 );
+ Bitmap::ScopedReadAccess pB(aBmp);
+ BitmapScopedWriteAccess pW(aDither);
+
+ if( pB && pP && pA && pW )
+ {
+ int nOutY;
+
+ for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ )
+ {
+ long nMapY = pMapY[ nY ];
+ if (bVMirr)
+ {
+ nMapY = aBmpRect.Bottom() - nMapY;
+ }
+ const long nModY = ( nOutY & 0x0FL ) << 4;
+ int nOutX;
+
+ Scanline pScanline = pW->GetScanline(nY);
+ Scanline pScanlineAlpha = pA->GetScanline(nMapY);
+ for( nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ )
+ {
+ long nMapX = pMapX[ nX ];
+ if (bHMirr)
+ {
+ nMapX = aBmpRect.Right() - nMapX;
+ }
+ const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ];
+
+ aDstCol = pB->GetColor( nY, nX );
+ aDstCol.Merge( pP->GetColor( nMapY, nMapX ), 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();
+ res = aDither;
+ }
+ else
+ {
+ BitmapScopedWriteAccess pB(aBmp);
+
+ bool bFastBlend = false;
+ if( pP && pA && pB && !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( pP && pA && pB && !bFastBlend )
+ {
+ switch( pP->GetScanlineFormat() )
+ {
+ case ScanlineFormat::N8BitPal:
+ {
+ for( nY = 0; nY < nDstHeight; nY++ )
+ {
+ long nMapY = pMapY[ nY ];
+ if ( bVMirr )
+ {
+ nMapY = aBmpRect.Bottom() - nMapY;
+ }
+ Scanline pPScan = pP->GetScanline( nMapY );
+ Scanline pAScan = pA->GetScanline( nMapY );
+ Scanline pBScan = pB->GetScanline( nY );
+
+ for( nX = 0; nX < nDstWidth; nX++ )
+ {
+ long nMapX = pMapX[ nX ];
+
+ if ( bHMirr )
+ {
+ nMapX = aBmpRect.Right() - nMapX;
+ }
+ aDstCol = pB->GetPixelFromData( pBScan, nX );
+ aDstCol.Merge( pP->GetPaletteColor( pPScan[ nMapX ] ), pAScan[ nMapX ] );
+ pB->SetPixelOnData( pBScan, nX, aDstCol );
+ }
+ }
+ }
+ break;
+
+ default:
+ {
+
+ for( nY = 0; nY < nDstHeight; nY++ )
+ {
+ long nMapY = pMapY[ nY ];
+
+ if ( bVMirr )
+ {
+ nMapY = aBmpRect.Bottom() - nMapY;
+ }
+ Scanline pAScan = pA->GetScanline( nMapY );
+ Scanline pBScan = pB->GetScanline(nY);
+ for( nX = 0; nX < nDstWidth; nX++ )
+ {
+ long nMapX = pMapX[ nX ];
+
+ if ( bHMirr )
+ {
+ nMapX = aBmpRect.Right() - nMapX;
+ }
+ aDstCol = pB->GetPixelFromData( pBScan, nX );
+ aDstCol.Merge( pP->GetColor( nMapY, nMapX ), pAScan[ nMapX ] );
+ pB->SetPixelOnData( pBScan, nX, aDstCol );
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ pB.reset();
+ res = aBmp;
+ }
+
+ return res;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/clipping.cxx b/vcl/source/outdev/clipping.cxx
new file mode 100644
index 000000000..1c6e4551c
--- /dev/null
+++ b/vcl/source/outdev/clipping.cxx
@@ -0,0 +1,226 @@
+/* -*- 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 <vcl/gdimtf.hxx>
+#include <vcl/outdev.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;
+ pGraphics = mpGraphics;
+ }
+
+ bool bClipRegion = pGraphics->SetClipRegion( rRegion, this );
+ OSL_ENSURE( bClipRegion, "OutputDevice::SelectClipRegion() - can't create region" );
+ return bClipRegion;
+}
+
+void OutputDevice::MoveClipRegion( long nHorzMove, 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 000000000..d25d69ad1
--- /dev/null
+++ b/vcl/source/outdev/curvedshapes.cxx
@@ -0,0 +1,210 @@
+/* -*- 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 <cassert>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+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;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ tools::Polygon aRectPoly( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 );
+ if ( aRectPoly.GetSize() >= 2 )
+ {
+ SalPoint* pPtAry = reinterpret_cast<SalPoint*>(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;
+
+ 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 )
+ {
+ SalPoint* pPtAry = reinterpret_cast<SalPoint*>(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;
+
+ 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 )
+ {
+ SalPoint* pPtAry = reinterpret_cast<SalPoint*>(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;
+
+ 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 )
+ {
+ SalPoint* pPtAry = reinterpret_cast<SalPoint*>(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/font.cxx b/vcl/source/outdev/font.cxx
new file mode 100644
index 000000000..cf50830dc
--- /dev/null
+++ b/vcl/source/outdev/font.cxx
@@ -0,0 +1,1435 @@
+/* -*- 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 <i18nlangtag/mslangid.hxx>
+#include <i18nlangtag/lang.h>
+
+#include <unotools/configmgr.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/print.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/fontcharmap.hxx>
+#include <vcl/event.hxx>
+#include <font/FeatureCollector.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/debug.hxx>
+
+#include <sallayout.hxx>
+#include <salgdi.hxx>
+#include <svdata.hxx>
+#include <impglyphitem.hxx>
+
+#include <outdev.h>
+#include <window.h>
+
+#include <PhysicalFontCollection.hxx>
+
+#include <strings.hrc>
+
+FontMetric OutputDevice::GetDevFont( int nDevFontIndex ) const
+{
+ FontMetric aFontMetric;
+
+ ImplInitFontList();
+
+ int nCount = GetDevFontCount();
+ if( nDevFontIndex < nCount )
+ {
+ const PhysicalFontFace& rData = *mpDeviceFontList->Get( nDevFontIndex );
+ aFontMetric.SetFamilyName( rData.GetFamilyName() );
+ aFontMetric.SetStyleName( rData.GetStyleName() );
+ aFontMetric.SetCharSet( rData.GetCharSet() );
+ aFontMetric.SetFamily( rData.GetFamilyType() );
+ aFontMetric.SetPitch( rData.GetPitch() );
+ aFontMetric.SetWeight( rData.GetWeight() );
+ aFontMetric.SetItalic( rData.GetItalic() );
+ aFontMetric.SetAlignment( TextAlign::ALIGN_TOP );
+ aFontMetric.SetWidthType( rData.GetWidthType() );
+ aFontMetric.SetQuality( rData.GetQuality() );
+ }
+
+ return aFontMetric;
+}
+
+int OutputDevice::GetDevFontCount() const
+{
+ if( !mpDeviceFontList )
+ {
+ if (!mxFontCollection)
+ {
+ return 0;
+ }
+
+ mpDeviceFontList = mxFontCollection->GetDeviceFontList();
+
+ if (!mpDeviceFontList->Count())
+ {
+ mpDeviceFontList.reset();
+ return 0;
+ }
+ }
+ return mpDeviceFontList->Count();
+}
+
+bool OutputDevice::IsFontAvailable( const OUString& rFontName ) const
+{
+ ImplInitFontList();
+ PhysicalFontFamily* pFound = mxFontCollection->FindFontFamily( rFontName );
+ return (pFound != nullptr);
+}
+
+int OutputDevice::GetDevFontSizeCount( const vcl::Font& rFont ) const
+{
+ mpDeviceFontSizeList.reset();
+
+ ImplInitFontList();
+ mpDeviceFontSizeList = mxFontCollection->GetDeviceFontSizeList( rFont.GetFamilyName() );
+ return mpDeviceFontSizeList->Count();
+}
+
+Size OutputDevice::GetDevFontSize( const vcl::Font& rFont, int nSizeIndex ) const
+{
+ // check range
+ int nCount = GetDevFontSizeCount( rFont );
+ if ( nSizeIndex >= nCount )
+ return Size();
+
+ // when mapping is enabled round to .5 points
+ Size aSize( 0, mpDeviceFontSizeList->Get( nSizeIndex ) );
+ if ( mbMap )
+ {
+ aSize.setHeight( aSize.Height() * 10 );
+ MapMode aMap( MapUnit::Map10thInch, Point(), Fraction( 1, 72 ), Fraction( 1, 72 ) );
+ aSize = PixelToLogic( aSize, aMap );
+ aSize.AdjustHeight(5 );
+ aSize.setHeight( aSize.Height() / 10 );
+ long nRound = aSize.Height() % 5;
+ if ( nRound >= 3 )
+ aSize.AdjustHeight(5-nRound);
+ else
+ aSize.AdjustHeight( -nRound );
+ aSize.setHeight( aSize.Height() * 10 );
+ aSize = LogicToPixel( aSize, aMap );
+ aSize = PixelToLogic( aSize );
+ aSize.AdjustHeight(5 );
+ aSize.setHeight( aSize.Height() / 10 );
+ }
+ return aSize;
+}
+
+bool OutputDevice::AddTempDevFont( const OUString& rFileURL, const OUString& rFontName )
+{
+ ImplInitFontList();
+
+ if( !mpGraphics && !AcquireGraphics() )
+ return false;
+
+ 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;
+
+ hb_font_t* pHbFont = pFontInstance->GetHbFont();
+ if (!pHbFont)
+ return false;
+
+ hb_face_t* pHbFace = hb_font_get_face(pHbFont);
+ if (!pHbFace)
+ return false;
+
+ const LanguageType eOfficeLanguage = Application::GetSettings().GetLanguageTag().getLanguageType();
+
+ vcl::font::FeatureCollector aFeatureCollector(pHbFace, rFontFeatures, eOfficeLanguage);
+ aFeatureCollector.collect();
+
+ return true;
+}
+
+FontMetric OutputDevice::GetFontMetric() const
+{
+ FontMetric aMetric;
+ if (!ImplNewFont())
+ return aMetric;
+
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ ImplFontMetricDataRef 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->IsSymbolFont() ? 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() ) );
+
+ // 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);
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+SystemFontData OutputDevice::GetSysFontData(int nFallbacklevel) const
+{
+ SystemFontData aSysFontData;
+
+ if (!mpGraphics)
+ (void) AcquireGraphics();
+
+ if (mpGraphics)
+ aSysFontData = mpGraphics->GetSysFontData(nFallbacklevel);
+
+ return aSysFontData;
+}
+
+#endif // ENABLE_CAIRO_CANVAS
+
+void OutputDevice::ImplGetEmphasisMark( tools::PolyPolygon& rPolyPoly, bool& rPolyLine,
+ tools::Rectangle& rRect1, tools::Rectangle& rRect2,
+ long& rYOff, long& rWidth,
+ FontEmphasisMark eEmphasis,
+ long nHeight )
+{
+ static const PolyFlags aAccentPolyFlags[24] =
+ {
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Normal, PolyFlags::Control,
+ PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control
+ };
+
+ static const long aAccentPos[48] =
+ {
+ 78, 0,
+ 348, 79,
+ 599, 235,
+ 843, 469,
+ 938, 574,
+ 990, 669,
+ 990, 773,
+ 990, 843,
+ 964, 895,
+ 921, 947,
+ 886, 982,
+ 860, 999,
+ 825, 999,
+ 764, 999,
+ 721, 964,
+ 686, 895,
+ 625, 791,
+ 556, 660,
+ 469, 504,
+ 400, 400,
+ 261, 252,
+ 61, 61,
+ 0, 27,
+ 9, 0
+ };
+
+ rWidth = 0;
+ rYOff = 0;
+ rPolyLine = false;
+
+ if ( !nHeight )
+ return;
+
+ FontEmphasisMark nEmphasisStyle = eEmphasis & FontEmphasisMark::Style;
+ long nDotSize = 0;
+ switch ( nEmphasisStyle )
+ {
+ case FontEmphasisMark::Dot:
+ // Dot has 55% of the height
+ nDotSize = (nHeight*550)/1000;
+ if ( !nDotSize )
+ nDotSize = 1;
+ if ( nDotSize <= 2 )
+ rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ else
+ {
+ long nRad = nDotSize/2;
+ tools::Polygon aPoly( Point( nRad, nRad ), nRad, nRad );
+ rPolyPoly.Insert( aPoly );
+ }
+ rYOff = ((nHeight*250)/1000)/2; // Center to the another EmphasisMarks
+ rWidth = nDotSize;
+ break;
+
+ case FontEmphasisMark::Circle:
+ // Dot has 80% of the height
+ nDotSize = (nHeight*800)/1000;
+ if ( !nDotSize )
+ nDotSize = 1;
+ if ( nDotSize <= 2 )
+ rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ else
+ {
+ long nRad = nDotSize/2;
+ tools::Polygon aPoly( Point( nRad, nRad ), nRad, nRad );
+ rPolyPoly.Insert( aPoly );
+ // BorderWidth is 15%
+ long nBorder = (nDotSize*150)/1000;
+ if ( nBorder <= 1 )
+ rPolyLine = true;
+ else
+ {
+ tools::Polygon aPoly2( Point( nRad, nRad ),
+ nRad-nBorder, nRad-nBorder );
+ rPolyPoly.Insert( aPoly2 );
+ }
+ }
+ rWidth = nDotSize;
+ break;
+
+ case FontEmphasisMark::Disc:
+ // Dot has 80% of the height
+ nDotSize = (nHeight*800)/1000;
+ if ( !nDotSize )
+ nDotSize = 1;
+ if ( nDotSize <= 2 )
+ rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ else
+ {
+ long nRad = nDotSize/2;
+ tools::Polygon aPoly( Point( nRad, nRad ), nRad, nRad );
+ rPolyPoly.Insert( aPoly );
+ }
+ rWidth = nDotSize;
+ break;
+
+ case FontEmphasisMark::Accent:
+ // Dot has 80% of the height
+ nDotSize = (nHeight*800)/1000;
+ if ( !nDotSize )
+ nDotSize = 1;
+ if ( nDotSize <= 2 )
+ {
+ if ( nDotSize == 1 )
+ {
+ rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) );
+ rWidth = nDotSize;
+ }
+ else
+ {
+ rRect1 = tools::Rectangle( Point(), Size( 1, 1 ) );
+ rRect2 = tools::Rectangle( Point( 1, 1 ), Size( 1, 1 ) );
+ }
+ }
+ else
+ {
+ tools::Polygon aPoly( SAL_N_ELEMENTS( aAccentPos ) / 2,
+ reinterpret_cast<const Point*>(aAccentPos),
+ aAccentPolyFlags );
+ double dScale = static_cast<double>(nDotSize)/1000.0;
+ aPoly.Scale( dScale, dScale );
+ tools::Polygon aTemp;
+ aPoly.AdaptiveSubdivide( aTemp );
+ tools::Rectangle aBoundRect = aTemp.GetBoundRect();
+ rWidth = aBoundRect.GetWidth();
+ nDotSize = aBoundRect.GetHeight();
+ rPolyPoly.Insert( aTemp );
+ }
+ break;
+ default: break;
+ }
+
+ // calculate position
+ long nOffY = 1+(mnDPIY/300); // one visible pixel space
+ long nSpaceY = nHeight-nDotSize;
+ if ( nSpaceY >= nOffY*2 )
+ rYOff += nOffY;
+ if ( !(eEmphasis & FontEmphasisMark::PosBelow) )
+ rYOff += nDotSize;
+}
+
+FontEmphasisMark OutputDevice::ImplGetEmphasisMarkStyle( const vcl::Font& rFont )
+{
+ FontEmphasisMark nEmphasisMark = rFont.GetEmphasisMark();
+
+ // If no Position is set, then calculate the default position, which
+ // depends on the language
+ if ( !(nEmphasisMark & (FontEmphasisMark::PosAbove | FontEmphasisMark::PosBelow)) )
+ {
+ LanguageType eLang = rFont.GetLanguage();
+ // In Chinese Simplified the EmphasisMarks are below/left
+ if (MsLangId::isSimplifiedChinese(eLang))
+ nEmphasisMark |= FontEmphasisMark::PosBelow;
+ else
+ {
+ eLang = rFont.GetCJKContextLanguage();
+ // In Chinese Simplified the EmphasisMarks are below/left
+ if (MsLangId::isSimplifiedChinese(eLang))
+ nEmphasisMark |= FontEmphasisMark::PosBelow;
+ else
+ nEmphasisMark |= FontEmphasisMark::PosAbove;
+ }
+ }
+
+ return nEmphasisMark;
+}
+
+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 )
+ {
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.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 )
+ {
+ pSVData->maGDIData.mxScreenFontList->Clear();
+ vcl::Window * pFrame = pSVData->maFrameData.mpFirstFrame;
+ if ( pFrame )
+ {
+ if ( pFrame->AcquireGraphics() )
+ {
+ OutputDevice *pDevice = pFrame;
+ 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->*pHdl )( bNewFontLists );
+
+ vcl::Window* pSysWin = pFrame->mpWindowImpl->mpFrameData->mpFirstOverlap;
+ while ( pSysWin )
+ {
+ ( pSysWin->*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 )
+{
+ ImplDirectFontSubstitution*& rpSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst;
+ if( !rpSubst )
+ rpSubst = new ImplDirectFontSubstitution;
+ rpSubst->AddFontSubstitute( rFontName, rReplaceFontName, nFlags );
+ ImplGetSVData()->maGDIData.mbFontSubChanged = true;
+}
+
+void ImplDirectFontSubstitution::AddFontSubstitute( const OUString& rFontName,
+ const OUString& rSubstFontName, AddFontSubstituteFlags nFlags )
+{
+ maFontSubstList.emplace_back( rFontName, rSubstFontName, nFlags );
+}
+
+ImplFontSubstEntry::ImplFontSubstEntry( const OUString& rFontName,
+ const OUString& rSubstFontName, AddFontSubstituteFlags nSubstFlags )
+: mnFlags( nSubstFlags )
+{
+ maSearchName = GetEnglishSearchFontName( rFontName );
+ maSearchReplaceName = GetEnglishSearchFontName( rSubstFontName );
+}
+
+void OutputDevice::RemoveFontsSubstitute()
+{
+ ImplDirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst;
+ if( pSubst )
+ pSubst->RemoveFontsSubstitute();
+}
+
+void ImplDirectFontSubstitution::RemoveFontsSubstitute()
+{
+ maFontSubstList.clear();
+}
+
+bool ImplDirectFontSubstitution::FindFontSubstitute( OUString& rSubstName,
+ const OUString& rSearchName ) const
+{
+ // TODO: get rid of O(N) searches
+ std::vector<ImplFontSubstEntry>::const_iterator it = std::find_if (
+ maFontSubstList.begin(), maFontSubstList.end(),
+ [&] (const ImplFontSubstEntry& s) { return (s.mnFlags & AddFontSubstituteFlags::ALWAYS)
+ && (s.maSearchName == rSearchName); } );
+ if (it != maFontSubstList.end())
+ {
+ rSubstName = it->maSearchReplaceName;
+ return true;
+ }
+ return false;
+}
+
+void ImplFontSubstitute( OUString& rFontName )
+{
+ // must be canonicalised
+ assert( GetEnglishSearchFontName( rFontName ) == rFontName );
+
+ OUString aSubstFontName;
+
+ // apply user-configurable font replacement (eg, from the list in Tools->Options)
+ const ImplDirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst;
+ if( pSubst && pSubst->FindFontSubstitute( aSubstFontName, rFontName ) )
+ {
+ rFontName = aSubstFontName;
+ return;
+ }
+}
+
+//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 )
+{
+ if (!pOutDev && !utl::ConfigManager::IsFuzzing()) // default is NULL
+ pOutDev = Application::GetDefaultDevice();
+
+ OUString aSearch;
+ if (!utl::ConfigManager::IsFuzzing())
+ {
+ 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
+ }
+ 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
+ {
+ 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() )
+ {
+ if( mpGraphics || AcquireGraphics() )
+ {
+ 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;
+
+ 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();
+
+ // get correct font list on the PDF writer if necessary
+ if (GetOutDevType() == OUTDEV_PDF)
+ {
+ const ImplSVData* pSVData = ImplGetSVData();
+ if( mxFontCollection == pSVData->maGDIData.mxScreenFontList
+ || mxFontCache == pSVData->maGDIData.mxScreenFontCache )
+ const_cast<OutputDevice&>(*this).ImplUpdateFontData();
+ }
+
+ if ( !mbNewFont )
+ return true;
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ {
+ SAL_WARN("vcl.gdi", "OutputDevice::ImplNewFont(): no Graphics, no Font");
+ return false;
+ }
+
+ ImplInitFontList();
+
+ // convert to pixel height
+ // TODO: replace integer based aSize completely with subpixel accurate type
+ float fExactHeight = ImplFloatLogicHeightToDevicePixel( static_cast<float>(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( sal::static_int_cast<short>(mpFontInstance->GetFontSelectPattern().mnOrientation) );
+ mpGraphics->GetFontMetric( pFontInstance->mxFontMetric, 0 );
+
+ pFontInstance->mxFontMetric->ImplInitTextLineSize( this );
+ pFontInstance->mxFontMetric->ImplInitAboveTextLineSize();
+ 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 = ImplGetEmphasisMarkStyle( maFont );
+ 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() )
+ {
+ int nOrigWidth = pFontInstance->mxFontMetric->GetWidth();
+ float fStretch = static_cast<float>(maMapRes.mnMapScNumX) * maMapRes.mnMapScDenomY;
+ fStretch /= static_cast<float>(maMapRes.mnMapScNumY) * maMapRes.mnMapScDenomX;
+ int nNewWidth = static_cast<int>(nOrigWidth * fStretch + 0.5);
+ if( (nNewWidth != nOrigWidth) && (nNewWidth != 0) )
+ {
+ Size aOrigSize = maFont.GetFontSize();
+ const_cast<vcl::Font&>(maFont).SetFontSize( Size( nNewWidth, aSize.Height() ) );
+ mbMap = false;
+ mbNewFont = true;
+ bRet = ImplNewFont(); // recurse once using stretched width
+ mbMap = true;
+ const_cast<vcl::Font&>(maFont).SetFontSize( aOrigSize );
+ }
+ }
+
+ return bRet;
+}
+
+void OutputDevice::SetFontOrientation( LogicalFontInstance* const pFontInstance ) const
+{
+ if( pFontInstance->GetFontSelectPattern().mnOrientation && !pFontInstance->mxFontMetric->GetOrientation() )
+ {
+ pFontInstance->mnOwnOrientation = sal::static_int_cast<short>(pFontInstance->GetFontSelectPattern().mnOrientation);
+ pFontInstance->mnOrientation = pFontInstance->mnOwnOrientation;
+ }
+ else
+ {
+ pFontInstance->mnOrientation = pFontInstance->mxFontMetric->GetOrientation();
+ }
+}
+
+void OutputDevice::ImplDrawEmphasisMark( long nBaseX, long nX, 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 = ImplGetEmphasisMarkStyle( maFont );
+ tools::PolyPolygon aPolyPoly;
+ tools::Rectangle aRect1;
+ tools::Rectangle aRect2;
+ long nEmphasisYOff;
+ long nEmphasisWidth;
+ long nEmphasisHeight;
+ bool bPolyLine;
+
+ if ( nEmphasisMark & FontEmphasisMark::PosBelow )
+ nEmphasisHeight = mnEmphasisDescent;
+ else
+ nEmphasisHeight = mnEmphasisAscent;
+
+ ImplGetEmphasisMark( aPolyPoly, bPolyLine,
+ aRect1, aRect2,
+ nEmphasisYOff, nEmphasisWidth,
+ nEmphasisMark,
+ nEmphasisHeight );
+
+ if ( bPolyLine )
+ {
+ SetLineColor( GetTextColor() );
+ SetFillColor();
+ }
+ else
+ {
+ SetLineColor();
+ SetFillColor( GetTextColor() );
+ }
+
+ Point aOffset(0,0);
+
+ if ( nEmphasisMark & FontEmphasisMark::PosBelow )
+ aOffset.AdjustY(mpFontInstance->mxFontMetric->GetDescent() + nEmphasisYOff );
+ else
+ aOffset.AdjustY( -(mpFontInstance->mxFontMetric->GetAscent() + nEmphasisYOff) );
+
+ long nEmphasisWidth2 = nEmphasisWidth / 2;
+ long nEmphasisHeight2 = nEmphasisHeight / 2;
+ aOffset += Point( nEmphasisWidth2, nEmphasisHeight2 );
+
+ Point aOutPoint;
+ tools::Rectangle aRectangle;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ while (rSalLayout.GetNextGlyph(&pGlyph, aOutPoint, nStart))
+ {
+ if (!pGlyph->GetGlyphBoundRect(aRectangle))
+ continue;
+
+ if (!pGlyph->IsSpacing())
+ {
+ Point aAdjPoint = aOffset;
+ aAdjPoint.AdjustX(aRectangle.Left() + (aRectangle.GetWidth() - nEmphasisWidth) / 2 );
+ if ( mpFontInstance->mnOrientation )
+ {
+ Point aOriginPt(0, 0);
+ aOriginPt.RotateAround( aAdjPoint, mpFontInstance->mnOrientation );
+ }
+ aOutPoint += aAdjPoint;
+ aOutPoint -= Point( nEmphasisWidth2, nEmphasisHeight2 );
+ ImplDrawEmphasisMark( rSalLayout.DrawBase().X(),
+ aOutPoint.X(), aOutPoint.Y(),
+ aPolyPoly, bPolyLine, aRect1, aRect2 );
+ }
+ }
+
+ SetLineColor( aOldLineColor );
+ SetFillColor( aOldFillColor );
+ EnableMapMode( bOldMap );
+ mpMetaFile = pOldMetaFile;
+}
+
+std::unique_ptr<SalLayout> OutputDevice::getFallbackLayout(
+ LogicalFontInstance* pLogicalFont, int nFallbackLevel,
+ ImplLayoutArgs& rLayoutArgs) 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, nullptr))
+ {
+ // there is no need for a font that couldn't resolve anything
+ return nullptr;
+ }
+
+ return pFallback;
+}
+
+std::unique_ptr<SalLayout> OutputDevice::ImplGlyphFallbackLayout( std::unique_ptr<SalLayout> pSalLayout, ImplLayoutArgs& rLayoutArgs ) 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();
+ rLayoutArgs.mnFlags |= SalLayoutFlags::ForFallback;
+
+ // get list of code units that need glyph fallback
+ int nCharPos = -1;
+ bool bRTL = false;
+ OUStringBuffer aMissingCodeBuf(512);
+ while (rLayoutArgs.GetNextPos( &nCharPos, &bRTL))
+ aMissingCodeBuf.append(rLayoutArgs.mrStr[nCharPos]);
+ rLayoutArgs.ResetPos();
+ OUString aMissingCodes = aMissingCodeBuf.makeStringAndClear();
+
+ FontSelectPattern aFontSelData(mpFontInstance->GetFontSelectPattern());
+
+ // try if fallback fonts support the missing code units
+ for( int nFallbackLevel = 1; nFallbackLevel < MAX_FALLBACK; ++nFallbackLevel )
+ {
+ // find a font family suited for glyph fallback
+ // GetGlyphFallbackFont() needs a valid FontInstance
+ // if the system-specific glyph fallback is active
+ rtl::Reference<LogicalFontInstance> pFallbackFont = mxFontCache->GetGlyphFallbackFont( mxFontCollection.get(),
+ aFontSelData, mpFontInstance.get(), nFallbackLevel, aMissingCodes );
+ if( !pFallbackFont )
+ break;
+
+ if( nFallbackLevel < MAX_FALLBACK-1)
+ {
+ // ignore fallback font if it is the same as the original font
+ // unless we are looking for a substitution for 0x202F, in which
+ // case we'll just use a normal space
+ if( mpFontInstance->GetFontFace() == pFallbackFont->GetFontFace() &&
+ aMissingCodes.indexOf(0x202F) == -1 )
+ {
+ continue;
+ }
+ }
+
+ // create and add glyph fallback layout to multilayout
+ std::unique_ptr<SalLayout> pFallback = getFallbackLayout(pFallbackFont.get(),
+ nFallbackLevel, rLayoutArgs);
+ 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);
+ }
+
+ // break when this fallback was sufficient
+ if( !rLayoutArgs.PrepareFallback() )
+ break;
+ }
+
+ if( pMultiSalLayout && pMultiSalLayout->LayoutText( rLayoutArgs, nullptr ) )
+ pSalLayout = std::move(pMultiSalLayout);
+
+ // restore orig font settings
+ pSalLayout->InitFont();
+ rLayoutArgs.maRuns = aLayoutRuns;
+
+ return pSalLayout;
+}
+
+long OutputDevice::GetMinKashida() const
+{
+ if (!ImplNewFont())
+ return 0;
+
+ return ImplDevicePixelToLogicWidth( mpFontInstance->mxFontMetric->GetMinKashida() );
+}
+
+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;
+ sal_Int32 nDropped = 0;
+ for( int i = 0; i < nKashCount; ++i )
+ {
+ if( !pSalLayout->IsKashidaPosValid( pKashidaPos[ i ] ))
+ {
+ pKashidaPosDropped[ nDropped ] = pKashidaPos [ i ];
+ ++nDropped;
+ }
+ }
+ return nDropped;
+}
+
+bool OutputDevice::GetGlyphBoundRects( const Point& rOrigin, const OUString& rStr,
+ int nIndex, int nLen, MetricVector& rVector )
+{
+ 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, const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen ) const
+{
+ if( nIndex >= rStr.getLength() )
+ return nIndex;
+ sal_Int32 nEnd;
+ if( nLen == -1 )
+ nEnd = rStr.getLength();
+ else
+ nEnd = std::min( rStr.getLength(), nIndex + nLen );
+
+ SAL_WARN_IF( nIndex >= nEnd, "vcl.gdi", "StartPos >= EndPos?" );
+ SAL_WARN_IF( nEnd > rStr.getLength(), "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>();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/gradient.cxx b/vcl/source/outdev/gradient.cxx
new file mode 100644
index 000000000..2e8406714
--- /dev/null
+++ b/vcl/source/outdev/gradient.cxx
@@ -0,0 +1,1045 @@
+/* -*- 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 <memory>
+#include <cassert>
+
+#include <tools/poly.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+
+#include <salgdi.hxx>
+
+#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( PushFlags::LINECOLOR | PushFlags::FILLCOLOR );
+ SetLineColor( aColor );
+ SetFillColor( aColor );
+ DrawPolyPolygon( rPolyPoly );
+ Pop();
+ return;
+ }
+
+ Gradient aGradient( rGradient );
+
+ if ( mnDrawMode & DrawModeFlags::GrayGradient )
+ {
+ SetGrayscaleColors( aGradient );
+ }
+
+ 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.Justify();
+
+ // 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( PushFlags::CLIPREGION );
+ IntersectClipRegion( aBoundRect );
+
+ if (mbInitClipRegion)
+ InitClipRegion();
+
+ // try to draw gradient natively
+ bDrawn = mpGraphics->DrawGradient( aClixPolyPoly, aGradient );
+
+ 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() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial )
+ DrawLinearGradient( aRect, aGradient, aClixPolyPoly.IsRect() ? nullptr : &aClixPolyPoly );
+ else
+ DrawComplexGradient( aRect, aGradient, aClixPolyPoly.IsRect() ? nullptr : &aClixPolyPoly );
+ }
+
+ Pop();
+ }
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawPolyPolygon( rPolyPoly );
+}
+
+void OutputDevice::ClipAndDrawGradientMetafile ( const Gradient &rGradient, const tools::PolyPolygon &rPolyPoly )
+{
+ const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
+ const bool bOldOutput = IsOutputEnabled();
+
+ EnableOutput( false );
+ Push( PushFlags::RASTEROP );
+ SetRasterOp( RasterOp::Xor );
+ DrawGradient( aBoundRect, rGradient );
+ SetFillColor( COL_BLACK );
+ SetRasterOp( RasterOp::N0 );
+ DrawPolyPolygon( rPolyPoly );
+ SetRasterOp( RasterOp::Xor );
+ DrawGradient( aBoundRect, 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() )
+ {
+ Gradient aGradient( rGradient );
+
+ if ( mnDrawMode & DrawModeFlags::GrayGradient )
+ {
+ SetGrayscaleColors( aGradient );
+ }
+
+ const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() );
+
+ if ( rPolyPoly.IsRect() )
+ {
+ mpMetaFile->AddAction( new MetaGradientAction( aBoundRect, aGradient ) );
+ }
+ else
+ {
+ mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_BEGIN" ) );
+ mpMetaFile->AddAction( new MetaGradientExAction( rPolyPoly, rGradient ) );
+
+ ClipAndDrawGradientMetafile ( rGradient, rPolyPoly );
+
+ mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_END" ) );
+ }
+
+ if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ // Clip and then draw the gradient
+ if( !tools::Rectangle( PixelToLogic( Point() ), GetOutputSize() ).IsEmpty() )
+ {
+ // convert rectangle to pixels
+ tools::Rectangle aRect( ImplLogicToDevicePixel( aBoundRect ) );
+ aRect.Justify();
+
+ // do nothing if the rectangle is empty
+ if ( !aRect.IsEmpty() )
+ {
+ if( !mbOutputClipped )
+ {
+ // 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() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial )
+ DrawLinearGradientToMetafile( aRect, aGradient );
+ else
+ DrawComplexGradientToMetafile( aRect, aGradient );
+ }
+ }
+ }
+ }
+}
+
+namespace
+{
+ sal_uInt8 GetGradientColorValue( 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;
+ sal_uInt16 nAngle = rGradient.GetAngle() % 3600;
+
+ rGradient.GetBoundRect( rRect, aRect, aCenter );
+
+ bool bLinear = (rGradient.GetStyle() == 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
+ long nFactor;
+ Color aStartCol = rGradient.GetStartColor();
+ Color aEndCol = rGradient.GetEndColor();
+ long nStartRed = aStartCol.GetRed();
+ long nStartGreen = aStartCol.GetGreen();
+ long nStartBlue = aStartCol.GetBlue();
+ long nEndRed = aEndCol.GetRed();
+ long nEndGreen = aEndCol.GetGreen();
+ 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)
+ {
+ long nTempColor = nStartRed;
+ nStartRed = nEndRed;
+ nEndRed = nTempColor;
+ nTempColor = nStartGreen;
+ nStartGreen = nEndGreen;
+ nEndGreen = nTempColor;
+ nTempColor = nStartBlue;
+ nStartBlue = nEndBlue;
+ nEndBlue = nTempColor;
+ }
+
+ 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<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<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
+ long nStepCount = GetGradientSteps( rGradient, aRect, false/*bMtf*/ );
+
+ // minimal three steps and maximal as max color steps
+ long nAbsRedSteps = std::abs( nEndRed - nStartRed );
+ long nAbsGreenSteps = std::abs( nEndGreen - nStartGreen );
+ long nAbsBlueSteps = std::abs( nEndBlue - nStartBlue );
+ long nMaxColorSteps = std::max( nAbsRedSteps , nAbsGreenSteps );
+ nMaxColorSteps = std::max( nMaxColorSteps, nAbsBlueSteps );
+ 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 ( 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<long>(fTempColor));
+ fTempColor = static_cast<double>(nStartGreen) * (1.0-fAlpha) + static_cast<double>(nEndGreen) * fAlpha;
+ nGreen = GetGradientColorValue(static_cast<long>(fTempColor));
+ fTempColor = static_cast<double>(nStartBlue) * (1.0-fAlpha) + static_cast<double>(nEndBlue) * fAlpha;
+ nBlue = GetGradientColorValue(static_cast<long>(fTempColor));
+
+ mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) );
+
+ // Polygon for this color step
+ aRect.SetTop( static_cast<long>( fGradientLine + static_cast<double>(i) * fScanInc ) );
+ aRect.SetBottom( static_cast<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<long>( fMirrorGradientLine - static_cast<double>(i) * fScanInc ) );
+ aMirrorRect.SetTop( static_cast<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<long>( fGradientLine + static_cast<double>(nSteps) * fScanInc ) );
+ aRect.SetBottom( static_cast<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
+{
+ const vcl::Window *pWindow = dynamic_cast<const vcl::Window*>(this);
+ return pWindow && pWindow->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::unique_ptr<tools::PolyPolygon> xPolyPoly;
+ tools::Rectangle aRect;
+ Point aCenter;
+ Color aStartCol( rGradient.GetStartColor() );
+ Color aEndCol( rGradient.GetEndColor() );
+ long nStartRed = ( static_cast<long>(aStartCol.GetRed()) * rGradient.GetStartIntensity() ) / 100;
+ long nStartGreen = ( static_cast<long>(aStartCol.GetGreen()) * rGradient.GetStartIntensity() ) / 100;
+ long nStartBlue = ( static_cast<long>(aStartCol.GetBlue()) * rGradient.GetStartIntensity() ) / 100;
+ long nEndRed = ( static_cast<long>(aEndCol.GetRed()) * rGradient.GetEndIntensity() ) / 100;
+ long nEndGreen = ( static_cast<long>(aEndCol.GetGreen()) * rGradient.GetEndIntensity() ) / 100;
+ long nEndBlue = ( static_cast<long>(aEndCol.GetBlue()) * rGradient.GetEndIntensity() ) / 100;
+ long nRedSteps = nEndRed - nStartRed;
+ long nGreenSteps = nEndGreen - nStartGreen;
+ long nBlueSteps = nEndBlue - nStartBlue;
+ sal_uInt16 nAngle = rGradient.GetAngle() % 3600;
+
+ rGradient.GetBoundRect( rRect, aRect, aCenter );
+
+ if ( UsePolyPolygonForComplexGradient() )
+ xPolyPoly.reset(new tools::PolyPolygon( 2 ));
+
+ long nStepCount = GetGradientSteps( rGradient, rRect, false/*bMtf*/, true/*bComplex*/ );
+
+ // at least three steps and at most the number of colour differences
+ long nSteps = std::max( nStepCount, 2L );
+ long nCalcSteps = std::abs( nRedSteps );
+ 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() != 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 = 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 = aExtRect;
+ ImplDrawPolygon( aPoly, pClixPolyPoly );
+ }
+
+ // loop to output Polygon/PolyPolygon sequentially
+ for( long i = 1; i < nSteps; i++ )
+ {
+ // calculate new Polygon
+ fScanLeft += fScanIncX;
+ aRect.SetLeft( static_cast<long>( fScanLeft ) );
+ fScanTop += fScanIncY;
+ aRect.SetTop( static_cast<long>( fScanTop ) );
+ fScanRight -= fScanIncX;
+ aRect.SetRight( static_cast<long>( fScanRight ) );
+ fScanBottom -= fScanIncY;
+ aRect.SetBottom( static_cast<long>( fScanBottom ) );
+
+ if( ( aRect.GetWidth() < 2 ) || ( aRect.GetHeight() < 2 ) )
+ break;
+
+ if( rGradient.GetStyle() == GradientStyle::Radial || rGradient.GetStyle() == 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 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 )
+ {
+ const tools::Polygon& rPoly = xPolyPoly->GetObject( 1 );
+
+ if( !rPoly.GetBoundRect().IsEmpty() )
+ {
+ // #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 );
+ }
+ }
+}
+
+void OutputDevice::DrawLinearGradientToMetafile( const tools::Rectangle& rRect,
+ const Gradient& rGradient )
+{
+ assert(!is_double_buffered_window());
+
+ // get BoundRect of rotated rectangle
+ tools::Rectangle aRect;
+ Point aCenter;
+ sal_uInt16 nAngle = rGradient.GetAngle() % 3600;
+
+ rGradient.GetBoundRect( rRect, aRect, aCenter );
+
+ bool bLinear = (rGradient.GetStyle() == 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
+ long nFactor;
+ Color aStartCol = rGradient.GetStartColor();
+ Color aEndCol = rGradient.GetEndColor();
+ long nStartRed = aStartCol.GetRed();
+ long nStartGreen = aStartCol.GetGreen();
+ long nStartBlue = aStartCol.GetBlue();
+ long nEndRed = aEndCol.GetRed();
+ long nEndGreen = aEndCol.GetGreen();
+ 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)
+ {
+ long nTempColor = nStartRed;
+ nStartRed = nEndRed;
+ nEndRed = nTempColor;
+ nTempColor = nStartGreen;
+ nStartGreen = nEndGreen;
+ nEndGreen = nTempColor;
+ nTempColor = nStartBlue;
+ nStartBlue = nEndBlue;
+ nEndBlue = nTempColor;
+ }
+
+ 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);
+
+ mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ aBorderRect.SetBottom( static_cast<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 );
+
+ mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) );
+
+ if ( !bLinear)
+ {
+ aBorderRect = aMirrorRect;
+ aBorderRect.SetTop( static_cast<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 );
+
+ mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) );
+ }
+ }
+
+ long nStepCount = GetGradientSteps( rGradient, aRect, true/*bMtf*/ );
+
+ // minimal three steps and maximal as max color steps
+ long nAbsRedSteps = std::abs( nEndRed - nStartRed );
+ long nAbsGreenSteps = std::abs( nEndGreen - nStartGreen );
+ long nAbsBlueSteps = std::abs( nEndBlue - nStartBlue );
+ long nMaxColorSteps = std::max( nAbsRedSteps , nAbsGreenSteps );
+ nMaxColorSteps = std::max( nMaxColorSteps, nAbsBlueSteps );
+ 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 ( long i = 0; i < nSteps; i++ )
+ {
+ // linear interpolation of color
+ 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<long>(fTempColor));
+ fTempColor = static_cast<double>(nStartGreen) * (1.0-fAlpha) + static_cast<double>(nEndGreen) * fAlpha;
+ nGreen = GetGradientColorValue(static_cast<long>(fTempColor));
+ fTempColor = static_cast<double>(nStartBlue) * (1.0-fAlpha) + static_cast<double>(nEndBlue) * fAlpha;
+ nBlue = GetGradientColorValue(static_cast<long>(fTempColor));
+
+ mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ // Polygon for this color step
+ aRect.SetTop( static_cast<long>( fGradientLine + static_cast<double>(i) * fScanInc ) );
+ aRect.SetBottom( static_cast<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 );
+
+ mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) );
+
+ if ( !bLinear )
+ {
+ aMirrorRect.SetBottom( static_cast<long>( fMirrorGradientLine - static_cast<double>(i) * fScanInc ) );
+ aMirrorRect.SetTop( static_cast<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 );
+
+ mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) );
+ }
+ }
+ if ( bLinear)
+ return;
+
+ // draw middle polygon with end color
+ nRed = GetGradientColorValue(nEndRed);
+ nGreen = GetGradientColorValue(nEndGreen);
+ nBlue = GetGradientColorValue(nEndBlue);
+
+ mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ aRect.SetTop( static_cast<long>( fGradientLine + static_cast<double>(nSteps) * fScanInc ) );
+ aRect.SetBottom( static_cast<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 );
+
+ mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) );
+
+}
+
+void OutputDevice::DrawComplexGradientToMetafile( const tools::Rectangle& rRect,
+ const Gradient& rGradient )
+{
+ 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::unique_ptr<tools::PolyPolygon> xPolyPoly;
+ tools::Rectangle aRect;
+ Point aCenter;
+ Color aStartCol( rGradient.GetStartColor() );
+ Color aEndCol( rGradient.GetEndColor() );
+ long nStartRed = ( static_cast<long>(aStartCol.GetRed()) * rGradient.GetStartIntensity() ) / 100;
+ long nStartGreen = ( static_cast<long>(aStartCol.GetGreen()) * rGradient.GetStartIntensity() ) / 100;
+ long nStartBlue = ( static_cast<long>(aStartCol.GetBlue()) * rGradient.GetStartIntensity() ) / 100;
+ long nEndRed = ( static_cast<long>(aEndCol.GetRed()) * rGradient.GetEndIntensity() ) / 100;
+ long nEndGreen = ( static_cast<long>(aEndCol.GetGreen()) * rGradient.GetEndIntensity() ) / 100;
+ long nEndBlue = ( static_cast<long>(aEndCol.GetBlue()) * rGradient.GetEndIntensity() ) / 100;
+ long nRedSteps = nEndRed - nStartRed;
+ long nGreenSteps = nEndGreen - nStartGreen;
+ long nBlueSteps = nEndBlue - nStartBlue;
+ sal_uInt16 nAngle = rGradient.GetAngle() % 3600;
+
+ rGradient.GetBoundRect( rRect, aRect, aCenter );
+
+ xPolyPoly.reset(new tools::PolyPolygon( 2 ));
+
+ // last parameter - true if complex gradient, false if linear
+ long nStepCount = GetGradientSteps( rGradient, rRect, true, true );
+
+ // at least three steps and at most the number of colour differences
+ long nSteps = std::max( nStepCount, 2L );
+ long nCalcSteps = std::abs( nRedSteps );
+ 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() != 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
+
+ mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+
+ aPoly = rRect;
+ xPolyPoly->Insert( aPoly );
+ xPolyPoly->Insert( aPoly );
+
+ // loop to output Polygon/PolyPolygon sequentially
+ for( long i = 1; i < nSteps; i++ )
+ {
+ // calculate new Polygon
+ fScanLeft += fScanIncX;
+ aRect.SetLeft( static_cast<long>( fScanLeft ) );
+ fScanTop += fScanIncY;
+ aRect.SetTop( static_cast<long>( fScanTop ) );
+ fScanRight -= fScanIncX;
+ aRect.SetRight( static_cast<long>( fScanRight ) );
+ fScanBottom -= fScanIncY;
+ aRect.SetBottom( static_cast<long>( fScanBottom ) );
+
+ if( ( aRect.GetWidth() < 2 ) || ( aRect.GetHeight() < 2 ) )
+ break;
+
+ if( rGradient.GetStyle() == GradientStyle::Radial || rGradient.GetStyle() == 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 long nStepIndex = ( xPolyPoly ? i : ( i + 1 ) );
+ nRed = GetGradientColorValue( nStartRed + ( ( nRedSteps * nStepIndex ) / nSteps ) );
+ nGreen = GetGradientColorValue( nStartGreen + ( ( nGreenSteps * nStepIndex ) / nSteps ) );
+ nBlue = GetGradientColorValue( nStartBlue + ( ( nBlueSteps * nStepIndex ) / nSteps ) );
+
+ bPaintLastPolygon = true; // #107349# Paint last polygon only if loop has generated any output
+
+ xPolyPoly->Replace( xPolyPoly->GetObject( 1 ), 0 );
+ xPolyPoly->Replace( aPoly, 1 );
+
+ mpMetaFile->AddAction( new MetaPolyPolygonAction( *xPolyPoly ) );
+
+ // #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.
+ mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+ }
+
+ const tools::Polygon& rPoly = xPolyPoly->GetObject( 1 );
+
+ if( !rPoly.GetBoundRect().IsEmpty() )
+ {
+ // #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 );
+ }
+
+ mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) );
+ mpMetaFile->AddAction( new MetaPolygonAction( rPoly ) );
+ }
+}
+
+long OutputDevice::GetGradientStepCount( long nMinRect )
+{
+ long nInc = (nMinRect < 50) ? 2 : 4;
+
+ return nInc;
+}
+
+long OutputDevice::GetGradientSteps( const Gradient& rGradient, const tools::Rectangle& rRect, bool bMtf, bool bComplex )
+{
+ // calculate step count
+ long nStepCount = rGradient.GetSteps();
+ long nMinRect;
+
+ // generate nStepCount, if not passed
+ if (bComplex)
+ nMinRect = std::min( rRect.GetWidth(), rRect.GetHeight() );
+ else
+ nMinRect = rRect.GetHeight();
+
+ if ( !nStepCount )
+ {
+ long nInc;
+
+ nInc = GetGradientStepCount (nMinRect);
+ if ( !nInc || bMtf )
+ nInc = 1;
+ nStepCount = nMinRect / nInc;
+ }
+
+ return nStepCount;
+}
+
+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 )
+ aColor = GetSettings().GetStyleSettings().GetWindowColor();
+
+ return aColor;
+}
+
+void OutputDevice::SetGrayscaleColors( Gradient &rGradient )
+{
+ // this should only be called with the drawing mode is for grayscale gradients
+ assert ( mnDrawMode & DrawModeFlags::GrayGradient );
+
+ Color aStartCol( rGradient.GetStartColor() );
+ Color aEndCol( rGradient.GetEndColor() );
+
+ if ( mnDrawMode & DrawModeFlags::GrayGradient )
+ {
+ sal_uInt8 cStartLum = aStartCol.GetLuminance(), cEndLum = aEndCol.GetLuminance();
+ aStartCol = Color( cStartLum, cStartLum, cStartLum );
+ aEndCol = Color( cEndLum, cEndLum, cEndLum );
+ }
+
+ rGradient.SetStartColor( aStartCol );
+ rGradient.SetEndColor( aEndCol );
+}
+
+void OutputDevice::AddGradientActions( const tools::Rectangle& rRect, const Gradient& rGradient,
+ GDIMetaFile& rMtf )
+{
+
+ tools::Rectangle aRect( rRect );
+
+ aRect.Justify();
+
+ // do nothing if the rectangle is empty
+ if ( aRect.IsEmpty() )
+ return;
+
+ Gradient aGradient( rGradient );
+ GDIMetaFile* pOldMtf = mpMetaFile;
+
+ mpMetaFile = &rMtf;
+ mpMetaFile->AddAction( new MetaPushAction( PushFlags::ALL ) );
+ mpMetaFile->AddAction( new MetaISectRectClipRegionAction( aRect ) );
+ mpMetaFile->AddAction( new MetaLineColorAction( Color(), false ) );
+
+ // 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 );
+
+ // calculate step count if necessary
+ if ( !aGradient.GetSteps() )
+ aGradient.SetSteps( GRADIENT_DEFAULT_STEPCOUNT );
+
+ if( aGradient.GetStyle() == GradientStyle::Linear || aGradient.GetStyle() == GradientStyle::Axial )
+ DrawLinearGradientToMetafile( aRect, aGradient );
+ else
+ DrawComplexGradientToMetafile( aRect, aGradient );
+
+ mpMetaFile->AddAction( new MetaPopAction() );
+ mpMetaFile = pOldMtf;
+
+}
+
+/* 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 000000000..c5cba1277
--- /dev/null
+++ b/vcl/source/outdev/hatch.cxx
@@ -0,0 +1,409 @@
+/* -*- 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 <cassert>
+
+#include <osl/diagnose.h>
+#include <tools/line.hxx>
+#include <tools/helpers.hxx>
+
+#include <vcl/hatch.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+#include <memory>
+
+#define HATCH_MAXPOINTS 1024
+
+extern "C" {
+
+static int HatchCmpFnc( const void* p1, const void* p2 )
+{
+ const long nX1 = static_cast<Point const *>(p1)->X();
+ const long nX2 = static_cast<Point const *>(p2)->X();
+ const long nY1 = static_cast<Point const *>(p1)->Y();
+ const 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 );
+
+ if ( mnDrawMode & ( DrawModeFlags::BlackLine | DrawModeFlags::WhiteLine |
+ DrawModeFlags::GrayLine |
+ DrawModeFlags::SettingsLine ) )
+ {
+ Color aColor( rHatch.GetColor() );
+
+ if ( mnDrawMode & DrawModeFlags::BlackLine )
+ aColor = COL_BLACK;
+ else if ( mnDrawMode & DrawModeFlags::WhiteLine )
+ aColor = COL_WHITE;
+ else if ( mnDrawMode & DrawModeFlags::GrayLine )
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color( cLum, cLum, cLum );
+ }
+ else if( mnDrawMode & DrawModeFlags::SettingsLine )
+ {
+ aColor = GetSettings().GetStyleSettings().GetFontColor();
+ }
+
+ aHatch.SetColor( aColor );
+ }
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaHatchAction( rPolyPoly, aHatch ) );
+
+ if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ 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( 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( PushFlags::ALL ) );
+ mpMetaFile->AddAction( new MetaLineColorAction( rHatch.GetColor(), true ) );
+ DrawHatch( aPolyPoly, rHatch, true );
+ mpMetaFile->AddAction( new MetaPopAction() );
+ mpMetaFile = pOldMtf;
+ }
+}
+
+void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch, bool bMtf )
+{
+ assert(!is_double_buffered_window());
+
+ if(rPolyPoly.Count())
+ {
+ // #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 long nLogPixelWidth = ImplDevicePixelToLogicWidth( 1 );
+ const long nWidth = ImplDevicePixelToLogicWidth( std::max( ImplLogicWidthToDevicePixel( rHatch.GetDistance() ), 3L ) );
+ 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 );
+ 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, aPt1, aPt2, aInc, aEndPt1 );
+ 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, aPt1, aPt2, aInc, aEndPt1 );
+ 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, long nDist, sal_uInt16 nAngle10,
+ Point& rPt1, Point& rPt2, Size& rInc, Point& rEndPt1 )
+{
+ Point aRef;
+ long nAngle = nAngle10 % 1800;
+ long nOffset = 0;
+
+ if( nAngle > 900 )
+ nAngle -= 1800;
+
+ aRef = ( !IsRefPoint() ? rRect.TopLeft() : GetRefPoint() );
+
+ if( 0 == 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 == 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 >= -450 && nAngle <= 450 )
+ {
+ const double fAngle = F_PI1800 * labs( nAngle );
+ const double fTan = tan( fAngle );
+ const long nYOff = FRound( ( rRect.Right() - rRect.Left() ) * fTan );
+ long nPY;
+
+ nDist = FRound( nDist / cos( fAngle ) );
+ rInc = Size( 0, nDist );
+
+ if( nAngle > 0 )
+ {
+ 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 = F_PI1800 * labs( nAngle );
+ const double fTan = tan( fAngle );
+ const long nXOff = FRound( ( rRect.Bottom() - rRect.Top() ) / fTan );
+ long nPX;
+
+ nDist = FRound( nDist / sin( fAngle ) );
+ rInc = Size( nDist, 0 );
+
+ if( nAngle > 0 )
+ {
+ rPt1 = rRect.TopLeft();
+ rPt2 = Point( rRect.Left() - nXOff, rRect.Bottom() );
+ rEndPt1 = Point( rRect.Right() + nXOff, rRect.Top() );
+ nPX = FRound( aRef.X() - ( ( 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() + ( ( 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;
+ long nAdd, nPCounter = 0;
+
+ for( 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( 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 )
+ pPtBuffer[ nPCounter++ ] = Point( FRound( fX ), FRound( fY ) );
+ }
+
+ aCurSegment.SetStart( aCurSegment.GetEnd() );
+ }
+ }
+ }
+
+ if( nPCounter > 1 )
+ {
+ qsort( pPtBuffer, nPCounter, sizeof( Point ), HatchCmpFnc );
+
+ if( nPCounter & 1 )
+ nPCounter--;
+
+ if( bMtf )
+ {
+ for( long i = 0; i < nPCounter; i += 2 )
+ mpMetaFile->AddAction( new MetaLineAction( pPtBuffer[ i ], pPtBuffer[ i + 1 ] ) );
+ }
+ else
+ {
+ for( 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 000000000..f451b6e1f
--- /dev/null
+++ b/vcl/source/outdev/line.cxx
@@ -0,0 +1,304 @@
+/* -*- 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 <cassert>
+#include <numeric>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+
+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;
+
+ 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;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ // #i101598# support AA and snap for lines, too
+ if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw)
+ && mpGraphics->supportsOperation(OutDevSupportType::B2DDraw)
+ && 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);
+
+ if( 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))
+ {
+ return;
+ }
+ }
+
+ 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 )
+{
+ const bool bTryAA((mnAntialiasing & AntialiasingFlags::EnableB2dDraw)
+ && mpGraphics->supportsOperation(OutDevSupportType::B2DDraw)
+ && RasterOp::OverPaint == GetRasterOp()
+ && IsLineColor());
+ basegfx::B2DPolyPolygon aFillPolyPolygon;
+ const bool bDashUsed(LineStyle::Dash == rInfo.GetStyle());
+ const bool bLineWidthUsed(rInfo.GetWidth() > 1);
+
+ if(bDashUsed && aLinePolyPolygon.count())
+ {
+ ::std::vector< double > fDotDashArray;
+ const double fDashLen(rInfo.GetDashLen());
+ const double fDotLen(rInfo.GetDotLen());
+ const double fDistance(rInfo.GetDistance());
+
+ for(sal_uInt16 a(0); a < rInfo.GetDashCount(); a++)
+ {
+ fDotDashArray.push_back(fDashLen);
+ fDotDashArray.push_back(fDistance);
+ }
+
+ for(sal_uInt16 b(0); b < rInfo.GetDotCount(); b++)
+ {
+ fDotDashArray.push_back(fDotLen);
+ fDotDashArray.push_back(fDistance);
+ }
+
+ const double fAccumulated(::std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0));
+
+ if(fAccumulated > 0.0)
+ {
+ basegfx::B2DPolyPolygon aResult;
+
+ for(auto const& rPolygon : 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.
+ aLinePolyPolygon = basegfx::utils::adaptiveSubdivideByDistance(aLinePolyPolygon, 1.0);
+ }
+
+ for(auto const& rPolygon : 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 : aLinePolyPolygon)
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+ bool bDone(false);
+
+ if(bTryAA)
+ {
+ 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(),
+ reinterpret_cast<SalPoint*>(aPolygon.GetPointAry()),
+ this);
+ }
+ }
+ }
+
+ if(aFillPolyPolygon.count())
+ {
+ const Color aOldLineColor( maLineColor );
+ const Color aOldFillColor( maFillColor );
+
+ SetLineColor();
+ InitLineColor();
+ SetFillColor( aOldLineColor );
+ InitFillColor();
+
+ bool bDone(false);
+
+ if(bTryAA)
+ {
+ bDone = mpGraphics->DrawPolyPolygon(
+ basegfx::B2DHomMatrix(),
+ aFillPolyPolygon,
+ 0.0,
+ this);
+ }
+
+ if(!bDone)
+ {
+ for(auto const& rB2DPolygon : aFillPolyPolygon)
+ {
+ tools::Polygon aPolygon(rB2DPolygon);
+
+ // need to subdivide, mpGraphics->DrawPolygon ignores curves
+ aPolygon.AdaptiveSubdivide(aPolygon);
+ mpGraphics->DrawPolygon(aPolygon.GetSize(), reinterpret_cast<const SalPoint*>(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 000000000..92d9e6322
--- /dev/null
+++ b/vcl/source/outdev/map.cxx
@@ -0,0 +1,1936 @@
+/* -*- 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/bigint.hxx>
+#include <tools/debug.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/lineinfo.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/wrkwin.hxx>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <svdata.hxx>
+#include <window.h>
+#include <outdev.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <o3tl/enumarray.hxx>
+
+// we don't actually handle units beyond, hence the zeros in the arrays
+static const MapUnit s_MaxValidUnit = MapUnit::MapPixel;
+static const o3tl::enumarray<MapUnit,long> aImplNumeratorAry =
+ { 1, 1, 5, 50, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 };
+static const o3tl::enumarray<MapUnit,long> aImplDenominatorAry =
+ { 2540, 254, 127, 127, 1000, 100, 10, 1, 72, 1440, 1, 0, 0, 0 };
+
+/*
+Reduces accuracy until it is a fraction (should become
+ctor fraction once); we could also do this with BigInts
+*/
+
+static Fraction ImplMakeFraction( long nN1, long nN2, long nD1, long nD2 )
+{
+ if( nD1 == 0 || nD2 == 0 ) //under these bad circumstances the following while loop will be endless
+ {
+ SAL_WARN("vcl.gdi", "Invalid parameter for ImplMakeFraction");
+ return Fraction( 1, 1 );
+ }
+
+ long i = 1;
+
+ if ( nN1 < 0 ) { i = -i; nN1 = -nN1; }
+ if ( nN2 < 0 ) { i = -i; nN2 = -nN2; }
+ if ( nD1 < 0 ) { i = -i; nD1 = -nD1; }
+ if ( nD2 < 0 ) { i = -i; nD2 = -nD2; }
+ // all positive; i sign
+
+ Fraction aF = Fraction( i*nN1, nD1 ) * Fraction( nN2, nD2 );
+
+ while ( !aF.IsValid() ) {
+ if ( nN1 > nN2 )
+ nN1 = (nN1 + 1) / 2;
+ else
+ nN2 = (nN2 + 1) / 2;
+ if ( nD1 > nD2 )
+ nD1 = (nD1 + 1) / 2;
+ else
+ nD2 = (nD2 + 1) / 2;
+
+ aF = Fraction( i*nN1, nD1 ) * Fraction( nN2, nD2 );
+ }
+
+ aF.ReduceInaccurate(32);
+ return aF;
+}
+
+// Fraction.GetNumerator()
+// Fraction.GetDenominator() > 0
+// rOutRes.nPixPerInch? > 0
+// rMapRes.nMapScNum?
+// rMapRes.nMapScDenom? > 0
+
+static void ImplCalcBigIntThreshold( long nDPIX, long nDPIY,
+ const ImplMapRes& rMapRes,
+ ImplThresholdRes& rThresRes )
+{
+ if ( nDPIX && (LONG_MAX / nDPIX < std::abs( rMapRes.mnMapScNumX ) ) ) // #111139# avoid div by zero
+ {
+ rThresRes.mnThresLogToPixX = 0;
+ rThresRes.mnThresPixToLogX = 0;
+ }
+ else
+ {
+ // calculate thresholds for BigInt arithmetic
+ long nDenomHalfX = rMapRes.mnMapScDenomX / 2;
+ sal_uLong nDenomX = rMapRes.mnMapScDenomX;
+ long nProductX = nDPIX * rMapRes.mnMapScNumX;
+
+ if ( !nProductX )
+ rThresRes.mnThresLogToPixX = LONG_MAX;
+ else
+ rThresRes.mnThresLogToPixX = std::abs( (LONG_MAX - nDenomHalfX) / nProductX );
+
+ if ( !nDenomX )
+ rThresRes.mnThresPixToLogX = LONG_MAX;
+ else if ( nProductX >= 0 )
+ rThresRes.mnThresPixToLogX = static_cast<long>((sal_uLong(LONG_MAX) - static_cast<sal_uLong>( nProductX/2)) / nDenomX);
+ else
+ rThresRes.mnThresPixToLogX = static_cast<long>((sal_uLong(LONG_MAX) + static_cast<sal_uLong>(-nProductX/2)) / nDenomX);
+ }
+
+ if ( nDPIY && (LONG_MAX / nDPIY < std::abs( rMapRes.mnMapScNumY ) ) ) // #111139# avoid div by zero
+ {
+ rThresRes.mnThresLogToPixY = 0;
+ rThresRes.mnThresPixToLogY = 0;
+ }
+ else
+ {
+ // calculate thresholds for BigInt arithmetic
+ long nDenomHalfY = rMapRes.mnMapScDenomY / 2;
+ sal_uLong nDenomY = rMapRes.mnMapScDenomY;
+ long nProductY = nDPIY * rMapRes.mnMapScNumY;
+
+ if ( !nProductY )
+ rThresRes.mnThresLogToPixY = LONG_MAX;
+ else
+ rThresRes.mnThresLogToPixY = std::abs( (LONG_MAX - nDenomHalfY) / nProductY );
+
+ if ( !nDenomY )
+ rThresRes.mnThresPixToLogY = LONG_MAX;
+ else if ( nProductY >= 0 )
+ rThresRes.mnThresPixToLogY = static_cast<long>((sal_uLong(LONG_MAX) - static_cast<sal_uLong>( nProductY/2)) / nDenomY);
+ else
+ rThresRes.mnThresPixToLogY = static_cast<long>((sal_uLong(LONG_MAX) + static_cast<sal_uLong>(-nProductY/2)) / nDenomY);
+ }
+
+ rThresRes.mnThresLogToPixX /= 2;
+ rThresRes.mnThresLogToPixY /= 2;
+ rThresRes.mnThresPixToLogX /= 2;
+ rThresRes.mnThresPixToLogY /= 2;
+}
+
+static void ImplCalcMapResolution( const MapMode& rMapMode,
+ long nDPIX, long nDPIY, ImplMapRes& rMapRes )
+{
+ switch ( rMapMode.GetMapUnit() )
+ {
+ case MapUnit::MapRelative:
+ break;
+ case MapUnit::Map100thMM:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 2540;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 2540;
+ break;
+ case MapUnit::Map10thMM:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 254;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 254;
+ break;
+ case MapUnit::MapMM:
+ rMapRes.mnMapScNumX = 5; // 10
+ rMapRes.mnMapScDenomX = 127; // 254
+ rMapRes.mnMapScNumY = 5; // 10
+ rMapRes.mnMapScDenomY = 127; // 254
+ break;
+ case MapUnit::MapCM:
+ rMapRes.mnMapScNumX = 50; // 100
+ rMapRes.mnMapScDenomX = 127; // 254
+ rMapRes.mnMapScNumY = 50; // 100
+ rMapRes.mnMapScDenomY = 127; // 254
+ break;
+ case MapUnit::Map1000thInch:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 1000;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 1000;
+ break;
+ case MapUnit::Map100thInch:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 100;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 100;
+ break;
+ case MapUnit::Map10thInch:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 10;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 10;
+ break;
+ case MapUnit::MapInch:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 1;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 1;
+ break;
+ case MapUnit::MapPoint:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 72;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 72;
+ break;
+ case MapUnit::MapTwip:
+ rMapRes.mnMapScNumX = 1;
+ rMapRes.mnMapScDenomX = 1440;
+ rMapRes.mnMapScNumY = 1;
+ rMapRes.mnMapScDenomY = 1440;
+ 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 nXNumerator = aScaleX.GetNumerator();
+ auto nYNumerator = aScaleY.GetNumerator();
+ assert(nXNumerator != 0 && nYNumerator != 0);
+
+ BigInt aX( rMapRes.mnMapOfsX );
+ aX *= BigInt( aScaleX.GetDenominator() );
+ if ( rMapRes.mnMapOfsX >= 0 )
+ {
+ if (nXNumerator >= 0)
+ aX += BigInt(nXNumerator / 2);
+ else
+ aX -= BigInt((nXNumerator + 1) / 2);
+ }
+ else
+ {
+ if (nXNumerator >= 0 )
+ aX -= BigInt((nXNumerator - 1) / 2);
+ else
+ aX += BigInt(nXNumerator / 2);
+ }
+ aX /= BigInt(nXNumerator);
+ rMapRes.mnMapOfsX = static_cast<long>(aX) + aOrigin.X();
+ BigInt aY( rMapRes.mnMapOfsY );
+ aY *= BigInt( aScaleY.GetDenominator() );
+ if( rMapRes.mnMapOfsY >= 0 )
+ {
+ if (nYNumerator >= 0)
+ aY += BigInt(nYNumerator / 2);
+ else
+ aY -= BigInt((nYNumerator + 1) / 2);
+ }
+ else
+ {
+ if (nYNumerator >= 0)
+ aY -= BigInt((nYNumerator - 1) / 2);
+ else
+ aY += BigInt(nYNumerator / 2);
+ }
+ aY /= BigInt(nYNumerator);
+ rMapRes.mnMapOfsY = static_cast<long>(aY) + aOrigin.Y();
+ }
+
+ // calculate scaling factor according to MapMode
+ // aTemp? = rMapRes.mnMapSc? * aScale?
+ Fraction aTempX = ImplMakeFraction( rMapRes.mnMapScNumX,
+ aScaleX.GetNumerator(),
+ rMapRes.mnMapScDenomX,
+ aScaleX.GetDenominator() );
+ Fraction aTempY = ImplMakeFraction( rMapRes.mnMapScNumY,
+ aScaleY.GetNumerator(),
+ rMapRes.mnMapScDenomY,
+ aScaleY.GetDenominator() );
+ rMapRes.mnMapScNumX = aTempX.GetNumerator();
+ rMapRes.mnMapScDenomX = aTempX.GetDenominator();
+ rMapRes.mnMapScNumY = aTempY.GetNumerator();
+ rMapRes.mnMapScDenomY = aTempY.GetDenominator();
+}
+
+static void ImplCalcMapResolution( const MapMode& rMapMode,
+ long nDPIX, long nDPIY,
+ ImplMapRes& rMapRes,
+ ImplThresholdRes& rThresRes )
+{
+ ImplCalcMapResolution( rMapMode, nDPIX, nDPIY, rMapRes );
+ ImplCalcBigIntThreshold( nDPIX, nDPIY, rMapRes, rThresRes );
+}
+
+// #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 long ImplLogicToPixel( long n, long nDPI, long nMapNum, long nMapDenom,
+ long nThres )
+{
+ assert(nDPI > 0);
+#if (SAL_TYPES_SIZEOFLONG < 8)
+ if( (+n < nThres) && (-n < nThres) )
+ {
+ n *= nMapNum * nDPI;
+ if( nMapDenom != 1 )
+ {
+ n = (2 * n) / nMapDenom;
+ if( n < 0 ) --n; else ++n;
+ n /= 2;
+ }
+ }
+ else
+#else
+ (void) nThres;
+ assert(nMapNum >= 0);
+ assert(nMapNum == 0 || std::abs(n) < std::numeric_limits<long>::max() / nMapNum / nDPI); //detect overflows
+#endif
+ {
+ sal_Int64 n64 = n;
+ n64 *= nMapNum;
+ n64 *= nDPI;
+ if( nMapDenom == 1 )
+ n = static_cast<long>(n64);
+ else
+ {
+ n = static_cast<long>(2 * n64 / nMapDenom);
+ if( n < 0 ) --n; else ++n;
+ n /= 2;
+ }
+ }
+ return n;
+}
+
+static long ImplPixelToLogic( long n, long nDPI, long nMapNum, long nMapDenom,
+ long nThres )
+{
+ assert(nDPI > 0);
+ long nDenom = nDPI * nMapNum;
+ if (nDenom == 0)
+ {
+ return 0;
+ }
+
+#if (SAL_TYPES_SIZEOFLONG < 8)
+ if( (+n < nThres) && (-n < nThres) )
+ n = (2 * n * nMapDenom) / nDenom;
+ else
+#else
+ (void) nThres;
+#endif
+ {
+ sal_Int64 n64 = n;
+ n64 *= nMapDenom;
+ n = static_cast<long>(2 * n64 / nDenom);
+ }
+ if( n < 0 ) --n; else ++n;
+ return (n / 2);
+}
+
+long OutputDevice::ImplLogicXToDevicePixel( long nX ) const
+{
+ if ( !mbMap )
+ return nX+mnOutOffX;
+
+ return ImplLogicToPixel( nX + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX )+mnOutOffX+mnOutOffOrigX;
+}
+
+long OutputDevice::ImplLogicYToDevicePixel( long nY ) const
+{
+ if ( !mbMap )
+ return nY+mnOutOffY;
+
+ return ImplLogicToPixel( nY + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+mnOutOffY+mnOutOffOrigY;
+}
+
+long OutputDevice::ImplLogicWidthToDevicePixel( long nWidth ) const
+{
+ if ( !mbMap )
+ return nWidth;
+
+ return ImplLogicToPixel( nWidth, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX );
+}
+
+long OutputDevice::ImplLogicHeightToDevicePixel( long nHeight ) const
+{
+ if ( !mbMap )
+ return nHeight;
+
+ return ImplLogicToPixel( nHeight, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY );
+}
+
+float OutputDevice::ImplFloatLogicHeightToDevicePixel( float fLogicHeight) const
+{
+ if( !mbMap)
+ return fLogicHeight;
+ float fPixelHeight = (fLogicHeight * mnDPIY * maMapRes.mnMapScNumY) / maMapRes.mnMapScDenomY;
+ return fPixelHeight;
+}
+
+long OutputDevice::ImplDevicePixelToLogicWidth( long nWidth ) const
+{
+ if ( !mbMap )
+ return nWidth;
+
+ return ImplPixelToLogic( nWidth, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX );
+}
+
+long OutputDevice::ImplDevicePixelToLogicHeight( long nHeight ) const
+{
+ if ( !mbMap )
+ return nHeight;
+
+ return ImplPixelToLogic( nHeight, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY );
+}
+
+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,
+ maThresRes.mnThresLogToPixX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicPt.Y() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+mnOutOffY+mnOutOffOrigY );
+}
+
+Size OutputDevice::ImplLogicToDevicePixel( const Size& rLogicSize ) const
+{
+ if ( !mbMap )
+ return rLogicSize;
+
+ return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX ),
+ ImplLogicToPixel( rLogicSize.Height(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY ) );
+}
+
+tools::Rectangle OutputDevice::ImplLogicToDevicePixel( const tools::Rectangle& rLogicRect ) const
+{
+ if ( rLogicRect.IsEmpty() )
+ return rLogicRect;
+
+ if ( !mbMap )
+ {
+ return tools::Rectangle( rLogicRect.Left()+mnOutOffX, rLogicRect.Top()+mnOutOffY,
+ rLogicRect.Right()+mnOutOffX, rLogicRect.Bottom()+mnOutOffY );
+ }
+
+ return tools::Rectangle( ImplLogicToPixel( rLogicRect.Left()+maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Top()+maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+mnOutOffY+mnOutOffOrigY,
+ ImplLogicToPixel( rLogicRect.Right()+maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX )+mnOutOffX+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Bottom()+maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+mnOutOffY+mnOutOffOrigY );
+}
+
+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* pPt = &(pPointAry[i]);
+ Point aPt;
+ aPt.setX( ImplLogicToPixel( pPt->X()+maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX )+mnOutOffX+mnOutOffOrigX );
+ aPt.setY( ImplLogicToPixel( pPt->Y()+maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+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;
+}
+
+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() ), 1L ) );
+ else
+ aInfo.SetDotCount( 0 );
+
+ if( aInfo.GetDashCount() && aInfo.GetDashLen() )
+ aInfo.SetDashLen( std::max( ImplLogicWidthToDevicePixel( aInfo.GetDashLen() ), 1L ) );
+ 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
+{
+ if ( rPixelRect.IsEmpty() )
+ return rPixelRect;
+
+ if ( !mbMap )
+ {
+ return tools::Rectangle( rPixelRect.Left()-mnOutOffX, rPixelRect.Top()-mnOutOffY,
+ rPixelRect.Right()-mnOutOffX, rPixelRect.Bottom()-mnOutOffY );
+ }
+
+ return tools::Rectangle( ImplPixelToLogic( rPixelRect.Left()-mnOutOffX-mnOutOffOrigX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX )-maMapRes.mnMapOfsX,
+ ImplPixelToLogic( rPixelRect.Top()-mnOutOffY-mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY )-maMapRes.mnMapOfsY,
+ ImplPixelToLogic( rPixelRect.Right()-mnOutOffX-mnOutOffOrigX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX )-maMapRes.mnMapOfsX,
+ ImplPixelToLogic( rPixelRect.Bottom()-mnOutOffY-mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY )-maMapRes.mnMapOfsY );
+}
+
+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, maThresRes );
+ }
+
+ // set new MapMode
+ if ( bRelMap )
+ {
+ Point aOrigin( maMapRes.mnMapOfsX, maMapRes.mnMapOfsY );
+ // aScale? = maMapMode.GetScale?() * rNewMapMode.GetScale?()
+ Fraction aScaleX = ImplMakeFraction( maMapMode.GetScaleX().GetNumerator(),
+ rNewMapMode.GetScaleX().GetNumerator(),
+ maMapMode.GetScaleX().GetDenominator(),
+ rNewMapMode.GetScaleX().GetDenominator() );
+ Fraction aScaleY = ImplMakeFraction( maMapMode.GetScaleY().GetNumerator(),
+ rNewMapMode.GetScaleY().GetNumerator(),
+ maMapMode.GetScaleY().GetDenominator(),
+ rNewMapMode.GetScaleY().GetDenominator() );
+ maMapMode.SetOrigin( aOrigin );
+ maMapMode.SetScaleX( aScaleX );
+ maMapMode.SetScaleY( aScaleY );
+ }
+ 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,
+ maThresRes.mnThresPixToLogX );
+ mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY );
+
+ // #i75163#
+ ImplInvalidateViewTransform();
+}
+
+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 = ImplMakeFraction( rNewMapMode.GetScaleX().GetNumerator(),
+ maMapMode.GetScaleX().GetDenominator(),
+ rNewMapMode.GetScaleX().GetDenominator(),
+ maMapMode.GetScaleX().GetNumerator() );
+ Fraction aYF = ImplMakeFraction( 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
+ {
+ Fraction aF( aImplNumeratorAry[eNew] * aImplDenominatorAry[eOld],
+ aImplNumeratorAry[eOld] * aImplDenominatorAry[eNew] );
+
+ // a?F = a?F * aF
+ aXF = ImplMakeFraction( aXF.GetNumerator(), aF.GetNumerator(),
+ aXF.GetDenominator(), aF.GetDenominator() );
+ aYF = ImplMakeFraction( 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,
+ maThresRes.mnThresPixToLogX );
+ mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY );
+
+ 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;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ 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,
+ maThresRes.mnThresLogToPixX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicPt.Y() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+mnOutOffOrigY );
+}
+
+Size OutputDevice::LogicToPixel( const Size& rLogicSize ) const
+{
+
+ if ( !mbMap )
+ return rLogicSize;
+
+ return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX ),
+ ImplLogicToPixel( rLogicSize.Height(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY ) );
+}
+
+tools::Rectangle OutputDevice::LogicToPixel( const tools::Rectangle& rLogicRect ) const
+{
+
+ if ( !mbMap || rLogicRect.IsEmpty() )
+ return rLogicRect;
+
+ return tools::Rectangle( ImplLogicToPixel( rLogicRect.Left() + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Top() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+mnOutOffOrigY,
+ ImplLogicToPixel( rLogicRect.Right() + maMapRes.mnMapOfsX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Bottom() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+mnOutOffOrigY );
+}
+
+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,
+ maThresRes.mnThresLogToPixX )+mnOutOffOrigX );
+ aPt.setY( ImplLogicToPixel( pPt->Y() + maMapRes.mnMapOfsY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresLogToPixY )+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;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ return Point( ImplLogicToPixel( rLogicPt.X() + aMapRes.mnMapOfsX, mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresLogToPixX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicPt.Y() + aMapRes.mnMapOfsY, mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresLogToPixY )+mnOutOffOrigY );
+}
+
+Size OutputDevice::LogicToPixel( const Size& rLogicSize,
+ const MapMode& rMapMode ) const
+{
+
+ if ( rMapMode.IsDefault() )
+ return rLogicSize;
+
+ // convert MapMode resolution and convert
+ ImplMapRes aMapRes;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresLogToPixX ),
+ ImplLogicToPixel( rLogicSize.Height(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresLogToPixY ) );
+}
+
+tools::Rectangle OutputDevice::LogicToPixel( const tools::Rectangle& rLogicRect,
+ const MapMode& rMapMode ) const
+{
+
+ if ( rMapMode.IsDefault() || rLogicRect.IsEmpty() )
+ return rLogicRect;
+
+ // convert MapMode resolution and convert
+ ImplMapRes aMapRes;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ return tools::Rectangle( ImplLogicToPixel( rLogicRect.Left() + aMapRes.mnMapOfsX, mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresLogToPixX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Top() + aMapRes.mnMapOfsY, mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresLogToPixY )+mnOutOffOrigY,
+ ImplLogicToPixel( rLogicRect.Right() + aMapRes.mnMapOfsX, mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresLogToPixX )+mnOutOffOrigX,
+ ImplLogicToPixel( rLogicRect.Bottom() + aMapRes.mnMapOfsY, mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresLogToPixY )+mnOutOffOrigY );
+}
+
+tools::Polygon OutputDevice::LogicToPixel( const tools::Polygon& rLogicPoly,
+ const MapMode& rMapMode ) const
+{
+
+ if ( rMapMode.IsDefault() )
+ return rLogicPoly;
+
+ // convert MapMode resolution and convert
+ ImplMapRes aMapRes;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ 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,
+ aThresRes.mnThresLogToPixX )+mnOutOffOrigX );
+ aPt.setY( ImplLogicToPixel( pPt->Y() + aMapRes.mnMapOfsY, mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresLogToPixY )+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,
+ maThresRes.mnThresPixToLogX ) - maMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDevicePt.Y(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY ) - maMapRes.mnMapOfsY - mnOutOffLogicY );
+}
+
+Size OutputDevice::PixelToLogic( const Size& rDeviceSize ) const
+{
+
+ if ( !mbMap )
+ return rDeviceSize;
+
+ return Size( ImplPixelToLogic( rDeviceSize.Width(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX ),
+ ImplPixelToLogic( rDeviceSize.Height(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY ) );
+}
+
+tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect ) const
+{
+
+ if ( !mbMap || rDeviceRect.IsEmpty() )
+ return rDeviceRect;
+
+ return tools::Rectangle( ImplPixelToLogic( rDeviceRect.Left(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX ) - maMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDeviceRect.Top(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY ) - maMapRes.mnMapOfsY - mnOutOffLogicY,
+ ImplPixelToLogic( rDeviceRect.Right(), mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX ) - maMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDeviceRect.Bottom(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY ) - maMapRes.mnMapOfsY - mnOutOffLogicY );
+}
+
+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,
+ maThresRes.mnThresPixToLogX ) - maMapRes.mnMapOfsX - mnOutOffLogicX );
+ aPt.setY( ImplPixelToLogic( pPt->Y(), mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY ) - 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;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ return Point( ImplPixelToLogic( rDevicePt.X(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresPixToLogX ) - aMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDevicePt.Y(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresPixToLogY ) - 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;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ return Size( ImplPixelToLogic( rDeviceSize.Width(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresPixToLogX ),
+ ImplPixelToLogic( rDeviceSize.Height(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresPixToLogY ) );
+}
+
+tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect,
+ const MapMode& rMapMode ) const
+{
+
+ // calculate nothing if default-MapMode
+ if ( rMapMode.IsDefault() || rDeviceRect.IsEmpty() )
+ return rDeviceRect;
+
+ // calculate MapMode-resolution and convert
+ ImplMapRes aMapRes;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ return tools::Rectangle( ImplPixelToLogic( rDeviceRect.Left(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresPixToLogX ) - aMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDeviceRect.Top(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresPixToLogY ) - aMapRes.mnMapOfsY - mnOutOffLogicY,
+ ImplPixelToLogic( rDeviceRect.Right(), mnDPIX,
+ aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX,
+ aThresRes.mnThresPixToLogX ) - aMapRes.mnMapOfsX - mnOutOffLogicX,
+ ImplPixelToLogic( rDeviceRect.Bottom(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresPixToLogY ) - aMapRes.mnMapOfsY - mnOutOffLogicY );
+}
+
+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;
+ ImplThresholdRes aThresRes;
+ ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes );
+
+ 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,
+ aThresRes.mnThresPixToLogX ) - aMapRes.mnMapOfsX - mnOutOffLogicX );
+ aPt.setY( ImplPixelToLogic( pPt->Y(), mnDPIY,
+ aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY,
+ aThresRes.mnThresPixToLogY ) - 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; \
+ aMapResSource.mnMapOfsX = 0; \
+ aMapResSource.mnMapOfsY = 0; \
+ aMapResSource.mnMapScNumX = 1; \
+ aMapResSource.mnMapScNumY = 1; \
+ aMapResSource.mnMapScDenomX = 1; \
+ aMapResSource.mnMapScDenomY = 1; \
+ ImplMapRes aMapResDest(aMapResSource); \
+ \
+ 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" );
+}
+
+#define ENTER3( eUnitSource, eUnitDest ) \
+ long nNumerator = 1; \
+ long nDenominator = 1; \
+ SAL_WARN_IF( eUnitSource > s_MaxValidUnit, "vcl.gdi", "Invalid source map unit"); \
+ SAL_WARN_IF( eUnitDest > s_MaxValidUnit, "vcl.gdi", "Invalid destination map unit"); \
+ if( (eUnitSource <= s_MaxValidUnit) && (eUnitDest <= s_MaxValidUnit) ) \
+ { \
+ nNumerator = aImplNumeratorAry[eUnitSource] * \
+ aImplDenominatorAry[eUnitDest]; \
+ nDenominator = aImplNumeratorAry[eUnitDest] * \
+ aImplDenominatorAry[eUnitSource]; \
+ } \
+ if ( eUnitSource == MapUnit::MapPixel ) \
+ nDenominator *= 72; \
+ else if( eUnitDest == MapUnit::MapPixel ) \
+ nNumerator *= 72
+
+#define ENTER4( rMapModeSource, rMapModeDest ) \
+ ImplMapRes aMapResSource; \
+ aMapResSource.mnMapOfsX = 0; \
+ aMapResSource.mnMapOfsY = 0; \
+ aMapResSource.mnMapScNumX = 1; \
+ aMapResSource.mnMapScNumY = 1; \
+ aMapResSource.mnMapScDenomX = 1; \
+ aMapResSource.mnMapScDenomY = 1; \
+ ImplMapRes aMapResDest(aMapResSource); \
+ \
+ ImplCalcMapResolution( rMapModeSource, 72, 72, aMapResSource ); \
+ ImplCalcMapResolution( rMapModeDest, 72, 72, aMapResDest )
+
+// return (n1 * n2 * n3) / (n4 * n5)
+static long fn5( const long n1,
+ const long n2,
+ const long n3,
+ const long n4,
+ const long n5 )
+{
+ if ( n1 == 0 || n2 == 0 || n3 == 0 || n4 == 0 || n5 == 0 )
+ return 0;
+ if ( LONG_MAX / std::abs(n2) < std::abs(n3) )
+ {
+ // a6 is skipped
+ BigInt a7 = n2;
+ a7 *= n3;
+ a7 *= n1;
+
+ if ( 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
+ {
+ long n8 = n4 * n5;
+
+ if ( a7.IsNeg() )
+ a7 -= n8 / 2;
+ else
+ a7 += n8 / 2;
+
+ a7 /= n8;
+ } // of else
+ return static_cast<long>(a7);
+ } // of if
+ else
+ {
+ long n6 = n2 * n3;
+
+ if ( LONG_MAX / std::abs(n1) < std::abs(n6) )
+ {
+ BigInt a7 = n1;
+ a7 *= n6;
+
+ if ( 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
+ {
+ long n8 = n4 * n5;
+
+ if ( a7.IsNeg() )
+ a7 -= n8 / 2;
+ else
+ a7 += n8 / 2;
+
+ a7 /= n8;
+ } // of else
+ return static_cast<long>(a7);
+ } // of if
+ else
+ {
+ long n7 = n1 * n6;
+
+ if ( 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<long>(a7);
+ } // of if
+ else
+ {
+ const long n8 = n4 * n5;
+ const long n8_2 = n8 / 2;
+
+ if( n7 < 0 )
+ {
+ if( ( n7 - LONG_MIN ) >= n8_2 )
+ n7 -= n8_2;
+ }
+ else if( ( LONG_MAX - n7 ) >= n8_2 )
+ n7 += n8_2;
+
+ return n7 / n8;
+ } // of else
+ } // of else
+ } // of else
+}
+
+// return (n1 * n2) / n3
+static long fn3( const long n1, const long n2, const long n3 )
+{
+ if ( n1 == 0 || n2 == 0 || n3 == 0 )
+ return 0;
+ if ( LONG_MAX / std::abs(n1) < std::abs(n2) )
+ {
+ BigInt a4 = n1;
+ a4 *= n2;
+
+ if ( a4.IsNeg() )
+ a4 -= n3 / 2;
+ else
+ a4 += n3 / 2;
+
+ a4 /= n3;
+ return static_cast<long>(a4);
+ } // of if
+ else
+ {
+ long n4 = n1 * n2;
+ const long n3_2 = n3 / 2;
+
+ if( n4 < 0 )
+ {
+ if( ( n4 - LONG_MIN ) >= n3_2 )
+ n4 -= n3_2;
+ }
+ else if( ( LONG_MAX - n4 ) >= n3_2 )
+ n4 += n3_2;
+
+ return n4 / n3;
+ } // of else
+}
+
+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())
+ {
+ ENTER3( eUnitSource, eUnitDest );
+
+ return Point( fn3( rPtSource.X(), nNumerator, nDenominator ),
+ fn3( rPtSource.Y(), nNumerator, nDenominator ) );
+ }
+ else
+ {
+ 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())
+ {
+ ENTER3( eUnitSource, eUnitDest );
+
+ return Size( fn3( rSzSource.Width(), nNumerator, nDenominator ),
+ fn3( rSzSource.Height(), nNumerator, nDenominator ) );
+ }
+ else
+ {
+ 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())
+ {
+ ENTER3(eUnitSource, eUnitDest);
+
+ const double fScaleFactor(static_cast<double>(nNumerator) / static_cast<double>(nDenominator));
+ aTransform.set(0, 0, fScaleFactor);
+ aTransform.set(1, 1, fScaleFactor);
+ }
+ else
+ {
+ 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 );
+
+ if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple())
+ {
+ ENTER3( eUnitSource, eUnitDest );
+
+ auto left = fn3( rRectSource.Left(), nNumerator, nDenominator );
+ auto top = fn3( rRectSource.Top(), nNumerator, nDenominator );
+ if (rRectSource.IsEmpty())
+ return tools::Rectangle( left, top );
+
+ auto right = fn3( rRectSource.Right(), nNumerator, nDenominator );
+ auto bottom = fn3( rRectSource.Bottom(), nNumerator, nDenominator );
+ return tools::Rectangle(left, top, right, bottom);
+ }
+ else
+ {
+ 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;
+ if (rRectSource.IsEmpty())
+ return tools::Rectangle(left, top);
+
+ auto right = fn5( rRectSource.Right() + aMapResSource.mnMapOfsX,
+ aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX,
+ aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) -
+ aMapResDest.mnMapOfsX;
+ auto bottom = fn5( rRectSource.Bottom() + aMapResSource.mnMapOfsY,
+ aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY,
+ aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) -
+ aMapResDest.mnMapOfsY;
+ return tools::Rectangle(left, top, right, bottom);
+ }
+}
+
+long OutputDevice::LogicToLogic( long nLongSource,
+ MapUnit eUnitSource, MapUnit eUnitDest )
+{
+ if ( eUnitSource == eUnitDest )
+ return nLongSource;
+
+ verifyUnitSourceDest( eUnitSource, eUnitDest );
+ ENTER3( eUnitSource, eUnitDest );
+
+ return fn3( nLongSource, nNumerator, nDenominator );
+}
+
+void OutputDevice::SetPixelOffset( const Size& rOffset )
+{
+ mnOutOffOrigX = rOffset.Width();
+ mnOutOffOrigY = rOffset.Height();
+
+ mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresPixToLogX );
+ mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY,
+ maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY,
+ maThresRes.mnThresPixToLogY );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetPixelOffset( rOffset );
+}
+
+
+DeviceCoordinate OutputDevice::LogicWidthToDeviceCoordinate( long nWidth ) const
+{
+ if ( !mbMap )
+ return static_cast<DeviceCoordinate>(nWidth);
+
+#if VCL_FLOAT_DEVICE_PIXEL
+ return (double)nWidth * maMapRes.mfScaleX * mnDPIX;
+#else
+
+ return ImplLogicToPixel( nWidth, mnDPIX,
+ maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX,
+ maThresRes.mnThresLogToPixX );
+#endif
+}
+
+/* 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 000000000..209389cfb
--- /dev/null
+++ b/vcl/source/outdev/mask.cxx
@@ -0,0 +1,154 @@
+/* -*- 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 <cassert>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+#include <salbmp.hxx>
+
+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,
+ 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 )
+ {
+ 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 000000000..14ad647db
--- /dev/null
+++ b/vcl/source/outdev/nativecontrols.cxx
@@ -0,0 +1,356 @@
+/* -*- 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 <cassert>
+
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/window.hxx>
+#include <sal/log.hxx>
+
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/pdfextoutdevdata.hxx>
+
+#include <salgdi.hxx>
+
+static bool EnableNativeWidget( const OutputDevice& i_rDevice )
+{
+ const OutDevType eType( i_rDevice.GetOutDevType() );
+ switch ( eType )
+ {
+
+ case OUTDEV_WINDOW:
+ {
+ const vcl::Window* pWindow = dynamic_cast< const vcl::Window* >( &i_rDevice );
+ if (pWindow)
+ {
+ return pWindow->IsNativeWidgetEnabled();
+ }
+ else
+ {
+ SAL_WARN ("vcl.gdi", "Could not cast i_rDevice to Window");
+ assert (pWindow);
+ return false;
+ }
+ }
+
+ case OUTDEV_PDF:
+ [[fallthrough]];
+ case OUTDEV_VIRDEV:
+ {
+ const vcl::ExtOutDevData* pOutDevData( i_rDevice.GetExtOutDevData() );
+ const vcl::PDFExtOutDevData* pPDFData( dynamic_cast< const vcl::PDFExtOutDevData* >( pOutDevData ) );
+ return pPDFData == nullptr;
+ }
+
+ default:
+ return false;
+ }
+}
+
+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( !EnableNativeWidget( *this ) )
+ return false;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+
+ return mpGraphics->IsNativeControlSupported(nType, nPart);
+}
+
+bool OutputDevice::HitTestNativeScrollbar(
+ ControlPart nPart,
+ const tools::Rectangle& rControlRegion,
+ const Point& aPos,
+ bool& rIsInside ) const
+{
+ if( !EnableNativeWidget( *this ) )
+ return false;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+
+ 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::shared_ptr< ImplControlValue > TransformControlValue( const ImplControlValue& rVal, const OutputDevice& rDev )
+{
+ std::shared_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_shared<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( !EnableNativeWidget( *this ) )
+ return false;
+
+ // make sure the current clip region is initialized correctly
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+
+ 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::shared_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( !EnableNativeWidget( *this ) )
+ return false;
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return false;
+
+ // Convert the coordinates from relative to Window-absolute, so we draw
+ // in the correct place in platform code
+ std::shared_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 000000000..020a57a6a
--- /dev/null
+++ b/vcl/source/outdev/outdev.cxx
@@ -0,0 +1,707 @@
+/* -*- 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/gdimtf.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/toolkit/unowrap.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <salgdi.hxx>
+#include <window.h>
+#include <outdev.h>
+
+#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
+
+// Begin initializer and accessor public functions
+
+OutputDevice::OutputDevice(OutDevType eOutDevType) :
+ meOutDevType(eOutDevType),
+ maRegion(true),
+ maFillColor( COL_WHITE ),
+ maTextLineColor( COL_TRANSPARENT ),
+ mxSettings( new AllSettings(Application::GetSettings()) )
+{
+ mpGraphics = nullptr;
+ mpUnoGraphicsList = nullptr;
+ mpPrevGraphics = nullptr;
+ mpNextGraphics = nullptr;
+ mpMetaFile = nullptr;
+ mpFontInstance = nullptr;
+ mpDeviceFontList = nullptr;
+ mpDeviceFontSizeList = 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 = ComplexTextLayoutFlags::Default;
+
+ if( AllSettings::GetLayoutRTL() ) //#i84553# tip BiDi preference to RTL
+ mnTextLayoutMode = ComplexTextLayoutFlags::BiDiRtl | 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 ImplThresholdRes
+ maThresRes.mnThresLogToPixX = 0;
+ maThresRes.mnThresLogToPixY = 0;
+ maThresRes.mnThresPixToLogX = 0;
+ maThresRes.mnThresPixToLogY = 0;
+
+ // 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();
+
+ // remove cached results of GetDevFontList/GetDevSizeList
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.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 )
+{
+ *mxSettings = rSettings;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetSettings( rSettings );
+}
+
+SystemGraphicsData OutputDevice::GetSystemGfxData() const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return SystemGraphicsData();
+
+ return mpGraphics->GetGraphicsData();
+}
+
+#if ENABLE_CAIRO_CANVAS
+
+bool OutputDevice::SupportsCairo() const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return false;
+
+ return mpGraphics->SupportsCairo();
+}
+
+cairo::SurfaceSharedPtr OutputDevice::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return cairo::SurfaceSharedPtr();
+ return mpGraphics->CreateSurface(rSurface);
+}
+
+cairo::SurfaceSharedPtr OutputDevice::CreateSurface(int x, int y, int width, int height) const
+{
+ if (!mpGraphics && !AcquireGraphics())
+ return cairo::SurfaceSharedPtr();
+ 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();
+ 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();
+ 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::makeAny(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 );
+}
+
+sal_uInt16 OutputDevice::GetBitCount() const
+{
+ // we need a graphics instance
+ if ( !mpGraphics && !AcquireGraphics() )
+ return 0;
+
+ return mpGraphics->GetBitCount();
+}
+
+void OutputDevice::SetOutOffXPixel(long nOutOffX)
+{
+ mnOutOffX = nOutOffX;
+}
+
+void OutputDevice::SetOutOffYPixel(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;
+ 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;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ long nSrcWidth = ImplLogicWidthToDevicePixel( rSrcSize.Width() );
+ long nSrcHeight = ImplLogicHeightToDevicePixel( rSrcSize.Height() );
+ long nDestWidth = ImplLogicWidthToDevicePixel( rDestSize.Width() );
+ 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);
+
+ const tools::Rectangle aSrcOutRect( Point( mnOutOffX, mnOutOffY ),
+ Size( mnOutWidth, mnOutHeight ) );
+
+ AdjustTwoRect( aPosAry, aSrcOutRect );
+
+ if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight )
+ mpGraphics->CopyBits( aPosAry, nullptr, this, nullptr );
+ }
+
+ 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;
+
+ 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;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ long nSrcWidth = ImplLogicWidthToDevicePixel( rSrcSize.Width() );
+ 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);
+
+ const tools::Rectangle aSrcOutRect( Point( mnOutOffX, mnOutOffY ),
+ Size( mnOutWidth, mnOutHeight ) );
+
+ AdjustTwoRect( aPosAry, aSrcOutRect );
+
+ 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, nullptr, this, nullptr);
+}
+
+// Direct OutputDevice drawing private function
+
+void OutputDevice::drawOutDevDirect( const OutputDevice* pSrcDev, SalTwoRect& rPosAry )
+{
+ SalGraphics* pSrcGraphics;
+ if (const OutputDevice* pCheckedSrc = DrawOutDevDirectCheck(pSrcDev))
+ {
+ if (!pCheckedSrc->mpGraphics && !pCheckedSrc->AcquireGraphics())
+ return;
+ pSrcGraphics = pCheckedSrc->mpGraphics;
+ }
+ else
+ pSrcGraphics = nullptr;
+
+ if (!mpGraphics && !AcquireGraphics())
+ return;
+
+ // #102532# Offset only has to be pseudo window offset
+ const tools::Rectangle aSrcOutRect( Point( pSrcDev->mnOutOffX, pSrcDev->mnOutOffY ),
+ Size( pSrcDev->mnOutWidth, pSrcDev->mnOutHeight ) );
+
+ AdjustTwoRect( rPosAry, aSrcOutRect );
+
+ if ( rPosAry.mnSrcWidth && rPosAry.mnSrcHeight && rPosAry.mnDestWidth && rPosAry.mnDestHeight )
+ {
+ // if this is no window, but pSrcDev is a window
+ // mirroring may be required
+ // because only windows have a SalGraphicsLayout
+ // mirroring is performed here
+ DrawOutDevDirectProcess( pSrcDev, rPosAry, pSrcGraphics);
+ }
+}
+
+const OutputDevice* OutputDevice::DrawOutDevDirectCheck(const OutputDevice* pSrcDev) const
+{
+ return this == pSrcDev ? nullptr : pSrcDev;
+}
+
+void OutputDevice::DrawOutDevDirectProcess( const OutputDevice* pSrcDev, SalTwoRect& rPosAry, SalGraphics* pSrcGraphics )
+{
+ if( pSrcGraphics && (pSrcGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) )
+ {
+ SalTwoRect aPosAry2 = rPosAry;
+ pSrcGraphics->mirror( aPosAry2.mnSrcX, aPosAry2.mnSrcWidth, pSrcDev );
+ mpGraphics->CopyBits( aPosAry2, pSrcGraphics, this, pSrcDev );
+ }
+ else
+ mpGraphics->CopyBits( rPosAry, pSrcGraphics, this, pSrcDev );
+}
+
+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
+{
+ 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;
+}
+
+// EPS public function
+
+bool OutputDevice::DrawEPS( const Point& rPoint, const Size& rSize,
+ const GfxLink& rGfxLink, GDIMetaFile* pSubst )
+{
+ bool bDrawn(true);
+
+ if ( mpMetaFile )
+ {
+ GDIMetaFile aSubst;
+
+ if( pSubst )
+ aSubst = *pSubst;
+
+ mpMetaFile->AddAction( new MetaEPSAction( rPoint, rSize, rGfxLink, aSubst ) );
+ }
+
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return bDrawn;
+
+ if( mbOutputClipped )
+ return bDrawn;
+
+ tools::Rectangle aRect( ImplLogicToDevicePixel( tools::Rectangle( rPoint, rSize ) ) );
+
+ if( !aRect.IsEmpty() )
+ {
+ // draw the real EPS graphics
+ if( rGfxLink.GetData() && rGfxLink.GetDataSize() )
+ {
+ if( !mpGraphics && !AcquireGraphics() )
+ return bDrawn;
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ aRect.Justify();
+ 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: */
diff --git a/vcl/source/outdev/outdevstate.cxx b/vcl/source/outdev/outdevstate.cxx
new file mode 100644
index 000000000..a9ca52d94
--- /dev/null
+++ b/vcl/source/outdev/outdevstate.cxx
@@ -0,0 +1,609 @@
+/* -*- 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/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdevstate.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/settings.hxx>
+
+#include <outdev.h>
+#include <outdata.hxx>
+#include <salgdi.hxx>
+
+OutDevState::OutDevState()
+ : mbMapActive(false)
+ , meTextAlign(ALIGN_TOP)
+ , meRasterOp(RasterOp::OverPaint)
+ , mnTextLayoutMode(ComplexTextLayoutFlags::Default)
+ , meTextLanguage(0)
+ , mnFlags(PushFlags::NONE)
+{
+}
+
+OutDevState::OutDevState(OutDevState&&) = default;
+
+OutDevState::~OutDevState()
+{
+ mpLineColor.reset();
+ mpFillColor.reset();
+ mpFont.reset();
+ mpTextColor.reset();
+ mpTextFillColor.reset();
+ mpTextLineColor.reset();
+ mpOverlineColor.reset();
+ mpMapMode.reset();
+ mpClipRegion.reset();
+ mpRefPoint.reset();
+}
+
+void OutputDevice::Push( PushFlags nFlags )
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPushAction( nFlags ) );
+
+ maOutDevStateStack.emplace_back();
+ OutDevState& rState = maOutDevStateStack.back();
+
+ rState.mnFlags = nFlags;
+
+ if (nFlags & PushFlags::LINECOLOR && mbLineColor)
+ {
+ rState.mpLineColor = maLineColor;
+ }
+ if (nFlags & PushFlags::FILLCOLOR && mbFillColor)
+ {
+ rState.mpFillColor = maFillColor;
+ }
+ if ( nFlags & PushFlags::FONT )
+ rState.mpFont.reset( new vcl::Font( maFont ) );
+ if ( nFlags & PushFlags::TEXTCOLOR )
+ rState.mpTextColor = GetTextColor();
+ if (nFlags & PushFlags::TEXTFILLCOLOR && IsTextFillColor())
+ {
+ rState.mpTextFillColor = GetTextFillColor();
+ }
+ if (nFlags & PushFlags::TEXTLINECOLOR && IsTextLineColor())
+ {
+ rState.mpTextLineColor = GetTextLineColor();
+ }
+ if (nFlags & PushFlags::OVERLINECOLOR && IsOverlineColor())
+ {
+ rState.mpOverlineColor = GetOverlineColor();
+ }
+ if ( nFlags & PushFlags::TEXTALIGN )
+ rState.meTextAlign = GetTextAlign();
+ if( nFlags & PushFlags::TEXTLAYOUTMODE )
+ rState.mnTextLayoutMode = GetLayoutMode();
+ if( nFlags & PushFlags::TEXTLANGUAGE )
+ rState.meTextLanguage = GetDigitLanguage();
+ if ( nFlags & PushFlags::RASTEROP )
+ rState.meRasterOp = GetRasterOp();
+ if ( nFlags & PushFlags::MAPMODE )
+ {
+ rState.mpMapMode = maMapMode;
+ rState.mbMapActive = mbMap;
+ }
+ if (nFlags & PushFlags::CLIPREGION && mbClipRegion)
+ {
+ rState.mpClipRegion.reset( new vcl::Region( maRegion ) );
+ }
+ if (nFlags & PushFlags::REFPOINT && mbRefPoint)
+ {
+ rState.mpRefPoint = maRefPoint;
+ }
+
+ 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 OutDevState& rState = maOutDevStateStack.back();
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->Pop();
+
+ if ( rState.mnFlags & PushFlags::LINECOLOR )
+ {
+ if ( rState.mpLineColor )
+ SetLineColor( *rState.mpLineColor );
+ else
+ SetLineColor();
+ }
+
+ if ( rState.mnFlags & PushFlags::FILLCOLOR )
+ {
+ if ( rState.mpFillColor )
+ SetFillColor( *rState.mpFillColor );
+ else
+ SetFillColor();
+ }
+
+ if ( rState.mnFlags & PushFlags::FONT )
+ SetFont( *rState.mpFont );
+
+ if ( rState.mnFlags & PushFlags::TEXTCOLOR )
+ SetTextColor( *rState.mpTextColor );
+
+ if ( rState.mnFlags & PushFlags::TEXTFILLCOLOR )
+ {
+ if ( rState.mpTextFillColor )
+ SetTextFillColor( *rState.mpTextFillColor );
+ else
+ SetTextFillColor();
+ }
+
+ if ( rState.mnFlags & PushFlags::TEXTLINECOLOR )
+ {
+ if ( rState.mpTextLineColor )
+ SetTextLineColor( *rState.mpTextLineColor );
+ else
+ SetTextLineColor();
+ }
+
+ if ( rState.mnFlags & PushFlags::OVERLINECOLOR )
+ {
+ if ( rState.mpOverlineColor )
+ SetOverlineColor( *rState.mpOverlineColor );
+ else
+ SetOverlineColor();
+ }
+
+ if ( rState.mnFlags & PushFlags::TEXTALIGN )
+ SetTextAlign( rState.meTextAlign );
+
+ if( rState.mnFlags & PushFlags::TEXTLAYOUTMODE )
+ SetLayoutMode( rState.mnTextLayoutMode );
+
+ if( rState.mnFlags & PushFlags::TEXTLANGUAGE )
+ SetDigitLanguage( rState.meTextLanguage );
+
+ if ( rState.mnFlags & PushFlags::RASTEROP )
+ SetRasterOp( rState.meRasterOp );
+
+ if ( rState.mnFlags & PushFlags::MAPMODE )
+ {
+ if ( rState.mpMapMode )
+ SetMapMode( *rState.mpMapMode );
+ else
+ SetMapMode();
+ mbMap = rState.mbMapActive;
+ }
+
+ if ( rState.mnFlags & PushFlags::CLIPREGION )
+ SetDeviceClipRegion( rState.mpClipRegion.get() );
+
+ if ( rState.mnFlags & PushFlags::REFPOINT )
+ {
+ if ( rState.mpRefPoint )
+ SetRefPoint( *rState.mpRefPoint );
+ else
+ SetRefPoint();
+ }
+
+ maOutDevStateStack.pop_back();
+
+ mpMetaFile = pOldMetaFile;
+}
+
+sal_uInt32 OutputDevice::GetGCStackDepth() const
+{
+ return maOutDevStateStack.size();
+}
+
+void OutputDevice::ClearStack()
+{
+ sal_uInt32 nCount = GetGCStackDepth();
+ while( nCount-- )
+ Pop();
+}
+
+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->setAntiAliasB2DDraw(bool(mnAntialiasing & AntialiasingFlags::EnableB2dDraw));
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetAntialiasing( nMode );
+}
+
+void OutputDevice::SetDrawMode( DrawModeFlags nDrawMode )
+{
+
+ mnDrawMode = nDrawMode;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetDrawMode( nDrawMode );
+}
+
+void OutputDevice::SetLayoutMode( 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::SetRasterOp( RasterOp eRasterOp )
+{
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaRasterOpAction( eRasterOp ) );
+
+ if ( meRasterOp != eRasterOp )
+ {
+ meRasterOp = eRasterOp;
+ mbInitLineColor = mbInitFillColor = true;
+
+ if( mpGraphics || AcquireGraphics() )
+ mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp), RasterOp::Invert == meRasterOp );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetRasterOp( eRasterOp );
+}
+
+
+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( rColor );
+
+ if( mnDrawMode & ( DrawModeFlags::BlackFill | DrawModeFlags::WhiteFill |
+ DrawModeFlags::GrayFill | DrawModeFlags::NoFill |
+ DrawModeFlags::SettingsFill ) )
+ {
+ if( !ImplIsColorTransparent( aColor ) )
+ {
+ if( mnDrawMode & DrawModeFlags::BlackFill )
+ {
+ aColor = COL_BLACK;
+ }
+ else if( mnDrawMode & DrawModeFlags::WhiteFill )
+ {
+ aColor = COL_WHITE;
+ }
+ else if( mnDrawMode & DrawModeFlags::GrayFill )
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color( cLum, cLum, cLum );
+ }
+ else if( mnDrawMode & DrawModeFlags::NoFill )
+ {
+ aColor = COL_TRANSPARENT;
+ }
+ else if( mnDrawMode & DrawModeFlags::SettingsFill )
+ {
+ aColor = GetSettings().GetStyleSettings().GetWindowColor();
+ }
+ }
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaFillColorAction( aColor, true ) );
+
+ if ( ImplIsColorTransparent( aColor ) )
+ {
+ if ( mbFillColor )
+ {
+ mbInitFillColor = true;
+ mbFillColor = false;
+ maFillColor = COL_TRANSPARENT;
+ }
+ }
+ else
+ {
+ if ( maFillColor != aColor )
+ {
+ mbInitFillColor = true;
+ mbFillColor = true;
+ maFillColor = aColor;
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetFillColor( COL_BLACK );
+}
+
+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 = ImplDrawModeToColor( rColor );
+
+ if( mpMetaFile )
+ mpMetaFile->AddAction( new MetaLineColorAction( aColor, true ) );
+
+ if( ImplIsColorTransparent( aColor ) )
+ {
+ if ( mbLineColor )
+ {
+ mbInitLineColor = true;
+ mbLineColor = false;
+ maLineColor = COL_TRANSPARENT;
+ }
+ }
+ else
+ {
+ if( maLineColor != aColor )
+ {
+ mbInitLineColor = true;
+ mbLineColor = true;
+ maLineColor = aColor;
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetLineColor( COL_BLACK );
+}
+
+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 )
+ mpAlphaVDev->SetBackground( rBackground );
+}
+
+void OutputDevice::SetFont( const vcl::Font& rNewFont )
+{
+
+ vcl::Font aFont( rNewFont );
+ if ( mnDrawMode & (DrawModeFlags::BlackText | DrawModeFlags::WhiteText | DrawModeFlags::GrayText | DrawModeFlags::SettingsText |
+ DrawModeFlags::BlackFill | DrawModeFlags::WhiteFill | DrawModeFlags::GrayFill | DrawModeFlags::NoFill |
+ DrawModeFlags::SettingsFill ) )
+ {
+ Color aTextColor( aFont.GetColor() );
+
+ if ( mnDrawMode & DrawModeFlags::BlackText )
+ aTextColor = COL_BLACK;
+ else if ( mnDrawMode & DrawModeFlags::WhiteText )
+ aTextColor = COL_WHITE;
+ else if ( mnDrawMode & DrawModeFlags::GrayText )
+ {
+ const sal_uInt8 cLum = aTextColor.GetLuminance();
+ aTextColor = Color( cLum, cLum, cLum );
+ }
+ else if ( mnDrawMode & DrawModeFlags::SettingsText )
+ aTextColor = GetSettings().GetStyleSettings().GetFontColor();
+
+ aFont.SetColor( aTextColor );
+
+ bool bTransFill = aFont.IsTransparent();
+ if ( !bTransFill )
+ {
+ Color aTextFillColor( aFont.GetFillColor() );
+
+ if ( mnDrawMode & DrawModeFlags::BlackFill )
+ aTextFillColor = COL_BLACK;
+ else if ( mnDrawMode & DrawModeFlags::WhiteFill )
+ aTextFillColor = COL_WHITE;
+ else if ( mnDrawMode & DrawModeFlags::GrayFill )
+ {
+ const sal_uInt8 cLum = aTextFillColor.GetLuminance();
+ aTextFillColor = Color( cLum, cLum, cLum );
+ }
+ else if( mnDrawMode & DrawModeFlags::SettingsFill )
+ aTextFillColor = GetSettings().GetStyleSettings().GetWindowColor();
+ else if ( mnDrawMode & DrawModeFlags::NoFill )
+ {
+ aTextFillColor = COL_TRANSPARENT;
+ }
+
+ aFont.SetFillColor( aTextFillColor );
+ }
+ }
+
+ 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 ) )
+ {
+ // 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 )
+ {
+ // #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_BLACK );
+ aFont.SetColor( COL_TRANSPARENT );
+ }
+
+ mpAlphaVDev->SetFont( aFont );
+ }
+ }
+}
+
+
+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::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;
+}
+
+void OutputDevice::ImplReleaseFonts()
+{
+ mpGraphics->ReleaseFonts();
+
+ mbNewFont = true;
+ mbInitFont = true;
+
+ mpFontInstance.clear();
+ mpDeviceFontList.reset();
+ mpDeviceFontSizeList.reset();
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/outdev/pixel.cxx b/vcl/source/outdev/pixel.cxx
new file mode 100644
index 000000000..0a80a8f1e
--- /dev/null
+++ b/vcl/source/outdev/pixel.cxx
@@ -0,0 +1,116 @@
+/* -*- 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 <cassert>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+Color OutputDevice::GetPixel(const Point& rPoint) const
+{
+ Color aColor;
+
+ if (mpGraphics || AcquireGraphics())
+ {
+ if (mbInitClipRegion)
+ const_cast<OutputDevice*>(this)->InitClipRegion();
+
+ if (!mbOutputClipped)
+ {
+ const long nX = ImplLogicXToDevicePixel(rPoint.X());
+ const long nY = ImplLogicYToDevicePixel(rPoint.Y());
+ aColor = mpGraphics->GetPixel(nX, nY, this);
+
+ if (mpAlphaVDev)
+ {
+ Color aAlphaColor = mpAlphaVDev->GetPixel(rPoint);
+ aColor.SetTransparency(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;
+
+ 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 = ImplDrawModeToColor( rColor );
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaPixelAction( rPt, aColor ) );
+
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ Point aPt = ImplLogicToDevicePixel( rPt );
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ mpGraphics->DrawPixel( aPt.X(), aPt.Y(), aColor, this );
+
+ if (mpAlphaVDev)
+ {
+ Color aAlphaColor(rColor.GetTransparency(), rColor.GetTransparency(), rColor.GetTransparency());
+ 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 000000000..e031fb059
--- /dev/null
+++ b/vcl/source/outdev/polygon.cxx
@@ -0,0 +1,518 @@
+/* -*- 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 <cassert>
+
+#include <sal/types.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <memory>
+#include <tools/poly.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+#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;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ // use b2dpolygon drawing if possible
+ if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) &&
+ mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ RasterOp::OverPaint == GetRasterOp() &&
+ (IsLineColor() || IsFillColor()))
+ {
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon());
+ bool bSuccess(true);
+
+ // ensure closed - may be asserted, will prevent buffering
+ if(!aB2DPolyPolygon.isClosed())
+ {
+ aB2DPolyPolygon.setClosed(true);
+ }
+
+ if(IsFillColor())
+ {
+ bSuccess = mpGraphics->DrawPolyPolygon(
+ aTransform,
+ aB2DPolyPolygon,
+ 0.0,
+ this);
+ }
+
+ if(bSuccess && IsLineColor())
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : 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;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ if ( mbInitFillColor )
+ InitFillColor();
+
+ // use b2dpolygon drawing if possible
+ if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) &&
+ mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ RasterOp::OverPaint == GetRasterOp() &&
+ (IsLineColor() || IsFillColor()))
+ {
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ basegfx::B2DPolygon aB2DPolygon(rPoly.getB2DPolygon());
+ bool bSuccess(true);
+
+ // ensure closed - maybe assert, hinders buffering
+ if(!aB2DPolygon.isClosed())
+ {
+ aB2DPolygon.setClosed(true);
+ }
+
+ if(IsFillColor())
+ {
+ bSuccess = mpGraphics->DrawPolyPolygon(
+ aTransform,
+ basegfx::B2DPolyPolygon(aB2DPolygon),
+ 0.0,
+ this);
+ }
+
+ if(bSuccess && 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 SalPoint* pPtAry = reinterpret_cast<const SalPoint*>(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 = reinterpret_cast<const SalPoint*>(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;
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ if( mbInitFillColor )
+ InitFillColor();
+
+ bool bSuccess(false);
+
+ if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) &&
+ mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ 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())
+ {
+ bSuccess = mpGraphics->DrawPolyPolygon(
+ aTransform,
+ aB2DPolyPolygon,
+ 0.0,
+ this);
+ }
+
+ if(bSuccess && IsLineColor())
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : 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];
+ PCONSTSALPOINT aStackAry2[OUTDEV_POLYPOLY_STACKBUF];
+ PolyFlags* aStackAry3[OUTDEV_POLYPOLY_STACKBUF];
+ sal_uInt32* pPointAry;
+ PCONSTSALPOINT* 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 PCONSTSALPOINT[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] = reinterpret_cast<PCONSTSALPOINT>(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(), reinterpret_cast<const SalPoint*>(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( rPoly, pClipPolyPoly );
+ }
+ else
+ {
+ sal_uInt16 nPoints = rPoly.GetSize();
+
+ if ( nPoints < 2 )
+ return;
+
+ const SalPoint* pPtAry = reinterpret_cast<const SalPoint*>(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 SalPoint* pPtAry = reinterpret_cast<const SalPoint*>(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<PCONSTSALPOINT[]> pPointAryAry(new PCONSTSALPOINT[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] = reinterpret_cast<PCONSTSALPOINT>(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 000000000..672854a9e
--- /dev/null
+++ b/vcl/source/outdev/polyline.cxx
@@ -0,0 +1,386 @@
+/* -*- 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 <cassert>
+
+#include <sal/types.h>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+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;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if ( mbInitLineColor )
+ InitLineColor();
+
+ // use b2dpolygon drawing if possible
+ if(DrawPolyLineDirect(
+ basegfx::B2DHomMatrix(),
+ rPoly.getB2DPolygon()))
+ {
+ return;
+ }
+
+ const basegfx::B2DPolygon aB2DPolyLine(rPoly.getB2DPolygon());
+ const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ if(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))
+ {
+ return;
+ }
+
+ tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly );
+ SalPoint* pPtAry = reinterpret_cast<SalPoint*>(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 = reinterpret_cast<SalPoint*>(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;
+ }
+
+ // #i101491#
+ // Try direct Fallback to B2D-Version of DrawPolyLine
+ if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) &&
+ LineStyle::Solid == rLineInfo.GetStyle())
+ {
+ DrawPolyLine(
+ rPoly.getB2DPolygon(),
+ static_cast< double >(rLineInfo.GetWidth()),
+ rLineInfo.GetLineJoin(),
+ rLineInfo.GetLineCap(),
+ basegfx::deg2rad(15.0) /* default fMiterMinimumAngle, value not available in LineInfo */);
+ 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( static_cast<long>(fLineWidth+0.5) );
+
+ const tools::Polygon aToolsPolygon( rB2DPolygon );
+ mpMetaFile->AddAction( new MetaPolyLineAction( aToolsPolygon, aLineInfo ) );
+ }
+
+ // Do not paint empty PolyPolygons
+ if(!rB2DPolygon.count() || !IsDeviceOutputNecessary())
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ // use b2dpolygon drawing if possible
+ if(DrawPolyLineDirect(
+ 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();
+
+ const bool bTryAA((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) &&
+ mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ RasterOp::OverPaint == GetRasterOp() &&
+ IsLineColor());
+
+ // when AA it is necessary to also paint the filled polygon's outline
+ // to avoid optical gaps
+ for(auto const& rPolygon : aAreaPolyPolygon)
+ {
+ (void)DrawPolyLineDirect(
+ basegfx::B2DHomMatrix(),
+ rPolygon,
+ 0.0,
+ 0.0,
+ nullptr, // MM01
+ basegfx::B2DLineJoin::NONE,
+ css::drawing::LineCap_BUTT,
+ basegfx::deg2rad(15.0) /*default, not used*/,
+ bTryAA);
+ }
+ }
+ else
+ {
+ // fallback to old polygon drawing if needed
+ const tools::Polygon aToolsPolygon( rB2DPolygon );
+ LineInfo aLineInfo;
+ if( fLineWidth != 0.0 )
+ aLineInfo.SetWidth( static_cast<long>(fLineWidth+0.5) );
+
+ 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;
+
+ tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly );
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ 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)
+ {
+ drawLine ( basegfx::B2DPolyPolygon(aPoly.getB2DPolygon()), aInfo );
+ }
+ else
+ {
+ // #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, reinterpret_cast<SalPoint*>(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,
+ bool bBypassAACheck)
+{
+ 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;
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return true;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ const bool bTryAA( bBypassAACheck ||
+ ((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) &&
+ mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ RasterOp::OverPaint == GetRasterOp() &&
+ IsLineColor()));
+
+ if(bTryAA)
+ {
+ // 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 )
+ {
+ // worked, add metafile action (if recorded) and return true
+ if( mpMetaFile )
+ {
+ LineInfo aLineInfo;
+ if( fLineWidth != 0.0 )
+ aLineInfo.SetWidth( static_cast<long>(fLineWidth+0.5) );
+ // Transport known information, might be needed
+ aLineInfo.SetLineJoin(eLineJoin);
+ aLineInfo.SetLineCap(eLineCap);
+ // MiterMinimumAngle does not exist yet in LineInfo
+ const tools::Polygon aToolsPolygon( rB2DPolygon );
+ mpMetaFile->AddAction( new MetaPolyLineAction( aToolsPolygon, aLineInfo ) );
+ }
+
+ if (mpAlphaVDev)
+ mpAlphaVDev->DrawPolyLineDirect(rObjectTransform, rB2DPolygon, fLineWidth,
+ fTransparency, pStroke, eLineJoin, eLineCap,
+ fMiterMinimumAngle, bBypassAACheck);
+
+ 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 000000000..ecbeb12fb
--- /dev/null
+++ b/vcl/source/outdev/rect.cxx
@@ -0,0 +1,415 @@
+/* -*- 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 <cassert>
+
+#include <sal/types.h>
+
+#include <tools/poly.hxx>
+#include <tools/helpers.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+#include <salgdi.hxx>
+
+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.Justify();
+
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ 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;
+
+ 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 )
+ {
+ SalPoint* pPtAry = reinterpret_cast<SalPoint*>(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.Justify();
+
+ // we need a graphics
+ if ( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ 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;
+
+ 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 SalPoint* pPtAry = reinterpret_cast<const SalPoint*>(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(PushFlags::LINECOLOR|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;
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ const long nDistX = std::max( rDist.Width(), 1L );
+ const long nDistY = std::max( rDist.Height(), 1L );
+ long nX = ( rRect.Left() >= aDstRect.Left() ) ? rRect.Left() : ( rRect.Left() + ( ( aDstRect.Left() - rRect.Left() ) / nDistX ) * nDistX );
+ long nY = ( rRect.Top() >= aDstRect.Top() ) ? rRect.Top() : ( rRect.Top() + ( ( aDstRect.Top() - rRect.Top() ) / nDistY ) * nDistY );
+ const long nRight = aDstRect.Right();
+ const long nBottom = aDstRect.Bottom();
+ const long nStartX = ImplLogicXToDevicePixel( nX );
+ const long nEndX = ImplLogicXToDevicePixel( nRight );
+ const long nStartY = ImplLogicYToDevicePixel( nY );
+ const long nEndY = ImplLogicYToDevicePixel( nBottom );
+ long nHorzCount = 0;
+ 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( long i = 0; i < nVertCount; i++ )
+ {
+ for( long j = 0, Y = aVertBuf[ i ]; j < nHorzCount; j++ )
+ {
+ mpGraphics->DrawPixel( aHorzBuf[ j ], Y, this );
+ }
+ }
+ }
+ else
+ {
+ if( nFlags & DrawGridFlags::HorzLines )
+ {
+ for( long i = 0; i < nVertCount; i++ )
+ {
+ nY = aVertBuf[ i ];
+ mpGraphics->DrawLine( nStartX, nY, nEndX, nY, this );
+ }
+ }
+
+ if( nFlags & DrawGridFlags::VertLines )
+ {
+ for( 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 long nDstX1 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Left() - rTwoRect.mnSrcX ) );
+ const long nDstY1 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Top() - rTwoRect.mnSrcY ) );
+ const long nDstX2 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Right() - rTwoRect.mnSrcX ) );
+ const 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() ) )
+ {
+ 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 long nDstX1 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Left() - rTwoRect.mnSrcX ) );
+ const long nDstY1 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Top() - rTwoRect.mnSrcY ) );
+ const long nDstX2 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Right() - rTwoRect.mnSrcX ) );
+ const 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/text.cxx b/vcl/source/outdev/text.cxx
new file mode 100644
index 000000000..34db8e629
--- /dev/null
+++ b/vcl/source/outdev/text.cxx
@@ -0,0 +1,2510 @@
+/* -*- 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 <memory>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <com/sun/star/linguistic2/LinguServiceManager.hpp>
+
+#include <comphelper/processfactory.hxx>
+#include <osl/file.h>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/lineend.hxx>
+#include <tools/debug.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/textrectinfo.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/sysdata.hxx>
+#include <vcl/unohelp.hxx>
+#include <vcl/toolkit/controllayout.hxx>
+#ifdef MACOSX
+# include <vcl/opengl/OpenGLHelper.hxx>
+#endif
+
+#include <outdata.hxx>
+#include <outdev.h>
+#include <salgdi.hxx>
+#include <svdata.hxx>
+#include <textlayout.hxx>
+#include <textlineinfo.hxx>
+#include <impglyphitem.hxx>
+#include <optional>
+
+#define TEXT_DRAW_ELLIPSIS (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis)
+
+ImplMultiTextLineInfo::ImplMultiTextLineInfo()
+{
+}
+
+ImplMultiTextLineInfo::~ImplMultiTextLineInfo()
+{
+}
+
+void ImplMultiTextLineInfo::AddLine( ImplTextLineInfo* pLine )
+{
+ mvLines.push_back(std::unique_ptr<ImplTextLineInfo>(pLine));
+}
+
+void ImplMultiTextLineInfo::Clear()
+{
+ mvLines.clear();
+}
+
+void OutputDevice::ImplInitTextColor()
+{
+ DBG_TESTSOLARMUTEX();
+
+ if ( mbInitTextColor )
+ {
+ mpGraphics->SetTextColor( GetTextColor() );
+ mbInitTextColor = false;
+ }
+}
+
+void OutputDevice::ImplDrawTextRect( long nBaseX, long nBaseY,
+ long nDistX, long nDistY, long nWidth, long nHeight )
+{
+ long nX = nDistX;
+ long nY = nDistY;
+
+ short nOrientation = mpFontInstance->mnOrientation;
+ if ( nOrientation )
+ {
+ // Rotate rect without rounding problems for 90 degree rotations
+ if ( !(nOrientation % 900) )
+ {
+ if ( nOrientation == 900 )
+ {
+ long nTemp = nX;
+ nX = nY;
+ nY = -nTemp;
+ nTemp = nWidth;
+ nWidth = nHeight;
+ nHeight = nTemp;
+ nY -= nHeight;
+ }
+ else if ( nOrientation == 1800 )
+ {
+ nX = -nX;
+ nY = -nY;
+ nX -= nWidth;
+ nY -= nHeight;
+ }
+ else /* ( nOrientation == 2700 ) */
+ {
+ 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 long nWidth = rSalLayout.GetTextWidth() / rSalLayout.GetUnitsPerPixel();
+ const Point aBase = rSalLayout.DrawBase();
+ const long nX = aBase.X();
+ const long nY = aBase.Y();
+
+ 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 )
+{
+ Point aPoint = rSalLayout.GetDrawPosition();
+ long nX = aPoint.X();
+ long nY = aPoint.Y();
+
+ long nWidth = rSalLayout.GetTextWidth();
+ long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent;
+
+ nY -= mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;
+
+ if ( mpFontInstance->mnOrientation )
+ {
+ long nBaseX = nX, nBaseY = nY;
+ if ( !(mpFontInstance->mnOrientation % 900) )
+ {
+ long nX2 = nX+nWidth;
+ 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 )
+{
+ long nX = rSalLayout.DrawBase().X();
+ long nY = rSalLayout.DrawBase().Y();
+
+ tools::Rectangle aBoundRect;
+ rSalLayout.DrawBase() = Point( 0, 0 );
+ rSalLayout.DrawOffset() = Point( 0, 0 );
+ if (!rSalLayout.GetBoundRect(aBoundRect))
+ {
+ // guess vertical text extents if GetBoundRect failed
+ long nRight = rSalLayout.GetTextWidth();
+ long nTop = mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent;
+ 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, DeviceFormat::BITMASK);
+ VirtualDevice* pVDev = mpOutDevData->mpRotateDev;
+
+ // size it accordingly
+ if( !pVDev->SetOutputSizePixel( aBoundRect.GetSize() ) )
+ return false;
+
+ const FontSelectPattern& rPattern = mpFontInstance->GetFontSelectPattern();
+ vcl::Font aFont( GetFont() );
+ aFont.SetOrientation( 0 );
+ 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() -= aBoundRect.TopLeft();
+ rSalLayout.DrawText( *pVDev->mpGraphics );
+
+ Bitmap aBmp = pVDev->GetBitmap( Point(), aBoundRect.GetSize() );
+ if ( !aBmp || !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;
+ long nOldOffX = mnOutOffX;
+ 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;
+
+ long nOldX = rSalLayout.DrawBase().X();
+ if( HasMirroredGraphics() )
+ {
+ long w = IsVirtual() ? mnOutWidth : mpGraphics->GetGraphicsWidth();
+ long x = rSalLayout.DrawBase().X();
+ rSalLayout.DrawBase().setX( w - 1 - x );
+ if( !IsRTLEnabled() )
+ {
+ OutputDevice *pOutDevRef = this;
+ // mirror this window back
+ long devX = w-pOutDevRef->mnOutWidth-pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX
+ rSalLayout.DrawBase().setX( devX + ( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().X() - devX) ) ) ;
+ }
+ }
+ else if( IsRTLEnabled() )
+ {
+ OutputDevice *pOutDevRef = this;
+
+ // mirror this window back
+ long devX = pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX
+ rSalLayout.DrawBase().setX( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().X() - 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();
+
+ Point 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
+ 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() )
+ {
+ 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() += Point( nOff, nOff );
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() -= Point( nOff, nOff );
+ SetTextColor( aOldColor );
+ SetTextLineColor( aOldTextLineColor );
+ SetOverlineColor( aOldOverlineColor );
+ ImplInitTextColor();
+
+ if ( !maFont.IsOutline() )
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ }
+
+ if ( maFont.IsOutline() )
+ {
+ rSalLayout.DrawBase() = aOrigPos + Point(-1,-1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + Point(+1,+1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + Point(-1,+0);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + Point(-1,+1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + Point(+0,+1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + Point(+0,-1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + Point(+1,-1);
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+ rSalLayout.DrawBase() = aOrigPos + Point(+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() += Point( mnTextOffX, mnTextOffY );
+
+ if( IsTextFillColor() )
+ ImplDrawTextBackground( rSalLayout );
+
+ if( mbTextSpecial )
+ ImplDrawSpecialText( rSalLayout );
+ else
+ ImplDrawTextDirect( rSalLayout, mbTextLines );
+}
+
+long OutputDevice::ImplGetTextLines( ImplMultiTextLineInfo& rLineInfo,
+ long nWidth, const OUString& rStr,
+ DrawTextFlags nStyle, const vcl::ITextLayout& _rLayout )
+{
+ SAL_WARN_IF( nWidth <= 0, "vcl", "ImplGetTextLines: nWidth <= 0!" );
+
+ if ( nWidth <= 0 )
+ nWidth = 1;
+
+ long nMaxLineWidth = 0;
+ rLineInfo.Clear();
+ if (!rStr.isEmpty())
+ {
+ const bool bHyphenate = (nStyle & DrawTextFlags::WordBreakHyphenation) == DrawTextFlags::WordBreakHyphenation;
+ css::uno::Reference< css::linguistic2::XHyphenator > xHyph;
+ if (bHyphenate)
+ {
+ // get service provider
+ css::uno::Reference<css::uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
+ css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLinguMgr = css::linguistic2::LinguServiceManager::create(xContext);
+ xHyph = xLinguMgr->getHyphenator();
+ }
+
+ css::uno::Reference<css::i18n::XBreakIterator> xBI;
+ sal_Int32 nPos = 0;
+ sal_Int32 nLen = rStr.getLength();
+ while ( nPos < nLen )
+ {
+ sal_Int32 nBreakPos = nPos;
+
+ while ( ( nBreakPos < nLen ) && ( rStr[ nBreakPos ] != '\r' ) && ( rStr[ nBreakPos ] != '\n' ) )
+ nBreakPos++;
+
+ long nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos );
+ if ( ( nLineWidth > nWidth ) && ( nStyle & DrawTextFlags::WordBreak ) )
+ {
+ if ( !xBI.is() )
+ xBI = vcl::unohelper::CreateBreakIterator();
+
+ if ( xBI.is() )
+ {
+ const css::lang::Locale& rDefLocale(Application::GetSettings().GetUILanguageTag().getLocale());
+ sal_Int32 nSoftBreak = _rLayout.GetTextBreak( rStr, nWidth, nPos, nBreakPos - nPos );
+ if (nSoftBreak == -1)
+ {
+ nSoftBreak = nPos;
+ }
+ SAL_WARN_IF( nSoftBreak >= nBreakPos, "vcl", "Break?!" );
+ css::i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, css::uno::Sequence <css::beans::PropertyValue>(), 1 );
+ css::i18n::LineBreakUserOptions aUserOptions;
+ css::i18n::LineBreakResults aLBR = xBI->getLineBreak( rStr, nSoftBreak, rDefLocale, nPos, aHyphOptions, aUserOptions );
+ nBreakPos = aLBR.breakIndex;
+ if ( nBreakPos <= nPos )
+ nBreakPos = nSoftBreak;
+ if ( bHyphenate )
+ {
+ // Whether hyphen or not: Put the word after the hyphen through
+ // word boundary.
+
+ // nMaxBreakPos the last char that fits into the line
+ // nBreakPos is the word's start
+
+ // We run into a problem if the doc is so narrow, that a word
+ // is broken into more than two lines ...
+ if ( xHyph.is() )
+ {
+ sal_Unicode cAlternateReplChar = 0;
+ css::i18n::Boundary aBoundary = xBI->getWordBoundary( rStr, nBreakPos, rDefLocale, css::i18n::WordType::DICTIONARY_WORD, true );
+ sal_Int32 nWordStart = nPos;
+ sal_Int32 nWordEnd = aBoundary.endPos;
+ SAL_WARN_IF( nWordEnd <= nWordStart, "vcl", "ImpBreakLine: Start >= End?" );
+
+ sal_Int32 nWordLen = nWordEnd - nWordStart;
+ if ( ( nWordEnd >= nSoftBreak ) && ( nWordLen > 3 ) )
+ {
+ // #104415# May happen, because getLineBreak may differ from getWordBoudary with DICTIONARY_WORD
+ // SAL_WARN_IF( nWordEnd < nMaxBreakPos, "vcl", "Hyph: Break?" );
+ OUString aWord = rStr.copy( nWordStart, nWordLen );
+ sal_Int32 nMinTrail = nWordEnd-nSoftBreak+1; //+1: Before the "broken off" char
+ css::uno::Reference< css::linguistic2::XHyphenatedWord > xHyphWord;
+ if (xHyph.is())
+ xHyphWord = xHyph->hyphenate( aWord, rDefLocale, aWord.getLength() - nMinTrail, css::uno::Sequence< css::beans::PropertyValue >() );
+ if (xHyphWord.is())
+ {
+ bool bAlternate = xHyphWord->isAlternativeSpelling();
+ sal_Int32 _nWordLen = 1 + xHyphWord->getHyphenPos();
+
+ if ( ( _nWordLen >= 2 ) && ( (nWordStart+_nWordLen) >= 2 ) )
+ {
+ if ( !bAlternate )
+ {
+ nBreakPos = nWordStart + _nWordLen;
+ }
+ else
+ {
+ OUString aAlt( xHyphWord->getHyphenatedWord() );
+
+ // We can have two cases:
+ // 1) "packen" turns into "pak-ken"
+ // 2) "Schiffahrt" turns into "Schiff-fahrt"
+
+ // In case 1 we need to replace a char
+ // In case 2 we add a char
+
+ // Correct recognition is made harder by words such as
+ // "Schiffahrtsbrennesseln", as the Hyphenator splits all
+ // positions of the word and comes up with "Schifffahrtsbrennnesseln"
+ // Thus, we cannot infer the aWord from the AlternativeWord's
+ // index.
+ // TODO: The whole junk will be made easier by a function in
+ // the Hyphenator, as soon as AMA adds it.
+ sal_Int32 nAltStart = _nWordLen - 1;
+ sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength());
+ sal_Int32 nTxtEnd = nTxtStart;
+ sal_Int32 nAltEnd = nAltStart;
+
+ // The area between nStart and nEnd is the difference
+ // between AlternativeString and OriginalString
+ while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() &&
+ aWord[nTxtEnd] != aAlt[nAltEnd] )
+ {
+ ++nTxtEnd;
+ ++nAltEnd;
+ }
+
+ // If a char was added, we notice it now:
+ if( nAltEnd > nTxtEnd && nAltStart == nAltEnd &&
+ aWord[ nTxtEnd ] == aAlt[nAltEnd] )
+ {
+ ++nAltEnd;
+ ++nTxtStart;
+ ++nTxtEnd;
+ }
+
+ SAL_WARN_IF( ( nAltEnd - nAltStart ) != 1, "vcl", "Alternate: Wrong assumption!" );
+
+ if ( nTxtEnd > nTxtStart )
+ cAlternateReplChar = aAlt[ nAltStart ];
+
+ nBreakPos = nWordStart + nTxtStart;
+ if ( cAlternateReplChar )
+ nBreakPos++;
+ }
+ }
+ }
+ }
+ }
+ }
+ nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos );
+ }
+ else
+ {
+ // fallback to something really simple
+ sal_Int32 nSpacePos = rStr.getLength();
+ long nW = 0;
+ do
+ {
+ nSpacePos = rStr.lastIndexOf( ' ', nSpacePos );
+ if( nSpacePos != -1 )
+ {
+ if( nSpacePos > nPos )
+ nSpacePos--;
+ nW = _rLayout.GetTextWidth( rStr, nPos, nSpacePos-nPos );
+ }
+ } while( nW > nWidth );
+
+ if( nSpacePos != -1 )
+ {
+ nBreakPos = nSpacePos;
+ nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos );
+ if( nBreakPos < rStr.getLength()-1 )
+ nBreakPos++;
+ }
+ }
+ }
+
+ if ( nLineWidth > nMaxLineWidth )
+ nMaxLineWidth = nLineWidth;
+
+ rLineInfo.AddLine( new ImplTextLineInfo( nLineWidth, nPos, nBreakPos-nPos ) );
+
+ if ( nBreakPos == nPos )
+ nBreakPos++;
+ nPos = nBreakPos;
+
+ if ( nPos < nLen && ( ( rStr[ nPos ] == '\r' ) || ( rStr[ nPos ] == '\n' ) ) )
+ {
+ nPos++;
+ // CR/LF?
+ if ( ( nPos < nLen ) && ( rStr[ nPos ] == '\n' ) && ( rStr[ nPos-1 ] == '\r' ) )
+ nPos++;
+ }
+ }
+ }
+#ifdef DBG_UTIL
+ for ( sal_Int32 nL = 0; nL < rLineInfo.Count(); nL++ )
+ {
+ ImplTextLineInfo* pLine = rLineInfo.GetLine( nL );
+ OUString aLine = rStr.copy( pLine->GetIndex(), pLine->GetLen() );
+ SAL_WARN_IF( aLine.indexOf( '\r' ) != -1, "vcl", "ImplGetTextLines - Found CR!" );
+ SAL_WARN_IF( aLine.indexOf( '\n' ) != -1, "vcl", "ImplGetTextLines - Found LF!" );
+ }
+#endif
+
+ return nMaxLineWidth;
+}
+
+void OutputDevice::SetTextColor( const Color& rColor )
+{
+
+ Color aColor( rColor );
+
+ if ( mnDrawMode & ( DrawModeFlags::BlackText | DrawModeFlags::WhiteText |
+ DrawModeFlags::GrayText |
+ DrawModeFlags::SettingsText ) )
+ {
+ if ( mnDrawMode & DrawModeFlags::BlackText )
+ aColor = COL_BLACK;
+ else if ( mnDrawMode & DrawModeFlags::WhiteText )
+ aColor = COL_WHITE;
+ else if ( mnDrawMode & DrawModeFlags::GrayText )
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color( cLum, cLum, cLum );
+ }
+ else if ( mnDrawMode & DrawModeFlags::SettingsText )
+ aColor = GetSettings().GetStyleSettings().GetFontColor();
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextColorAction( aColor ) );
+
+ if ( maTextColor != aColor )
+ {
+ maTextColor = aColor;
+ mbInitTextColor = true;
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextColor( COL_BLACK );
+}
+
+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( rColor );
+ bool bTransFill = ImplIsColorTransparent( aColor );
+
+ if ( !bTransFill )
+ {
+ if ( mnDrawMode & ( DrawModeFlags::BlackFill | DrawModeFlags::WhiteFill |
+ DrawModeFlags::GrayFill | DrawModeFlags::NoFill |
+ DrawModeFlags::SettingsFill ) )
+ {
+ if ( mnDrawMode & DrawModeFlags::BlackFill )
+ aColor = COL_BLACK;
+ else if ( mnDrawMode & DrawModeFlags::WhiteFill )
+ aColor = COL_WHITE;
+ else if ( mnDrawMode & DrawModeFlags::GrayFill )
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color( cLum, cLum, cLum );
+ }
+ else if( mnDrawMode & DrawModeFlags::SettingsFill )
+ aColor = GetSettings().GetStyleSettings().GetWindowColor();
+ else if ( mnDrawMode & DrawModeFlags::NoFill )
+ {
+ aColor = COL_TRANSPARENT;
+ bTransFill = true;
+ }
+ }
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextFillColorAction( aColor, true ) );
+
+ if ( maFont.GetFillColor() != aColor )
+ maFont.SetFillColor( aColor );
+ if ( maFont.IsTransparent() != bTransFill )
+ maFont.SetTransparent( bTransFill );
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextFillColor( COL_BLACK );
+}
+
+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();
+}
+
+void OutputDevice::DrawText( const Point& rStartPt, const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ MetricVector* 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() )
+ {
+ MetricVector aTmp;
+ GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, aTmp );
+
+ bool bInserted = false;
+ for( MetricVector::const_iterator it = aTmp.begin(); it != aTmp.end(); ++it, nIndex++ )
+ {
+ bool bAppend = false;
+
+ if( aClip.IsOver( *it ) )
+ bAppend = true;
+ else if( rStr[ nIndex ] == ' ' && bInserted )
+ {
+ MetricVector::const_iterator next = it;
+ ++next;
+ if( next != aTmp.end() && aClip.IsOver( *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.copy( nIndex, nLen );
+ }
+ }
+
+ if ( !IsDeviceOutputNecessary() || pVector )
+ return;
+
+ if(mpFontInstance)
+ // do not use cache with modified string
+ if(mpFontInstance->mpConversion)
+ pLayoutCache = nullptr;
+
+#ifdef MACOSX
+ // FIXME: tdf#112990
+ // Cache text layout crashes on mac with OpenGL enabled
+ // Force it to not use the cache
+ if(OpenGLHelper::isVCLOpenGLEnabled())
+ pLayoutCache = nullptr;
+#endif
+
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, nullptr, SalLayoutFlags::NONE, nullptr, pLayoutCache);
+ if(pSalLayout)
+ {
+ ImplDrawText( *pSalLayout );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawText( rStartPt, rStr, nIndex, nLen, pVector, pDisplayText );
+}
+
+long OutputDevice::GetTextWidth( const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen,
+ vcl::TextLayoutCache const*const pLayoutCache,
+ SalLayoutGlyphs const*const pSalLayoutCache) const
+{
+
+ long nWidth = GetTextArray( rStr, nullptr, nIndex,
+ nLen, pLayoutCache, pSalLayoutCache );
+
+ return nWidth;
+}
+
+long OutputDevice::GetTextHeight() const
+{
+ if (!InitFont())
+ return 0;
+
+ 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,
+ const long* pDXAry,
+ 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, nIndex, nLen ) );
+
+ if ( !IsDeviceOutputNecessary() )
+ return;
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+ if( mbInitClipRegion )
+ InitClipRegion();
+ if( mbOutputClipped )
+ return;
+
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry, flags, nullptr, pSalLayoutCache);
+ if( pSalLayout )
+ {
+ ImplDrawText( *pSalLayout );
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawTextArray( rStartPt, rStr, pDXAry, nIndex, nLen, flags );
+}
+
+long OutputDevice::GetTextArray( const OUString& rStr, long* pDXAry,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ vcl::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;
+ }
+
+ // do layout
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen,
+ Point(0,0), 0, nullptr, SalLayoutFlags::NONE, 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)
+ {
+ memset(pDXAry, 0, nLen * sizeof(*pDXAry));
+ }
+ return 0;
+ }
+
+#if VCL_FLOAT_DEVICE_PIXEL
+ std::unique_ptr<DeviceCoordinate[]> pDXPixelArray;
+ if(pDXAry)
+ {
+ pDXPixelArray.reset(new DeviceCoordinate[nLen]);
+ }
+ DeviceCoordinate nWidth = pSalLayout->FillDXArray( pDXPixelArray.get() );
+ int nWidthFactor = pSalLayout->GetUnitsPerPixel();
+
+ // convert virtual char widths to virtual absolute positions
+ if( pDXPixelArray )
+ {
+ for( int i = 1; i < nLen; ++i )
+ {
+ pDXPixelArray[ i ] += pDXPixelArray[ i-1 ];
+ }
+ }
+ if( mbMap )
+ {
+ if( pDXPixelArray )
+ {
+ for( int i = 0; i < nLen; ++i )
+ {
+ pDXPixelArray[i] = ImplDevicePixelToLogicWidth( pDXPixelArray[i] );
+ }
+ }
+ nWidth = ImplDevicePixelToLogicWidth( nWidth );
+ }
+ if( nWidthFactor > 1 )
+ {
+ if( pDXPixelArray )
+ {
+ for( int i = 0; i < nLen; ++i )
+ {
+ pDXPixelArray[i] /= nWidthFactor;
+ }
+ }
+ nWidth /= nWidthFactor;
+ }
+ if(pDXAry)
+ {
+ for( int i = 0; i < nLen; ++i )
+ {
+ pDXAry[i] = basegfx::fround(pDXPixelArray[i]);
+ }
+ }
+ return basegfx::fround(nWidth);
+
+#else /* ! VCL_FLOAT_DEVICE_PIXEL */
+
+ long nWidth = pSalLayout->FillDXArray( pDXAry );
+ int nWidthFactor = pSalLayout->GetUnitsPerPixel();
+
+ // convert virtual char widths to virtual absolute positions
+ if( pDXAry )
+ for( int i = 1; i < nLen; ++i )
+ pDXAry[ i ] += pDXAry[ i-1 ];
+
+ // convert from font units to logical units
+ if( mbMap )
+ {
+ if( pDXAry )
+ for( int i = 0; i < nLen; ++i )
+ pDXAry[i] = ImplDevicePixelToLogicWidth( pDXAry[i] );
+ nWidth = ImplDevicePixelToLogicWidth( nWidth );
+ }
+
+ if( nWidthFactor > 1 )
+ {
+ if( pDXAry )
+ for( int i = 0; i < nLen; ++i )
+ pDXAry[i] /= nWidthFactor;
+ nWidth /= nWidthFactor;
+ }
+ return nWidth;
+#endif /* VCL_FLOAT_DEVICE_PIXEL */
+}
+
+void OutputDevice::GetCaretPositions( const OUString& rStr, long* pCaretXArray,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ const SalLayoutGlyphs* pGlyphs ) const
+{
+
+ if( nIndex >= rStr.getLength() )
+ return;
+ if( nIndex+nLen >= rStr.getLength() )
+ nLen = rStr.getLength() - nIndex;
+
+ // layout complex text
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, Point(0, 0), 0, nullptr,
+ SalLayoutFlags::NONE, nullptr, pGlyphs);
+ if( !pSalLayout )
+ return;
+
+ int nWidthFactor = pSalLayout->GetUnitsPerPixel();
+ pSalLayout->GetCaretPositions( 2*nLen, pCaretXArray );
+ long nWidth = pSalLayout->GetTextWidth();
+
+ // fixup unknown caret positions
+ int i;
+ for( i = 0; i < 2 * nLen; ++i )
+ if( pCaretXArray[ i ] >= 0 )
+ break;
+ long nXPos = pCaretXArray[ i ];
+ for( i = 0; i < 2 * nLen; ++i )
+ {
+ if( pCaretXArray[ i ] >= 0 )
+ nXPos = pCaretXArray[ i ];
+ else
+ pCaretXArray[ i ] = nXPos;
+ }
+
+ // handle window mirroring
+ if( IsRTLEnabled() )
+ {
+ for( i = 0; i < 2 * nLen; ++i )
+ pCaretXArray[i] = nWidth - pCaretXArray[i] - 1;
+ }
+
+ // convert from font units to logical units
+ if( mbMap )
+ {
+ for( i = 0; i < 2*nLen; ++i )
+ pCaretXArray[i] = ImplDevicePixelToLogicWidth( pCaretXArray[i] );
+ }
+
+ if( nWidthFactor != 1 )
+ {
+ for( i = 0; i < 2*nLen; ++i )
+ pCaretXArray[i] /= nWidthFactor;
+ }
+}
+
+void OutputDevice::DrawStretchText( const Point& rStartPt, sal_uLong 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 );
+}
+
+ImplLayoutArgs OutputDevice::ImplPrepareLayoutArgs( OUString& rStr,
+ const sal_Int32 nMinIndex, const sal_Int32 nLen,
+ DeviceCoordinate nPixelWidth, const DeviceCoordinate* pDXArray,
+ SalLayoutFlags nLayoutFlags,
+ vcl::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;
+
+ if( mnTextLayoutMode & ComplexTextLayoutFlags::BiDiRtl )
+ nLayoutFlags |= SalLayoutFlags::BiDiRtl;
+ if( mnTextLayoutMode & ComplexTextLayoutFlags::BiDiStrong )
+ nLayoutFlags |= SalLayoutFlags::BiDiStrong;
+ else if( !(mnTextLayoutMode & 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;
+ }
+
+ if( !maFont.IsKerning() )
+ nLayoutFlags |= SalLayoutFlags::DisableKerning;
+ if( maFont.GetKerning() & FontKerning::Asian )
+ nLayoutFlags |= SalLayoutFlags::KerningAsian;
+ if( maFont.IsVertical() )
+ nLayoutFlags |= SalLayoutFlags::Vertical;
+
+ if( meTextLanguage ) //TODO: (mnTextLayoutMode & 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 & ComplexTextLayoutFlags::BiDiRtl);
+ if( mnTextLayoutMode & ComplexTextLayoutFlags::TextOriginLeft )
+ bRightAlign = false;
+ else if ( mnTextLayoutMode & 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
+ ImplLayoutArgs aLayoutArgs(rStr, nMinIndex, nEndIndex, nLayoutFlags, maFont.GetLanguageTag(), pLayoutCache);
+
+ int nOrientation = mpFontInstance ? mpFontInstance->mnOrientation : 0;
+ aLayoutArgs.SetOrientation( nOrientation );
+
+ aLayoutArgs.SetLayoutWidth( nPixelWidth );
+ aLayoutArgs.SetDXArray( pDXArray );
+
+ return aLayoutArgs;
+}
+
+std::unique_ptr<SalLayout> OutputDevice::ImplLayout(const OUString& rOrigStr,
+ sal_Int32 nMinIndex, sal_Int32 nLen,
+ const Point& rLogicalPos, long nLogicalWidth,
+ const long* pDXArray, SalLayoutFlags flags,
+ vcl::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;
+ }
+
+ 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;
+
+ // convert from logical units to physical units
+ // 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;
+ }
+
+ DeviceCoordinate nPixelWidth = static_cast<DeviceCoordinate>(nLogicalWidth);
+ if( nLogicalWidth && mbMap )
+ {
+ nPixelWidth = LogicWidthToDeviceCoordinate( nLogicalWidth );
+ }
+
+ std::unique_ptr<DeviceCoordinate[]> xDXPixelArray;
+ DeviceCoordinate* pDXPixelArray(nullptr);
+ if( pDXArray)
+ {
+ if(mbMap)
+ {
+ // convert from logical units to font units using a temporary array
+ xDXPixelArray.reset(new DeviceCoordinate[nLen]);
+ pDXPixelArray = xDXPixelArray.get();
+ // using base position for better rounding a.k.a. "dancing characters"
+ DeviceCoordinate nPixelXOfs = LogicWidthToDeviceCoordinate( rLogicalPos.X() );
+ for( int i = 0; i < nLen; ++i )
+ {
+ pDXPixelArray[i] = LogicWidthToDeviceCoordinate( rLogicalPos.X() + pDXArray[i] ) - nPixelXOfs;
+ }
+ }
+ else
+ {
+#if VCL_FLOAT_DEVICE_PIXEL
+ xDXPixelArray.reset(new DeviceCoordinate[nLen]);
+ pDXPixelArray = xDXPixelArray.get();
+ for( int i = 0; i < nLen; ++i )
+ {
+ pDXPixelArray[i] = pDXArray[i];
+ }
+#else /* !VCL_FLOAT_DEVICE_PIXEL */
+ pDXPixelArray = const_cast<DeviceCoordinate*>(pDXArray);
+#endif /* !VCL_FLOAT_DEVICE_PIXEL */
+ }
+ }
+
+ ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nMinIndex, nLen,
+ nPixelWidth, pDXPixelArray, flags, pLayoutCache);
+
+ // get matching layout object for base font
+ std::unique_ptr<SalLayout> pSalLayout = mpGraphics->GetTextLayout(0);
+
+ // layout text
+ if( pSalLayout && !pSalLayout->LayoutText( aLayoutArgs, pGlyphs ) )
+ {
+ pSalLayout.reset();
+ }
+
+ if( !pSalLayout )
+ return nullptr;
+
+ // do glyph fallback if needed
+ // #105768# avoid fallback for very small font sizes
+ if (aLayoutArgs.NeedFallback() && mpFontInstance->GetFontSelectPattern().mnHeight >= 3)
+ pSalLayout = ImplGlyphFallbackLayout(std::move(pSalLayout), aLayoutArgs);
+
+ 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 );
+ pSalLayout->DrawBase() = ImplLogicToDevicePixel( rLogicalPos );
+ // adjust to right alignment if necessary
+ if( aLayoutArgs.mnFlags & SalLayoutFlags::RightAlign )
+ {
+ DeviceCoordinate nRTLOffset;
+ if( pDXPixelArray )
+ nRTLOffset = pDXPixelArray[ nLen - 1 ];
+ else if( nPixelWidth )
+ nRTLOffset = nPixelWidth;
+ else
+ nRTLOffset = pSalLayout->GetTextWidth() / pSalLayout->GetUnitsPerPixel();
+ pSalLayout->DrawOffset().setX( 1 - nRTLOffset );
+ }
+
+ return pSalLayout;
+}
+
+std::shared_ptr<vcl::TextLayoutCache> OutputDevice::CreateTextLayoutCache(
+ OUString const& rString)
+{
+ return GenericSalLayout::CreateTextLayoutCache(rString);
+}
+
+bool OutputDevice::GetTextIsRTL( const OUString& rString, sal_Int32 nIndex, sal_Int32 nLen ) const
+{
+ OUString aStr( rString );
+ ImplLayoutArgs aArgs = ImplPrepareLayoutArgs( aStr, nIndex, nLen, 0, nullptr );
+ bool bRTL = false;
+ int nCharPos = -1;
+ if (!aArgs.GetNextPos(&nCharPos, &bRTL))
+ return false;
+ return (nCharPos != nIndex);
+}
+
+sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, long nTextWidth,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ long nCharExtra,
+ vcl::TextLayoutCache const*const pLayoutCache,
+ const SalLayoutGlyphs* pGlyphs) const
+{
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
+ Point(0,0), 0, nullptr, SalLayoutFlags::NONE, 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
+ long nWidthFactor = pSalLayout->GetUnitsPerPixel();
+ long nSubPixelFactor = (nWidthFactor < 64 ) ? 64 : 1;
+ nTextWidth *= nWidthFactor * nSubPixelFactor;
+ DeviceCoordinate nTextPixelWidth = LogicWidthToDeviceCoordinate( nTextWidth );
+ DeviceCoordinate nExtraPixelWidth = 0;
+ if( nCharExtra != 0 )
+ {
+ nCharExtra *= nWidthFactor * nSubPixelFactor;
+ nExtraPixelWidth = LogicWidthToDeviceCoordinate( nCharExtra );
+ }
+ nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor );
+ }
+
+ return nRetVal;
+}
+
+sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, long nTextWidth,
+ sal_Unicode nHyphenChar, sal_Int32& rHyphenPos,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ long nCharExtra,
+ vcl::TextLayoutCache const*const pLayoutCache) const
+{
+ rHyphenPos = -1;
+
+ std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen,
+ Point(0,0), 0, nullptr, SalLayoutFlags::NONE, pLayoutCache);
+ 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
+ long nWidthFactor = pSalLayout->GetUnitsPerPixel();
+ long nSubPixelFactor = (nWidthFactor < 64 ) ? 64 : 1;
+
+ nTextWidth *= nWidthFactor * nSubPixelFactor;
+ DeviceCoordinate nTextPixelWidth = LogicWidthToDeviceCoordinate( nTextWidth );
+ DeviceCoordinate nExtraPixelWidth = 0;
+ if( nCharExtra != 0 )
+ {
+ nCharExtra *= nWidthFactor * nSubPixelFactor;
+ nExtraPixelWidth = LogicWidthToDeviceCoordinate( nCharExtra );
+ }
+
+ // 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
+ long 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,
+ MetricVector* pVector, OUString* pDisplayText,
+ vcl::ITextLayout& _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() );
+ }
+ }
+
+ long nWidth = rRect.GetWidth();
+ long nHeight = rRect.GetHeight();
+
+ if ( ((nWidth <= 0) || (nHeight <= 0)) && (nStyle & DrawTextFlags::Clip) )
+ return;
+
+ Point aPos = rRect.TopLeft();
+
+ long nTextHeight = rTargetDevice.GetTextHeight();
+ TextAlign eAlign = rTargetDevice.GetTextAlign();
+ sal_Int32 nMnemonicPos = -1;
+
+ OUString aStr = rOrigStr;
+ if ( nStyle & DrawTextFlags::Mnemonic )
+ aStr = GetNonMnemonicString( aStr, nMnemonicPos );
+
+ const bool bDrawMnemonics = !(rTargetDevice.GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector;
+
+ // We treat multiline text differently
+ if ( nStyle & DrawTextFlags::MultiLine )
+ {
+
+ OUString aLastLine;
+ ImplMultiTextLineInfo aMultiLineInfo;
+ ImplTextLineInfo* pLineInfo;
+ sal_Int32 i;
+ sal_Int32 nLines;
+ sal_Int32 nFormatLines;
+
+ if ( nTextHeight )
+ {
+ long nMaxTextWidth = ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, _rLayout );
+ nLines = static_cast<sal_Int32>(nHeight/nTextHeight);
+ nFormatLines = aMultiLineInfo.Count();
+ if (nLines <= 0)
+ nLines = 1;
+ if ( nFormatLines > nLines )
+ {
+ if ( nStyle & DrawTextFlags::EndEllipsis )
+ {
+ // Create last line and shorten it
+ nFormatLines = nLines-1;
+
+ pLineInfo = aMultiLineInfo.GetLine( nFormatLines );
+ aLastLine = convertLineEnd(aStr.copy(pLineInfo->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 = ImplGetEllipsisString( rTargetDevice, aLastLine, nWidth, nStyle, _rLayout );
+ 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( 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++ )
+ {
+ pLineInfo = aMultiLineInfo.GetLine( i );
+ if ( nStyle & DrawTextFlags::Right )
+ aPos.AdjustX(nWidth-pLineInfo->GetWidth() );
+ else if ( nStyle & DrawTextFlags::Center )
+ aPos.AdjustX((nWidth-pLineInfo->GetWidth())/2 );
+ sal_Int32 nIndex = pLineInfo->GetIndex();
+ sal_Int32 nLineLen = pLineInfo->GetLen();
+ _rLayout.DrawText( aPos, aStr, nIndex, nLineLen, pVector, pDisplayText );
+ if ( bDrawMnemonics )
+ {
+ if ( (nMnemonicPos >= nIndex) && (nMnemonicPos < nIndex+nLineLen) )
+ {
+ long nMnemonicX;
+ long nMnemonicY;
+ DeviceCoordinate nMnemonicWidth;
+
+ std::unique_ptr<long[]> const pCaretXArray(new long[2 * nLineLen]);
+ /*sal_Bool bRet =*/ _rLayout.GetCaretPositions( aStr, pCaretXArray.get(),
+ nIndex, nLineLen );
+ long lc_x1 = pCaretXArray[2*(nMnemonicPos - nIndex)];
+ long lc_x2 = pCaretXArray[2*(nMnemonicPos - nIndex)+1];
+ nMnemonicWidth = rTargetDevice.LogicWidthToDeviceCoordinate( 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
+ {
+ long nTextWidth = _rLayout.GetTextWidth( aStr, 0, -1 );
+
+ // Clip text if needed
+ if ( nTextWidth > nWidth )
+ {
+ if ( nStyle & TEXT_DRAW_ELLIPSIS )
+ {
+ aStr = ImplGetEllipsisString( rTargetDevice, aStr, nWidth, nStyle, _rLayout );
+ 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 );
+
+ long nMnemonicX = 0;
+ long nMnemonicY = 0;
+ DeviceCoordinate nMnemonicWidth = 0;
+ if ( nMnemonicPos != -1 )
+ {
+ std::unique_ptr<long[]> const pCaretXArray(new long[2 * aStr.getLength()]);
+ /*sal_Bool bRet =*/ _rLayout.GetCaretPositions( aStr, pCaretXArray.get(), 0, aStr.getLength() );
+ long lc_x1 = pCaretXArray[2*nMnemonicPos];
+ long lc_x2 = pCaretXArray[2*nMnemonicPos+1];
+ nMnemonicWidth = rTargetDevice.LogicWidthToDeviceCoordinate( 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( 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;
+ 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,
+ MetricVector* pVector, OUString* pDisplayText,
+ vcl::ITextLayout* _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;
+ if( mbInitClipRegion )
+ InitClipRegion();
+ if( mbOutputClipped && !bDecomposeTextRectAction )
+ 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::ITextLayout* _pTextLayout ) const
+{
+
+ tools::Rectangle aRect = rRect;
+ sal_Int32 nLines;
+ long nWidth = rRect.GetWidth();
+ long nMaxWidth;
+ long nTextHeight = GetTextHeight();
+
+ OUString aStr = rStr;
+ if ( nStyle & DrawTextFlags::Mnemonic )
+ aStr = GetNonMnemonicString( aStr );
+
+ if ( nStyle & DrawTextFlags::MultiLine )
+ {
+ ImplMultiTextLineInfo aMultiLineInfo;
+ ImplTextLineInfo* pLineInfo;
+ sal_Int32 nFormatLines;
+ sal_Int32 i;
+
+ nMaxWidth = 0;
+ vcl::DefaultTextLayout aDefaultLayout( *const_cast< OutputDevice* >( this ) );
+ ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, _pTextLayout ? *_pTextLayout : aDefaultLayout );
+ 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++ )
+ {
+ pLineInfo = aMultiLineInfo.GetLine( i );
+ if ( bMaxWidth && (pLineInfo->GetWidth() > nMaxWidth) )
+ nMaxWidth = pLineInfo->GetWidth();
+ if ( pLineInfo->GetWidth() > pInfo->mnMaxWidth )
+ pInfo->mnMaxWidth = pLineInfo->GetWidth();
+ }
+ }
+ else if ( !nMaxWidth )
+ {
+ for ( i = 0; i < nLines; i++ )
+ {
+ pLineInfo = aMultiLineInfo.GetLine( i );
+ if ( pLineInfo->GetWidth() > nMaxWidth )
+ nMaxWidth = pLineInfo->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 );
+ return aRect;
+}
+
+static bool ImplIsCharIn( sal_Unicode c, const char* pStr )
+{
+ while ( *pStr )
+ {
+ if ( *pStr == c )
+ return true;
+ pStr++;
+ }
+
+ return false;
+}
+
+OUString OutputDevice::GetEllipsisString( const OUString& rOrigStr, long nMaxWidth,
+ DrawTextFlags nStyle ) const
+{
+ vcl::DefaultTextLayout aTextLayout( *const_cast< OutputDevice* >( this ) );
+ return ImplGetEllipsisString( *this, rOrigStr, nMaxWidth, nStyle, aTextLayout );
+}
+
+OUString OutputDevice::ImplGetEllipsisString( const OutputDevice& rTargetDevice, const OUString& rOrigStr, long nMaxWidth,
+ DrawTextFlags nStyle, const vcl::ITextLayout& _rLayout )
+{
+ OUString aStr = rOrigStr;
+ sal_Int32 nIndex = _rLayout.GetTextBreak( aStr, nMaxWidth, 0, aStr.getLength() );
+
+ if ( nIndex != -1 )
+ {
+ if( (nStyle & DrawTextFlags::CenterEllipsis) == DrawTextFlags::CenterEllipsis )
+ {
+ OUStringBuffer aTmpStr( aStr );
+ // speed it up by removing all but 1.33x as many as the break pos.
+ sal_Int32 nEraseChars = std::max<sal_Int32>(4, aStr.getLength() - (nIndex*4)/3);
+ while( nEraseChars < aStr.getLength() && _rLayout.GetTextWidth( aTmpStr.toString(), 0, aTmpStr.getLength() ) > nMaxWidth )
+ {
+ aTmpStr = aStr;
+ sal_Int32 i = (aTmpStr.getLength() - nEraseChars)/2;
+ aTmpStr.remove(i, nEraseChars++);
+ aTmpStr.insert(i, "...");
+ }
+ aStr = aTmpStr.makeStringAndClear();
+ }
+ else if ( nStyle & DrawTextFlags::EndEllipsis )
+ {
+ aStr = aStr.copy(0, nIndex);
+ if ( nIndex > 1 )
+ {
+ aStr += "...";
+ while ( !aStr.isEmpty() && (_rLayout.GetTextWidth( aStr, 0, aStr.getLength() ) > nMaxWidth) )
+ {
+ if ( (nIndex > 1) || (nIndex == aStr.getLength()) )
+ nIndex--;
+ aStr = aStr.replaceAt( nIndex, 1, "");
+ }
+ }
+
+ if ( aStr.isEmpty() && (nStyle & DrawTextFlags::Clip) )
+ aStr += OUStringChar(rOrigStr[ 0 ]);
+ }
+ else if ( nStyle & DrawTextFlags::PathEllipsis )
+ {
+ OUString aPath( rOrigStr );
+ OUString aAbbreviatedPath;
+ osl_abbreviateSystemPath( aPath.pData, &aAbbreviatedPath.pData, nIndex, nullptr );
+ aStr = aAbbreviatedPath;
+ }
+ else if ( nStyle & DrawTextFlags::NewsEllipsis )
+ {
+ static char const pSepChars[] = ".";
+ // Determine last section
+ sal_Int32 nLastContent = aStr.getLength();
+ while ( nLastContent )
+ {
+ nLastContent--;
+ if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) )
+ break;
+ }
+ while ( nLastContent &&
+ ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) )
+ nLastContent--;
+
+ OUString aLastStr = aStr.copy(nLastContent);
+ OUString aTempLastStr1 = "..." + aLastStr;
+ if ( _rLayout.GetTextWidth( aTempLastStr1, 0, aTempLastStr1.getLength() ) > nMaxWidth )
+ aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout );
+ else
+ {
+ sal_Int32 nFirstContent = 0;
+ while ( nFirstContent < nLastContent )
+ {
+ nFirstContent++;
+ if ( ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) )
+ break;
+ }
+ while ( (nFirstContent < nLastContent) &&
+ ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) )
+ nFirstContent++;
+ // MEM continue here
+ if ( nFirstContent >= nLastContent )
+ aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout );
+ else
+ {
+ if ( nFirstContent > 4 )
+ nFirstContent = 4;
+ OUString aFirstStr = aStr.copy( 0, nFirstContent ) + "...";
+ OUString aTempStr = aFirstStr + aLastStr;
+ if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth )
+ aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout );
+ else
+ {
+ do
+ {
+ aStr = aTempStr;
+ if( nLastContent > aStr.getLength() )
+ nLastContent = aStr.getLength();
+ while ( nFirstContent < nLastContent )
+ {
+ nLastContent--;
+ if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) )
+ break;
+
+ }
+ while ( (nFirstContent < nLastContent) &&
+ ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) )
+ nLastContent--;
+
+ if ( nFirstContent < nLastContent )
+ {
+ OUString aTempLastStr = aStr.copy( nLastContent );
+ aTempStr = aFirstStr + aTempLastStr;
+
+ if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth )
+ break;
+ }
+ }
+ while ( nFirstContent < nLastContent );
+ }
+ }
+ }
+ }
+ }
+
+ return aStr;
+}
+
+void OutputDevice::DrawCtrlText( const Point& rPos, const OUString& rStr,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ DrawTextFlags nStyle, MetricVector* 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;
+ 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;
+
+ long nMnemonicX = 0;
+ long nMnemonicY = 0;
+ long nMnemonicWidth = 0;
+ if ( (nStyle & DrawTextFlags::Mnemonic) && nLen > 1 )
+ {
+ aStr = GetNonMnemonicString( 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;
+ }
+
+ std::unique_ptr<long[]> const pCaretXArray(new long[2 * nLen]);
+ /*sal_Bool bRet =*/ GetCaretPositions( aStr, pCaretXArray.get(), nIndex, nLen, pGlyphs );
+ long lc_x1 = pCaretXArray[ 2*(nMnemonicPos - nIndex) ];
+ long lc_x2 = pCaretXArray[ 2*(nMnemonicPos - nIndex)+1 ];
+ nMnemonicWidth = ::abs(static_cast<int>(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();
+ }
+ }
+
+ bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel;
+
+ 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)
+ && (!autoacc || !(nStyle & DrawTextFlags::HideMnemonic)) )
+ {
+ 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
+ && (!autoacc || !(nStyle & DrawTextFlags::HideMnemonic)) )
+ {
+ if ( nMnemonicPos != -1 )
+ ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawCtrlText( rPos, rStr, nIndex, nLen, nStyle, pVector, pDisplayText );
+}
+
+long OutputDevice::GetCtrlTextWidth( const OUString& rStr, const SalLayoutGlyphs* pGlyphs ) const
+{
+ sal_Int32 nLen = rStr.getLength();
+ sal_Int32 nIndex = 0;
+
+ sal_Int32 nMnemonicPos;
+ OUString aStr = GetNonMnemonicString( 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 );
+}
+
+OUString OutputDevice::GetNonMnemonicString( const OUString& rStr, sal_Int32& rMnemonicPos )
+{
+ OUString aStr = rStr;
+ sal_Int32 nLen = aStr.getLength();
+ sal_Int32 i = 0;
+
+ rMnemonicPos = -1;
+ while ( i < nLen )
+ {
+ if ( aStr[ i ] == '~' )
+ {
+ if ( nLen <= i+1 )
+ break;
+
+ if ( aStr[ i+1 ] != '~' )
+ {
+ if ( rMnemonicPos == -1 )
+ rMnemonicPos = i;
+ aStr = aStr.replaceAt( i, 1, "" );
+ nLen--;
+ }
+ else
+ {
+ aStr = aStr.replaceAt( i, 1, "" );
+ nLen--;
+ i++;
+ }
+ }
+ else
+ i++;
+ }
+
+ return aStr;
+}
+
+/** OutputDevice::GetSysTextLayoutData
+ *
+ * @param rStartPt Start point of the text
+ * @param rStr Text string that will be transformed into layout of glyphs
+ * @param nIndex Position in the string from where layout will be done
+ * @param nLen Length of the string
+ * @param pDXAry Custom layout adjustment data
+ *
+ * Export finalized glyph layout data as platform independent SystemTextLayoutData
+ * (see vcl/inc/vcl/sysdata.hxx)
+ *
+ * Only parameters rStartPt and rStr are mandatory, the rest is optional
+ * (default values will be used)
+ *
+ * @return SystemTextLayoutData
+ **/
+SystemTextLayoutData OutputDevice::GetSysTextLayoutData(const Point& rStartPt, const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen,
+ const long* pDXAry) const
+{
+ if( (nLen < 0) || (nIndex + nLen >= rStr.getLength()))
+ {
+ nLen = rStr.getLength() - nIndex;
+ }
+
+ SystemTextLayoutData aSysLayoutData;
+ aSysLayoutData.rGlyphData.reserve( 256 );
+ aSysLayoutData.orientation = 0;
+
+ if ( mpMetaFile )
+ {
+ if (pDXAry)
+ mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, nIndex, nLen ) );
+ else
+ mpMetaFile->AddAction( new MetaTextAction( rStartPt, rStr, nIndex, nLen ) );
+ }
+
+ if ( !IsDeviceOutputNecessary() ) return aSysLayoutData;
+
+ std::unique_ptr<SalLayout> pLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry);
+
+ if ( !pLayout ) return aSysLayoutData;
+
+ // setup glyphs
+ Point aPos;
+ const GlyphItem* pGlyph;
+ int nStart = 0;
+ SystemGlyphData aSystemGlyph;
+ while (pLayout->GetNextGlyph(&pGlyph, aPos, nStart, nullptr, &aSystemGlyph.fallbacklevel))
+ {
+ aSystemGlyph.index = pGlyph->glyphId();
+ aSystemGlyph.x = aPos.X();
+ aSystemGlyph.y = aPos.Y();
+ aSysLayoutData.rGlyphData.push_back(aSystemGlyph);
+ }
+
+ // Get font data
+ aSysLayoutData.orientation = pLayout->GetOrientation();
+
+ return aSysLayoutData;
+}
+
+bool OutputDevice::GetTextBoundRect( tools::Rectangle& rRect,
+ const OUString& rStr, sal_Int32 nBase,
+ sal_Int32 nIndex, sal_Int32 nLen,
+ sal_uLong nLayoutWidth, const long* pDXAry,
+ const SalLayoutGlyphs* pGlyphs ) const
+{
+ bool bRet = false;
+ rRect.SetEmpty();
+
+ std::unique_ptr<SalLayout> pSalLayout;
+ const Point aPoint;
+ // calculate offset when nBase!=nIndex
+ long 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 );
+ if( pSalLayout )
+ {
+ nXOffset = pSalLayout->GetTextWidth();
+ nXOffset /= pSalLayout->GetUnitsPerPixel();
+ // TODO: fix offset calculation for Bidi case
+ if( nBase < nIndex)
+ nXOffset = -nXOffset;
+ }
+ }
+
+ pSalLayout = ImplLayout(rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry, SalLayoutFlags::NONE,
+ nullptr, pGlyphs);
+ tools::Rectangle aPixelRect;
+ if( pSalLayout )
+ {
+ bRet = pSalLayout->GetBoundRect(aPixelRect);
+
+ if( bRet )
+ {
+ int nWidthFactor = pSalLayout->GetUnitsPerPixel();
+
+ if( nWidthFactor > 1 )
+ {
+ double fFactor = 1.0 / nWidthFactor;
+ aPixelRect.SetLeft(
+ static_cast< long >(aPixelRect.Left() * fFactor) );
+ aPixelRect.SetRight(
+ static_cast< long >(aPixelRect.Right() * fFactor) );
+ aPixelRect.SetTop(
+ static_cast< long >(aPixelRect.Top() * fFactor) );
+ aPixelRect.SetBottom(
+ static_cast< long >(aPixelRect.Bottom() * fFactor) );
+ }
+
+ Point aRotatedOfs( mnTextOffX, mnTextOffY );
+ aRotatedOfs -= pSalLayout->GetDrawPosition( Point( nXOffset, 0 ) );
+ 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, const long* pDXArray ) 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
+ long 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 );
+ 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 );
+ if( pSalLayout )
+ {
+ bRet = pSalLayout->GetOutline(rVector);
+ if( bRet )
+ {
+ // transform polygon to pixel units
+ basegfx::B2DHomMatrix aMatrix;
+
+ int nWidthFactor = pSalLayout->GetUnitsPerPixel();
+ if( nXOffset | mnTextOffX | mnTextOffY )
+ {
+ Point aRotatedOfs( mnTextOffX*nWidthFactor, mnTextOffY*nWidthFactor );
+ aRotatedOfs -= pSalLayout->GetDrawPosition( Point( nXOffset, 0 ) );
+ aMatrix.translate( aRotatedOfs.X(), aRotatedOfs.Y() );
+ }
+
+ if( nWidthFactor > 1 )
+ {
+ double fFactor = 1.0 / nWidthFactor;
+ aMatrix.scale( fFactor, fFactor );
+ }
+
+ 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 nTWidth, const long* pDXArray ) const
+{
+ rResultVector.clear();
+
+ // get the basegfx polypolygon vector
+ basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
+ if( !GetTextOutlines( aB2DPolyPolyVector, rStr, nBase, nIndex, nLen,
+ nTWidth, pDXArray ) )
+ 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,
+ sal_Int32 nLen,
+ sal_uLong nTWidth, const long* pDXArray ) const
+{
+ rPolyPoly.Clear();
+
+ // get the basegfx polypolygon vector
+ basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
+ if( !GetTextOutlines( aB2DPolyPolyVector, rStr, 0/*nBase*/, 0/*nIndex*/, nLen,
+ nTWidth, 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;
+}
+
+/* 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 000000000..6ade6113f
--- /dev/null
+++ b/vcl/source/outdev/textline.cxx
@@ -0,0 +1,1026 @@
+/* -*- 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 <cassert>
+
+#include <sal/types.h>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+
+#include <tools/helpers.hxx>
+
+#include <salgdi.hxx>
+#include <impglyphitem.hxx>
+
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/WaveLine.hxx>
+
+#define UNDERLINE_LAST LINESTYLE_BOLDWAVE
+#define STRIKEOUT_LAST STRIKEOUT_X
+
+void OutputDevice::ImplInitTextLineSize()
+{
+ mpFontInstance->mxFontMetric->ImplInitTextLineSize( this );
+}
+
+void OutputDevice::ImplInitAboveTextLineSize()
+{
+ mpFontInstance->mxFontMetric->ImplInitAboveTextLineSize();
+}
+
+void OutputDevice::ImplDrawWavePixel( long nOriginX, long nOriginY,
+ long nCurX, long nCurY,
+ short nOrientation,
+ SalGraphics* pGraphics,
+ OutputDevice const * pOutDev,
+ bool bDrawPixAsRect,
+ long nPixWidth, long nPixHeight )
+{
+ if ( nOrientation )
+ {
+ Point aPoint( nOriginX, nOriginY );
+ aPoint.RotateAround( nCurX, nCurY, nOrientation );
+ }
+
+ if ( bDrawPixAsRect )
+ {
+
+ pGraphics->DrawRect( nCurX, nCurY, nPixWidth, nPixHeight, pOutDev );
+ }
+ else
+ {
+ pGraphics->DrawPixel( nCurX, nCurY, pOutDev );
+ }
+}
+
+void OutputDevice::ImplDrawWaveLine( long nBaseX, long nBaseY,
+ long nDistX, long nDistY,
+ long nWidth, long nHeight,
+ long nLineWidth, short nOrientation,
+ const Color& rColor )
+{
+ if ( !nHeight )
+ return;
+
+ long nStartX = nBaseX + nDistX;
+ 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;
+
+ long nEndX = nStartX+nWidth;
+ 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
+ {
+ long nCurX = nStartX;
+ long nCurY = nStartY;
+ long nDiffX = 2;
+ long nDiffY = nHeight-1;
+ long nCount = nWidth;
+ long nOffY = -1;
+ long nPixWidth;
+ long nPixHeight;
+ bool bDrawPixAsRect;
+ // On printers that output pixel via DrawRect()
+ if ( (GetOutDevType() == OUTDEV_PRINTER) || (nLineWidth > 1) )
+ {
+ if ( mbLineColor || mbInitLineColor )
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+ mpGraphics->SetFillColor( rColor );
+ mbInitFillColor = true;
+ bDrawPixAsRect = true;
+ nPixWidth = nLineWidth;
+ nPixHeight = ((nLineWidth*mnDPIX)+(mnDPIY/2))/mnDPIY;
+ }
+ else
+ {
+ mpGraphics->SetLineColor( rColor );
+ mbInitLineColor = true;
+ nPixWidth = 1;
+ nPixHeight = 1;
+ bDrawPixAsRect = false;
+ }
+
+ if ( !nDiffY )
+ {
+ while ( nWidth )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation,
+ mpGraphics, this,
+ bDrawPixAsRect, nPixWidth, nPixHeight );
+ nCurX++;
+ nWidth--;
+ }
+ }
+ else
+ {
+ nCurY += nDiffY;
+ long nFreq = nCount / (nDiffX+nDiffY);
+ while ( nFreq-- )
+ {
+ for( long i = nDiffY; i; --i )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation,
+ mpGraphics, this,
+ bDrawPixAsRect, nPixWidth, nPixHeight );
+ nCurX++;
+ nCurY += nOffY;
+ }
+ for( long i = nDiffX; i; --i )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation,
+ mpGraphics, this,
+ bDrawPixAsRect, nPixWidth, nPixHeight );
+ nCurX++;
+ }
+ nOffY = -nOffY;
+ }
+ nFreq = nCount % (nDiffX+nDiffY);
+ if ( nFreq )
+ {
+ for( long i = nDiffY; i && nFreq; --i, --nFreq )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation,
+ mpGraphics, this,
+ bDrawPixAsRect, nPixWidth, nPixHeight );
+ nCurX++;
+ nCurY += nOffY;
+
+ }
+ for( long i = nDiffX; i && nFreq; --i, --nFreq )
+ {
+ ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation,
+ mpGraphics, this,
+ bDrawPixAsRect, nPixWidth, nPixHeight );
+ nCurX++;
+ }
+ }
+ }
+ }
+}
+
+void OutputDevice::ImplDrawWaveTextLine( long nBaseX, long nBaseY,
+ long nDistX, long nDistY, long nWidth,
+ FontLineStyle eTextLine,
+ Color aColor,
+ bool bIsAbove )
+{
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ long nLineHeight;
+ 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;
+
+ long nLineWidth = mnDPIX / 300;
+ if ( !nLineWidth )
+ nLineWidth = 1;
+
+ if ( eTextLine == LINESTYLE_BOLDWAVE )
+ nLineWidth *= 2;
+
+ nLinePos += nDistY - (nLineHeight / 2);
+
+ long nLineWidthHeight = ((nLineWidth * mnDPIX) + (mnDPIY / 2)) / mnDPIY;
+ if ( eTextLine == LINESTYLE_DOUBLEWAVE )
+ {
+ long nOrgLineHeight = nLineHeight;
+ nLineHeight /= 3;
+ if ( nLineHeight < 2 )
+ {
+ if ( nOrgLineHeight > 1 )
+ nLineHeight = 2;
+ else
+ nLineHeight = 1;
+ }
+
+ long nLineDY = nOrgLineHeight-(nLineHeight*2);
+ if ( nLineDY < nLineWidthHeight )
+ nLineDY = nLineWidthHeight;
+
+ 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( long nBaseX, long nBaseY,
+ long nDistX, long nDistY, long nWidth,
+ FontLineStyle eTextLine,
+ Color aColor,
+ bool bIsAbove )
+{
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ long nLineHeight = 0;
+ long nLinePos = 0;
+ long nLinePos2 = 0;
+
+ const 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 )
+ {
+ if ( mbLineColor || mbInitLineColor )
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+ mpGraphics->SetFillColor( aColor );
+ mbInitFillColor = true;
+
+ 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:
+ {
+ long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ long nTempWidth = nDotWidth;
+ 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:
+ {
+ long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ long nMinDashWidth;
+ long nMinSpaceWidth;
+ long nSpaceWidth;
+ 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;
+
+ long nTempWidth = nDashWidth;
+ 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:
+ {
+ long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ long nDashWidth = ((100*mnDPIX)+1270)/2540;
+ 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;
+
+ long nTempDotWidth = nDotWidth;
+ long nTempDashWidth = nDashWidth;
+ 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:
+ {
+ long nDotWidth = nLineHeight*mnDPIY;
+ nDotWidth += mnDPIY/2;
+ nDotWidth /= mnDPIY;
+
+ long nDashWidth = ((100*mnDPIX)+1270)/2540;
+ 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;
+
+ long nTempDotWidth = nDotWidth;
+ long nTempDashWidth = nDashWidth;
+ 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( long nBaseX, long nBaseY,
+ long nDistX, long nDistY, long nWidth,
+ FontStrikeout eStrikeout,
+ Color aColor )
+{
+ LogicalFontInstance* pFontInstance = mpFontInstance.get();
+ long nLineHeight = 0;
+ long nLinePos = 0;
+ long nLinePos2 = 0;
+
+ 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 )
+ {
+ if ( mbLineColor || mbInitLineColor )
+ {
+ mpGraphics->SetLineColor();
+ mbInitLineColor = true;
+ }
+ mpGraphics->SetFillColor( aColor );
+ mbInitFillColor = true;
+
+ const 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( long nBaseX, long nBaseY,
+ long nDistX, long nDistY, 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
+ long nStrikeoutWidth = 0;
+ std::unique_ptr<SalLayout> pLayout = ImplLayout( aStrikeoutTest, 0, nTestStrLen );
+ if( pLayout )
+ {
+ nStrikeoutWidth = pLayout->GetTextWidth() / (nTestStrLen * pLayout->GetUnitsPerPixel());
+ }
+ if( nStrikeoutWidth <= 0 ) // sanity check
+ return;
+
+ int nStrikeStrLen = (nWidth+(nStrikeoutWidth-1)) / nStrikeoutWidth;
+ if( nStrikeStrLen > nMaxStrikeStrLen )
+ nStrikeStrLen = nMaxStrikeStrLen;
+
+ // 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
+ ComplexTextLayoutFlags nOrigTLM = mnTextLayoutMode;
+ mnTextLayoutMode = 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() = Point( 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( PushFlags::CLIPREGION );
+ IntersectClipRegion( PixelToLogic(aPixelRect) );
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ pLayout->DrawText( *mpGraphics );
+
+ Pop();
+
+ SetTextColor( aOldColor );
+ ImplInitTextColor();
+}
+
+void OutputDevice::ImplDrawTextLine( long nX, long nY,
+ long nDistX, DeviceCoordinate 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() )
+ {
+ long nXAdd = nWidth - nDistX;
+ if( mpFontInstance->mnOrientation )
+ nXAdd = FRound( nXAdd * cos( mpFontInstance->mnOrientation * F_PI1800 ) );
+
+ 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 Point aStartPt = rSalLayout.DrawBase();
+
+ // calculate distance of each word from the base point
+ Point aPos;
+ DeviceCoordinate nDist = 0;
+ DeviceCoordinate 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.X() - aStartPt.X();
+ if( mpFontInstance->mnOrientation )
+ {
+ const long nDY = aPos.Y() - aStartPt.Y();
+ const double fRad = mpFontInstance->mnOrientation * F_PI1800;
+ nDist = FRound( nDist*cos(fRad) - nDY*sin(fRad) );
+ }
+ }
+
+ // update the length of the textline
+ nWidth += pGlyph->m_nNewWidth;
+ }
+ else if( nWidth > 0 )
+ {
+ // draw the textline for each word
+ ImplDrawTextLine( aStartPt.X(), aStartPt.Y(), nDist, nWidth,
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ nWidth = 0;
+ }
+ }
+
+ // draw textline for the last word
+ if( nWidth > 0 )
+ {
+ ImplDrawTextLine( aStartPt.X(), aStartPt.Y(), nDist, nWidth,
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+ }
+ else
+ {
+ Point aStartPt = rSalLayout.GetDrawPosition();
+ ImplDrawTextLine( aStartPt.X(), aStartPt.Y(), 0,
+ rSalLayout.GetTextWidth() / rSalLayout.GetUnitsPerPixel(),
+ eStrikeout, eUnderline, eOverline, bUnderlineAbove );
+ }
+}
+
+void OutputDevice::ImplDrawMnemonicLine( long nX, long nY, long nWidth )
+{
+ long nBaseX = nX;
+ if( /*HasMirroredGraphics() &&*/ IsRTLEnabled() )
+ {
+ // add some strange offset
+ nX += 2;
+ // 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( rColor );
+
+ if ( mnDrawMode & ( DrawModeFlags::BlackText | DrawModeFlags::WhiteText |
+ DrawModeFlags::GrayText |
+ DrawModeFlags::SettingsText ) )
+ {
+ if ( mnDrawMode & DrawModeFlags::BlackText )
+ {
+ aColor = COL_BLACK;
+ }
+ else if ( mnDrawMode & DrawModeFlags::WhiteText )
+ {
+ aColor = COL_WHITE;
+ }
+ else if ( mnDrawMode & DrawModeFlags::GrayText )
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color( cLum, cLum, cLum );
+ }
+ else if ( mnDrawMode & DrawModeFlags::SettingsText )
+ {
+ aColor = GetSettings().GetStyleSettings().GetFontColor();
+ }
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaTextLineColorAction( aColor, true ) );
+
+ maTextLineColor = aColor;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetTextLineColor( COL_BLACK );
+}
+
+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( rColor );
+
+ if ( mnDrawMode & ( DrawModeFlags::BlackText | DrawModeFlags::WhiteText |
+ DrawModeFlags::GrayText |
+ DrawModeFlags::SettingsText ) )
+ {
+ if ( mnDrawMode & DrawModeFlags::BlackText )
+ {
+ aColor = COL_BLACK;
+ }
+ else if ( mnDrawMode & DrawModeFlags::WhiteText )
+ {
+ aColor = COL_WHITE;
+ }
+ else if ( mnDrawMode & DrawModeFlags::GrayText )
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color( cLum, cLum, cLum );
+ }
+ else if ( mnDrawMode & DrawModeFlags::SettingsText )
+ {
+ aColor = GetSettings().GetStyleSettings().GetFontColor();
+ }
+ }
+
+ if ( mpMetaFile )
+ mpMetaFile->AddAction( new MetaOverlineColorAction( aColor, true ) );
+
+ maOverlineColor = aColor;
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->SetOverlineColor( COL_BLACK );
+}
+
+void OutputDevice::DrawTextLine( const Point& rPos, 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 );
+ DeviceCoordinate fWidth;
+ fWidth = LogicWidthToDeviceCoordinate( 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, long nLineWidth)
+{
+ assert(!is_double_buffered_window());
+
+ if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
+ return;
+
+ // we need a graphics
+ if( !mpGraphics && !AcquireGraphics() )
+ return;
+
+ if ( mbInitClipRegion )
+ InitClipRegion();
+
+ if ( mbOutputClipped )
+ return;
+
+ if (!InitFont())
+ return;
+
+ Point aStartPt = ImplLogicToDevicePixel(rStartPos);
+ Point aEndPt = ImplLogicToDevicePixel(rEndPos);
+
+ long nStartX = aStartPt.X();
+ long nStartY = aStartPt.Y();
+ long nEndX = aEndPt.X();
+ long nEndY = aEndPt.Y();
+ double fOrientation = 0.0;
+
+ // handle rotation
+ if (nStartY != nEndY || nStartX > nEndX)
+ {
+ long nLengthX = nEndX - nStartX;
+ fOrientation = std::atan2(nStartY - nEndY, (nLengthX == 0 ? 0.000000001 : nLengthX));
+ fOrientation /= F_PI180;
+ // un-rotate the end point
+ aStartPt.RotateAround(nEndX, nEndY, -fOrientation * 10.0);
+ }
+
+ long nWaveHeight = 3;
+
+ // 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;
+ }
+
+ 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->DrawWaveLine( rStartPos, rEndPos, 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 000000000..56e4f2d03
--- /dev/null
+++ b/vcl/source/outdev/transparent.cxx
@@ -0,0 +1,859 @@
+/* -*- 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 <cassert>
+
+#include <sal/types.h>
+#include <tools/helpers.hxx>
+#include <rtl/math.hxx>
+
+#include <memory>
+
+#include <vcl/bitmapaccess.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+
+#include <outdata.hxx>
+#include <salgdi.hxx>
+#include <bitmapwriteaccess.hxx>
+
+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;
+ }
+}
+
+Color OutputDevice::ImplDrawModeToColor( const Color& rColor ) const
+{
+ Color aColor( rColor );
+ DrawModeFlags nDrawMode = GetDrawMode();
+
+ if( nDrawMode & ( DrawModeFlags::BlackLine | DrawModeFlags::WhiteLine |
+ DrawModeFlags::GrayLine |
+ DrawModeFlags::SettingsLine ) )
+ {
+ if( !ImplIsColorTransparent( aColor ) )
+ {
+ if( nDrawMode & DrawModeFlags::BlackLine )
+ {
+ aColor = COL_BLACK;
+ }
+ else if( nDrawMode & DrawModeFlags::WhiteLine )
+ {
+ aColor = COL_WHITE;
+ }
+ else if( nDrawMode & DrawModeFlags::GrayLine )
+ {
+ const sal_uInt8 cLum = aColor.GetLuminance();
+ aColor = Color( cLum, cLum, cLum );
+ }
+ else if( nDrawMode & DrawModeFlags::SettingsLine )
+ {
+ aColor = GetSettings().GetStyleSettings().GetFontColor();
+ }
+ }
+ }
+ return aColor;
+}
+
+void OutputDevice::ImplPrintTransparent( const Bitmap& rBmp, const Bitmap& rMask,
+ const Point& rDestPt, const Size& rDestSize,
+ const Point& rSrcPtPixel, const Size& rSrcSizePixel )
+{
+ Point aDestPt( LogicToPixel( rDestPt ) );
+ Size aDestSz( LogicToPixel( rDestSize ) );
+ tools::Rectangle aSrcRect( rSrcPtPixel, rSrcSizePixel );
+
+ aSrcRect.Justify();
+
+ if( rBmp.IsEmpty() || !aSrcRect.GetWidth() || !aSrcRect.GetHeight() || !aDestSz.Width() || !aDestSz.Height() )
+ return;
+
+ Bitmap aPaint( rBmp ), aMask( rMask );
+ BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE;
+
+ if( aMask.GetBitCount() > 1 )
+ aMask.Convert( BmpConversion::N1BitThreshold );
+
+ // mirrored horizontically
+ if( aDestSz.Width() < 0 )
+ {
+ aDestSz.setWidth( -aDestSz.Width() );
+ aDestPt.AdjustX( -( aDestSz.Width() - 1 ) );
+ nMirrFlags |= BmpMirrorFlags::Horizontal;
+ }
+
+ // mirrored vertically
+ if( aDestSz.Height() < 0 )
+ {
+ aDestSz.setHeight( -aDestSz.Height() );
+ aDestPt.AdjustY( -( aDestSz.Height() - 1 ) );
+ nMirrFlags |= BmpMirrorFlags::Vertical;
+ }
+
+ // source cropped?
+ if( aSrcRect != tools::Rectangle( Point(), aPaint.GetSizePixel() ) )
+ {
+ aPaint.Crop( aSrcRect );
+ aMask.Crop( aSrcRect );
+ }
+
+ // destination mirrored
+ if( nMirrFlags != BmpMirrorFlags::NONE )
+ {
+ aPaint.Mirror( nMirrFlags );
+ aMask.Mirror( nMirrFlags );
+ }
+
+ // we always want to have a mask
+ if( aMask.IsEmpty() )
+ {
+ aMask = Bitmap( aSrcRect.GetSize(), 1 );
+ aMask.Erase( COL_BLACK );
+ }
+
+ // do painting
+ const long nSrcWidth = aSrcRect.GetWidth(), nSrcHeight = aSrcRect.GetHeight();
+ long nX, nY; // , nWorkX, nWorkY, nWorkWidth, nWorkHeight;
+ std::unique_ptr<long[]> pMapX(new long[ nSrcWidth + 1 ]);
+ std::unique_ptr<long[]> pMapY(new long[ nSrcHeight + 1 ]);
+ const bool bOldMap = mbMap;
+
+ mbMap = false;
+
+ // create forward mapping tables
+ for( nX = 0; nX <= nSrcWidth; nX++ )
+ pMapX[ nX ] = aDestPt.X() + FRound( static_cast<double>(aDestSz.Width()) * nX / nSrcWidth );
+
+ for( nY = 0; nY <= nSrcHeight; nY++ )
+ pMapY[ nY ] = aDestPt.Y() + FRound( static_cast<double>(aDestSz.Height()) * nY / nSrcHeight );
+
+ // walk through all rectangles of mask
+ const vcl::Region aWorkRgn(aMask.CreateRegion(COL_BLACK, tools::Rectangle(Point(), aMask.GetSizePixel())));
+ RectangleVector aRectangles;
+ aWorkRgn.GetRegionRectangles(aRectangles);
+
+ for (auto const& rectangle : aRectangles)
+ {
+ const Point aMapPt(pMapX[rectangle.Left()], pMapY[rectangle.Top()]);
+ const Size aMapSz( pMapX[rectangle.Right() + 1] - aMapPt.X(), // pMapX[L + W] -> L + ((R - L) + 1) -> R + 1
+ pMapY[rectangle.Bottom() + 1] - aMapPt.Y()); // same for Y
+ Bitmap aBandBmp(aPaint);
+
+ aBandBmp.Crop(rectangle);
+ DrawBitmap(aMapPt, aMapSz, Point(), aBandBmp.GetSizePixel(), aBandBmp);
+ }
+
+ mbMap = bOldMap;
+
+}
+
+// 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;
+
+ if( mbInitClipRegion )
+ InitClipRegion();
+
+ if( mbOutputClipped )
+ return;
+
+ if( mbInitLineColor )
+ InitLineColor();
+
+ if( mbInitFillColor )
+ InitFillColor();
+
+ if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) &&
+ mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) &&
+ (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
+ // aplha... but that requires using premultiplied alpha also for already drawn data
+ const double fAdjustedTransparency = mpAlphaVDev ? 0 : fTransparency;
+ bool bDrawnOk(true);
+
+ if( IsFillColor() )
+ {
+ bDrawnOk = mpGraphics->DrawPolyPolygon(
+ aFullTransform,
+ aB2DPolyPolygon,
+ fAdjustedTransparency,
+ this);
+ }
+
+ if( bDrawnOk && IsLineColor() )
+ {
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : 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( bDrawnOk )
+ {
+ 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));
+}
+
+void OutputDevice::DrawInvisiblePolygon( const tools::PolyPolygon& rPolyPoly )
+{
+ assert(!is_double_buffered_window());
+
+ // short circuit if the polygon border is invisible too
+ if( !mbLineColor )
+ return;
+
+ // we assume that the border is NOT to be drawn transparently???
+ Push( PushFlags::FILLCOLOR );
+ SetFillColor();
+ DrawPolyPolygon( rPolyPoly );
+ Pop();
+}
+
+bool OutputDevice::DrawTransparentNatively ( const tools::PolyPolygon& rPolyPoly,
+ sal_uInt16 nTransparencePercent )
+{
+ assert(!is_double_buffered_window());
+
+ bool bDrawn = false;
+
+ // debug helper:
+ static const char* pDisableNative = getenv( "SAL_DISABLE_NATIVE_ALPHA");
+
+ if( !pDisableNative &&
+ mpGraphics->supportsOperation( OutDevSupportType::B2DDraw )
+#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 false;
+
+ 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)
+ bDrawn = mpGraphics->DrawPolyPolygon(
+ aTransform,
+ aB2DPolyPolygon,
+ fTransparency,
+ this);
+ }
+
+ if( mbLineColor )
+ {
+ // disable the fill color for now
+ mpGraphics->SetFillColor();
+
+ // draw the border line
+ const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+ for(auto const& rPolygon : 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;
+
+ // debug helper:
+ static const char* pDisableNative = getenv( "SAL_DISABLE_NATIVE_ALPHA" );
+
+ // #i66849# Added fast path for exactly rectangular
+ // polygons
+ // #i83087# Naturally, system alpha blending cannot
+ // work with separate alpha VDev
+ if( !mpAlphaVDev && !pDisableNative && 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.getWidth(), aPixelRect.getHeight(),
+ sal::static_int_cast<sal_uInt8>(nTransparencePercent),
+ this );
+ }
+ else
+ {
+ bDrawn = true;
+ }
+ }
+
+ if( !bDrawn )
+ {
+ ScopedVclPtrInstance< VirtualDevice > aVDev(*this, DeviceFormat::BITMASK);
+ const Size aDstSz( aDstRect.GetSize() );
+ const sal_uInt8 cTrans = static_cast<sal_uInt8>(MinMax( FRound( nTransparencePercent * 2.55 ), 0, 255 ));
+
+ 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 && !!aPolyMask )
+ {
+ BitmapScopedWriteAccess pW(aPaint);
+ Bitmap::ScopedReadAccess pR(aPolyMask);
+
+ if( pW && pR )
+ {
+ BitmapColor aPixCol;
+ const BitmapColor aFillCol( GetFillColor() );
+ const BitmapColor aBlack( pR->GetBestMatchingColor( COL_BLACK ) );
+ const long nWidth = pW->Width();
+ const long nHeight = pW->Height();
+ const long nR = aFillCol.GetRed();
+ const long nG = aFillCol.GetGreen();
+ const long nB = aFillCol.GetBlue();
+ long nX, nY;
+
+ if( aPaint.GetBitCount() <= 8 )
+ {
+ const BitmapPalette& rPal = pW->GetPalette();
+ const sal_uInt16 nCount = rPal.GetEntryCount();
+ BitmapColor* pMap = reinterpret_cast<BitmapColor*>(new sal_uInt8[ nCount * sizeof( BitmapColor ) ]);
+
+ 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 ) ] );
+ }
+ }
+ }
+ }
+ delete[] reinterpret_cast<sal_uInt8*>(pMap);
+ }
+ 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 ] = ColorChannelMerge( pWScan[ 0 ], nB, cTrans );
+ pWScan[ 1 ] = ColorChannelMerge( pWScan[ 1 ], nG, cTrans );
+ pWScan[ 2 ] = 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( 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 || (nTransparencePercent >= 100) )
+ {
+ DrawInvisiblePolygon( rPolyPoly );
+ 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;
+
+ // 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() );
+ mpAlphaVDev->SetFillColor( Color(sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100),
+ sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100),
+ sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100)) );
+
+ mpAlphaVDev->DrawTransparent( rPolyPoly, nTransparencePercent );
+
+ mpAlphaVDev->SetFillColor( aFillCol );
+ }
+}
+
+void OutputDevice::DrawTransparent( const GDIMetaFile& rMtf, const Point& rPos,
+ const Size& rSize, 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, rPos, rSize );
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ }
+ else
+ {
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ tools::Rectangle aOutRect( LogicToPixel( rPos ), LogicToPixel( rSize ) );
+ Point aPoint;
+ tools::Rectangle aDstRect( aPoint, GetOutputSizePixel() );
+
+ mpMetaFile = nullptr;
+ aDstRect.Intersection( aOutRect );
+
+ ClipToPaintRegion( aDstRect );
+
+ if( !aDstRect.IsEmpty() )
+ {
+ ScopedVclPtrInstance< VirtualDevice > xVDev;
+
+ xVDev->mnDPIX = mnDPIX;
+ xVDev->mnDPIY = mnDPIY;
+
+ if( xVDev->SetOutputSizePixel( aDstRect.GetSize() ) )
+ {
+ if(GetAntialiasing() != AntialiasingFlags::NONE)
+ {
+ // #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.get(), rPos, rSize);
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+
+ // get content bitmap from buffer
+ xVDev->EnableMapMode(false);
+
+ const Bitmap aPaint(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel()));
+
+ // create alpha mask from gradient and get as Bitmap
+ xVDev->EnableMapMode(bBufferMapModeEnabled);
+ xVDev->SetDrawMode(DrawModeFlags::GrayGradient);
+ xVDev->DrawGradient(tools::Rectangle(rPos, rSize), rTransparenceGradient);
+ xVDev->SetDrawMode(DrawModeFlags::Default);
+ xVDev->EnableMapMode(false);
+
+ const AlphaMask aAlpha(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel()));
+
+ xVDev.disposeAndClear();
+
+ // draw masked content to target and restore MapMode
+ DrawBitmapEx(aDstRect.TopLeft(), BitmapEx(aPaint, aAlpha));
+ EnableMapMode(bOrigMapModeEnabled);
+ }
+ else
+ {
+ Bitmap aPaint, aMask;
+ AlphaMask aAlpha;
+ 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.get(), rPos, rSize );
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ xVDev->EnableMapMode( false );
+ aPaint = xVDev->GetBitmap( Point(), xVDev->GetOutputSizePixel() );
+ xVDev->EnableMapMode( bVDevOldMap ); // #i35331#: MUST NOT use EnableMapMode( sal_True ) here!
+
+ // create mask bitmap
+ xVDev->SetLineColor( COL_BLACK );
+ xVDev->SetFillColor( COL_BLACK );
+ xVDev->DrawRect( tools::Rectangle( xVDev->PixelToLogic( Point() ), xVDev->GetOutputSize() ) );
+ xVDev->SetDrawMode( DrawModeFlags::WhiteLine | DrawModeFlags::WhiteFill | DrawModeFlags::WhiteText |
+ DrawModeFlags::WhiteBitmap | DrawModeFlags::WhiteGradient );
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ const_cast<GDIMetaFile&>(rMtf).Play( xVDev.get(), rPos, rSize );
+ const_cast<GDIMetaFile&>(rMtf).WindStart();
+ xVDev->EnableMapMode( false );
+ aMask = xVDev->GetBitmap( 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( rPos, rSize ), rTransparenceGradient );
+ xVDev->SetDrawMode( DrawModeFlags::Default );
+ xVDev->EnableMapMode( false );
+ xVDev->DrawMask( Point(), xVDev->GetOutputSizePixel(), aMask, COL_WHITE );
+
+ aAlpha = xVDev->GetBitmap( Point(), xVDev->GetOutputSizePixel() );
+
+ xVDev.disposeAndClear();
+
+ EnableMapMode( false );
+ DrawBitmapEx( aDstRect.TopLeft(), BitmapEx( aPaint, aAlpha ) );
+ EnableMapMode( bOldMap );
+ }
+ }
+ }
+
+ mpMetaFile = pOldMetaFile;
+ }
+}
+
+/* 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 000000000..62dd0e67f
--- /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 000000000..594fdbe05
--- /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 <cassert>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/virdev.hxx>
+
+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;
+}
+
+Color OutputDevice::GetBackgroundColor() const
+{
+ return GetBackground().GetColor();
+}
+
+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.Justify();
+
+ if ( !aRect.IsEmpty() )
+ {
+ DrawWallpaper( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(),
+ rWallpaper );
+ }
+ }
+
+ if( mpAlphaVDev )
+ mpAlphaVDev->DrawWallpaper( rRect, rWallpaper );
+}
+
+void OutputDevice::DrawWallpaper( long nX, long nY,
+ long nWidth, 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( long nX, long nY,
+ long nWidth, 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( long nX, long nY,
+ long nWidth, long nHeight,
+ const Wallpaper& rWallpaper )
+{
+ assert(!is_double_buffered_window());
+
+ BitmapEx aBmpEx;
+ const BitmapEx* pCached = rWallpaper.ImplGetCachedBitmap();
+ Point aPos;
+ Size aSize;
+ GDIMetaFile* pOldMetaFile = mpMetaFile;
+ const WallpaperStyle eStyle = rWallpaper.GetStyle();
+ const bool bOldMap = mbMap;
+ bool bDrawn = false;
+ bool bDrawGradientBackground = false;
+ bool bDrawColorBackground = false;
+
+ if( pCached )
+ aBmpEx = *pCached;
+ else
+ aBmpEx = rWallpaper.GetBitmap();
+
+ const long nBmpWidth = aBmpEx.GetSizePixel().Width();
+ const long nBmpHeight = aBmpEx.GetSizePixel().Height();
+ const bool bTransparent = aBmpEx.IsTransparent();
+
+ // draw background
+ if( bTransparent )
+ {
+ if( rWallpaper.IsGradient() )
+ bDrawGradientBackground = true;
+ else
+ {
+ if( !pCached && !rWallpaper.GetColor().GetTransparency() )
+ {
+ 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;
+ }
+
+ // 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( PushFlags::CLIPREGION );
+ IntersectClipRegion( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ) );
+
+ 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.GetMask() );
+ }
+ 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 long nRight = nX + nWidth - 1;
+ const long nBottom = nY + nHeight - 1;
+ long nFirstX;
+ 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 long nOffX = ( nFirstX - nX ) % nBmpWidth;
+ const long nOffY = ( nFirstY - nY ) % nBmpHeight;
+ long nStartX = nX + nOffX;
+ long nStartY = nY + nOffY;
+
+ if( nOffX > 0 )
+ nStartX -= nBmpWidth;
+
+ if( nOffY > 0 )
+ nStartY -= nBmpHeight;
+
+ for( long nBmpY = nStartY; nBmpY <= nBottom; nBmpY += nBmpHeight )
+ {
+ for( 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.Justify();
+ 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.Justify();
+ 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.Justify();
+ 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.Justify();
+ 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( long nX, long nY,
+ long nWidth, 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( 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: */