diff options
Diffstat (limited to 'vcl/source/outdev')
25 files changed, 15555 insertions, 0 deletions
diff --git a/vcl/source/outdev/background.cxx b/vcl/source/outdev/background.cxx new file mode 100644 index 0000000000..7c07367a82 --- /dev/null +++ b/vcl/source/outdev/background.cxx @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/virdev.hxx> + +Color OutputDevice::GetBackgroundColor() const +{ + return GetBackground().GetColor(); +} + +void OutputDevice::SetBackground() +{ + + maBackground = Wallpaper(); + mbBackground = false; + + if( mpAlphaVDev ) + mpAlphaVDev->SetBackground(); +} + +void OutputDevice::SetBackground( const Wallpaper& rBackground ) +{ + + maBackground = rBackground; + + if( rBackground.GetStyle() == WallpaperStyle::NONE ) + mbBackground = false; + else + mbBackground = true; + + if( !mpAlphaVDev ) + return; + + // Some of these are probably wrong (e.g. if the gradient has transparency), + // but hopefully nobody uses that. If you do, feel free to implement it properly. + if( rBackground.GetStyle() == WallpaperStyle::NONE ) + { + mpAlphaVDev->SetBackground( rBackground ); + } + else if( rBackground.IsBitmap()) + { + BitmapEx bitmap = rBackground.GetBitmap(); + if( bitmap.IsAlpha()) + mpAlphaVDev->SetBackground( Wallpaper( BitmapEx( bitmap.GetAlphaMask().GetBitmap() ) ) ); + else + mpAlphaVDev->SetBackground( Wallpaper( COL_ALPHA_OPAQUE )); + } + else if( rBackground.IsGradient()) + { + mpAlphaVDev->SetBackground( Wallpaper( COL_ALPHA_OPAQUE )); + } + else + { + // Color background. + int alpha = rBackground.GetColor().GetAlpha(); + mpAlphaVDev->SetBackground( Wallpaper( Color( alpha, alpha, alpha ))); + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/vcl/source/outdev/bitmap.cxx b/vcl/source/outdev/bitmap.cxx new file mode 100644 index 0000000000..86ac231375 --- /dev/null +++ b/vcl/source/outdev/bitmap.cxx @@ -0,0 +1,993 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_features.h> + +#include <osl/diagnose.h> +#include <tools/debug.hxx> +#include <tools/helpers.hxx> + +#include <vcl/image.hxx> +#include <vcl/metaact.hxx> +#include <vcl/skia/SkiaHelper.hxx> +#include <vcl/virdev.hxx> +#include <vcl/BitmapWriteAccess.hxx> + +#include <bitmap/bmpfast.hxx> +#include <drawmode.hxx> +#include <salbmp.hxx> +#include <salgdi.hxx> + +void OutputDevice::DrawBitmap( const Point& rDestPt, const Bitmap& rBitmap ) +{ + assert(!is_double_buffered_window()); + + const Size aSizePix( rBitmap.GetSizePixel() ); + DrawBitmap( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmap, MetaActionType::BMP ); +} + +void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize, const Bitmap& rBitmap ) +{ + assert(!is_double_buffered_window()); + + DrawBitmap( rDestPt, rDestSize, Point(), rBitmap.GetSizePixel(), rBitmap, MetaActionType::BMPSCALE ); +} + +void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel, + const Bitmap& rBitmap) +{ + assert(!is_double_buffered_window()); + + DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmap, MetaActionType::BMPSCALEPART ); +} + +void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel, + const Bitmap& rBitmap, const MetaActionType nAction ) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if ( RasterOp::Invert == meRasterOp ) + { + DrawRect( tools::Rectangle( rDestPt, rDestSize ) ); + return; + } + + Bitmap aBmp( rBitmap ); + + if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | + DrawModeFlags::GrayBitmap ) ) + { + if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap ) ) + { + sal_uInt8 cCmpVal; + + if ( mnDrawMode & DrawModeFlags::BlackBitmap ) + cCmpVal = 0; + else + cCmpVal = 255; + + Color aCol( cCmpVal, cCmpVal, cCmpVal ); + Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR ); + SetLineColor( aCol ); + SetFillColor( aCol ); + DrawRect( tools::Rectangle( rDestPt, rDestSize ) ); + Pop(); + return; + } + else if( !aBmp.IsEmpty() ) + { + if ( mnDrawMode & DrawModeFlags::GrayBitmap ) + aBmp.Convert( BmpConversion::N8BitGreys ); + } + } + + if ( mpMetaFile ) + { + switch( nAction ) + { + case MetaActionType::BMP: + mpMetaFile->AddAction( new MetaBmpAction( rDestPt, aBmp ) ); + break; + + case MetaActionType::BMPSCALE: + mpMetaFile->AddAction( new MetaBmpScaleAction( rDestPt, rDestSize, aBmp ) ); + break; + + case MetaActionType::BMPSCALEPART: + mpMetaFile->AddAction( new MetaBmpScalePartAction( + rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmp ) ); + break; + + default: break; + } + } + + if ( !IsDeviceOutputNecessary() ) + return; + + if (!mpGraphics && !AcquireGraphics()) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if( !aBmp.IsEmpty() ) + { + SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(), + ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()), + ImplLogicWidthToDevicePixel(rDestSize.Width()), + ImplLogicHeightToDevicePixel(rDestSize.Height())); + + if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight ) + { + const BmpMirrorFlags nMirrFlags = AdjustTwoRect( aPosAry, aBmp.GetSizePixel() ); + + if ( nMirrFlags != BmpMirrorFlags::NONE ) + aBmp.Mirror( nMirrFlags ); + + if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight ) + { + if (nAction == MetaActionType::BMPSCALE && CanSubsampleBitmap()) + { + double nScaleX = aPosAry.mnDestWidth / static_cast<double>(aPosAry.mnSrcWidth); + double nScaleY = aPosAry.mnDestHeight / static_cast<double>(aPosAry.mnSrcHeight); + + // If subsampling, use Bitmap::Scale() for subsampling of better quality. + + // but hidpi surfaces like the cairo one have their own scale, so don't downscale + // past the surface scaling which can retain the extra detail + double fScale(1.0); + if (mpGraphics->ShouldDownscaleIconsAtSurface(&fScale)) + { + nScaleX *= fScale; + nScaleY *= fScale; + } + + if ( nScaleX < 1.0 || nScaleY < 1.0 ) + { + aBmp.Scale(nScaleX, nScaleY); + aPosAry.mnSrcWidth = aPosAry.mnDestWidth * fScale; + aPosAry.mnSrcHeight = aPosAry.mnDestHeight * fScale; + } + } + + mpGraphics->DrawBitmap( aPosAry, *aBmp.ImplGetSalBitmap(), *this ); + } + } + } + + if( mpAlphaVDev ) + { + // #i32109#: Make bitmap area opaque + mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) ); + } +} + +Bitmap OutputDevice::GetBitmap( const Point& rSrcPt, const Size& rSize ) const +{ + if ( !mpGraphics && !AcquireGraphics() ) + return Bitmap(); + + assert(mpGraphics); + + tools::Long nX = ImplLogicXToDevicePixel( rSrcPt.X() ); + tools::Long nY = ImplLogicYToDevicePixel( rSrcPt.Y() ); + tools::Long nWidth = ImplLogicWidthToDevicePixel( rSize.Width() ); + tools::Long nHeight = ImplLogicHeightToDevicePixel( rSize.Height() ); + if ( nWidth <= 0 || nHeight <= 0 || nX > (mnOutWidth + mnOutOffX) || nY > (mnOutHeight + mnOutOffY)) + return Bitmap(); + + Bitmap aBmp; + tools::Rectangle aRect( Point( nX, nY ), Size( nWidth, nHeight ) ); + bool bClipped = false; + + // X-Coordinate outside of draw area? + if ( nX < mnOutOffX ) + { + nWidth -= ( mnOutOffX - nX ); + nX = mnOutOffX; + bClipped = true; + } + + // Y-Coordinate outside of draw area? + if ( nY < mnOutOffY ) + { + nHeight -= ( mnOutOffY - nY ); + nY = mnOutOffY; + bClipped = true; + } + + // Width outside of draw area? + if ( (nWidth + nX) > (mnOutWidth + mnOutOffX) ) + { + nWidth = mnOutOffX + mnOutWidth - nX; + bClipped = true; + } + + // Height outside of draw area? + if ( (nHeight + nY) > (mnOutHeight + mnOutOffY) ) + { + nHeight = mnOutOffY + mnOutHeight - nY; + bClipped = true; + } + + if ( bClipped ) + { + // If the visible part has been clipped, we have to create a + // Bitmap with the correct size in which we copy the clipped + // Bitmap to the correct position. + ScopedVclPtrInstance< VirtualDevice > aVDev( *this ); + + if ( aVDev->SetOutputSizePixel( aRect.GetSize() ) ) + { + if ( aVDev->mpGraphics || aVDev->AcquireGraphics() ) + { + if ( (nWidth > 0) && (nHeight > 0) ) + { + SalTwoRect aPosAry(nX, nY, nWidth, nHeight, + (aRect.Left() < mnOutOffX) ? (mnOutOffX - aRect.Left()) : 0L, + (aRect.Top() < mnOutOffY) ? (mnOutOffY - aRect.Top()) : 0L, + nWidth, nHeight); + aVDev->mpGraphics->CopyBits(aPosAry, *mpGraphics, *this, *this); + } + else + { + OSL_ENSURE(false, "CopyBits with zero or negative width or height"); + } + + aBmp = aVDev->GetBitmap( Point(), aVDev->GetOutputSizePixel() ); + } + else + bClipped = false; + } + else + bClipped = false; + } + + if ( !bClipped ) + { + std::shared_ptr<SalBitmap> pSalBmp = mpGraphics->GetBitmap( nX, nY, nWidth, nHeight, *this ); + + if( pSalBmp ) + { + aBmp.ImplSetSalBitmap(pSalBmp); + } + } + + return aBmp; +} + +void OutputDevice::DrawDeviceAlphaBitmap( const Bitmap& rBmp, const AlphaMask& rAlpha, + const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel ) +{ + assert(!is_double_buffered_window()); + + Point aOutPt(LogicToPixel(rDestPt)); + Size aOutSz(LogicToPixel(rDestSize)); + tools::Rectangle aDstRect(Point(), GetOutputSizePixel()); + + const bool bHMirr = aOutSz.Width() < 0; + const bool bVMirr = aOutSz.Height() < 0; + + ClipToPaintRegion(aDstRect); + + BmpMirrorFlags mirrorFlags = BmpMirrorFlags::NONE; + if (bHMirr) + { + aOutSz.setWidth( -aOutSz.Width() ); + aOutPt.AdjustX( -(aOutSz.Width() - 1) ); + mirrorFlags |= BmpMirrorFlags::Horizontal; + } + + if (bVMirr) + { + aOutSz.setHeight( -aOutSz.Height() ); + aOutPt.AdjustY( -(aOutSz.Height() - 1) ); + mirrorFlags |= BmpMirrorFlags::Vertical; + } + + if (aDstRect.Intersection(tools::Rectangle(aOutPt, aOutSz)).IsEmpty()) + return; + + { + Point aRelPt = aOutPt + Point(mnOutOffX, mnOutOffY); + SalTwoRect aTR( + rSrcPtPixel.X(), rSrcPtPixel.Y(), + rSrcSizePixel.Width(), rSrcSizePixel.Height(), + aRelPt.X(), aRelPt.Y(), + aOutSz.Width(), aOutSz.Height()); + + Bitmap bitmap(rBmp); + AlphaMask alpha(rAlpha); + if(bHMirr || bVMirr) + { + bitmap.Mirror(mirrorFlags); + alpha.Mirror(mirrorFlags); + } + SalBitmap* pSalSrcBmp = bitmap.ImplGetSalBitmap().get(); + SalBitmap* pSalAlphaBmp = alpha.GetBitmap().ImplGetSalBitmap().get(); + + // #i83087# Naturally, system alpha blending (SalGraphics::DrawAlphaBitmap) cannot work + // with separate alpha VDev + + // try to blend the alpha bitmap with the alpha virtual device + if (mpAlphaVDev) + { + if (ImplLogicToDevicePixel(aOutSz).IsEmpty()) // nothing to draw + return; + Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aRelPt, aOutSz ) ); + if (SalBitmap* pSalAlphaBmp2 = aAlphaBitmap.ImplGetSalBitmap().get()) + { + if (mpGraphics->BlendAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, *pSalAlphaBmp2, *this)) + { + mpAlphaVDev->BlendBitmap(aTR, rAlpha.GetBitmap()); + return; + } + } + } + else + { + if (mpGraphics->DrawAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, *this)) + return; + } + + // we need to make sure Skia never reaches this slow code path + // (but do not fail in no-op cases) + assert(!SkiaHelper::isVCLSkiaEnabled() || !SkiaHelper::isAlphaMaskBlendingEnabled() + || tools::Rectangle(Point(), rBmp.GetSizePixel()) + .Intersection(tools::Rectangle(rSrcPtPixel, rSrcSizePixel)).IsEmpty() + || mpAlphaVDev->LogicToPixel(mpAlphaVDev->GetOutputSizePixel()).IsEmpty()); + } + + tools::Rectangle aBmpRect(Point(), rBmp.GetSizePixel()); + if (aBmpRect.Intersection(tools::Rectangle(rSrcPtPixel, rSrcSizePixel)).IsEmpty()) + return; + + Point auxOutPt(LogicToPixel(rDestPt)); + Size auxOutSz(LogicToPixel(rDestSize)); + + // HACK: The function is broken with alpha vdev and mirroring, mirror here. + Bitmap bitmap(rBmp); + AlphaMask alpha(rAlpha); + if(mpAlphaVDev && (bHMirr || bVMirr)) + { + bitmap.Mirror(mirrorFlags); + alpha.Mirror(mirrorFlags); + auxOutPt = aOutPt; + auxOutSz = aOutSz; + } + DrawDeviceAlphaBitmapSlowPath(bitmap, alpha, aDstRect, aBmpRect, auxOutSz, auxOutPt); +} + +namespace +{ + +struct LinearScaleContext +{ + std::unique_ptr<sal_Int32[]> mpMapX; + std::unique_ptr<sal_Int32[]> mpMapY; + + std::unique_ptr<sal_Int32[]> mpMapXOffset; + std::unique_ptr<sal_Int32[]> mpMapYOffset; + + LinearScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect, + Size const & aOutSize, tools::Long nOffX, tools::Long nOffY) + + : mpMapX(new sal_Int32[aDstRect.GetWidth()]) + , mpMapY(new sal_Int32[aDstRect.GetHeight()]) + , mpMapXOffset(new sal_Int32[aDstRect.GetWidth()]) + , mpMapYOffset(new sal_Int32[aDstRect.GetHeight()]) + { + const tools::Long nSrcWidth = aBitmapRect.GetWidth(); + const tools::Long nSrcHeight = aBitmapRect.GetHeight(); + + generateSimpleMap( + nSrcWidth, aDstRect.GetWidth(), aBitmapRect.Left(), + aOutSize.Width(), nOffX, mpMapX.get(), mpMapXOffset.get()); + + generateSimpleMap( + nSrcHeight, aDstRect.GetHeight(), aBitmapRect.Top(), + aOutSize.Height(), nOffY, mpMapY.get(), mpMapYOffset.get()); + } + +private: + + static void generateSimpleMap(tools::Long nSrcDimension, tools::Long nDstDimension, tools::Long nDstLocation, + tools::Long nOutDimension, tools::Long nOffset, sal_Int32* pMap, sal_Int32* pMapOffset) + { + + const double fReverseScale = (std::abs(nOutDimension) > 1) ? (nSrcDimension - 1) / double(std::abs(nOutDimension) - 1) : 0.0; + + tools::Long nSampleRange = std::max(tools::Long(0), nSrcDimension - 2); + + for (tools::Long i = 0; i < nDstDimension; i++) + { + double fTemp = std::abs((nOffset + i) * fReverseScale); + + pMap[i] = std::clamp(nDstLocation + tools::Long(fTemp), tools::Long(0), nSampleRange); + pMapOffset[i] = static_cast<tools::Long>((fTemp - pMap[i]) * 128.0); + } + } + +public: + bool blendBitmap( + const BitmapWriteAccess* pDestination, + const BitmapReadAccess* pSource, + const BitmapReadAccess* pSourceAlpha, + const tools::Long nDstWidth, + const tools::Long nDstHeight) + { + if (!pSource || !pSourceAlpha || !pDestination) + return false; + + ScanlineFormat nSourceFormat = pSource->GetScanlineFormat(); + ScanlineFormat nDestinationFormat = pDestination->GetScanlineFormat(); + + switch (nSourceFormat) + { + case ScanlineFormat::N24BitTcRgb: + case ScanlineFormat::N24BitTcBgr: + { + if ( (nSourceFormat == ScanlineFormat::N24BitTcBgr && nDestinationFormat == ScanlineFormat::N32BitTcBgra) + || (nSourceFormat == ScanlineFormat::N24BitTcRgb && nDestinationFormat == ScanlineFormat::N32BitTcRgba)) + { + blendBitmap24(pDestination, pSource, pSourceAlpha, nDstWidth, nDstHeight); + return true; + } + } + break; + default: break; + } + return false; + } + + void blendBitmap24( + const BitmapWriteAccess* pDestination, + const BitmapReadAccess* pSource, + const BitmapReadAccess* pSourceAlpha, + const tools::Long nDstWidth, + const tools::Long nDstHeight) + { + Scanline pLine0, pLine1; + Scanline pLineAlpha0, pLineAlpha1; + Scanline pColorSample1, pColorSample2; + Scanline pDestScanline; + + tools::Long nColor1Line1, nColor2Line1, nColor3Line1; + tools::Long nColor1Line2, nColor2Line2, nColor3Line2; + tools::Long nAlphaLine1, nAlphaLine2; + + sal_uInt8 nColor1, nColor2, nColor3, nAlpha; + + for (tools::Long nY = 0; nY < nDstHeight; nY++) + { + const tools::Long nMapY = mpMapY[nY]; + const tools::Long nMapFY = mpMapYOffset[nY]; + + pLine0 = pSource->GetScanline(nMapY); + // tdf#95481 guard nMapY + 1 to be within bounds + pLine1 = (nMapY + 1 < pSource->Height()) ? pSource->GetScanline(nMapY + 1) : pLine0; + + pLineAlpha0 = pSourceAlpha->GetScanline(nMapY); + // tdf#95481 guard nMapY + 1 to be within bounds + pLineAlpha1 = (nMapY + 1 < pSourceAlpha->Height()) ? pSourceAlpha->GetScanline(nMapY + 1) : pLineAlpha0; + + pDestScanline = pDestination->GetScanline(nY); + + for (tools::Long nX = 0; nX < nDstWidth; nX++) + { + const tools::Long nMapX = mpMapX[nX]; + const tools::Long nMapFX = mpMapXOffset[nX]; + + pColorSample1 = pLine0 + 3 * nMapX; + pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1; + nColor1Line1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1); + + pColorSample1++; + pColorSample2++; + nColor2Line1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1); + + pColorSample1++; + pColorSample2++; + nColor3Line1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1); + + pColorSample1 = pLine1 + 3 * nMapX; + pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1; + nColor1Line2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1); + + pColorSample1++; + pColorSample2++; + nColor2Line2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1); + + pColorSample1++; + pColorSample2++; + nColor3Line2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1); + + pColorSample1 = pLineAlpha0 + nMapX; + pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1; + nAlphaLine1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1); + + pColorSample1 = pLineAlpha1 + nMapX; + pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1; + nAlphaLine2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1); + + nColor1 = (nColor1Line1 + nMapFY * ((nColor1Line2 >> 7) - (nColor1Line1 >> 7))) >> 7; + nColor2 = (nColor2Line1 + nMapFY * ((nColor2Line2 >> 7) - (nColor2Line1 >> 7))) >> 7; + nColor3 = (nColor3Line1 + nMapFY * ((nColor3Line2 >> 7) - (nColor3Line1 >> 7))) >> 7; + + nAlpha = (nAlphaLine1 + nMapFY * ((nAlphaLine2 >> 7) - (nAlphaLine1 >> 7))) >> 7; + + *pDestScanline = color::ColorChannelMerge(*pDestScanline, nColor1, nAlpha); + pDestScanline++; + *pDestScanline = color::ColorChannelMerge(*pDestScanline, nColor2, nAlpha); + pDestScanline++; + *pDestScanline = color::ColorChannelMerge(*pDestScanline, nColor3, nAlpha); + pDestScanline++; + pDestScanline++; + } + } + } +}; + +struct TradScaleContext +{ + std::unique_ptr<sal_Int32[]> mpMapX; + std::unique_ptr<sal_Int32[]> mpMapY; + + TradScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect, + Size const & aOutSize, tools::Long nOffX, tools::Long nOffY) + + : mpMapX(new sal_Int32[aDstRect.GetWidth()]) + , mpMapY(new sal_Int32[aDstRect.GetHeight()]) + { + const tools::Long nSrcWidth = aBitmapRect.GetWidth(); + const tools::Long nSrcHeight = aBitmapRect.GetHeight(); + + const bool bHMirr = aOutSize.Width() < 0; + const bool bVMirr = aOutSize.Height() < 0; + + generateSimpleMap( + nSrcWidth, aDstRect.GetWidth(), aBitmapRect.Left(), + aOutSize.Width(), nOffX, bHMirr, mpMapX.get()); + + generateSimpleMap( + nSrcHeight, aDstRect.GetHeight(), aBitmapRect.Top(), + aOutSize.Height(), nOffY, bVMirr, mpMapY.get()); + } + +private: + + static void generateSimpleMap(tools::Long nSrcDimension, tools::Long nDstDimension, tools::Long nDstLocation, + tools::Long nOutDimension, tools::Long nOffset, bool bMirror, sal_Int32* pMap) + { + tools::Long nMirrorOffset = 0; + + if (bMirror) + nMirrorOffset = (nDstLocation << 1) + nSrcDimension - 1; + + for (tools::Long i = 0; i < nDstDimension; ++i, ++nOffset) + { + pMap[i] = nDstLocation + nOffset * nSrcDimension / nOutDimension; + if (bMirror) + pMap[i] = nMirrorOffset - pMap[i]; + } + } +}; + + +} // end anonymous namespace + +void OutputDevice::DrawDeviceAlphaBitmapSlowPath(const Bitmap& rBitmap, + const AlphaMask& rAlpha, tools::Rectangle aDstRect, tools::Rectangle aBmpRect, Size const & aOutSize, Point const & aOutPoint) +{ + assert(!is_double_buffered_window()); + + VirtualDevice* pOldVDev = mpAlphaVDev; + + const bool bHMirr = aOutSize.Width() < 0; + const bool bVMirr = aOutSize.Height() < 0; + + // The scaling in this code path produces really ugly results - it + // does the most trivial scaling with no smoothing. + GDIMetaFile* pOldMetaFile = mpMetaFile; + const bool bOldMap = mbMap; + + mpMetaFile = nullptr; // fdo#55044 reset before GetBitmap! + mbMap = false; + + Bitmap aBmp(GetBitmap(aDstRect.TopLeft(), aDstRect.GetSize())); + + // #109044# The generated bitmap need not necessarily be + // of aDstRect dimensions, it's internally clipped to + // window bounds. Thus, we correct the dest size here, + // since we later use it (in nDstWidth/Height) for pixel + // access) + // #i38887# reading from screen may sometimes fail + if (aBmp.ImplGetSalBitmap()) + { + aDstRect.SetSize(aBmp.GetSizePixel()); + } + + const tools::Long nDstWidth = aDstRect.GetWidth(); + const tools::Long nDstHeight = aDstRect.GetHeight(); + + // calculate offset in original bitmap + // in RTL case this is a little more complicated since the contents of the + // bitmap is not mirrored (it never is), however the paint region and bmp region + // are in mirrored coordinates, so the intersection of (aOutPt,aOutSz) with these + // is content wise somewhere else and needs to take mirroring into account + const tools::Long nOffX = IsRTLEnabled() + ? aOutSize.Width() - aDstRect.GetWidth() - (aDstRect.Left() - aOutPoint.X()) + : aDstRect.Left() - aOutPoint.X(); + + const tools::Long nOffY = aDstRect.Top() - aOutPoint.Y(); + + TradScaleContext aTradContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY); + + BitmapScopedReadAccess pBitmapReadAccess(rBitmap); + BitmapScopedReadAccess pAlphaReadAccess(rAlpha); + + SAL_WARN_IF(pAlphaReadAccess->GetScanlineFormat() != ScanlineFormat::N8BitPal, "vcl.gdi", "non-8bit alpha no longer supported!"); + assert(pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal); + + // #i38887# reading from screen may sometimes fail + if (aBmp.ImplGetSalBitmap()) + { + Bitmap aNewBitmap; + + if (mpAlphaVDev) + { + aNewBitmap = BlendBitmapWithAlpha( + aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(), + aDstRect, + nOffY, nDstHeight, + nOffX, nDstWidth, + aTradContext.mpMapX.get(), aTradContext.mpMapY.get() ); + } + else + { + LinearScaleContext aLinearContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY); + + if (aLinearContext.blendBitmap( BitmapScopedWriteAccess(aBmp).get(), pBitmapReadAccess.get(), pAlphaReadAccess.get(), + nDstWidth, nDstHeight)) + { + aNewBitmap = aBmp; + } + else + { + aNewBitmap = BlendBitmap( + aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(), + nOffY, nDstHeight, + nOffX, nDstWidth, + aBmpRect, aOutSize, + bHMirr, bVMirr, + aTradContext.mpMapX.get(), aTradContext.mpMapY.get() ); + } + } + + // #110958# Disable alpha VDev, we're doing the necessary + // stuff explicitly further below + if (mpAlphaVDev) + mpAlphaVDev = nullptr; + + DrawBitmap(aDstRect.TopLeft(), aNewBitmap); + + // #110958# Enable alpha VDev again + mpAlphaVDev = pOldVDev; + } + + mbMap = bOldMap; + mpMetaFile = pOldMetaFile; +} + +bool OutputDevice::HasFastDrawTransformedBitmap() const +{ + if( ImplIsRecordLayout() ) + return false; + + if (!mpGraphics && !AcquireGraphics()) + return false; + assert(mpGraphics); + + return mpGraphics->HasFastDrawTransformedBitmap(); +} + +void OutputDevice::DrawImage( const Point& rPos, const Image& rImage, DrawImageFlags nStyle ) +{ + assert(!is_double_buffered_window()); + + DrawImage( rPos, Size(), rImage, nStyle ); +} + +void OutputDevice::DrawImage( const Point& rPos, const Size& rSize, + const Image& rImage, DrawImageFlags nStyle ) +{ + assert(!is_double_buffered_window()); + + bool bIsSizeValid = !rSize.IsEmpty(); + + if (!ImplIsRecordLayout()) + { + Image& rNonConstImage = const_cast<Image&>(rImage); + if (bIsSizeValid) + rNonConstImage.Draw(this, rPos, nStyle, &rSize); + else + rNonConstImage.Draw(this, rPos, nStyle); + } +} + +namespace +{ + // Co = Cs + Cd*(1-As) premultiplied alpha -or- + // Co = (AsCs + AdCd*(1-As)) / Ao + sal_uInt8 CalcColor( const sal_uInt8 nSourceColor, const sal_uInt8 nSourceAlpha, + const sal_uInt8 nDstAlpha, const sal_uInt8 nResAlpha, const sal_uInt8 nDestColor ) + { + int c = nResAlpha ? ( static_cast<int>(nSourceAlpha)*nSourceColor + static_cast<int>(nDstAlpha)*nDestColor - + static_cast<int>(nDstAlpha)*nDestColor*nSourceAlpha/255 ) / static_cast<int>(nResAlpha) : 0; + return sal_uInt8( c ); + } + + BitmapColor AlphaBlend( int nX, int nY, + const tools::Long nMapX, + const tools::Long nMapY, + BitmapReadAccess const * pP, + BitmapReadAccess const * pA, + BitmapReadAccess const * pB, + BitmapWriteAccess const * pAlphaW, + sal_uInt8& nResAlpha ) + { + BitmapColor aDstCol,aSrcCol; + aSrcCol = pP->GetColor( nMapY, nMapX ); + aDstCol = pB->GetColor( nY, nX ); + + const sal_uInt8 nSrcAlpha = pA->GetPixelIndex( nMapY, nMapX ); + const sal_uInt8 nDstAlpha = pAlphaW->GetPixelIndex( nY, nX ); + + // Perform porter-duff compositing 'over' operation + + // Co = Cs + Cd*(1-As) + // Ad = As + Ad*(1-As) + nResAlpha = static_cast<int>(nSrcAlpha) + static_cast<int>(nDstAlpha) - static_cast<int>(nDstAlpha)*nSrcAlpha/255; + + aDstCol.SetRed( CalcColor( aSrcCol.GetRed(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetRed() ) ); + aDstCol.SetBlue( CalcColor( aSrcCol.GetBlue(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetBlue() ) ); + aDstCol.SetGreen( CalcColor( aSrcCol.GetGreen(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetGreen() ) ); + + return aDstCol; + } +} + +void OutputDevice::BlendBitmap( + const SalTwoRect& rPosAry, + const Bitmap& rBmp ) +{ + mpGraphics->BlendBitmap( rPosAry, *rBmp.ImplGetSalBitmap(), *this ); +} + +Bitmap OutputDevice::BlendBitmapWithAlpha( + Bitmap& aBmp, + BitmapReadAccess const * pP, + BitmapReadAccess const * pA, + const tools::Rectangle& aDstRect, + const sal_Int32 nOffY, + const sal_Int32 nDstHeight, + const sal_Int32 nOffX, + const sal_Int32 nDstWidth, + const sal_Int32* pMapX, + const sal_Int32* pMapY ) + +{ + BitmapColor aDstCol; + Bitmap res; + int nX, nY; + sal_uInt8 nResAlpha; + + SAL_WARN_IF( !mpAlphaVDev, "vcl.gdi", "BlendBitmapWithAlpha(): call me only with valid alpha VirtualDevice!" ); + + bool bOldMapMode( mpAlphaVDev->IsMapModeEnabled() ); + mpAlphaVDev->EnableMapMode(false); + + Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aDstRect.TopLeft(), aDstRect.GetSize() ) ); + BitmapScopedWriteAccess pAlphaW(aAlphaBitmap); + + if( GetBitCount() <= 8 ) + { + Bitmap aDither(aBmp.GetSizePixel(), vcl::PixelFormat::N8_BPP); + BitmapColor aIndex( 0 ); + BitmapScopedReadAccess pB(aBmp); + BitmapScopedWriteAccess pW(aDither); + + if (pB && pP && pA && pW && pAlphaW) + { + int nOutY; + + for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ ) + { + const tools::Long nMapY = pMapY[ nY ]; + const tools::Long nModY = ( nOutY & 0x0FL ) << 4; + int nOutX; + + Scanline pScanline = pW->GetScanline(nY); + Scanline pScanlineAlpha = pAlphaW->GetScanline(nY); + for( nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ ) + { + const tools::Long nMapX = pMapX[ nX ]; + const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ]; + + aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha ); + + aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] + + nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] + + nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) ); + pW->SetPixelOnData( pScanline, nX, aIndex ); + + aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ nResAlpha ] + nD ) >> 16 ] + + nVCLGLut[ ( nVCLLut[ nResAlpha ] + nD ) >> 16 ] + + nVCLBLut[ ( nVCLLut[ nResAlpha ] + nD ) >> 16 ] ) ); + pAlphaW->SetPixelOnData( pScanlineAlpha, nX, aIndex ); + } + } + } + pB.reset(); + pW.reset(); + res = aDither; + } + else + { + BitmapScopedWriteAccess pB(aBmp); + if (pB && pP && pA && pAlphaW) + { + for( nY = 0; nY < nDstHeight; nY++ ) + { + const tools::Long nMapY = pMapY[ nY ]; + Scanline pScanlineB = pB->GetScanline(nY); + Scanline pScanlineAlpha = pAlphaW->GetScanline(nY); + + for( nX = 0; nX < nDstWidth; nX++ ) + { + const tools::Long nMapX = pMapX[ nX ]; + aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha ); + + pB->SetPixelOnData(pScanlineB, nX, pB->GetBestMatchingColor(aDstCol)); + pAlphaW->SetPixelOnData(pScanlineAlpha, nX, pB->GetBestMatchingColor(Color(nResAlpha, nResAlpha, nResAlpha))); + } + } + } + pB.reset(); + res = aBmp; + } + + pAlphaW.reset(); + mpAlphaVDev->DrawBitmap( aDstRect.TopLeft(), aAlphaBitmap ); + mpAlphaVDev->EnableMapMode( bOldMapMode ); + + return res; +} + +Bitmap OutputDevice::BlendBitmap( + Bitmap& aBmp, + BitmapReadAccess const * pP, + BitmapReadAccess const * pA, + const sal_Int32 nOffY, + const sal_Int32 nDstHeight, + const sal_Int32 nOffX, + const sal_Int32 nDstWidth, + const tools::Rectangle& aBmpRect, + const Size& aOutSz, + const bool bHMirr, + const bool bVMirr, + const sal_Int32* pMapX, + const sal_Int32* pMapY ) +{ + if( !pP || !pA ) + return aBmp; + + if( GetBitCount() <= 8 ) + { + Bitmap aDither(aBmp.GetSizePixel(), vcl::PixelFormat::N8_BPP); + BitmapColor aIndex( 0 ); + BitmapScopedReadAccess pB(aBmp); + BitmapScopedWriteAccess pW(aDither); + + for( int nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ ) + { + tools::Long nMapY = pMapY[ nY ]; + if (bVMirr) + { + nMapY = aBmpRect.Bottom() - nMapY; + } + const tools::Long nModY = ( nOutY & 0x0FL ) << 4; + + Scanline pScanline = pW->GetScanline(nY); + Scanline pScanlineAlpha = pA->GetScanline(nMapY); + for( int nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ ) + { + tools::Long nMapX = pMapX[ nX ]; + if (bHMirr) + { + nMapX = aBmpRect.Right() - nMapX; + } + const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ]; + + BitmapColor aDstCol = pB->GetColor( nY, nX ); + aDstCol.Merge( pP->GetColor( nMapY, nMapX ), 255 - pA->GetIndexFromData( pScanlineAlpha, nMapX ) ); + aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] + + nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] + + nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) ); + pW->SetPixelOnData( pScanline, nX, aIndex ); + } + } + + pB.reset(); + pW.reset(); + return aDither; + } + + BitmapScopedWriteAccess pB(aBmp); + + bool bFastBlend = false; + if (!bHMirr && !bVMirr) + { + SalTwoRect aTR(aBmpRect.Left(), aBmpRect.Top(), aBmpRect.GetWidth(), aBmpRect.GetHeight(), + nOffX, nOffY, aOutSz.Width(), aOutSz.Height()); + + bFastBlend = ImplFastBitmapBlending(*pB, *pP, *pA, aTR); + } + + if (!bFastBlend) + { + for (int nY = 0; nY < nDstHeight; nY++) + { + tools::Long nMapY = pMapY[nY]; + + if (bVMirr) + nMapY = aBmpRect.Bottom() - nMapY; + + Scanline pAScan = pA->GetScanline(nMapY); + Scanline pBScan = pB->GetScanline(nY); + for(int nX = 0; nX < nDstWidth; nX++) + { + tools::Long nMapX = pMapX[nX]; + + if (bHMirr) + nMapX = aBmpRect.Right() - nMapX; + + BitmapColor aDstCol = pB->GetPixelFromData(pBScan, nX); + aDstCol.Merge(pP->GetColor(nMapY, nMapX), pAScan[nMapX]); + pB->SetPixelOnData(pBScan, nX, aDstCol); + } + } + } + + pB.reset(); + return aBmp; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/bitmapex.cxx b/vcl/source/outdev/bitmapex.cxx new file mode 100644 index 0000000000..04a50d5545 --- /dev/null +++ b/vcl/source/outdev/bitmapex.cxx @@ -0,0 +1,680 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_features.h> + +#include <rtl/math.hxx> +#include <comphelper/lok.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +#include <vcl/canvastools.hxx> +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> + +#include <drawmode.hxx> +#include <salgdi.hxx> + +void OutputDevice::DrawBitmapEx( const Point& rDestPt, + const BitmapEx& rBitmapEx ) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if( !rBitmapEx.IsAlpha() ) + { + DrawBitmap( rDestPt, rBitmapEx.GetBitmap() ); + } + else + { + const Size aSizePix( rBitmapEx.GetSizePixel() ); + DrawBitmapEx( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmapEx, MetaActionType::BMPEX ); + } +} + +void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize, + const BitmapEx& rBitmapEx ) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if ( !rBitmapEx.IsAlpha() ) + { + DrawBitmap( rDestPt, rDestSize, rBitmapEx.GetBitmap() ); + } + else + { + DrawBitmapEx( rDestPt, rDestSize, Point(), rBitmapEx.GetSizePixel(), rBitmapEx, MetaActionType::BMPEXSCALE ); + } +} + +void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel, + const BitmapEx& rBitmapEx) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if ( !rBitmapEx.IsAlpha() ) + { + DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx.GetBitmap() ); + } + else + { + DrawBitmapEx( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx, MetaActionType::BMPEXSCALEPART ); + } +} + +void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel, + const BitmapEx& rBitmapEx, const MetaActionType nAction ) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if( !rBitmapEx.IsAlpha() ) + { + DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx.GetBitmap() ); + } + else + { + if ( RasterOp::Invert == meRasterOp ) + { + DrawRect( tools::Rectangle( rDestPt, rDestSize ) ); + return; + } + + BitmapEx aBmpEx(vcl::drawmode::GetBitmapEx(rBitmapEx, GetDrawMode())); + + if ( mpMetaFile ) + { + switch( nAction ) + { + case MetaActionType::BMPEX: + mpMetaFile->AddAction( new MetaBmpExAction( rDestPt, aBmpEx ) ); + break; + + case MetaActionType::BMPEXSCALE: + mpMetaFile->AddAction( new MetaBmpExScaleAction( rDestPt, rDestSize, aBmpEx ) ); + break; + + case MetaActionType::BMPEXSCALEPART: + mpMetaFile->AddAction( new MetaBmpExScalePartAction( rDestPt, rDestSize, + rSrcPtPixel, rSrcSizePixel, aBmpEx ) ); + break; + + default: break; + } + } + + if ( !IsDeviceOutputNecessary() ) + return; + + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + DrawDeviceBitmapEx( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmpEx ); + } +} + +BitmapEx OutputDevice::GetBitmapEx( const Point& rSrcPt, const Size& rSize ) const +{ + + // #110958# Extract alpha value from VDev, if any + if( mpAlphaVDev ) + { + Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( rSrcPt, rSize ) ); + + // ensure 8 bit alpha + if (aAlphaBitmap.getPixelFormat() > vcl::PixelFormat::N8_BPP) + aAlphaBitmap.Convert( BmpConversion::N8BitNoConversion ); + + return BitmapEx(GetBitmap( rSrcPt, rSize ), AlphaMask( aAlphaBitmap ) ); + } + + return BitmapEx(GetBitmap( rSrcPt, rSize )); +} + +void OutputDevice::DrawDeviceBitmapEx( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel, + BitmapEx& rBitmapEx ) +{ + assert(!is_double_buffered_window()); + + if (rBitmapEx.IsAlpha()) + { + DrawDeviceAlphaBitmap(rBitmapEx.GetBitmap(), rBitmapEx.GetAlphaMask(), rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel); + } + else if (!rBitmapEx.IsEmpty()) + { + SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(), + ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()), + ImplLogicWidthToDevicePixel(rDestSize.Width()), + ImplLogicHeightToDevicePixel(rDestSize.Height())); + + const BmpMirrorFlags nMirrFlags = AdjustTwoRect(aPosAry, rBitmapEx.GetSizePixel()); + + if (aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight) + { + + if (nMirrFlags != BmpMirrorFlags::NONE) + rBitmapEx.Mirror(nMirrFlags); + + const SalBitmap* pSalSrcBmp = rBitmapEx.ImplGetBitmapSalBitmap().get(); + std::shared_ptr<SalBitmap> xMaskBmp = rBitmapEx.maAlphaMask.GetBitmap().ImplGetSalBitmap(); + + if (xMaskBmp) + { + bool bTryDirectPaint(pSalSrcBmp); + + if (bTryDirectPaint && mpGraphics->DrawAlphaBitmap(aPosAry, *pSalSrcBmp, *xMaskBmp, *this)) + { + // tried to paint as alpha directly. If this worked, we are done (except + // alpha, see below) + } + else + { + // #4919452# reduce operation area to bounds of + // cliprect. since masked transparency involves + // creation of a large vdev and copying the screen + // content into that (slooow read from framebuffer), + // that should considerably increase performance for + // large bitmaps and small clippings. + + // Note that this optimization is a workaround for a + // Writer peculiarity, namely, to decompose background + // graphics into myriads of disjunct, tiny + // rectangles. That otherwise kills us here, since for + // transparent output, SAL always prepares the whole + // bitmap, if aPosAry contains the whole bitmap (and + // it's _not_ to blame for that). + + // Note the call to ImplPixelToDevicePixel(), since + // aPosAry already contains the mnOutOff-offsets, they + // also have to be applied to the region + tools::Rectangle aClipRegionBounds( ImplPixelToDevicePixel(maRegion).GetBoundRect() ); + + // TODO: Also respect scaling (that's a bit tricky, + // since the source points have to move fractional + // amounts (which is not possible, thus has to be + // emulated by increases copy area) + // const double nScaleX( aPosAry.mnDestWidth / aPosAry.mnSrcWidth ); + // const double nScaleY( aPosAry.mnDestHeight / aPosAry.mnSrcHeight ); + + // for now, only identity scales allowed + if (!aClipRegionBounds.IsEmpty() && + aPosAry.mnDestWidth == aPosAry.mnSrcWidth && + aPosAry.mnDestHeight == aPosAry.mnSrcHeight) + { + // now intersect dest rect with clip region + aClipRegionBounds.Intersection(tools::Rectangle(aPosAry.mnDestX, + aPosAry.mnDestY, + aPosAry.mnDestX + aPosAry.mnDestWidth - 1, + aPosAry.mnDestY + aPosAry.mnDestHeight - 1)); + + // Note: I could theoretically optimize away the + // DrawBitmap below, if the region is empty + // here. Unfortunately, cannot rule out that + // somebody relies on the side effects. + if (!aClipRegionBounds.IsEmpty()) + { + aPosAry.mnSrcX += aClipRegionBounds.Left() - aPosAry.mnDestX; + aPosAry.mnSrcY += aClipRegionBounds.Top() - aPosAry.mnDestY; + aPosAry.mnSrcWidth = aClipRegionBounds.GetWidth(); + aPosAry.mnSrcHeight = aClipRegionBounds.GetHeight(); + + aPosAry.mnDestX = aClipRegionBounds.Left(); + aPosAry.mnDestY = aClipRegionBounds.Top(); + aPosAry.mnDestWidth = aClipRegionBounds.GetWidth(); + aPosAry.mnDestHeight = aClipRegionBounds.GetHeight(); + } + } + + mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, *xMaskBmp, *this); + } + + // #110958# Paint mask to alpha channel. Luckily, the + // black and white representation of the mask maps to + // the alpha channel + + // #i25167# Restrict mask painting to _opaque_ areas + // of the mask, otherwise we spoil areas where no + // bitmap content was ever visible. Interestingly + // enough, this can be achieved by taking the mask as + // the transparency mask of itself + if (mpAlphaVDev) + mpAlphaVDev->DrawBitmapEx(rDestPt, + rDestSize, + BitmapEx(rBitmapEx.GetAlphaMask().GetBitmap(), + rBitmapEx.GetAlphaMask())); + } + else + { + mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, *this); + + if (mpAlphaVDev) + { + // #i32109#: Make bitmap area opaque + mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) ); + } + } + } + } +} + +bool OutputDevice::DrawTransformBitmapExDirect( + const basegfx::B2DHomMatrix& aFullTransform, + const BitmapEx& rBitmapEx, + double fAlpha) +{ + assert(!is_double_buffered_window()); + + bool bDone = false; + + // try to paint directly + const basegfx::B2DPoint aNull(aFullTransform * basegfx::B2DPoint(0.0, 0.0)); + const basegfx::B2DPoint aTopX(aFullTransform * basegfx::B2DPoint(1.0, 0.0)); + const basegfx::B2DPoint aTopY(aFullTransform * basegfx::B2DPoint(0.0, 1.0)); + SalBitmap* pSalSrcBmp = rBitmapEx.GetBitmap().ImplGetSalBitmap().get(); + AlphaMask aAlphaBitmap; + + if(rBitmapEx.IsAlpha()) + { + aAlphaBitmap = rBitmapEx.GetAlphaMask(); + } + else if (mpAlphaVDev) + { + aAlphaBitmap = AlphaMask(rBitmapEx.GetSizePixel()); + aAlphaBitmap.Erase(0); // opaque + } + + SalBitmap* pSalAlphaBmp = aAlphaBitmap.GetBitmap().ImplGetSalBitmap().get(); + + bDone = mpGraphics->DrawTransformedBitmap( + aNull, + aTopX, + aTopY, + *pSalSrcBmp, + pSalAlphaBmp, + fAlpha, + *this); + + if (mpAlphaVDev) + { + // Merge bitmap alpha to alpha device + AlphaMask aAlpha(rBitmapEx.GetSizePixel()); + aAlpha.Erase( ( 1 - fAlpha ) * 255 ); + mpAlphaVDev->DrawTransformBitmapExDirect(aFullTransform, BitmapEx(aAlpha.GetBitmap(), aAlphaBitmap)); + } + + return bDone; +}; + +bool OutputDevice::TransformAndReduceBitmapExToTargetRange( + const basegfx::B2DHomMatrix& aFullTransform, + basegfx::B2DRange &aVisibleRange, + double &fMaximumArea) +{ + // limit TargetRange to existing pixels (if pixel device) + // first get discrete range of object + basegfx::B2DRange aFullPixelRange(aVisibleRange); + + aFullPixelRange.transform(aFullTransform); + + if(basegfx::fTools::equalZero(aFullPixelRange.getWidth()) || basegfx::fTools::equalZero(aFullPixelRange.getHeight())) + { + // object is outside of visible area + return false; + } + + // now get discrete target pixels; start with OutDev pixel size and evtl. + // intersect with active clipping area + basegfx::B2DRange aOutPixel( + 0.0, + 0.0, + GetOutputSizePixel().Width(), + GetOutputSizePixel().Height()); + + if(IsClipRegion()) + { + tools::Rectangle aRegionRectangle(GetActiveClipRegion().GetBoundRect()); + + // caution! Range from rectangle, one too much (!) + aRegionRectangle.AdjustRight(-1); + aRegionRectangle.AdjustBottom(-1); + aOutPixel.intersect( vcl::unotools::b2DRectangleFromRectangle(aRegionRectangle) ); + } + + if(aOutPixel.isEmpty()) + { + // no active output area + return false; + } + + // if aFullPixelRange is not completely inside of aOutPixel, + // reduction of target pixels is possible + basegfx::B2DRange aVisiblePixelRange(aFullPixelRange); + + if(!aOutPixel.isInside(aFullPixelRange)) + { + aVisiblePixelRange.intersect(aOutPixel); + + if(aVisiblePixelRange.isEmpty()) + { + // nothing in visible part, reduces to nothing + return false; + } + + // aVisiblePixelRange contains the reduced output area in + // discrete coordinates. To make it useful everywhere, make it relative to + // the object range + basegfx::B2DHomMatrix aMakeVisibleRangeRelative; + + aVisibleRange = aVisiblePixelRange; + aMakeVisibleRangeRelative.translate( + -aFullPixelRange.getMinX(), + -aFullPixelRange.getMinY()); + aMakeVisibleRangeRelative.scale( + 1.0 / aFullPixelRange.getWidth(), + 1.0 / aFullPixelRange.getHeight()); + aVisibleRange.transform(aMakeVisibleRangeRelative); + } + + // for pixel devices, do *not* limit size, else OutputDevice::DrawDeviceAlphaBitmap + // will create another, badly scaled bitmap to do the job. Nonetheless, do a + // maximum clipping of something big (1600x1280x2). Add 1.0 to avoid rounding + // errors in rough estimations + const double fNewMaxArea(aVisiblePixelRange.getWidth() * aVisiblePixelRange.getHeight()); + + fMaximumArea = std::min(4096000.0, fNewMaxArea + 1.0); + + return true; +} + +// MM02 add some test class to get a simple timer-based output to be able +// to check if it gets faster - and how much. Uncomment next line or set +// DO_TIME_TEST for compile time if you want to use it +// #define DO_TIME_TEST +#ifdef DO_TIME_TEST +#include <tools/time.hxx> +struct LocalTimeTest +{ + const sal_uInt64 nStartTime; + LocalTimeTest() : nStartTime(tools::Time::GetSystemTicks()) {} + ~LocalTimeTest() + { + const sal_uInt64 nEndTime(tools::Time::GetSystemTicks()); + const sal_uInt64 nDiffTime(nEndTime - nStartTime); + + if(nDiffTime > 0) + { + OStringBuffer aOutput("Time: "); + OString aNumber(OString::number(nDiffTime)); + aOutput.append(aNumber); + OSL_FAIL(aOutput.getStr()); + } + } +}; +#endif + +void OutputDevice::DrawTransformedBitmapEx( + const basegfx::B2DHomMatrix& rTransformation, + const BitmapEx& rBitmapEx, + double fAlpha) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if(rBitmapEx.IsEmpty()) + return; + + if(rtl::math::approxEqual( fAlpha, 0.0 )) + return; + + // MM02 compared to other public methods of OutputDevice + // this test was missing and led to zero-ptr-accesses + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + const bool bMetafile(nullptr != mpMetaFile); + /* + tdf#135325 typically in these OutputDevice methods, for the in + record-to-metafile case the MetaFile is already written to before the + test against mbOutputClipped to determine that output to the current + device would result in no visual output. In this case the metafile is + written after the test, so we must continue past mbOutputClipped if + recording to a metafile. It's typical to record with a device of nominal + size and play back later against something of a totally different size. + */ + if (mbOutputClipped && !bMetafile) + return; + +#ifdef DO_TIME_TEST + // MM02 start time test when some data (not for trivial stuff). Will + // trigger and show data when leaving this method by destructing helper + static const char* pEnableBitmapDrawTimerTimer(getenv("SAL_ENABLE_TIMER_BITMAPDRAW")); + static bool bUseTimer(nullptr != pEnableBitmapDrawTimerTimer); + std::unique_ptr<LocalTimeTest> aTimeTest( + bUseTimer && rBitmapEx.GetSizeBytes() > 10000 + ? new LocalTimeTest() + : nullptr); +#endif + + BitmapEx bitmapEx = rBitmapEx; + + const bool bInvert(RasterOp::Invert == meRasterOp); + const bool bBitmapChangedColor(mnDrawMode & (DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | DrawModeFlags::GrayBitmap )); + const bool bTryDirectPaint(!bInvert && !bBitmapChangedColor && !bMetafile); + // tdf#130768 CAUTION(!) using GetViewTransformation() is *not* enough here, it may + // be that mnOutOffX/mnOutOffY is used - see AOO bug 75163, mentioned at + // ImplGetDeviceTransformation declaration + basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rTransformation); + + // First try to handle additional alpha blending, either directly, or modify the bitmap. + if(!rtl::math::approxEqual( fAlpha, 1.0 )) + { + if(bTryDirectPaint) + { + if(DrawTransformBitmapExDirect(aFullTransform, bitmapEx, fAlpha)) + { + // we are done + return; + } + } + // Apply the alpha manually. + sal_uInt8 nTransparency( static_cast<sal_uInt8>( ::basegfx::fround( 255.0*(1.0 - fAlpha) + .5) ) ); + AlphaMask aAlpha( bitmapEx.GetSizePixel(), &nTransparency ); + if( bitmapEx.IsAlpha()) + aAlpha.BlendWith( bitmapEx.GetAlphaMask()); + bitmapEx = BitmapEx( bitmapEx.GetBitmap(), aAlpha ); + } + + // If the backend's implementation is known to not need any optimizations here, pass to it directly. + // With most backends it's more performant to try to simplify to DrawBitmapEx() first. + if(bTryDirectPaint && mpGraphics->HasFastDrawTransformedBitmap() && DrawTransformBitmapExDirect(aFullTransform, bitmapEx)) + return; + + // decompose matrix to check rotation and shear + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rTransformation.decompose(aScale, aTranslate, fRotate, fShearX); + const bool bRotated(!basegfx::fTools::equalZero(fRotate)); + const bool bSheared(!basegfx::fTools::equalZero(fShearX)); + const bool bMirroredX(basegfx::fTools::less(aScale.getX(), 0.0)); + const bool bMirroredY(basegfx::fTools::less(aScale.getY(), 0.0)); + + if(!bRotated && !bSheared && !bMirroredX && !bMirroredY) + { + // with no rotation, shear or mirroring it can be mapped to DrawBitmapEx + // do *not* execute the mirroring here, it's done in the fallback + // #i124580# the correct DestSize needs to be calculated based on MaxXY values + Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY())); + const Size aDestSize( + basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(), + basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y()); + const Point aOrigin = GetMapMode().GetOrigin(); + if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel) + { + aDestPt.Move(aOrigin.getX(), aOrigin.getY()); + EnableMapMode(false); + } + + DrawBitmapEx(aDestPt, aDestSize, bitmapEx); + if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel) + { + EnableMapMode(); + aDestPt.Move(-aOrigin.getX(), -aOrigin.getY()); + } + return; + } + + // Try the backend's implementation before resorting to the slower fallback here. + if(bTryDirectPaint && DrawTransformBitmapExDirect(aFullTransform, bitmapEx)) + return; + + // take the fallback when no rotate and shear, but mirror (else we would have done this above) + if(!bRotated && !bSheared) + { + // with no rotation or shear it can be mapped to DrawBitmapEx + // do *not* execute the mirroring here, it's done in the fallback + // #i124580# the correct DestSize needs to be calculated based on MaxXY values + const Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY())); + const Size aDestSize( + basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(), + basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y()); + + DrawBitmapEx(aDestPt, aDestSize, bitmapEx); + return; + } + + // at this point we are either sheared or rotated or both + assert(bSheared || bRotated); + + // fallback; create transformed bitmap the hard way (back-transform + // the pixels) and paint + basegfx::B2DRange aVisibleRange(0.0, 0.0, 1.0, 1.0); + + // limit maximum area to something looking good for non-pixel-based targets (metafile, printer) + // by using a fixed minimum (allow at least, but no need to utilize) for good smoothing and an area + // dependent of original size for good quality when e.g. rotated/sheared. Still, limit to a maximum + // to avoid crashes/resource problems (ca. 1500x3000 here) + const Size& rOriginalSizePixel(bitmapEx.GetSizePixel()); + const double fOrigArea(rOriginalSizePixel.Width() * rOriginalSizePixel.Height() * 0.5); + const double fOrigAreaScaled(fOrigArea * 1.44); + double fMaximumArea(std::clamp(fOrigAreaScaled, 1000000.0, 4500000.0)); + + if(!bMetafile) + { + if ( !TransformAndReduceBitmapExToTargetRange( aFullTransform, aVisibleRange, fMaximumArea ) ) + return; + } + + if(aVisibleRange.isEmpty()) + return; + + BitmapEx aTransformed(bitmapEx); + + // #122923# when the result needs an alpha channel due to being rotated or sheared + // and thus uncovering areas, add these channels so that the own transformer (used + // in getTransformed) also creates a transformed alpha channel + if(!aTransformed.IsAlpha() && (bSheared || bRotated)) + { + // parts will be uncovered, extend aTransformed with a mask bitmap + const Bitmap aContent(aTransformed.GetBitmap()); + + AlphaMask aMaskBmp(aContent.GetSizePixel()); + aMaskBmp.Erase(0); + + aTransformed = BitmapEx(aContent, aMaskBmp); + } + + basegfx::B2DVector aFullScale, aFullTranslate; + double fFullRotate, fFullShearX; + aFullTransform.decompose(aFullScale, aFullTranslate, fFullRotate, fFullShearX); + + double fSourceRatio = 1.0; + if (rOriginalSizePixel.getHeight() != 0) + { + fSourceRatio = rOriginalSizePixel.getWidth() / rOriginalSizePixel.getHeight(); + } + double fTargetRatio = 1.0; + if (aFullScale.getY() != 0) + { + fTargetRatio = aFullScale.getX() / aFullScale.getY(); + } + bool bAspectRatioKept = rtl::math::approxEqual(fSourceRatio, fTargetRatio); + if (bSheared || !bAspectRatioKept) + { + // Not only rotation, or scaling does not keep aspect ratio. + aTransformed = aTransformed.getTransformed( + aFullTransform, + aVisibleRange, + fMaximumArea); + } + else + { + // Just rotation, can do that directly. + fFullRotate = fmod(fFullRotate * -1, 2 * M_PI); + if (fFullRotate < 0) + { + fFullRotate += 2 * M_PI; + } + Degree10 nAngle10(basegfx::fround(basegfx::rad2deg<10>(fFullRotate))); + aTransformed.Rotate(nAngle10, COL_TRANSPARENT); + } + basegfx::B2DRange aTargetRange(0.0, 0.0, 1.0, 1.0); + + // get logic object target range + aTargetRange.transform(rTransformation); + + // get from unified/relative VisibleRange to logoc one + aVisibleRange.transform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aTargetRange.getRange(), + aTargetRange.getMinimum())); + + // extract point and size; do not remove size, the bitmap may have been prepared reduced by purpose + // #i124580# the correct DestSize needs to be calculated based on MaxXY values + const Point aDestPt(basegfx::fround(aVisibleRange.getMinX()), basegfx::fround(aVisibleRange.getMinY())); + const Size aDestSize( + basegfx::fround(aVisibleRange.getMaxX()) - aDestPt.X(), + basegfx::fround(aVisibleRange.getMaxY()) - aDestPt.Y()); + + DrawBitmapEx(aDestPt, aDestSize, aTransformed); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/vcl/source/outdev/clipping.cxx b/vcl/source/outdev/clipping.cxx new file mode 100644 index 0000000000..6efc9df9d0 --- /dev/null +++ b/vcl/source/outdev/clipping.cxx @@ -0,0 +1,224 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <osl/diagnose.h> +#include <tools/debug.hxx> + +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> + +#include <salgdi.hxx> + +void OutputDevice::SaveBackground(VirtualDevice& rSaveDevice, + const Point& rPos, const Size& rSize, const Size& rBackgroundSize) const +{ + rSaveDevice.DrawOutDev(Point(), rBackgroundSize, rPos, rSize, *this); +} + +vcl::Region OutputDevice::GetClipRegion() const +{ + + return PixelToLogic( maRegion ); +} + +void OutputDevice::SetClipRegion() +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaClipRegionAction( vcl::Region(), false ) ); + + SetDeviceClipRegion( nullptr ); + + if( mpAlphaVDev ) + mpAlphaVDev->SetClipRegion(); +} + +void OutputDevice::SetClipRegion( const vcl::Region& rRegion ) +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaClipRegionAction( rRegion, true ) ); + + if ( rRegion.IsNull() ) + { + SetDeviceClipRegion( nullptr ); + } + else + { + vcl::Region aRegion = LogicToPixel( rRegion ); + SetDeviceClipRegion( &aRegion ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetClipRegion( rRegion ); +} + +bool OutputDevice::SelectClipRegion( const vcl::Region& rRegion, SalGraphics* pGraphics ) +{ + DBG_TESTSOLARMUTEX(); + + if( !pGraphics ) + { + if( !mpGraphics && !AcquireGraphics() ) + return false; + assert(mpGraphics); + pGraphics = mpGraphics; + } + + pGraphics->SetClipRegion( rRegion, *this ); + return true; +} + +void OutputDevice::MoveClipRegion( tools::Long nHorzMove, tools::Long nVertMove ) +{ + + if ( mbClipRegion ) + { + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaMoveClipRegionAction( nHorzMove, nVertMove ) ); + + maRegion.Move( ImplLogicWidthToDevicePixel( nHorzMove ), + ImplLogicHeightToDevicePixel( nVertMove ) ); + mbInitClipRegion = true; + } + + if( mpAlphaVDev ) + mpAlphaVDev->MoveClipRegion( nHorzMove, nVertMove ); +} + +void OutputDevice::IntersectClipRegion( const tools::Rectangle& rRect ) +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaISectRectClipRegionAction( rRect ) ); + + tools::Rectangle aRect = LogicToPixel( rRect ); + maRegion.Intersect( aRect ); + mbClipRegion = true; + mbInitClipRegion = true; + + if( mpAlphaVDev ) + mpAlphaVDev->IntersectClipRegion( rRect ); +} + +void OutputDevice::IntersectClipRegion( const vcl::Region& rRegion ) +{ + + if(!rRegion.IsNull()) + { + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaISectRegionClipRegionAction( rRegion ) ); + + vcl::Region aRegion = LogicToPixel( rRegion ); + maRegion.Intersect( aRegion ); + mbClipRegion = true; + mbInitClipRegion = true; + } + + if( mpAlphaVDev ) + mpAlphaVDev->IntersectClipRegion( rRegion ); +} + +void OutputDevice::InitClipRegion() +{ + DBG_TESTSOLARMUTEX(); + + if ( mbClipRegion ) + { + if ( maRegion.IsEmpty() ) + mbOutputClipped = true; + else + { + mbOutputClipped = false; + + // #102532# Respect output offset also for clip region + vcl::Region aRegion = ClipToDeviceBounds(ImplPixelToDevicePixel(maRegion)); + + if ( aRegion.IsEmpty() ) + { + mbOutputClipped = true; + } + else + { + mbOutputClipped = false; + SelectClipRegion( aRegion ); + } + } + + mbClipRegionSet = true; + } + else + { + if ( mbClipRegionSet ) + { + if (mpGraphics) + mpGraphics->ResetClipRegion(); + mbClipRegionSet = false; + } + + mbOutputClipped = false; + } + + mbInitClipRegion = false; +} + +vcl::Region OutputDevice::ClipToDeviceBounds(vcl::Region aRegion) const +{ + aRegion.Intersect(tools::Rectangle{mnOutOffX, + mnOutOffY, + mnOutOffX + GetOutputWidthPixel() - 1, + mnOutOffY + GetOutputHeightPixel() - 1 + }); + return aRegion; +} + +vcl::Region OutputDevice::GetActiveClipRegion() const +{ + return GetClipRegion(); +} + +void OutputDevice::ClipToPaintRegion(tools::Rectangle& /*rDstRect*/) +{ + // this is only used in Window, but we still need it as it's called + // on in other clipping functions +} + +void OutputDevice::SetDeviceClipRegion( const vcl::Region* pRegion ) +{ + DBG_TESTSOLARMUTEX(); + + if ( !pRegion ) + { + if ( mbClipRegion ) + { + maRegion = vcl::Region(true); + mbClipRegion = false; + mbInitClipRegion = true; + } + } + else + { + maRegion = *pRegion; + mbClipRegion = true; + mbInitClipRegion = true; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/curvedshapes.cxx b/vcl/source/outdev/curvedshapes.cxx new file mode 100644 index 0000000000..b5a13fb721 --- /dev/null +++ b/vcl/source/outdev/curvedshapes.cxx @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> + +#include <salgdi.hxx> + +#include <cassert> + +void OutputDevice::DrawEllipse( const tools::Rectangle& rRect ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaEllipseAction( rRect ) ); + + if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() ) + return; + + tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) ); + if ( aRect.IsEmpty() ) + return; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + tools::Polygon aRectPoly( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 ); + if ( aRectPoly.GetSize() >= 2 ) + { + Point* pPtAry = aRectPoly.GetPointAry(); + if ( !mbFillColor ) + mpGraphics->DrawPolyLine( aRectPoly.GetSize(), pPtAry, *this ); + else + { + if ( mbInitFillColor ) + InitFillColor(); + mpGraphics->DrawPolygon( aRectPoly.GetSize(), pPtAry, *this ); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawEllipse( rRect ); +} + +void OutputDevice::DrawArc( const tools::Rectangle& rRect, + const Point& rStartPt, const Point& rEndPt ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaArcAction( rRect, rStartPt, rEndPt ) ); + + if ( !IsDeviceOutputNecessary() || !mbLineColor || ImplIsRecordLayout() ) + return; + + tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) ); + if ( aRect.IsEmpty() ) + return; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + const Point aStart( ImplLogicToDevicePixel( rStartPt ) ); + const Point aEnd( ImplLogicToDevicePixel( rEndPt ) ); + tools::Polygon aArcPoly( aRect, aStart, aEnd, PolyStyle::Arc ); + + if ( aArcPoly.GetSize() >= 2 ) + { + Point* pPtAry = aArcPoly.GetPointAry(); + mpGraphics->DrawPolyLine( aArcPoly.GetSize(), pPtAry, *this ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawArc( rRect, rStartPt, rEndPt ); +} + +void OutputDevice::DrawPie( const tools::Rectangle& rRect, + const Point& rStartPt, const Point& rEndPt ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaPieAction( rRect, rStartPt, rEndPt ) ); + + if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() ) + return; + + tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) ); + if ( aRect.IsEmpty() ) + return; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + const Point aStart( ImplLogicToDevicePixel( rStartPt ) ); + const Point aEnd( ImplLogicToDevicePixel( rEndPt ) ); + tools::Polygon aPiePoly( aRect, aStart, aEnd, PolyStyle::Pie ); + + if ( aPiePoly.GetSize() >= 2 ) + { + Point* pPtAry = aPiePoly.GetPointAry(); + if ( !mbFillColor ) + mpGraphics->DrawPolyLine( aPiePoly.GetSize(), pPtAry, *this ); + else + { + if ( mbInitFillColor ) + InitFillColor(); + mpGraphics->DrawPolygon( aPiePoly.GetSize(), pPtAry, *this ); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawPie( rRect, rStartPt, rEndPt ); +} + +void OutputDevice::DrawChord( const tools::Rectangle& rRect, + const Point& rStartPt, const Point& rEndPt ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaChordAction( rRect, rStartPt, rEndPt ) ); + + if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() ) + return; + + tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) ); + if ( aRect.IsEmpty() ) + return; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + const Point aStart( ImplLogicToDevicePixel( rStartPt ) ); + const Point aEnd( ImplLogicToDevicePixel( rEndPt ) ); + tools::Polygon aChordPoly( aRect, aStart, aEnd, PolyStyle::Chord ); + + if ( aChordPoly.GetSize() >= 2 ) + { + Point* pPtAry = aChordPoly.GetPointAry(); + if ( !mbFillColor ) + mpGraphics->DrawPolyLine( aChordPoly.GetSize(), pPtAry, *this ); + else + { + if ( mbInitFillColor ) + InitFillColor(); + mpGraphics->DrawPolygon( aChordPoly.GetSize(), pPtAry, *this ); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawChord( rRect, rStartPt, rEndPt ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/eps.cxx b/vcl/source/outdev/eps.cxx new file mode 100644 index 0000000000..3f436ee327 --- /dev/null +++ b/vcl/source/outdev/eps.cxx @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/gfxlink.hxx> +#include <vcl/graph.hxx> +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> + +#include <salgdi.hxx> + +bool OutputDevice::DrawEPS( const Point& rPoint, const Size& rSize, + const GfxLink& rGfxLink, GDIMetaFile* pSubst ) +{ + if ( mpMetaFile ) + { + GDIMetaFile aSubst; + + if( pSubst ) + aSubst = *pSubst; + + mpMetaFile->AddAction( new MetaEPSAction( rPoint, rSize, rGfxLink, aSubst ) ); + } + + if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return true; + + if( mbOutputClipped ) + return true; + + tools::Rectangle aRect( ImplLogicToDevicePixel( tools::Rectangle( rPoint, rSize ) ) ); + + bool bDrawn = true; + + if( !aRect.IsEmpty() ) + { + // draw the real EPS graphics + if( rGfxLink.GetData() && rGfxLink.GetDataSize() ) + { + if( !mpGraphics && !AcquireGraphics() ) + return bDrawn; + assert(mpGraphics); + + if( mbInitClipRegion ) + InitClipRegion(); + + aRect.Normalize(); + bDrawn = mpGraphics->DrawEPS( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), + const_cast<sal_uInt8*>(rGfxLink.GetData()), rGfxLink.GetDataSize(), *this ); + } + + // else draw the substitution graphics + if( !bDrawn && pSubst ) + { + GDIMetaFile* pOldMetaFile = mpMetaFile; + + mpMetaFile = nullptr; + Graphic(*pSubst).Draw(*this, rPoint, rSize); + mpMetaFile = pOldMetaFile; + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawEPS( rPoint, rSize, rGfxLink, pSubst ); + + return bDrawn; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/vcl/source/outdev/fill.cxx b/vcl/source/outdev/fill.cxx new file mode 100644 index 0000000000..c684595fb2 --- /dev/null +++ b/vcl/source/outdev/fill.cxx @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <tools/debug.hxx> + +#include <vcl/metaact.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> + +#include <drawmode.hxx> +#include <salgdi.hxx> + +void OutputDevice::SetFillColor() +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaFillColorAction( Color(), false ) ); + + if ( mbFillColor ) + { + mbInitFillColor = true; + mbFillColor = false; + maFillColor = COL_TRANSPARENT; + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetFillColor(); +} + +void OutputDevice::SetFillColor( const Color& rColor ) +{ + Color aColor(vcl::drawmode::GetFillColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings())); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaFillColorAction( aColor, true ) ); + + if ( aColor.IsTransparent() ) + { + if ( mbFillColor ) + { + mbInitFillColor = true; + mbFillColor = false; + maFillColor = COL_TRANSPARENT; + } + } + else + { + if ( maFillColor != aColor ) + { + mbInitFillColor = true; + mbFillColor = true; + maFillColor = aColor; + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetFillColor( COL_ALPHA_OPAQUE ); +} + +void OutputDevice::InitFillColor() +{ + DBG_TESTSOLARMUTEX(); + + if( mbFillColor ) + { + if( RasterOp::N0 == meRasterOp ) + mpGraphics->SetROPFillColor( SalROPColor::N0 ); + else if( RasterOp::N1 == meRasterOp ) + mpGraphics->SetROPFillColor( SalROPColor::N1 ); + else if( RasterOp::Invert == meRasterOp ) + mpGraphics->SetROPFillColor( SalROPColor::Invert ); + else + mpGraphics->SetFillColor( maFillColor ); + } + else + { + mpGraphics->SetFillColor(); + } + + mbInitFillColor = false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/vcl/source/outdev/font.cxx b/vcl/source/outdev/font.cxx new file mode 100644 index 0000000000..2086db7f63 --- /dev/null +++ b/vcl/source/outdev/font.cxx @@ -0,0 +1,1293 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <i18nlangtag/lang.h> +#include <unotools/configmgr.hxx> + +#include <vcl/event.hxx> +#include <vcl/fontcharmap.hxx> +#include <vcl/metaact.hxx> +#include <vcl/metric.hxx> +#include <vcl/print.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/virdev.hxx> + +#include <window.h> +#include <font/EmphasisMark.hxx> + +#include <ImplLayoutArgs.hxx> +#include <drawmode.hxx> +#include <impfontcache.hxx> +#include <font/DirectFontSubstitution.hxx> +#include <font/PhysicalFontFaceCollection.hxx> +#include <font/PhysicalFontCollection.hxx> +#include <font/FeatureCollector.hxx> +#include <impglyphitem.hxx> +#include <sallayout.hxx> +#include <salgdi.hxx> +#include <svdata.hxx> + +#include <unicode/uchar.h> + +#include <strings.hrc> + +void OutputDevice::SetFont( const vcl::Font& rNewFont ) +{ + vcl::Font aFont = vcl::drawmode::GetFont(rNewFont, GetDrawMode(), GetSettings().GetStyleSettings()); + + if ( mpMetaFile ) + { + mpMetaFile->AddAction( new MetaFontAction( aFont ) ); + // the color and alignment actions don't belong here + // TODO: get rid of them without breaking anything... + mpMetaFile->AddAction( new MetaTextAlignAction( aFont.GetAlignment() ) ); + mpMetaFile->AddAction( new MetaTextFillColorAction( aFont.GetFillColor(), !aFont.IsTransparent() ) ); + } + + if ( maFont.IsSameInstance( aFont ) ) + return; + + // Optimization MT/HDU: COL_TRANSPARENT means SetFont should ignore the font color, + // because SetTextColor() is used for this. + // #i28759# maTextColor might have been changed behind our back, commit then, too. + if( aFont.GetColor() != COL_TRANSPARENT + && (aFont.GetColor() != maFont.GetColor() || aFont.GetColor() != maTextColor ) ) + { + maTextColor = aFont.GetColor(); + mbInitTextColor = true; + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextColorAction( aFont.GetColor() ) ); + } + maFont = aFont; + mbNewFont = true; + + if( !mpAlphaVDev ) + return; + + // #i30463# + // Since SetFont might change the text color, apply that only + // selectively to alpha vdev (which normally paints opaque text + // with COL_BLACK) + if( aFont.GetColor() != COL_TRANSPARENT ) + { + mpAlphaVDev->SetTextColor( COL_ALPHA_OPAQUE ); + aFont.SetColor( COL_TRANSPARENT ); + } + + mpAlphaVDev->SetFont( aFont ); +} + +FontMetric OutputDevice::GetFontMetricFromCollection(int nDevFontIndex) const +{ + ImplInitFontList(); + + if (nDevFontIndex < GetFontFaceCollectionCount()) + return FontMetric(*mpFontFaceCollection->Get(nDevFontIndex)); + + return FontMetric(); +} + +int OutputDevice::GetFontFaceCollectionCount() const +{ + if( !mpFontFaceCollection ) + { + if (!mxFontCollection) + { + return 0; + } + + mpFontFaceCollection = mxFontCollection->GetFontFaceCollection(); + + if (!mpFontFaceCollection->Count()) + { + mpFontFaceCollection.reset(); + return 0; + } + } + return mpFontFaceCollection->Count(); +} + +bool OutputDevice::IsFontAvailable( std::u16string_view rFontName ) const +{ + ImplInitFontList(); + vcl::font::PhysicalFontFamily* pFound = mxFontCollection->FindFontFamily( rFontName ); + return (pFound != nullptr); +} + +bool OutputDevice::AddTempDevFont( const OUString& rFileURL, const OUString& rFontName ) +{ + ImplInitFontList(); + + if( !mpGraphics && !AcquireGraphics() ) + return false; + assert(mpGraphics); + + bool bRC = mpGraphics->AddTempDevFont( mxFontCollection.get(), rFileURL, rFontName ); + if( !bRC ) + return false; + + if( mpAlphaVDev ) + mpAlphaVDev->AddTempDevFont( rFileURL, rFontName ); + + return true; +} + +bool OutputDevice::GetFontFeatures(std::vector<vcl::font::Feature>& rFontFeatures) const +{ + if (!ImplNewFont()) + return false; + + LogicalFontInstance* pFontInstance = mpFontInstance.get(); + if (!pFontInstance) + return false; + + const LanguageTag& rOfficeLanguage = Application::GetSettings().GetUILanguageTag(); + + vcl::font::FeatureCollector aFeatureCollector(pFontInstance->GetFontFace(), rFontFeatures, rOfficeLanguage); + aFeatureCollector.collect(); + + return true; +} + +FontMetric OutputDevice::GetFontMetric() const +{ + FontMetric aMetric; + if (!ImplNewFont()) + return aMetric; + + LogicalFontInstance* pFontInstance = mpFontInstance.get(); + FontMetricDataRef xFontMetric = pFontInstance->mxFontMetric; + + // prepare metric + aMetric = maFont; + + // set aMetric with info from font + aMetric.SetFamilyName( maFont.GetFamilyName() ); + aMetric.SetStyleName( xFontMetric->GetStyleName() ); + aMetric.SetFontSize( PixelToLogic( Size( xFontMetric->GetWidth(), xFontMetric->GetAscent() + xFontMetric->GetDescent() - xFontMetric->GetInternalLeading() ) ) ); + aMetric.SetCharSet( xFontMetric->IsMicrosoftSymbolEncoded() ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); + aMetric.SetFamily( xFontMetric->GetFamilyType() ); + aMetric.SetPitch( xFontMetric->GetPitch() ); + aMetric.SetWeight( xFontMetric->GetWeight() ); + aMetric.SetItalic( xFontMetric->GetItalic() ); + aMetric.SetAlignment( TextAlign::ALIGN_TOP ); + aMetric.SetWidthType( xFontMetric->GetWidthType() ); + if ( pFontInstance->mnOwnOrientation ) + aMetric.SetOrientation( pFontInstance->mnOwnOrientation ); + else + aMetric.SetOrientation( xFontMetric->GetOrientation() ); + + // set remaining metric fields + aMetric.SetFullstopCenteredFlag( xFontMetric->IsFullstopCentered() ); + aMetric.SetBulletOffset( xFontMetric->GetBulletOffset() ); + aMetric.SetAscent( ImplDevicePixelToLogicHeight( xFontMetric->GetAscent() + mnEmphasisAscent ) ); + aMetric.SetDescent( ImplDevicePixelToLogicHeight( xFontMetric->GetDescent() + mnEmphasisDescent ) ); + aMetric.SetInternalLeading( ImplDevicePixelToLogicHeight( xFontMetric->GetInternalLeading() + mnEmphasisAscent ) ); + // OutputDevice has its own external leading function due to #i60945# + aMetric.SetExternalLeading( ImplDevicePixelToLogicHeight( GetFontExtLeading() ) ); + aMetric.SetLineHeight( ImplDevicePixelToLogicHeight( xFontMetric->GetAscent() + xFontMetric->GetDescent() + mnEmphasisAscent + mnEmphasisDescent ) ); + aMetric.SetSlant( ImplDevicePixelToLogicHeight( xFontMetric->GetSlant() ) ); + aMetric.SetHangingBaseline( ImplDevicePixelToLogicHeight( xFontMetric->GetHangingBaseline() ) ); + + // get miscellaneous data + aMetric.SetQuality( xFontMetric->GetQuality() ); + + SAL_INFO("vcl.gdi.fontmetric", "OutputDevice::GetFontMetric:" << aMetric); + + xFontMetric = nullptr; + + return aMetric; +} + +FontMetric OutputDevice::GetFontMetric( const vcl::Font& rFont ) const +{ + // select font, query metrics, select original font again + vcl::Font aOldFont = GetFont(); + const_cast<OutputDevice*>(this)->SetFont( rFont ); + FontMetric aMetric( GetFontMetric() ); + const_cast<OutputDevice*>(this)->SetFont( aOldFont ); + return aMetric; +} + +bool OutputDevice::GetFontCharMap( FontCharMapRef& rxFontCharMap ) const +{ + if (!InitFont()) + return false; + + FontCharMapRef xFontCharMap ( mpGraphics->GetFontCharMap() ); + if (!xFontCharMap.is()) + { + FontCharMapRef xDefaultMap( new FontCharMap() ); + rxFontCharMap = xDefaultMap; + } + else + rxFontCharMap = xFontCharMap; + + return !rxFontCharMap->IsDefaultMap(); +} + +bool OutputDevice::GetFontCapabilities( vcl::FontCapabilities& rFontCapabilities ) const +{ + if (!InitFont()) + return false; + return mpGraphics->GetFontCapabilities(rFontCapabilities); +} + +tools::Long OutputDevice::GetFontExtLeading() const +{ + return mpFontInstance->mxFontMetric->GetExternalLeading(); +} + +void OutputDevice::ImplClearFontData( const bool bNewFontLists ) +{ + // the currently selected logical font is no longer needed + mpFontInstance.clear(); + + mbInitFont = true; + mbNewFont = true; + + if ( bNewFontLists ) + { + mpFontFaceCollection.reset(); + + // release all physically selected fonts on this device + if( AcquireGraphics() ) + mpGraphics->ReleaseFonts(); + } + + ImplSVData* pSVData = ImplGetSVData(); + + if (mxFontCache && mxFontCache != pSVData->maGDIData.mxScreenFontCache) + mxFontCache->Invalidate(); + + if (bNewFontLists && AcquireGraphics()) + { + if (mxFontCollection && mxFontCollection != pSVData->maGDIData.mxScreenFontList) + mxFontCollection->Clear(); + } +} + +void OutputDevice::RefreshFontData( const bool bNewFontLists ) +{ + ImplRefreshFontData( bNewFontLists ); +} + +void OutputDevice::ImplRefreshFontData( const bool bNewFontLists ) +{ + if (bNewFontLists && AcquireGraphics()) + mpGraphics->GetDevFontList( mxFontCollection.get() ); +} + +void OutputDevice::ImplUpdateFontData() +{ + ImplClearFontData( true/*bNewFontLists*/ ); + ImplRefreshFontData( true/*bNewFontLists*/ ); +} + +void OutputDevice::ImplClearAllFontData(bool bNewFontLists) +{ + ImplSVData* pSVData = ImplGetSVData(); + + ImplUpdateFontDataForAllFrames( &OutputDevice::ImplClearFontData, bNewFontLists ); + + // clear global font lists to have them updated + pSVData->maGDIData.mxScreenFontCache->Invalidate(); + if ( !bNewFontLists ) + return; + + pSVData->maGDIData.mxScreenFontList->Clear(); + vcl::Window * pFrame = pSVData->maFrameData.mpFirstFrame; + if ( pFrame ) + { + if ( pFrame->GetOutDev()->AcquireGraphics() ) + { + OutputDevice *pDevice = pFrame->GetOutDev(); + pDevice->mpGraphics->ClearDevFontCache(); + pDevice->mpGraphics->GetDevFontList(pFrame->mpWindowImpl->mpFrameData->mxFontCollection.get()); + } + } +} + +void OutputDevice::ImplRefreshAllFontData(bool bNewFontLists) +{ + ImplUpdateFontDataForAllFrames( &OutputDevice::ImplRefreshFontData, bNewFontLists ); +} + +void OutputDevice::ImplUpdateAllFontData(bool bNewFontLists) +{ + OutputDevice::ImplClearAllFontData(bNewFontLists); + OutputDevice::ImplRefreshAllFontData(bNewFontLists); +} + +void OutputDevice::ImplUpdateFontDataForAllFrames( const FontUpdateHandler_t pHdl, const bool bNewFontLists ) +{ + ImplSVData* const pSVData = ImplGetSVData(); + + // update all windows + vcl::Window* pFrame = pSVData->maFrameData.mpFirstFrame; + while ( pFrame ) + { + ( pFrame->GetOutDev()->*pHdl )( bNewFontLists ); + + vcl::Window* pSysWin = pFrame->mpWindowImpl->mpFrameData->mpFirstOverlap; + while ( pSysWin ) + { + ( pSysWin->GetOutDev()->*pHdl )( bNewFontLists ); + pSysWin = pSysWin->mpWindowImpl->mpNextOverlap; + } + + pFrame = pFrame->mpWindowImpl->mpFrameData->mpNextFrame; + } + + // update all virtual devices + VirtualDevice* pVirDev = pSVData->maGDIData.mpFirstVirDev; + while ( pVirDev ) + { + ( pVirDev->*pHdl )( bNewFontLists ); + pVirDev = pVirDev->mpNext; + } + + // update all printers + Printer* pPrinter = pSVData->maGDIData.mpFirstPrinter; + while ( pPrinter ) + { + ( pPrinter->*pHdl )( bNewFontLists ); + pPrinter = pPrinter->mpNext; + } +} + +void OutputDevice::BeginFontSubstitution() +{ + ImplSVData* pSVData = ImplGetSVData(); + pSVData->maGDIData.mbFontSubChanged = false; +} + +void OutputDevice::EndFontSubstitution() +{ + ImplSVData* pSVData = ImplGetSVData(); + if ( pSVData->maGDIData.mbFontSubChanged ) + { + ImplUpdateAllFontData( false ); + + DataChangedEvent aDCEvt( DataChangedEventType::FONTSUBSTITUTION ); + Application::ImplCallEventListenersApplicationDataChanged(&aDCEvt); + Application::NotifyAllWindows( aDCEvt ); + pSVData->maGDIData.mbFontSubChanged = false; + } +} + +void OutputDevice::AddFontSubstitute( const OUString& rFontName, + const OUString& rReplaceFontName, + AddFontSubstituteFlags nFlags ) +{ + vcl::font::DirectFontSubstitution*& rpSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst; + if( !rpSubst ) + rpSubst = new vcl::font::DirectFontSubstitution; + rpSubst->AddFontSubstitute( rFontName, rReplaceFontName, nFlags ); + ImplGetSVData()->maGDIData.mbFontSubChanged = true; +} + +void OutputDevice::RemoveFontsSubstitute() +{ + vcl::font::DirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst; + if( pSubst ) + pSubst->RemoveFontsSubstitute(); +} + +//hidpi TODO: This routine has hard-coded font-sizes that break places such as DialControl +vcl::Font OutputDevice::GetDefaultFont( DefaultFontType nType, LanguageType eLang, + GetDefaultFontFlags nFlags, const OutputDevice* pOutDev ) +{ + static bool bFuzzing = utl::ConfigManager::IsFuzzing(); + static bool bAbortOnFontSubstitute = [] { + const char* pEnv = getenv("SAL_NON_APPLICATION_FONT_USE"); + return pEnv && strcmp(pEnv, "abort") == 0; + }(); + + if (!pOutDev && !bFuzzing) // default is NULL + pOutDev = Application::GetDefaultDevice(); + + OUString aSearch; + if (!bFuzzing) + { + LanguageTag aLanguageTag( + ( eLang == LANGUAGE_NONE || eLang == LANGUAGE_SYSTEM || eLang == LANGUAGE_DONTKNOW ) ? + Application::GetSettings().GetUILanguageTag() : + LanguageTag( eLang )); + + utl::DefaultFontConfiguration& rDefaults = utl::DefaultFontConfiguration::get(); + OUString aDefault = rDefaults.getDefaultFont( aLanguageTag, nType ); + + if( !aDefault.isEmpty() ) + aSearch = aDefault; + else + aSearch = rDefaults.getUserInterfaceFont( aLanguageTag ); // use the UI font as a fallback + + // during cppunit tests with SAL_NON_APPLICATION_FONT_USE set we don't have any bundled fonts + // that support the default CTL and CJK languages of Hindi and Chinese, so just pick something + // (unsuitable) that does exist, if they get used with SAL_NON_APPLICATION_FONT_USE=abort then + // glyph fallback will trigger std::abort + if (bAbortOnFontSubstitute) + { + if (eLang == LANGUAGE_HINDI || eLang == LANGUAGE_CHINESE_SIMPLIFIED) + aSearch = "DejaVu Sans"; + } + } + else + aSearch = "Liberation Serif"; + + vcl::Font aFont; + aFont.SetPitch( PITCH_VARIABLE ); + + switch ( nType ) + { + case DefaultFontType::SANS_UNICODE: + case DefaultFontType::UI_SANS: + aFont.SetFamily( FAMILY_SWISS ); + break; + + case DefaultFontType::SANS: + case DefaultFontType::LATIN_HEADING: + case DefaultFontType::LATIN_SPREADSHEET: + case DefaultFontType::LATIN_DISPLAY: + aFont.SetFamily( FAMILY_SWISS ); + break; + + case DefaultFontType::SERIF: + case DefaultFontType::LATIN_TEXT: + case DefaultFontType::LATIN_PRESENTATION: + aFont.SetFamily( FAMILY_ROMAN ); + break; + + case DefaultFontType::FIXED: + case DefaultFontType::LATIN_FIXED: + case DefaultFontType::UI_FIXED: + aFont.SetPitch( PITCH_FIXED ); + aFont.SetFamily( FAMILY_MODERN ); + break; + + case DefaultFontType::SYMBOL: + aFont.SetCharSet( RTL_TEXTENCODING_SYMBOL ); + break; + + case DefaultFontType::CJK_TEXT: + case DefaultFontType::CJK_PRESENTATION: + case DefaultFontType::CJK_SPREADSHEET: + case DefaultFontType::CJK_HEADING: + case DefaultFontType::CJK_DISPLAY: + aFont.SetFamily( FAMILY_SYSTEM ); // don't care, but don't use font subst config later... + break; + + case DefaultFontType::CTL_TEXT: + case DefaultFontType::CTL_PRESENTATION: + case DefaultFontType::CTL_SPREADSHEET: + case DefaultFontType::CTL_HEADING: + case DefaultFontType::CTL_DISPLAY: + aFont.SetFamily( FAMILY_SYSTEM ); // don't care, but don't use font subst config later... + break; + } + + if ( !aSearch.isEmpty() ) + { + aFont.SetFontHeight( 12 ); // corresponds to nDefaultHeight + aFont.SetWeight( WEIGHT_NORMAL ); + aFont.SetLanguage( eLang ); + + if ( aFont.GetCharSet() == RTL_TEXTENCODING_DONTKNOW ) + aFont.SetCharSet( osl_getThreadTextEncoding() ); + + // Should we only return available fonts on the given device + if ( pOutDev ) + { + pOutDev->ImplInitFontList(); + + // Search Font in the FontList + OUString aName; + sal_Int32 nIndex = 0; + do + { + vcl::font::PhysicalFontFamily* pFontFamily = pOutDev->mxFontCollection->FindFontFamily( GetNextFontToken( aSearch, nIndex ) ); + if( pFontFamily ) + { + AddTokenFontName( aName, pFontFamily->GetFamilyName() ); + if( nFlags & GetDefaultFontFlags::OnlyOne ) + break; + } + } + while ( nIndex != -1 ); + aFont.SetFamilyName( aName ); + } + + // No Name, then set all names + if ( aFont.GetFamilyName().isEmpty() ) + { + if ( nFlags & GetDefaultFontFlags::OnlyOne ) + { + if( !pOutDev ) + { + SAL_WARN_IF(!utl::ConfigManager::IsFuzzing(), "vcl.gdi", "No default window has been set for the application - we really shouldn't be able to get here"); + aFont.SetFamilyName( aSearch.getToken( 0, ';' ) ); + } + else + { + pOutDev->ImplInitFontList(); + + aFont.SetFamilyName( aSearch ); + + // convert to pixel height + Size aSize = pOutDev->ImplLogicToDevicePixel( aFont.GetFontSize() ); + if ( !aSize.Height() ) + { + // use default pixel height only when logical height is zero + if ( aFont.GetFontHeight() ) + aSize.setHeight( 1 ); + else + aSize.setHeight( (12*pOutDev->mnDPIY)/72 ); + } + + // use default width only when logical width is zero + if( (0 == aSize.Width()) && (0 != aFont.GetFontSize().Width()) ) + aSize.setWidth( 1 ); + + // get the name of the first available font + float fExactHeight = static_cast<float>(aSize.Height()); + rtl::Reference<LogicalFontInstance> pFontInstance = pOutDev->mxFontCache->GetFontInstance( pOutDev->mxFontCollection.get(), aFont, aSize, fExactHeight ); + if (pFontInstance) + { + assert(pFontInstance->GetFontFace()); + aFont.SetFamilyName(pFontInstance->GetFontFace()->GetFamilyName()); + } + } + } + else + aFont.SetFamilyName( aSearch ); + } + } + +#if OSL_DEBUG_LEVEL > 2 + const char* s = "SANS_UNKNOWN"; + switch ( nType ) + { + case DefaultFontType::SANS_UNICODE: s = "SANS_UNICODE"; break; + case DefaultFontType::UI_SANS: s = "UI_SANS"; break; + + case DefaultFontType::SANS: s = "SANS"; break; + case DefaultFontType::LATIN_HEADING: s = "LATIN_HEADING"; break; + case DefaultFontType::LATIN_SPREADSHEET: s = "LATIN_SPREADSHEET"; break; + case DefaultFontType::LATIN_DISPLAY: s = "LATIN_DISPLAY"; break; + + case DefaultFontType::SERIF: s = "SERIF"; break; + case DefaultFontType::LATIN_TEXT: s = "LATIN_TEXT"; break; + case DefaultFontType::LATIN_PRESENTATION: s = "LATIN_PRESENTATION"; break; + + case DefaultFontType::FIXED: s = "FIXED"; break; + case DefaultFontType::LATIN_FIXED: s = "LATIN_FIXED"; break; + case DefaultFontType::UI_FIXED: s = "UI_FIXED"; break; + + case DefaultFontType::SYMBOL: s = "SYMBOL"; break; + + case DefaultFontType::CJK_TEXT: s = "CJK_TEXT"; break; + case DefaultFontType::CJK_PRESENTATION: s = "CJK_PRESENTATION"; break; + case DefaultFontType::CJK_SPREADSHEET: s = "CJK_SPREADSHEET"; break; + case DefaultFontType::CJK_HEADING: s = "CJK_HEADING"; break; + case DefaultFontType::CJK_DISPLAY: s = "CJK_DISPLAY"; break; + + case DefaultFontType::CTL_TEXT: s = "CTL_TEXT"; break; + case DefaultFontType::CTL_PRESENTATION: s = "CTL_PRESENTATION"; break; + case DefaultFontType::CTL_SPREADSHEET: s = "CTL_SPREADSHEET"; break; + case DefaultFontType::CTL_HEADING: s = "CTL_HEADING"; break; + case DefaultFontType::CTL_DISPLAY: s = "CTL_DISPLAY"; break; + } + SAL_INFO("vcl.gdi", + "OutputDevice::GetDefaultFont() Type=" << s + << " lang=" << eLang + << " flags=" << static_cast<int>(nFlags) + << " family=\"" << aFont.GetFamilyName() << "\""); +#endif + + return aFont; +} + +void OutputDevice::ImplInitFontList() const +{ + if( mxFontCollection->Count() ) + return; + + if( !(mpGraphics || AcquireGraphics()) ) + return; + assert(mpGraphics); + + SAL_INFO( "vcl.gdi", "OutputDevice::ImplInitFontList()" ); + mpGraphics->GetDevFontList(mxFontCollection.get()); + + // There is absolutely no way there should be no fonts available on the device + if( !mxFontCollection->Count() ) + { + OUString aError( "Application error: no fonts and no vcl resource found on your system" ); + OUString aResStr(VclResId(SV_ACCESSERROR_NO_FONTS)); + if (!aResStr.isEmpty()) + aError = aResStr; + Application::Abort(aError); + } +} + +bool OutputDevice::InitFont() const +{ + DBG_TESTSOLARMUTEX(); + + if (!ImplNewFont()) + return false; + if (!mpFontInstance) + return false; + if (!mpGraphics) + { + if (!AcquireGraphics()) + return false; + } + else if (!mbInitFont) + return true; + + assert(mpGraphics); + mpGraphics->SetFont(mpFontInstance.get(), 0); + mbInitFont = false; + return true; +} + +const LogicalFontInstance* OutputDevice::GetFontInstance() const +{ + if (!InitFont()) + return nullptr; + return mpFontInstance.get(); +} + +bool OutputDevice::ImplNewFont() const +{ + DBG_TESTSOLARMUTEX(); + + if ( !mbNewFont ) + return true; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + { + SAL_WARN("vcl.gdi", "OutputDevice::ImplNewFont(): no Graphics, no Font"); + return false; + } + assert(mpGraphics); + + ImplInitFontList(); + + // convert to pixel height + // TODO: replace integer based aSize completely with subpixel accurate type + float fExactHeight = ImplLogicHeightToDeviceSubPixel(maFont.GetFontHeight()); + Size aSize = ImplLogicToDevicePixel( maFont.GetFontSize() ); + if ( !aSize.Height() ) + { + // use default pixel height only when logical height is zero + if ( maFont.GetFontSize().Height() ) + aSize.setHeight( 1 ); + else + aSize.setHeight( (12*mnDPIY)/72 ); + fExactHeight = static_cast<float>(aSize.Height()); + } + + // select the default width only when logical width is zero + if( (0 == aSize.Width()) && (0 != maFont.GetFontSize().Width()) ) + aSize.setWidth( 1 ); + + // decide if antialiasing is appropriate + bool bNonAntialiased(GetAntialiasing() & AntialiasingFlags::DisableText); + if (!utl::ConfigManager::IsFuzzing()) + { + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + bNonAntialiased |= bool(rStyleSettings.GetDisplayOptions() & DisplayOptions::AADisable); + bNonAntialiased |= (int(rStyleSettings.GetAntialiasingMinPixelHeight()) > maFont.GetFontSize().Height()); + } + + // get font entry + rtl::Reference<LogicalFontInstance> pOldFontInstance = mpFontInstance; + mpFontInstance = mxFontCache->GetFontInstance(mxFontCollection.get(), maFont, aSize, fExactHeight, bNonAntialiased); + const bool bNewFontInstance = pOldFontInstance.get() != mpFontInstance.get(); + pOldFontInstance.clear(); + + LogicalFontInstance* pFontInstance = mpFontInstance.get(); + + if (!pFontInstance) + { + SAL_WARN("vcl.gdi", "OutputDevice::ImplNewFont(): no LogicalFontInstance, no Font"); + return false; + } + + // mark when lower layers need to get involved + mbNewFont = false; + if( bNewFontInstance ) + mbInitFont = true; + + // select font when it has not been initialized yet + if (!pFontInstance->mbInit && InitFont()) + { + // get metric data from device layers + pFontInstance->mbInit = true; + + pFontInstance->mxFontMetric->SetOrientation( mpFontInstance->GetFontSelectPattern().mnOrientation ); + mpGraphics->GetFontMetric( pFontInstance->mxFontMetric, 0 ); + + pFontInstance->mxFontMetric->ImplInitTextLineSize( this ); + pFontInstance->mxFontMetric->ImplInitAboveTextLineSize( this ); + pFontInstance->mxFontMetric->ImplInitFlags( this ); + + pFontInstance->mnLineHeight = pFontInstance->mxFontMetric->GetAscent() + pFontInstance->mxFontMetric->GetDescent(); + + SetFontOrientation( pFontInstance ); + } + + // calculate EmphasisArea + mnEmphasisAscent = 0; + mnEmphasisDescent = 0; + if ( maFont.GetEmphasisMark() & FontEmphasisMark::Style ) + { + FontEmphasisMark nEmphasisMark = maFont.GetEmphasisMarkStyle(); + tools::Long nEmphasisHeight = (pFontInstance->mnLineHeight*250)/1000; + if ( nEmphasisHeight < 1 ) + nEmphasisHeight = 1; + if ( nEmphasisMark & FontEmphasisMark::PosBelow ) + mnEmphasisDescent = nEmphasisHeight; + else + mnEmphasisAscent = nEmphasisHeight; + } + + // calculate text offset depending on TextAlignment + TextAlign eAlign = maFont.GetAlignment(); + if ( eAlign == ALIGN_BASELINE ) + { + mnTextOffX = 0; + mnTextOffY = 0; + } + else if ( eAlign == ALIGN_TOP ) + { + mnTextOffX = 0; + mnTextOffY = +pFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent; + if ( pFontInstance->mnOrientation ) + { + Point aOriginPt(0, 0); + aOriginPt.RotateAround( mnTextOffX, mnTextOffY, pFontInstance->mnOrientation ); + } + } + else // eAlign == ALIGN_BOTTOM + { + mnTextOffX = 0; + mnTextOffY = -pFontInstance->mxFontMetric->GetDescent() + mnEmphasisDescent; + if ( pFontInstance->mnOrientation ) + { + Point aOriginPt(0, 0); + aOriginPt.RotateAround( mnTextOffX, mnTextOffY, pFontInstance->mnOrientation ); + } + } + + mbTextLines = ((maFont.GetUnderline() != LINESTYLE_NONE) && (maFont.GetUnderline() != LINESTYLE_DONTKNOW)) || + ((maFont.GetOverline() != LINESTYLE_NONE) && (maFont.GetOverline() != LINESTYLE_DONTKNOW)) || + ((maFont.GetStrikeout() != STRIKEOUT_NONE) && (maFont.GetStrikeout() != STRIKEOUT_DONTKNOW)); + mbTextSpecial = maFont.IsShadow() || maFont.IsOutline() || + (maFont.GetRelief() != FontRelief::NONE); + + + bool bRet = true; + + // #95414# fix for OLE objects which use scale factors very creatively + if (mbMap && !aSize.Width()) + bRet = AttemptOLEFontScaleFix(const_cast<vcl::Font&>(maFont), aSize.Height()); + + return bRet; +} + +bool OutputDevice::AttemptOLEFontScaleFix(vcl::Font& rFont, tools::Long nHeight) const +{ + const float fDenominator = static_cast<float>(maMapRes.mnMapScNumY) * maMapRes.mnMapScDenomX; + if (fDenominator == 0.0) + return false; + const float fNumerator = static_cast<float>(maMapRes.mnMapScNumX) * maMapRes.mnMapScDenomY; + float fStretch = fNumerator / fDenominator; + int nOrigWidth = mpFontInstance->mxFontMetric->GetWidth(); + int nNewWidth = static_cast<int>(nOrigWidth * fStretch + 0.5); + bool bRet = true; + if (nNewWidth != nOrigWidth && nNewWidth != 0) + { + Size aOrigSize = rFont.GetFontSize(); + rFont.SetFontSize(Size(nNewWidth, nHeight)); + mbMap = false; + mbNewFont = true; + bRet = ImplNewFont(); // recurse once using stretched width + mbMap = true; + rFont.SetFontSize(aOrigSize); + } + return bRet; +} + +void OutputDevice::SetFontOrientation( LogicalFontInstance* const pFontInstance ) const +{ + if( pFontInstance->GetFontSelectPattern().mnOrientation && !pFontInstance->mxFontMetric->GetOrientation() ) + { + pFontInstance->mnOwnOrientation = pFontInstance->GetFontSelectPattern().mnOrientation; + pFontInstance->mnOrientation = pFontInstance->mnOwnOrientation; + } + else + { + pFontInstance->mnOrientation = pFontInstance->mxFontMetric->GetOrientation(); + } +} + +void OutputDevice::ImplDrawEmphasisMark( tools::Long nBaseX, tools::Long nX, tools::Long nY, + const tools::PolyPolygon& rPolyPoly, bool bPolyLine, + const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 ) +{ + if( IsRTLEnabled() ) + nX = nBaseX - (nX - nBaseX - 1); + + nX -= mnOutOffX; + nY -= mnOutOffY; + + if ( rPolyPoly.Count() ) + { + if ( bPolyLine ) + { + tools::Polygon aPoly = rPolyPoly.GetObject( 0 ); + aPoly.Move( nX, nY ); + DrawPolyLine( aPoly ); + } + else + { + tools::PolyPolygon aPolyPoly = rPolyPoly; + aPolyPoly.Move( nX, nY ); + DrawPolyPolygon( aPolyPoly ); + } + } + + if ( !rRect1.IsEmpty() ) + { + tools::Rectangle aRect( Point( nX+rRect1.Left(), + nY+rRect1.Top() ), rRect1.GetSize() ); + DrawRect( aRect ); + } + + if ( !rRect2.IsEmpty() ) + { + tools::Rectangle aRect( Point( nX+rRect2.Left(), + nY+rRect2.Top() ), rRect2.GetSize() ); + + DrawRect( aRect ); + } +} + +void OutputDevice::ImplDrawEmphasisMarks( SalLayout& rSalLayout ) +{ + Color aOldLineColor = GetLineColor(); + Color aOldFillColor = GetFillColor(); + bool bOldMap = mbMap; + GDIMetaFile* pOldMetaFile = mpMetaFile; + mpMetaFile = nullptr; + EnableMapMode( false ); + + FontEmphasisMark nEmphasisMark = maFont.GetEmphasisMarkStyle(); + tools::Long nEmphasisHeight; + + if ( nEmphasisMark & FontEmphasisMark::PosBelow ) + nEmphasisHeight = mnEmphasisDescent; + else + nEmphasisHeight = mnEmphasisAscent; + + vcl::font::EmphasisMark aEmphasisMark(nEmphasisMark, nEmphasisHeight, GetDPIY()); + + if (aEmphasisMark.IsShapePolyLine()) + { + SetLineColor( GetTextColor() ); + SetFillColor(); + } + else + { + SetLineColor(); + SetFillColor( GetTextColor() ); + } + + Point aOffset(0,0); + Point aOffsetVert(0,0); + + if ( nEmphasisMark & FontEmphasisMark::PosBelow ) + { + aOffset.AdjustY(mpFontInstance->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset()); + aOffsetVert = aOffset; + } + else + { + aOffset.AdjustY(-(mpFontInstance->mxFontMetric->GetAscent() + aEmphasisMark.GetYOffset())); + // Todo: use ideographic em-box or ideographic character face information. + aOffsetVert.AdjustY(-(mpFontInstance->mxFontMetric->GetAscent() + + mpFontInstance->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset())); + } + + tools::Long nEmphasisWidth2 = aEmphasisMark.GetWidth() / 2; + tools::Long nEmphasisHeight2 = nEmphasisHeight / 2; + aOffset += Point( nEmphasisWidth2, nEmphasisHeight2 ); + + basegfx::B2DPoint aOutPoint; + tools::Rectangle aRectangle; + const GlyphItem* pGlyph; + const LogicalFontInstance* pGlyphFont; + int nStart = 0; + while (rSalLayout.GetNextGlyph(&pGlyph, aOutPoint, nStart, &pGlyphFont)) + { + if (!pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle)) + continue; + + if (!pGlyph->IsSpacing()) + { + Point aAdjPoint; + if (pGlyph->IsVertical()) + { + aAdjPoint = aOffsetVert; + aAdjPoint.AdjustX((-pGlyph->origWidth() + aEmphasisMark.GetWidth()) / 2); + } + else + { + aAdjPoint = aOffset; + aAdjPoint.AdjustX(aRectangle.Left() + (aRectangle.GetWidth() - aEmphasisMark.GetWidth()) / 2 ); + } + + if ( mpFontInstance->mnOrientation ) + { + Point aOriginPt(0, 0); + aOriginPt.RotateAround( aAdjPoint, mpFontInstance->mnOrientation ); + } + aOutPoint.adjustX(aAdjPoint.X() - nEmphasisWidth2); + aOutPoint.adjustY(aAdjPoint.Y() - nEmphasisHeight2); + ImplDrawEmphasisMark( rSalLayout.DrawBase().getX(), + aOutPoint.getX(), aOutPoint.getY(), + aEmphasisMark.GetShape(), aEmphasisMark.IsShapePolyLine(), + aEmphasisMark.GetRect1(), aEmphasisMark.GetRect2() ); + } + } + + SetLineColor( aOldLineColor ); + SetFillColor( aOldFillColor ); + EnableMapMode( bOldMap ); + mpMetaFile = pOldMetaFile; +} + +std::unique_ptr<SalLayout> OutputDevice::getFallbackLayout( + LogicalFontInstance* pLogicalFont, int nFallbackLevel, + vcl::text::ImplLayoutArgs& rLayoutArgs, const SalLayoutGlyphs* pGlyphs) const +{ + // we need a graphics + if (!mpGraphics && !AcquireGraphics()) + return nullptr; + + assert(mpGraphics != nullptr); + mpGraphics->SetFont( pLogicalFont, nFallbackLevel ); + + rLayoutArgs.ResetPos(); + std::unique_ptr<GenericSalLayout> pFallback = mpGraphics->GetTextLayout(nFallbackLevel); + + if (!pFallback) + return nullptr; + + if (!pFallback->LayoutText(rLayoutArgs, pGlyphs ? pGlyphs->Impl(nFallbackLevel) : nullptr)) + { + // there is no need for a font that couldn't resolve anything + return nullptr; + } + + return pFallback; +} + +bool OutputDevice::ForceFallbackFont(vcl::Font const& rFallbackFont) +{ + vcl::Font aOldFont = GetFont(); + SetFont(rFallbackFont); + if (!InitFont()) + return false; + + mpForcedFallbackInstance = mpFontInstance; + SetFont(aOldFont); + if (!InitFont()) + return false; + + if (mpForcedFallbackInstance) + return true; + + return false; +} + +std::unique_ptr<SalLayout> OutputDevice::ImplGlyphFallbackLayout( std::unique_ptr<SalLayout> pSalLayout, + vcl::text::ImplLayoutArgs& rLayoutArgs, const SalLayoutGlyphs* pGlyphs ) const +{ + // This function relies on a valid mpFontInstance, if it doesn't exist bail out + // - we'd have crashed later on anyway. At least here we can catch the error in debug + // mode. + if ( !mpFontInstance ) + { + SAL_WARN ("vcl.gdi", "No font entry set in OutputDevice"); + assert(mpFontInstance); + return nullptr; + } + + // prepare multi level glyph fallback + std::unique_ptr<MultiSalLayout> pMultiSalLayout; + ImplLayoutRuns aLayoutRuns = rLayoutArgs.maRuns; + rLayoutArgs.PrepareFallback(nullptr); + rLayoutArgs.mnFlags |= SalLayoutFlags::ForFallback; + + // get list of code units that need glyph fallback + bool bRTL; + int nMinRunPos, nEndRunPos; + OUStringBuffer aMissingCodeBuf(512); + while (rLayoutArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRTL)) + aMissingCodeBuf.append(rLayoutArgs.mrStr.subView(nMinRunPos, nEndRunPos - nMinRunPos)); + rLayoutArgs.ResetPos(); + OUString aMissingCodes = aMissingCodeBuf.makeStringAndClear(); + + vcl::font::FontSelectPattern aFontSelData(mpFontInstance->GetFontSelectPattern()); + SalLayoutGlyphsImpl* pGlyphsImpl = pGlyphs ? pGlyphs->Impl(1) : nullptr; + + bool bHasUsedFallback = false; + + // try if fallback fonts support the missing code units + for( int nFallbackLevel = 1; nFallbackLevel < MAX_FALLBACK; ++nFallbackLevel ) + { + rtl::Reference<LogicalFontInstance> pFallbackFont; + if (!bHasUsedFallback && mpForcedFallbackInstance) + { + pFallbackFont = mpForcedFallbackInstance; + bHasUsedFallback = true; + } + else if(pGlyphsImpl != nullptr) + { + pFallbackFont = pGlyphsImpl->GetFont(); + } + + // find a font family suited for glyph fallback + // GetGlyphFallbackFont() needs a valid FontInstance + // if the system-specific glyph fallback is active + OUString oldMissingCodes = aMissingCodes; + if( !pFallbackFont ) + pFallbackFont = mxFontCache->GetGlyphFallbackFont( mxFontCollection.get(), + aFontSelData, mpFontInstance.get(), nFallbackLevel, aMissingCodes ); + if( !pFallbackFont ) + break; + + SAL_INFO("vcl", "Fallback font (level " << nFallbackLevel << "): family: " << pFallbackFont->GetFontFace()->GetFamilyName() + << ", style: " << pFallbackFont->GetFontFace()->GetStyleName()); + + if( nFallbackLevel < MAX_FALLBACK-1) + { + // ignore fallback font if it is the same as the original font + // TODO: This seems broken. Either the font does not provide any of the missing + // codes, in which case the fallback should not select it. Or it does provide + // some of the missing codes, and then why weren't they used the first time? + // This will just loop repeatedly finding the same font (it used to remove + // the found font from mxFontCache, but doesn't do that anymore and I don't + // see how doing that would remove the font from consideration for fallback). + if( mpFontInstance->GetFontFace() == pFallbackFont->GetFontFace()) + { + if(aMissingCodes != oldMissingCodes) + { + SAL_INFO("vcl.gdi", "Font fallback to the same font, but has missing codes"); + // Restore the missing codes if we're not going to use this font. + aMissingCodes = oldMissingCodes; + } + continue; + } + } + + // create and add glyph fallback layout to multilayout + std::unique_ptr<SalLayout> pFallback = getFallbackLayout(pFallbackFont.get(), + nFallbackLevel, rLayoutArgs, pGlyphs); + if (pFallback) + { + if( !pMultiSalLayout ) + pMultiSalLayout.reset( new MultiSalLayout( std::move(pSalLayout) ) ); + pMultiSalLayout->AddFallback(std::move(pFallback), rLayoutArgs.maRuns); + if (nFallbackLevel == MAX_FALLBACK-1) + pMultiSalLayout->SetIncomplete(true); + } + + if (pGlyphs != nullptr) + pGlyphsImpl = pGlyphs->Impl(nFallbackLevel + 1); + + // break when this fallback was sufficient + if( !rLayoutArgs.PrepareFallback(pGlyphsImpl) ) + break; + } + + if (pMultiSalLayout) // due to missing glyphs, multilevel layout fallback attempted + { + // if it works, use that Layout + if (pMultiSalLayout->LayoutText(rLayoutArgs, nullptr)) + pSalLayout = std::move(pMultiSalLayout); + else + { + // if it doesn't, give up and restore ownership of the pSalLayout + // back to its original state + pSalLayout = pMultiSalLayout->ReleaseBaseLayout(); + } + } + + // restore orig font settings + pSalLayout->InitFont(); + rLayoutArgs.maRuns = aLayoutRuns; + + return pSalLayout; +} + +tools::Long OutputDevice::GetMinKashida() const +{ + if (!ImplNewFont()) + return 0; + + auto nKashidaWidth = mpFontInstance->mxFontMetric->GetMinKashida(); + if (!mbMap) + nKashidaWidth = std::ceil(nKashidaWidth); + + return ImplDevicePixelToLogicWidth(nKashidaWidth); +} + +sal_Int32 OutputDevice::ValidateKashidas ( const OUString& rTxt, + sal_Int32 nIdx, sal_Int32 nLen, + sal_Int32 nKashCount, + const sal_Int32* pKashidaPos, + sal_Int32* pKashidaPosDropped ) const +{ + // do layout + std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rTxt, nIdx, nLen ); + if( !pSalLayout ) + return 0; + + auto nEnd = nIdx + nLen - 1; + sal_Int32 nDropped = 0; + for( int i = 0; i < nKashCount; ++i ) + { + auto nPos = pKashidaPos[i]; + auto nNextPos = nPos + 1; + + // Skip combining marks to find the next character after this position. + while (nNextPos <= nEnd && + u_getIntPropertyValue(rTxt[nNextPos], UCHAR_JOINING_TYPE) == U_JT_TRANSPARENT) + nNextPos++; + + // The next position is past end of the layout, it would happen if we + // changed the text styling in the middle of a word. Since we don’t do + // apply OpenType features across different layouts, this can’t be an + // invalid place to insert Kashida. + if (nNextPos > nEnd) + continue; + + if (!pSalLayout->IsKashidaPosValid(nPos, nNextPos)) + pKashidaPosDropped[nDropped++] = nPos; + } + return nDropped; +} + +bool OutputDevice::GetGlyphBoundRects( const Point& rOrigin, const OUString& rStr, + int nIndex, int nLen, std::vector< tools::Rectangle >& rVector ) const +{ + rVector.clear(); + + if( nIndex >= rStr.getLength() ) + return false; + + if( nLen < 0 || nIndex + nLen >= rStr.getLength() ) + { + nLen = rStr.getLength() - nIndex; + } + + tools::Rectangle aRect; + for( int i = 0; i < nLen; i++ ) + { + if( !GetTextBoundRect( aRect, rStr, nIndex, nIndex + i, 1 ) ) + break; + aRect.Move( rOrigin.X(), rOrigin.Y() ); + rVector.push_back( aRect ); + } + + return (nLen == static_cast<int>(rVector.size())); +} + +sal_Int32 OutputDevice::HasGlyphs( const vcl::Font& rTempFont, std::u16string_view rStr, + sal_Int32 nIndex, sal_Int32 nLen ) const +{ + if( nIndex >= static_cast<sal_Int32>(rStr.size()) ) + return nIndex; + sal_Int32 nEnd; + if( nLen == -1 ) + nEnd = rStr.size(); + else + nEnd = std::min<sal_Int32>( rStr.size(), nIndex + nLen ); + + SAL_WARN_IF( nIndex >= nEnd, "vcl.gdi", "StartPos >= EndPos?" ); + SAL_WARN_IF( nEnd > static_cast<sal_Int32>(rStr.size()), "vcl.gdi", "String too short" ); + + // to get the map temporarily set font + const vcl::Font aOrigFont = GetFont(); + const_cast<OutputDevice&>(*this).SetFont( rTempFont ); + FontCharMapRef xFontCharMap; + bool bRet = GetFontCharMap( xFontCharMap ); + const_cast<OutputDevice&>(*this).SetFont( aOrigFont ); + + // if fontmap is unknown assume it doesn't have the glyphs + if( !bRet ) + return nIndex; + + for( sal_Int32 i = nIndex; nIndex < nEnd; ++i, ++nIndex ) + if( ! xFontCharMap->HasChar( rStr[i] ) ) + return nIndex; + + return -1; +} + +void OutputDevice::ReleaseFontCache() { mxFontCache.reset(); } + +void OutputDevice::ReleaseFontCollection() { mxFontCollection.reset(); } + +void OutputDevice::SetFontCollectionFromSVData() +{ + mxFontCollection = ImplGetSVData()->maGDIData.mxScreenFontList->Clone(); +} + +void OutputDevice::ResetNewFontCache() +{ + mxFontCache = std::make_shared<ImplFontCache>(); +} + +void OutputDevice::ImplReleaseFonts() +{ + mpGraphics->ReleaseFonts(); + + mbNewFont = true; + mbInitFont = true; + + mpFontInstance.clear(); + mpForcedFallbackInstance.clear(); + mpFontFaceCollection.reset(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/vcl/source/outdev/gradient.cxx b/vcl/source/outdev/gradient.cxx new file mode 100644 index 0000000000..e3803d660d --- /dev/null +++ b/vcl/source/outdev/gradient.cxx @@ -0,0 +1,624 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <tools/poly.hxx> + +#include <vcl/gradient.hxx> +#include <vcl/metaact.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> +#include <vcl/window.hxx> + +#include <salgdi.hxx> + +#include <cassert> +#include <memory> + +#define GRADIENT_DEFAULT_STEPCOUNT 0 + +void OutputDevice::DrawGradient( const tools::Rectangle& rRect, + const Gradient& rGradient ) +{ + assert(!is_double_buffered_window()); + + // Convert rectangle to a tools::PolyPolygon by first converting to a Polygon + tools::Polygon aPolygon ( rRect ); + tools::PolyPolygon aPolyPoly ( aPolygon ); + + DrawGradient ( aPolyPoly, rGradient ); +} + +void OutputDevice::DrawGradient( const tools::PolyPolygon& rPolyPoly, + const Gradient& rGradient ) +{ + assert(!is_double_buffered_window()); + + if (mbInitClipRegion) + InitClipRegion(); + // don't return on mbOutputClipped here, as we may need to draw the clipped metafile, even if the output is clipped + + if ( rPolyPoly.Count() && rPolyPoly[ 0 ].GetSize() ) + { + if ( mnDrawMode & ( DrawModeFlags::BlackGradient | DrawModeFlags::WhiteGradient | DrawModeFlags::SettingsGradient) ) + { + Color aColor = GetSingleColorGradientFill(); + + Push( vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR ); + SetLineColor( aColor ); + SetFillColor( aColor ); + DrawPolyPolygon( rPolyPoly ); + Pop(); + return; + } + + Gradient aGradient( rGradient ); + + if ( mnDrawMode & DrawModeFlags::GrayGradient ) + aGradient.MakeGrayscale(); + + DrawGradientToMetafile( rPolyPoly, rGradient ); + + if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + // Clip and then draw the gradient + if( !tools::Rectangle( PixelToLogic( Point() ), GetOutputSize() ).IsEmpty() ) + { + const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() ); + + // convert rectangle to pixels + tools::Rectangle aRect( ImplLogicToDevicePixel( aBoundRect ) ); + aRect.Normalize(); + + // do nothing if the rectangle is empty + if ( !aRect.IsEmpty() ) + { + tools::PolyPolygon aClixPolyPoly( ImplLogicToDevicePixel( rPolyPoly ) ); + bool bDrawn = false; + + if( !mpGraphics && !AcquireGraphics() ) + return; + + // secure clip region + Push( vcl::PushFlags::CLIPREGION ); + IntersectClipRegion( aBoundRect ); + + if (mbInitClipRegion) + InitClipRegion(); + + // try to draw gradient natively + if (!mbOutputClipped) + bDrawn = mpGraphics->DrawGradient( aClixPolyPoly, aGradient, *this ); + + if (!bDrawn && !mbOutputClipped) + { + // draw gradients without border + if( mbLineColor || mbInitLineColor ) + { + mpGraphics->SetLineColor(); + mbInitLineColor = true; + } + + mbInitFillColor = true; + + // calculate step count if necessary + if ( !aGradient.GetSteps() ) + aGradient.SetSteps( GRADIENT_DEFAULT_STEPCOUNT ); + + if ( rPolyPoly.IsRect() ) + { + // because we draw with no border line, we have to expand gradient + // rect to avoid missing lines on the right and bottom edge + aRect.AdjustLeft( -1 ); + aRect.AdjustTop( -1 ); + aRect.AdjustRight( 1 ); + aRect.AdjustBottom( 1 ); + } + + // if the clipping polypolygon is a rectangle, then it's the same size as the bounding of the + // polypolygon, so pass in a NULL for the clipping parameter + if( aGradient.GetStyle() == css::awt::GradientStyle_LINEAR || rGradient.GetStyle() == css::awt::GradientStyle_AXIAL ) + DrawLinearGradient( aRect, aGradient, aClixPolyPoly.IsRect() ? nullptr : &aClixPolyPoly ); + else + DrawComplexGradient( aRect, aGradient, aClixPolyPoly.IsRect() ? nullptr : &aClixPolyPoly ); + } + + Pop(); + } + } + } + + if( mpAlphaVDev ) + { + const Color aFillCol( mpAlphaVDev->GetFillColor() ); + mpAlphaVDev->SetFillColor( COL_ALPHA_OPAQUE ); + mpAlphaVDev->DrawPolyPolygon( rPolyPoly ); + mpAlphaVDev->SetFillColor( aFillCol ); + } +} + +void OutputDevice::ClipAndDrawGradientMetafile ( const Gradient &rGradient, const tools::PolyPolygon &rPolyPoly ) +{ + const bool bOldOutput = IsOutputEnabled(); + EnableOutput( false ); + + Push( vcl::PushFlags::CLIPREGION ); + SetClipRegion( vcl::Region( rPolyPoly ) ); + DrawGradient( rPolyPoly.GetBoundRect(), rGradient ); + Pop(); + + EnableOutput( bOldOutput ); +} + +void OutputDevice::DrawGradientToMetafile ( const tools::PolyPolygon& rPolyPoly, + const Gradient& rGradient ) +{ + assert(!is_double_buffered_window()); + + if ( !mpMetaFile ) + return; + + if ( !(rPolyPoly.Count() && rPolyPoly[ 0 ].GetSize()) ) + return; + + const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() ); + + if (aBoundRect.IsEmpty()) + return; + + Gradient aGradient( rGradient ); + + if (mnDrawMode & DrawModeFlags::GrayGradient) + aGradient.MakeGrayscale(); + + if ( rPolyPoly.IsRect() ) + { + mpMetaFile->AddAction( new MetaGradientAction( aBoundRect, std::move(aGradient) ) ); + } + else + { + mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_BEGIN"_ostr ) ); + mpMetaFile->AddAction( new MetaGradientExAction( rPolyPoly, rGradient ) ); + + ClipAndDrawGradientMetafile ( rGradient, rPolyPoly ); + + mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_END"_ostr ) ); + } +} + +namespace +{ + sal_uInt8 GetGradientColorValue( tools::Long nValue ) + { + if ( nValue < 0 ) + return 0; + else if ( nValue > 0xFF ) + return 0xFF; + else + return static_cast<sal_uInt8>(nValue); + } +} + +void OutputDevice::DrawLinearGradient( const tools::Rectangle& rRect, + const Gradient& rGradient, + const tools::PolyPolygon* pClixPolyPoly ) +{ + assert(!is_double_buffered_window()); + + // get BoundRect of rotated rectangle + tools::Rectangle aRect; + Point aCenter; + Degree10 nAngle = rGradient.GetAngle() % 3600_deg10; + + rGradient.GetBoundRect( rRect, aRect, aCenter ); + + bool bLinear = (rGradient.GetStyle() == css::awt::GradientStyle_LINEAR); + double fBorder = rGradient.GetBorder() * aRect.GetHeight() / 100.0; + if ( !bLinear ) + { + fBorder /= 2.0; + } + tools::Rectangle aMirrorRect = aRect; // used in style axial + aMirrorRect.SetTop( ( aRect.Top() + aRect.Bottom() ) / 2 ); + if ( !bLinear ) + { + aRect.SetBottom( aMirrorRect.Top() ); + } + + // colour-intensities of start- and finish; change if needed + tools::Long nFactor; + Color aStartCol = rGradient.GetStartColor(); + Color aEndCol = rGradient.GetEndColor(); + tools::Long nStartRed = aStartCol.GetRed(); + tools::Long nStartGreen = aStartCol.GetGreen(); + tools::Long nStartBlue = aStartCol.GetBlue(); + tools::Long nEndRed = aEndCol.GetRed(); + tools::Long nEndGreen = aEndCol.GetGreen(); + tools::Long nEndBlue = aEndCol.GetBlue(); + nFactor = rGradient.GetStartIntensity(); + nStartRed = (nStartRed * nFactor) / 100; + nStartGreen = (nStartGreen * nFactor) / 100; + nStartBlue = (nStartBlue * nFactor) / 100; + nFactor = rGradient.GetEndIntensity(); + nEndRed = (nEndRed * nFactor) / 100; + nEndGreen = (nEndGreen * nFactor) / 100; + nEndBlue = (nEndBlue * nFactor) / 100; + + // gradient style axial has exchanged start and end colors + if ( !bLinear) + { + std::swap( nStartRed, nEndRed ); + std::swap( nStartGreen, nEndGreen ); + std::swap( nStartBlue, nEndBlue ); + } + + sal_uInt8 nRed; + sal_uInt8 nGreen; + sal_uInt8 nBlue; + + // Create border + tools::Rectangle aBorderRect = aRect; + tools::Polygon aPoly( 4 ); + if (fBorder > 0.0) + { + nRed = static_cast<sal_uInt8>(nStartRed); + nGreen = static_cast<sal_uInt8>(nStartGreen); + nBlue = static_cast<sal_uInt8>(nStartBlue); + + mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) ); + + aBorderRect.SetBottom( static_cast<tools::Long>( aBorderRect.Top() + fBorder ) ); + aRect.SetTop( aBorderRect.Bottom() ); + aPoly[0] = aBorderRect.TopLeft(); + aPoly[1] = aBorderRect.TopRight(); + aPoly[2] = aBorderRect.BottomRight(); + aPoly[3] = aBorderRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + ImplDrawPolygon( aPoly, pClixPolyPoly ); + + if ( !bLinear) + { + aBorderRect = aMirrorRect; + aBorderRect.SetTop( static_cast<tools::Long>( aBorderRect.Bottom() - fBorder ) ); + aMirrorRect.SetBottom( aBorderRect.Top() ); + aPoly[0] = aBorderRect.TopLeft(); + aPoly[1] = aBorderRect.TopRight(); + aPoly[2] = aBorderRect.BottomRight(); + aPoly[3] = aBorderRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + ImplDrawPolygon( aPoly, pClixPolyPoly ); + } + } + + // calculate step count + tools::Long nStepCount = GetGradientSteps(rGradient, aRect); + + // minimal three steps and maximal as max color steps + tools::Long nAbsRedSteps = std::abs( nEndRed - nStartRed ); + tools::Long nAbsGreenSteps = std::abs( nEndGreen - nStartGreen ); + tools::Long nAbsBlueSteps = std::abs( nEndBlue - nStartBlue ); + tools::Long nMaxColorSteps = std::max( nAbsRedSteps , nAbsGreenSteps ); + nMaxColorSteps = std::max( nMaxColorSteps, nAbsBlueSteps ); + tools::Long nSteps = std::min( nStepCount, nMaxColorSteps ); + if ( nSteps < 3) + { + nSteps = 3; + } + + double fScanInc = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps); + double fGradientLine = static_cast<double>(aRect.Top()); + double fMirrorGradientLine = static_cast<double>(aMirrorRect.Bottom()); + + const double fStepsMinus1 = static_cast<double>(nSteps) - 1.0; + if ( !bLinear) + { + nSteps -= 1; // draw middle polygons as one polygon after loop to avoid gap + } + + for ( tools::Long i = 0; i < nSteps; i++ ) + { + // linear interpolation of color + const double fAlpha = static_cast<double>(i) / fStepsMinus1; + double fTempColor = static_cast<double>(nStartRed) * (1.0-fAlpha) + static_cast<double>(nEndRed) * fAlpha; + nRed = GetGradientColorValue(static_cast<tools::Long>(fTempColor)); + fTempColor = static_cast<double>(nStartGreen) * (1.0-fAlpha) + static_cast<double>(nEndGreen) * fAlpha; + nGreen = GetGradientColorValue(static_cast<tools::Long>(fTempColor)); + fTempColor = static_cast<double>(nStartBlue) * (1.0-fAlpha) + static_cast<double>(nEndBlue) * fAlpha; + nBlue = GetGradientColorValue(static_cast<tools::Long>(fTempColor)); + + mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) ); + + // Polygon for this color step + aRect.SetTop( static_cast<tools::Long>( fGradientLine + static_cast<double>(i) * fScanInc ) ); + aRect.SetBottom( static_cast<tools::Long>( fGradientLine + ( static_cast<double>(i) + 1.0 ) * fScanInc ) ); + aPoly[0] = aRect.TopLeft(); + aPoly[1] = aRect.TopRight(); + aPoly[2] = aRect.BottomRight(); + aPoly[3] = aRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + ImplDrawPolygon( aPoly, pClixPolyPoly ); + + if ( !bLinear ) + { + aMirrorRect.SetBottom( static_cast<tools::Long>( fMirrorGradientLine - static_cast<double>(i) * fScanInc ) ); + aMirrorRect.SetTop( static_cast<tools::Long>( fMirrorGradientLine - (static_cast<double>(i) + 1.0)* fScanInc ) ); + aPoly[0] = aMirrorRect.TopLeft(); + aPoly[1] = aMirrorRect.TopRight(); + aPoly[2] = aMirrorRect.BottomRight(); + aPoly[3] = aMirrorRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + ImplDrawPolygon( aPoly, pClixPolyPoly ); + } + } + if ( bLinear) + return; + + // draw middle polygon with end color + nRed = GetGradientColorValue(nEndRed); + nGreen = GetGradientColorValue(nEndGreen); + nBlue = GetGradientColorValue(nEndBlue); + + mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) ); + + aRect.SetTop( static_cast<tools::Long>( fGradientLine + static_cast<double>(nSteps) * fScanInc ) ); + aRect.SetBottom( static_cast<tools::Long>( fMirrorGradientLine - static_cast<double>(nSteps) * fScanInc ) ); + aPoly[0] = aRect.TopLeft(); + aPoly[1] = aRect.TopRight(); + aPoly[2] = aRect.BottomRight(); + aPoly[3] = aRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + ImplDrawPolygon( aPoly, pClixPolyPoly ); + +} + +bool OutputDevice::is_double_buffered_window() const +{ + auto pOwnerWindow = GetOwnerWindow(); + return pOwnerWindow && pOwnerWindow->SupportsDoubleBuffering(); +} + +void OutputDevice::DrawComplexGradient( const tools::Rectangle& rRect, + const Gradient& rGradient, + const tools::PolyPolygon* pClixPolyPoly ) +{ + assert(!is_double_buffered_window()); + + // Determine if we output via Polygon or PolyPolygon + // For all rasteroperations other than Overpaint always use PolyPolygon, + // as we will get wrong results if we output multiple times on top of each other. + // Also for printers always use PolyPolygon, as not all printers + // can print polygons on top of each other. + + std::optional<tools::PolyPolygon> xPolyPoly; + tools::Rectangle aRect; + Point aCenter; + Color aStartCol( rGradient.GetStartColor() ); + Color aEndCol( rGradient.GetEndColor() ); + tools::Long nStartRed = ( static_cast<tools::Long>(aStartCol.GetRed()) * rGradient.GetStartIntensity() ) / 100; + tools::Long nStartGreen = ( static_cast<tools::Long>(aStartCol.GetGreen()) * rGradient.GetStartIntensity() ) / 100; + tools::Long nStartBlue = ( static_cast<tools::Long>(aStartCol.GetBlue()) * rGradient.GetStartIntensity() ) / 100; + tools::Long nEndRed = ( static_cast<tools::Long>(aEndCol.GetRed()) * rGradient.GetEndIntensity() ) / 100; + tools::Long nEndGreen = ( static_cast<tools::Long>(aEndCol.GetGreen()) * rGradient.GetEndIntensity() ) / 100; + tools::Long nEndBlue = ( static_cast<tools::Long>(aEndCol.GetBlue()) * rGradient.GetEndIntensity() ) / 100; + tools::Long nRedSteps = nEndRed - nStartRed; + tools::Long nGreenSteps = nEndGreen - nStartGreen; + tools::Long nBlueSteps = nEndBlue - nStartBlue; + Degree10 nAngle = rGradient.GetAngle() % 3600_deg10; + + rGradient.GetBoundRect( rRect, aRect, aCenter ); + + if ( UsePolyPolygonForComplexGradient() ) + xPolyPoly = tools::PolyPolygon( 2 ); + + tools::Long nStepCount = GetGradientSteps(rGradient, rRect); + + // at least three steps and at most the number of colour differences + tools::Long nSteps = std::max( nStepCount, tools::Long(2) ); + tools::Long nCalcSteps = std::abs( nRedSteps ); + tools::Long nTempSteps = std::abs( nGreenSteps ); + if ( nTempSteps > nCalcSteps ) + nCalcSteps = nTempSteps; + nTempSteps = std::abs( nBlueSteps ); + if ( nTempSteps > nCalcSteps ) + nCalcSteps = nTempSteps; + if ( nCalcSteps < nSteps ) + nSteps = nCalcSteps; + if ( !nSteps ) + nSteps = 1; + + // determine output limits and stepsizes for all directions + tools::Polygon aPoly; + double fScanLeft = aRect.Left(); + double fScanTop = aRect.Top(); + double fScanRight = aRect.Right(); + double fScanBottom = aRect.Bottom(); + double fScanIncX = static_cast<double>(aRect.GetWidth()) / static_cast<double>(nSteps) * 0.5; + double fScanIncY = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps) * 0.5; + + // all gradients are rendered as nested rectangles which shrink + // equally in each dimension - except for 'square' gradients + // which shrink to a central vertex but are not per-se square. + if( rGradient.GetStyle() != css::awt::GradientStyle_SQUARE ) + { + fScanIncY = std::min( fScanIncY, fScanIncX ); + fScanIncX = fScanIncY; + } + sal_uInt8 nRed = static_cast<sal_uInt8>(nStartRed), nGreen = static_cast<sal_uInt8>(nStartGreen), nBlue = static_cast<sal_uInt8>(nStartBlue); + bool bPaintLastPolygon( false ); // #107349# Paint last polygon only if loop has generated any output + + mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) ); + + if( xPolyPoly ) + { + aPoly = tools::Polygon(rRect); + xPolyPoly->Insert( aPoly ); + xPolyPoly->Insert( aPoly ); + } + else + { + // extend rect, to avoid missing bounding line + tools::Rectangle aExtRect( rRect ); + + aExtRect.AdjustLeft( -1 ); + aExtRect.AdjustTop( -1 ); + aExtRect.AdjustRight(1 ); + aExtRect.AdjustBottom(1 ); + + aPoly = tools::Polygon(aExtRect); + ImplDrawPolygon( aPoly, pClixPolyPoly ); + } + + // loop to output Polygon/PolyPolygon sequentially + for( tools::Long i = 1; i < nSteps; i++ ) + { + // calculate new Polygon + fScanLeft += fScanIncX; + aRect.SetLeft( static_cast<tools::Long>( fScanLeft ) ); + fScanTop += fScanIncY; + aRect.SetTop( static_cast<tools::Long>( fScanTop ) ); + fScanRight -= fScanIncX; + aRect.SetRight( static_cast<tools::Long>( fScanRight ) ); + fScanBottom -= fScanIncY; + aRect.SetBottom( static_cast<tools::Long>( fScanBottom ) ); + + if( ( aRect.GetWidth() < 2 ) || ( aRect.GetHeight() < 2 ) ) + break; + + if( rGradient.GetStyle() == css::awt::GradientStyle_RADIAL || rGradient.GetStyle() == css::awt::GradientStyle_ELLIPTICAL ) + aPoly = tools::Polygon( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 ); + else + aPoly = tools::Polygon( aRect ); + + aPoly.Rotate( aCenter, nAngle ); + + // adapt colour accordingly + const tools::Long nStepIndex = ( xPolyPoly ? i : ( i + 1 ) ); + nRed = GetGradientColorValue( nStartRed + ( ( nRedSteps * nStepIndex ) / nSteps ) ); + nGreen = GetGradientColorValue( nStartGreen + ( ( nGreenSteps * nStepIndex ) / nSteps ) ); + nBlue = GetGradientColorValue( nStartBlue + ( ( nBlueSteps * nStepIndex ) / nSteps ) ); + + // either slow tools::PolyPolygon output or fast Polygon-Painting + if( xPolyPoly ) + { + bPaintLastPolygon = true; // #107349# Paint last polygon only if loop has generated any output + + xPolyPoly->Replace( xPolyPoly->GetObject( 1 ), 0 ); + xPolyPoly->Replace( aPoly, 1 ); + + ImplDrawPolyPolygon( *xPolyPoly, pClixPolyPoly ); + + // #107349# Set fill color _after_ geometry painting: + // xPolyPoly's geometry is the band from last iteration's + // aPoly to current iteration's aPoly. The window outdev + // path (see else below), on the other hand, paints the + // full aPoly. Thus, here, we're painting the band before + // the one painted in the window outdev path below. To get + // matching colors, have to delay color setting here. + mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) ); + } + else + { + // #107349# Set fill color _before_ geometry painting + mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) ); + + ImplDrawPolygon( aPoly, pClixPolyPoly ); + } + } + + // we should draw last inner Polygon if we output PolyPolygon + if( !xPolyPoly ) + return; + + const tools::Polygon& rPoly = xPolyPoly->GetObject( 1 ); + + if( rPoly.GetBoundRect().IsEmpty() ) + return; + + // #107349# Paint last polygon with end color only if loop + // has generated output. Otherwise, the current + // (i.e. start) color is taken, to generate _any_ output. + if( bPaintLastPolygon ) + { + nRed = GetGradientColorValue( nEndRed ); + nGreen = GetGradientColorValue( nEndGreen ); + nBlue = GetGradientColorValue( nEndBlue ); + } + + mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) ); + ImplDrawPolygon( rPoly, pClixPolyPoly ); +} + +tools::Long OutputDevice::GetGradientStepCount( tools::Long nMinRect ) +{ + tools::Long nInc = (nMinRect < 50) ? 2 : 4; + + return nInc; +} + +tools::Long OutputDevice::GetGradientSteps(Gradient const& rGradient, tools::Rectangle const& rRect) +{ + // calculate step count + tools::Long nStepCount = rGradient.GetSteps(); + + if (nStepCount) + return nStepCount; + + tools::Long nMinRect = 0; + + if (rGradient.GetStyle() == css::awt::GradientStyle_LINEAR || rGradient.GetStyle() == css::awt::GradientStyle_AXIAL) + nMinRect = rRect.GetHeight(); + else + nMinRect = std::min(rRect.GetWidth(), rRect.GetHeight()); + + tools::Long nInc = GetGradientStepCount(nMinRect); + + if (!nInc) + nInc = 1; + + return nMinRect / nInc; +} + +Color OutputDevice::GetSingleColorGradientFill() +{ + Color aColor; + + // we should never call on this function if any of these aren't set! + assert( mnDrawMode & ( DrawModeFlags::BlackGradient | DrawModeFlags::WhiteGradient | DrawModeFlags::SettingsGradient) ); + + if ( mnDrawMode & DrawModeFlags::BlackGradient ) + aColor = COL_BLACK; + else if ( mnDrawMode & DrawModeFlags::WhiteGradient ) + aColor = COL_WHITE; + else if ( mnDrawMode & DrawModeFlags::SettingsGradient ) + { + if (mnDrawMode & DrawModeFlags::SettingsForSelection) + aColor = GetSettings().GetStyleSettings().GetHighlightColor(); + else + aColor = GetSettings().GetStyleSettings().GetWindowColor(); + } + + return aColor; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/hatch.cxx b/vcl/source/outdev/hatch.cxx new file mode 100644 index 0000000000..0fc755864a --- /dev/null +++ b/vcl/source/outdev/hatch.cxx @@ -0,0 +1,439 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <osl/diagnose.h> +#include <tools/line.hxx> +#include <tools/helpers.hxx> +#include <unotools/configmgr.hxx> + +#include <vcl/hatch.hxx> +#include <vcl/metaact.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> + +#include <drawmode.hxx> +#include <salgdi.hxx> + +#include <cassert> +#include <cstdlib> +#include <memory> + +#define HATCH_MAXPOINTS 1024 + +extern "C" { + +static int HatchCmpFnc( const void* p1, const void* p2 ) +{ + const tools::Long nX1 = static_cast<Point const *>(p1)->X(); + const tools::Long nX2 = static_cast<Point const *>(p2)->X(); + const tools::Long nY1 = static_cast<Point const *>(p1)->Y(); + const tools::Long nY2 = static_cast<Point const *>(p2)->Y(); + + return ( nX1 > nX2 ? 1 : nX1 == nX2 ? nY1 > nY2 ? 1: nY1 == nY2 ? 0 : -1 : -1 ); +} + +} + +void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch ) +{ + assert(!is_double_buffered_window()); + + Hatch aHatch( rHatch ); + aHatch.SetColor(vcl::drawmode::GetHatchColor(rHatch.GetColor(), GetDrawMode(), GetSettings().GetStyleSettings())); + + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaHatchAction( rPolyPoly, aHatch ) ); + + if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + if( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return; + + if( rPolyPoly.Count() ) + { + tools::PolyPolygon aPolyPoly( LogicToPixel( rPolyPoly ) ); + GDIMetaFile* pOldMetaFile = mpMetaFile; + bool bOldMap = mbMap; + + aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME ); + aHatch.SetDistance( ImplLogicWidthToDevicePixel( aHatch.GetDistance() ) ); + + mpMetaFile = nullptr; + EnableMapMode( false ); + Push( vcl::PushFlags::LINECOLOR ); + SetLineColor( aHatch.GetColor() ); + InitLineColor(); + DrawHatch( aPolyPoly, aHatch, false ); + Pop(); + EnableMapMode( bOldMap ); + mpMetaFile = pOldMetaFile; + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawHatch( rPolyPoly, rHatch ); +} + +void OutputDevice::AddHatchActions( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch, + GDIMetaFile& rMtf ) +{ + + tools::PolyPolygon aPolyPoly( rPolyPoly ); + aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME | PolyOptimizeFlags::CLOSE ); + + if( aPolyPoly.Count() ) + { + GDIMetaFile* pOldMtf = mpMetaFile; + + mpMetaFile = &rMtf; + mpMetaFile->AddAction( new MetaPushAction( vcl::PushFlags::ALL ) ); + mpMetaFile->AddAction( new MetaLineColorAction( rHatch.GetColor(), true ) ); + DrawHatch( aPolyPoly, rHatch, true ); + mpMetaFile->AddAction( new MetaPopAction() ); + mpMetaFile = pOldMtf; + } +} + +static bool HasSaneNSteps(const Point& rPt1, const Point& rEndPt1, const Size& rInc) +{ + tools::Long nVertSteps = -1; + if (rInc.Height()) + { + bool bFail = o3tl::checked_sub(rEndPt1.Y(), rPt1.Y(), nVertSteps); + if (bFail) + nVertSteps = std::numeric_limits<tools::Long>::max(); + else + nVertSteps = nVertSteps / rInc.Height(); + } + tools::Long nHorzSteps = -1; + if (rInc.Width()) + { + bool bFail = o3tl::checked_sub(rEndPt1.X(), rPt1.X(), nHorzSteps); + if (bFail) + nHorzSteps = std::numeric_limits<tools::Long>::max(); + else + nHorzSteps = nHorzSteps / rInc.Width(); + } + auto nSteps = std::max(nVertSteps, nHorzSteps); + if (nSteps > 1024) + { + SAL_WARN("vcl.gdi", "skipping slow hatch with " << nSteps << " steps"); + return false; + } + return true; +} + +void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch, bool bMtf ) +{ + assert(!is_double_buffered_window()); + + if(!rPolyPoly.Count()) + return; + + // #i115630# DrawHatch does not work with beziers included in the polypolygon, take care of that + bool bIsCurve(false); + + for(sal_uInt16 a(0); !bIsCurve && a < rPolyPoly.Count(); a++) + { + if(rPolyPoly[a].HasFlags()) + { + bIsCurve = true; + } + } + + if(bIsCurve) + { + OSL_ENSURE(false, "DrawHatch does *not* support curves, falling back to AdaptiveSubdivide()..."); + tools::PolyPolygon aPolyPoly; + + rPolyPoly.AdaptiveSubdivide(aPolyPoly); + DrawHatch(aPolyPoly, rHatch, bMtf); + } + else + { + tools::Rectangle aRect( rPolyPoly.GetBoundRect() ); + const tools::Long nLogPixelWidth = ImplDevicePixelToLogicWidth( 1 ); + const tools::Long nWidth = ImplDevicePixelToLogicWidth( std::max( ImplLogicWidthToDevicePixel( rHatch.GetDistance() ), tools::Long(3) ) ); + std::unique_ptr<Point[]> pPtBuffer(new Point[ HATCH_MAXPOINTS ]); + Point aPt1, aPt2, aEndPt1; + Size aInc; + + // Single hatch + aRect.AdjustLeft( -nLogPixelWidth ); aRect.AdjustTop( -nLogPixelWidth ); aRect.AdjustRight(nLogPixelWidth ); aRect.AdjustBottom(nLogPixelWidth ); + CalcHatchValues( aRect, nWidth, rHatch.GetAngle(), aPt1, aPt2, aInc, aEndPt1 ); + if (utl::ConfigManager::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc)) + return; + + if (aInc.Width() <= 0 && aInc.Height() <= 0) + SAL_WARN("vcl.gdi", "invalid increment"); + else + { + do + { + DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf ); + aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() ); + aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() ); + } + while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) ); + } + + if( ( rHatch.GetStyle() == HatchStyle::Double ) || ( rHatch.GetStyle() == HatchStyle::Triple ) ) + { + // Double hatch + CalcHatchValues( aRect, nWidth, rHatch.GetAngle() + 900_deg10, aPt1, aPt2, aInc, aEndPt1 ); + if (utl::ConfigManager::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc)) + return; + + do + { + DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf ); + aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() ); + aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() ); + } + while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) ); + + if( rHatch.GetStyle() == HatchStyle::Triple ) + { + // Triple hatch + CalcHatchValues( aRect, nWidth, rHatch.GetAngle() + 450_deg10, aPt1, aPt2, aInc, aEndPt1 ); + if (utl::ConfigManager::IsFuzzing() && !HasSaneNSteps(aPt1, aEndPt1, aInc)) + return; + + do + { + DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf ); + aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() ); + aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() ); + } + while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) ); + } + } + } +} + +void OutputDevice::CalcHatchValues( const tools::Rectangle& rRect, tools::Long nDist, Degree10 nAngle10, + Point& rPt1, Point& rPt2, Size& rInc, Point& rEndPt1 ) +{ + Point aRef; + Degree10 nAngle = nAngle10 % 1800_deg10; + tools::Long nOffset = 0; + + if( nAngle > 900_deg10 ) + nAngle -= 1800_deg10; + + aRef = ( !IsRefPoint() ? rRect.TopLeft() : GetRefPoint() ); + + if( 0_deg10 == nAngle ) + { + rInc = Size( 0, nDist ); + rPt1 = rRect.TopLeft(); + rPt2 = rRect.TopRight(); + rEndPt1 = rRect.BottomLeft(); + + if( aRef.Y() <= rRect.Top() ) + nOffset = ( ( rRect.Top() - aRef.Y() ) % nDist ); + else + nOffset = ( nDist - ( ( aRef.Y() - rRect.Top() ) % nDist ) ); + + rPt1.AdjustY( -nOffset ); + rPt2.AdjustY( -nOffset ); + } + else if( 900_deg10 == nAngle ) + { + rInc = Size( nDist, 0 ); + rPt1 = rRect.TopLeft(); + rPt2 = rRect.BottomLeft(); + rEndPt1 = rRect.TopRight(); + + if( aRef.X() <= rRect.Left() ) + nOffset = ( rRect.Left() - aRef.X() ) % nDist; + else + nOffset = nDist - ( ( aRef.X() - rRect.Left() ) % nDist ); + + rPt1.AdjustX( -nOffset ); + rPt2.AdjustX( -nOffset ); + } + else if( nAngle >= Degree10(-450) && nAngle <= 450_deg10 ) + { + const double fAngle = std::abs( toRadians(nAngle) ); + const double fTan = tan( fAngle ); + const tools::Long nYOff = FRound( ( rRect.Right() - rRect.Left() ) * fTan ); + tools::Long nPY; + + nDist = FRound( nDist / cos( fAngle ) ); + rInc = Size( 0, nDist ); + + if( nAngle > 0_deg10 ) + { + rPt1 = rRect.TopLeft(); + rPt2 = Point( rRect.Right(), rRect.Top() - nYOff ); + rEndPt1 = Point( rRect.Left(), rRect.Bottom() + nYOff ); + nPY = FRound( aRef.Y() - ( ( rPt1.X() - aRef.X() ) * fTan ) ); + } + else + { + rPt1 = rRect.TopRight(); + rPt2 = Point( rRect.Left(), rRect.Top() - nYOff ); + rEndPt1 = Point( rRect.Right(), rRect.Bottom() + nYOff ); + nPY = FRound( aRef.Y() + ( ( rPt1.X() - aRef.X() ) * fTan ) ); + } + + if( nPY <= rPt1.Y() ) + nOffset = ( rPt1.Y() - nPY ) % nDist; + else + nOffset = nDist - ( ( nPY - rPt1.Y() ) % nDist ); + + rPt1.AdjustY( -nOffset ); + rPt2.AdjustY( -nOffset ); + } + else + { + const double fAngle = std::abs( toRadians(nAngle) ); + const double fTan = tan( fAngle ); + const tools::Long nXOff = FRound( (static_cast<double>(rRect.Bottom()) - rRect.Top()) / fTan ); + tools::Long nPX; + + nDist = FRound( nDist / sin( fAngle ) ); + rInc = Size( nDist, 0 ); + + if( nAngle > 0_deg10 ) + { + rPt1 = rRect.TopLeft(); + rPt2 = Point( rRect.Left() - nXOff, rRect.Bottom() ); + rEndPt1 = Point( rRect.Right() + nXOff, rRect.Top() ); + nPX = FRound( aRef.X() - ( (static_cast<double>(rPt1.Y()) - aRef.Y()) / fTan ) ); + } + else + { + rPt1 = rRect.BottomLeft(); + rPt2 = Point( rRect.Left() - nXOff, rRect.Top() ); + rEndPt1 = Point( rRect.Right() + nXOff, rRect.Bottom() ); + nPX = FRound( aRef.X() + ( (static_cast<double>(rPt1.Y()) - aRef.Y()) / fTan ) ); + } + + if( nPX <= rPt1.X() ) + nOffset = ( rPt1.X() - nPX ) % nDist; + else + nOffset = nDist - ( ( nPX - rPt1.X() ) % nDist ); + + rPt1.AdjustX( -nOffset ); + rPt2.AdjustX( -nOffset ); + } +} + +void OutputDevice::DrawHatchLine( const tools::Line& rLine, const tools::PolyPolygon& rPolyPoly, + Point* pPtBuffer, bool bMtf ) +{ + assert(!is_double_buffered_window()); + + double fX, fY; + tools::Long nAdd, nPCounter = 0; + + for( tools::Long nPoly = 0, nPolyCount = rPolyPoly.Count(); nPoly < nPolyCount; nPoly++ ) + { + const tools::Polygon& rPoly = rPolyPoly[ static_cast<sal_uInt16>(nPoly) ]; + + if( rPoly.GetSize() > 1 ) + { + tools::Line aCurSegment( rPoly[ 0 ], Point() ); + + for( tools::Long i = 1, nCount = rPoly.GetSize(); i <= nCount; i++ ) + { + aCurSegment.SetEnd( rPoly[ static_cast<sal_uInt16>( i % nCount ) ] ); + nAdd = 0; + + if( rLine.Intersection( aCurSegment, fX, fY ) ) + { + if( ( fabs( fX - aCurSegment.GetStart().X() ) <= 0.0000001 ) && + ( fabs( fY - aCurSegment.GetStart().Y() ) <= 0.0000001 ) ) + { + const tools::Line aPrevSegment( rPoly[ static_cast<sal_uInt16>( ( i > 1 ) ? ( i - 2 ) : ( nCount - 1 ) ) ], aCurSegment.GetStart() ); + const double fPrevDistance = rLine.GetDistance( aPrevSegment.GetStart() ); + const double fCurDistance = rLine.GetDistance( aCurSegment.GetEnd() ); + + if( ( fPrevDistance <= 0.0 && fCurDistance > 0.0 ) || + ( fPrevDistance > 0.0 && fCurDistance < 0.0 ) ) + { + nAdd = 1; + } + } + else if( ( fabs( fX - aCurSegment.GetEnd().X() ) <= 0.0000001 ) && + ( fabs( fY - aCurSegment.GetEnd().Y() ) <= 0.0000001 ) ) + { + const tools::Line aNextSegment( aCurSegment.GetEnd(), rPoly[ static_cast<sal_uInt16>( ( i + 1 ) % nCount ) ] ); + + if( ( fabs( rLine.GetDistance( aNextSegment.GetEnd() ) ) <= 0.0000001 ) && + ( rLine.GetDistance( aCurSegment.GetStart() ) > 0.0 ) ) + { + nAdd = 1; + } + } + else + nAdd = 1; + + if( nAdd ) + { + if (nPCounter == HATCH_MAXPOINTS) + { + SAL_WARN("vcl.gdi", "too many hatch points"); + return; + } + pPtBuffer[ nPCounter++ ] = Point( FRound( fX ), FRound( fY ) ); + } + } + + aCurSegment.SetStart( aCurSegment.GetEnd() ); + } + } + } + + if( nPCounter <= 1 ) + return; + + qsort( pPtBuffer, nPCounter, sizeof( Point ), HatchCmpFnc ); + + if( nPCounter & 1 ) + nPCounter--; + + if( bMtf ) + { + for( tools::Long i = 0; i < nPCounter; i += 2 ) + mpMetaFile->AddAction( new MetaLineAction( pPtBuffer[ i ], pPtBuffer[ i + 1 ] ) ); + } + else + { + for( tools::Long i = 0; i < nPCounter; i += 2 ) + DrawHatchLine_DrawLine(pPtBuffer[i], pPtBuffer[i+1]); + } +} + +void OutputDevice::DrawHatchLine_DrawLine(const Point& rStartPoint, const Point& rEndPoint) +{ + Point aPt1{ImplLogicToDevicePixel(rStartPoint)}, aPt2{ImplLogicToDevicePixel(rEndPoint)}; + mpGraphics->DrawLine(aPt1.X(), aPt1.Y(), aPt2.X(), aPt2.Y(), *this); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/line.cxx b/vcl/source/outdev/line.cxx new file mode 100644 index 0000000000..6bf59455da --- /dev/null +++ b/vcl/source/outdev/line.cxx @@ -0,0 +1,370 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dlinegeometry.hxx> +#include <tools/debug.hxx> +#include <unotools/configmgr.hxx> + +#include <vcl/lineinfo.hxx> +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> + +#include <drawmode.hxx> +#include <salgdi.hxx> + +#include <cassert> +#include <numeric> + +void OutputDevice::SetLineColor() +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaLineColorAction( Color(), false ) ); + + if ( mbLineColor ) + { + mbInitLineColor = true; + mbLineColor = false; + maLineColor = COL_TRANSPARENT; + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetLineColor(); +} + +void OutputDevice::SetLineColor( const Color& rColor ) +{ + + Color aColor = vcl::drawmode::GetLineColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()); + + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaLineColorAction( aColor, true ) ); + + if( aColor.IsTransparent() ) + { + if ( mbLineColor ) + { + mbInitLineColor = true; + mbLineColor = false; + maLineColor = COL_TRANSPARENT; + } + } + else + { + if( maLineColor != aColor ) + { + mbInitLineColor = true; + mbLineColor = true; + maLineColor = aColor; + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetLineColor( COL_ALPHA_OPAQUE ); +} + +void OutputDevice::InitLineColor() +{ + DBG_TESTSOLARMUTEX(); + + if( mbLineColor ) + { + if( RasterOp::N0 == meRasterOp ) + mpGraphics->SetROPLineColor( SalROPColor::N0 ); + else if( RasterOp::N1 == meRasterOp ) + mpGraphics->SetROPLineColor( SalROPColor::N1 ); + else if( RasterOp::Invert == meRasterOp ) + mpGraphics->SetROPLineColor( SalROPColor::Invert ); + else + mpGraphics->SetLineColor( maLineColor ); + } + else + { + mpGraphics->SetLineColor(); + } + + mbInitLineColor = false; +} + +void OutputDevice::DrawLine( const Point& rStartPt, const Point& rEndPt, + const LineInfo& rLineInfo ) +{ + assert(!is_double_buffered_window()); + + if ( rLineInfo.IsDefault() ) + { + DrawLine( rStartPt, rEndPt ); + return; + } + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaLineAction( rStartPt, rEndPt, rLineInfo ) ); + + if ( !IsDeviceOutputNecessary() || !mbLineColor || ( LineStyle::NONE == rLineInfo.GetStyle() ) || ImplIsRecordLayout() ) + return; + + if( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + const Point aStartPt( ImplLogicToDevicePixel( rStartPt ) ); + const Point aEndPt( ImplLogicToDevicePixel( rEndPt ) ); + const LineInfo aInfo( ImplLogicToDevicePixel( rLineInfo ) ); + const bool bDashUsed(LineStyle::Dash == aInfo.GetStyle()); + const bool bLineWidthUsed(aInfo.GetWidth() > 1); + + if ( mbInitLineColor ) + InitLineColor(); + + if(bDashUsed || bLineWidthUsed) + { + basegfx::B2DPolygon aLinePolygon; + aLinePolygon.append(basegfx::B2DPoint(aStartPt.X(), aStartPt.Y())); + aLinePolygon.append(basegfx::B2DPoint(aEndPt.X(), aEndPt.Y())); + + drawLine( basegfx::B2DPolyPolygon(aLinePolygon), aInfo ); + } + else + { + mpGraphics->DrawLine( aStartPt.X(), aStartPt.Y(), aEndPt.X(), aEndPt.Y(), *this ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawLine( rStartPt, rEndPt, rLineInfo ); +} + +void OutputDevice::DrawLine( const Point& rStartPt, const Point& rEndPt ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaLineAction( rStartPt, rEndPt ) ); + + if ( !IsDeviceOutputNecessary() || !mbLineColor || ImplIsRecordLayout() ) + return; + + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + bool bDrawn = false; + + // #i101598# support AA and snap for lines, too + if (RasterOp::OverPaint == GetRasterOp() && IsLineColor()) + { + // at least transform with double precision to device coordinates; this will + // avoid pixel snap of single, appended lines + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation()); + basegfx::B2DPolygon aB2DPolyLine; + + aB2DPolyLine.append(basegfx::B2DPoint(rStartPt.X(), rStartPt.Y())); + aB2DPolyLine.append(basegfx::B2DPoint(rEndPt.X(), rEndPt.Y())); + aB2DPolyLine.transform( aTransform ); + + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + bDrawn = mpGraphics->DrawPolyLine( + basegfx::B2DHomMatrix(), + aB2DPolyLine, + 0.0, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + *this); + } + if(!bDrawn) + { + const Point aStartPt(ImplLogicToDevicePixel(rStartPt)); + const Point aEndPt(ImplLogicToDevicePixel(rEndPt)); + mpGraphics->DrawLine( aStartPt.X(), aStartPt.Y(), aEndPt.X(), aEndPt.Y(), *this ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawLine( rStartPt, rEndPt ); +} + +void OutputDevice::drawLine( basegfx::B2DPolyPolygon aLinePolyPolygon, const LineInfo& rInfo ) +{ + static const bool bFuzzing = utl::ConfigManager::IsFuzzing(); + const bool bTryB2d(RasterOp::OverPaint == GetRasterOp() && IsLineColor()); + basegfx::B2DPolyPolygon aFillPolyPolygon; + const bool bDashUsed(LineStyle::Dash == rInfo.GetStyle()); + const bool bLineWidthUsed(rInfo.GetWidth() > 1); + + if (!bFuzzing && bDashUsed && aLinePolyPolygon.count()) + { + ::std::vector< double > fDotDashArray = rInfo.GetDotDashArray(); + const double fAccumulated(::std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0)); + + if(fAccumulated > 0.0) + { + basegfx::B2DPolyPolygon aResult; + + for(auto const& rPolygon : std::as_const(aLinePolyPolygon)) + { + basegfx::B2DPolyPolygon aLineTarget; + basegfx::utils::applyLineDashing( + rPolygon, + fDotDashArray, + &aLineTarget); + aResult.append(aLineTarget); + } + + aLinePolyPolygon = aResult; + } + } + + if(bLineWidthUsed && aLinePolyPolygon.count()) + { + const double fHalfLineWidth((rInfo.GetWidth() * 0.5) + 0.5); + + if(aLinePolyPolygon.areControlPointsUsed()) + { + // #i110768# When area geometry has to be created, do not + // use the fallback bezier decomposition inside createAreaGeometry, + // but one that is at least as good as ImplSubdivideBezier was. + // There, Polygon::AdaptiveSubdivide was used with default parameter + // 1.0 as quality index. + static int nRecurseLimit = utl::ConfigManager::IsFuzzing() ? 10 : 30; + aLinePolyPolygon = basegfx::utils::adaptiveSubdivideByDistance(aLinePolyPolygon, 1.0, nRecurseLimit); + } + + for(auto const& rPolygon : std::as_const(aLinePolyPolygon)) + { + aFillPolyPolygon.append(basegfx::utils::createAreaGeometry( + rPolygon, + fHalfLineWidth, + rInfo.GetLineJoin(), + rInfo.GetLineCap())); + } + + aLinePolyPolygon.clear(); + } + + GDIMetaFile* pOldMetaFile = mpMetaFile; + mpMetaFile = nullptr; + + if(aLinePolyPolygon.count()) + { + for(auto const& rB2DPolygon : std::as_const(aLinePolyPolygon)) + { + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + bool bDone(false); + + if(bTryB2d) + { + bDone = mpGraphics->DrawPolyLine( + basegfx::B2DHomMatrix(), + rB2DPolygon, + 0.0, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + *this); + } + + if(!bDone) + { + tools::Polygon aPolygon(rB2DPolygon); + mpGraphics->DrawPolyLine( + aPolygon.GetSize(), + aPolygon.GetPointAry(), + *this); + } + } + } + + if(aFillPolyPolygon.count()) + { + const Color aOldLineColor( maLineColor ); + const Color aOldFillColor( maFillColor ); + + SetLineColor(); + InitLineColor(); + SetFillColor( aOldLineColor ); + InitFillColor(); + + bool bDone(false); + + if (bFuzzing) + { + const basegfx::B2DRange aRange(basegfx::utils::getRange(aFillPolyPolygon)); + if (aRange.getMaxX() - aRange.getMinX() > 0x10000000 + || aRange.getMaxY() - aRange.getMinY() > 0x10000000) + { + SAL_WARN("vcl.gdi", "drawLine, skipping suspicious range of: " + << aRange << " for fuzzing performance"); + bDone = true; + } + } + + if (bTryB2d && !bDone) + { + mpGraphics->DrawPolyPolygon( + basegfx::B2DHomMatrix(), + aFillPolyPolygon, + 0.0, + *this); + bDone = true; + } + + if(!bDone) + { + for(auto const& rB2DPolygon : std::as_const(aFillPolyPolygon)) + { + tools::Polygon aPolygon(rB2DPolygon); + + // need to subdivide, mpGraphics->DrawPolygon ignores curves + aPolygon.AdaptiveSubdivide(aPolygon); + mpGraphics->DrawPolygon(aPolygon.GetSize(), aPolygon.GetConstPointAry(), *this); + } + } + + SetFillColor( aOldFillColor ); + SetLineColor( aOldLineColor ); + } + + mpMetaFile = pOldMetaFile; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/map.cxx b/vcl/source/outdev/map.cxx new file mode 100644 index 0000000000..23c68a2385 --- /dev/null +++ b/vcl/source/outdev/map.cxx @@ -0,0 +1,1868 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/bigint.hxx> +#include <tools/debug.hxx> + +#include <vcl/cursor.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> +#include <vcl/wrkwin.hxx> + +#include <ImplOutDevData.hxx> +#include <svdata.hxx> +#include <window.h> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <tools/UnitConversion.hxx> + +static auto setMapRes(ImplMapRes& rMapRes, const o3tl::Length eUnit) +{ + const auto [nNum, nDen] = o3tl::getConversionMulDiv(eUnit, o3tl::Length::in); + rMapRes.mnMapScNumX = rMapRes.mnMapScNumY = nNum; + rMapRes.mnMapScDenomX = rMapRes.mnMapScDenomY = nDen; +}; + +static void ImplCalcMapResolution( const MapMode& rMapMode, + tools::Long nDPIX, tools::Long nDPIY, ImplMapRes& rMapRes ) +{ + switch ( rMapMode.GetMapUnit() ) + { + case MapUnit::MapRelative: + break; + case MapUnit::Map100thMM: + setMapRes(rMapRes, o3tl::Length::mm100); + break; + case MapUnit::Map10thMM: + setMapRes(rMapRes, o3tl::Length::mm10); + break; + case MapUnit::MapMM: + setMapRes(rMapRes, o3tl::Length::mm); + break; + case MapUnit::MapCM: + setMapRes(rMapRes, o3tl::Length::cm); + break; + case MapUnit::Map1000thInch: + setMapRes(rMapRes, o3tl::Length::in1000); + break; + case MapUnit::Map100thInch: + setMapRes(rMapRes, o3tl::Length::in100); + break; + case MapUnit::Map10thInch: + setMapRes(rMapRes, o3tl::Length::in10); + break; + case MapUnit::MapInch: + setMapRes(rMapRes, o3tl::Length::in); + break; + case MapUnit::MapPoint: + setMapRes(rMapRes, o3tl::Length::pt); + break; + case MapUnit::MapTwip: + setMapRes(rMapRes, o3tl::Length::twip); + break; + case MapUnit::MapPixel: + rMapRes.mnMapScNumX = 1; + rMapRes.mnMapScDenomX = nDPIX; + rMapRes.mnMapScNumY = 1; + rMapRes.mnMapScDenomY = nDPIY; + break; + case MapUnit::MapSysFont: + case MapUnit::MapAppFont: + { + ImplSVData* pSVData = ImplGetSVData(); + if ( !pSVData->maGDIData.mnAppFontX ) + { + if (pSVData->maFrameData.mpFirstFrame) + vcl::Window::ImplInitAppFontData(pSVData->maFrameData.mpFirstFrame); + else + { + ScopedVclPtrInstance<WorkWindow> pWin( nullptr, 0 ); + vcl::Window::ImplInitAppFontData( pWin ); + } + } + rMapRes.mnMapScNumX = pSVData->maGDIData.mnAppFontX; + rMapRes.mnMapScDenomX = nDPIX * 40; + rMapRes.mnMapScNumY = pSVData->maGDIData.mnAppFontY; + rMapRes.mnMapScDenomY = nDPIY * 80; + } + break; + default: + OSL_FAIL( "unhandled MapUnit" ); + break; + } + + const Fraction& aScaleX = rMapMode.GetScaleX(); + const Fraction& aScaleY = rMapMode.GetScaleY(); + + // set offset according to MapMode + Point aOrigin = rMapMode.GetOrigin(); + if ( rMapMode.GetMapUnit() != MapUnit::MapRelative ) + { + rMapRes.mnMapOfsX = aOrigin.X(); + rMapRes.mnMapOfsY = aOrigin.Y(); + } + else + { + auto funcCalcOffset = [](const Fraction& rScale, tools::Long& rnMapOffset, tools::Long nOrigin) + { + auto nNumerator = rScale.GetNumerator(); + assert(nNumerator != 0); + + BigInt aX( rnMapOffset ); + aX *= BigInt( rScale.GetDenominator() ); + if ( rnMapOffset >= 0 ) + { + if (nNumerator >= 0) + aX += BigInt(nNumerator / 2); + else + aX -= BigInt((nNumerator + 1) / 2); + } + else + { + if (nNumerator >= 0 ) + aX -= BigInt((nNumerator - 1) / 2); + else + aX += BigInt(nNumerator / 2); + } + aX /= BigInt(nNumerator); + rnMapOffset = static_cast<tools::Long>(aX) + nOrigin; + }; + + funcCalcOffset(aScaleX, rMapRes.mnMapOfsX, aOrigin.X()); + funcCalcOffset(aScaleY, rMapRes.mnMapOfsY, aOrigin.Y()); + } + + // calculate scaling factor according to MapMode + // aTemp? = rMapRes.mnMapSc? * aScale? + Fraction aTempX = Fraction::MakeFraction( rMapRes.mnMapScNumX, + aScaleX.GetNumerator(), + rMapRes.mnMapScDenomX, + aScaleX.GetDenominator() ); + Fraction aTempY = Fraction::MakeFraction( rMapRes.mnMapScNumY, + aScaleY.GetNumerator(), + rMapRes.mnMapScDenomY, + aScaleY.GetDenominator() ); + rMapRes.mnMapScNumX = aTempX.GetNumerator(); + rMapRes.mnMapScDenomX = aTempX.GetDenominator(); + rMapRes.mnMapScNumY = aTempY.GetNumerator(); + rMapRes.mnMapScDenomY = aTempY.GetDenominator(); +} + +// #i75163# +void OutputDevice::ImplInvalidateViewTransform() +{ + if(!mpOutDevData) + return; + + if(mpOutDevData->mpViewTransform) + { + delete mpOutDevData->mpViewTransform; + mpOutDevData->mpViewTransform = nullptr; + } + + if(mpOutDevData->mpInverseViewTransform) + { + delete mpOutDevData->mpInverseViewTransform; + mpOutDevData->mpInverseViewTransform = nullptr; + } +} + +static tools::Long ImplLogicToPixel(tools::Long n, tools::Long nDPI, tools::Long nMapNum, + tools::Long nMapDenom) +{ + assert(nDPI > 0); + assert(nMapDenom != 0); + if constexpr (sizeof(tools::Long) >= 8) + { + assert(nMapNum >= 0); + //detect overflows + assert(nMapNum == 0 + || std::abs(n) < std::numeric_limits<tools::Long>::max() / nMapNum / nDPI); + } + sal_Int64 n64 = n; + n64 *= nMapNum; + n64 *= nDPI; + if (nMapDenom == 1) + n = static_cast<tools::Long>(n64); + else + { + n64 = 2 * n64 / nMapDenom; + if (n64 < 0) + --n64; + else + ++n64; + n = static_cast<tools::Long>(n64 / 2); + } + return n; +} + +static double ImplLogicToSubPixel(tools::Long n, tools::Long nDPI, tools::Long nMapNum, + tools::Long nMapDenom) +{ + assert(nDPI > 0); + assert(nMapDenom != 0); + return static_cast<double>(n) * nMapNum * nDPI / nMapDenom; +} + +static tools::Long ImplSubPixelToLogic(double n, tools::Long nDPI, tools::Long nMapNum, + tools::Long nMapDenom) +{ + assert(nDPI > 0); + assert(nMapNum != 0); + + return std::round(n * nMapDenom / nMapNum / nDPI); +} + +static tools::Long ImplPixelToLogic(tools::Long n, tools::Long nDPI, tools::Long nMapNum, + tools::Long nMapDenom) +{ + assert(nDPI > 0); + if (nMapNum == 0) + return 0; + sal_Int64 nDenom = nDPI; + nDenom *= nMapNum; + + sal_Int64 n64 = n; + n64 *= nMapDenom; + if (nDenom == 1) + n = static_cast<tools::Long>(n64); + else + { + n64 = 2 * n64 / nDenom; + if (n64 < 0) + --n64; + else + ++n64; + n = static_cast<tools::Long>(n64 / 2); + } + return n; +} + +tools::Long OutputDevice::ImplLogicXToDevicePixel( tools::Long nX ) const +{ + if ( !mbMap ) + return nX+mnOutOffX; + + return ImplLogicToPixel( nX + maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX; +} + +tools::Long OutputDevice::ImplLogicYToDevicePixel( tools::Long nY ) const +{ + if ( !mbMap ) + return nY+mnOutOffY; + + return ImplLogicToPixel( nY + maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY; +} + +tools::Long OutputDevice::ImplLogicWidthToDevicePixel( tools::Long nWidth ) const +{ + if ( !mbMap ) + return nWidth; + + return ImplLogicToPixel(nWidth, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX); +} + +tools::Long OutputDevice::ImplLogicHeightToDevicePixel( tools::Long nHeight ) const +{ + if ( !mbMap ) + return nHeight; + + return ImplLogicToPixel(nHeight, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY); +} + +tools::Long OutputDevice::ImplDevicePixelToLogicWidth( tools::Long nWidth ) const +{ + if ( !mbMap ) + return nWidth; + + return ImplPixelToLogic(nWidth, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX); +} + +tools::Long OutputDevice::ImplDevicePixelToLogicHeight( tools::Long nHeight ) const +{ + if ( !mbMap ) + return nHeight; + + return ImplPixelToLogic(nHeight, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY); +} + +Point OutputDevice::ImplLogicToDevicePixel( const Point& rLogicPt ) const +{ + if ( !mbMap ) + return Point( rLogicPt.X()+mnOutOffX, rLogicPt.Y()+mnOutOffY ); + + return Point( ImplLogicToPixel( rLogicPt.X() + maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX, + ImplLogicToPixel( rLogicPt.Y() + maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY ); +} + +Size OutputDevice::ImplLogicToDevicePixel( const Size& rLogicSize ) const +{ + if ( !mbMap ) + return rLogicSize; + + return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ), + ImplLogicToPixel( rLogicSize.Height(), mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) ); +} + +tools::Rectangle OutputDevice::ImplLogicToDevicePixel( const tools::Rectangle& rLogicRect ) const +{ + // tdf#141761 IsEmpty() removed + // Even if rLogicRect.IsEmpty(), transform of the Position contained + // in the Rectangle is necessary. Due to Rectangle::Right() returning + // Left() when IsEmpty(), the code *could* stay unchanged (same for Bottom), + // but: + // The Rectangle constructor used with the four tools::Long values does not + // check for IsEmpty(), so to keep that state correct there are two possibilities: + // (1) Add a test to the Rectangle constructor in question + // (2) Do it handish here + // I have tried (1) first, but test Test::test_rectangle() claims that for + // tools::Rectangle aRect(1, 1, 1, 1); + // tools::Long(1) == aRect.GetWidth() + // tools::Long(0) == aRect.getWidth() + // (remember: this means Left == Right == 1 -> GetWidth => 1, getWidth == 0) + // so indeed the 1's have to go uncommented/unchecked into the data body + // of rectangle. Switching to (2) *is* needed, doing so + tools::Rectangle aRetval; + + if ( !mbMap ) + { + aRetval = tools::Rectangle( + rLogicRect.Left()+mnOutOffX, + rLogicRect.Top()+mnOutOffY, + rLogicRect.IsWidthEmpty() ? 0 : rLogicRect.Right()+mnOutOffX, + rLogicRect.IsHeightEmpty() ? 0 : rLogicRect.Bottom()+mnOutOffY ); + } + else + { + aRetval = tools::Rectangle( + ImplLogicToPixel( rLogicRect.Left()+maMapRes.mnMapOfsX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX, + ImplLogicToPixel( rLogicRect.Top()+maMapRes.mnMapOfsY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY, + rLogicRect.IsWidthEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Right()+maMapRes.mnMapOfsX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX, + rLogicRect.IsHeightEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Bottom()+maMapRes.mnMapOfsY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY ); + } + + if(rLogicRect.IsWidthEmpty()) + aRetval.SetWidthEmpty(); + + if(rLogicRect.IsHeightEmpty()) + aRetval.SetHeightEmpty(); + + return aRetval; +} + +tools::Polygon OutputDevice::ImplLogicToDevicePixel( const tools::Polygon& rLogicPoly ) const +{ + if ( !mbMap && !mnOutOffX && !mnOutOffY ) + return rLogicPoly; + + sal_uInt16 i; + sal_uInt16 nPoints = rLogicPoly.GetSize(); + tools::Polygon aPoly( rLogicPoly ); + + // get pointer to Point-array (copy data) + const Point* pPointAry = aPoly.GetConstPointAry(); + + if ( mbMap ) + { + for ( i = 0; i < nPoints; i++ ) + { + const Point& rPt = pPointAry[i]; + Point aPt(ImplLogicToPixel( rPt.X()+maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX, + ImplLogicToPixel( rPt.Y()+maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY); + aPoly[i] = aPt; + } + } + else + { + for ( i = 0; i < nPoints; i++ ) + { + Point aPt = pPointAry[i]; + aPt.AdjustX(mnOutOffX ); + aPt.AdjustY(mnOutOffY ); + aPoly[i] = aPt; + } + } + + return aPoly; +} + +basegfx::B2DPolygon OutputDevice::ImplLogicToDevicePixel(const basegfx::B2DPolygon& rLogicPoly) const +{ + if (!mbMap && !mnOutOffX && !mnOutOffY) + return rLogicPoly; + + sal_uInt32 nPoints = rLogicPoly.count(); + basegfx::B2DPolygon aPoly(rLogicPoly); + + basegfx::B2DPoint aC1; + basegfx::B2DPoint aC2; + + if (mbMap) + { + for (sal_uInt32 i = 0; i < nPoints; ++i) + { + const basegfx::B2DPoint& rPt = aPoly.getB2DPoint(i); + basegfx::B2DPoint aPt(ImplLogicToPixel( rPt.getX()+maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX, + ImplLogicToPixel( rPt.getY()+maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY); + + const bool bC1 = aPoly.isPrevControlPointUsed(i); + if (bC1) + { + const basegfx::B2DPoint aB2DC1(aPoly.getPrevControlPoint(i)); + + aC1 = basegfx::B2DPoint(ImplLogicToPixel( aB2DC1.getX()+maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX, + ImplLogicToPixel( aB2DC1.getY()+maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY); + } + + const bool bC2 = aPoly.isNextControlPointUsed(i); + if (bC2) + { + const basegfx::B2DPoint aB2DC2(aPoly.getNextControlPoint(i)); + + aC2 = basegfx::B2DPoint(ImplLogicToPixel( aB2DC2.getX()+maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffX+mnOutOffOrigX, + ImplLogicToPixel( aB2DC2.getY()+maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffY+mnOutOffOrigY); + } + + aPoly.setB2DPoint(i, aPt); + + if (bC1) + aPoly.setPrevControlPoint(i, aC1); + + if (bC2) + aPoly.setNextControlPoint(i, aC2); + } + } + else + { + for (sal_uInt32 i = 0; i < nPoints; ++i) + { + const basegfx::B2DPoint& rPt = aPoly.getB2DPoint(i); + basegfx::B2DPoint aPt(rPt.getX() + mnOutOffX, rPt.getY() + mnOutOffY); + + const bool bC1 = aPoly.isPrevControlPointUsed(i); + if (bC1) + { + const basegfx::B2DPoint aB2DC1(aPoly.getPrevControlPoint(i)); + + aC1 = basegfx::B2DPoint(aB2DC1.getX() + mnOutOffX, aB2DC1.getY() + mnOutOffY); + } + + const bool bC2 = aPoly.isNextControlPointUsed(i); + if (bC2) + { + const basegfx::B2DPoint aB2DC2(aPoly.getNextControlPoint(i)); + + aC1 = basegfx::B2DPoint(aB2DC2.getX() + mnOutOffX, aB2DC2.getY() + mnOutOffY); + } + + aPoly.setB2DPoint(i, aPt); + + if (bC1) + aPoly.setPrevControlPoint(i, aC1); + + if (bC2) + aPoly.setNextControlPoint(i, aC2); + } + } + + return aPoly; +} + +tools::PolyPolygon OutputDevice::ImplLogicToDevicePixel( const tools::PolyPolygon& rLogicPolyPoly ) const +{ + if ( !mbMap && !mnOutOffX && !mnOutOffY ) + return rLogicPolyPoly; + + tools::PolyPolygon aPolyPoly( rLogicPolyPoly ); + sal_uInt16 nPoly = aPolyPoly.Count(); + for( sal_uInt16 i = 0; i < nPoly; i++ ) + { + tools::Polygon& rPoly = aPolyPoly[i]; + rPoly = ImplLogicToDevicePixel( rPoly ); + } + return aPolyPoly; +} + +LineInfo OutputDevice::ImplLogicToDevicePixel( const LineInfo& rLineInfo ) const +{ + LineInfo aInfo( rLineInfo ); + + if( aInfo.GetStyle() == LineStyle::Dash ) + { + if( aInfo.GetDotCount() && aInfo.GetDotLen() ) + aInfo.SetDotLen( std::max( ImplLogicWidthToDevicePixel( aInfo.GetDotLen() ), tools::Long(1) ) ); + else + aInfo.SetDotCount( 0 ); + + if( aInfo.GetDashCount() && aInfo.GetDashLen() ) + aInfo.SetDashLen( std::max( ImplLogicWidthToDevicePixel( aInfo.GetDashLen() ), tools::Long(1) ) ); + else + aInfo.SetDashCount( 0 ); + + aInfo.SetDistance( ImplLogicWidthToDevicePixel( aInfo.GetDistance() ) ); + + if( ( !aInfo.GetDashCount() && !aInfo.GetDotCount() ) || !aInfo.GetDistance() ) + aInfo.SetStyle( LineStyle::Solid ); + } + + aInfo.SetWidth( ImplLogicWidthToDevicePixel( aInfo.GetWidth() ) ); + + return aInfo; +} + +tools::Rectangle OutputDevice::ImplDevicePixelToLogic( const tools::Rectangle& rPixelRect ) const +{ + // tdf#141761 see comments above, IsEmpty() removed + tools::Rectangle aRetval; + + if ( !mbMap ) + { + aRetval = tools::Rectangle( + rPixelRect.Left()-mnOutOffX, + rPixelRect.Top()-mnOutOffY, + rPixelRect.IsWidthEmpty() ? 0 : rPixelRect.Right()-mnOutOffX, + rPixelRect.IsHeightEmpty() ? 0 : rPixelRect.Bottom()-mnOutOffY ); + } + else + { + aRetval = tools::Rectangle( + ImplPixelToLogic( rPixelRect.Left()-mnOutOffX-mnOutOffOrigX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )-maMapRes.mnMapOfsX, + ImplPixelToLogic( rPixelRect.Top()-mnOutOffY-mnOutOffOrigY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )-maMapRes.mnMapOfsY, + rPixelRect.IsWidthEmpty() ? 0 : ImplPixelToLogic( rPixelRect.Right()-mnOutOffX-mnOutOffOrigX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )-maMapRes.mnMapOfsX, + rPixelRect.IsHeightEmpty() ? 0 : ImplPixelToLogic( rPixelRect.Bottom()-mnOutOffY-mnOutOffOrigY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )-maMapRes.mnMapOfsY ); + } + + if(rPixelRect.IsWidthEmpty()) + aRetval.SetWidthEmpty(); + + if(rPixelRect.IsHeightEmpty()) + aRetval.SetHeightEmpty(); + + return aRetval; +} + +vcl::Region OutputDevice::ImplPixelToDevicePixel( const vcl::Region& rRegion ) const +{ + if ( !mnOutOffX && !mnOutOffY ) + return rRegion; + + vcl::Region aRegion( rRegion ); + aRegion.Move( mnOutOffX+mnOutOffOrigX, mnOutOffY+mnOutOffOrigY ); + return aRegion; +} + +void OutputDevice::EnableMapMode( bool bEnable ) +{ + mbMap = bEnable; + + if( mpAlphaVDev ) + mpAlphaVDev->EnableMapMode( bEnable ); +} + +void OutputDevice::SetMapMode() +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaMapModeAction( MapMode() ) ); + + if ( mbMap || !maMapMode.IsDefault() ) + { + mbMap = false; + maMapMode = MapMode(); + + // create new objects (clip region are not re-scaled) + mbNewFont = true; + mbInitFont = true; + ImplInitMapModeObjects(); + + // #106426# Adapt logical offset when changing mapmode + mnOutOffLogicX = mnOutOffOrigX; // no mapping -> equal offsets + mnOutOffLogicY = mnOutOffOrigY; + + // #i75163# + ImplInvalidateViewTransform(); + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetMapMode(); +} + +void OutputDevice::SetMapMode( const MapMode& rNewMapMode ) +{ + + bool bRelMap = (rNewMapMode.GetMapUnit() == MapUnit::MapRelative); + + if ( mpMetaFile ) + { + mpMetaFile->AddAction( new MetaMapModeAction( rNewMapMode ) ); + } + + // do nothing if MapMode was not changed + if ( maMapMode == rNewMapMode ) + return; + + if( mpAlphaVDev ) + mpAlphaVDev->SetMapMode( rNewMapMode ); + + // if default MapMode calculate nothing + bool bOldMap = mbMap; + mbMap = !rNewMapMode.IsDefault(); + if ( mbMap ) + { + // if only the origin is converted, do not scale new + if ( (rNewMapMode.GetMapUnit() == maMapMode.GetMapUnit()) && + (rNewMapMode.GetScaleX() == maMapMode.GetScaleX()) && + (rNewMapMode.GetScaleY() == maMapMode.GetScaleY()) && + (bOldMap == mbMap) ) + { + // set offset + Point aOrigin = rNewMapMode.GetOrigin(); + maMapRes.mnMapOfsX = aOrigin.X(); + maMapRes.mnMapOfsY = aOrigin.Y(); + maMapMode = rNewMapMode; + + // #i75163# + ImplInvalidateViewTransform(); + + return; + } + if ( !bOldMap && bRelMap ) + { + maMapRes.mnMapScNumX = 1; + maMapRes.mnMapScNumY = 1; + maMapRes.mnMapScDenomX = mnDPIX; + maMapRes.mnMapScDenomY = mnDPIY; + maMapRes.mnMapOfsX = 0; + maMapRes.mnMapOfsY = 0; + } + + // calculate new MapMode-resolution + ImplCalcMapResolution(rNewMapMode, mnDPIX, mnDPIY, maMapRes); + } + + // set new MapMode + if (bRelMap) + { + maMapMode.SetScaleX(Fraction::MakeFraction( + maMapMode.GetScaleX().GetNumerator(), rNewMapMode.GetScaleX().GetNumerator(), + maMapMode.GetScaleX().GetDenominator(), rNewMapMode.GetScaleX().GetDenominator())); + + maMapMode.SetScaleY(Fraction::MakeFraction( + maMapMode.GetScaleY().GetNumerator(), rNewMapMode.GetScaleY().GetNumerator(), + maMapMode.GetScaleY().GetDenominator(), rNewMapMode.GetScaleY().GetDenominator())); + + maMapMode.SetOrigin(Point(maMapRes.mnMapOfsX, maMapRes.mnMapOfsY)); + } + else + { + maMapMode = rNewMapMode; + } + + // create new objects (clip region are not re-scaled) + mbNewFont = true; + mbInitFont = true; + ImplInitMapModeObjects(); + + // #106426# Adapt logical offset when changing mapmode + mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ); + mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ); + + // #i75163# + ImplInvalidateViewTransform(); +} + +void OutputDevice::SetMetafileMapMode(const MapMode& rNewMapMode, bool bIsRecord) +{ + if (bIsRecord) + SetRelativeMapMode(rNewMapMode); + else + SetMapMode(rNewMapMode); +} + +void OutputDevice::ImplInitMapModeObjects() {} + +void OutputDevice::SetRelativeMapMode( const MapMode& rNewMapMode ) +{ + // do nothing if MapMode did not change + if ( maMapMode == rNewMapMode ) + return; + + MapUnit eOld = maMapMode.GetMapUnit(); + MapUnit eNew = rNewMapMode.GetMapUnit(); + + // a?F = rNewMapMode.GetScale?() / maMapMode.GetScale?() + Fraction aXF = Fraction::MakeFraction( rNewMapMode.GetScaleX().GetNumerator(), + maMapMode.GetScaleX().GetDenominator(), + rNewMapMode.GetScaleX().GetDenominator(), + maMapMode.GetScaleX().GetNumerator() ); + Fraction aYF = Fraction::MakeFraction( rNewMapMode.GetScaleY().GetNumerator(), + maMapMode.GetScaleY().GetDenominator(), + rNewMapMode.GetScaleY().GetDenominator(), + maMapMode.GetScaleY().GetNumerator() ); + + Point aPt( LogicToLogic( Point(), nullptr, &rNewMapMode ) ); + if ( eNew != eOld ) + { + if ( eOld > MapUnit::MapPixel ) + { + SAL_WARN( "vcl.gdi", "Not implemented MapUnit" ); + } + else if ( eNew > MapUnit::MapPixel ) + { + SAL_WARN( "vcl.gdi", "Not implemented MapUnit" ); + } + else + { + const auto eFrom = MapToO3tlLength(eOld, o3tl::Length::in); + const auto eTo = MapToO3tlLength(eNew, o3tl::Length::in); + const auto& [mul, div] = o3tl::getConversionMulDiv(eFrom, eTo); + Fraction aF(div, mul); + + // a?F = a?F * aF + aXF = Fraction::MakeFraction( aXF.GetNumerator(), aF.GetNumerator(), + aXF.GetDenominator(), aF.GetDenominator() ); + aYF = Fraction::MakeFraction( aYF.GetNumerator(), aF.GetNumerator(), + aYF.GetDenominator(), aF.GetDenominator() ); + if ( eOld == MapUnit::MapPixel ) + { + aXF *= Fraction( mnDPIX, 1 ); + aYF *= Fraction( mnDPIY, 1 ); + } + else if ( eNew == MapUnit::MapPixel ) + { + aXF *= Fraction( 1, mnDPIX ); + aYF *= Fraction( 1, mnDPIY ); + } + } + } + + MapMode aNewMapMode( MapUnit::MapRelative, Point( -aPt.X(), -aPt.Y() ), aXF, aYF ); + SetMapMode( aNewMapMode ); + + if ( eNew != eOld ) + maMapMode = rNewMapMode; + + // #106426# Adapt logical offset when changing MapMode + mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ); + mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ); + + if( mpAlphaVDev ) + mpAlphaVDev->SetRelativeMapMode( rNewMapMode ); +} + +// #i75163# +basegfx::B2DHomMatrix OutputDevice::GetViewTransformation() const +{ + if(mbMap && mpOutDevData) + { + if(!mpOutDevData->mpViewTransform) + { + mpOutDevData->mpViewTransform = new basegfx::B2DHomMatrix; + + const double fScaleFactorX(static_cast<double>(mnDPIX) * static_cast<double>(maMapRes.mnMapScNumX) / static_cast<double>(maMapRes.mnMapScDenomX)); + const double fScaleFactorY(static_cast<double>(mnDPIY) * static_cast<double>(maMapRes.mnMapScNumY) / static_cast<double>(maMapRes.mnMapScDenomY)); + const double fZeroPointX((static_cast<double>(maMapRes.mnMapOfsX) * fScaleFactorX) + static_cast<double>(mnOutOffOrigX)); + const double fZeroPointY((static_cast<double>(maMapRes.mnMapOfsY) * fScaleFactorY) + static_cast<double>(mnOutOffOrigY)); + + mpOutDevData->mpViewTransform->set(0, 0, fScaleFactorX); + mpOutDevData->mpViewTransform->set(1, 1, fScaleFactorY); + mpOutDevData->mpViewTransform->set(0, 2, fZeroPointX); + mpOutDevData->mpViewTransform->set(1, 2, fZeroPointY); + } + + return *mpOutDevData->mpViewTransform; + } + else + { + return basegfx::B2DHomMatrix(); + } +} + +// #i75163# +basegfx::B2DHomMatrix OutputDevice::GetInverseViewTransformation() const +{ + if(mbMap && mpOutDevData) + { + if(!mpOutDevData->mpInverseViewTransform) + { + GetViewTransformation(); + mpOutDevData->mpInverseViewTransform = new basegfx::B2DHomMatrix(*mpOutDevData->mpViewTransform); + mpOutDevData->mpInverseViewTransform->invert(); + } + + return *mpOutDevData->mpInverseViewTransform; + } + else + { + return basegfx::B2DHomMatrix(); + } +} + +// #i75163# +basegfx::B2DHomMatrix OutputDevice::GetViewTransformation( const MapMode& rMapMode ) const +{ + // #i82615# + ImplMapRes aMapRes; + ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes); + + basegfx::B2DHomMatrix aTransform; + + const double fScaleFactorX(static_cast<double>(mnDPIX) * static_cast<double>(aMapRes.mnMapScNumX) / static_cast<double>(aMapRes.mnMapScDenomX)); + const double fScaleFactorY(static_cast<double>(mnDPIY) * static_cast<double>(aMapRes.mnMapScNumY) / static_cast<double>(aMapRes.mnMapScDenomY)); + const double fZeroPointX((static_cast<double>(aMapRes.mnMapOfsX) * fScaleFactorX) + static_cast<double>(mnOutOffOrigX)); + const double fZeroPointY((static_cast<double>(aMapRes.mnMapOfsY) * fScaleFactorY) + static_cast<double>(mnOutOffOrigY)); + + aTransform.set(0, 0, fScaleFactorX); + aTransform.set(1, 1, fScaleFactorY); + aTransform.set(0, 2, fZeroPointX); + aTransform.set(1, 2, fZeroPointY); + + return aTransform; +} + +// #i75163# +basegfx::B2DHomMatrix OutputDevice::GetInverseViewTransformation( const MapMode& rMapMode ) const +{ + basegfx::B2DHomMatrix aMatrix( GetViewTransformation( rMapMode ) ); + aMatrix.invert(); + return aMatrix; +} + +basegfx::B2DHomMatrix OutputDevice::ImplGetDeviceTransformation() const +{ + basegfx::B2DHomMatrix aTransformation = GetViewTransformation(); + // TODO: is it worth to cache the transformed result? + if( mnOutOffX || mnOutOffY ) + aTransformation.translate( mnOutOffX, mnOutOffY ); + return aTransformation; +} + +Point OutputDevice::LogicToPixel( const Point& rLogicPt ) const +{ + + if ( !mbMap ) + return rLogicPt; + + return Point( ImplLogicToPixel( rLogicPt.X() + maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffOrigX, + ImplLogicToPixel( rLogicPt.Y() + maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffOrigY ); +} + +Size OutputDevice::LogicToPixel( const Size& rLogicSize ) const +{ + + if ( !mbMap ) + return rLogicSize; + + return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ), + ImplLogicToPixel( rLogicSize.Height(), mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) ); +} + +tools::Rectangle OutputDevice::LogicToPixel( const tools::Rectangle& rLogicRect ) const +{ + // tdf#141761 see comments above, IsEmpty() removed + if ( !mbMap ) + return rLogicRect; + + tools::Rectangle aRetval( + ImplLogicToPixel( rLogicRect.Left() + maMapRes.mnMapOfsX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffOrigX, + ImplLogicToPixel( rLogicRect.Top() + maMapRes.mnMapOfsY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffOrigY, + rLogicRect.IsWidthEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Right() + maMapRes.mnMapOfsX, mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffOrigX, + rLogicRect.IsHeightEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Bottom() + maMapRes.mnMapOfsY, mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffOrigY ); + + if(rLogicRect.IsWidthEmpty()) + aRetval.SetWidthEmpty(); + + if(rLogicRect.IsHeightEmpty()) + aRetval.SetHeightEmpty(); + + return aRetval; +} + +tools::Polygon OutputDevice::LogicToPixel( const tools::Polygon& rLogicPoly ) const +{ + + if ( !mbMap ) + return rLogicPoly; + + sal_uInt16 i; + sal_uInt16 nPoints = rLogicPoly.GetSize(); + tools::Polygon aPoly( rLogicPoly ); + + // get pointer to Point-array (copy data) + const Point* pPointAry = aPoly.GetConstPointAry(); + + for ( i = 0; i < nPoints; i++ ) + { + const Point* pPt = &(pPointAry[i]); + Point aPt; + aPt.setX( ImplLogicToPixel( pPt->X() + maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX )+mnOutOffOrigX ); + aPt.setY( ImplLogicToPixel( pPt->Y() + maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY )+mnOutOffOrigY ); + aPoly[i] = aPt; + } + + return aPoly; +} + +tools::PolyPolygon OutputDevice::LogicToPixel( const tools::PolyPolygon& rLogicPolyPoly ) const +{ + + if ( !mbMap ) + return rLogicPolyPoly; + + tools::PolyPolygon aPolyPoly( rLogicPolyPoly ); + sal_uInt16 nPoly = aPolyPoly.Count(); + for( sal_uInt16 i = 0; i < nPoly; i++ ) + { + tools::Polygon& rPoly = aPolyPoly[i]; + rPoly = LogicToPixel( rPoly ); + } + return aPolyPoly; +} + +basegfx::B2DPolyPolygon OutputDevice::LogicToPixel( const basegfx::B2DPolyPolygon& rLogicPolyPoly ) const +{ + basegfx::B2DPolyPolygon aTransformedPoly = rLogicPolyPoly; + const basegfx::B2DHomMatrix& rTransformationMatrix = GetViewTransformation(); + aTransformedPoly.transform( rTransformationMatrix ); + return aTransformedPoly; +} + +vcl::Region OutputDevice::LogicToPixel( const vcl::Region& rLogicRegion ) const +{ + + if(!mbMap || rLogicRegion.IsNull() || rLogicRegion.IsEmpty()) + { + return rLogicRegion; + } + + vcl::Region aRegion; + + if(rLogicRegion.getB2DPolyPolygon()) + { + aRegion = vcl::Region(LogicToPixel(*rLogicRegion.getB2DPolyPolygon())); + } + else if(rLogicRegion.getPolyPolygon()) + { + aRegion = vcl::Region(LogicToPixel(*rLogicRegion.getPolyPolygon())); + } + else if(rLogicRegion.getRegionBand()) + { + RectangleVector aRectangles; + rLogicRegion.GetRegionRectangles(aRectangles); + const RectangleVector& rRectangles(aRectangles); // needed to make the '!=' work + + // make reverse run to fill new region bottom-up, this will speed it up due to the used data structuring + for(RectangleVector::const_reverse_iterator aRectIter(rRectangles.rbegin()); aRectIter != rRectangles.rend(); ++aRectIter) + { + aRegion.Union(LogicToPixel(*aRectIter)); + } + } + + return aRegion; +} + +Point OutputDevice::LogicToPixel( const Point& rLogicPt, + const MapMode& rMapMode ) const +{ + + if ( rMapMode.IsDefault() ) + return rLogicPt; + + // convert MapMode resolution and convert + ImplMapRes aMapRes; + ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes); + + return Point( ImplLogicToPixel( rLogicPt.X() + aMapRes.mnMapOfsX, mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX )+mnOutOffOrigX, + ImplLogicToPixel( rLogicPt.Y() + aMapRes.mnMapOfsY, mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY )+mnOutOffOrigY ); +} + +Size OutputDevice::LogicToPixel( const Size& rLogicSize, + const MapMode& rMapMode ) const +{ + + if ( rMapMode.IsDefault() ) + return rLogicSize; + + // convert MapMode resolution and convert + ImplMapRes aMapRes; + ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes); + + return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ), + ImplLogicToPixel( rLogicSize.Height(), mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) ); +} + +tools::Rectangle OutputDevice::LogicToPixel( const tools::Rectangle& rLogicRect, + const MapMode& rMapMode ) const +{ + // tdf#141761 see comments above, IsEmpty() removed + if ( rMapMode.IsDefault() ) + return rLogicRect; + + // convert MapMode resolution and convert + ImplMapRes aMapRes; + ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes); + + tools::Rectangle aRetval( + ImplLogicToPixel( rLogicRect.Left() + aMapRes.mnMapOfsX, mnDPIX, aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX )+mnOutOffOrigX, + ImplLogicToPixel( rLogicRect.Top() + aMapRes.mnMapOfsY, mnDPIY, aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY )+mnOutOffOrigY, + rLogicRect.IsWidthEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Right() + aMapRes.mnMapOfsX, mnDPIX, aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX )+mnOutOffOrigX, + rLogicRect.IsHeightEmpty() ? 0 : ImplLogicToPixel( rLogicRect.Bottom() + aMapRes.mnMapOfsY, mnDPIY, aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY )+mnOutOffOrigY ); + + if(rLogicRect.IsWidthEmpty()) + aRetval.SetWidthEmpty(); + + if(rLogicRect.IsHeightEmpty()) + aRetval.SetHeightEmpty(); + + return aRetval; +} + +tools::Polygon OutputDevice::LogicToPixel( const tools::Polygon& rLogicPoly, + const MapMode& rMapMode ) const +{ + + if ( rMapMode.IsDefault() ) + return rLogicPoly; + + // convert MapMode resolution and convert + ImplMapRes aMapRes; + ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes); + + sal_uInt16 i; + sal_uInt16 nPoints = rLogicPoly.GetSize(); + tools::Polygon aPoly( rLogicPoly ); + + // get pointer to Point-array (copy data) + const Point* pPointAry = aPoly.GetConstPointAry(); + + for ( i = 0; i < nPoints; i++ ) + { + const Point* pPt = &(pPointAry[i]); + Point aPt; + aPt.setX( ImplLogicToPixel( pPt->X() + aMapRes.mnMapOfsX, mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX )+mnOutOffOrigX ); + aPt.setY( ImplLogicToPixel( pPt->Y() + aMapRes.mnMapOfsY, mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY )+mnOutOffOrigY ); + aPoly[i] = aPt; + } + + return aPoly; +} + +basegfx::B2DPolyPolygon OutputDevice::LogicToPixel( const basegfx::B2DPolyPolygon& rLogicPolyPoly, + const MapMode& rMapMode ) const +{ + basegfx::B2DPolyPolygon aTransformedPoly = rLogicPolyPoly; + const basegfx::B2DHomMatrix& rTransformationMatrix = GetViewTransformation( rMapMode ); + aTransformedPoly.transform( rTransformationMatrix ); + return aTransformedPoly; +} + +Point OutputDevice::PixelToLogic( const Point& rDevicePt ) const +{ + + if ( !mbMap ) + return rDevicePt; + + return Point( ImplPixelToLogic( rDevicePt.X(), mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ) - maMapRes.mnMapOfsX - mnOutOffLogicX, + ImplPixelToLogic( rDevicePt.Y(), mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) - maMapRes.mnMapOfsY - mnOutOffLogicY ); +} + +Point OutputDevice::SubPixelToLogic(const basegfx::B2DPoint& rDevicePt) const +{ + if (!mbMap) + { + assert(floor(rDevicePt.getX() == rDevicePt.getX()) && floor(rDevicePt.getY() == rDevicePt.getY())); + return Point(rDevicePt.getX(), rDevicePt.getY()); + } + + return Point(ImplSubPixelToLogic(rDevicePt.getX(), mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX) - maMapRes.mnMapOfsX - mnOutOffLogicX, + ImplSubPixelToLogic(rDevicePt.getY(), mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY) - maMapRes.mnMapOfsY - mnOutOffLogicY); +} + +Size OutputDevice::PixelToLogic( const Size& rDeviceSize ) const +{ + + if ( !mbMap ) + return rDeviceSize; + + return Size( ImplPixelToLogic( rDeviceSize.Width(), mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ), + ImplPixelToLogic( rDeviceSize.Height(), mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) ); +} + +tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect ) const +{ + // tdf#141761 see comments above, IsEmpty() removed + if ( !mbMap ) + return rDeviceRect; + + tools::Rectangle aRetval( + ImplPixelToLogic( rDeviceRect.Left(), mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ) - maMapRes.mnMapOfsX - mnOutOffLogicX, + ImplPixelToLogic( rDeviceRect.Top(), mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) - maMapRes.mnMapOfsY - mnOutOffLogicY, + rDeviceRect.IsWidthEmpty() ? 0 : ImplPixelToLogic( rDeviceRect.Right(), mnDPIX, maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ) - maMapRes.mnMapOfsX - mnOutOffLogicX, + rDeviceRect.IsHeightEmpty() ? 0 : ImplPixelToLogic( rDeviceRect.Bottom(), mnDPIY, maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) - maMapRes.mnMapOfsY - mnOutOffLogicY ); + + if(rDeviceRect.IsWidthEmpty()) + aRetval.SetWidthEmpty(); + + if(rDeviceRect.IsHeightEmpty()) + aRetval.SetHeightEmpty(); + + return aRetval; +} + +tools::Polygon OutputDevice::PixelToLogic( const tools::Polygon& rDevicePoly ) const +{ + + if ( !mbMap ) + return rDevicePoly; + + sal_uInt16 i; + sal_uInt16 nPoints = rDevicePoly.GetSize(); + tools::Polygon aPoly( rDevicePoly ); + + // get pointer to Point-array (copy data) + const Point* pPointAry = aPoly.GetConstPointAry(); + + for ( i = 0; i < nPoints; i++ ) + { + const Point* pPt = &(pPointAry[i]); + Point aPt; + aPt.setX( ImplPixelToLogic( pPt->X(), mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ) - maMapRes.mnMapOfsX - mnOutOffLogicX ); + aPt.setY( ImplPixelToLogic( pPt->Y(), mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ) - maMapRes.mnMapOfsY - mnOutOffLogicY ); + aPoly[i] = aPt; + } + + return aPoly; +} + +tools::PolyPolygon OutputDevice::PixelToLogic( const tools::PolyPolygon& rDevicePolyPoly ) const +{ + + if ( !mbMap ) + return rDevicePolyPoly; + + tools::PolyPolygon aPolyPoly( rDevicePolyPoly ); + sal_uInt16 nPoly = aPolyPoly.Count(); + for( sal_uInt16 i = 0; i < nPoly; i++ ) + { + tools::Polygon& rPoly = aPolyPoly[i]; + rPoly = PixelToLogic( rPoly ); + } + return aPolyPoly; +} + +basegfx::B2DPolyPolygon OutputDevice::PixelToLogic( const basegfx::B2DPolyPolygon& rPixelPolyPoly ) const +{ + basegfx::B2DPolyPolygon aTransformedPoly = rPixelPolyPoly; + const basegfx::B2DHomMatrix& rTransformationMatrix = GetInverseViewTransformation(); + aTransformedPoly.transform( rTransformationMatrix ); + return aTransformedPoly; +} + +vcl::Region OutputDevice::PixelToLogic( const vcl::Region& rDeviceRegion ) const +{ + + if(!mbMap || rDeviceRegion.IsNull() || rDeviceRegion.IsEmpty()) + { + return rDeviceRegion; + } + + vcl::Region aRegion; + + if(rDeviceRegion.getB2DPolyPolygon()) + { + aRegion = vcl::Region(PixelToLogic(*rDeviceRegion.getB2DPolyPolygon())); + } + else if(rDeviceRegion.getPolyPolygon()) + { + aRegion = vcl::Region(PixelToLogic(*rDeviceRegion.getPolyPolygon())); + } + else if(rDeviceRegion.getRegionBand()) + { + RectangleVector aRectangles; + rDeviceRegion.GetRegionRectangles(aRectangles); + const RectangleVector& rRectangles(aRectangles); // needed to make the '!=' work + + // make reverse run to fill new region bottom-up, this will speed it up due to the used data structuring + for(RectangleVector::const_reverse_iterator aRectIter(rRectangles.rbegin()); aRectIter != rRectangles.rend(); ++aRectIter) + { + aRegion.Union(PixelToLogic(*aRectIter)); + } + } + + return aRegion; +} + +Point OutputDevice::PixelToLogic( const Point& rDevicePt, + const MapMode& rMapMode ) const +{ + + // calculate nothing if default-MapMode + if ( rMapMode.IsDefault() ) + return rDevicePt; + + // calculate MapMode-resolution and convert + ImplMapRes aMapRes; + ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes); + + return Point( ImplPixelToLogic( rDevicePt.X(), mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ) - aMapRes.mnMapOfsX - mnOutOffLogicX, + ImplPixelToLogic( rDevicePt.Y(), mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) - aMapRes.mnMapOfsY - mnOutOffLogicY ); +} + +Size OutputDevice::PixelToLogic( const Size& rDeviceSize, + const MapMode& rMapMode ) const +{ + + // calculate nothing if default-MapMode + if ( rMapMode.IsDefault() ) + return rDeviceSize; + + // calculate MapMode-resolution and convert + ImplMapRes aMapRes; + ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes); + + return Size( ImplPixelToLogic( rDeviceSize.Width(), mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ), + ImplPixelToLogic( rDeviceSize.Height(), mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) ); +} + +tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect, + const MapMode& rMapMode ) const +{ + // calculate nothing if default-MapMode + // tdf#141761 see comments above, IsEmpty() removed + if ( rMapMode.IsDefault() ) + return rDeviceRect; + + // calculate MapMode-resolution and convert + ImplMapRes aMapRes; + ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes); + + tools::Rectangle aRetval( + ImplPixelToLogic( rDeviceRect.Left(), mnDPIX, aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ) - aMapRes.mnMapOfsX - mnOutOffLogicX, + ImplPixelToLogic( rDeviceRect.Top(), mnDPIY, aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) - aMapRes.mnMapOfsY - mnOutOffLogicY, + rDeviceRect.IsWidthEmpty() ? 0 : ImplPixelToLogic( rDeviceRect.Right(), mnDPIX, aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ) - aMapRes.mnMapOfsX - mnOutOffLogicX, + rDeviceRect.IsHeightEmpty() ? 0 : ImplPixelToLogic( rDeviceRect.Bottom(), mnDPIY, aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) - aMapRes.mnMapOfsY - mnOutOffLogicY ); + + if(rDeviceRect.IsWidthEmpty()) + aRetval.SetWidthEmpty(); + + if(rDeviceRect.IsHeightEmpty()) + aRetval.SetHeightEmpty(); + + return aRetval; +} + +tools::Polygon OutputDevice::PixelToLogic( const tools::Polygon& rDevicePoly, + const MapMode& rMapMode ) const +{ + + // calculate nothing if default-MapMode + if ( rMapMode.IsDefault() ) + return rDevicePoly; + + // calculate MapMode-resolution and convert + ImplMapRes aMapRes; + ImplCalcMapResolution(rMapMode, mnDPIX, mnDPIY, aMapRes); + + sal_uInt16 i; + sal_uInt16 nPoints = rDevicePoly.GetSize(); + tools::Polygon aPoly( rDevicePoly ); + + // get pointer to Point-array (copy data) + const Point* pPointAry = aPoly.GetConstPointAry(); + + for ( i = 0; i < nPoints; i++ ) + { + const Point* pPt = &(pPointAry[i]); + Point aPt; + aPt.setX( ImplPixelToLogic( pPt->X(), mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX ) - aMapRes.mnMapOfsX - mnOutOffLogicX ); + aPt.setY( ImplPixelToLogic( pPt->Y(), mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY ) - aMapRes.mnMapOfsY - mnOutOffLogicY ); + aPoly[i] = aPt; + } + + return aPoly; +} + +basegfx::B2DPolygon OutputDevice::PixelToLogic( const basegfx::B2DPolygon& rPixelPoly, + const MapMode& rMapMode ) const +{ + basegfx::B2DPolygon aTransformedPoly = rPixelPoly; + const basegfx::B2DHomMatrix& rTransformationMatrix = GetInverseViewTransformation( rMapMode ); + aTransformedPoly.transform( rTransformationMatrix ); + return aTransformedPoly; +} + +basegfx::B2DPolyPolygon OutputDevice::PixelToLogic( const basegfx::B2DPolyPolygon& rPixelPolyPoly, + const MapMode& rMapMode ) const +{ + basegfx::B2DPolyPolygon aTransformedPoly = rPixelPolyPoly; + const basegfx::B2DHomMatrix& rTransformationMatrix = GetInverseViewTransformation( rMapMode ); + aTransformedPoly.transform( rTransformationMatrix ); + return aTransformedPoly; +} + +#define ENTER1( rSource, pMapModeSource, pMapModeDest ) \ + if ( !pMapModeSource ) \ + pMapModeSource = &maMapMode; \ + if ( !pMapModeDest ) \ + pMapModeDest = &maMapMode; \ + if ( *pMapModeSource == *pMapModeDest ) \ + return rSource; \ + \ + ImplMapRes aMapResSource; \ + ImplMapRes aMapResDest; \ + \ + if ( !mbMap || pMapModeSource != &maMapMode ) \ + { \ + if ( pMapModeSource->GetMapUnit() == MapUnit::MapRelative ) \ + aMapResSource = maMapRes; \ + ImplCalcMapResolution( *pMapModeSource, \ + mnDPIX, mnDPIY, aMapResSource ); \ + } \ + else \ + aMapResSource = maMapRes; \ + if ( !mbMap || pMapModeDest != &maMapMode ) \ + { \ + if ( pMapModeDest->GetMapUnit() == MapUnit::MapRelative ) \ + aMapResDest = maMapRes; \ + ImplCalcMapResolution( *pMapModeDest, \ + mnDPIX, mnDPIY, aMapResDest ); \ + } \ + else \ + aMapResDest = maMapRes + +static void verifyUnitSourceDest( MapUnit eUnitSource, MapUnit eUnitDest ) +{ + DBG_ASSERT( eUnitSource != MapUnit::MapSysFont + && eUnitSource != MapUnit::MapAppFont + && eUnitSource != MapUnit::MapRelative, + "Source MapUnit is not permitted" ); + DBG_ASSERT( eUnitDest != MapUnit::MapSysFont + && eUnitDest != MapUnit::MapAppFont + && eUnitDest != MapUnit::MapRelative, + "Destination MapUnit is not permitted" ); +} + +namespace +{ +auto getCorrectedUnit(MapUnit eMapSrc, MapUnit eMapDst) +{ + o3tl::Length eSrc = o3tl::Length::invalid; + o3tl::Length eDst = o3tl::Length::invalid; + if (eMapSrc > MapUnit::MapPixel) + SAL_WARN("vcl.gdi", "Invalid source map unit"); + else if (eMapDst > MapUnit::MapPixel) + SAL_WARN("vcl.gdi", "Invalid destination map unit"); + else if (eMapSrc != eMapDst) + { + // Here 72 PPI is assumed for MapPixel + eSrc = MapToO3tlLength(eMapSrc, o3tl::Length::pt); + eDst = MapToO3tlLength(eMapDst, o3tl::Length::pt); + } + return std::make_pair(eSrc, eDst); +} + +std::pair<ImplMapRes, ImplMapRes> ENTER4(const MapMode& rMMSource, const MapMode& rMMDest) +{ + std::pair<ImplMapRes, ImplMapRes> result; + ImplCalcMapResolution(rMMSource, 72, 72, result.first); + ImplCalcMapResolution(rMMDest, 72, 72, result.second); + return result; +} +} + +// return (n1 * n2 * n3) / (n4 * n5) +static tools::Long fn5( const tools::Long n1, + const tools::Long n2, + const tools::Long n3, + const tools::Long n4, + const tools::Long n5 ) +{ + if ( n1 == 0 || n2 == 0 || n3 == 0 || n4 == 0 || n5 == 0 ) + return 0; + if (std::numeric_limits<tools::Long>::max() / std::abs(n2) < std::abs(n3)) + { + // a6 is skipped + BigInt a7 = n2; + a7 *= n3; + a7 *= n1; + + if (std::numeric_limits<tools::Long>::max() / std::abs(n4) < std::abs(n5)) + { + BigInt a8 = n4; + a8 *= n5; + + BigInt a9 = a8; + a9 /= 2; + if ( a7.IsNeg() ) + a7 -= a9; + else + a7 += a9; + + a7 /= a8; + } // of if + else + { + tools::Long n8 = n4 * n5; + + if ( a7.IsNeg() ) + a7 -= n8 / 2; + else + a7 += n8 / 2; + + a7 /= n8; + } // of else + return static_cast<tools::Long>(a7); + } // of if + else + { + tools::Long n6 = n2 * n3; + + if (std::numeric_limits<tools::Long>::max() / std::abs(n1) < std::abs(n6)) + { + BigInt a7 = n1; + a7 *= n6; + + if (std::numeric_limits<tools::Long>::max() / std::abs(n4) < std::abs(n5)) + { + BigInt a8 = n4; + a8 *= n5; + + BigInt a9 = a8; + a9 /= 2; + if ( a7.IsNeg() ) + a7 -= a9; + else + a7 += a9; + + a7 /= a8; + } // of if + else + { + tools::Long n8 = n4 * n5; + + if ( a7.IsNeg() ) + a7 -= n8 / 2; + else + a7 += n8 / 2; + + a7 /= n8; + } // of else + return static_cast<tools::Long>(a7); + } // of if + else + { + tools::Long n7 = n1 * n6; + + if (std::numeric_limits<tools::Long>::max() / std::abs(n4) < std::abs(n5)) + { + BigInt a7 = n7; + BigInt a8 = n4; + a8 *= n5; + + BigInt a9 = a8; + a9 /= 2; + if ( a7.IsNeg() ) + a7 -= a9; + else + a7 += a9; + + a7 /= a8; + return static_cast<tools::Long>(a7); + } // of if + else + { + const tools::Long n8 = n4 * n5; + const tools::Long n8_2 = n8 / 2; + + if( n7 < 0 ) + { + if ((n7 - std::numeric_limits<tools::Long>::min()) >= n8_2) + n7 -= n8_2; + } + else if ((std::numeric_limits<tools::Long>::max() - n7) >= n8_2) + n7 += n8_2; + + return n7 / n8; + } // of else + } // of else + } // of else +} + +static tools::Long fn3(const tools::Long n1, const o3tl::Length eFrom, const o3tl::Length eTo) +{ + if (n1 == 0 || eFrom == o3tl::Length::invalid || eTo == o3tl::Length::invalid) + return 0; + bool bOverflow; + const auto nResult = o3tl::convert(n1, eFrom, eTo, bOverflow); + if (bOverflow) + { + const auto& [n2, n3] = o3tl::getConversionMulDiv(eFrom, eTo); + BigInt a4 = n1; + a4 *= n2; + + if ( a4.IsNeg() ) + a4 -= n3 / 2; + else + a4 += n3 / 2; + + a4 /= n3; + return static_cast<tools::Long>(a4); + } // of if + else + return nResult; +} + +Point OutputDevice::LogicToLogic( const Point& rPtSource, + const MapMode* pMapModeSource, + const MapMode* pMapModeDest ) const +{ + ENTER1( rPtSource, pMapModeSource, pMapModeDest ); + + return Point( fn5( rPtSource.X() + aMapResSource.mnMapOfsX, + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) - + aMapResDest.mnMapOfsX, + fn5( rPtSource.Y() + aMapResSource.mnMapOfsY, + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) - + aMapResDest.mnMapOfsY ); +} + +Size OutputDevice::LogicToLogic( const Size& rSzSource, + const MapMode* pMapModeSource, + const MapMode* pMapModeDest ) const +{ + ENTER1( rSzSource, pMapModeSource, pMapModeDest ); + + return Size( fn5( rSzSource.Width(), + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ), + fn5( rSzSource.Height(), + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) ); +} + +tools::Rectangle OutputDevice::LogicToLogic( const tools::Rectangle& rRectSource, + const MapMode* pMapModeSource, + const MapMode* pMapModeDest ) const +{ + ENTER1( rRectSource, pMapModeSource, pMapModeDest ); + + return tools::Rectangle( fn5( rRectSource.Left() + aMapResSource.mnMapOfsX, + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) - + aMapResDest.mnMapOfsX, + fn5( rRectSource.Top() + aMapResSource.mnMapOfsY, + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) - + aMapResDest.mnMapOfsY, + fn5( rRectSource.Right() + aMapResSource.mnMapOfsX, + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) - + aMapResDest.mnMapOfsX, + fn5( rRectSource.Bottom() + aMapResSource.mnMapOfsY, + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) - + aMapResDest.mnMapOfsY ); +} + +Point OutputDevice::LogicToLogic( const Point& rPtSource, + const MapMode& rMapModeSource, + const MapMode& rMapModeDest ) +{ + if ( rMapModeSource == rMapModeDest ) + return rPtSource; + + MapUnit eUnitSource = rMapModeSource.GetMapUnit(); + MapUnit eUnitDest = rMapModeDest.GetMapUnit(); + verifyUnitSourceDest( eUnitSource, eUnitDest ); + + if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple()) + { + const auto& [eFrom, eTo] = getCorrectedUnit(eUnitSource, eUnitDest); + return Point(fn3(rPtSource.X(), eFrom, eTo), fn3(rPtSource.Y(), eFrom, eTo)); + } + else + { + const auto& [aMapResSource, aMapResDest] = ENTER4( rMapModeSource, rMapModeDest ); + + return Point( fn5( rPtSource.X() + aMapResSource.mnMapOfsX, + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) - + aMapResDest.mnMapOfsX, + fn5( rPtSource.Y() + aMapResSource.mnMapOfsY, + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) - + aMapResDest.mnMapOfsY ); + } +} + +Size OutputDevice::LogicToLogic( const Size& rSzSource, + const MapMode& rMapModeSource, + const MapMode& rMapModeDest ) +{ + if ( rMapModeSource == rMapModeDest ) + return rSzSource; + + MapUnit eUnitSource = rMapModeSource.GetMapUnit(); + MapUnit eUnitDest = rMapModeDest.GetMapUnit(); + verifyUnitSourceDest( eUnitSource, eUnitDest ); + + if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple()) + { + const auto& [eFrom, eTo] = getCorrectedUnit(eUnitSource, eUnitDest); + return Size(fn3(rSzSource.Width(), eFrom, eTo), fn3(rSzSource.Height(), eFrom, eTo)); + } + else + { + const auto& [aMapResSource, aMapResDest] = ENTER4( rMapModeSource, rMapModeDest ); + + return Size( fn5( rSzSource.Width(), + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ), + fn5( rSzSource.Height(), + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) ); + } +} + +basegfx::B2DPolygon OutputDevice::LogicToLogic( const basegfx::B2DPolygon& rPolySource, + const MapMode& rMapModeSource, + const MapMode& rMapModeDest ) +{ + if(rMapModeSource == rMapModeDest) + { + return rPolySource; + } + + const basegfx::B2DHomMatrix aTransform(LogicToLogic(rMapModeSource, rMapModeDest)); + basegfx::B2DPolygon aPoly(rPolySource); + + aPoly.transform(aTransform); + return aPoly; +} + +basegfx::B2DHomMatrix OutputDevice::LogicToLogic(const MapMode& rMapModeSource, const MapMode& rMapModeDest) +{ + basegfx::B2DHomMatrix aTransform; + + if(rMapModeSource == rMapModeDest) + { + return aTransform; + } + + MapUnit eUnitSource = rMapModeSource.GetMapUnit(); + MapUnit eUnitDest = rMapModeDest.GetMapUnit(); + verifyUnitSourceDest(eUnitSource, eUnitDest); + + if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple()) + { + const auto& [eFrom, eTo] = getCorrectedUnit(eUnitSource, eUnitDest); + const double fScaleFactor(eFrom == o3tl::Length::invalid || eTo == o3tl::Length::invalid + ? std::numeric_limits<double>::quiet_NaN() + : o3tl::convert(1.0, eFrom, eTo)); + aTransform.set(0, 0, fScaleFactor); + aTransform.set(1, 1, fScaleFactor); + } + else + { + const auto& [aMapResSource, aMapResDest] = ENTER4(rMapModeSource, rMapModeDest); + + const double fScaleFactorX((double(aMapResSource.mnMapScNumX) * double(aMapResDest.mnMapScDenomX)) / (double(aMapResSource.mnMapScDenomX) * double(aMapResDest.mnMapScNumX))); + const double fScaleFactorY((double(aMapResSource.mnMapScNumY) * double(aMapResDest.mnMapScDenomY)) / (double(aMapResSource.mnMapScDenomY) * double(aMapResDest.mnMapScNumY))); + const double fZeroPointX(double(aMapResSource.mnMapOfsX) * fScaleFactorX - double(aMapResDest.mnMapOfsX)); + const double fZeroPointY(double(aMapResSource.mnMapOfsY) * fScaleFactorY - double(aMapResDest.mnMapOfsY)); + + aTransform.set(0, 0, fScaleFactorX); + aTransform.set(1, 1, fScaleFactorY); + aTransform.set(0, 2, fZeroPointX); + aTransform.set(1, 2, fZeroPointY); + } + + return aTransform; +} + +tools::Rectangle OutputDevice::LogicToLogic( const tools::Rectangle& rRectSource, + const MapMode& rMapModeSource, + const MapMode& rMapModeDest ) +{ + if ( rMapModeSource == rMapModeDest ) + return rRectSource; + + MapUnit eUnitSource = rMapModeSource.GetMapUnit(); + MapUnit eUnitDest = rMapModeDest.GetMapUnit(); + verifyUnitSourceDest( eUnitSource, eUnitDest ); + + tools::Rectangle aRetval; + + if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple()) + { + const auto& [eFrom, eTo] = getCorrectedUnit(eUnitSource, eUnitDest); + + auto left = fn3(rRectSource.Left(), eFrom, eTo); + auto top = fn3(rRectSource.Top(), eFrom, eTo); + + // tdf#141761 see comments above, IsEmpty() removed + auto right = rRectSource.IsWidthEmpty() ? 0 : fn3(rRectSource.Right(), eFrom, eTo); + auto bottom = rRectSource.IsHeightEmpty() ? 0 : fn3(rRectSource.Bottom(), eFrom, eTo); + + aRetval = tools::Rectangle(left, top, right, bottom); + } + else + { + const auto& [aMapResSource, aMapResDest] = ENTER4( rMapModeSource, rMapModeDest ); + + auto left = fn5( rRectSource.Left() + aMapResSource.mnMapOfsX, + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) - + aMapResDest.mnMapOfsX; + auto top = fn5( rRectSource.Top() + aMapResSource.mnMapOfsY, + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) - + aMapResDest.mnMapOfsY; + + // tdf#141761 see comments above, IsEmpty() removed + auto right = rRectSource.IsWidthEmpty() ? 0 : fn5( rRectSource.Right() + aMapResSource.mnMapOfsX, + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) - + aMapResDest.mnMapOfsX; + auto bottom = rRectSource.IsHeightEmpty() ? 0 : fn5( rRectSource.Bottom() + aMapResSource.mnMapOfsY, + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) - + aMapResDest.mnMapOfsY; + + aRetval = tools::Rectangle(left, top, right, bottom); + } + + if(rRectSource.IsWidthEmpty()) + aRetval.SetWidthEmpty(); + + if(rRectSource.IsHeightEmpty()) + aRetval.SetHeightEmpty(); + + return aRetval; +} + +tools::Long OutputDevice::LogicToLogic( tools::Long nLongSource, + MapUnit eUnitSource, MapUnit eUnitDest ) +{ + if ( eUnitSource == eUnitDest ) + return nLongSource; + + verifyUnitSourceDest( eUnitSource, eUnitDest ); + const auto& [eFrom, eTo] = getCorrectedUnit(eUnitSource, eUnitDest); + return fn3(nLongSource, eFrom, eTo); +} + +void OutputDevice::SetPixelOffset( const Size& rOffset ) +{ + mnOutOffOrigX = rOffset.Width(); + mnOutOffOrigY = rOffset.Height(); + + mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX ); + mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY ); + + if( mpAlphaVDev ) + mpAlphaVDev->SetPixelOffset( rOffset ); +} + + +double OutputDevice::ImplLogicWidthToDeviceSubPixel(tools::Long nWidth) const +{ + if (!mbMap) + return nWidth; + + return ImplLogicToSubPixel(nWidth, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX); +} + +double OutputDevice::ImplLogicHeightToDeviceSubPixel(tools::Long nHeight) const +{ + if (!mbMap) + return nHeight; + + return ImplLogicToSubPixel(nHeight, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY); +} + +basegfx::B2DPoint OutputDevice::ImplLogicToDeviceSubPixel(const Point& rPoint) const +{ + if (!mbMap) + return basegfx::B2DPoint(rPoint.X() + mnOutOffX, rPoint.Y() + mnOutOffY); + + return basegfx::B2DPoint(ImplLogicToSubPixel(rPoint.X() + maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX) + + mnOutOffX + mnOutOffOrigX, + ImplLogicToSubPixel(rPoint.Y() + maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY) + + mnOutOffY + mnOutOffOrigY); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/mask.cxx b/vcl/source/outdev/mask.cxx new file mode 100644 index 0000000000..004b248785 --- /dev/null +++ b/vcl/source/outdev/mask.cxx @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> + +#include <salgdi.hxx> +#include <salbmp.hxx> + +#include <cassert> + +void OutputDevice::DrawMask( const Point& rDestPt, + const Bitmap& rBitmap, const Color& rMaskColor ) +{ + assert(!is_double_buffered_window()); + + const Size aSizePix( rBitmap.GetSizePixel() ); + DrawMask( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmap, rMaskColor, MetaActionType::MASK ); +} + +void OutputDevice::DrawMask( const Point& rDestPt, const Size& rDestSize, + const Bitmap& rBitmap, const Color& rMaskColor ) +{ + assert(!is_double_buffered_window()); + + DrawMask( rDestPt, rDestSize, Point(), rBitmap.GetSizePixel(), rBitmap, rMaskColor, MetaActionType::MASKSCALE ); +} + +void OutputDevice::DrawMask( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel, + const Bitmap& rBitmap, const Color& rMaskColor) +{ + + assert(!is_double_buffered_window()); + + DrawMask( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmap, rMaskColor, MetaActionType::MASKSCALEPART ); +} + +void OutputDevice::DrawMask( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel, + const Bitmap& rBitmap, const Color& rMaskColor, + const MetaActionType nAction ) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if( RasterOp::Invert == meRasterOp ) + { + DrawRect( tools::Rectangle( rDestPt, rDestSize ) ); + return; + } + + if ( mpMetaFile ) + { + switch( nAction ) + { + case MetaActionType::MASK: + mpMetaFile->AddAction( new MetaMaskAction( rDestPt, + rBitmap, rMaskColor ) ); + break; + + case MetaActionType::MASKSCALE: + mpMetaFile->AddAction( new MetaMaskScaleAction( rDestPt, + rDestSize, rBitmap, rMaskColor ) ); + break; + + case MetaActionType::MASKSCALEPART: + mpMetaFile->AddAction( new MetaMaskScalePartAction( rDestPt, rDestSize, + rSrcPtPixel, rSrcSizePixel, rBitmap, rMaskColor ) ); + break; + + default: break; + } + } + + if ( !IsDeviceOutputNecessary() ) + return; + + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + DrawDeviceMask( rBitmap, rMaskColor, rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel ); + +} + +void OutputDevice::DrawDeviceMask( const Bitmap& rMask, const Color& rMaskColor, + const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel ) +{ + assert(!is_double_buffered_window()); + + const std::shared_ptr<SalBitmap>& xImpBmp = rMask.ImplGetSalBitmap(); + if (xImpBmp) + { + SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(), + ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()), + ImplLogicWidthToDevicePixel(rDestSize.Width()), + ImplLogicHeightToDevicePixel(rDestSize.Height())); + + // we don't want to mirror via coordinates + const BmpMirrorFlags nMirrFlags = AdjustTwoRect( aPosAry, xImpBmp->GetSize() ); + + // check if output is necessary + if( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight ) + { + + if( nMirrFlags != BmpMirrorFlags::NONE ) + { + Bitmap aTmp( rMask ); + aTmp.Mirror( nMirrFlags ); + mpGraphics->DrawMask( aPosAry, *aTmp.ImplGetSalBitmap(), + rMaskColor, *this); + } + else + mpGraphics->DrawMask( aPosAry, *xImpBmp, rMaskColor, *this ); + + } + } + + // TODO: Use mask here + if( !mpAlphaVDev ) + return; + + const Bitmap& rAlphaMask( rMask.CreateMask( rMaskColor ) ); + + // #i25167# Restrict mask painting to _opaque_ areas + // of the mask, otherwise we spoil areas where no + // bitmap content was ever visible. Interestingly + // enough, this can be achieved by taking the mask as + // the transparency mask of itself + mpAlphaVDev->DrawBitmapEx( rDestPt, + rDestSize, + rSrcPtPixel, + rSrcSizePixel, + BitmapEx( rAlphaMask, rMask ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/nativecontrols.cxx b/vcl/source/outdev/nativecontrols.cxx new file mode 100644 index 0000000000..1b035c72bd --- /dev/null +++ b/vcl/source/outdev/nativecontrols.cxx @@ -0,0 +1,326 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/log.hxx> + +#include <vcl/pdfextoutdevdata.hxx> +#include <vcl/salnativewidgets.hxx> +#include <vcl/virdev.hxx> +#include <vcl/window.hxx> + +#include <salgdi.hxx> +#include <toolbarvalue.hxx> +#include <menubarvalue.hxx> + +#include <cassert> + +ImplControlValue::~ImplControlValue() +{ +} + +ImplControlValue* ImplControlValue::clone() const +{ + assert( typeid( const ImplControlValue ) == typeid( *this )); + return new ImplControlValue( *this ); +} + +ScrollbarValue::~ScrollbarValue() +{ +} + +ScrollbarValue* ScrollbarValue::clone() const +{ + assert( typeid( const ScrollbarValue ) == typeid( *this )); + return new ScrollbarValue( *this ); +} + +SliderValue::~SliderValue() +{ +} + +SliderValue* SliderValue::clone() const +{ + assert( typeid( const SliderValue ) == typeid( *this )); + return new SliderValue( *this ); +} + +int TabPaneValue::m_nOverlap = 0; + +TabPaneValue* TabPaneValue::clone() const +{ + assert(typeid(const TabPaneValue) == typeid(*this)); + return new TabPaneValue(*this); +} + +TabitemValue::~TabitemValue() +{ +} + +TabitemValue* TabitemValue::clone() const +{ + assert( typeid( const TabitemValue ) == typeid( *this )); + return new TabitemValue( *this ); +} + +SpinbuttonValue::~SpinbuttonValue() +{ +} + +SpinbuttonValue* SpinbuttonValue::clone() const +{ + assert( typeid( const SpinbuttonValue ) == typeid( *this )); + return new SpinbuttonValue( *this ); +} + +ToolbarValue::~ToolbarValue() +{ +} + +ToolbarValue* ToolbarValue::clone() const +{ + assert( typeid( const ToolbarValue ) == typeid( *this )); + return new ToolbarValue( *this ); +} + +MenubarValue::~MenubarValue() +{ +} + +MenubarValue* MenubarValue::clone() const +{ + assert( typeid( const MenubarValue ) == typeid( *this )); + return new MenubarValue( *this ); +} + +MenupopupValue::~MenupopupValue() +{ +} + +MenupopupValue* MenupopupValue::clone() const +{ + assert( typeid( const MenupopupValue ) == typeid( *this )); + return new MenupopupValue( *this ); +} + +PushButtonValue::~PushButtonValue() +{ +} + +PushButtonValue* PushButtonValue::clone() const +{ + assert( typeid( const PushButtonValue ) == typeid( *this )); + return new PushButtonValue( *this ); +} + +// These functions are mainly passthrough functions that allow access to +// the SalFrame behind a Window object for native widget rendering purposes. + +bool OutputDevice::IsNativeControlSupported( ControlType nType, ControlPart nPart ) const +{ + if( !CanEnableNativeWidget() ) + return false; + + if ( !mpGraphics && !AcquireGraphics() ) + return false; + assert(mpGraphics); + + return mpGraphics->IsNativeControlSupported(nType, nPart); +} + +bool OutputDevice::HitTestNativeScrollbar( + ControlPart nPart, + const tools::Rectangle& rControlRegion, + const Point& aPos, + bool& rIsInside ) const +{ + if( !CanEnableNativeWidget() ) + return false; + + if ( !mpGraphics && !AcquireGraphics() ) + return false; + assert(mpGraphics); + + Point aWinOffs( mnOutOffX, mnOutOffY ); + tools::Rectangle screenRegion( rControlRegion ); + screenRegion.Move( aWinOffs.X(), aWinOffs.Y()); + + return mpGraphics->HitTestNativeScrollbar( nPart, screenRegion, Point( aPos.X() + mnOutOffX, aPos.Y() + mnOutOffY ), + rIsInside, *this ); +} + +static std::unique_ptr< ImplControlValue > TransformControlValue( const ImplControlValue& rVal, const OutputDevice& rDev ) +{ + std::unique_ptr< ImplControlValue > aResult; + switch( rVal.getType() ) + { + case ControlType::Slider: + { + const SliderValue* pSlVal = static_cast<const SliderValue*>(&rVal); + SliderValue* pNew = new SliderValue( *pSlVal ); + aResult.reset( pNew ); + pNew->maThumbRect = rDev.ImplLogicToDevicePixel( pSlVal->maThumbRect ); + } + break; + case ControlType::Scrollbar: + { + const ScrollbarValue* pScVal = static_cast<const ScrollbarValue*>(&rVal); + ScrollbarValue* pNew = new ScrollbarValue( *pScVal ); + aResult.reset( pNew ); + pNew->maThumbRect = rDev.ImplLogicToDevicePixel( pScVal->maThumbRect ); + pNew->maButton1Rect = rDev.ImplLogicToDevicePixel( pScVal->maButton1Rect ); + pNew->maButton2Rect = rDev.ImplLogicToDevicePixel( pScVal->maButton2Rect ); + } + break; + case ControlType::SpinButtons: + { + const SpinbuttonValue* pSpVal = static_cast<const SpinbuttonValue*>(&rVal); + SpinbuttonValue* pNew = new SpinbuttonValue( *pSpVal ); + aResult.reset( pNew ); + pNew->maUpperRect = rDev.ImplLogicToDevicePixel( pSpVal->maUpperRect ); + pNew->maLowerRect = rDev.ImplLogicToDevicePixel( pSpVal->maLowerRect ); + } + break; + case ControlType::Toolbar: + { + const ToolbarValue* pTVal = static_cast<const ToolbarValue*>(&rVal); + ToolbarValue* pNew = new ToolbarValue( *pTVal ); + aResult.reset( pNew ); + pNew->maGripRect = rDev.ImplLogicToDevicePixel( pTVal->maGripRect ); + } + break; + case ControlType::TabPane: + { + const TabPaneValue* pTIVal = static_cast<const TabPaneValue*>(&rVal); + TabPaneValue* pNew = new TabPaneValue(*pTIVal); + pNew->m_aTabHeaderRect = rDev.ImplLogicToDevicePixel(pTIVal->m_aTabHeaderRect); + pNew->m_aSelectedTabRect = rDev.ImplLogicToDevicePixel(pTIVal->m_aSelectedTabRect); + aResult.reset(pNew); + } + break; + case ControlType::TabItem: + { + const TabitemValue* pTIVal = static_cast<const TabitemValue*>(&rVal); + TabitemValue* pNew = new TabitemValue( *pTIVal ); + pNew->maContentRect = rDev.ImplLogicToDevicePixel(pTIVal->maContentRect); + aResult.reset( pNew ); + } + break; + case ControlType::Menubar: + { + const MenubarValue* pMVal = static_cast<const MenubarValue*>(&rVal); + MenubarValue* pNew = new MenubarValue( *pMVal ); + aResult.reset( pNew ); + } + break; + case ControlType::Pushbutton: + { + const PushButtonValue* pBVal = static_cast<const PushButtonValue*>(&rVal); + PushButtonValue* pNew = new PushButtonValue( *pBVal ); + aResult.reset( pNew ); + } + break; + case ControlType::Generic: + aResult = std::make_unique<ImplControlValue>( rVal ); + break; + case ControlType::MenuPopup: + { + const MenupopupValue* pMVal = static_cast<const MenupopupValue*>(&rVal); + MenupopupValue* pNew = new MenupopupValue( *pMVal ); + pNew->maItemRect = rDev.ImplLogicToDevicePixel( pMVal->maItemRect ); + aResult.reset( pNew ); + } + break; + default: + std::abort(); + break; + } + return aResult; +} +bool OutputDevice::DrawNativeControl( ControlType nType, + ControlPart nPart, + const tools::Rectangle& rControlRegion, + ControlState nState, + const ImplControlValue& aValue, + const OUString& aCaption, + const Color& rBackgroundColor ) +{ + assert(!is_double_buffered_window()); + + if( !CanEnableNativeWidget() ) + return false; + + // make sure the current clip region is initialized correctly + if ( !mpGraphics && !AcquireGraphics() ) + return false; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + if ( mbOutputClipped ) + return true; + + if ( mbInitLineColor ) + InitLineColor(); + if ( mbInitFillColor ) + InitFillColor(); + + // Convert the coordinates from relative to Window-absolute, so we draw + // in the correct place in platform code + std::unique_ptr< ImplControlValue > aScreenCtrlValue( TransformControlValue( aValue, *this ) ); + tools::Rectangle screenRegion( ImplLogicToDevicePixel( rControlRegion ) ); + + bool bRet = mpGraphics->DrawNativeControl(nType, nPart, screenRegion, nState, *aScreenCtrlValue, aCaption, *this, rBackgroundColor); + + return bRet; +} + +bool OutputDevice::GetNativeControlRegion( ControlType nType, + ControlPart nPart, + const tools::Rectangle& rControlRegion, + ControlState nState, + const ImplControlValue& aValue, + tools::Rectangle &rNativeBoundingRegion, + tools::Rectangle &rNativeContentRegion ) const +{ + if( !CanEnableNativeWidget() ) + return false; + + if ( !mpGraphics && !AcquireGraphics() ) + return false; + assert(mpGraphics); + + // Convert the coordinates from relative to Window-absolute, so we draw + // in the correct place in platform code + std::unique_ptr< ImplControlValue > aScreenCtrlValue( TransformControlValue( aValue, *this ) ); + tools::Rectangle screenRegion( ImplLogicToDevicePixel( rControlRegion ) ); + + bool bRet = mpGraphics->GetNativeControlRegion(nType, nPart, screenRegion, nState, *aScreenCtrlValue, + rNativeBoundingRegion, + rNativeContentRegion, *this ); + if( bRet ) + { + // transform back native regions + rNativeBoundingRegion = ImplDevicePixelToLogic( rNativeBoundingRegion ); + rNativeContentRegion = ImplDevicePixelToLogic( rNativeContentRegion ); + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/outdev.cxx b/vcl/source/outdev/outdev.cxx new file mode 100644 index 0000000000..828a121cca --- /dev/null +++ b/vcl/source/outdev/outdev.cxx @@ -0,0 +1,822 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <sal/log.hxx> +#include <comphelper/processfactory.hxx> +#include <tools/debug.hxx> + +#include <vcl/graph.hxx> +#include <vcl/lazydelete.hxx> +#include <vcl/metaact.hxx> +#include <vcl/toolkit/unowrap.hxx> +#include <vcl/svapp.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/virdev.hxx> + +#include <ImplOutDevData.hxx> +#include <font/PhysicalFontFaceCollection.hxx> +#include <salgdi.hxx> +#include <window.h> + +#include <com/sun/star/awt/DeviceCapability.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/rendering/CanvasFactory.hpp> +#include <com/sun/star/rendering/XSpriteCanvas.hpp> + +#ifdef DISABLE_DYNLOADING +// Linking all needed LO code into one .so/executable, these already +// exist in the tools library, so put them in the anonymous namespace +// here to avoid clash... +namespace { +#endif +#ifdef DISABLE_DYNLOADING +} +#endif + +using namespace ::com::sun::star::uno; + +// Begin initializer and accessor public functions + +OutputDevice::OutputDevice(OutDevType eOutDevType) : + meOutDevType(eOutDevType), + maRegion(true), + maFillColor( COL_WHITE ), + maTextLineColor( COL_TRANSPARENT ), + moSettings( Application::GetSettings() ) +{ + mpGraphics = nullptr; + mpUnoGraphicsList = nullptr; + mpPrevGraphics = nullptr; + mpNextGraphics = nullptr; + mpMetaFile = nullptr; + mpFontInstance = nullptr; + mpForcedFallbackInstance = nullptr; + mpFontFaceCollection = nullptr; + mpAlphaVDev = nullptr; + mpExtOutDevData = nullptr; + mnOutOffX = 0; + mnOutOffY = 0; + mnOutWidth = 0; + mnOutHeight = 0; + mnDPIX = 0; + mnDPIY = 0; + mnDPIScalePercentage = 100; + mnTextOffX = 0; + mnTextOffY = 0; + mnOutOffOrigX = 0; + mnOutOffLogicX = 0; + mnOutOffOrigY = 0; + mnOutOffLogicY = 0; + mnEmphasisAscent = 0; + mnEmphasisDescent = 0; + mnDrawMode = DrawModeFlags::Default; + mnTextLayoutMode = vcl::text::ComplexTextLayoutFlags::Default; + + if( AllSettings::GetLayoutRTL() ) //#i84553# tip BiDi preference to RTL + mnTextLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft; + + meOutDevViewType = OutDevViewType::DontKnow; + mbMap = false; + mbClipRegion = false; + mbBackground = false; + mbOutput = true; + mbDevOutput = false; + mbOutputClipped = false; + maTextColor = COL_BLACK; + maOverlineColor = COL_TRANSPARENT; + meRasterOp = RasterOp::OverPaint; + mnAntialiasing = AntialiasingFlags::NONE; + meTextLanguage = LANGUAGE_SYSTEM; // TODO: get default from configuration? + mbLineColor = true; + mbFillColor = true; + mbInitLineColor = true; + mbInitFillColor = true; + mbInitFont = true; + mbInitTextColor = true; + mbInitClipRegion = true; + mbClipRegionSet = false; + mbNewFont = true; + mbTextLines = false; + mbTextSpecial = false; + mbRefPoint = false; + mbEnableRTL = false; // mirroring must be explicitly allowed (typically for windows only) + + // struct ImplMapRes + maMapRes.mnMapOfsX = 0; + maMapRes.mnMapOfsY = 0; + maMapRes.mnMapScNumX = 1; + maMapRes.mnMapScNumY = 1; + maMapRes.mnMapScDenomX = 1; + maMapRes.mnMapScDenomY = 1; + + // struct ImplOutDevData- see #i82615# + mpOutDevData.reset(new ImplOutDevData); + mpOutDevData->mpRotateDev = nullptr; + mpOutDevData->mpRecordLayout = nullptr; + + // #i75163# + mpOutDevData->mpViewTransform = nullptr; + mpOutDevData->mpInverseViewTransform = nullptr; +} + +OutputDevice::~OutputDevice() +{ + disposeOnce(); +} + +void OutputDevice::dispose() +{ + if ( GetUnoGraphicsList() ) + { + UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper( false ); + if ( pWrapper ) + pWrapper->ReleaseAllGraphics( this ); + delete mpUnoGraphicsList; + mpUnoGraphicsList = nullptr; + } + + mpOutDevData->mpRotateDev.disposeAndClear(); + + // #i75163# + ImplInvalidateViewTransform(); + + mpOutDevData.reset(); + + // for some reason, we haven't removed state from the stack properly + if ( !maOutDevStateStack.empty() ) + SAL_WARN( "vcl.gdi", "OutputDevice::~OutputDevice(): OutputDevice::Push() calls != OutputDevice::Pop() calls" ); + maOutDevStateStack.clear(); + + // release the active font instance + mpFontInstance.clear(); + mpForcedFallbackInstance.clear(); + + // remove cached results of GetDevFontList/GetDevSizeList + mpFontFaceCollection.reset(); + + // release ImplFontCache specific to this OutputDevice + mxFontCache.reset(); + + // release ImplFontList specific to this OutputDevice + mxFontCollection.reset(); + + mpAlphaVDev.disposeAndClear(); + mpPrevGraphics.clear(); + mpNextGraphics.clear(); + VclReferenceBase::dispose(); +} + +bool OutputDevice::IsVirtual() const +{ + return false; +} + +SalGraphics* OutputDevice::GetGraphics() +{ + DBG_TESTSOLARMUTEX(); + + if (!mpGraphics && !AcquireGraphics()) + SAL_WARN("vcl.gdi", "No mpGraphics set"); + + return mpGraphics; +} + +SalGraphics const *OutputDevice::GetGraphics() const +{ + DBG_TESTSOLARMUTEX(); + + if (!mpGraphics && !AcquireGraphics()) + SAL_WARN("vcl.gdi", "No mpGraphics set"); + + return mpGraphics; +} + +void OutputDevice::SetConnectMetaFile( GDIMetaFile* pMtf ) +{ + mpMetaFile = pMtf; +} + +void OutputDevice::SetSettings( const AllSettings& rSettings ) +{ + *moSettings = rSettings; + + if( mpAlphaVDev ) + mpAlphaVDev->SetSettings( rSettings ); +} + +SystemGraphicsData OutputDevice::GetSystemGfxData() const +{ + if (!mpGraphics && !AcquireGraphics()) + return SystemGraphicsData(); + assert(mpGraphics); + + return mpGraphics->GetGraphicsData(); +} + +OUString OutputDevice::GetRenderBackendName() const +{ + if (!mpGraphics && !AcquireGraphics()) + return {}; + assert(mpGraphics); + + return mpGraphics->getRenderBackendName(); +} + +#if ENABLE_CAIRO_CANVAS + +bool OutputDevice::SupportsCairo() const +{ + if (!mpGraphics && !AcquireGraphics()) + return false; + assert(mpGraphics); + + return mpGraphics->SupportsCairo(); +} + +cairo::SurfaceSharedPtr OutputDevice::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const +{ + if (!mpGraphics && !AcquireGraphics()) + return cairo::SurfaceSharedPtr(); + assert(mpGraphics); + return mpGraphics->CreateSurface(rSurface); +} + +cairo::SurfaceSharedPtr OutputDevice::CreateSurface(int x, int y, int width, int height) const +{ + if (!mpGraphics && !AcquireGraphics()) + return cairo::SurfaceSharedPtr(); + assert(mpGraphics); + return mpGraphics->CreateSurface(*this, x, y, width, height); +} + +cairo::SurfaceSharedPtr OutputDevice::CreateBitmapSurface(const BitmapSystemData& rData, const Size& rSize) const +{ + if (!mpGraphics && !AcquireGraphics()) + return cairo::SurfaceSharedPtr(); + assert(mpGraphics); + return mpGraphics->CreateBitmapSurface(*this, rData, rSize); +} + +css::uno::Any OutputDevice::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& rSurface, const basegfx::B2ISize& rSize) const +{ + if (!mpGraphics && !AcquireGraphics()) + return css::uno::Any(); + assert(mpGraphics); + return mpGraphics->GetNativeSurfaceHandle(rSurface, rSize); +} + +#endif // ENABLE_CAIRO_CANVAS + +css::uno::Any OutputDevice::GetSystemGfxDataAny() const +{ + const SystemGraphicsData aSysData = GetSystemGfxData(); + css::uno::Sequence< sal_Int8 > aSeq( reinterpret_cast<sal_Int8 const *>(&aSysData), + aSysData.nSize ); + + return css::uno::Any(aSeq); +} + +void OutputDevice::SetRefPoint() +{ + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaRefPointAction( Point(), false ) ); + + mbRefPoint = false; + maRefPoint.setX(0); + maRefPoint.setY(0); + + if( mpAlphaVDev ) + mpAlphaVDev->SetRefPoint(); +} + +void OutputDevice::SetRefPoint( const Point& rRefPoint ) +{ + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaRefPointAction( rRefPoint, true ) ); + + mbRefPoint = true; + maRefPoint = rRefPoint; + + if( mpAlphaVDev ) + mpAlphaVDev->SetRefPoint( rRefPoint ); +} + +void OutputDevice::SetRasterOp( RasterOp eRasterOp ) +{ + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaRasterOpAction( eRasterOp ) ); + + if ( meRasterOp != eRasterOp ) + { + meRasterOp = eRasterOp; + mbInitLineColor = mbInitFillColor = true; + + if( mpGraphics || AcquireGraphics() ) + { + assert(mpGraphics); + mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp), RasterOp::Invert == meRasterOp ); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetRasterOp( eRasterOp ); +} + +void OutputDevice::EnableOutput( bool bEnable ) +{ + mbOutput = bEnable; + + if( mpAlphaVDev ) + mpAlphaVDev->EnableOutput( bEnable ); +} + +void OutputDevice::SetAntialiasing( AntialiasingFlags nMode ) +{ + if ( mnAntialiasing != nMode ) + { + mnAntialiasing = nMode; + mbInitFont = true; + + if (mpGraphics) + mpGraphics->setAntiAlias(bool(mnAntialiasing & AntialiasingFlags::Enable)); + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetAntialiasing( nMode ); +} + +void OutputDevice::SetDrawMode(DrawModeFlags nDrawMode) +{ + mnDrawMode = nDrawMode; + + if (mpAlphaVDev) + mpAlphaVDev->SetDrawMode(nDrawMode); +} + +sal_uInt16 OutputDevice::GetBitCount() const +{ + // we need a graphics instance + if ( !mpGraphics && !AcquireGraphics() ) + return 0; + assert(mpGraphics); + + return mpGraphics->GetBitCount(); +} + +void OutputDevice::SetOutOffXPixel(tools::Long nOutOffX) +{ + mnOutOffX = nOutOffX; +} + +void OutputDevice::SetOutOffYPixel(tools::Long nOutOffY) +{ + mnOutOffY = nOutOffY; +} + +css::uno::Reference< css::awt::XGraphics > OutputDevice::CreateUnoGraphics() +{ + UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper(); + return pWrapper ? pWrapper->CreateGraphics( this ) : css::uno::Reference< css::awt::XGraphics >(); +} + +std::vector< VCLXGraphics* > *OutputDevice::CreateUnoGraphicsList() +{ + mpUnoGraphicsList = new std::vector< VCLXGraphics* >; + return mpUnoGraphicsList; +} + +// Helper public function + +bool OutputDevice::SupportsOperation( OutDevSupportType eType ) const +{ + if( !mpGraphics && !AcquireGraphics() ) + return false; + assert(mpGraphics); + const bool bHasSupport = mpGraphics->supportsOperation( eType ); + return bHasSupport; +} + +// Direct OutputDevice drawing public functions + +void OutputDevice::DrawOutDev( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPt, const Size& rSrcSize ) +{ + if( ImplIsRecordLayout() ) + return; + + if ( RasterOp::Invert == meRasterOp ) + { + DrawRect( tools::Rectangle( rDestPt, rDestSize ) ); + return; + } + + if ( mpMetaFile ) + { + const Bitmap aBmp( GetBitmap( rSrcPt, rSrcSize ) ); + mpMetaFile->AddAction( new MetaBmpScaleAction( rDestPt, rDestSize, aBmp ) ); + } + + if ( !IsDeviceOutputNecessary() ) + return; + + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + tools::Long nSrcWidth = ImplLogicWidthToDevicePixel( rSrcSize.Width() ); + tools::Long nSrcHeight = ImplLogicHeightToDevicePixel( rSrcSize.Height() ); + tools::Long nDestWidth = ImplLogicWidthToDevicePixel( rDestSize.Width() ); + tools::Long nDestHeight = ImplLogicHeightToDevicePixel( rDestSize.Height() ); + + if (nSrcWidth && nSrcHeight && nDestWidth && nDestHeight) + { + SalTwoRect aPosAry(ImplLogicXToDevicePixel(rSrcPt.X()), ImplLogicYToDevicePixel(rSrcPt.Y()), + nSrcWidth, nSrcHeight, + ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()), + nDestWidth, nDestHeight); + + AdjustTwoRect( aPosAry, GetOutputRectPixel() ); + + if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight ) + mpGraphics->CopyBits(aPosAry, *this); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawOutDev( rDestPt, rDestSize, rSrcPt, rSrcSize ); +} + +void OutputDevice::DrawOutDev( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPt, const Size& rSrcSize, + const OutputDevice& rOutDev ) +{ + if ( ImplIsRecordLayout() ) + return; + + if ( RasterOp::Invert == meRasterOp ) + { + DrawRect( tools::Rectangle( rDestPt, rDestSize ) ); + return; + } + + if ( mpMetaFile ) + { + if (rOutDev.mpAlphaVDev) + { + const BitmapEx aBmpEx(rOutDev.GetBitmapEx(rSrcPt, rSrcSize)); + mpMetaFile->AddAction(new MetaBmpExScaleAction(rDestPt, rDestSize, aBmpEx)); + } + else + { + const Bitmap aBmp(rOutDev.GetBitmap(rSrcPt, rSrcSize)); + mpMetaFile->AddAction(new MetaBmpScaleAction(rDestPt, rDestSize, aBmp)); + } + } + + if ( !IsDeviceOutputNecessary() ) + return; + + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if (rOutDev.mpAlphaVDev) + { + // alpha-blend source over destination + DrawBitmapEx(rDestPt, rDestSize, rOutDev.GetBitmapEx(rSrcPt, rSrcSize)); + } + else + { + SalTwoRect aPosAry(rOutDev.ImplLogicXToDevicePixel(rSrcPt.X()), + rOutDev.ImplLogicYToDevicePixel(rSrcPt.Y()), + rOutDev.ImplLogicWidthToDevicePixel(rSrcSize.Width()), + rOutDev.ImplLogicHeightToDevicePixel(rSrcSize.Height()), + ImplLogicXToDevicePixel(rDestPt.X()), + ImplLogicYToDevicePixel(rDestPt.Y()), + ImplLogicWidthToDevicePixel(rDestSize.Width()), + ImplLogicHeightToDevicePixel(rDestSize.Height())); + + drawOutDevDirect(rOutDev, aPosAry); + + // #i32109#: make destination rectangle opaque - source has no alpha + if (mpAlphaVDev) + mpAlphaVDev->ImplFillOpaqueRectangle(tools::Rectangle(rDestPt, rDestSize)); + } +} + +void OutputDevice::CopyArea( const Point& rDestPt, + const Point& rSrcPt, const Size& rSrcSize, + bool bWindowInvalidate ) +{ + if ( ImplIsRecordLayout() ) + return; + + RasterOp eOldRop = GetRasterOp(); + SetRasterOp( RasterOp::OverPaint ); + + if ( !IsDeviceOutputNecessary() ) + return; + + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + tools::Long nSrcWidth = ImplLogicWidthToDevicePixel( rSrcSize.Width() ); + tools::Long nSrcHeight = ImplLogicHeightToDevicePixel( rSrcSize.Height() ); + if (nSrcWidth && nSrcHeight) + { + SalTwoRect aPosAry(ImplLogicXToDevicePixel(rSrcPt.X()), ImplLogicYToDevicePixel(rSrcPt.Y()), + nSrcWidth, nSrcHeight, + ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()), + nSrcWidth, nSrcHeight); + + AdjustTwoRect( aPosAry, GetOutputRectPixel() ); + + CopyDeviceArea( aPosAry, bWindowInvalidate ); + } + + SetRasterOp( eOldRop ); + + if( mpAlphaVDev ) + mpAlphaVDev->CopyArea( rDestPt, rSrcPt, rSrcSize, bWindowInvalidate ); +} + +// Direct OutputDevice drawing protected function + +void OutputDevice::CopyDeviceArea( SalTwoRect& aPosAry, bool /*bWindowInvalidate*/) +{ + if (aPosAry.mnSrcWidth == 0 || aPosAry.mnSrcHeight == 0 || aPosAry.mnDestWidth == 0 || aPosAry.mnDestHeight == 0) + return; + + aPosAry.mnDestWidth = aPosAry.mnSrcWidth; + aPosAry.mnDestHeight = aPosAry.mnSrcHeight; + mpGraphics->CopyBits(aPosAry, *this); +} + +// Direct OutputDevice drawing private function +void OutputDevice::drawOutDevDirect(const OutputDevice& rSrcDev, SalTwoRect& rPosAry) +{ + SalGraphics* pSrcGraphics; + if (const OutputDevice* pCheckedSrc = DrawOutDevDirectCheck(rSrcDev)) + { + if (!pCheckedSrc->mpGraphics && !pCheckedSrc->AcquireGraphics()) + return; + pSrcGraphics = pCheckedSrc->mpGraphics; + } + else + pSrcGraphics = nullptr; + + if (!mpGraphics && !AcquireGraphics()) + return; + assert(mpGraphics); + + // #102532# Offset only has to be pseudo window offset + + AdjustTwoRect( rPosAry, rSrcDev.GetOutputRectPixel() ); + + if ( rPosAry.mnSrcWidth && rPosAry.mnSrcHeight && rPosAry.mnDestWidth && rPosAry.mnDestHeight ) + { + // if this is no window, but rSrcDev is a window + // mirroring may be required + // because only windows have a SalGraphicsLayout + // mirroring is performed here + DrawOutDevDirectProcess(rSrcDev, rPosAry, pSrcGraphics); + } +} + +const OutputDevice* OutputDevice::DrawOutDevDirectCheck(const OutputDevice& rSrcDev) const +{ + return this == &rSrcDev ? nullptr : &rSrcDev; +} + +void OutputDevice::DrawOutDevDirectProcess(const OutputDevice& rSrcDev, SalTwoRect& rPosAry, SalGraphics* pSrcGraphics) +{ + if( pSrcGraphics && (pSrcGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) ) + { + SalTwoRect aPosAry2 = rPosAry; + pSrcGraphics->mirror( aPosAry2.mnSrcX, aPosAry2.mnSrcWidth, rSrcDev ); + mpGraphics->CopyBits( aPosAry2, *pSrcGraphics, *this, rSrcDev ); + return; + } + if (pSrcGraphics) + mpGraphics->CopyBits( rPosAry, *pSrcGraphics, *this, rSrcDev ); + else + mpGraphics->CopyBits( rPosAry, *this ); +} + +tools::Rectangle OutputDevice::GetBackgroundComponentBounds() const +{ + return tools::Rectangle( Point( 0, 0 ), GetOutputSizePixel() ); +} + +// Layout public functions + +void OutputDevice::EnableRTL( bool bEnable ) +{ + mbEnableRTL = bEnable; + + if( mpAlphaVDev ) + mpAlphaVDev->EnableRTL( bEnable ); +} + +bool OutputDevice::ImplIsAntiparallel() const +{ + bool bRet = false; + if( AcquireGraphics() ) + { + if( ( (mpGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) && ! IsRTLEnabled() ) || + ( ! (mpGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) && IsRTLEnabled() ) ) + { + bRet = true; + } + } + return bRet; +} + +// note: the coordinates to be remirrored are in frame coordinates ! + +void OutputDevice::ReMirror( Point &rPoint ) const +{ + rPoint.setX( mnOutOffX + mnOutWidth - 1 - rPoint.X() + mnOutOffX ); +} +void OutputDevice::ReMirror( tools::Rectangle &rRect ) const +{ + tools::Long nWidth = rRect.Right() - rRect.Left(); + + //long lc_x = rRect.nLeft - mnOutOffX; // normalize + //lc_x = mnOutWidth - nWidth - 1 - lc_x; // mirror + //rRect.nLeft = lc_x + mnOutOffX; // re-normalize + + rRect.SetLeft( mnOutOffX + mnOutWidth - nWidth - 1 - rRect.Left() + mnOutOffX ); + rRect.SetRight( rRect.Left() + nWidth ); +} + +void OutputDevice::ReMirror( vcl::Region &rRegion ) const +{ + RectangleVector aRectangles; + rRegion.GetRegionRectangles(aRectangles); + vcl::Region aMirroredRegion; + + for (auto & rectangle : aRectangles) + { + ReMirror(rectangle); + aMirroredRegion.Union(rectangle); + } + + rRegion = aMirroredRegion; + +} + +bool OutputDevice::HasMirroredGraphics() const +{ + return ( AcquireGraphics() && (mpGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) ); +} + +bool OutputDevice::ImplIsRecordLayout() const +{ + if (!mpOutDevData) + return false; + + return mpOutDevData->mpRecordLayout; +} + +css::awt::DeviceInfo OutputDevice::GetCommonDeviceInfo(Size const& rDevSz) const +{ + css::awt::DeviceInfo aInfo; + + aInfo.Width = rDevSz.Width(); + aInfo.Height = rDevSz.Height(); + + Size aTmpSz = LogicToPixel(Size(1000, 1000), MapMode(MapUnit::MapMM)); + aInfo.PixelPerMeterX = aTmpSz.Width(); + aInfo.PixelPerMeterY = aTmpSz.Height(); + aInfo.BitsPerPixel = GetBitCount(); + + aInfo.Capabilities = css::awt::DeviceCapability::RASTEROPERATIONS | + css::awt::DeviceCapability::GETBITS; + + return aInfo; +} + +css::awt::DeviceInfo OutputDevice::GetDeviceInfo() const +{ + css::awt::DeviceInfo aInfo = GetCommonDeviceInfo(GetOutputSizePixel()); + + aInfo.LeftInset = 0; + aInfo.TopInset = 0; + aInfo.RightInset = 0; + aInfo.BottomInset = 0; + + return aInfo; +} + +Reference< css::rendering::XCanvas > OutputDevice::GetCanvas() const +{ + // try to retrieve hard reference from weak member + Reference< css::rendering::XCanvas > xCanvas( mxCanvas ); + // canvas still valid? Then we're done. + if( xCanvas.is() ) + return xCanvas; + xCanvas = ImplGetCanvas( false ); + mxCanvas = xCanvas; + return xCanvas; +} + +Reference< css::rendering::XSpriteCanvas > OutputDevice::GetSpriteCanvas() const +{ + Reference< css::rendering::XCanvas > xCanvas( mxCanvas ); + Reference< css::rendering::XSpriteCanvas > xSpriteCanvas( xCanvas, UNO_QUERY ); + if( xSpriteCanvas.is() ) + return xSpriteCanvas; + xCanvas = ImplGetCanvas( true ); + mxCanvas = xCanvas; + return Reference< css::rendering::XSpriteCanvas >( xCanvas, UNO_QUERY ); +} + +// Generic implementation, Window will override. +com::sun::star::uno::Reference< css::rendering::XCanvas > OutputDevice::ImplGetCanvas( bool bSpriteCanvas ) const +{ + /* Arguments: + 0: ptr to creating instance (Window or VirtualDevice) + 1: current bounds of creating instance + 2: bool, denoting always on top state for Window (always false for VirtualDevice) + 3: XWindow for creating Window (or empty for VirtualDevice) + 4: SystemGraphicsData as a streamed Any + */ + Sequence< Any > aArg{ + Any(reinterpret_cast<sal_Int64>(this)), + Any(css::awt::Rectangle( mnOutOffX, mnOutOffY, mnOutWidth, mnOutHeight )), + Any(false), + Any(Reference< css::awt::XWindow >()), + GetSystemGfxDataAny() + }; + + Reference< XComponentContext > xContext = comphelper::getProcessComponentContext(); + + static vcl::DeleteUnoReferenceOnDeinit<css::lang::XMultiComponentFactory> xStaticCanvasFactory( + css::rendering::CanvasFactory::create( xContext ) ); + Reference<css::lang::XMultiComponentFactory> xCanvasFactory(xStaticCanvasFactory.get()); + Reference< css::rendering::XCanvas > xCanvas; + + if(xCanvasFactory.is()) + { + xCanvas.set( xCanvasFactory->createInstanceWithArgumentsAndContext( + bSpriteCanvas ? + OUString( "com.sun.star.rendering.SpriteCanvas" ) : + OUString( "com.sun.star.rendering.Canvas" ), + aArg, + xContext ), + UNO_QUERY ); + } + + // no factory??? Empty reference, then. + return xCanvas; +} + +void OutputDevice::ImplDisposeCanvas() +{ + css::uno::Reference< css::rendering::XCanvas > xCanvas( mxCanvas ); + if( xCanvas.is() ) + { + css::uno::Reference< css::lang::XComponent > xCanvasComponent( xCanvas, css::uno::UNO_QUERY ); + if( xCanvasComponent.is() ) + xCanvasComponent->dispose(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/vcl/source/outdev/pixel.cxx b/vcl/source/outdev/pixel.cxx new file mode 100644 index 0000000000..8c97aeb432 --- /dev/null +++ b/vcl/source/outdev/pixel.cxx @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> + +#include <drawmode.hxx> +#include <salgdi.hxx> + +#include <cassert> + +Color OutputDevice::GetPixel(const Point& rPoint) const +{ + Color aColor; + + if (mpGraphics || AcquireGraphics()) + { + assert(mpGraphics); + if (mbInitClipRegion) + const_cast<OutputDevice*>(this)->InitClipRegion(); + + if (!mbOutputClipped) + { + const tools::Long nX = ImplLogicXToDevicePixel(rPoint.X()); + const tools::Long nY = ImplLogicYToDevicePixel(rPoint.Y()); + aColor = mpGraphics->GetPixel(nX, nY, *this); + + if (mpAlphaVDev) + { + Color aAlphaColor = mpAlphaVDev->GetPixel(rPoint); + aColor.SetAlpha(aAlphaColor.GetBlue()); + } + } + } + return aColor; +} + +void OutputDevice::DrawPixel( const Point& rPt ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaPointAction( rPt ) ); + + if ( !IsDeviceOutputNecessary() || !mbLineColor || ImplIsRecordLayout() ) + return; + + Point aPt = ImplLogicToDevicePixel( rPt ); + + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + mpGraphics->DrawPixel( aPt.X(), aPt.Y(), *this ); + + if( mpAlphaVDev ) + mpAlphaVDev->DrawPixel( rPt ); +} + +void OutputDevice::DrawPixel( const Point& rPt, const Color& rColor ) +{ + assert(!is_double_buffered_window()); + + Color aColor = vcl::drawmode::GetLineColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaPixelAction( rPt, aColor ) ); + + if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + Point aPt = ImplLogicToDevicePixel( rPt ); + + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + mpGraphics->DrawPixel( aPt.X(), aPt.Y(), aColor, *this ); + + if (mpAlphaVDev) + { + Color aAlphaColor(rColor.GetAlpha(), rColor.GetAlpha(), rColor.GetAlpha()); + mpAlphaVDev->DrawPixel(rPt, aAlphaColor); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/polygon.cxx b/vcl/source/outdev/polygon.cxx new file mode 100644 index 0000000000..e764e6b66d --- /dev/null +++ b/vcl/source/outdev/polygon.cxx @@ -0,0 +1,510 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/types.h> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <tools/poly.hxx> + +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> + +#include <salgdi.hxx> + +#include <cassert> +#include <memory> + +#define OUTDEV_POLYPOLY_STACKBUF 32 + +void OutputDevice::DrawPolyPolygon( const tools::PolyPolygon& rPolyPoly ) +{ + assert(!is_double_buffered_window()); + + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaPolyPolygonAction( rPolyPoly ) ); + + sal_uInt16 nPoly = rPolyPoly.Count(); + + if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || !nPoly || ImplIsRecordLayout() ) + return; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + if ( mbInitFillColor ) + InitFillColor(); + + // use b2dpolygon drawing if possible + if (RasterOp::OverPaint == GetRasterOp() && (IsLineColor() || IsFillColor())) + { + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation()); + basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon()); + + // ensure closed - may be asserted, will prevent buffering + if(!aB2DPolyPolygon.isClosed()) + { + aB2DPolyPolygon.setClosed(true); + } + + if (IsFillColor()) + { + mpGraphics->DrawPolyPolygon( + aTransform, + aB2DPolyPolygon, + 0.0, + *this); + } + + bool bSuccess(true); + if (IsLineColor()) + { + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + for(auto const& rPolygon : std::as_const(aB2DPolyPolygon)) + { + bSuccess = mpGraphics->DrawPolyLine( + aTransform, + rPolygon, + 0.0, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + *this); + if (!bSuccess) + break; + } + } + + if(bSuccess) + { + if( mpAlphaVDev ) + mpAlphaVDev->DrawPolyPolygon( rPolyPoly ); + return; + } + } + + if ( nPoly == 1 ) + { + // #100127# Map to DrawPolygon + const tools::Polygon& aPoly = rPolyPoly.GetObject( 0 ); + if( aPoly.GetSize() >= 2 ) + { + GDIMetaFile* pOldMF = mpMetaFile; + mpMetaFile = nullptr; + + DrawPolygon( aPoly ); + + mpMetaFile = pOldMF; + } + } + else + { + // #100127# moved real tools::PolyPolygon draw to separate method, + // have to call recursively, avoiding duplicate + // ImplLogicToDevicePixel calls + ImplDrawPolyPolygon( nPoly, ImplLogicToDevicePixel( rPolyPoly ) ); + } + if( mpAlphaVDev ) + mpAlphaVDev->DrawPolyPolygon( rPolyPoly ); +} + +void OutputDevice::DrawPolygon( const basegfx::B2DPolygon& rB2DPolygon) +{ + assert(!is_double_buffered_window()); + + // AW: Do NOT paint empty polygons + if(rB2DPolygon.count()) + { + basegfx::B2DPolyPolygon aPP( rB2DPolygon ); + DrawPolyPolygon( aPP ); + } +} + +void OutputDevice::DrawPolygon( const tools::Polygon& rPoly ) +{ + assert(!is_double_buffered_window()); + + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaPolygonAction( rPoly ) ); + + sal_uInt16 nPoints = rPoly.GetSize(); + + if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || (nPoints < 2) || ImplIsRecordLayout() ) + return; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + if ( mbInitFillColor ) + InitFillColor(); + + // use b2dpolygon drawing if possible + if (RasterOp::OverPaint == GetRasterOp() && (IsLineColor() || IsFillColor())) + { + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation()); + basegfx::B2DPolygon aB2DPolygon(rPoly.getB2DPolygon()); + + // ensure closed - maybe assert, hinders buffering + if(!aB2DPolygon.isClosed()) + { + aB2DPolygon.setClosed(true); + } + + if (IsFillColor()) + { + mpGraphics->DrawPolyPolygon( + aTransform, + basegfx::B2DPolyPolygon(aB2DPolygon), + 0.0, + *this); + } + + bool bSuccess(true); + if (IsLineColor()) + { + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + bSuccess = mpGraphics->DrawPolyLine( + aTransform, + aB2DPolygon, + 0.0, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + *this); + } + + if(bSuccess) + { + if( mpAlphaVDev ) + mpAlphaVDev->DrawPolygon( rPoly ); + return; + } + } + + tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly ); + const Point* pPtAry = aPoly.GetConstPointAry(); + + // #100127# Forward beziers to sal, if any + if( aPoly.HasFlags() ) + { + const PolyFlags* pFlgAry = aPoly.GetConstFlagAry(); + if( !mpGraphics->DrawPolygonBezier( nPoints, pPtAry, pFlgAry, *this ) ) + { + aPoly = tools::Polygon::SubdivideBezier(aPoly); + pPtAry = aPoly.GetConstPointAry(); + mpGraphics->DrawPolygon( aPoly.GetSize(), pPtAry, *this ); + } + } + else + { + mpGraphics->DrawPolygon( nPoints, pPtAry, *this ); + } + if( mpAlphaVDev ) + mpAlphaVDev->DrawPolygon( rPoly ); +} + +// Caution: This method is nearly the same as +// OutputDevice::DrawTransparent( const basegfx::B2DPolyPolygon& rB2DPolyPoly, double fTransparency), +// so when changes are made here do not forget to make changes there, too + +void OutputDevice::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rB2DPolyPoly ) +{ + assert(!is_double_buffered_window()); + + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaPolyPolygonAction( tools::PolyPolygon( rB2DPolyPoly ) ) ); + + // call helper + ImplDrawPolyPolygonWithB2DPolyPolygon(rB2DPolyPoly); +} + +void OutputDevice::ImplDrawPolyPolygonWithB2DPolyPolygon(const basegfx::B2DPolyPolygon& rB2DPolyPoly) +{ + // Do not paint empty PolyPolygons + if(!rB2DPolyPoly.count() || !IsDeviceOutputNecessary()) + return; + + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return; + + if( mbInitLineColor ) + InitLineColor(); + + if( mbInitFillColor ) + InitFillColor(); + + bool bSuccess(false); + + if (RasterOp::OverPaint == GetRasterOp() && (IsLineColor() || IsFillColor())) + { + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation()); + basegfx::B2DPolyPolygon aB2DPolyPolygon(rB2DPolyPoly); + bSuccess = true; + + // ensure closed - maybe assert, hinders buffering + if(!aB2DPolyPolygon.isClosed()) + { + aB2DPolyPolygon.setClosed(true); + } + + if (IsFillColor()) + { + mpGraphics->DrawPolyPolygon( + aTransform, + aB2DPolyPolygon, + 0.0, + *this); + } + + if (IsLineColor()) + { + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + for(auto const& rPolygon : std::as_const(aB2DPolyPolygon)) + { + bSuccess = mpGraphics->DrawPolyLine( + aTransform, + rPolygon, + 0.0, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + *this); + if (!bSuccess) + break; + } + } + } + + if (!bSuccess) + { + // fallback to old polygon drawing if needed + const tools::PolyPolygon aToolsPolyPolygon(rB2DPolyPoly); + const tools::PolyPolygon aPixelPolyPolygon = ImplLogicToDevicePixel(aToolsPolyPolygon); + ImplDrawPolyPolygon(aPixelPolyPolygon.Count(), aPixelPolyPolygon); + } + + if (mpAlphaVDev) + mpAlphaVDev->ImplDrawPolyPolygonWithB2DPolyPolygon(rB2DPolyPoly); +} + +// #100127# Extracted from OutputDevice::DrawPolyPolygon() +void OutputDevice::ImplDrawPolyPolygon( sal_uInt16 nPoly, const tools::PolyPolygon& rPolyPoly ) +{ + // AW: This crashes on empty PolyPolygons, avoid that + if(!nPoly) + return; + + sal_uInt32 aStackAry1[OUTDEV_POLYPOLY_STACKBUF]; + const Point* aStackAry2[OUTDEV_POLYPOLY_STACKBUF]; + PolyFlags* aStackAry3[OUTDEV_POLYPOLY_STACKBUF]; + sal_uInt32* pPointAry; + const Point** pPointAryAry; + const PolyFlags** pFlagAryAry; + sal_uInt16 i = 0; + sal_uInt16 j = 0; + sal_uInt16 last = 0; + bool bHaveBezier = false; + if ( nPoly > OUTDEV_POLYPOLY_STACKBUF ) + { + pPointAry = new sal_uInt32[nPoly]; + pPointAryAry = new const Point*[nPoly]; + pFlagAryAry = new const PolyFlags*[nPoly]; + } + else + { + pPointAry = aStackAry1; + pPointAryAry = aStackAry2; + pFlagAryAry = const_cast<const PolyFlags**>(aStackAry3); + } + + do + { + const tools::Polygon& rPoly = rPolyPoly.GetObject( i ); + sal_uInt16 nSize = rPoly.GetSize(); + if ( nSize ) + { + pPointAry[j] = nSize; + pPointAryAry[j] = rPoly.GetConstPointAry(); + pFlagAryAry[j] = rPoly.GetConstFlagAry(); + last = i; + + if( pFlagAryAry[j] ) + bHaveBezier = true; + + ++j; + } + ++i; + } + while ( i < nPoly ); + + if ( j == 1 ) + { + // #100127# Forward beziers to sal, if any + if( bHaveBezier ) + { + if( !mpGraphics->DrawPolygonBezier( *pPointAry, *pPointAryAry, *pFlagAryAry, *this ) ) + { + tools::Polygon aPoly = tools::Polygon::SubdivideBezier( rPolyPoly.GetObject( last ) ); + mpGraphics->DrawPolygon( aPoly.GetSize(), aPoly.GetConstPointAry(), *this ); + } + } + else + { + mpGraphics->DrawPolygon( *pPointAry, *pPointAryAry, *this ); + } + } + else + { + // #100127# Forward beziers to sal, if any + if( bHaveBezier ) + { + if (!mpGraphics->DrawPolyPolygonBezier(j, pPointAry, pPointAryAry, pFlagAryAry, *this)) + { + tools::PolyPolygon aPolyPoly = tools::PolyPolygon::SubdivideBezier( rPolyPoly ); + ImplDrawPolyPolygon( aPolyPoly.Count(), aPolyPoly ); + } + } + else + { + mpGraphics->DrawPolyPolygon( j, pPointAry, pPointAryAry, *this ); + } + } + + if ( pPointAry != aStackAry1 ) + { + delete[] pPointAry; + delete[] pPointAryAry; + delete[] pFlagAryAry; + } +} + +void OutputDevice::ImplDrawPolygon( const tools::Polygon& rPoly, const tools::PolyPolygon* pClipPolyPoly ) +{ + if( pClipPolyPoly ) + { + ImplDrawPolyPolygon( tools::PolyPolygon(rPoly), pClipPolyPoly ); + } + else + { + sal_uInt16 nPoints = rPoly.GetSize(); + + if ( nPoints < 2 ) + return; + + const Point* pPtAry = rPoly.GetConstPointAry(); + mpGraphics->DrawPolygon( nPoints, pPtAry, *this ); + } +} + +void OutputDevice::ImplDrawPolyPolygon( const tools::PolyPolygon& rPolyPoly, const tools::PolyPolygon* pClipPolyPoly ) +{ + tools::PolyPolygon* pPolyPoly; + + if( pClipPolyPoly ) + { + pPolyPoly = new tools::PolyPolygon; + rPolyPoly.GetIntersection( *pClipPolyPoly, *pPolyPoly ); + } + else + { + pPolyPoly = const_cast<tools::PolyPolygon*>(&rPolyPoly); + } + if( pPolyPoly->Count() == 1 ) + { + const tools::Polygon& rPoly = pPolyPoly->GetObject( 0 ); + sal_uInt16 nSize = rPoly.GetSize(); + + if( nSize >= 2 ) + { + const Point* pPtAry = rPoly.GetConstPointAry(); + mpGraphics->DrawPolygon( nSize, pPtAry, *this ); + } + } + else if( pPolyPoly->Count() ) + { + sal_uInt16 nCount = pPolyPoly->Count(); + std::unique_ptr<sal_uInt32[]> pPointAry(new sal_uInt32[nCount]); + std::unique_ptr<const Point*[]> pPointAryAry(new const Point*[nCount]); + sal_uInt16 i = 0; + do + { + const tools::Polygon& rPoly = pPolyPoly->GetObject( i ); + sal_uInt16 nSize = rPoly.GetSize(); + if ( nSize ) + { + pPointAry[i] = nSize; + pPointAryAry[i] = rPoly.GetConstPointAry(); + i++; + } + else + nCount--; + } + while( i < nCount ); + + if( nCount == 1 ) + mpGraphics->DrawPolygon( pPointAry[0], pPointAryAry[0], *this ); + else + mpGraphics->DrawPolyPolygon( nCount, pPointAry.get(), pPointAryAry.get(), *this ); + } + + if( pClipPolyPoly ) + delete pPolyPoly; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/polyline.cxx b/vcl/source/outdev/polyline.cxx new file mode 100644 index 0000000000..75e8252f4a --- /dev/null +++ b/vcl/source/outdev/polyline.cxx @@ -0,0 +1,404 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/types.h> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dlinegeometry.hxx> + +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> + +#include <salgdi.hxx> + +#include <cassert> + +void OutputDevice::DrawPolyLine( const tools::Polygon& rPoly ) +{ + assert(!is_double_buffered_window()); + + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaPolyLineAction( rPoly ) ); + + sal_uInt16 nPoints = rPoly.GetSize(); + + if ( !IsDeviceOutputNecessary() || !mbLineColor || (nPoints < 2) || ImplIsRecordLayout() ) + return; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + // use b2dpolygon drawing if possible + if(DrawPolyLineDirectInternal( + basegfx::B2DHomMatrix(), + rPoly.getB2DPolygon())) + { + return; + } + + const basegfx::B2DPolygon aB2DPolyLine(rPoly.getB2DPolygon()); + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation()); + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + bool bDrawn = mpGraphics->DrawPolyLine( + aTransform, + aB2DPolyLine, + 0.0, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0) /*default fMiterMinimumAngle, not used*/, + bPixelSnapHairline, + *this); + + if(!bDrawn) + { + tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly ); + Point* pPtAry = aPoly.GetPointAry(); + + // #100127# Forward beziers to sal, if any + if( aPoly.HasFlags() ) + { + const PolyFlags* pFlgAry = aPoly.GetConstFlagAry(); + if( !mpGraphics->DrawPolyLineBezier( nPoints, pPtAry, pFlgAry, *this ) ) + { + aPoly = tools::Polygon::SubdivideBezier(aPoly); + pPtAry = aPoly.GetPointAry(); + mpGraphics->DrawPolyLine( aPoly.GetSize(), pPtAry, *this ); + } + } + else + { + mpGraphics->DrawPolyLine( nPoints, pPtAry, *this ); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawPolyLine( rPoly ); +} + +void OutputDevice::DrawPolyLine( const tools::Polygon& rPoly, const LineInfo& rLineInfo ) +{ + assert(!is_double_buffered_window()); + + if ( rLineInfo.IsDefault() ) + { + DrawPolyLine( rPoly ); + return; + } + + if (IsDeviceOutputNecessary()) + { + auto eLineStyle = rLineInfo.GetStyle(); + switch (eLineStyle) + { + case LineStyle::NONE: + case LineStyle::Dash: + // use drawPolyLine for these + break; + case LineStyle::Solid: + // #i101491# Try direct Fallback to B2D-Version of DrawPolyLine + DrawPolyLine( + rPoly.getB2DPolygon(), + rLineInfo.GetWidth(), + rLineInfo.GetLineJoin(), + rLineInfo.GetLineCap(), + basegfx::deg2rad(15.0) /* default fMiterMinimumAngle, value not available in LineInfo */); + return; + default: + SAL_WARN("vcl.gdi", "Unknown LineStyle: " << static_cast<int>(eLineStyle)); + return; + } + } + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaPolyLineAction( rPoly, rLineInfo ) ); + + drawPolyLine(rPoly, rLineInfo); +} + +void OutputDevice::DrawPolyLine( const basegfx::B2DPolygon& rB2DPolygon, + double fLineWidth, + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle) +{ + assert(!is_double_buffered_window()); + + if( mpMetaFile ) + { + LineInfo aLineInfo; + if( fLineWidth != 0.0 ) + aLineInfo.SetWidth( fLineWidth ); + + aLineInfo.SetLineJoin(eLineJoin); + aLineInfo.SetLineCap(eLineCap); + + tools::Polygon aToolsPolygon( rB2DPolygon ); + mpMetaFile->AddAction( new MetaPolyLineAction( std::move(aToolsPolygon), std::move(aLineInfo) ) ); + } + + // Do not paint empty PolyPolygons + if(!rB2DPolygon.count() || !IsDeviceOutputNecessary()) + return; + + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return; + + if( mbInitLineColor ) + InitLineColor(); + + // use b2dpolygon drawing if possible + if(DrawPolyLineDirectInternal( + basegfx::B2DHomMatrix(), + rB2DPolygon, + fLineWidth, + 0.0, + nullptr, // MM01 + eLineJoin, + eLineCap, + fMiterMinimumAngle)) + { + return; + } + + // #i101491# + // no output yet; fallback to geometry decomposition and use filled polygon paint + // when line is fat and not too complex. ImplDrawPolyPolygonWithB2DPolyPolygon + // will do internal needed AA checks etc. + if(fLineWidth >= 2.5 && + rB2DPolygon.count() && + rB2DPolygon.count() <= 1000) + { + const double fHalfLineWidth((fLineWidth * 0.5) + 0.5); + const basegfx::B2DPolyPolygon aAreaPolyPolygon( + basegfx::utils::createAreaGeometry( rB2DPolygon, + fHalfLineWidth, + eLineJoin, + eLineCap, + fMiterMinimumAngle)); + const Color aOldLineColor(maLineColor); + const Color aOldFillColor(maFillColor); + + SetLineColor(); + InitLineColor(); + SetFillColor(aOldLineColor); + InitFillColor(); + + // draw using a loop; else the topology will paint a PolyPolygon + for(auto const& rPolygon : aAreaPolyPolygon) + { + ImplDrawPolyPolygonWithB2DPolyPolygon( + basegfx::B2DPolyPolygon(rPolygon)); + } + + SetLineColor(aOldLineColor); + InitLineColor(); + SetFillColor(aOldFillColor); + InitFillColor(); + + // when AA it is necessary to also paint the filled polygon's outline + // to avoid optical gaps + for(auto const& rPolygon : aAreaPolyPolygon) + { + (void)DrawPolyLineDirectInternal( + basegfx::B2DHomMatrix(), + rPolygon); + } + } + else + { + // fallback to old polygon drawing if needed + const tools::Polygon aToolsPolygon( rB2DPolygon ); + LineInfo aLineInfo; + if( fLineWidth != 0.0 ) + aLineInfo.SetWidth( fLineWidth ); + + drawPolyLine( aToolsPolygon, aLineInfo ); + } +} + +void OutputDevice::drawPolyLine(const tools::Polygon& rPoly, const LineInfo& rLineInfo) +{ + sal_uInt16 nPoints(rPoly.GetSize()); + + if ( !IsDeviceOutputNecessary() || !mbLineColor || ( nPoints < 2 ) || ( LineStyle::NONE == rLineInfo.GetStyle() ) || ImplIsRecordLayout() ) + return; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + const LineInfo aInfo( ImplLogicToDevicePixel( rLineInfo ) ); + const bool bDashUsed(LineStyle::Dash == aInfo.GetStyle()); + const bool bLineWidthUsed(aInfo.GetWidth() > 1); + + if (bDashUsed || bLineWidthUsed) + { + basegfx::B2DPolygon aPoly = ImplLogicToDevicePixel(rPoly.getB2DPolygon()); + drawLine(basegfx::B2DPolyPolygon(aPoly), aInfo); + } + else + { + tools::Polygon aPoly = ImplLogicToDevicePixel(rPoly); + + // #100127# the subdivision HAS to be done here since only a pointer + // to an array of points is given to the DrawPolyLine method, there is + // NO way to find out there that it's a curve. + if( aPoly.HasFlags() ) + { + aPoly = tools::Polygon::SubdivideBezier( aPoly ); + nPoints = aPoly.GetSize(); + } + + mpGraphics->DrawPolyLine(nPoints, aPoly.GetPointAry(), *this); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawPolyLine( rPoly, rLineInfo ); +} + +bool OutputDevice::DrawPolyLineDirect( + const basegfx::B2DHomMatrix& rObjectTransform, + const basegfx::B2DPolygon& rB2DPolygon, + double fLineWidth, + double fTransparency, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle) +{ + if(DrawPolyLineDirectInternal(rObjectTransform, rB2DPolygon, fLineWidth, fTransparency, + pStroke, eLineJoin, eLineCap, fMiterMinimumAngle)) + { + // Worked, add metafile action (if recorded). This is done only here, + // because this function is public, other OutDev functions already add metafile + // actions, so they call the internal function directly. + if( mpMetaFile ) + { + LineInfo aLineInfo; + if( fLineWidth != 0.0 ) + aLineInfo.SetWidth( fLineWidth ); + // Transport known information, might be needed + aLineInfo.SetLineJoin(eLineJoin); + aLineInfo.SetLineCap(eLineCap); + // MiterMinimumAngle does not exist yet in LineInfo + tools::Polygon aToolsPolygon( rB2DPolygon ); + mpMetaFile->AddAction( new MetaPolyLineAction( std::move(aToolsPolygon), std::move(aLineInfo) ) ); + } + return true; + } + return false; +} + +bool OutputDevice::DrawPolyLineDirectInternal( + const basegfx::B2DHomMatrix& rObjectTransform, + const basegfx::B2DPolygon& rB2DPolygon, + double fLineWidth, + double fTransparency, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle) +{ + assert(!is_double_buffered_window()); + + // AW: Do NOT paint empty PolyPolygons + if(!rB2DPolygon.count()) + return true; + + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return false; + assert(mpGraphics); + + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return true; + + if( mbInitLineColor ) + InitLineColor(); + + const bool bTryB2d(RasterOp::OverPaint == GetRasterOp() && IsLineColor()); + + if(bTryB2d) + { + // combine rObjectTransform with WorldToDevice + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation() * rObjectTransform); + const bool bPixelSnapHairline((mnAntialiasing & AntialiasingFlags::PixelSnapHairline) && rB2DPolygon.count() < 1000); + + const double fAdjustedTransparency = mpAlphaVDev ? 0 : fTransparency; + // draw the polyline + bool bDrawSuccess = mpGraphics->DrawPolyLine( + aTransform, + rB2DPolygon, + fAdjustedTransparency, + fLineWidth, // tdf#124848 use LineWidth direct, do not try to solve for zero-case (aka hairline) + pStroke, // MM01 + eLineJoin, + eLineCap, + fMiterMinimumAngle, + bPixelSnapHairline, + *this); + + if( bDrawSuccess ) + { + if (mpAlphaVDev) + mpAlphaVDev->DrawPolyLineDirect(rObjectTransform, rB2DPolygon, fLineWidth, + fTransparency, pStroke, eLineJoin, eLineCap, + fMiterMinimumAngle); + + return true; + } + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/rect.cxx b/vcl/source/outdev/rect.cxx new file mode 100644 index 0000000000..63ec16c62c --- /dev/null +++ b/vcl/source/outdev/rect.cxx @@ -0,0 +1,438 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/types.h> +#include <tools/poly.hxx> +#include <tools/helpers.hxx> + +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> + +#include <salgdi.hxx> + +#include <cassert> + +void OutputDevice::DrawBorder(tools::Rectangle aBorderRect) +{ + sal_uInt16 nPixel = static_cast<sal_uInt16>(PixelToLogic(Size(1, 1)).Width()); + + aBorderRect.AdjustLeft(nPixel); + aBorderRect.AdjustTop(nPixel); + + SetLineColor(COL_LIGHTGRAY); + DrawRect(aBorderRect); + + aBorderRect.AdjustLeft(-nPixel); + aBorderRect.AdjustTop(-nPixel); + aBorderRect.AdjustRight(-nPixel); + aBorderRect.AdjustBottom(-nPixel); + SetLineColor(COL_GRAY); + + DrawRect(aBorderRect); +} + +void OutputDevice::DrawRect( const tools::Rectangle& rRect ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaRectAction( rRect ) ); + + if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() ) + return; + + tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) ); + + if ( aRect.IsEmpty() ) + return; + + aRect.Normalize(); + + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + if ( mbInitFillColor ) + InitFillColor(); + + mpGraphics->DrawRect( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), *this ); + + if( mpAlphaVDev ) + mpAlphaVDev->DrawRect( rRect ); +} + +void OutputDevice::DrawRect( const tools::Rectangle& rRect, + sal_uLong nHorzRound, sal_uLong nVertRound ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaRoundRectAction( rRect, nHorzRound, nVertRound ) ); + + if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() ) + return; + + const tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) ); + + if ( aRect.IsEmpty() ) + return; + + nHorzRound = ImplLogicWidthToDevicePixel( nHorzRound ); + nVertRound = ImplLogicHeightToDevicePixel( nVertRound ); + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + if ( mbInitFillColor ) + InitFillColor(); + + if ( !nHorzRound && !nVertRound ) + { + mpGraphics->DrawRect( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), *this ); + } + else + { + tools::Polygon aRoundRectPoly( aRect, nHorzRound, nVertRound ); + + if ( aRoundRectPoly.GetSize() >= 2 ) + { + Point* pPtAry = aRoundRectPoly.GetPointAry(); + + if ( !mbFillColor ) + mpGraphics->DrawPolyLine( aRoundRectPoly.GetSize(), pPtAry, *this ); + else + mpGraphics->DrawPolygon( aRoundRectPoly.GetSize(), pPtAry, *this ); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawRect( rRect, nHorzRound, nVertRound ); +} + +void OutputDevice::Invert( const tools::Rectangle& rRect, InvertFlags nFlags ) +{ + assert(!is_double_buffered_window()); + if ( !IsDeviceOutputNecessary() ) + return; + + tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) ); + + if ( aRect.IsEmpty() ) + return; + aRect.Normalize(); + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + SalInvert nSalFlags = SalInvert::NONE; + if ( nFlags & InvertFlags::N50 ) + nSalFlags |= SalInvert::N50; + if ( nFlags & InvertFlags::TrackFrame ) + nSalFlags |= SalInvert::TrackFrame; + mpGraphics->Invert( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), nSalFlags, *this ); +} + +void OutputDevice::Invert( const tools::Polygon& rPoly, InvertFlags nFlags ) +{ + assert(!is_double_buffered_window()); + if ( !IsDeviceOutputNecessary() ) + return; + + sal_uInt16 nPoints = rPoly.GetSize(); + + if ( nPoints < 2 ) + return; + + tools::Polygon aPoly( ImplLogicToDevicePixel( rPoly ) ); + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + SalInvert nSalFlags = SalInvert::NONE; + if ( nFlags & InvertFlags::N50 ) + nSalFlags |= SalInvert::N50; + if ( nFlags & InvertFlags::TrackFrame ) + nSalFlags |= SalInvert::TrackFrame; + const Point* pPtAry = aPoly.GetConstPointAry(); + mpGraphics->Invert( nPoints, pPtAry, nSalFlags, *this ); +} + +void OutputDevice::DrawCheckered(const Point& rPos, const Size& rSize, sal_uInt32 nLen, Color aStart, Color aEnd) +{ + assert(!is_double_buffered_window()); + + const sal_uInt32 nMaxX(rPos.X() + rSize.Width()); + const sal_uInt32 nMaxY(rPos.Y() + rSize.Height()); + + Push(vcl::PushFlags::LINECOLOR|vcl::PushFlags::FILLCOLOR); + SetLineColor(); + + for(sal_uInt32 x(0), nX(rPos.X()); nX < nMaxX; x++, nX += nLen) + { + const sal_uInt32 nRight(std::min(nMaxX, nX + nLen)); + + for(sal_uInt32 y(0), nY(rPos.Y()); nY < nMaxY; y++, nY += nLen) + { + const sal_uInt32 nBottom(std::min(nMaxY, nY + nLen)); + + SetFillColor(((x & 0x0001) ^ (y & 0x0001)) ? aStart : aEnd); + DrawRect(tools::Rectangle(nX, nY, nRight, nBottom)); + } + } + + Pop(); +} + +void OutputDevice::DrawGrid( const tools::Rectangle& rRect, const Size& rDist, DrawGridFlags nFlags ) +{ + assert(!is_double_buffered_window()); + + tools::Rectangle aDstRect( PixelToLogic( Point() ), GetOutputSize() ); + aDstRect.Intersection( rRect ); + + if( aDstRect.IsEmpty() || ImplIsRecordLayout() ) + return; + + if( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return; + + const tools::Long nDistX = std::max( rDist.Width(), tools::Long(1) ); + const tools::Long nDistY = std::max( rDist.Height(), tools::Long(1) ); + tools::Long nX = ( rRect.Left() >= aDstRect.Left() ) ? rRect.Left() : ( rRect.Left() + ( ( aDstRect.Left() - rRect.Left() ) / nDistX ) * nDistX ); + tools::Long nY = ( rRect.Top() >= aDstRect.Top() ) ? rRect.Top() : ( rRect.Top() + ( ( aDstRect.Top() - rRect.Top() ) / nDistY ) * nDistY ); + const tools::Long nRight = aDstRect.Right(); + const tools::Long nBottom = aDstRect.Bottom(); + const tools::Long nStartX = ImplLogicXToDevicePixel( nX ); + const tools::Long nEndX = ImplLogicXToDevicePixel( nRight ); + const tools::Long nStartY = ImplLogicYToDevicePixel( nY ); + const tools::Long nEndY = ImplLogicYToDevicePixel( nBottom ); + tools::Long nHorzCount = 0; + tools::Long nVertCount = 0; + + std::vector< sal_Int32 > aVertBuf; + std::vector< sal_Int32 > aHorzBuf; + + if( ( nFlags & DrawGridFlags::Dots ) || ( nFlags & DrawGridFlags::HorzLines ) ) + { + aVertBuf.resize( aDstRect.GetHeight() / nDistY + 2 ); + aVertBuf[ nVertCount++ ] = nStartY; + while( ( nY += nDistY ) <= nBottom ) + { + aVertBuf[ nVertCount++ ] = ImplLogicYToDevicePixel( nY ); + } + } + + if( ( nFlags & DrawGridFlags::Dots ) || ( nFlags & DrawGridFlags::VertLines ) ) + { + aHorzBuf.resize( aDstRect.GetWidth() / nDistX + 2 ); + aHorzBuf[ nHorzCount++ ] = nStartX; + while( ( nX += nDistX ) <= nRight ) + { + aHorzBuf[ nHorzCount++ ] = ImplLogicXToDevicePixel( nX ); + } + } + + if( mbInitLineColor ) + InitLineColor(); + + if( mbInitFillColor ) + InitFillColor(); + + const bool bOldMap = mbMap; + EnableMapMode( false ); + + if( nFlags & DrawGridFlags::Dots ) + { + for( tools::Long i = 0; i < nVertCount; i++ ) + { + for( tools::Long j = 0, Y = aVertBuf[ i ]; j < nHorzCount; j++ ) + { + mpGraphics->DrawPixel( aHorzBuf[ j ], Y, *this ); + } + } + } + else + { + if( nFlags & DrawGridFlags::HorzLines ) + { + for( tools::Long i = 0; i < nVertCount; i++ ) + { + nY = aVertBuf[ i ]; + mpGraphics->DrawLine( nStartX, nY, nEndX, nY, *this ); + } + } + + if( nFlags & DrawGridFlags::VertLines ) + { + for( tools::Long i = 0; i < nHorzCount; i++ ) + { + nX = aHorzBuf[ i ]; + mpGraphics->DrawLine( nX, nStartY, nX, nEndY, *this ); + } + } + } + + EnableMapMode( bOldMap ); + + if( mpAlphaVDev ) + mpAlphaVDev->DrawGrid( rRect, rDist, nFlags ); +} + +BmpMirrorFlags AdjustTwoRect( SalTwoRect& rTwoRect, const Size& rSizePix ) +{ + BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE; + + if ( rTwoRect.mnDestWidth < 0 ) + { + rTwoRect.mnSrcX = rSizePix.Width() - rTwoRect.mnSrcX - rTwoRect.mnSrcWidth; + rTwoRect.mnDestWidth = -rTwoRect.mnDestWidth; + rTwoRect.mnDestX -= rTwoRect.mnDestWidth-1; + nMirrFlags |= BmpMirrorFlags::Horizontal; + } + + if ( rTwoRect.mnDestHeight < 0 ) + { + rTwoRect.mnSrcY = rSizePix.Height() - rTwoRect.mnSrcY - rTwoRect.mnSrcHeight; + rTwoRect.mnDestHeight = -rTwoRect.mnDestHeight; + rTwoRect.mnDestY -= rTwoRect.mnDestHeight-1; + nMirrFlags |= BmpMirrorFlags::Vertical; + } + + if( ( rTwoRect.mnSrcX < 0 ) || ( rTwoRect.mnSrcX >= rSizePix.Width() ) || + ( rTwoRect.mnSrcY < 0 ) || ( rTwoRect.mnSrcY >= rSizePix.Height() ) || + ( ( rTwoRect.mnSrcX + rTwoRect.mnSrcWidth ) > rSizePix.Width() ) || + ( ( rTwoRect.mnSrcY + rTwoRect.mnSrcHeight ) > rSizePix.Height() ) ) + { + const tools::Rectangle aSourceRect( Point( rTwoRect.mnSrcX, rTwoRect.mnSrcY ), + Size( rTwoRect.mnSrcWidth, rTwoRect.mnSrcHeight ) ); + tools::Rectangle aCropRect( aSourceRect ); + + aCropRect.Intersection( tools::Rectangle( Point(), rSizePix ) ); + + if( aCropRect.IsEmpty() ) + { + rTwoRect.mnSrcWidth = rTwoRect.mnSrcHeight = rTwoRect.mnDestWidth = rTwoRect.mnDestHeight = 0; + } + else + { + const double fFactorX = ( rTwoRect.mnSrcWidth > 1 ) ? static_cast<double>( rTwoRect.mnDestWidth - 1 ) / ( rTwoRect.mnSrcWidth - 1 ) : 0.0; + const double fFactorY = ( rTwoRect.mnSrcHeight > 1 ) ? static_cast<double>( rTwoRect.mnDestHeight - 1 ) / ( rTwoRect.mnSrcHeight - 1 ) : 0.0; + + const tools::Long nDstX1 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Left() - rTwoRect.mnSrcX ) ); + const tools::Long nDstY1 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Top() - rTwoRect.mnSrcY ) ); + const tools::Long nDstX2 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Right() - rTwoRect.mnSrcX ) ); + const tools::Long nDstY2 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Bottom() - rTwoRect.mnSrcY ) ); + + rTwoRect.mnSrcX = aCropRect.Left(); + rTwoRect.mnSrcY = aCropRect.Top(); + rTwoRect.mnSrcWidth = aCropRect.GetWidth(); + rTwoRect.mnSrcHeight = aCropRect.GetHeight(); + rTwoRect.mnDestX = nDstX1; + rTwoRect.mnDestY = nDstY1; + rTwoRect.mnDestWidth = nDstX2 - nDstX1 + 1; + rTwoRect.mnDestHeight = nDstY2 - nDstY1 + 1; + } + } + + return nMirrFlags; +} + +void AdjustTwoRect( SalTwoRect& rTwoRect, const tools::Rectangle& rValidSrcRect ) +{ + if( !(( rTwoRect.mnSrcX < rValidSrcRect.Left() ) || ( rTwoRect.mnSrcX >= rValidSrcRect.Right() ) || + ( rTwoRect.mnSrcY < rValidSrcRect.Top() ) || ( rTwoRect.mnSrcY >= rValidSrcRect.Bottom() ) || + ( ( rTwoRect.mnSrcX + rTwoRect.mnSrcWidth ) > rValidSrcRect.Right() ) || + ( ( rTwoRect.mnSrcY + rTwoRect.mnSrcHeight ) > rValidSrcRect.Bottom() )) ) + return; + + const tools::Rectangle aSourceRect( Point( rTwoRect.mnSrcX, rTwoRect.mnSrcY ), + Size( rTwoRect.mnSrcWidth, rTwoRect.mnSrcHeight ) ); + tools::Rectangle aCropRect( aSourceRect ); + + aCropRect.Intersection( rValidSrcRect ); + + if( aCropRect.IsEmpty() ) + { + rTwoRect.mnSrcWidth = rTwoRect.mnSrcHeight = rTwoRect.mnDestWidth = rTwoRect.mnDestHeight = 0; + } + else + { + const double fFactorX = ( rTwoRect.mnSrcWidth > 1 ) ? static_cast<double>( rTwoRect.mnDestWidth - 1 ) / ( rTwoRect.mnSrcWidth - 1 ) : 0.0; + const double fFactorY = ( rTwoRect.mnSrcHeight > 1 ) ? static_cast<double>( rTwoRect.mnDestHeight - 1 ) / ( rTwoRect.mnSrcHeight - 1 ) : 0.0; + + const tools::Long nDstX1 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Left() - rTwoRect.mnSrcX ) ); + const tools::Long nDstY1 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Top() - rTwoRect.mnSrcY ) ); + const tools::Long nDstX2 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Right() - rTwoRect.mnSrcX ) ); + const tools::Long nDstY2 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Bottom() - rTwoRect.mnSrcY ) ); + + rTwoRect.mnSrcX = aCropRect.Left(); + rTwoRect.mnSrcY = aCropRect.Top(); + rTwoRect.mnSrcWidth = aCropRect.GetWidth(); + rTwoRect.mnSrcHeight = aCropRect.GetHeight(); + rTwoRect.mnDestX = nDstX1; + rTwoRect.mnDestY = nDstY1; + rTwoRect.mnDestWidth = nDstX2 - nDstX1 + 1; + rTwoRect.mnDestHeight = nDstY2 - nDstY1 + 1; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/stack.cxx b/vcl/source/outdev/stack.cxx new file mode 100644 index 0000000000..129348051e --- /dev/null +++ b/vcl/source/outdev/stack.cxx @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> +#include <tools/debug.hxx> + +#include <vcl/metaact.hxx> +#include <vcl/rendercontext/State.hxx> +#include <vcl/virdev.hxx> +#include <vcl/settings.hxx> + +#include <drawmode.hxx> +#include <salgdi.hxx> + +void OutputDevice::Push(vcl::PushFlags nFlags) +{ + if (mpMetaFile) + mpMetaFile->AddAction(new MetaPushAction(nFlags)); + + maOutDevStateStack.emplace_back(); + vcl::State& rState = maOutDevStateStack.back(); + + rState.mnFlags = nFlags; + + if (nFlags & vcl::PushFlags::LINECOLOR && mbLineColor) + rState.mpLineColor = maLineColor; + + if (nFlags & vcl::PushFlags::FILLCOLOR && mbFillColor) + rState.mpFillColor = maFillColor; + + if (nFlags & vcl::PushFlags::FONT) + rState.mpFont = maFont; + + if (nFlags & vcl::PushFlags::TEXTCOLOR) + rState.mpTextColor = GetTextColor(); + + if (nFlags & vcl::PushFlags::TEXTFILLCOLOR && IsTextFillColor()) + rState.mpTextFillColor = GetTextFillColor(); + + if (nFlags & vcl::PushFlags::TEXTLINECOLOR && IsTextLineColor()) + rState.mpTextLineColor = GetTextLineColor(); + + if (nFlags & vcl::PushFlags::OVERLINECOLOR && IsOverlineColor()) + rState.mpOverlineColor = GetOverlineColor(); + + if (nFlags & vcl::PushFlags::TEXTALIGN) + rState.meTextAlign = GetTextAlign(); + + if (nFlags & vcl::PushFlags::TEXTLAYOUTMODE) + rState.mnTextLayoutMode = GetLayoutMode(); + + if (nFlags & vcl::PushFlags::TEXTLANGUAGE) + rState.meTextLanguage = GetDigitLanguage(); + + if (nFlags & vcl::PushFlags::RASTEROP) + rState.meRasterOp = GetRasterOp(); + + if (nFlags & vcl::PushFlags::MAPMODE) + { + rState.mpMapMode = maMapMode; + rState.mbMapActive = mbMap; + } + + if (nFlags & vcl::PushFlags::CLIPREGION && mbClipRegion) + rState.mpClipRegion.reset(new vcl::Region(maRegion)); + + if (nFlags & vcl::PushFlags::REFPOINT && mbRefPoint) + rState.mpRefPoint = maRefPoint; + + if (nFlags & vcl::PushFlags::RTLENABLED) + rState.mbRTLEnabled = IsRTLEnabled(); + + if (mpAlphaVDev) + mpAlphaVDev->Push(); +} + +void OutputDevice::Pop() +{ + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaPopAction() ); + + GDIMetaFile* pOldMetaFile = mpMetaFile; + mpMetaFile = nullptr; + + if ( maOutDevStateStack.empty() ) + { + SAL_WARN( "vcl.gdi", "OutputDevice::Pop() without OutputDevice::Push()" ); + return; + } + const vcl::State& rState = maOutDevStateStack.back(); + + if( mpAlphaVDev ) + mpAlphaVDev->Pop(); + + if ( rState.mnFlags & vcl::PushFlags::LINECOLOR ) + { + if ( rState.mpLineColor ) + SetLineColor( *rState.mpLineColor ); + else + SetLineColor(); + } + + if ( rState.mnFlags & vcl::PushFlags::FILLCOLOR ) + { + if ( rState.mpFillColor ) + SetFillColor( *rState.mpFillColor ); + else + SetFillColor(); + } + + if ( rState.mnFlags & vcl::PushFlags::FONT ) + SetFont( *rState.mpFont ); + + if ( rState.mnFlags & vcl::PushFlags::TEXTCOLOR ) + SetTextColor( *rState.mpTextColor ); + + if ( rState.mnFlags & vcl::PushFlags::TEXTFILLCOLOR ) + { + if ( rState.mpTextFillColor ) + SetTextFillColor( *rState.mpTextFillColor ); + else + SetTextFillColor(); + } + + if ( rState.mnFlags & vcl::PushFlags::TEXTLINECOLOR ) + { + if ( rState.mpTextLineColor ) + SetTextLineColor( *rState.mpTextLineColor ); + else + SetTextLineColor(); + } + + if ( rState.mnFlags & vcl::PushFlags::OVERLINECOLOR ) + { + if ( rState.mpOverlineColor ) + SetOverlineColor( *rState.mpOverlineColor ); + else + SetOverlineColor(); + } + + if ( rState.mnFlags & vcl::PushFlags::TEXTALIGN ) + SetTextAlign( rState.meTextAlign ); + + if( rState.mnFlags & vcl::PushFlags::TEXTLAYOUTMODE ) + SetLayoutMode( rState.mnTextLayoutMode ); + + if( rState.mnFlags & vcl::PushFlags::TEXTLANGUAGE ) + SetDigitLanguage( rState.meTextLanguage ); + + if ( rState.mnFlags & vcl::PushFlags::RASTEROP ) + SetRasterOp( rState.meRasterOp ); + + if ( rState.mnFlags & vcl::PushFlags::MAPMODE ) + { + if ( rState.mpMapMode ) + SetMapMode( *rState.mpMapMode ); + else + SetMapMode(); + mbMap = rState.mbMapActive; + } + + if ( rState.mnFlags & vcl::PushFlags::CLIPREGION ) + SetDeviceClipRegion( rState.mpClipRegion.get() ); + + if ( rState.mnFlags & vcl::PushFlags::REFPOINT ) + { + if ( rState.mpRefPoint ) + SetRefPoint( *rState.mpRefPoint ); + else + SetRefPoint(); + } + + if ( rState.mnFlags & vcl::PushFlags::RTLENABLED ) + EnableRTL( rState.mbRTLEnabled ); + + maOutDevStateStack.pop_back(); + + mpMetaFile = pOldMetaFile; +} + +void OutputDevice::ClearStack() +{ + sal_uInt32 nCount = maOutDevStateStack.size(); + while( nCount-- ) + Pop(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/text.cxx b/vcl/source/outdev/text.cxx new file mode 100644 index 0000000000..1b40a1b2de --- /dev/null +++ b/vcl/source/outdev/text.cxx @@ -0,0 +1,2106 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <sal/log.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <tools/lineend.hxx> +#include <tools/debug.hxx> +#include <unotools/configmgr.hxx> + +#include <vcl/ctrl.hxx> +#include <vcl/metaact.hxx> +#include <vcl/metric.hxx> +#include <vcl/mnemonic.hxx> +#include <vcl/textrectinfo.hxx> +#include <vcl/virdev.hxx> +#include <vcl/sysdata.hxx> + +#include <ImplLayoutArgs.hxx> +#include <ImplOutDevData.hxx> +#include <drawmode.hxx> +#include <salgdi.hxx> +#include <svdata.hxx> +#include <textlayout.hxx> +#include <textlineinfo.hxx> +#include <impglyphitem.hxx> +#include <TextLayoutCache.hxx> +#include <font/PhysicalFontFace.hxx> + +#include <memory> +#include <optional> + +#define TEXT_DRAW_ELLIPSIS (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) + +void OutputDevice::SetLayoutMode( vcl::text::ComplexTextLayoutFlags nTextLayoutMode ) +{ + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaLayoutModeAction( nTextLayoutMode ) ); + + mnTextLayoutMode = nTextLayoutMode; + + if( mpAlphaVDev ) + mpAlphaVDev->SetLayoutMode( nTextLayoutMode ); +} + +void OutputDevice::SetDigitLanguage( LanguageType eTextLanguage ) +{ + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextLanguageAction( eTextLanguage ) ); + + meTextLanguage = eTextLanguage; + + if( mpAlphaVDev ) + mpAlphaVDev->SetDigitLanguage( eTextLanguage ); +} + +void OutputDevice::ImplInitTextColor() +{ + DBG_TESTSOLARMUTEX(); + + if ( mbInitTextColor ) + { + mpGraphics->SetTextColor( GetTextColor() ); + mbInitTextColor = false; + } +} + +OUString OutputDevice::GetEllipsisString( const OUString& rOrigStr, tools::Long nMaxWidth, + DrawTextFlags nStyle ) const +{ + vcl::DefaultTextLayout aTextLayout(*const_cast< OutputDevice* >(this)); + return aTextLayout.GetEllipsisString(rOrigStr, nMaxWidth, nStyle); +} + +void OutputDevice::ImplDrawTextRect( tools::Long nBaseX, tools::Long nBaseY, + tools::Long nDistX, tools::Long nDistY, tools::Long nWidth, tools::Long nHeight ) +{ + tools::Long nX = nDistX; + tools::Long nY = nDistY; + + Degree10 nOrientation = mpFontInstance->mnOrientation; + if ( nOrientation ) + { + // Rotate rect without rounding problems for 90 degree rotations + if ( !(nOrientation % 900_deg10) ) + { + if ( nOrientation == 900_deg10 ) + { + tools::Long nTemp = nX; + nX = nY; + nY = -nTemp; + nTemp = nWidth; + nWidth = nHeight; + nHeight = nTemp; + nY -= nHeight; + } + else if ( nOrientation == 1800_deg10 ) + { + nX = -nX; + nY = -nY; + nX -= nWidth; + nY -= nHeight; + } + else /* ( nOrientation == 2700 ) */ + { + tools::Long nTemp = nX; + nX = -nY; + nY = nTemp; + nTemp = nWidth; + nWidth = nHeight; + nHeight = nTemp; + nX -= nWidth; + } + } + else + { + nX += nBaseX; + nY += nBaseY; + // inflate because polygons are drawn smaller + tools::Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) ); + tools::Polygon aPoly( aRect ); + aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontInstance->mnOrientation ); + ImplDrawPolygon( aPoly ); + return; + } + } + + nX += nBaseX; + nY += nBaseY; + mpGraphics->DrawRect( nX, nY, nWidth, nHeight, *this ); // original code + +} + +void OutputDevice::ImplDrawTextBackground( const SalLayout& rSalLayout ) +{ + const double nWidth = rSalLayout.GetTextWidth(); + const basegfx::B2DPoint aBase = rSalLayout.DrawBase(); + const tools::Long nX = aBase.getX(); + const tools::Long nY = aBase.getY(); + + if ( mbLineColor || mbInitLineColor ) + { + mpGraphics->SetLineColor(); + mbInitLineColor = true; + } + mpGraphics->SetFillColor( GetTextFillColor() ); + mbInitFillColor = true; + + ImplDrawTextRect( nX, nY, 0, -(mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent), + nWidth, + mpFontInstance->mnLineHeight+mnEmphasisAscent+mnEmphasisDescent ); +} + +tools::Rectangle OutputDevice::ImplGetTextBoundRect( const SalLayout& rSalLayout ) const +{ + basegfx::B2DPoint aPoint = rSalLayout.GetDrawPosition(); + tools::Long nX = aPoint.getX(); + tools::Long nY = aPoint.getY(); + + double nWidth = rSalLayout.GetTextWidth(); + tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent; + + nY -= mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent; + + if ( mpFontInstance->mnOrientation ) + { + tools::Long nBaseX = nX, nBaseY = nY; + if ( !(mpFontInstance->mnOrientation % 900_deg10) ) + { + tools::Long nX2 = nX+nWidth; + tools::Long nY2 = nY+nHeight; + + Point aBasePt( nBaseX, nBaseY ); + aBasePt.RotateAround( nX, nY, mpFontInstance->mnOrientation ); + aBasePt.RotateAround( nX2, nY2, mpFontInstance->mnOrientation ); + nWidth = nX2-nX; + nHeight = nY2-nY; + } + else + { + // inflate by +1+1 because polygons are drawn smaller + tools::Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) ); + tools::Polygon aPoly( aRect ); + aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontInstance->mnOrientation ); + return aPoly.GetBoundRect(); + } + } + + return tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ); +} + +bool OutputDevice::ImplDrawRotateText( SalLayout& rSalLayout ) +{ + tools::Long nX = rSalLayout.DrawBase().getX(); + tools::Long nY = rSalLayout.DrawBase().getY(); + + tools::Rectangle aBoundRect; + rSalLayout.DrawBase() = basegfx::B2DPoint( 0, 0 ); + rSalLayout.DrawOffset() = Point( 0, 0 ); + if (!rSalLayout.GetBoundRect(aBoundRect)) + { + // guess vertical text extents if GetBoundRect failed + double nRight = rSalLayout.GetTextWidth(); + tools::Long nTop = mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent; + tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent; + aBoundRect = tools::Rectangle( 0, -nTop, nRight, nHeight - nTop ); + } + + // cache virtual device for rotation + if (!mpOutDevData->mpRotateDev) + mpOutDevData->mpRotateDev = VclPtr<VirtualDevice>::Create(*this); + VirtualDevice* pVDev = mpOutDevData->mpRotateDev; + + // size it accordingly + if( !pVDev->SetOutputSizePixel( aBoundRect.GetSize() ) ) + return false; + + const vcl::font::FontSelectPattern& rPattern = mpFontInstance->GetFontSelectPattern(); + vcl::Font aFont( GetFont() ); + aFont.SetOrientation( 0_deg10 ); + aFont.SetFontSize( Size( rPattern.mnWidth, rPattern.mnHeight ) ); + pVDev->SetFont( aFont ); + pVDev->SetTextColor( COL_BLACK ); + pVDev->SetTextFillColor(); + if (!pVDev->InitFont()) + return false; + pVDev->ImplInitTextColor(); + + // draw text into upper left corner + rSalLayout.DrawBase().adjustX(-aBoundRect.Left()); + rSalLayout.DrawBase().adjustY(-aBoundRect.Top()); + rSalLayout.DrawText( *pVDev->mpGraphics ); + + Bitmap aBmp = pVDev->GetBitmap( Point(), aBoundRect.GetSize() ); + if ( aBmp.IsEmpty() || !aBmp.Rotate( mpFontInstance->mnOwnOrientation, COL_WHITE ) ) + return false; + + // calculate rotation offset + tools::Polygon aPoly( aBoundRect ); + aPoly.Rotate( Point(), mpFontInstance->mnOwnOrientation ); + Point aPoint = aPoly.GetBoundRect().TopLeft(); + aPoint += Point( nX, nY ); + + // mask output with text colored bitmap + GDIMetaFile* pOldMetaFile = mpMetaFile; + tools::Long nOldOffX = mnOutOffX; + tools::Long nOldOffY = mnOutOffY; + bool bOldMap = mbMap; + + mnOutOffX = 0; + mnOutOffY = 0; + mpMetaFile = nullptr; + EnableMapMode( false ); + + DrawMask( aPoint, aBmp, GetTextColor() ); + + EnableMapMode( bOldMap ); + mnOutOffX = nOldOffX; + mnOutOffY = nOldOffY; + mpMetaFile = pOldMetaFile; + + return true; +} + +void OutputDevice::ImplDrawTextDirect( SalLayout& rSalLayout, + bool bTextLines) +{ + if( mpFontInstance->mnOwnOrientation ) + if( ImplDrawRotateText( rSalLayout ) ) + return; + + auto nOldX = rSalLayout.DrawBase().getX(); + if( HasMirroredGraphics() ) + { + tools::Long w = IsVirtual() ? mnOutWidth : mpGraphics->GetGraphicsWidth(); + auto x = rSalLayout.DrawBase().getX(); + rSalLayout.DrawBase().setX( w - 1 - x ); + if( !IsRTLEnabled() ) + { + OutputDevice *pOutDevRef = this; + // mirror this window back + tools::Long devX = w-pOutDevRef->mnOutWidth-pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX + rSalLayout.DrawBase().setX( devX + ( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().getX() - devX) ) ) ; + } + } + else if( IsRTLEnabled() ) + { + OutputDevice *pOutDevRef = this; + + // mirror this window back + tools::Long devX = pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX + rSalLayout.DrawBase().setX( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().getX() - devX) + devX ); + } + + rSalLayout.DrawText( *mpGraphics ); + rSalLayout.DrawBase().setX( nOldX ); + + if( bTextLines ) + ImplDrawTextLines( rSalLayout, + maFont.GetStrikeout(), maFont.GetUnderline(), maFont.GetOverline(), + maFont.IsWordLineMode(), maFont.IsUnderlineAbove() ); + + // emphasis marks + if( maFont.GetEmphasisMark() & FontEmphasisMark::Style ) + ImplDrawEmphasisMarks( rSalLayout ); +} + +void OutputDevice::ImplDrawSpecialText( SalLayout& rSalLayout ) +{ + Color aOldColor = GetTextColor(); + Color aOldTextLineColor = GetTextLineColor(); + Color aOldOverlineColor = GetOverlineColor(); + FontRelief eRelief = maFont.GetRelief(); + + basegfx::B2DPoint aOrigPos = rSalLayout.DrawBase(); + if ( eRelief != FontRelief::NONE ) + { + Color aReliefColor( COL_LIGHTGRAY ); + Color aTextColor( aOldColor ); + + Color aTextLineColor( aOldTextLineColor ); + Color aOverlineColor( aOldOverlineColor ); + + // we don't have an automatic color, so black is always drawn on white + if ( aTextColor == COL_BLACK ) + aTextColor = COL_WHITE; + if ( aTextLineColor == COL_BLACK ) + aTextLineColor = COL_WHITE; + if ( aOverlineColor == COL_BLACK ) + aOverlineColor = COL_WHITE; + + // relief-color is black for white text, in all other cases + // we set this to LightGray + // coverity[copy_paste_error: FALSE] - this is intentional + if ( aTextColor == COL_WHITE ) + aReliefColor = COL_BLACK; + SetTextLineColor( aReliefColor ); + SetOverlineColor( aReliefColor ); + SetTextColor( aReliefColor ); + ImplInitTextColor(); + + // calculate offset - for high resolution printers the offset + // should be greater so that the effect is visible + tools::Long nOff = 1; + nOff += mnDPIX/300; + + if ( eRelief == FontRelief::Engraved ) + nOff = -nOff; + rSalLayout.DrawOffset() += Point( nOff, nOff); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawOffset() -= Point( nOff, nOff); + + SetTextLineColor( aTextLineColor ); + SetOverlineColor( aOverlineColor ); + SetTextColor( aTextColor ); + ImplInitTextColor(); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + + SetTextLineColor( aOldTextLineColor ); + SetOverlineColor( aOldOverlineColor ); + + if ( aTextColor != aOldColor ) + { + SetTextColor( aOldColor ); + ImplInitTextColor(); + } + } + else + { + if ( maFont.IsShadow() ) + { + tools::Long nOff = 1 + ((mpFontInstance->mnLineHeight-24)/24); + if ( maFont.IsOutline() ) + nOff++; + SetTextLineColor(); + SetOverlineColor(); + if ( (GetTextColor() == COL_BLACK) + || (GetTextColor().GetLuminance() < 8) ) + SetTextColor( COL_LIGHTGRAY ); + else + SetTextColor( COL_BLACK ); + ImplInitTextColor(); + rSalLayout.DrawBase() += basegfx::B2DPoint( nOff, nOff ); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() -= basegfx::B2DPoint( nOff, nOff ); + SetTextColor( aOldColor ); + SetTextLineColor( aOldTextLineColor ); + SetOverlineColor( aOldOverlineColor ); + ImplInitTextColor(); + + if ( !maFont.IsOutline() ) + ImplDrawTextDirect( rSalLayout, mbTextLines ); + } + + if ( maFont.IsOutline() ) + { + rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(-1,-1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+1,+1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(-1,+0); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(-1,+1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+0,+1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+0,-1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+1,-1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + basegfx::B2DPoint(+1,+0); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos; + + SetTextColor( COL_WHITE ); + SetTextLineColor( COL_WHITE ); + SetOverlineColor( COL_WHITE ); + ImplInitTextColor(); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + SetTextColor( aOldColor ); + SetTextLineColor( aOldTextLineColor ); + SetOverlineColor( aOldOverlineColor ); + ImplInitTextColor(); + } + } +} + +void OutputDevice::ImplDrawText( SalLayout& rSalLayout ) +{ + + if( mbInitClipRegion ) + InitClipRegion(); + if( mbOutputClipped ) + return; + if( mbInitTextColor ) + ImplInitTextColor(); + + rSalLayout.DrawBase() += basegfx::B2DPoint(mnTextOffX, mnTextOffY); + + if( IsTextFillColor() ) + ImplDrawTextBackground( rSalLayout ); + + if( mbTextSpecial ) + ImplDrawSpecialText( rSalLayout ); + else + ImplDrawTextDirect( rSalLayout, mbTextLines ); +} + +void OutputDevice::SetTextColor( const Color& rColor ) +{ + + Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings())); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextColorAction( aColor ) ); + + if ( maTextColor != aColor ) + { + maTextColor = aColor; + mbInitTextColor = true; + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextColor( COL_ALPHA_OPAQUE ); +} + +void OutputDevice::SetTextFillColor() +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextFillColorAction( Color(), false ) ); + + if ( maFont.GetColor() != COL_TRANSPARENT ) { + maFont.SetFillColor( COL_TRANSPARENT ); + } + if ( !maFont.IsTransparent() ) + maFont.SetTransparent( true ); + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextFillColor(); +} + +void OutputDevice::SetTextFillColor( const Color& rColor ) +{ + Color aColor(vcl::drawmode::GetFillColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings())); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextFillColorAction( aColor, true ) ); + + if ( maFont.GetFillColor() != aColor ) + maFont.SetFillColor( aColor ); + if ( maFont.IsTransparent() != rColor.IsTransparent() ) + maFont.SetTransparent( rColor.IsTransparent() ); + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextFillColor( COL_ALPHA_OPAQUE ); +} + +Color OutputDevice::GetTextFillColor() const +{ + if ( maFont.IsTransparent() ) + return COL_TRANSPARENT; + else + return maFont.GetFillColor(); +} + +void OutputDevice::SetTextAlign( TextAlign eAlign ) +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextAlignAction( eAlign ) ); + + if ( maFont.GetAlignment() != eAlign ) + { + maFont.SetAlignment( eAlign ); + mbNewFont = true; + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextAlign( eAlign ); +} + +vcl::Region OutputDevice::GetOutputBoundsClipRegion() const +{ + return GetClipRegion(); +} + +const SalLayoutFlags eDefaultLayout = SalLayoutFlags::NONE; + +void OutputDevice::DrawText( const Point& rStartPt, const OUString& rStr, + sal_Int32 nIndex, sal_Int32 nLen, + std::vector< tools::Rectangle >* pVector, OUString* pDisplayText, + const SalLayoutGlyphs* pLayoutCache + ) +{ + assert(!is_double_buffered_window()); + + if( (nLen < 0) || (nIndex + nLen >= rStr.getLength())) + { + nLen = rStr.getLength() - nIndex; + } + + if (mpOutDevData->mpRecordLayout) + { + pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects; + pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText; + } + +#if OSL_DEBUG_LEVEL > 2 + SAL_INFO("vcl.gdi", "OutputDevice::DrawText(\"" << rStr << "\")"); +#endif + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextAction( rStartPt, rStr, nIndex, nLen ) ); + if( pVector ) + { + vcl::Region aClip(GetOutputBoundsClipRegion()); + + if (mpOutDevData->mpRecordLayout) + { + mpOutDevData->mpRecordLayout->m_aLineIndices.push_back( mpOutDevData->mpRecordLayout->m_aDisplayText.getLength() ); + aClip.Intersect( mpOutDevData->maRecordRect ); + } + if( ! aClip.IsNull() ) + { + std::vector< tools::Rectangle > aTmp; + GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, aTmp ); + + bool bInserted = false; + for( std::vector< tools::Rectangle >::const_iterator it = aTmp.begin(); it != aTmp.end(); ++it, nIndex++ ) + { + bool bAppend = false; + + if( aClip.Overlaps( *it ) ) + bAppend = true; + else if( rStr[ nIndex ] == ' ' && bInserted ) + { + std::vector< tools::Rectangle >::const_iterator next = it; + ++next; + if( next != aTmp.end() && aClip.Overlaps( *next ) ) + bAppend = true; + } + + if( bAppend ) + { + pVector->push_back( *it ); + if( pDisplayText ) + *pDisplayText += OUStringChar(rStr[ nIndex ]); + bInserted = true; + } + } + } + else + { + GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, *pVector ); + if( pDisplayText ) + *pDisplayText += rStr.subView( nIndex, nLen ); + } + } + + if ( !IsDeviceOutputNecessary() || pVector ) + return; + + if(mpFontInstance) + // do not use cache with modified string + if(mpFontInstance->mpConversion) + pLayoutCache = nullptr; + + std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, {}, {}, eDefaultLayout, nullptr, pLayoutCache); + if(pSalLayout) + { + ImplDrawText( *pSalLayout ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawText( rStartPt, rStr, nIndex, nLen, pVector, pDisplayText ); +} + +tools::Long OutputDevice::GetTextWidth( const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen, + vcl::text::TextLayoutCache const*const pLayoutCache, + SalLayoutGlyphs const*const pSalLayoutCache) const +{ + + tools::Long nWidth = GetTextArray( rStr, nullptr, nIndex, + nLen, false, pLayoutCache, pSalLayoutCache ); + + return nWidth; +} + +tools::Long OutputDevice::GetTextHeight() const +{ + if (!InitFont()) + return 0; + + tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent; + + if ( mbMap ) + nHeight = ImplDevicePixelToLogicHeight( nHeight ); + + return nHeight; +} + +float OutputDevice::approximate_char_width() const +{ + //note pango uses "The quick brown fox jumps over the lazy dog." for english + //and has a bunch of per-language strings which corresponds somewhat with + //makeRepresentativeText in include/svtools/sampletext.hxx + return GetTextWidth("aemnnxEM") / 8.0; +} + +float OutputDevice::approximate_digit_width() const +{ + return GetTextWidth("0123456789") / 10.0; +} + +void OutputDevice::DrawTextArray( const Point& rStartPt, const OUString& rStr, + KernArraySpan pDXAry, + std::span<const sal_Bool> pKashidaAry, + sal_Int32 nIndex, sal_Int32 nLen, SalLayoutFlags flags, + const SalLayoutGlyphs* pSalLayoutCache ) +{ + assert(!is_double_buffered_window()); + + if( nLen < 0 || nIndex + nLen >= rStr.getLength() ) + { + nLen = rStr.getLength() - nIndex; + } + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen ) ); + + if ( !IsDeviceOutputNecessary() ) + return; + if( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + if( mbInitClipRegion ) + InitClipRegion(); + if( mbOutputClipped ) + return; + + std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry, pKashidaAry, flags, nullptr, pSalLayoutCache); + if( pSalLayout ) + { + ImplDrawText( *pSalLayout ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawTextArray( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen, flags ); +} + +tools::Long OutputDevice::GetTextArray( const OUString& rStr, KernArray* pKernArray, + sal_Int32 nIndex, sal_Int32 nLen, bool bCaret, + vcl::text::TextLayoutCache const*const pLayoutCache, + SalLayoutGlyphs const*const pSalLayoutCache) const +{ + if( nIndex >= rStr.getLength() ) + return 0; // TODO: this looks like a buggy caller? + + if( nLen < 0 || nIndex + nLen >= rStr.getLength() ) + { + nLen = rStr.getLength() - nIndex; + } + + std::vector<sal_Int32>* pDXAry = pKernArray ? &pKernArray->get_subunit_array() : nullptr; + + // do layout + std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, + Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pSalLayoutCache); + if( !pSalLayout ) + { + // The caller expects this to init the elements of pDXAry. + // Adapting all the callers to check that GetTextArray succeeded seems + // too much work. + // Init here to 0 only in the (rare) error case, so that any missing + // element init in the happy case will still be found by tools, + // and hope that is sufficient. + if (pDXAry) + { + pDXAry->resize(nLen); + std::fill(pDXAry->begin(), pDXAry->end(), 0); + } + return 0; + } + + std::unique_ptr<std::vector<double>> xDXPixelArray; + if(pDXAry) + { + xDXPixelArray.reset(new std::vector<double>(nLen)); + } + std::vector<double>* pDXPixelArray = xDXPixelArray.get(); + double nWidth = pSalLayout->FillDXArray(pDXPixelArray, bCaret ? rStr : OUString()); + + // convert virtual char widths to virtual absolute positions + if( pDXPixelArray ) + { + for( int i = 1; i < nLen; ++i ) + { + (*pDXPixelArray)[i] += (*pDXPixelArray)[i - 1]; + } + } + + // convert from font units to logical units + if (pDXPixelArray) + { + int nSubPixelFactor = pKernArray->get_factor(); + if (mbMap) + { + for (int i = 0; i < nLen; ++i) + (*pDXPixelArray)[i] = ImplDevicePixelToLogicWidth((*pDXPixelArray)[i] * nSubPixelFactor); + } + else if (nSubPixelFactor) + { + for (int i = 0; i < nLen; ++i) + (*pDXPixelArray)[i] *= nSubPixelFactor; + } + } + + if (pDXAry) + { + pDXAry->resize(nLen); + for (int i = 0; i < nLen; ++i) + (*pDXAry)[i] = basegfx::fround((*pDXPixelArray)[i]); + } + + if (mbMap) + nWidth = ImplDevicePixelToLogicWidth( nWidth ); + + return basegfx::fround(nWidth); +} + +void OutputDevice::GetCaretPositions( const OUString& rStr, KernArray& rCaretXArray, + sal_Int32 nIndex, sal_Int32 nLen, + const SalLayoutGlyphs* pGlyphs ) const +{ + + if( nIndex >= rStr.getLength() ) + return; + if( nIndex+nLen >= rStr.getLength() ) + nLen = rStr.getLength() - nIndex; + + sal_Int32 nCaretPos = nLen * 2; + std::vector<sal_Int32>& rCaretPos = rCaretXArray.get_subunit_array(); + rCaretPos.resize(nCaretPos); + + // do layout + std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, Point(0, 0), 0, {}, {}, + eDefaultLayout, nullptr, pGlyphs); + if( !pSalLayout ) + { + std::fill(rCaretPos.begin(), rCaretPos.end(), -1); + return; + } + + std::vector<double> aCaretPixelPos; + pSalLayout->GetCaretPositions(aCaretPixelPos, rStr); + + // fixup unknown caret positions + int i; + for (i = 0; i < nCaretPos; ++i) + if (aCaretPixelPos[i] >= 0) + break; + tools::Long nXPos = (i < nCaretPos) ? aCaretPixelPos[i] : -1; + for (i = 0; i < nCaretPos; ++i) + { + if (aCaretPixelPos[i] >= 0) + nXPos = aCaretPixelPos[i]; + else + aCaretPixelPos[i] = nXPos; + } + + // handle window mirroring + if( IsRTLEnabled() ) + { + double nWidth = pSalLayout->GetTextWidth(); + for (i = 0; i < nCaretPos; ++i) + aCaretPixelPos[i] = nWidth - aCaretPixelPos[i] - 1; + } + + int nSubPixelFactor = rCaretXArray.get_factor(); + // convert from font units to logical units + if( mbMap ) + { + for (i = 0; i < nCaretPos; ++i) + aCaretPixelPos[i] = ImplDevicePixelToLogicWidth(aCaretPixelPos[i] * nSubPixelFactor); + } + else if (nSubPixelFactor) + { + for (i = 0; i < nCaretPos; ++i) + aCaretPixelPos[i] *= nSubPixelFactor; + } + + for (i = 0; i < nCaretPos; ++i) + rCaretPos[i] = basegfx::fround(aCaretPixelPos[i]); +} + +void OutputDevice::DrawStretchText( const Point& rStartPt, sal_Int32 nWidth, + const OUString& rStr, + sal_Int32 nIndex, sal_Int32 nLen) +{ + assert(!is_double_buffered_window()); + + if( (nLen < 0) || (nIndex + nLen >= rStr.getLength())) + { + nLen = rStr.getLength() - nIndex; + } + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaStretchTextAction( rStartPt, nWidth, rStr, nIndex, nLen ) ); + + if ( !IsDeviceOutputNecessary() ) + return; + + std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, nWidth); + if( pSalLayout ) + { + ImplDrawText( *pSalLayout ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawStretchText( rStartPt, nWidth, rStr, nIndex, nLen ); +} + +vcl::text::ImplLayoutArgs OutputDevice::ImplPrepareLayoutArgs( OUString& rStr, + const sal_Int32 nMinIndex, const sal_Int32 nLen, + double nPixelWidth, + SalLayoutFlags nLayoutFlags, + vcl::text::TextLayoutCache const*const pLayoutCache) const +{ + assert(nMinIndex >= 0); + assert(nLen >= 0); + + // get string length for calculating extents + sal_Int32 nEndIndex = rStr.getLength(); + if( nMinIndex + nLen < nEndIndex ) + nEndIndex = nMinIndex + nLen; + + // don't bother if there is nothing to do + if( nEndIndex < nMinIndex ) + nEndIndex = nMinIndex; + + nLayoutFlags |= GetBiDiLayoutFlags( rStr, nMinIndex, nEndIndex ); + + if( !maFont.IsKerning() ) + nLayoutFlags |= SalLayoutFlags::DisableKerning; + if( maFont.GetKerning() & FontKerning::Asian ) + nLayoutFlags |= SalLayoutFlags::KerningAsian; + if( maFont.IsVertical() ) + nLayoutFlags |= SalLayoutFlags::Vertical; + if( maFont.IsFixKerning() || + ( mpFontInstance && mpFontInstance->GetFontSelectPattern().GetPitch() == PITCH_FIXED ) ) + nLayoutFlags |= SalLayoutFlags::DisableLigatures; + + if( meTextLanguage ) //TODO: (mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::SubstituteDigits) + { + // disable character localization when no digits used + const sal_Unicode* pBase = rStr.getStr(); + const sal_Unicode* pStr = pBase + nMinIndex; + const sal_Unicode* pEnd = pBase + nEndIndex; + std::optional<OUStringBuffer> xTmpStr; + for( ; pStr < pEnd; ++pStr ) + { + // TODO: are there non-digit localizations? + if( (*pStr >= '0') && (*pStr <= '9') ) + { + // translate characters to local preference + sal_UCS4 cChar = GetLocalizedChar( *pStr, meTextLanguage ); + if( cChar != *pStr ) + { + if (!xTmpStr) + xTmpStr = OUStringBuffer(rStr); + // TODO: are the localized digit surrogates? + (*xTmpStr)[pStr - pBase] = cChar; + } + } + } + if (xTmpStr) + rStr = (*xTmpStr).makeStringAndClear(); + } + + // right align for RTL text, DRAWPOS_REVERSED, RTL window style + bool bRightAlign = bool(mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl); + if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginLeft ) + bRightAlign = false; + else if ( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginRight ) + bRightAlign = true; + // SSA: hack for western office, ie text get right aligned + // for debugging purposes of mirrored UI + bool bRTLWindow = IsRTLEnabled(); + bRightAlign ^= bRTLWindow; + if( bRightAlign ) + nLayoutFlags |= SalLayoutFlags::RightAlign; + + // set layout options + vcl::text::ImplLayoutArgs aLayoutArgs(rStr, nMinIndex, nEndIndex, nLayoutFlags, maFont.GetLanguageTag(), pLayoutCache); + + Degree10 nOrientation = mpFontInstance ? mpFontInstance->mnOrientation : 0_deg10; + aLayoutArgs.SetOrientation( nOrientation ); + + aLayoutArgs.SetLayoutWidth( nPixelWidth ); + + return aLayoutArgs; +} + +SalLayoutFlags OutputDevice::GetBiDiLayoutFlags( std::u16string_view rStr, + const sal_Int32 nMinIndex, + const sal_Int32 nEndIndex ) const +{ + SalLayoutFlags nLayoutFlags = SalLayoutFlags::NONE; + if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl ) + nLayoutFlags |= SalLayoutFlags::BiDiRtl; + if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiStrong ) + nLayoutFlags |= SalLayoutFlags::BiDiStrong; + else if( !(mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl) ) + { + // Disable Bidi if no RTL hint and only known LTR codes used. + bool bAllLtr = true; + for (sal_Int32 i = nMinIndex; i < nEndIndex; i++) + { + // [0x0000, 0x052F] are Latin, Greek and Cyrillic. + // [0x0370, 0x03FF] has a few holes as if Unicode 10.0.0, but + // hopefully no RTL character will be encoded there. + if (rStr[i] > 0x052F) + { + bAllLtr = false; + break; + } + } + if (bAllLtr) + nLayoutFlags |= SalLayoutFlags::BiDiStrong; + } + return nLayoutFlags; +} + +static OutputDevice::FontMappingUseData* fontMappingUseData = nullptr; + +static inline bool IsTrackingFontMappingUse() +{ + return fontMappingUseData != nullptr; +} + +static void TrackFontMappingUse( const vcl::Font& originalFont, const SalLayout* salLayout) +{ + assert(fontMappingUseData); + OUString originalName = originalFont.GetStyleName().isEmpty() + ? originalFont.GetFamilyName() + : originalFont.GetFamilyName() + "/" + originalFont.GetStyleName(); + std::vector<OUString> usedFontNames; + SalLayoutGlyphs glyphs = salLayout->GetGlyphs(); // includes all font fallbacks + int level = 0; + while( const SalLayoutGlyphsImpl* impl = glyphs.Impl(level++)) + { + const vcl::font::PhysicalFontFace* face = impl->GetFont()->GetFontFace(); + OUString name = face->GetStyleName().isEmpty() + ? face->GetFamilyName() + : face->GetFamilyName() + "/" + face->GetStyleName(); + usedFontNames.push_back( name ); + } + for( OutputDevice::FontMappingUseItem& item : *fontMappingUseData ) + { + if( item.mOriginalFont == originalName && item.mUsedFonts == usedFontNames ) + { + ++item.mCount; + return; + } + } + fontMappingUseData->push_back( { originalName, usedFontNames, 1 } ); +} + +void OutputDevice::StartTrackingFontMappingUse() +{ + delete fontMappingUseData; + fontMappingUseData = new FontMappingUseData; +} + +OutputDevice::FontMappingUseData OutputDevice::FinishTrackingFontMappingUse() +{ + if(!fontMappingUseData) + return {}; + FontMappingUseData ret = std::move( *fontMappingUseData ); + delete fontMappingUseData; + fontMappingUseData = nullptr; + return ret; +} + +std::unique_ptr<SalLayout> OutputDevice::ImplLayout(const OUString& rOrigStr, + sal_Int32 nMinIndex, sal_Int32 nLen, + const Point& rLogicalPos, tools::Long nLogicalWidth, + KernArraySpan pDXArray, + std::span<const sal_Bool> pKashidaArray, + SalLayoutFlags flags, + vcl::text::TextLayoutCache const* pLayoutCache, + const SalLayoutGlyphs* pGlyphs) const +{ + if (pGlyphs && !pGlyphs->IsValid()) + { + SAL_WARN("vcl", "Trying to setup invalid cached glyphs - falling back to relayout!"); + pGlyphs = nullptr; + } +#ifdef DBG_UTIL + if (pGlyphs) + { + for( int level = 0;; ++level ) + { + SalLayoutGlyphsImpl* glyphsImpl = pGlyphs->Impl(level); + if(glyphsImpl == nullptr) + break; + // It is allowed to reuse only glyphs created with SalLayoutFlags::GlyphItemsOnly. + // If the glyphs have already been used, the AdjustLayout() call below might have + // altered them (MultiSalLayout::ImplAdjustMultiLayout() drops glyphs that need + // fallback from the base layout, but then GenericSalLayout::LayoutText() + // would not know to call SetNeedFallback()). + assert(glyphsImpl->GetFlags() & SalLayoutFlags::GlyphItemsOnly); + } + } +#endif + + if (!InitFont()) + return nullptr; + + // check string index and length + if( -1 == nLen || nMinIndex + nLen > rOrigStr.getLength() ) + { + const sal_Int32 nNewLen = rOrigStr.getLength() - nMinIndex; + if( nNewLen <= 0 ) + return nullptr; + nLen = nNewLen; + } + + OUString aStr = rOrigStr; + + // recode string if needed + if( mpFontInstance->mpConversion ) { + mpFontInstance->mpConversion->RecodeString( aStr, 0, aStr.getLength() ); + pLayoutCache = nullptr; // don't use cache with modified string! + pGlyphs = nullptr; + } + + double nPixelWidth = nLogicalWidth; + if( nLogicalWidth && mbMap ) + { + // convert from logical units to physical units + nPixelWidth = ImplLogicWidthToDeviceSubPixel(nLogicalWidth); + } + + vcl::text::ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nMinIndex, nLen, + nPixelWidth, flags, pLayoutCache); + + double nEndGlyphCoord(0); + std::unique_ptr<double[]> xDXPixelArray; + if( !pDXArray.empty() ) + { + xDXPixelArray.reset(new double[nLen]); + + if (mbMap) + { + // convert from logical units to font units without rounding, + // keeping accuracy for lower levels + int nSubPixels = pDXArray.get_factor(); + for (int i = 0; i < nLen; ++i) + xDXPixelArray[i] = ImplLogicWidthToDeviceSubPixel(pDXArray.get_subunit(i)) / nSubPixels; + nEndGlyphCoord = xDXPixelArray[nLen - 1]; + } + else + { + for(int i = 0; i < nLen; ++i) + xDXPixelArray[i] = pDXArray.get(i); + nEndGlyphCoord = std::lround(xDXPixelArray[nLen - 1]); + } + + aLayoutArgs.SetDXArray(xDXPixelArray.get()); + } + + if (!pKashidaArray.empty()) + aLayoutArgs.SetKashidaArray(pKashidaArray.data()); + + // get matching layout object for base font + std::unique_ptr<SalLayout> pSalLayout = mpGraphics->GetTextLayout(0); + + if (pSalLayout) + pSalLayout->SetSubpixelPositioning(mbMap); + + // layout text + if( pSalLayout && !pSalLayout->LayoutText( aLayoutArgs, pGlyphs ? pGlyphs->Impl(0) : nullptr ) ) + { + pSalLayout.reset(); + } + + if( !pSalLayout ) + return nullptr; + + // do glyph fallback if needed + // #105768# avoid fallback for very small font sizes + if (aLayoutArgs.HasFallbackRun() && mpFontInstance->GetFontSelectPattern().mnHeight >= 3) + pSalLayout = ImplGlyphFallbackLayout(std::move(pSalLayout), aLayoutArgs, pGlyphs); + + if (flags & SalLayoutFlags::GlyphItemsOnly) + // Return glyph items only after fallback handling. Otherwise they may + // contain invalid glyph IDs. + return pSalLayout; + + // position, justify, etc. the layout + pSalLayout->AdjustLayout( aLayoutArgs ); + + // default to on for pdf export, which uses SubPixelToLogic to convert back to + // the logical coord space, of if we are scaling/mapping + if (mbMap || meOutDevType == OUTDEV_PDF) + pSalLayout->DrawBase() = ImplLogicToDeviceSubPixel(rLogicalPos); + else + { + Point aDevicePos = ImplLogicToDevicePixel(rLogicalPos); + pSalLayout->DrawBase() = basegfx::B2DPoint(aDevicePos.X(), aDevicePos.Y()); + } + + // adjust to right alignment if necessary + if( aLayoutArgs.mnFlags & SalLayoutFlags::RightAlign ) + { + double nRTLOffset; + if (!pDXArray.empty()) + nRTLOffset = nEndGlyphCoord; + else if( nPixelWidth ) + nRTLOffset = nPixelWidth; + else + nRTLOffset = pSalLayout->GetTextWidth(); + pSalLayout->DrawOffset().setX( 1 - nRTLOffset ); + } + + if(IsTrackingFontMappingUse()) + TrackFontMappingUse(GetFont(), pSalLayout.get()); + + return pSalLayout; +} + +std::shared_ptr<const vcl::text::TextLayoutCache> OutputDevice::CreateTextLayoutCache( + OUString const& rString) +{ + return vcl::text::TextLayoutCache::Create(rString); +} + +bool OutputDevice::GetTextIsRTL( const OUString& rString, sal_Int32 nIndex, sal_Int32 nLen ) const +{ + OUString aStr( rString ); + vcl::text::ImplLayoutArgs aArgs = ImplPrepareLayoutArgs(aStr, nIndex, nLen, 0); + bool bRTL = false; + int nCharPos = -1; + if (!aArgs.GetNextPos(&nCharPos, &bRTL)) + return false; + return (nCharPos != nIndex); +} + +sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, tools::Long nTextWidth, + sal_Int32 nIndex, sal_Int32 nLen, + tools::Long nCharExtra, + vcl::text::TextLayoutCache const*const pLayoutCache, + const SalLayoutGlyphs* pGlyphs) const +{ + std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen, + Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pGlyphs); + sal_Int32 nRetVal = -1; + if( pSalLayout ) + { + // convert logical widths into layout units + // NOTE: be very careful to avoid rounding errors for nCharExtra case + // problem with rounding errors especially for small nCharExtras + // TODO: remove when layout units have subpixel granularity + tools::Long nSubPixelFactor = 1; + if (!mbMap) + nSubPixelFactor = 64; + double nTextPixelWidth = ImplLogicWidthToDeviceSubPixel(nTextWidth * nSubPixelFactor); + double nExtraPixelWidth = 0; + if( nCharExtra != 0 ) + nExtraPixelWidth = ImplLogicWidthToDeviceSubPixel(nCharExtra * nSubPixelFactor); + nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor ); + } + + return nRetVal; +} + +sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, tools::Long nTextWidth, + sal_Unicode nHyphenChar, sal_Int32& rHyphenPos, + sal_Int32 nIndex, sal_Int32 nLen, + tools::Long nCharExtra, + vcl::text::TextLayoutCache const*const pLayoutCache, + const SalLayoutGlyphs* pGlyphs) const +{ + rHyphenPos = -1; + + std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen, + Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pGlyphs); + sal_Int32 nRetVal = -1; + if( pSalLayout ) + { + // convert logical widths into layout units + // NOTE: be very careful to avoid rounding errors for nCharExtra case + // problem with rounding errors especially for small nCharExtras + // TODO: remove when layout units have subpixel granularity + tools::Long nSubPixelFactor = 1; + if (!mbMap) + nSubPixelFactor = 64; + + double nTextPixelWidth = ImplLogicWidthToDeviceSubPixel(nTextWidth * nSubPixelFactor); + double nExtraPixelWidth = 0; + if( nCharExtra != 0 ) + nExtraPixelWidth = ImplLogicWidthToDeviceSubPixel(nCharExtra * nSubPixelFactor); + + // calculate un-hyphenated break position + nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor ); + + // calculate hyphenated break position + OUString aHyphenStr(nHyphenChar); + std::unique_ptr<SalLayout> pHyphenLayout = ImplLayout( aHyphenStr, 0, 1 ); + if( pHyphenLayout ) + { + // calculate subpixel width of hyphenation character + double nHyphenPixelWidth = pHyphenLayout->GetTextWidth() * nSubPixelFactor; + + // calculate hyphenated break position + nTextPixelWidth -= nHyphenPixelWidth; + if( nExtraPixelWidth > 0 ) + nTextPixelWidth -= nExtraPixelWidth; + + rHyphenPos = pSalLayout->GetTextBreak(nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor); + + if( rHyphenPos > nRetVal ) + rHyphenPos = nRetVal; + } + } + + return nRetVal; +} + +void OutputDevice::ImplDrawText( OutputDevice& rTargetDevice, const tools::Rectangle& rRect, + const OUString& rOrigStr, DrawTextFlags nStyle, + std::vector< tools::Rectangle >* pVector, OUString* pDisplayText, + vcl::TextLayoutCommon& _rLayout ) +{ + + Color aOldTextColor; + Color aOldTextFillColor; + bool bRestoreFillColor = false; + if ( (nStyle & DrawTextFlags::Disable) && ! pVector ) + { + bool bHighContrastBlack = false; + bool bHighContrastWhite = false; + const StyleSettings& rStyleSettings( rTargetDevice.GetSettings().GetStyleSettings() ); + if( rStyleSettings.GetHighContrastMode() ) + { + Color aCol; + if( rTargetDevice.IsBackground() ) + aCol = rTargetDevice.GetBackground().GetColor(); + else + // best guess is the face color here + // but it may be totally wrong. the background color + // was typically already reset + aCol = rStyleSettings.GetFaceColor(); + + bHighContrastBlack = aCol.IsDark(); + bHighContrastWhite = aCol.IsBright(); + } + + aOldTextColor = rTargetDevice.GetTextColor(); + if ( rTargetDevice.IsTextFillColor() ) + { + bRestoreFillColor = true; + aOldTextFillColor = rTargetDevice.GetTextFillColor(); + } + if( bHighContrastBlack ) + rTargetDevice.SetTextColor( COL_GREEN ); + else if( bHighContrastWhite ) + rTargetDevice.SetTextColor( COL_LIGHTGREEN ); + else + { + // draw disabled text always without shadow + // as it fits better with native look + rTargetDevice.SetTextColor( rTargetDevice.GetSettings().GetStyleSettings().GetDisableColor() ); + } + } + + tools::Long nWidth = rRect.GetWidth(); + tools::Long nHeight = rRect.GetHeight(); + + if (nWidth <= 0 || nHeight <= 0) + { + if (nStyle & DrawTextFlags::Clip) + return; + static bool bFuzzing = utl::ConfigManager::IsFuzzing(); + SAL_WARN_IF(bFuzzing, "vcl", "skipping negative rectangle of: " << nWidth << " x " << nHeight); + if (bFuzzing) + return; + } + + Point aPos = rRect.TopLeft(); + + tools::Long nTextHeight = rTargetDevice.GetTextHeight(); + TextAlign eAlign = rTargetDevice.GetTextAlign(); + sal_Int32 nMnemonicPos = -1; + + OUString aStr = rOrigStr; + if ( nStyle & DrawTextFlags::Mnemonic ) + aStr = removeMnemonicFromString( aStr, nMnemonicPos ); + + const bool bDrawMnemonics = !(rTargetDevice.GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector; + + // We treat multiline text differently + if ( nStyle & DrawTextFlags::MultiLine ) + { + + ImplMultiTextLineInfo aMultiLineInfo; + sal_Int32 i; + sal_Int32 nFormatLines; + + if ( nTextHeight ) + { + tools::Long nMaxTextWidth = _rLayout.GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle); + sal_Int32 nLines = static_cast<sal_Int32>(nHeight/nTextHeight); + OUString aLastLine; + nFormatLines = aMultiLineInfo.Count(); + if (nLines <= 0) + nLines = 1; + if ( nFormatLines > nLines ) + { + if ( nStyle & DrawTextFlags::EndEllipsis ) + { + // Create last line and shorten it + nFormatLines = nLines-1; + + ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines ); + aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF); + // Replace all LineFeeds with Spaces + OUStringBuffer aLastLineBuffer(aLastLine); + sal_Int32 nLastLineLen = aLastLineBuffer.getLength(); + for ( i = 0; i < nLastLineLen; i++ ) + { + if ( aLastLineBuffer[ i ] == '\n' ) + aLastLineBuffer[ i ] = ' '; + } + aLastLine = aLastLineBuffer.makeStringAndClear(); + aLastLine = _rLayout.GetEllipsisString(aLastLine, nWidth, nStyle); + nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom); + nStyle |= DrawTextFlags::Top; + } + } + else + { + if ( nMaxTextWidth <= nWidth ) + nStyle &= ~DrawTextFlags::Clip; + } + + // Do we need to clip the height? + if ( nFormatLines*nTextHeight > nHeight ) + nStyle |= DrawTextFlags::Clip; + + // Set clipping + if ( nStyle & DrawTextFlags::Clip ) + { + rTargetDevice.Push( vcl::PushFlags::CLIPREGION ); + rTargetDevice.IntersectClipRegion( rRect ); + } + + // Vertical alignment + if ( nStyle & DrawTextFlags::Bottom ) + aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) ); + else if ( nStyle & DrawTextFlags::VCenter ) + aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 ); + + // Font alignment + if ( eAlign == ALIGN_BOTTOM ) + aPos.AdjustY(nTextHeight ); + else if ( eAlign == ALIGN_BASELINE ) + aPos.AdjustY(rTargetDevice.GetFontMetric().GetAscent() ); + + // Output all lines except for the last one + for ( i = 0; i < nFormatLines; i++ ) + { + ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i ); + if ( nStyle & DrawTextFlags::Right ) + aPos.AdjustX(nWidth-rLineInfo.GetWidth() ); + else if ( nStyle & DrawTextFlags::Center ) + aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 ); + sal_Int32 nIndex = rLineInfo.GetIndex(); + sal_Int32 nLineLen = rLineInfo.GetLen(); + _rLayout.DrawText( aPos, aStr, nIndex, nLineLen, pVector, pDisplayText ); + if ( bDrawMnemonics ) + { + if ( (nMnemonicPos >= nIndex) && (nMnemonicPos < nIndex+nLineLen) ) + { + tools::Long nMnemonicX; + tools::Long nMnemonicY; + + KernArray aDXArray; + _rLayout.GetTextArray(aStr, &aDXArray, nIndex, nLineLen, true); + sal_Int32 nPos = nMnemonicPos - nIndex; + sal_Int32 lc_x1 = nPos ? aDXArray[nPos - 1] : 0; + sal_Int32 lc_x2 = aDXArray[nPos]; + double nMnemonicWidth = rTargetDevice.ImplLogicWidthToDeviceSubPixel(std::abs(lc_x1 - lc_x2)); + + Point aTempPos = rTargetDevice.LogicToPixel( aPos ); + nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min( lc_x1, lc_x2 ) ); + nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() ); + rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); + } + } + aPos.AdjustY(nTextHeight ); + aPos.setX( rRect.Left() ); + } + + // If there still is a last line, we output it left-aligned as the line would be clipped + if ( !aLastLine.isEmpty() ) + _rLayout.DrawText( aPos, aLastLine, 0, aLastLine.getLength(), pVector, pDisplayText ); + + // Reset clipping + if ( nStyle & DrawTextFlags::Clip ) + rTargetDevice.Pop(); + } + } + else + { + tools::Long nTextWidth = _rLayout.GetTextWidth( aStr, 0, -1 ); + + // Clip text if needed + if ( nTextWidth > nWidth ) + { + if ( nStyle & TEXT_DRAW_ELLIPSIS ) + { + aStr = _rLayout.GetEllipsisString(aStr, nWidth, nStyle); + nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right); + nStyle |= DrawTextFlags::Left; + nTextWidth = _rLayout.GetTextWidth( aStr, 0, aStr.getLength() ); + } + } + else + { + if ( nTextHeight <= nHeight ) + nStyle &= ~DrawTextFlags::Clip; + } + + // horizontal text alignment + if ( nStyle & DrawTextFlags::Right ) + aPos.AdjustX(nWidth-nTextWidth ); + else if ( nStyle & DrawTextFlags::Center ) + aPos.AdjustX((nWidth-nTextWidth)/2 ); + + // vertical font alignment + if ( eAlign == ALIGN_BOTTOM ) + aPos.AdjustY(nTextHeight ); + else if ( eAlign == ALIGN_BASELINE ) + aPos.AdjustY(rTargetDevice.GetFontMetric().GetAscent() ); + + if ( nStyle & DrawTextFlags::Bottom ) + aPos.AdjustY(nHeight-nTextHeight ); + else if ( nStyle & DrawTextFlags::VCenter ) + aPos.AdjustY((nHeight-nTextHeight)/2 ); + + tools::Long nMnemonicX = 0; + tools::Long nMnemonicY = 0; + double nMnemonicWidth = 0; + if (nMnemonicPos != -1 && nMnemonicPos < aStr.getLength()) + { + KernArray aDXArray; + _rLayout.GetTextArray(aStr, &aDXArray, 0, aStr.getLength(), true); + tools::Long lc_x1 = nMnemonicPos? aDXArray[nMnemonicPos - 1] : 0; + tools::Long lc_x2 = aDXArray[nMnemonicPos]; + nMnemonicWidth = rTargetDevice.ImplLogicWidthToDeviceSubPixel(std::abs(lc_x1 - lc_x2)); + + Point aTempPos = rTargetDevice.LogicToPixel( aPos ); + nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min(lc_x1, lc_x2) ); + nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() ); + } + + if ( nStyle & DrawTextFlags::Clip ) + { + rTargetDevice.Push( vcl::PushFlags::CLIPREGION ); + rTargetDevice.IntersectClipRegion( rRect ); + _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText ); + if ( bDrawMnemonics && nMnemonicPos != -1 ) + rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); + rTargetDevice.Pop(); + } + else + { + _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText ); + if ( bDrawMnemonics && nMnemonicPos != -1 ) + rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); + } + } + + if ( nStyle & DrawTextFlags::Disable && !pVector ) + { + rTargetDevice.SetTextColor( aOldTextColor ); + if ( bRestoreFillColor ) + rTargetDevice.SetTextFillColor( aOldTextFillColor ); + } +} + +void OutputDevice::AddTextRectActions( const tools::Rectangle& rRect, + const OUString& rOrigStr, + DrawTextFlags nStyle, + GDIMetaFile& rMtf ) +{ + + if ( rOrigStr.isEmpty() || rRect.IsEmpty() ) + return; + + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + if( mbInitClipRegion ) + InitClipRegion(); + + // temporarily swap in passed mtf for action generation, and + // disable output generation. + const bool bOutputEnabled( IsOutputEnabled() ); + GDIMetaFile* pMtf = mpMetaFile; + + mpMetaFile = &rMtf; + EnableOutput( false ); + + // #i47157# Factored out to ImplDrawTextRect(), to be shared + // between us and DrawText() + vcl::DefaultTextLayout aLayout( *this ); + ImplDrawText( *this, rRect, rOrigStr, nStyle, nullptr, nullptr, aLayout ); + + // and restore again + EnableOutput( bOutputEnabled ); + mpMetaFile = pMtf; +} + +void OutputDevice::DrawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle, + std::vector< tools::Rectangle >* pVector, OUString* pDisplayText, + vcl::TextLayoutCommon* _pTextLayout ) +{ + assert(!is_double_buffered_window()); + + if (mpOutDevData->mpRecordLayout) + { + pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects; + pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText; + } + + bool bDecomposeTextRectAction = ( _pTextLayout != nullptr ) && _pTextLayout->DecomposeTextRectAction(); + if ( mpMetaFile && !bDecomposeTextRectAction ) + mpMetaFile->AddAction( new MetaTextRectAction( rRect, rOrigStr, nStyle ) ); + + if ( ( !IsDeviceOutputNecessary() && !pVector && !bDecomposeTextRectAction ) || rOrigStr.isEmpty() || rRect.IsEmpty() ) + return; + + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + if( mbInitClipRegion ) + InitClipRegion(); + if (mbOutputClipped && !bDecomposeTextRectAction && !pDisplayText) + return; + + // temporarily disable mtf action generation (ImplDrawText _does_ + // create MetaActionType::TEXTs otherwise) + GDIMetaFile* pMtf = mpMetaFile; + if ( !bDecomposeTextRectAction ) + mpMetaFile = nullptr; + + // #i47157# Factored out to ImplDrawText(), to be used also + // from AddTextRectActions() + vcl::DefaultTextLayout aDefaultLayout( *this ); + ImplDrawText( *this, rRect, rOrigStr, nStyle, pVector, pDisplayText, _pTextLayout ? *_pTextLayout : aDefaultLayout ); + + // and enable again + mpMetaFile = pMtf; + + if( mpAlphaVDev ) + mpAlphaVDev->DrawText( rRect, rOrigStr, nStyle, pVector, pDisplayText ); +} + +tools::Rectangle OutputDevice::GetTextRect( const tools::Rectangle& rRect, + const OUString& rStr, DrawTextFlags nStyle, + TextRectInfo* pInfo, + const vcl::TextLayoutCommon* _pTextLayout ) const +{ + + tools::Rectangle aRect = rRect; + sal_Int32 nLines; + tools::Long nWidth = rRect.GetWidth(); + tools::Long nMaxWidth; + tools::Long nTextHeight = GetTextHeight(); + + OUString aStr = rStr; + if ( nStyle & DrawTextFlags::Mnemonic ) + aStr = removeMnemonicFromString( aStr ); + + if ( nStyle & DrawTextFlags::MultiLine ) + { + ImplMultiTextLineInfo aMultiLineInfo; + sal_Int32 nFormatLines; + sal_Int32 i; + + nMaxWidth = 0; + vcl::DefaultTextLayout aDefaultLayout( *const_cast< OutputDevice* >( this ) ); + + if (_pTextLayout) + const_cast<vcl::TextLayoutCommon*>(_pTextLayout)->GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle); + else + aDefaultLayout.GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle); + + nFormatLines = aMultiLineInfo.Count(); + if ( !nTextHeight ) + nTextHeight = 1; + nLines = static_cast<sal_uInt16>(aRect.GetHeight()/nTextHeight); + if ( pInfo ) + pInfo->mnLineCount = nFormatLines; + if ( !nLines ) + nLines = 1; + if ( nFormatLines <= nLines ) + nLines = nFormatLines; + else + { + if ( !(nStyle & DrawTextFlags::EndEllipsis) ) + nLines = nFormatLines; + else + { + if ( pInfo ) + pInfo->mbEllipsis = true; + nMaxWidth = nWidth; + } + } + if ( pInfo ) + { + bool bMaxWidth = nMaxWidth == 0; + pInfo->mnMaxWidth = 0; + for ( i = 0; i < nLines; i++ ) + { + ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i ); + if ( bMaxWidth && (rLineInfo.GetWidth() > nMaxWidth) ) + nMaxWidth = rLineInfo.GetWidth(); + if ( rLineInfo.GetWidth() > pInfo->mnMaxWidth ) + pInfo->mnMaxWidth = rLineInfo.GetWidth(); + } + } + else if ( !nMaxWidth ) + { + for ( i = 0; i < nLines; i++ ) + { + ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i ); + if ( rLineInfo.GetWidth() > nMaxWidth ) + nMaxWidth = rLineInfo.GetWidth(); + } + } + } + else + { + nLines = 1; + nMaxWidth = _pTextLayout ? _pTextLayout->GetTextWidth( aStr, 0, aStr.getLength() ) : GetTextWidth( aStr ); + + if ( pInfo ) + { + pInfo->mnLineCount = 1; + pInfo->mnMaxWidth = nMaxWidth; + } + + if ( (nMaxWidth > nWidth) && (nStyle & TEXT_DRAW_ELLIPSIS) ) + { + if ( pInfo ) + pInfo->mbEllipsis = true; + nMaxWidth = nWidth; + } + } + + if ( nStyle & DrawTextFlags::Right ) + aRect.SetLeft( aRect.Right()-nMaxWidth+1 ); + else if ( nStyle & DrawTextFlags::Center ) + { + aRect.AdjustLeft((nWidth-nMaxWidth)/2 ); + aRect.SetRight( aRect.Left()+nMaxWidth-1 ); + } + else + aRect.SetRight( aRect.Left()+nMaxWidth-1 ); + + if ( nStyle & DrawTextFlags::Bottom ) + aRect.SetTop( aRect.Bottom()-(nTextHeight*nLines)+1 ); + else if ( nStyle & DrawTextFlags::VCenter ) + { + aRect.AdjustTop((aRect.GetHeight()-(nTextHeight*nLines))/2 ); + aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 ); + } + else + aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 ); + + // #99188# get rid of rounding problems when using this rect later + if (nStyle & DrawTextFlags::Right) + aRect.AdjustLeft( -1 ); + else + aRect.AdjustRight( 1 ); + + if (maFont.GetOrientation() != 0_deg10) + { + tools::Polygon aRotatedPolygon(aRect); + aRotatedPolygon.Rotate(Point(aRect.GetWidth() / 2, aRect.GetHeight() / 2), maFont.GetOrientation()); + return aRotatedPolygon.GetBoundRect(); + } + + return aRect; +} + +void OutputDevice::DrawCtrlText( const Point& rPos, const OUString& rStr, + sal_Int32 nIndex, sal_Int32 nLen, + DrawTextFlags nStyle, std::vector< tools::Rectangle >* pVector, OUString* pDisplayText, + const SalLayoutGlyphs* pGlyphs ) +{ + assert(!is_double_buffered_window()); + + if( (nLen < 0) || (nIndex + nLen >= rStr.getLength())) + { + nLen = rStr.getLength() - nIndex; + } + + if ( !IsDeviceOutputNecessary() || (nIndex >= rStr.getLength()) ) + return; + + // better get graphics here because ImplDrawMnemonicLine() will not + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + if( mbInitClipRegion ) + InitClipRegion(); + if ( mbOutputClipped ) + return; + + if( nIndex >= rStr.getLength() ) + return; + + if( (nLen < 0) || (nIndex + nLen >= rStr.getLength())) + { + nLen = rStr.getLength() - nIndex; + } + OUString aStr = rStr; + sal_Int32 nMnemonicPos = -1; + + tools::Long nMnemonicX = 0; + tools::Long nMnemonicY = 0; + tools::Long nMnemonicWidth = 0; + if ( (nStyle & DrawTextFlags::Mnemonic) && nLen > 1 ) + { + aStr = removeMnemonicFromString( aStr, nMnemonicPos ); + if ( nMnemonicPos != -1 ) + { + if( nMnemonicPos < nIndex ) + { + --nIndex; + } + else + { + if( nMnemonicPos < (nIndex+nLen) ) + --nLen; + SAL_WARN_IF( nMnemonicPos >= (nIndex+nLen), "vcl", "Mnemonic underline marker after last character" ); + } + bool bInvalidPos = false; + + if( nMnemonicPos >= nLen ) + { + // may occur in BiDi-Strings: the '~' is sometimes found behind the last char + // due to some strange BiDi text editors + // -> place the underline behind the string to indicate a failure + bInvalidPos = true; + nMnemonicPos = nLen-1; + } + + KernArray aDXArray; + GetTextArray(aStr, &aDXArray, nIndex, nLen, true, nullptr, pGlyphs); + sal_Int32 nPos = nMnemonicPos - nIndex; + sal_Int32 lc_x1 = nPos ? aDXArray[nPos - 1] : 0; + sal_Int32 lc_x2 = aDXArray[nPos]; + nMnemonicWidth = std::abs(lc_x1 - lc_x2); + + Point aTempPos( std::min(lc_x1,lc_x2), GetFontMetric().GetAscent() ); + if( bInvalidPos ) // #106952#, place behind the (last) character + aTempPos = Point( std::max(lc_x1,lc_x2), GetFontMetric().GetAscent() ); + + aTempPos += rPos; + aTempPos = LogicToPixel( aTempPos ); + nMnemonicX = mnOutOffX + aTempPos.X(); + nMnemonicY = mnOutOffY + aTempPos.Y(); + } + } + + if ( nStyle & DrawTextFlags::Disable && ! pVector ) + { + Color aOldTextColor; + Color aOldTextFillColor; + bool bRestoreFillColor; + bool bHighContrastBlack = false; + bool bHighContrastWhite = false; + const StyleSettings& rStyleSettings( GetSettings().GetStyleSettings() ); + if( rStyleSettings.GetHighContrastMode() ) + { + if( IsBackground() ) + { + Wallpaper aWall = GetBackground(); + Color aCol = aWall.GetColor(); + bHighContrastBlack = aCol.IsDark(); + bHighContrastWhite = aCol.IsBright(); + } + } + + aOldTextColor = GetTextColor(); + if ( IsTextFillColor() ) + { + bRestoreFillColor = true; + aOldTextFillColor = GetTextFillColor(); + } + else + bRestoreFillColor = false; + + if( bHighContrastBlack ) + SetTextColor( COL_GREEN ); + else if( bHighContrastWhite ) + SetTextColor( COL_LIGHTGREEN ); + else + SetTextColor( GetSettings().GetStyleSettings().GetDisableColor() ); + + DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText ); + if (!(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics)) + { + if ( nMnemonicPos != -1 ) + ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); + } + SetTextColor( aOldTextColor ); + if ( bRestoreFillColor ) + SetTextFillColor( aOldTextFillColor ); + } + else + { + DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText, pGlyphs ); + if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector ) + { + if ( nMnemonicPos != -1 ) + ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawCtrlText( rPos, rStr, nIndex, nLen, nStyle, pVector, pDisplayText ); +} + +tools::Long OutputDevice::GetCtrlTextWidth( const OUString& rStr, const SalLayoutGlyphs* pGlyphs ) const +{ + sal_Int32 nLen = rStr.getLength(); + sal_Int32 nIndex = 0; + + sal_Int32 nMnemonicPos; + OUString aStr = removeMnemonicFromString( rStr, nMnemonicPos ); + if ( nMnemonicPos != -1 ) + { + if ( nMnemonicPos < nIndex ) + nIndex--; + else if (static_cast<sal_uLong>(nMnemonicPos) < static_cast<sal_uLong>(nIndex+nLen)) + nLen--; + } + return GetTextWidth( aStr, nIndex, nLen, nullptr, pGlyphs ); +} + +bool OutputDevice::GetTextBoundRect( tools::Rectangle& rRect, + const OUString& rStr, sal_Int32 nBase, + sal_Int32 nIndex, sal_Int32 nLen, + sal_uLong nLayoutWidth, KernArraySpan pDXAry, + std::span<const sal_Bool> pKashidaAry, + const SalLayoutGlyphs* pGlyphs ) const +{ + bool bRet = false; + rRect.SetEmpty(); + + std::unique_ptr<SalLayout> pSalLayout; + const Point aPoint; + // calculate offset when nBase!=nIndex + double nXOffset = 0; + if( nBase != nIndex ) + { + sal_Int32 nStart = std::min( nBase, nIndex ); + sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart; + pSalLayout = ImplLayout( rStr, nStart, nOfsLen, aPoint, nLayoutWidth, pDXAry, pKashidaAry ); + if( pSalLayout ) + { + nXOffset = pSalLayout->GetTextWidth(); + // TODO: fix offset calculation for Bidi case + if( nBase < nIndex) + nXOffset = -nXOffset; + } + } + + pSalLayout = ImplLayout(rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry, pKashidaAry, eDefaultLayout, + nullptr, pGlyphs); + if( pSalLayout ) + { + tools::Rectangle aPixelRect; + bRet = pSalLayout->GetBoundRect(aPixelRect); + + if( bRet ) + { + Point aRotatedOfs( mnTextOffX, mnTextOffY ); + basegfx::B2DPoint aPos = pSalLayout->GetDrawPosition(basegfx::B2DPoint(nXOffset, 0)); + aRotatedOfs -= Point(aPos.getX(), aPos.getY()); + aPixelRect += aRotatedOfs; + rRect = PixelToLogic( aPixelRect ); + if( mbMap ) + rRect += Point( maMapRes.mnMapOfsX, maMapRes.mnMapOfsY ); + } + } + + return bRet; +} + +bool OutputDevice::GetTextOutlines( basegfx::B2DPolyPolygonVector& rVector, + const OUString& rStr, sal_Int32 nBase, + sal_Int32 nIndex, sal_Int32 nLen, + sal_uLong nLayoutWidth, + KernArraySpan pDXArray, + std::span<const sal_Bool> pKashidaArray ) const +{ + if (!InitFont()) + return false; + + bool bRet = false; + rVector.clear(); + if( nLen < 0 ) + { + nLen = rStr.getLength() - nIndex; + } + rVector.reserve( nLen ); + + // we want to get the Rectangle in logical units, so to + // avoid rounding errors we just size the font in logical units + bool bOldMap = mbMap; + if( bOldMap ) + { + const_cast<OutputDevice&>(*this).mbMap = false; + const_cast<OutputDevice&>(*this).mbNewFont = true; + } + + std::unique_ptr<SalLayout> pSalLayout; + + // calculate offset when nBase!=nIndex + double nXOffset = 0; + if( nBase != nIndex ) + { + sal_Int32 nStart = std::min( nBase, nIndex ); + sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart; + pSalLayout = ImplLayout( rStr, nStart, nOfsLen, Point(0,0), nLayoutWidth, pDXArray, pKashidaArray); + if( pSalLayout ) + { + nXOffset = pSalLayout->GetTextWidth(); + pSalLayout.reset(); + // TODO: fix offset calculation for Bidi case + if( nBase > nIndex) + nXOffset = -nXOffset; + } + } + + pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), nLayoutWidth, pDXArray, pKashidaArray ); + if( pSalLayout ) + { + bRet = pSalLayout->GetOutline(rVector); + if( bRet ) + { + // transform polygon to pixel units + basegfx::B2DHomMatrix aMatrix; + + if (nXOffset || mnTextOffX || mnTextOffY) + { + basegfx::B2DPoint aRotatedOfs(mnTextOffX, mnTextOffY); + aRotatedOfs -= pSalLayout->GetDrawPosition(basegfx::B2DPoint(nXOffset, 0)); + aMatrix.translate( aRotatedOfs.getX(), aRotatedOfs.getY() ); + } + + if( !aMatrix.isIdentity() ) + { + for (auto & elem : rVector) + elem.transform( aMatrix ); + } + } + + pSalLayout.reset(); + } + + if( bOldMap ) + { + // restore original font size and map mode + const_cast<OutputDevice&>(*this).mbMap = bOldMap; + const_cast<OutputDevice&>(*this).mbNewFont = true; + } + + return bRet; +} + +bool OutputDevice::GetTextOutlines( PolyPolyVector& rResultVector, + const OUString& rStr, sal_Int32 nBase, + sal_Int32 nIndex, sal_Int32 nLen, + sal_uLong nLayoutWidth, KernArraySpan pDXArray, + std::span<const sal_Bool> pKashidaArray ) const +{ + rResultVector.clear(); + + // get the basegfx polypolygon vector + basegfx::B2DPolyPolygonVector aB2DPolyPolyVector; + if( !GetTextOutlines( aB2DPolyPolyVector, rStr, nBase, nIndex, nLen, + nLayoutWidth, pDXArray, pKashidaArray ) ) + return false; + + // convert to a tool polypolygon vector + rResultVector.reserve( aB2DPolyPolyVector.size() ); + for (auto const& elem : aB2DPolyPolyVector) + rResultVector.emplace_back(elem); // #i76339# + + return true; +} + +bool OutputDevice::GetTextOutline( tools::PolyPolygon& rPolyPoly, const OUString& rStr ) const +{ + rPolyPoly.Clear(); + + // get the basegfx polypolygon vector + basegfx::B2DPolyPolygonVector aB2DPolyPolyVector; + if( !GetTextOutlines( aB2DPolyPolyVector, rStr, 0/*nBase*/, 0/*nIndex*/, /*nLen*/-1, + /*nLayoutWidth*/0, /*pDXArray*/{} ) ) + return false; + + // convert and merge into a tool polypolygon + for (auto const& elem : aB2DPolyPolyVector) + for(auto const& rB2DPolygon : elem) + rPolyPoly.Insert(tools::Polygon(rB2DPolygon)); // #i76339# + + return true; +} + +void OutputDevice::SetSystemTextColor(SystemTextColorFlags nFlags, bool bEnabled) +{ + if (nFlags & SystemTextColorFlags::Mono) + { + SetTextColor(COL_BLACK); + } + else + { + if (!bEnabled) + { + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + SetTextColor(rStyleSettings.GetDisableColor()); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/textline.cxx b/vcl/source/outdev/textline.cxx new file mode 100644 index 0000000000..84965e87b5 --- /dev/null +++ b/vcl/source/outdev/textline.cxx @@ -0,0 +1,1126 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/types.h> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/WaveLine.hxx> +#include <tools/helpers.hxx> +#include <o3tl/hash_combine.hxx> +#include <o3tl/lru_map.hxx> +#include <unotools/configmgr.hxx> +#include <vcl/lazydelete.hxx> +#include <vcl/metaact.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> + +#include <drawmode.hxx> +#include <salgdi.hxx> +#include <impglyphitem.hxx> + +#include <cassert> + +#define UNDERLINE_LAST LINESTYLE_BOLDWAVE +#define STRIKEOUT_LAST STRIKEOUT_X + +namespace { + struct WavyLineCache final + { + WavyLineCache () : m_aItems( 10 ) {} + + bool find( Color aLineColor, size_t nLineWidth, size_t nWaveHeight, size_t nWordWidth, BitmapEx& rOutput ) + { + Key aKey = { nWaveHeight, sal_uInt32(aLineColor) }; + auto item = m_aItems.find( aKey ); + if ( item == m_aItems.end() ) + return false; + // needs update + if ( item->second.m_aLineWidth != nLineWidth || item->second.m_aWordWidth < nWordWidth ) + { + return false; + } + rOutput = item->second.m_Bitmap; + return true; + } + + void insert( const BitmapEx& aBitmap, const Color& aLineColor, const size_t nLineWidth, const size_t nWaveHeight, const size_t nWordWidth, BitmapEx& rOutput ) + { + Key aKey = { nWaveHeight, sal_uInt32(aLineColor) }; + m_aItems.insert( std::pair< Key, WavyLineCacheItem>( aKey, { nLineWidth, nWordWidth, aBitmap } ) ); + rOutput = aBitmap; + } + + private: + struct WavyLineCacheItem + { + size_t m_aLineWidth; + size_t m_aWordWidth; + BitmapEx m_Bitmap; + }; + + struct Key + { + size_t m_aFirst; + size_t m_aSecond; + bool operator ==( const Key& rOther ) const + { + return ( m_aFirst == rOther.m_aFirst && m_aSecond == rOther.m_aSecond ); + } + }; + + struct Hash + { + size_t operator() ( const Key& rKey ) const + { + size_t aSeed = 0; + o3tl::hash_combine(aSeed, rKey.m_aFirst); + o3tl::hash_combine(aSeed, rKey.m_aSecond); + return aSeed; + } + }; + + o3tl::lru_map< Key, WavyLineCacheItem, Hash > m_aItems; + }; +} + +void OutputDevice::ImplInitTextLineSize() +{ + mpFontInstance->mxFontMetric->ImplInitTextLineSize( this ); +} + +void OutputDevice::ImplInitAboveTextLineSize() +{ + mpFontInstance->mxFontMetric->ImplInitAboveTextLineSize( this ); +} + +void OutputDevice::ImplDrawWavePixel( tools::Long nOriginX, tools::Long nOriginY, + tools::Long nCurX, tools::Long nCurY, + tools::Long nWidth, + Degree10 nOrientation, + SalGraphics* pGraphics, + const OutputDevice& rOutDev, + tools::Long nPixWidth, tools::Long nPixHeight ) +{ + if (nOrientation) + { + Point aPoint( nOriginX, nOriginY ); + aPoint.RotateAround( nCurX, nCurY, nOrientation ); + } + + if (shouldDrawWavePixelAsRect(nWidth)) + { + pGraphics->DrawRect( nCurX, nCurY, nPixWidth, nPixHeight, rOutDev ); + } + else + { + pGraphics->DrawPixel( nCurX, nCurY, rOutDev ); + } +} + +bool OutputDevice::shouldDrawWavePixelAsRect(tools::Long nLineWidth) const +{ + if (nLineWidth > 1) + return true; + + return false; +} + +void OutputDevice::SetWaveLineColors(Color const& rColor, tools::Long nLineWidth) +{ + // On printers that output pixel via DrawRect() + if (nLineWidth > 1) + { + if (mbLineColor || mbInitLineColor) + { + mpGraphics->SetLineColor(); + mbInitLineColor = true; + } + + mpGraphics->SetFillColor( rColor ); + mbInitFillColor = true; + } + else + { + mpGraphics->SetLineColor( rColor ); + mbInitLineColor = true; + } +} + +Size OutputDevice::GetWaveLineSize(tools::Long nLineWidth) const +{ + if (nLineWidth > 1) + return Size(nLineWidth, ((nLineWidth*mnDPIX)+(mnDPIY/2))/mnDPIY); + + return Size(1, 1); +} + +void OutputDevice::ImplDrawWaveLine( tools::Long nBaseX, tools::Long nBaseY, + tools::Long nDistX, tools::Long nDistY, + tools::Long nWidth, tools::Long nHeight, + tools::Long nLineWidth, Degree10 nOrientation, + const Color& rColor ) +{ + if ( !nHeight ) + return; + + tools::Long nStartX = nBaseX + nDistX; + tools::Long nStartY = nBaseY + nDistY; + + // If the height is 1 pixel, it's enough output a line + if ( (nLineWidth == 1) && (nHeight == 1) ) + { + mpGraphics->SetLineColor( rColor ); + mbInitLineColor = true; + + tools::Long nEndX = nStartX+nWidth; + tools::Long nEndY = nStartY; + if ( nOrientation ) + { + Point aOriginPt( nBaseX, nBaseY ); + aOriginPt.RotateAround( nStartX, nStartY, nOrientation ); + aOriginPt.RotateAround( nEndX, nEndY, nOrientation ); + } + mpGraphics->DrawLine( nStartX, nStartY, nEndX, nEndY, *this ); + } + else + { + tools::Long nCurX = nStartX; + tools::Long nCurY = nStartY; + tools::Long nDiffX = 2; + tools::Long nDiffY = nHeight-1; + tools::Long nCount = nWidth; + tools::Long nOffY = -1; + + SetWaveLineColors(rColor, nLineWidth); + Size aSize(GetWaveLineSize(nLineWidth)); + + tools::Long nPixWidth = aSize.Width(); + tools::Long nPixHeight = aSize.Height(); + + if ( !nDiffY ) + { + while ( nWidth ) + { + ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation, + mpGraphics, *this, + nPixWidth, nPixHeight ); + nCurX++; + nWidth--; + } + } + else + { + nCurY += nDiffY; + tools::Long nFreq = nCount / (nDiffX+nDiffY); + while ( nFreq-- ) + { + for( tools::Long i = nDiffY; i; --i ) + { + ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation, + mpGraphics, *this, + nPixWidth, nPixHeight ); + nCurX++; + nCurY += nOffY; + } + for( tools::Long i = nDiffX; i; --i ) + { + ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation, + mpGraphics, *this, + nPixWidth, nPixHeight ); + nCurX++; + } + nOffY = -nOffY; + } + nFreq = nCount % (nDiffX+nDiffY); + if ( nFreq ) + { + for( tools::Long i = nDiffY; i && nFreq; --i, --nFreq ) + { + ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation, + mpGraphics, *this, + nPixWidth, nPixHeight ); + nCurX++; + nCurY += nOffY; + + } + for( tools::Long i = nDiffX; i && nFreq; --i, --nFreq ) + { + ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation, + mpGraphics, *this, + nPixWidth, nPixHeight ); + nCurX++; + } + } + } + } +} + +void OutputDevice::ImplDrawWaveTextLine( tools::Long nBaseX, tools::Long nBaseY, + tools::Long nDistX, tools::Long nDistY, tools::Long nWidth, + FontLineStyle eTextLine, + Color aColor, + bool bIsAbove ) +{ + static bool bFuzzing = utl::ConfigManager::IsFuzzing(); + if (bFuzzing && nWidth > 10000) + { + SAL_WARN("vcl.gdi", "drawLine, skipping suspicious WaveTextLine of length: " + << nWidth << " for fuzzing performance"); + return; + } + + LogicalFontInstance* pFontInstance = mpFontInstance.get(); + tools::Long nLineHeight; + tools::Long nLinePos; + + if ( bIsAbove ) + { + nLineHeight = pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize(); + nLinePos = pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset(); + } + else + { + nLineHeight = pFontInstance->mxFontMetric->GetWavelineUnderlineSize(); + nLinePos = pFontInstance->mxFontMetric->GetWavelineUnderlineOffset(); + } + if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) ) + nLineHeight = 3; + + tools::Long nLineWidth = mnDPIX / 300; + if ( !nLineWidth ) + nLineWidth = 1; + + if ( eTextLine == LINESTYLE_BOLDWAVE ) + nLineWidth *= 2; + + nLinePos += nDistY - (nLineHeight / 2); + + tools::Long nLineWidthHeight = ((nLineWidth * mnDPIX) + (mnDPIY / 2)) / mnDPIY; + if ( eTextLine == LINESTYLE_DOUBLEWAVE ) + { + tools::Long nOrgLineHeight = nLineHeight; + nLineHeight /= 3; + if ( nLineHeight < 2 ) + { + if ( nOrgLineHeight > 1 ) + nLineHeight = 2; + else + nLineHeight = 1; + } + + tools::Long nLineDY = nOrgLineHeight-(nLineHeight*2); + if ( nLineDY < nLineWidthHeight ) + nLineDY = nLineWidthHeight; + + tools::Long nLineDY2 = nLineDY/2; + if ( !nLineDY2 ) + nLineDY2 = 1; + + nLinePos -= nLineWidthHeight-nLineDY2; + ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight, + nLineWidth, mpFontInstance->mnOrientation, aColor ); + nLinePos += nLineWidthHeight+nLineDY; + ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight, + nLineWidth, mpFontInstance->mnOrientation, aColor ); + } + else + { + nLinePos -= nLineWidthHeight/2; + ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight, + nLineWidth, mpFontInstance->mnOrientation, aColor ); + } +} + +void OutputDevice::ImplDrawStraightTextLine( tools::Long nBaseX, tools::Long nBaseY, + tools::Long nDistX, tools::Long nDistY, tools::Long nWidth, + FontLineStyle eTextLine, + Color aColor, + bool bIsAbove ) +{ + static bool bFuzzing = utl::ConfigManager::IsFuzzing(); + if (bFuzzing && nWidth > 100000) + { + SAL_WARN("vcl.gdi", "drawLine, skipping suspicious TextLine of length: " + << nWidth << " for fuzzing performance"); + return; + } + + LogicalFontInstance* pFontInstance = mpFontInstance.get(); + tools::Long nLineHeight = 0; + tools::Long nLinePos = 0; + tools::Long nLinePos2 = 0; + + const tools::Long nY = nDistY; + + if ( eTextLine > UNDERLINE_LAST ) + eTextLine = LINESTYLE_SINGLE; + + switch ( eTextLine ) + { + case LINESTYLE_SINGLE: + case LINESTYLE_DOTTED: + case LINESTYLE_DASH: + case LINESTYLE_LONGDASH: + case LINESTYLE_DASHDOT: + case LINESTYLE_DASHDOTDOT: + if ( bIsAbove ) + { + nLineHeight = pFontInstance->mxFontMetric->GetAboveUnderlineSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetAboveUnderlineOffset(); + } + else + { + nLineHeight = pFontInstance->mxFontMetric->GetUnderlineSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetUnderlineOffset(); + } + break; + case LINESTYLE_BOLD: + case LINESTYLE_BOLDDOTTED: + case LINESTYLE_BOLDDASH: + case LINESTYLE_BOLDLONGDASH: + case LINESTYLE_BOLDDASHDOT: + case LINESTYLE_BOLDDASHDOTDOT: + if ( bIsAbove ) + { + nLineHeight = pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset(); + } + else + { + nLineHeight = pFontInstance->mxFontMetric->GetBoldUnderlineSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetBoldUnderlineOffset(); + } + break; + case LINESTYLE_DOUBLE: + if ( bIsAbove ) + { + nLineHeight = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1(); + nLinePos2 = nY + pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2(); + } + else + { + nLineHeight = pFontInstance->mxFontMetric->GetDoubleUnderlineSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1(); + nLinePos2 = nY + pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2(); + } + break; + default: + break; + } + + if ( !nLineHeight ) + return; + + if ( mbLineColor || mbInitLineColor ) + { + mpGraphics->SetLineColor(); + mbInitLineColor = true; + } + mpGraphics->SetFillColor( aColor ); + mbInitFillColor = true; + + tools::Long nLeft = nDistX; + + switch ( eTextLine ) + { + case LINESTYLE_SINGLE: + case LINESTYLE_BOLD: + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight ); + break; + case LINESTYLE_DOUBLE: + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight ); + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos2, nWidth, nLineHeight ); + break; + case LINESTYLE_DOTTED: + case LINESTYLE_BOLDDOTTED: + { + tools::Long nDotWidth = nLineHeight*mnDPIY; + nDotWidth += mnDPIY/2; + nDotWidth /= mnDPIY; + + tools::Long nTempWidth = nDotWidth; + tools::Long nEnd = nLeft+nWidth; + while ( nLeft < nEnd ) + { + if ( nLeft+nTempWidth > nEnd ) + nTempWidth = nEnd-nLeft; + + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempWidth, nLineHeight ); + nLeft += nDotWidth*2; + } + } + break; + case LINESTYLE_DASH: + case LINESTYLE_LONGDASH: + case LINESTYLE_BOLDDASH: + case LINESTYLE_BOLDLONGDASH: + { + tools::Long nDotWidth = nLineHeight*mnDPIY; + nDotWidth += mnDPIY/2; + nDotWidth /= mnDPIY; + + tools::Long nMinDashWidth; + tools::Long nMinSpaceWidth; + tools::Long nSpaceWidth; + tools::Long nDashWidth; + if ( (eTextLine == LINESTYLE_LONGDASH) || + (eTextLine == LINESTYLE_BOLDLONGDASH) ) + { + nMinDashWidth = nDotWidth*6; + nMinSpaceWidth = nDotWidth*2; + nDashWidth = 200; + nSpaceWidth = 100; + } + else + { + nMinDashWidth = nDotWidth*4; + nMinSpaceWidth = (nDotWidth*150)/100; + nDashWidth = 100; + nSpaceWidth = 50; + } + nDashWidth = ((nDashWidth*mnDPIX)+1270)/2540; + nSpaceWidth = ((nSpaceWidth*mnDPIX)+1270)/2540; + // DashWidth will be increased if the line is getting too thick + // in proportion to the line's length + if ( nDashWidth < nMinDashWidth ) + nDashWidth = nMinDashWidth; + if ( nSpaceWidth < nMinSpaceWidth ) + nSpaceWidth = nMinSpaceWidth; + + tools::Long nTempWidth = nDashWidth; + tools::Long nEnd = nLeft+nWidth; + while ( nLeft < nEnd ) + { + if ( nLeft+nTempWidth > nEnd ) + nTempWidth = nEnd-nLeft; + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempWidth, nLineHeight ); + nLeft += nDashWidth+nSpaceWidth; + } + } + break; + case LINESTYLE_DASHDOT: + case LINESTYLE_BOLDDASHDOT: + { + tools::Long nDotWidth = nLineHeight*mnDPIY; + nDotWidth += mnDPIY/2; + nDotWidth /= mnDPIY; + + tools::Long nDashWidth = ((100*mnDPIX)+1270)/2540; + tools::Long nMinDashWidth = nDotWidth*4; + // DashWidth will be increased if the line is getting too thick + // in proportion to the line's length + if ( nDashWidth < nMinDashWidth ) + nDashWidth = nMinDashWidth; + + tools::Long nTempDotWidth = nDotWidth; + tools::Long nTempDashWidth = nDashWidth; + tools::Long nEnd = nLeft+nWidth; + while ( nLeft < nEnd ) + { + if ( nLeft+nTempDotWidth > nEnd ) + nTempDotWidth = nEnd-nLeft; + + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight ); + nLeft += nDotWidth*2; + if ( nLeft > nEnd ) + break; + + if ( nLeft+nTempDashWidth > nEnd ) + nTempDashWidth = nEnd-nLeft; + + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDashWidth, nLineHeight ); + nLeft += nDashWidth+nDotWidth; + } + } + break; + case LINESTYLE_DASHDOTDOT: + case LINESTYLE_BOLDDASHDOTDOT: + { + tools::Long nDotWidth = nLineHeight*mnDPIY; + nDotWidth += mnDPIY/2; + nDotWidth /= mnDPIY; + + tools::Long nDashWidth = ((100*mnDPIX)+1270)/2540; + tools::Long nMinDashWidth = nDotWidth*4; + // DashWidth will be increased if the line is getting too thick + // in proportion to the line's length + if ( nDashWidth < nMinDashWidth ) + nDashWidth = nMinDashWidth; + + tools::Long nTempDotWidth = nDotWidth; + tools::Long nTempDashWidth = nDashWidth; + tools::Long nEnd = nLeft+nWidth; + while ( nLeft < nEnd ) + { + if ( nLeft+nTempDotWidth > nEnd ) + nTempDotWidth = nEnd-nLeft; + + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight ); + nLeft += nDotWidth*2; + if ( nLeft > nEnd ) + break; + + if ( nLeft+nTempDotWidth > nEnd ) + nTempDotWidth = nEnd-nLeft; + + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight ); + nLeft += nDotWidth*2; + if ( nLeft > nEnd ) + break; + + if ( nLeft+nTempDashWidth > nEnd ) + nTempDashWidth = nEnd-nLeft; + + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDashWidth, nLineHeight ); + nLeft += nDashWidth+nDotWidth; + } + } + break; + default: + break; + } +} + +void OutputDevice::ImplDrawStrikeoutLine( tools::Long nBaseX, tools::Long nBaseY, + tools::Long nDistX, tools::Long nDistY, tools::Long nWidth, + FontStrikeout eStrikeout, + Color aColor ) +{ + LogicalFontInstance* pFontInstance = mpFontInstance.get(); + tools::Long nLineHeight = 0; + tools::Long nLinePos = 0; + tools::Long nLinePos2 = 0; + + tools::Long nY = nDistY; + + if ( eStrikeout > STRIKEOUT_LAST ) + eStrikeout = STRIKEOUT_SINGLE; + + switch ( eStrikeout ) + { + case STRIKEOUT_SINGLE: + nLineHeight = pFontInstance->mxFontMetric->GetStrikeoutSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetStrikeoutOffset(); + break; + case STRIKEOUT_BOLD: + nLineHeight = pFontInstance->mxFontMetric->GetBoldStrikeoutSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetBoldStrikeoutOffset(); + break; + case STRIKEOUT_DOUBLE: + nLineHeight = pFontInstance->mxFontMetric->GetDoubleStrikeoutSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1(); + nLinePos2 = nY + pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2(); + break; + default: + break; + } + + if ( !nLineHeight ) + return; + + if ( mbLineColor || mbInitLineColor ) + { + mpGraphics->SetLineColor(); + mbInitLineColor = true; + } + mpGraphics->SetFillColor( aColor ); + mbInitFillColor = true; + + const tools::Long& nLeft = nDistX; + + switch ( eStrikeout ) + { + case STRIKEOUT_SINGLE: + case STRIKEOUT_BOLD: + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight ); + break; + case STRIKEOUT_DOUBLE: + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight ); + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos2, nWidth, nLineHeight ); + break; + default: + break; + } +} + +void OutputDevice::ImplDrawStrikeoutChar( tools::Long nBaseX, tools::Long nBaseY, + tools::Long nDistX, tools::Long nDistY, tools::Long nWidth, + FontStrikeout eStrikeout, + Color aColor ) +{ + // See qadevOOo/testdocs/StrikeThrough.odt for examples if you need + // to tweak this + if (!nWidth) + return; + + // prepare string for strikeout measurement + const char cStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? '/' : 'X'; + static const int nTestStrLen = 4; + static const int nMaxStrikeStrLen = 2048; + sal_Unicode aChars[nMaxStrikeStrLen+1]; // +1 for valgrind... + + for( int i = 0; i < nTestStrLen; ++i) + aChars[i] = cStrikeoutChar; + + const OUString aStrikeoutTest(aChars, nTestStrLen); + + // calculate approximation of strikeout atom size + tools::Long nStrikeoutWidth = 0; + std::unique_ptr<SalLayout> pLayout = ImplLayout( aStrikeoutTest, 0, nTestStrLen ); + if( pLayout ) + { + nStrikeoutWidth = pLayout->GetTextWidth() / nTestStrLen; + } + if( nStrikeoutWidth <= 0 ) // sanity check + return; + + int nStrikeStrLen = (nWidth+(nStrikeoutWidth-1)) / nStrikeoutWidth; + if( nStrikeStrLen > nMaxStrikeStrLen ) + nStrikeStrLen = nMaxStrikeStrLen; + else if (nStrikeStrLen < 0) + nStrikeStrLen = 0; + + // build the strikeout string + for( int i = nTestStrLen; i < nStrikeStrLen; ++i) + aChars[i] = cStrikeoutChar; + + const OUString aStrikeoutText(aChars, nStrikeStrLen); + + if( mpFontInstance->mnOrientation ) + { + Point aOriginPt(0, 0); + aOriginPt.RotateAround( nDistX, nDistY, mpFontInstance->mnOrientation ); + } + + nBaseX += nDistX; + nBaseY += nDistY; + + // strikeout text has to be left aligned + vcl::text::ComplexTextLayoutFlags nOrigTLM = mnTextLayoutMode; + mnTextLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiStrong; + pLayout = ImplLayout( aStrikeoutText, 0, aStrikeoutText.getLength() ); + mnTextLayoutMode = nOrigTLM; + + if( !pLayout ) + return; + + // draw the strikeout text + const Color aOldColor = GetTextColor(); + SetTextColor( aColor ); + ImplInitTextColor(); + + pLayout->DrawBase() = basegfx::B2DPoint(nBaseX + mnTextOffX, nBaseY + mnTextOffY); + + tools::Rectangle aPixelRect; + aPixelRect.SetLeft( nBaseX+mnTextOffX ); + aPixelRect.SetRight( aPixelRect.Left()+nWidth ); + aPixelRect.SetBottom( nBaseY+mpFontInstance->mxFontMetric->GetDescent() ); + aPixelRect.SetTop( nBaseY-mpFontInstance->mxFontMetric->GetAscent() ); + + if (mpFontInstance->mnOrientation) + { + tools::Polygon aPoly( aPixelRect ); + aPoly.Rotate( Point(nBaseX+mnTextOffX, nBaseY+mnTextOffY), mpFontInstance->mnOrientation); + aPixelRect = aPoly.GetBoundRect(); + } + + Push( vcl::PushFlags::CLIPREGION ); + IntersectClipRegion( PixelToLogic(aPixelRect) ); + if( mbInitClipRegion ) + InitClipRegion(); + + pLayout->DrawText( *mpGraphics ); + + Pop(); + + SetTextColor( aOldColor ); + ImplInitTextColor(); +} + +void OutputDevice::ImplDrawTextLine( tools::Long nX, tools::Long nY, + tools::Long nDistX, double nWidth, + FontStrikeout eStrikeout, + FontLineStyle eUnderline, + FontLineStyle eOverline, + bool bUnderlineAbove ) +{ + if ( !nWidth ) + return; + + Color aStrikeoutColor = GetTextColor(); + Color aUnderlineColor = GetTextLineColor(); + Color aOverlineColor = GetOverlineColor(); + bool bStrikeoutDone = false; + bool bUnderlineDone = false; + bool bOverlineDone = false; + + if ( IsRTLEnabled() ) + { + tools::Long nXAdd = nWidth - nDistX; + if( mpFontInstance->mnOrientation ) + nXAdd = FRound( nXAdd * cos( toRadians(mpFontInstance->mnOrientation) ) ); + + nX += nXAdd - 1; + } + + if ( !IsTextLineColor() ) + aUnderlineColor = GetTextColor(); + + if ( !IsOverlineColor() ) + aOverlineColor = GetTextColor(); + + if ( (eUnderline == LINESTYLE_SMALLWAVE) || + (eUnderline == LINESTYLE_WAVE) || + (eUnderline == LINESTYLE_DOUBLEWAVE) || + (eUnderline == LINESTYLE_BOLDWAVE) ) + { + ImplDrawWaveTextLine( nX, nY, nDistX, 0, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); + bUnderlineDone = true; + } + if ( (eOverline == LINESTYLE_SMALLWAVE) || + (eOverline == LINESTYLE_WAVE) || + (eOverline == LINESTYLE_DOUBLEWAVE) || + (eOverline == LINESTYLE_BOLDWAVE) ) + { + ImplDrawWaveTextLine( nX, nY, nDistX, 0, nWidth, eOverline, aOverlineColor, true ); + bOverlineDone = true; + } + + if ( (eStrikeout == STRIKEOUT_SLASH) || + (eStrikeout == STRIKEOUT_X) ) + { + ImplDrawStrikeoutChar( nX, nY, nDistX, 0, nWidth, eStrikeout, aStrikeoutColor ); + bStrikeoutDone = true; + } + + if ( !bUnderlineDone ) + ImplDrawStraightTextLine( nX, nY, nDistX, 0, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); + + if ( !bOverlineDone ) + ImplDrawStraightTextLine( nX, nY, nDistX, 0, nWidth, eOverline, aOverlineColor, true ); + + if ( !bStrikeoutDone ) + ImplDrawStrikeoutLine( nX, nY, nDistX, 0, nWidth, eStrikeout, aStrikeoutColor ); +} + +void OutputDevice::ImplDrawTextLines( SalLayout& rSalLayout, FontStrikeout eStrikeout, + FontLineStyle eUnderline, FontLineStyle eOverline, + bool bWordLine, bool bUnderlineAbove ) +{ + if( bWordLine ) + { + // draw everything relative to the layout base point + const basegfx::B2DPoint aStartPt = rSalLayout.DrawBase(); + + // calculate distance of each word from the base point + basegfx::B2DPoint aPos; + double nDist = 0; + double nWidth = 0; + const GlyphItem* pGlyph; + int nStart = 0; + while (rSalLayout.GetNextGlyph(&pGlyph, aPos, nStart)) + { + // calculate the boundaries of each word + if (!pGlyph->IsSpacing()) + { + if( !nWidth ) + { + // get the distance to the base point (as projected to baseline) + nDist = aPos.getX() - aStartPt.getX(); + if( mpFontInstance->mnOrientation ) + { + const double nDY = aPos.getY() - aStartPt.getY(); + const double fRad = toRadians(mpFontInstance->mnOrientation); + nDist = FRound( nDist*cos(fRad) - nDY*sin(fRad) ); + } + } + + // update the length of the textline + nWidth += pGlyph->newWidth(); + } + else if( nWidth > 0 ) + { + // draw the textline for each word + ImplDrawTextLine( aStartPt.getX(), aStartPt.getY(), nDist, nWidth, + eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + nWidth = 0; + } + } + + // draw textline for the last word + if( nWidth > 0 ) + { + ImplDrawTextLine( aStartPt.getX(), aStartPt.getY(), nDist, nWidth, + eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + } + } + else + { + basegfx::B2DPoint aStartPt = rSalLayout.GetDrawPosition(); + ImplDrawTextLine( aStartPt.getX(), aStartPt.getY(), 0, + rSalLayout.GetTextWidth(), + eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + } +} + +void OutputDevice::ImplDrawMnemonicLine( tools::Long nX, tools::Long nY, tools::Long nWidth ) +{ + tools::Long nBaseX = nX; + if( /*HasMirroredGraphics() &&*/ IsRTLEnabled() ) + { + // revert the hack that will be done later in ImplDrawTextLine + nX = nBaseX - nWidth - (nX - nBaseX - 1); + } + + ImplDrawTextLine( nX, nY, 0, nWidth, STRIKEOUT_NONE, LINESTYLE_SINGLE, LINESTYLE_NONE, false ); +} + +void OutputDevice::SetTextLineColor() +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextLineColorAction( Color(), false ) ); + + maTextLineColor = COL_TRANSPARENT; + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextLineColor(); +} + +void OutputDevice::SetTextLineColor( const Color& rColor ) +{ + Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings())); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextLineColorAction( aColor, true ) ); + + maTextLineColor = aColor; + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextLineColor( COL_ALPHA_OPAQUE ); +} + +void OutputDevice::SetOverlineColor() +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaOverlineColorAction( Color(), false ) ); + + maOverlineColor = COL_TRANSPARENT; + + if( mpAlphaVDev ) + mpAlphaVDev->SetOverlineColor(); +} + +void OutputDevice::SetOverlineColor( const Color& rColor ) +{ + Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings())); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaOverlineColorAction( aColor, true ) ); + + maOverlineColor = aColor; + + if( mpAlphaVDev ) + mpAlphaVDev->SetOverlineColor( COL_ALPHA_OPAQUE ); +} + +void OutputDevice::DrawTextLine( const Point& rPos, tools::Long nWidth, + FontStrikeout eStrikeout, + FontLineStyle eUnderline, + FontLineStyle eOverline, + bool bUnderlineAbove ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextLineAction( rPos, nWidth, eStrikeout, eUnderline, eOverline ) ); + + if ( ((eUnderline == LINESTYLE_NONE) || (eUnderline == LINESTYLE_DONTKNOW)) && + ((eOverline == LINESTYLE_NONE) || (eOverline == LINESTYLE_DONTKNOW)) && + ((eStrikeout == STRIKEOUT_NONE) || (eStrikeout == STRIKEOUT_DONTKNOW)) ) + { + return; + } + if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return; + + // initialize font if needed to get text offsets + // TODO: only needed for mnTextOff!=(0,0) + if (!InitFont()) + return; + + Point aPos = ImplLogicToDevicePixel( rPos ); + double fWidth = ImplLogicWidthToDeviceSubPixel(nWidth); + aPos += Point( mnTextOffX, mnTextOffY ); + ImplDrawTextLine( aPos.X(), aPos.X(), 0, fWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + + if( mpAlphaVDev ) + mpAlphaVDev->DrawTextLine( rPos, nWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove ); +} + +void OutputDevice::DrawWaveLine(const Point& rStartPos, const Point& rEndPos, tools::Long nLineWidth, tools::Long nWaveHeight) +{ + assert(!is_double_buffered_window()); + + if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if (!InitFont()) + return; + + Point aStartPt = ImplLogicToDevicePixel(rStartPos); + Point aEndPt = ImplLogicToDevicePixel(rEndPos); + + tools::Long nStartX = aStartPt.X(); + tools::Long nStartY = aStartPt.Y(); + tools::Long nEndX = aEndPt.X(); + tools::Long nEndY = aEndPt.Y(); + double fOrientation = 0.0; + + // handle rotation + if (nStartY != nEndY || nStartX > nEndX) + { + fOrientation = basegfx::rad2deg(std::atan2(nStartY - nEndY, nEndX - nStartX)); + // un-rotate the end point + aStartPt.RotateAround(nEndX, nEndY, Degree10(static_cast<sal_Int16>(-fOrientation * 10.0))); + } + + // Handle HiDPI + float fScaleFactor = GetDPIScaleFactor(); + if (fScaleFactor > 1.0f) + { + nWaveHeight *= fScaleFactor; + + nStartY += fScaleFactor - 1; // Shift down additional pixel(s) to create more visual separation. + + // odd heights look better than even + if (nWaveHeight % 2 == 0) + { + nWaveHeight--; + } + } + + // #109280# make sure the waveline does not exceed the descent to avoid paint problems + LogicalFontInstance* pFontInstance = mpFontInstance.get(); + if (nWaveHeight > pFontInstance->mxFontMetric->GetWavelineUnderlineSize()) + { + nWaveHeight = pFontInstance->mxFontMetric->GetWavelineUnderlineSize(); + // tdf#124848 hairline + nLineWidth = 0; + } + + if ( fOrientation == 0.0 ) + { + static vcl::DeleteOnDeinit< WavyLineCache > snLineCache {}; + if ( !snLineCache.get() ) + return; + WavyLineCache& rLineCache = *snLineCache.get(); + BitmapEx aWavylinebmp; + if ( !rLineCache.find( GetLineColor(), nLineWidth, nWaveHeight, nEndX - nStartX, aWavylinebmp ) ) + { + size_t nWordLength = nEndX - nStartX; + // start with something big to avoid updating it frequently + nWordLength = nWordLength < 1024 ? 1024 : nWordLength; + ScopedVclPtrInstance< VirtualDevice > pVirtDev( *this, DeviceFormat::WITH_ALPHA ); + pVirtDev->SetOutputSizePixel( Size( nWordLength, nWaveHeight * 2 ), false ); + pVirtDev->SetLineColor( GetLineColor() ); + pVirtDev->SetBackground( Wallpaper( COL_TRANSPARENT ) ); + pVirtDev->Erase(); + pVirtDev->SetAntialiasing( AntialiasingFlags::Enable ); + pVirtDev->ImplDrawWaveLineBezier( 0, 0, nWordLength, 0, nWaveHeight, fOrientation, nLineWidth ); + BitmapEx aBitmapEx(pVirtDev->GetBitmapEx(Point(0, 0), pVirtDev->GetOutputSize())); + + // Ideally we don't need this block, but in the split rgb surface + separate alpha surface + // with Antialiasing enabled and the svp/cairo backend we get both surfaces antialiased + // so their combination of aliases merge to overly wash-out the color. Hack it by taking just + // the alpha surface and use it to blend the original solid line color + Bitmap aSolidColor(aBitmapEx.GetBitmap()); + aSolidColor.Erase(GetLineColor()); + aBitmapEx = BitmapEx(aSolidColor, aBitmapEx.GetAlphaMask()); + + rLineCache.insert( aBitmapEx, GetLineColor(), nLineWidth, nWaveHeight, nWordLength, aWavylinebmp ); + } + if ( aWavylinebmp.ImplGetBitmapSalBitmap() != nullptr ) + { + Size _size( nEndX - nStartX, aWavylinebmp.GetSizePixel().Height() ); + DrawBitmapEx(Point( rStartPos.X(), rStartPos.Y() ), PixelToLogic( _size ), Point(), _size, aWavylinebmp); + } + return; + } + + ImplDrawWaveLineBezier( nStartX, nStartY, nEndX, nEndY, nWaveHeight, fOrientation, nLineWidth ); +} + +void OutputDevice::ImplDrawWaveLineBezier(tools::Long nStartX, tools::Long nStartY, tools::Long nEndX, tools::Long nEndY, tools::Long nWaveHeight, double fOrientation, tools::Long nLineWidth) +{ + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if (!InitFont()) + return; + + const basegfx::B2DRectangle aWaveLineRectangle(nStartX, nStartY, nEndX, nEndY + nWaveHeight); + const basegfx::B2DPolygon aWaveLinePolygon = basegfx::createWaveLinePolygon(aWaveLineRectangle); + const basegfx::B2DHomMatrix aRotationMatrix = basegfx::utils::createRotateAroundPoint(nStartX, nStartY, basegfx::deg2rad(-fOrientation)); + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + mpGraphics->SetLineColor(GetLineColor()); + mpGraphics->DrawPolyLine( + aRotationMatrix, + aWaveLinePolygon, + 0.0, + nLineWidth, + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), + bPixelSnapHairline, + *this); + + if( mpAlphaVDev ) + mpAlphaVDev->ImplDrawWaveLineBezier(nStartX, nStartY, nEndX, nEndY, nWaveHeight, fOrientation, nLineWidth); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/transparent.cxx b/vcl/source/outdev/transparent.cxx new file mode 100644 index 0000000000..7588dec556 --- /dev/null +++ b/vcl/source/outdev/transparent.cxx @@ -0,0 +1,1935 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/types.h> +#include <osl/diagnose.h> +#include <rtl/math.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <tools/helpers.hxx> +#include <officecfg/Office/Common.hxx> + +#include <vcl/BitmapTools.hxx> +#include <vcl/metaact.hxx> +#include <vcl/print.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <vcl/BitmapWriteAccess.hxx> +#include <pdf/pdfwriter_impl.hxx> +#include <salgdi.hxx> + +#include <list> +#include <memory> + +#define MAX_TILE_WIDTH 1024 +#define MAX_TILE_HEIGHT 1024 + +namespace +{ + /** + * Perform a safe approximation of a polygon from double-precision + * coordinates to integer coordinates, to ensure that it has at least 2 + * pixels in both X and Y directions. + */ + tools::Polygon toPolygon( const basegfx::B2DPolygon& rPoly ) + { + basegfx::B2DRange aRange = rPoly.getB2DRange(); + double fW = aRange.getWidth(), fH = aRange.getHeight(); + if (0.0 < fW && 0.0 < fH && (fW <= 1.0 || fH <= 1.0)) + { + // This polygon not empty but is too small to display. Approximate it + // with a rectangle large enough to be displayed. + double nX = aRange.getMinX(), nY = aRange.getMinY(); + double nW = std::max<double>(1.0, rtl::math::round(fW)); + double nH = std::max<double>(1.0, rtl::math::round(fH)); + + tools::Polygon aTarget; + aTarget.Insert(0, Point(nX, nY)); + aTarget.Insert(1, Point(nX+nW, nY)); + aTarget.Insert(2, Point(nX+nW, nY+nH)); + aTarget.Insert(3, Point(nX, nY+nH)); + aTarget.Insert(4, Point(nX, nY)); + return aTarget; + } + return tools::Polygon(rPoly); + } + + tools::PolyPolygon toPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly ) + { + tools::PolyPolygon aTarget; + for (auto const& rB2DPolygon : rPolyPoly) + aTarget.Insert(toPolygon(rB2DPolygon)); + + return aTarget; + } +} + +// Caution: This method is nearly the same as +// void OutputDevice::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rB2DPolyPoly ) +// so when changes are made here do not forget to make changes there, too + +void OutputDevice::DrawTransparent( + const basegfx::B2DHomMatrix& rObjectTransform, + const basegfx::B2DPolyPolygon& rB2DPolyPoly, + double fTransparency) +{ + assert(!is_double_buffered_window()); + + // AW: Do NOT paint empty PolyPolygons + if(!rB2DPolyPoly.count()) + return; + + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return; + + if( mbInitLineColor ) + InitLineColor(); + + if( mbInitFillColor ) + InitFillColor(); + + if (RasterOp::OverPaint == GetRasterOp()) + { + // b2dpolygon support not implemented yet on non-UNX platforms + basegfx::B2DPolyPolygon aB2DPolyPolygon(rB2DPolyPoly); + + // ensure it is closed + if(!aB2DPolyPolygon.isClosed()) + { + // maybe assert, prevents buffering due to making a copy + aB2DPolyPolygon.setClosed( true ); + } + + // create ObjectToDevice transformation + const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rObjectTransform); + // TODO: this must not drop transparency for mpAlphaVDev case, but instead use premultiplied + // alpha... but that requires using premultiplied alpha also for already drawn data + const double fAdjustedTransparency = mpAlphaVDev ? 0 : fTransparency; + + if (IsFillColor()) + { + mpGraphics->DrawPolyPolygon( + aFullTransform, + aB2DPolyPolygon, + fAdjustedTransparency, + *this); + } + + if (IsLineColor()) + { + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + for(auto const& rPolygon : std::as_const(aB2DPolyPolygon)) + { + mpGraphics->DrawPolyLine( + aFullTransform, + rPolygon, + fAdjustedTransparency, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + *this ); + } + } + + if( mpMetaFile ) + { + // tdf#119843 need transformed Polygon here + basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly); + aB2DPolyPoly.transform(rObjectTransform); + mpMetaFile->AddAction( + new MetaTransparentAction( + tools::PolyPolygon(aB2DPolyPoly), + static_cast< sal_uInt16 >(fTransparency * 100.0))); + } + + if (mpAlphaVDev) + mpAlphaVDev->DrawTransparent(rObjectTransform, rB2DPolyPoly, fTransparency); + + return; + } + + // fallback to old polygon drawing if needed + // tdf#119843 need transformed Polygon here + basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly); + aB2DPolyPoly.transform(rObjectTransform); + DrawTransparent( + toPolyPolygon(aB2DPolyPoly), + static_cast<sal_uInt16>(fTransparency * 100.0)); +} + +bool OutputDevice::DrawTransparentNatively ( const tools::PolyPolygon& rPolyPoly, + sal_uInt16 nTransparencePercent ) +{ + assert(!is_double_buffered_window()); + + bool bDrawn = false; + + if (true +#if defined UNX && ! defined MACOSX && ! defined IOS + && GetBitCount() > 8 +#endif +#ifdef _WIN32 + // workaround bad dithering on remote displaying when using GDI+ with toolbar button highlighting + && !rPolyPoly.IsRect() +#endif + ) + { + // prepare the graphics device + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return true; + + if( mbInitLineColor ) + InitLineColor(); + + if( mbInitFillColor ) + InitFillColor(); + + // get the polygon in device coordinates + basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon()); + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation()); + + const double fTransparency = 0.01 * nTransparencePercent; + if( mbFillColor ) + { + // #i121591# + // CAUTION: Only non printing (pixel-renderer) VCL commands from OutputDevices + // should be used when printing. Normally this is avoided by the printer being + // non-AAed and thus e.g. on WIN GdiPlus calls are not used. It may be necessary + // to figure out a way of moving this code to its own function that is + // overridden by the Print class, which will mean we deliberately override the + // functionality and we use the fallback some lines below (which is not very good, + // though. For now, WinSalGraphics::drawPolyPolygon will detect printer usage and + // correct the wrong mapping (see there for details) + mpGraphics->DrawPolyPolygon( + aTransform, + aB2DPolyPolygon, + fTransparency, + *this); + bDrawn = true; + } + + if( mbLineColor ) + { + // disable the fill color for now + mpGraphics->SetFillColor(); + + // draw the border line + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + for(auto const& rPolygon : std::as_const(aB2DPolyPolygon)) + { + bDrawn = mpGraphics->DrawPolyLine( + aTransform, + rPolygon, + fTransparency, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + *this ); + } + + // prepare to restore the fill color + mbInitFillColor = mbFillColor; + } + } + + return bDrawn; +} + +void OutputDevice::EmulateDrawTransparent ( const tools::PolyPolygon& rPolyPoly, + sal_uInt16 nTransparencePercent ) +{ + // #110958# Disable alpha VDev, we perform the necessary + VirtualDevice* pOldAlphaVDev = mpAlphaVDev; + + // operation explicitly further below. + if( mpAlphaVDev ) + mpAlphaVDev = nullptr; + + GDIMetaFile* pOldMetaFile = mpMetaFile; + mpMetaFile = nullptr; + + tools::PolyPolygon aPolyPoly( LogicToPixel( rPolyPoly ) ); + tools::Rectangle aPolyRect( aPolyPoly.GetBoundRect() ); + tools::Rectangle aDstRect( Point(), GetOutputSizePixel() ); + + aDstRect.Intersection( aPolyRect ); + + ClipToPaintRegion( aDstRect ); + + if( !aDstRect.IsEmpty() ) + { + bool bDrawn = false; + + // #i66849# Added fast path for exactly rectangular + // polygons + // #i83087# Naturally, system alpha blending cannot + // work with separate alpha VDev + if( !mpAlphaVDev && aPolyPoly.IsRect() ) + { + // setup Graphics only here (other cases delegate + // to basic OutDev methods) + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbInitLineColor ) + InitLineColor(); + + if ( mbInitFillColor ) + InitFillColor(); + + tools::Rectangle aLogicPolyRect( rPolyPoly.GetBoundRect() ); + tools::Rectangle aPixelRect( ImplLogicToDevicePixel( aLogicPolyRect ) ); + + if( !mbOutputClipped ) + { + bDrawn = mpGraphics->DrawAlphaRect( aPixelRect.Left(), aPixelRect.Top(), + // #i98405# use methods with small g, else one pixel too much will be painted. + // This is because the source is a polygon which when painted would not paint + // the rightmost and lowest pixel line(s), so use one pixel less for the + // rectangle, too. + aPixelRect.getOpenWidth(), aPixelRect.getOpenHeight(), + sal::static_int_cast<sal_uInt8>(nTransparencePercent), + *this ); + } + else + { + bDrawn = true; + } + } + + if( !bDrawn ) + { + ScopedVclPtrInstance< VirtualDevice > aVDev(*this); + const Size aDstSz( aDstRect.GetSize() ); + const sal_uInt8 cTrans = FRound( std::clamp( nTransparencePercent * 2.55, 0.0, 255.0 ) ); + + if( aDstRect.Left() || aDstRect.Top() ) + aPolyPoly.Move( -aDstRect.Left(), -aDstRect.Top() ); + + if( aVDev->SetOutputSizePixel( aDstSz ) ) + { + const bool bOldMap = mbMap; + + EnableMapMode( false ); + + aVDev->SetLineColor( COL_BLACK ); + aVDev->SetFillColor( COL_BLACK ); + aVDev->DrawPolyPolygon( aPolyPoly ); + + Bitmap aPaint( GetBitmap( aDstRect.TopLeft(), aDstSz ) ); + Bitmap aPolyMask( aVDev->GetBitmap( Point(), aDstSz ) ); + + // #107766# check for non-empty bitmaps before accessing them + if( !aPaint.IsEmpty() && !aPolyMask.IsEmpty() ) + { + BitmapScopedWriteAccess pW(aPaint); + BitmapScopedReadAccess pR(aPolyMask); + + if( pW && pR ) + { + BitmapColor aPixCol; + const BitmapColor aFillCol( GetFillColor() ); + const BitmapColor aBlack( pR->GetBestMatchingColor( COL_BLACK ) ); + const tools::Long nWidth = pW->Width(); + const tools::Long nHeight = pW->Height(); + const tools::Long nR = aFillCol.GetRed(); + const tools::Long nG = aFillCol.GetGreen(); + const tools::Long nB = aFillCol.GetBlue(); + tools::Long nX, nY; + + if (vcl::isPalettePixelFormat(aPaint.getPixelFormat())) + { + const BitmapPalette& rPal = pW->GetPalette(); + const sal_uInt16 nCount = rPal.GetEntryCount(); + std::unique_ptr<sal_uInt8[]> xMap(new sal_uInt8[ nCount * sizeof( BitmapColor )]); + BitmapColor* pMap = reinterpret_cast<BitmapColor*>(xMap.get()); + + for( sal_uInt16 i = 0; i < nCount; i++ ) + { + BitmapColor aCol( rPal[ i ] ); + aCol.Merge( aFillCol, cTrans ); + pMap[ i ] = BitmapColor( static_cast<sal_uInt8>(rPal.GetBestIndex( aCol )) ); + } + + if( pR->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal && + pW->GetScanlineFormat() == ScanlineFormat::N8BitPal ) + { + const sal_uInt8 cBlack = aBlack.GetIndex(); + + for( nY = 0; nY < nHeight; nY++ ) + { + Scanline pWScan = pW->GetScanline( nY ); + Scanline pRScan = pR->GetScanline( nY ); + sal_uInt8 cBit = 128; + + for( nX = 0; nX < nWidth; nX++, cBit >>= 1, pWScan++ ) + { + if( !cBit ) + { + cBit = 128; + pRScan += 1; + } + if( ( *pRScan & cBit ) == cBlack ) + { + *pWScan = pMap[ *pWScan ].GetIndex(); + } + } + } + } + else + { + for( nY = 0; nY < nHeight; nY++ ) + { + Scanline pScanline = pW->GetScanline(nY); + Scanline pScanlineRead = pR->GetScanline(nY); + for( nX = 0; nX < nWidth; nX++ ) + { + if( pR->GetPixelFromData( pScanlineRead, nX ) == aBlack ) + { + pW->SetPixelOnData( pScanline, nX, pMap[ pW->GetIndexFromData( pScanline, nX ) ] ); + } + } + } + } + } + else + { + if( pR->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal && + pW->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr ) + { + const sal_uInt8 cBlack = aBlack.GetIndex(); + + for( nY = 0; nY < nHeight; nY++ ) + { + Scanline pWScan = pW->GetScanline( nY ); + Scanline pRScan = pR->GetScanline( nY ); + sal_uInt8 cBit = 128; + + for( nX = 0; nX < nWidth; nX++, cBit >>= 1, pWScan += 3 ) + { + if( !cBit ) + { + cBit = 128; + pRScan += 1; + } + if( ( *pRScan & cBit ) == cBlack ) + { + pWScan[ 0 ] = color::ColorChannelMerge( pWScan[ 0 ], nB, cTrans ); + pWScan[ 1 ] = color::ColorChannelMerge( pWScan[ 1 ], nG, cTrans ); + pWScan[ 2 ] = color::ColorChannelMerge( pWScan[ 2 ], nR, cTrans ); + } + } + } + } + else + { + for( nY = 0; nY < nHeight; nY++ ) + { + Scanline pScanline = pW->GetScanline(nY); + Scanline pScanlineRead = pR->GetScanline(nY); + for( nX = 0; nX < nWidth; nX++ ) + { + if( pR->GetPixelFromData( pScanlineRead, nX ) == aBlack ) + { + aPixCol = pW->GetColor( nY, nX ); + aPixCol.Merge(aFillCol, cTrans); + pW->SetPixelOnData(pScanline, nX, aPixCol); + } + } + } + } + } + } + + pR.reset(); + pW.reset(); + + DrawBitmap( aDstRect.TopLeft(), aPaint ); + + EnableMapMode( bOldMap ); + + if( mbLineColor ) + { + Push( vcl::PushFlags::FILLCOLOR ); + SetFillColor(); + DrawPolyPolygon( rPolyPoly ); + Pop(); + } + } + } + else + { + DrawPolyPolygon( rPolyPoly ); + } + } + } + + mpMetaFile = pOldMetaFile; + + // #110958# Restore disabled alpha VDev + mpAlphaVDev = pOldAlphaVDev; +} + +void OutputDevice::DrawTransparent( const tools::PolyPolygon& rPolyPoly, + sal_uInt16 nTransparencePercent ) +{ + assert(!is_double_buffered_window()); + + // short circuit for drawing an opaque polygon + if( (nTransparencePercent < 1) || (mnDrawMode & DrawModeFlags::NoTransparency) ) + { + DrawPolyPolygon( rPolyPoly ); + return; + } + + // short circuit for drawing an invisible polygon + if( (!mbFillColor && !mbLineColor) || (nTransparencePercent >= 100) ) + return; // tdf#84294: do not record it in metafile + + // handle metafile recording + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaTransparentAction( rPolyPoly, nTransparencePercent ) ); + + bool bDrawn = !IsDeviceOutputNecessary() || ImplIsRecordLayout(); + if( bDrawn ) + return; + + // get the device graphics as drawing target + if( !mpGraphics && !AcquireGraphics() ) + return; + assert(mpGraphics); + + // try hard to draw it directly, because the emulation layers are slower + bDrawn = DrawTransparentNatively( rPolyPoly, nTransparencePercent ); + + if (!bDrawn) + EmulateDrawTransparent( rPolyPoly, nTransparencePercent ); + + // #110958# Apply alpha value also to VDev alpha channel + if( mpAlphaVDev ) + { + const Color aFillCol( mpAlphaVDev->GetFillColor() ); + sal_uInt8 nAlpha = 255 - sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100); + mpAlphaVDev->SetFillColor( Color(nAlpha, nAlpha, nAlpha) ); + + mpAlphaVDev->DrawTransparent( rPolyPoly, nTransparencePercent ); + + mpAlphaVDev->SetFillColor( aFillCol ); + } +} + +void OutputDevice::DrawTransparent( const GDIMetaFile& rMtf, const Point& rPos, + const Size& rSize, const Gradient& rTransparenceGradient ) +{ + DrawTransparent( rMtf, rPos, rSize, rPos, rSize, rTransparenceGradient ); +} + +void OutputDevice::DrawTransparent( const GDIMetaFile& rMtf, const Point& rPos, const Size& rSize, + const Point& rMtfPos, const Size& rMtfSize, + const Gradient& rTransparenceGradient ) +{ + assert(!is_double_buffered_window()); + + const Color aBlack( COL_BLACK ); + + if( mpMetaFile ) + { + // missing here is to map the data using the DeviceTransformation + mpMetaFile->AddAction( new MetaFloatTransparentAction( rMtf, rPos, rSize, rTransparenceGradient ) ); + } + + if ( !IsDeviceOutputNecessary() ) + return; + + if( ( rTransparenceGradient.GetStartColor() == aBlack && rTransparenceGradient.GetEndColor() == aBlack ) || + ( mnDrawMode & DrawModeFlags::NoTransparency ) ) + { + const_cast<GDIMetaFile&>(rMtf).WindStart(); + const_cast<GDIMetaFile&>(rMtf).Play(*this, rMtfPos, rMtfSize); + const_cast<GDIMetaFile&>(rMtf).WindStart(); + } + else + { + GDIMetaFile* pOldMetaFile = mpMetaFile; + tools::Rectangle aOutRect( LogicToPixel( tools::Rectangle(rPos, rSize) ) ); + Point aPoint; + tools::Rectangle aDstRect( aPoint, GetOutputSizePixel() ); + + mpMetaFile = nullptr; + aDstRect.Intersection( aOutRect ); + + ClipToPaintRegion( aDstRect ); + + if( !aDstRect.IsEmpty() ) + { + // Create transparent buffer + ScopedVclPtrInstance<VirtualDevice> xVDev(DeviceFormat::WITH_ALPHA); + + xVDev->mnDPIX = mnDPIX; + xVDev->mnDPIY = mnDPIY; + + if( xVDev->SetOutputSizePixel( aDstRect.GetSize(), true, true ) ) + { + // tdf#150610 fix broken rendering of text meta actions + // Even when drawing to a VirtualDevice that has antialiasing + // disabled, text will still be drawn with some antialiased + // pixels on HiDPI displays. So, use the antialiasing enabled + // code to render if there are any text meta actions in the + // metafile. + if(GetAntialiasing() != AntialiasingFlags::NONE || rPos != rMtfPos || rSize != rMtfSize) + { + // #i102109# + // For MetaFile replay (see task) it may now be necessary to take + // into account that the content is AntiAlialiased and needs to be masked + // like that. Instead of masking, i will use a copy-modify-paste cycle + // here (as i already use in the VclPrimiziveRenderer with success) + xVDev->SetAntialiasing(GetAntialiasing()); + + // create MapMode for buffer (offset needed) and set + MapMode aMap(GetMapMode()); + const Point aOutPos(PixelToLogic(aDstRect.TopLeft())); + aMap.SetOrigin(Point(-aOutPos.X(), -aOutPos.Y())); + xVDev->SetMapMode(aMap); + + // copy MapMode state and disable for target + const bool bOrigMapModeEnabled(IsMapModeEnabled()); + EnableMapMode(false); + + // copy MapMode state and disable for buffer + const bool bBufferMapModeEnabled(xVDev->IsMapModeEnabled()); + xVDev->EnableMapMode(false); + + // copy content from original to buffer + xVDev->DrawOutDev( aPoint, xVDev->GetOutputSizePixel(), // dest + aDstRect.TopLeft(), xVDev->GetOutputSizePixel(), // source + *this); + + // draw MetaFile to buffer + xVDev->EnableMapMode(bBufferMapModeEnabled); + const_cast<GDIMetaFile&>(rMtf).WindStart(); + const_cast<GDIMetaFile&>(rMtf).Play(*xVDev, rMtfPos, rMtfSize); + const_cast<GDIMetaFile&>(rMtf).WindStart(); + + // get content bitmap from buffer + xVDev->EnableMapMode(false); + + const BitmapEx aPaint(xVDev->GetBitmapEx(aPoint, xVDev->GetOutputSizePixel())); + + // create alpha mask from gradient and get as Bitmap + xVDev->EnableMapMode(bBufferMapModeEnabled); + xVDev->SetDrawMode(DrawModeFlags::GrayGradient); + // Related tdf#150610 draw gradient to VirtualDevice bounds + // If we are here and the metafile bounds differs from the + // VirtualDevice bounds so that we apply the transparency + // gradient to any pixels drawn outside of the metafile + // bounds. + xVDev->DrawGradient(tools::Rectangle(rPos, rSize), rTransparenceGradient); + xVDev->SetDrawMode(DrawModeFlags::Default); + xVDev->EnableMapMode(false); + + AlphaMask aAlpha(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel())); + AlphaMask aPaintAlpha(aPaint.GetAlphaMask()); + // The alpha mask is inverted from what + // is expected so invert it again + aAlpha.Invert(); // convert to alpha + aAlpha.BlendWith(aPaintAlpha); + + xVDev.disposeAndClear(); + + // draw masked content to target and restore MapMode + DrawBitmapEx(aDstRect.TopLeft(), BitmapEx(aPaint.GetBitmap(), aAlpha)); + EnableMapMode(bOrigMapModeEnabled); + } + else + { + MapMode aMap( GetMapMode() ); + Point aOutPos( PixelToLogic( aDstRect.TopLeft() ) ); + const bool bOldMap = mbMap; + + aMap.SetOrigin( Point( -aOutPos.X(), -aOutPos.Y() ) ); + xVDev->SetMapMode( aMap ); + const bool bVDevOldMap = xVDev->IsMapModeEnabled(); + + // create paint bitmap + const_cast<GDIMetaFile&>(rMtf).WindStart(); + const_cast<GDIMetaFile&>(rMtf).Play(*xVDev, rMtfPos, rMtfSize); + const_cast<GDIMetaFile&>(rMtf).WindStart(); + xVDev->EnableMapMode( false ); + BitmapEx aPaint = xVDev->GetBitmapEx(Point(), xVDev->GetOutputSizePixel()); + xVDev->EnableMapMode( bVDevOldMap ); // #i35331#: MUST NOT use EnableMapMode( sal_True ) here! + + // create alpha mask from gradient + xVDev->SetDrawMode( DrawModeFlags::GrayGradient ); + xVDev->DrawGradient( tools::Rectangle( rMtfPos, rMtfSize ), rTransparenceGradient ); + xVDev->SetDrawMode( DrawModeFlags::Default ); + xVDev->EnableMapMode( false ); + + AlphaMask aAlpha(xVDev->GetBitmap(Point(), xVDev->GetOutputSizePixel())); + AlphaMask aPaintAlpha(aPaint.GetAlphaMask()); + // The alpha mask is inverted from what + // is expected so invert it again + aAlpha.Invert(); // convert to alpha + aAlpha.BlendWith(aPaintAlpha); + + xVDev.disposeAndClear(); + + EnableMapMode( false ); + DrawBitmapEx(aDstRect.TopLeft(), BitmapEx(aPaint.GetBitmap(), aAlpha)); + EnableMapMode( bOldMap ); + } + } + } + + mpMetaFile = pOldMetaFile; + } +} + +typedef ::std::pair< MetaAction*, int > Component; // MetaAction plus index in metafile + +namespace { + +// List of (intersecting) actions, plus overall bounds +struct ConnectedComponents +{ + ConnectedComponents() : + aComponentList(), + aBounds(), + aBgColor(COL_WHITE), + bIsSpecial(false), + bIsFullyTransparent(false) + {} + + ::std::list< Component > aComponentList; + tools::Rectangle aBounds; + Color aBgColor; + bool bIsSpecial; + bool bIsFullyTransparent; +}; + +} + +namespace { + +/** Determines whether the action can handle transparency correctly + (i.e. when painted on white background, does the action still look + correct)? + */ +bool DoesActionHandleTransparency( const MetaAction& rAct ) +{ + // MetaActionType::FLOATTRANSPARENT can contain a whole metafile, + // which is to be rendered with the given transparent gradient. We + // currently cannot emulate transparent painting on a white + // background reliably. + + // the remainder can handle printing itself correctly on a uniform + // white background. + switch( rAct.GetType() ) + { + case MetaActionType::Transparent: + case MetaActionType::BMPEX: + case MetaActionType::BMPEXSCALE: + case MetaActionType::BMPEXSCALEPART: + return true; + + default: + return false; + } +} + +bool doesRectCoverWithUniformColor( + tools::Rectangle const & rPrevRect, + tools::Rectangle const & rCurrRect, + OutputDevice const & rMapModeVDev) +{ + // shape needs to fully cover previous content, and have uniform + // color + return (rMapModeVDev.LogicToPixel(rCurrRect).Contains(rPrevRect) && + rMapModeVDev.IsFillColor()); +} + +/** Check whether rCurrRect rectangle fully covers io_rPrevRect - if + yes, return true and update o_rBgColor + */ +bool checkRect( tools::Rectangle& io_rPrevRect, + Color& o_rBgColor, + const tools::Rectangle& rCurrRect, + OutputDevice const & rMapModeVDev ) +{ + bool bRet = doesRectCoverWithUniformColor(io_rPrevRect, rCurrRect, rMapModeVDev); + + if( bRet ) + { + io_rPrevRect = rCurrRect; + o_rBgColor = rMapModeVDev.GetFillColor(); + } + + return bRet; +} + +/** #107169# Convert BitmapEx to Bitmap with appropriately blended + color. Convert MetaTransparentAction to plain polygon, + appropriately colored + + @param o_rMtf + Add converted actions to this metafile +*/ +void ImplConvertTransparentAction( GDIMetaFile& o_rMtf, + const MetaAction& rAct, + const OutputDevice& rStateOutDev, + Color aBgColor ) +{ + if (rAct.GetType() == MetaActionType::Transparent) + { + const MetaTransparentAction* pTransAct = static_cast<const MetaTransparentAction*>(&rAct); + sal_uInt16 nTransparency( pTransAct->GetTransparence() ); + + // #i10613# Respect transparency for draw color + if (nTransparency) + { + o_rMtf.AddAction(new MetaPushAction(vcl::PushFlags::LINECOLOR|vcl::PushFlags::FILLCOLOR)); + + // assume white background for alpha blending + Color aLineColor(rStateOutDev.GetLineColor()); + aLineColor.SetRed(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency) * aLineColor.GetRed()) / 100)); + aLineColor.SetGreen(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency) * aLineColor.GetGreen()) / 100)); + aLineColor.SetBlue(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency) * aLineColor.GetBlue()) / 100)); + o_rMtf.AddAction(new MetaLineColorAction(aLineColor, true)); + + Color aFillColor(rStateOutDev.GetFillColor()); + aFillColor.SetRed(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency)*aFillColor.GetRed()) / 100)); + aFillColor.SetGreen(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency)*aFillColor.GetGreen()) / 100)); + aFillColor.SetBlue(static_cast<sal_uInt8>((255*nTransparency + (100 - nTransparency)*aFillColor.GetBlue()) / 100)); + o_rMtf.AddAction(new MetaFillColorAction(aFillColor, true)); + } + + o_rMtf.AddAction(new MetaPolyPolygonAction(pTransAct->GetPolyPolygon())); + + if(nTransparency) + o_rMtf.AddAction(new MetaPopAction()); + } + else + { + BitmapEx aBmpEx; + + switch (rAct.GetType()) + { + case MetaActionType::BMPEX: + aBmpEx = static_cast<const MetaBmpExAction&>(rAct).GetBitmapEx(); + break; + + case MetaActionType::BMPEXSCALE: + aBmpEx = static_cast<const MetaBmpExScaleAction&>(rAct).GetBitmapEx(); + break; + + case MetaActionType::BMPEXSCALEPART: + aBmpEx = static_cast<const MetaBmpExScaleAction&>(rAct).GetBitmapEx(); + break; + + case MetaActionType::Transparent: + + default: + OSL_FAIL("Printer::GetPreparedMetafile impossible state reached"); + break; + } + + Bitmap aBmp(aBmpEx.GetBitmap()); + if (aBmpEx.IsAlpha()) + { + // blend with alpha channel + aBmp.Convert(BmpConversion::N24Bit); + aBmp.Blend(aBmpEx.GetAlphaMask(), aBgColor); + } + + // add corresponding action + switch (rAct.GetType()) + { + case MetaActionType::BMPEX: + o_rMtf.AddAction(new MetaBmpAction( + static_cast<const MetaBmpExAction&>(rAct).GetPoint(), + aBmp)); + break; + case MetaActionType::BMPEXSCALE: + o_rMtf.AddAction(new MetaBmpScaleAction( + static_cast<const MetaBmpExScaleAction&>(rAct).GetPoint(), + static_cast<const MetaBmpExScaleAction&>(rAct).GetSize(), + aBmp)); + break; + case MetaActionType::BMPEXSCALEPART: + o_rMtf.AddAction(new MetaBmpScalePartAction( + static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestPoint(), + static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestSize(), + static_cast<const MetaBmpExScalePartAction&>(rAct).GetSrcPoint(), + static_cast<const MetaBmpExScalePartAction&>(rAct).GetSrcSize(), + aBmp)); + break; + default: + OSL_FAIL("Unexpected case"); + break; + } + } +} + +// #i10613# Extracted from ImplCheckRect::ImplCreate +// Returns true, if given action creates visible (i.e. non-transparent) output +bool ImplIsNotTransparent( const MetaAction& rAct, const OutputDevice& rOut ) +{ + const bool bLineTransparency( !rOut.IsLineColor() || rOut.GetLineColor().IsFullyTransparent() ); + const bool bFillTransparency( !rOut.IsFillColor() || rOut.GetFillColor().IsFullyTransparent() ); + bool bRet( false ); + + switch( rAct.GetType() ) + { + case MetaActionType::POINT: + if( !bLineTransparency ) + bRet = true; + break; + + case MetaActionType::LINE: + if( !bLineTransparency ) + bRet = true; + break; + + case MetaActionType::RECT: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case MetaActionType::ROUNDRECT: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case MetaActionType::ELLIPSE: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case MetaActionType::ARC: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case MetaActionType::PIE: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case MetaActionType::CHORD: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case MetaActionType::POLYLINE: + if( !bLineTransparency ) + bRet = true; + break; + + case MetaActionType::POLYGON: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case MetaActionType::POLYPOLYGON: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case MetaActionType::TEXT: + { + const MetaTextAction& rTextAct = static_cast<const MetaTextAction&>(rAct); + const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) ); + if (!aString.isEmpty()) + bRet = true; + } + break; + + case MetaActionType::TEXTARRAY: + { + const MetaTextArrayAction& rTextAct = static_cast<const MetaTextArrayAction&>(rAct); + const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) ); + if (!aString.isEmpty()) + bRet = true; + } + break; + + case MetaActionType::PIXEL: + case MetaActionType::BMP: + case MetaActionType::BMPSCALE: + case MetaActionType::BMPSCALEPART: + case MetaActionType::BMPEX: + case MetaActionType::BMPEXSCALE: + case MetaActionType::BMPEXSCALEPART: + case MetaActionType::MASK: + case MetaActionType::MASKSCALE: + case MetaActionType::MASKSCALEPART: + case MetaActionType::GRADIENT: + case MetaActionType::GRADIENTEX: + case MetaActionType::HATCH: + case MetaActionType::WALLPAPER: + case MetaActionType::Transparent: + case MetaActionType::FLOATTRANSPARENT: + case MetaActionType::EPS: + case MetaActionType::TEXTRECT: + case MetaActionType::STRETCHTEXT: + case MetaActionType::TEXTLINE: + // all other actions: generate non-transparent output + bRet = true; + break; + + default: + break; + } + + return bRet; +} + +// #i10613# Extracted from ImplCheckRect::ImplCreate +tools::Rectangle ImplCalcActionBounds( const MetaAction& rAct, const OutputDevice& rOut ) +{ + tools::Rectangle aActionBounds; + + switch( rAct.GetType() ) + { + case MetaActionType::PIXEL: + aActionBounds = tools::Rectangle( static_cast<const MetaPixelAction&>(rAct).GetPoint(), Size( 1, 1 ) ); + break; + + case MetaActionType::POINT: + aActionBounds = tools::Rectangle( static_cast<const MetaPointAction&>(rAct).GetPoint(), Size( 1, 1 ) ); + break; + + case MetaActionType::LINE: + { + const MetaLineAction& rMetaLineAction = static_cast<const MetaLineAction&>(rAct); + aActionBounds = tools::Rectangle( rMetaLineAction.GetStartPoint(), rMetaLineAction.GetEndPoint() ); + aActionBounds.Normalize(); + const tools::Long nLineWidth(rMetaLineAction.GetLineInfo().GetWidth()); + if(nLineWidth) + { + const tools::Long nHalfLineWidth((nLineWidth + 1) / 2); + aActionBounds.AdjustLeft( -nHalfLineWidth ); + aActionBounds.AdjustTop( -nHalfLineWidth ); + aActionBounds.AdjustRight(nHalfLineWidth ); + aActionBounds.AdjustBottom(nHalfLineWidth ); + } + break; + } + + case MetaActionType::RECT: + aActionBounds = static_cast<const MetaRectAction&>(rAct).GetRect(); + break; + + case MetaActionType::ROUNDRECT: + aActionBounds = tools::Polygon( static_cast<const MetaRoundRectAction&>(rAct).GetRect(), + static_cast<const MetaRoundRectAction&>(rAct).GetHorzRound(), + static_cast<const MetaRoundRectAction&>(rAct).GetVertRound() ).GetBoundRect(); + break; + + case MetaActionType::ELLIPSE: + { + const tools::Rectangle& rRect = static_cast<const MetaEllipseAction&>(rAct).GetRect(); + aActionBounds = tools::Polygon( rRect.Center(), + rRect.GetWidth() >> 1, + rRect.GetHeight() >> 1 ).GetBoundRect(); + break; + } + + case MetaActionType::ARC: + aActionBounds = tools::Polygon( static_cast<const MetaArcAction&>(rAct).GetRect(), + static_cast<const MetaArcAction&>(rAct).GetStartPoint(), + static_cast<const MetaArcAction&>(rAct).GetEndPoint(), PolyStyle::Arc ).GetBoundRect(); + break; + + case MetaActionType::PIE: + aActionBounds = tools::Polygon( static_cast<const MetaPieAction&>(rAct).GetRect(), + static_cast<const MetaPieAction&>(rAct).GetStartPoint(), + static_cast<const MetaPieAction&>(rAct).GetEndPoint(), PolyStyle::Pie ).GetBoundRect(); + break; + + case MetaActionType::CHORD: + aActionBounds = tools::Polygon( static_cast<const MetaChordAction&>(rAct).GetRect(), + static_cast<const MetaChordAction&>(rAct).GetStartPoint(), + static_cast<const MetaChordAction&>(rAct).GetEndPoint(), PolyStyle::Chord ).GetBoundRect(); + break; + + case MetaActionType::POLYLINE: + { + const MetaPolyLineAction& rMetaPolyLineAction = static_cast<const MetaPolyLineAction&>(rAct); + aActionBounds = rMetaPolyLineAction.GetPolygon().GetBoundRect(); + const tools::Long nLineWidth(rMetaPolyLineAction.GetLineInfo().GetWidth()); + if(nLineWidth) + { + const tools::Long nHalfLineWidth((nLineWidth + 1) / 2); + aActionBounds.AdjustLeft( -nHalfLineWidth ); + aActionBounds.AdjustTop( -nHalfLineWidth ); + aActionBounds.AdjustRight(nHalfLineWidth ); + aActionBounds.AdjustBottom(nHalfLineWidth ); + } + break; + } + + case MetaActionType::POLYGON: + aActionBounds = static_cast<const MetaPolygonAction&>(rAct).GetPolygon().GetBoundRect(); + break; + + case MetaActionType::POLYPOLYGON: + aActionBounds = static_cast<const MetaPolyPolygonAction&>(rAct).GetPolyPolygon().GetBoundRect(); + break; + + case MetaActionType::BMP: + aActionBounds = tools::Rectangle( static_cast<const MetaBmpAction&>(rAct).GetPoint(), + rOut.PixelToLogic( static_cast<const MetaBmpAction&>(rAct).GetBitmap().GetSizePixel() ) ); + break; + + case MetaActionType::BMPSCALE: + aActionBounds = tools::Rectangle( static_cast<const MetaBmpScaleAction&>(rAct).GetPoint(), + static_cast<const MetaBmpScaleAction&>(rAct).GetSize() ); + break; + + case MetaActionType::BMPSCALEPART: + aActionBounds = tools::Rectangle( static_cast<const MetaBmpScalePartAction&>(rAct).GetDestPoint(), + static_cast<const MetaBmpScalePartAction&>(rAct).GetDestSize() ); + break; + + case MetaActionType::BMPEX: + aActionBounds = tools::Rectangle( static_cast<const MetaBmpExAction&>(rAct).GetPoint(), + rOut.PixelToLogic( static_cast<const MetaBmpExAction&>(rAct).GetBitmapEx().GetSizePixel() ) ); + break; + + case MetaActionType::BMPEXSCALE: + aActionBounds = tools::Rectangle( static_cast<const MetaBmpExScaleAction&>(rAct).GetPoint(), + static_cast<const MetaBmpExScaleAction&>(rAct).GetSize() ); + break; + + case MetaActionType::BMPEXSCALEPART: + aActionBounds = tools::Rectangle( static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestPoint(), + static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestSize() ); + break; + + case MetaActionType::MASK: + aActionBounds = tools::Rectangle( static_cast<const MetaMaskAction&>(rAct).GetPoint(), + rOut.PixelToLogic( static_cast<const MetaMaskAction&>(rAct).GetBitmap().GetSizePixel() ) ); + break; + + case MetaActionType::MASKSCALE: + aActionBounds = tools::Rectangle( static_cast<const MetaMaskScaleAction&>(rAct).GetPoint(), + static_cast<const MetaMaskScaleAction&>(rAct).GetSize() ); + break; + + case MetaActionType::MASKSCALEPART: + aActionBounds = tools::Rectangle( static_cast<const MetaMaskScalePartAction&>(rAct).GetDestPoint(), + static_cast<const MetaMaskScalePartAction&>(rAct).GetDestSize() ); + break; + + case MetaActionType::GRADIENT: + aActionBounds = static_cast<const MetaGradientAction&>(rAct).GetRect(); + break; + + case MetaActionType::GRADIENTEX: + aActionBounds = static_cast<const MetaGradientExAction&>(rAct).GetPolyPolygon().GetBoundRect(); + break; + + case MetaActionType::HATCH: + aActionBounds = static_cast<const MetaHatchAction&>(rAct).GetPolyPolygon().GetBoundRect(); + break; + + case MetaActionType::WALLPAPER: + aActionBounds = static_cast<const MetaWallpaperAction&>(rAct).GetRect(); + break; + + case MetaActionType::Transparent: + aActionBounds = static_cast<const MetaTransparentAction&>(rAct).GetPolyPolygon().GetBoundRect(); + break; + + case MetaActionType::FLOATTRANSPARENT: + aActionBounds = tools::Rectangle( static_cast<const MetaFloatTransparentAction&>(rAct).GetPoint(), + static_cast<const MetaFloatTransparentAction&>(rAct).GetSize() ); + break; + + case MetaActionType::EPS: + aActionBounds = tools::Rectangle( static_cast<const MetaEPSAction&>(rAct).GetPoint(), + static_cast<const MetaEPSAction&>(rAct).GetSize() ); + break; + + case MetaActionType::TEXT: + { + const MetaTextAction& rTextAct = static_cast<const MetaTextAction&>(rAct); + const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) ); + + if (!aString.isEmpty()) + { + const Point aPtLog( rTextAct.GetPoint() ); + + // #105987# Use API method instead of Impl* methods + // #107490# Set base parameter equal to index parameter + rOut.GetTextBoundRect( aActionBounds, rTextAct.GetText(), rTextAct.GetIndex(), + rTextAct.GetIndex(), rTextAct.GetLen() ); + aActionBounds.Move( aPtLog.X(), aPtLog.Y() ); + } + } + break; + + case MetaActionType::TEXTARRAY: + { + const MetaTextArrayAction& rTextAct = static_cast<const MetaTextArrayAction&>(rAct); + const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) ); + + if( !aString.isEmpty() ) + { + // #105987# ImplLayout takes everything in logical coordinates + std::unique_ptr<SalLayout> pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(), + rTextAct.GetLen(), rTextAct.GetPoint(), + 0, rTextAct.GetDXArray(), rTextAct.GetKashidaArray() ); + if( pSalLayout ) + { + tools::Rectangle aBoundRect( rOut.ImplGetTextBoundRect( *pSalLayout ) ); + aActionBounds = rOut.PixelToLogic( aBoundRect ); + } + } + } + break; + + case MetaActionType::TEXTRECT: + aActionBounds = static_cast<const MetaTextRectAction&>(rAct).GetRect(); + break; + + case MetaActionType::STRETCHTEXT: + { + const MetaStretchTextAction& rTextAct = static_cast<const MetaStretchTextAction&>(rAct); + const OUString aString( rTextAct.GetText().copy(rTextAct.GetIndex(), rTextAct.GetLen()) ); + + // #i16195# Literate copy from TextArray action, the + // semantics for the ImplLayout call are copied from the + // OutDev::DrawStretchText() code. Unfortunately, also in + // this case, public outdev methods such as GetTextWidth() + // don't provide enough info. + if( !aString.isEmpty() ) + { + // #105987# ImplLayout takes everything in logical coordinates + std::unique_ptr<SalLayout> pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(), + rTextAct.GetLen(), rTextAct.GetPoint(), + rTextAct.GetWidth() ); + if( pSalLayout ) + { + tools::Rectangle aBoundRect( rOut.ImplGetTextBoundRect( *pSalLayout ) ); + aActionBounds = rOut.PixelToLogic( aBoundRect ); + } + } + } + break; + + case MetaActionType::TEXTLINE: + OSL_FAIL("MetaActionType::TEXTLINE not supported"); + break; + + default: + break; + } + + if( !aActionBounds.IsEmpty() ) + { + // fdo#40421 limit current action's output to clipped area + if( rOut.IsClipRegion() ) + return rOut.LogicToPixel( + rOut.GetClipRegion().GetBoundRect().Intersection( aActionBounds ) ); + else + return rOut.LogicToPixel( aActionBounds ); + } + else + return tools::Rectangle(); +} + +} // end anon namespace + +// TODO: this massive function operates on metafiles, so eventually it should probably +// be shifted to the GDIMetaFile class +bool OutputDevice::RemoveTransparenciesFromMetaFile( const GDIMetaFile& rInMtf, GDIMetaFile& rOutMtf, + tools::Long nMaxBmpDPIX, tools::Long nMaxBmpDPIY, + bool bReduceTransparency, bool bTransparencyAutoMode, + bool bDownsampleBitmaps, + const Color& rBackground + ) +{ + MetaAction* pCurrAct; + bool bTransparent( false ); + + rOutMtf.Clear(); + + if(!bReduceTransparency || bTransparencyAutoMode) + bTransparent = rInMtf.HasTransparentActions(); + + // #i10613# Determine set of connected components containing transparent objects. These are + // then processed as bitmaps, the original actions are removed from the metafile. + if( !bTransparent ) + { + // nothing transparent -> just copy + rOutMtf = rInMtf; + } + else + { + // #i10613# + // This works as follows: we want a number of distinct sets of + // connected components, where each set contains metafile + // actions that are intersecting (note: there are possibly + // more actions contained as are directly intersecting, + // because we can only produce rectangular bitmaps later + // on. Thus, each set of connected components is the smallest + // enclosing, axis-aligned rectangle that completely bounds a + // number of intersecting metafile actions, plus any action + // that would otherwise be cut in two). Therefore, we + // iteratively add metafile actions from the original metafile + // to this connected components list (aCCList), by checking + // each element's bounding box against intersection with the + // metaaction at hand. + // All those intersecting elements are removed from aCCList + // and collected in a temporary list (aCCMergeList). After all + // elements have been checked, the aCCMergeList elements are + // merged with the metaaction at hand into one resulting + // connected component, with one big bounding box, and + // inserted into aCCList again. + // The time complexity of this algorithm is O(n^3), where n is + // the number of metafile actions, and it finds all distinct + // regions of rectangle-bounded connected components. This + // algorithm was designed by AF. + + // STAGE 1: Detect background + + // Receives uniform background content, and is _not_ merged + // nor checked for intersection against other aCCList elements + ConnectedComponents aBackgroundComponent; + + // Read the configuration value of minimal object area where transparency will be removed + double fReduceTransparencyMinArea = officecfg::Office::Common::VCL::ReduceTransparencyMinArea::get() / 100.0; + SAL_WARN_IF(fReduceTransparencyMinArea > 1.0, "vcl", + "Value of ReduceTransparencyMinArea config option is too high"); + SAL_WARN_IF(fReduceTransparencyMinArea < 0.0, "vcl", + "Value of ReduceTransparencyMinArea config option is too low"); + fReduceTransparencyMinArea = std::clamp(fReduceTransparencyMinArea, 0.0, 1.0); + + // create an OutputDevice to record mapmode changes and the like + ScopedVclPtrInstance< VirtualDevice > aMapModeVDev; + aMapModeVDev->mnDPIX = mnDPIX; + aMapModeVDev->mnDPIY = mnDPIY; + aMapModeVDev->EnableOutput(false); + + // weed out page-filling background objects (if they are + // uniformly coloured). Keeping them outside the other + // connected components often prevents whole-page bitmap + // generation. + bool bStillBackground=true; // true until first non-bg action + int nActionNum = 0, nLastBgAction = -1; + pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(); + if( rBackground != COL_TRANSPARENT ) + { + aBackgroundComponent.aBgColor = rBackground; + aBackgroundComponent.aBounds = GetBackgroundComponentBounds(); + } + while( pCurrAct && bStillBackground ) + { + switch( pCurrAct->GetType() ) + { + case MetaActionType::RECT: + { + if( !checkRect( + aBackgroundComponent.aBounds, + aBackgroundComponent.aBgColor, + static_cast<const MetaRectAction*>(pCurrAct)->GetRect(), + *aMapModeVDev) ) + bStillBackground=false; // incomplete occlusion of background + else + nLastBgAction=nActionNum; // this _is_ background + break; + } + case MetaActionType::POLYGON: + { + const tools::Polygon aPoly( + static_cast<const MetaPolygonAction*>(pCurrAct)->GetPolygon()); + if( !basegfx::utils::isRectangle( + aPoly.getB2DPolygon()) || + !checkRect( + aBackgroundComponent.aBounds, + aBackgroundComponent.aBgColor, + aPoly.GetBoundRect(), + *aMapModeVDev) ) + bStillBackground=false; // incomplete occlusion of background + else + nLastBgAction=nActionNum; // this _is_ background + break; + } + case MetaActionType::POLYPOLYGON: + { + const tools::PolyPolygon aPoly( + static_cast<const MetaPolyPolygonAction*>(pCurrAct)->GetPolyPolygon()); + if( aPoly.Count() != 1 || + !basegfx::utils::isRectangle( + aPoly[0].getB2DPolygon()) || + !checkRect( + aBackgroundComponent.aBounds, + aBackgroundComponent.aBgColor, + aPoly.GetBoundRect(), + *aMapModeVDev) ) + bStillBackground=false; // incomplete occlusion of background + else + nLastBgAction=nActionNum; // this _is_ background + break; + } + case MetaActionType::WALLPAPER: + { + if( !checkRect( + aBackgroundComponent.aBounds, + aBackgroundComponent.aBgColor, + static_cast<const MetaWallpaperAction*>(pCurrAct)->GetRect(), + *aMapModeVDev) ) + bStillBackground=false; // incomplete occlusion of background + else + nLastBgAction=nActionNum; // this _is_ background + break; + } + default: + { + if( ImplIsNotTransparent( *pCurrAct, + *aMapModeVDev ) ) + bStillBackground=false; // non-transparent action, possibly + // not uniform + else + // extend current bounds (next uniform action + // needs to fully cover this area) + aBackgroundComponent.aBounds.Union( + ImplCalcActionBounds(*pCurrAct, *aMapModeVDev) ); + break; + } + } + + // execute action to get correct MapModes etc. + pCurrAct->Execute( aMapModeVDev.get() ); + + pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(); + ++nActionNum; + } + + if (nLastBgAction != -1) + { + size_t nActionSize = rInMtf.GetActionSize(); + // tdf#134736 move nLastBgAction to also include any trailing pops + for (size_t nPostLastBgAction = nLastBgAction + 1; nPostLastBgAction < nActionSize; ++nPostLastBgAction) + { + if (rInMtf.GetAction(nPostLastBgAction)->GetType() != MetaActionType::POP) + break; + nLastBgAction = nPostLastBgAction; + } + } + + aMapModeVDev->ClearStack(); // clean up aMapModeVDev + + // fast-forward until one after the last background action + // (need to reconstruct map mode vdev state) + nActionNum=0; + pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(); + while( pCurrAct && nActionNum<=nLastBgAction ) + { + // up to and including last ink-generating background + // action go to background component + aBackgroundComponent.aComponentList.emplace_back( + pCurrAct, nActionNum ); + + // execute action to get correct MapModes etc. + pCurrAct->Execute( aMapModeVDev.get() ); + pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(); + ++nActionNum; + } + + // STAGE 2: Generate connected components list + + ::std::vector<ConnectedComponents> aCCList; // contains distinct sets of connected components as elements. + + // iterate over all actions (start where background action + // search left off) + for( ; + pCurrAct; + pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum ) + { + // execute action to get correct MapModes etc. + pCurrAct->Execute( aMapModeVDev.get() ); + + // cache bounds of current action + const tools::Rectangle aBBCurrAct( ImplCalcActionBounds(*pCurrAct, *aMapModeVDev) ); + + // accumulate collected bounds here, initialize with current action + tools::Rectangle aTotalBounds( aBBCurrAct ); // thus, aTotalComponents.aBounds is empty + // for non-output-generating actions + bool bTreatSpecial( false ); + ConnectedComponents aTotalComponents; + + // STAGE 2.1: Search for intersecting cc entries + + // if aBBCurrAct is empty, it will intersect with no + // aCCList member. Thus, we can save the check. + // Furthermore, this ensures that non-output-generating + // actions get their own aCCList entry, which is necessary + // when copying them to the output metafile (see stage 4 + // below). + + // #107169# Wholly transparent objects need + // not be considered for connected components, + // too. Just put each of them into a separate + // component. + aTotalComponents.bIsFullyTransparent = !ImplIsNotTransparent(*pCurrAct, *aMapModeVDev); + + if( !aBBCurrAct.IsEmpty() && + !aTotalComponents.bIsFullyTransparent ) + { + if( !aBackgroundComponent.aComponentList.empty() && + !aBackgroundComponent.aBounds.Contains(aTotalBounds) ) + { + // it seems the background is not large enough. to + // be on the safe side, combine with this component. + aTotalBounds.Union( aBackgroundComponent.aBounds ); + + // extract all aCurr actions to aTotalComponents + aTotalComponents.aComponentList.splice( aTotalComponents.aComponentList.end(), + aBackgroundComponent.aComponentList ); + + if( aBackgroundComponent.bIsSpecial ) + bTreatSpecial = true; + } + + bool bSomeComponentsChanged; + + // now, this is unfortunate: since changing anyone of + // the aCCList elements (e.g. by merging or addition + // of an action) might generate new intersection with + // other aCCList elements, have to repeat the whole + // element scanning, until nothing changes anymore. + // Thus, this loop here makes us O(n^3) in the worst + // case. + do + { + // only loop here if 'intersects' branch below was hit + bSomeComponentsChanged = false; + + // iterate over all current members of aCCList + for( auto aCurrCC=aCCList.begin(); aCurrCC != aCCList.end(); ) + { + // first check if current element's bounds are + // empty. This ensures that empty actions are not + // merged into one component, as a matter of fact, + // they have no position. + + // #107169# Wholly transparent objects need + // not be considered for connected components, + // too. Just put each of them into a separate + // component. + if( !aCurrCC->aBounds.IsEmpty() && + !aCurrCC->bIsFullyTransparent && + aCurrCC->aBounds.Overlaps( aTotalBounds ) ) + { + // union the intersecting aCCList element into aTotalComponents + + // calc union bounding box + aTotalBounds.Union( aCurrCC->aBounds ); + + // extract all aCurr actions to aTotalComponents + aTotalComponents.aComponentList.splice( aTotalComponents.aComponentList.end(), + aCurrCC->aComponentList ); + + if( aCurrCC->bIsSpecial ) + bTreatSpecial = true; + + // remove and delete aCurrCC element from list (we've now merged its content) + aCurrCC = aCCList.erase( aCurrCC ); + + // at least one component changed, need to rescan everything + bSomeComponentsChanged = true; + } + else + { + ++aCurrCC; + } + } + } + while( bSomeComponentsChanged ); + } + + // STAGE 2.2: Determine special state for cc element + + // now test whether the whole connected component must be + // treated specially (i.e. rendered as a bitmap): if the + // added action is the very first action, or all actions + // before it are completely transparent, the connected + // component need not be treated specially, not even if + // the added action contains transparency. This is because + // painting of transparent objects on _white background_ + // works without alpha compositing (you just calculate the + // color). Note that for the test "all objects before me + // are transparent" no sorting is necessary, since the + // added metaaction pCurrAct is always in the order the + // metafile is painted. Generally, the order of the + // metaactions in the ConnectedComponents are not + // guaranteed to be the same as in the metafile. + if( bTreatSpecial ) + { + // prev component(s) special -> this one, too + aTotalComponents.bIsSpecial = true; + } + else if(!pCurrAct->IsTransparent()) + { + // added action and none of prev components special -> + // this one normal, too + aTotalComponents.bIsSpecial = false; + } + else + { + // added action is special and none of prev components + // special -> do the detailed tests + + // can the action handle transparency correctly + // (i.e. when painted on white background, does the + // action still look correct)? + if( !DoesActionHandleTransparency( *pCurrAct ) ) + { + // no, action cannot handle its transparency on + // a printer device, render to bitmap + aTotalComponents.bIsSpecial = true; + } + else + { + // yes, action can handle its transparency, so + // check whether we're on white background + if( aTotalComponents.aComponentList.empty() ) + { + // nothing between pCurrAct and page + // background -> don't be special + aTotalComponents.bIsSpecial = false; + } + else + { + // #107169# Fixes above now ensure that _no_ + // object in the list is fully transparent. Thus, + // if the component list is not empty above, we + // must assume that we have to treat this + // component special. + + // there are non-transparent objects between + // pCurrAct and the empty sheet of paper -> be + // special, then + aTotalComponents.bIsSpecial = true; + } + } + } + + // STAGE 2.3: Add newly generated CC list element + + // set new bounds and add action to list + aTotalComponents.aBounds = aTotalBounds; + aTotalComponents.aComponentList.emplace_back( + pCurrAct, nActionNum ); + + // add aTotalComponents as a new entry to aCCList + aCCList.push_back( aTotalComponents ); + + SAL_WARN_IF( aTotalComponents.aComponentList.empty(), "vcl", + "Printer::GetPreparedMetaFile empty component" ); + SAL_WARN_IF( aTotalComponents.aBounds.IsEmpty() && (aTotalComponents.aComponentList.size() != 1), "vcl", + "Printer::GetPreparedMetaFile non-output generating actions must be solitary"); + SAL_WARN_IF( aTotalComponents.bIsFullyTransparent && (aTotalComponents.aComponentList.size() != 1), "vcl", + "Printer::GetPreparedMetaFile fully transparent actions must be solitary"); + } + + // well now, we've got the list of disjunct connected + // components. Now we've got to create a map, which contains + // the corresponding aCCList element for every + // metaaction. Later on, we always process the complete + // metafile for each bitmap to be generated, but switch on + // output only for actions contained in the then current + // aCCList element. This ensures correct mapmode and attribute + // settings for all cases. + + // maps mtf actions to CC list entries + ::std::vector< const ConnectedComponents* > aCCList_MemberMap( rInMtf.GetActionSize() ); + + // iterate over all aCCList members and their contained metaactions + for (auto const& currentItem : aCCList) + { + for (auto const& currentAction : currentItem.aComponentList) + { + // set pointer to aCCList element for corresponding index + aCCList_MemberMap[ currentAction.second ] = ¤tItem; + } + } + + // STAGE 3.1: Output background mtf actions (if there are any) + + for (auto & component : aBackgroundComponent.aComponentList) + { + // simply add this action (above, we inserted the actions + // starting at index 0 up to and including nLastBgAction) + rOutMtf.AddAction( component.first ); + } + + // STAGE 3.2: Generate banded bitmaps for special regions + + Point aPageOffset; + Size aTmpSize( GetOutputSizePixel() ); + if( meOutDevType == OUTDEV_PDF ) + { + auto pPdfWriter = static_cast<vcl::PDFWriterImpl*>(this); + aTmpSize = LogicToPixel(pPdfWriter->getCurPageSize(), MapMode(MapUnit::MapPoint)); + + // also add error code to PDFWriter + pPdfWriter->insertError(vcl::PDFWriter::Warning_Transparency_Converted); + } + else if( meOutDevType == OUTDEV_PRINTER ) + { + Printer* pThis = dynamic_cast<Printer*>(this); + assert(pThis); + aPageOffset = pThis->GetPageOffsetPixel(); + aPageOffset = Point( 0, 0 ) - aPageOffset; + aTmpSize = pThis->GetPaperSizePixel(); + } + const tools::Rectangle aOutputRect( aPageOffset, aTmpSize ); + bool bTiling = dynamic_cast<Printer*>(this) != nullptr; + + // iterate over all aCCList members and generate bitmaps for the special ones + for (auto & currentItem : aCCList) + { + if( currentItem.bIsSpecial ) + { + tools::Rectangle aBoundRect( currentItem.aBounds ); + aBoundRect.Intersection( aOutputRect ); + + const double fBmpArea( static_cast<double>(aBoundRect.GetWidth()) * aBoundRect.GetHeight() ); + const double fOutArea( static_cast<double>(aOutputRect.GetWidth()) * aOutputRect.GetHeight() ); + + // check if output doesn't exceed given size + if( bReduceTransparency && bTransparencyAutoMode && ( fBmpArea > ( fReduceTransparencyMinArea * fOutArea ) ) ) + { + // output normally. Therefore, we simply clear the + // special attribute, as everything non-special is + // copied to rOutMtf further below. + currentItem.bIsSpecial = false; + } + else + { + // create new bitmap action first + if( aBoundRect.GetWidth() && aBoundRect.GetHeight() ) + { + Point aDstPtPix( aBoundRect.TopLeft() ); + Size aDstSzPix; + + ScopedVclPtrInstance<VirtualDevice> aMapVDev; // here, we record only mapmode information + aMapVDev->EnableOutput(false); + + ScopedVclPtrInstance<VirtualDevice> aPaintVDev; // into this one, we render. + aPaintVDev->SetBackground( aBackgroundComponent.aBgColor ); + + rOutMtf.AddAction( new MetaPushAction( vcl::PushFlags::MAPMODE ) ); + rOutMtf.AddAction( new MetaMapModeAction() ); + + aPaintVDev->SetDrawMode( GetDrawMode() ); + + while( aDstPtPix.Y() <= aBoundRect.Bottom() ) + { + aDstPtPix.setX( aBoundRect.Left() ); + aDstSzPix = bTiling ? Size( MAX_TILE_WIDTH, MAX_TILE_HEIGHT ) : aBoundRect.GetSize(); + + if( ( aDstPtPix.Y() + aDstSzPix.Height() - 1 ) > aBoundRect.Bottom() ) + aDstSzPix.setHeight( aBoundRect.Bottom() - aDstPtPix.Y() + 1 ); + + while( aDstPtPix.X() <= aBoundRect.Right() ) + { + if( ( aDstPtPix.X() + aDstSzPix.Width() - 1 ) > aBoundRect.Right() ) + aDstSzPix.setWidth( aBoundRect.Right() - aDstPtPix.X() + 1 ); + + if( !tools::Rectangle( aDstPtPix, aDstSzPix ).Intersection( aBoundRect ).IsEmpty() && + aPaintVDev->SetOutputSizePixel( aDstSzPix ) ) + { + aPaintVDev->Push(); + aMapVDev->Push(); + + aMapVDev->mnDPIX = aPaintVDev->mnDPIX = mnDPIX; + aMapVDev->mnDPIY = aPaintVDev->mnDPIY = mnDPIY; + + aPaintVDev->EnableOutput(false); + + // iterate over all actions + for( pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(), nActionNum=0; + pCurrAct; + pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum ) + { + // enable output only for + // actions that are members of + // the current aCCList element + // (currentItem) + if( aCCList_MemberMap[nActionNum] == ¤tItem ) + aPaintVDev->EnableOutput(); + + // but process every action + const MetaActionType nType( pCurrAct->GetType() ); + + if( MetaActionType::MAPMODE == nType ) + { + pCurrAct->Execute( aMapVDev.get() ); + + MapMode aMtfMap( aMapVDev->GetMapMode() ); + const Point aNewOrg( aMapVDev->PixelToLogic( aDstPtPix ) ); + + aMtfMap.SetOrigin( Point( -aNewOrg.X(), -aNewOrg.Y() ) ); + aPaintVDev->SetMapMode( aMtfMap ); + } + else if( ( MetaActionType::PUSH == nType ) || MetaActionType::POP == nType ) + { + pCurrAct->Execute( aMapVDev.get() ); + pCurrAct->Execute( aPaintVDev.get() ); + } + else if( MetaActionType::GRADIENT == nType ) + { + MetaGradientAction* pGradientAction = static_cast<MetaGradientAction*>(pCurrAct); + Printer* pPrinter = dynamic_cast< Printer* >(this); + if( pPrinter ) + pPrinter->DrawGradientEx( aPaintVDev.get(), pGradientAction->GetRect(), pGradientAction->GetGradient() ); + else + DrawGradient( pGradientAction->GetRect(), pGradientAction->GetGradient() ); + } + else + { + pCurrAct->Execute( aPaintVDev.get() ); + } + + Application::Reschedule( true ); + } + + const bool bOldMap = mbMap; + mbMap = aPaintVDev->mbMap = false; + + Bitmap aBandBmp( aPaintVDev->GetBitmap( Point(), aDstSzPix ) ); + + // scale down bitmap, if requested + if( bDownsampleBitmaps ) + aBandBmp = vcl::bitmap::GetDownsampledBitmap(PixelToLogic(LogicToPixel(aDstSzPix), MapMode(MapUnit::MapTwip)), + Point(), aBandBmp.GetSizePixel(), + aBandBmp, nMaxBmpDPIX, nMaxBmpDPIY); + + rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_BEGIN"_ostr ) ); + rOutMtf.AddAction( new MetaBmpScaleAction( aDstPtPix, aDstSzPix, aBandBmp ) ); + rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_END"_ostr ) ); + + aPaintVDev->mbMap = true; + mbMap = bOldMap; + aMapVDev->Pop(); + aPaintVDev->Pop(); + } + + // overlapping bands to avoid missing lines (e.g. PostScript) + aDstPtPix.AdjustX(aDstSzPix.Width() ); + } + + // overlapping bands to avoid missing lines (e.g. PostScript) + aDstPtPix.AdjustY(aDstSzPix.Height() ); + } + + rOutMtf.AddAction( new MetaPopAction() ); + } + } + } + } + + aMapModeVDev->ClearStack(); // clean up aMapModeVDev + + // STAGE 4: Copy actions to output metafile + + // iterate over all actions and duplicate the ones not in a + // special aCCList member into rOutMtf + for( pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(), nActionNum=0; + pCurrAct; + pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum ) + { + const ConnectedComponents* pCurrAssociatedComponent = aCCList_MemberMap[nActionNum]; + + // NOTE: This relies on the fact that map-mode or draw + // mode changing actions are solitary aCCList elements and + // have empty bounding boxes, see comment on stage 2.1 + // above + if( pCurrAssociatedComponent && + (pCurrAssociatedComponent->aBounds.IsEmpty() || + !pCurrAssociatedComponent->bIsSpecial) ) + { + // #107169# Treat transparent bitmaps special, if they + // are the first (or sole) action in their bounds + // list. Note that we previously ensured that no + // fully-transparent objects are before us here. + if( DoesActionHandleTransparency( *pCurrAct ) && + pCurrAssociatedComponent->aComponentList.begin()->first == pCurrAct ) + { + // convert actions, where masked-out parts are of + // given background color + ImplConvertTransparentAction(rOutMtf, + *pCurrAct, + *aMapModeVDev, + aBackgroundComponent.aBgColor); + } + else + { + // simply add this action + rOutMtf.AddAction( pCurrAct ); + } + + pCurrAct->Execute(aMapModeVDev.get()); + } + } + + rOutMtf.SetPrefMapMode( rInMtf.GetPrefMapMode() ); + rOutMtf.SetPrefSize( rInMtf.GetPrefSize() ); + +#if OSL_DEBUG_LEVEL > 1 + // iterate over all aCCList members and generate rectangles for the bounding boxes + rOutMtf.AddAction( new MetaFillColorAction( COL_WHITE, false ) ); + for(auto const& aCurr:aCCList) + { + if( aCurr.bIsSpecial ) + rOutMtf.AddAction( new MetaLineColorAction( COL_RED, true) ); + else + rOutMtf.AddAction( new MetaLineColorAction( COL_BLUE, true) ); + + rOutMtf.AddAction( new MetaRectAction( aMapModeVDev->PixelToLogic( aCurr.aBounds ) ) ); + } +#endif + } + return bTransparent; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/vclreferencebase.cxx b/vcl/source/outdev/vclreferencebase.cxx new file mode 100644 index 0000000000..62dd0e67f1 --- /dev/null +++ b/vcl/source/outdev/vclreferencebase.cxx @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/vclreferencebase.hxx> + +VclReferenceBase::VclReferenceBase() : + mnRefCnt(1), // cf. VclPtrInstance and README.lifecycle + mbDisposed(false) +{ +} + +VclReferenceBase::~VclReferenceBase() +{ + disposeOnce(); +} + +void VclReferenceBase::disposeOnce() +{ + if ( mbDisposed ) + return; + mbDisposed = true; + dispose(); +} + +void VclReferenceBase::dispose() +{ +} + diff --git a/vcl/source/outdev/wallpaper.cxx b/vcl/source/outdev/wallpaper.cxx new file mode 100644 index 0000000000..2ae7cb2e5a --- /dev/null +++ b/vcl/source/outdev/wallpaper.cxx @@ -0,0 +1,396 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> + +#include <cassert> + +Color OutputDevice::GetReadableFontColor(const Color& rFontColor, const Color& rBgColor) const +{ + if (rBgColor.IsDark() && rFontColor.IsDark()) + return COL_WHITE; + else if (rBgColor.IsBright() && rFontColor.IsBright()) + return COL_BLACK; + else + return rFontColor; +} + +void OutputDevice::DrawWallpaper( const tools::Rectangle& rRect, + const Wallpaper& rWallpaper ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaWallpaperAction( rRect, rWallpaper ) ); + + if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + if ( rWallpaper.GetStyle() != WallpaperStyle::NONE ) + { + tools::Rectangle aRect = LogicToPixel( rRect ); + aRect.Normalize(); + + if ( !aRect.IsEmpty() ) + { + DrawWallpaper( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), + rWallpaper ); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawWallpaper( rRect, rWallpaper ); +} + +void OutputDevice::DrawWallpaper( tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + const Wallpaper& rWallpaper ) +{ + assert(!is_double_buffered_window()); + + if( rWallpaper.IsBitmap() ) + DrawBitmapWallpaper( nX, nY, nWidth, nHeight, rWallpaper ); + else if( rWallpaper.IsGradient() ) + DrawGradientWallpaper( nX, nY, nWidth, nHeight, rWallpaper ); + else + DrawColorWallpaper( nX, nY, nWidth, nHeight, rWallpaper ); +} + +void OutputDevice::DrawColorWallpaper( tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + const Wallpaper& rWallpaper ) +{ + assert(!is_double_buffered_window()); + + // draw wallpaper without border + Color aOldLineColor = GetLineColor(); + Color aOldFillColor = GetFillColor(); + SetLineColor(); + SetFillColor( rWallpaper.GetColor() ); + + bool bMap = mbMap; + EnableMapMode( false ); + DrawRect( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ) ); + SetLineColor( aOldLineColor ); + SetFillColor( aOldFillColor ); + EnableMapMode( bMap ); +} + +void OutputDevice::Erase() +{ + if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + if ( mbBackground ) + { + RasterOp eRasterOp = GetRasterOp(); + if ( eRasterOp != RasterOp::OverPaint ) + SetRasterOp( RasterOp::OverPaint ); + DrawWallpaper( 0, 0, mnOutWidth, mnOutHeight, maBackground ); + if ( eRasterOp != RasterOp::OverPaint ) + SetRasterOp( eRasterOp ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->Erase(); +} + +void OutputDevice::Erase(const tools::Rectangle& rRect) +{ + const RasterOp eRasterOp = GetRasterOp(); + if ( eRasterOp != RasterOp::OverPaint ) + SetRasterOp( RasterOp::OverPaint ); + DrawWallpaper(rRect, GetBackground()); + if ( eRasterOp != RasterOp::OverPaint ) + SetRasterOp( eRasterOp ); + + if (mpAlphaVDev) + mpAlphaVDev->Erase(rRect); +} + +void OutputDevice::DrawBitmapWallpaper( tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + const Wallpaper& rWallpaper ) +{ + assert(!is_double_buffered_window()); + + const BitmapEx* pCached = rWallpaper.ImplGetCachedBitmap(); + + GDIMetaFile* pOldMetaFile = mpMetaFile; + const bool bOldMap = mbMap; + + BitmapEx aBmpEx; + if( pCached ) + aBmpEx = *pCached; + else + aBmpEx = rWallpaper.GetBitmap(); + + const tools::Long nBmpWidth = aBmpEx.GetSizePixel().Width(); + const tools::Long nBmpHeight = aBmpEx.GetSizePixel().Height(); + const bool bTransparent = aBmpEx.IsAlpha(); + + const WallpaperStyle eStyle = rWallpaper.GetStyle(); + + bool bDrawGradientBackground = false; + bool bDrawColorBackground = false; + + // draw background + if( bTransparent ) + { + if( rWallpaper.IsGradient() ) + bDrawGradientBackground = true; + else + { + if( !pCached && !rWallpaper.GetColor().IsTransparent() ) + { + ScopedVclPtrInstance< VirtualDevice > aVDev( *this ); + aVDev->SetBackground( rWallpaper.GetColor() ); + aVDev->SetOutputSizePixel( Size( nBmpWidth, nBmpHeight ) ); + aVDev->DrawBitmapEx( Point(), aBmpEx ); + aBmpEx = aVDev->GetBitmapEx( Point(), aVDev->GetOutputSizePixel() ); + } + + bDrawColorBackground = true; + } + } + else if( eStyle != WallpaperStyle::Tile && eStyle != WallpaperStyle::Scale ) + { + if( rWallpaper.IsGradient() ) + bDrawGradientBackground = true; + else + bDrawColorBackground = true; + } + + // background of bitmap? + if( bDrawGradientBackground ) + { + DrawGradientWallpaper( nX, nY, nWidth, nHeight, rWallpaper ); + } + else if( bDrawColorBackground && bTransparent ) + { + DrawColorWallpaper( nX, nY, nWidth, nHeight, rWallpaper ); + bDrawColorBackground = false; + } + + Point aPos; + Size aSize; + + // calc pos and size + if( rWallpaper.IsRect() ) + { + const tools::Rectangle aBound( LogicToPixel( rWallpaper.GetRect() ) ); + aPos = aBound.TopLeft(); + aSize = aBound.GetSize(); + } + else + { + aPos = Point( 0, 0 ); + aSize = Size( nWidth, nHeight ); + } + + mpMetaFile = nullptr; + EnableMapMode( false ); + Push( vcl::PushFlags::CLIPREGION ); + IntersectClipRegion( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ) ); + + bool bDrawn = false; + + switch( eStyle ) + { + case WallpaperStyle::Scale: + if( !pCached || ( pCached->GetSizePixel() != aSize ) ) + { + if( pCached ) + rWallpaper.ImplReleaseCachedBitmap(); + + aBmpEx = rWallpaper.GetBitmap(); + aBmpEx.Scale( aSize ); + aBmpEx = BitmapEx( aBmpEx.GetBitmap().CreateDisplayBitmap( this ), aBmpEx.GetAlphaMask() ); + } + break; + + case WallpaperStyle::TopLeft: + break; + + case WallpaperStyle::Top: + aPos.AdjustX(( aSize.Width() - nBmpWidth ) >> 1 ); + break; + + case WallpaperStyle::TopRight: + aPos.AdjustX( aSize.Width() - nBmpWidth); + break; + + case WallpaperStyle::Left: + aPos.AdjustY(( aSize.Height() - nBmpHeight ) >> 1 ); + break; + + case WallpaperStyle::Center: + aPos.AdjustX(( aSize.Width() - nBmpWidth ) >> 1 ); + aPos.AdjustY(( aSize.Height() - nBmpHeight ) >> 1 ); + break; + + case WallpaperStyle::Right: + aPos.AdjustX(aSize.Width() - nBmpWidth); + aPos.AdjustY(( aSize.Height() - nBmpHeight ) >> 1 ); + break; + + case WallpaperStyle::BottomLeft: + aPos.AdjustY( aSize.Height() - nBmpHeight ); + break; + + case WallpaperStyle::Bottom: + aPos.AdjustX(( aSize.Width() - nBmpWidth ) >> 1 ); + aPos.AdjustY( aSize.Height() - nBmpHeight ); + break; + + case WallpaperStyle::BottomRight: + aPos.AdjustX( aSize.Width() - nBmpWidth ); + aPos.AdjustY( aSize.Height() - nBmpHeight ); + break; + + default: + { + const tools::Long nRight = nX + nWidth - 1; + const tools::Long nBottom = nY + nHeight - 1; + tools::Long nFirstX; + tools::Long nFirstY; + + if( eStyle == WallpaperStyle::Tile ) + { + nFirstX = aPos.X(); + nFirstY = aPos.Y(); + } + else + { + nFirstX = aPos.X() + ( ( aSize.Width() - nBmpWidth ) >> 1 ); + nFirstY = aPos.Y() + ( ( aSize.Height() - nBmpHeight ) >> 1 ); + } + + const tools::Long nOffX = ( nFirstX - nX ) % nBmpWidth; + const tools::Long nOffY = ( nFirstY - nY ) % nBmpHeight; + tools::Long nStartX = nX + nOffX; + tools::Long nStartY = nY + nOffY; + + if( nOffX > 0 ) + nStartX -= nBmpWidth; + + if( nOffY > 0 ) + nStartY -= nBmpHeight; + + for( tools::Long nBmpY = nStartY; nBmpY <= nBottom; nBmpY += nBmpHeight ) + { + for( tools::Long nBmpX = nStartX; nBmpX <= nRight; nBmpX += nBmpWidth ) + { + DrawBitmapEx( Point( nBmpX, nBmpY ), aBmpEx ); + } + } + bDrawn = true; + } + break; + } + + if( !bDrawn ) + { + // optimized for non-transparent bitmaps + if( bDrawColorBackground ) + { + const Size aBmpSize( aBmpEx.GetSizePixel() ); + const Point aTmpPoint; + const tools::Rectangle aOutRect( aTmpPoint, GetOutputSizePixel() ); + const tools::Rectangle aColRect( Point( nX, nY ), Size( nWidth, nHeight ) ); + + tools::Rectangle aWorkRect( 0, 0, aOutRect.Right(), aPos.Y() - 1 ); + aWorkRect.Normalize(); + aWorkRect.Intersection( aColRect ); + if( !aWorkRect.IsEmpty() ) + { + DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(), + aWorkRect.GetWidth(), aWorkRect.GetHeight(), + rWallpaper ); + } + + aWorkRect = tools::Rectangle( 0, aPos.Y(), aPos.X() - 1, aPos.Y() + aBmpSize.Height() - 1 ); + aWorkRect.Normalize(); + aWorkRect.Intersection( aColRect ); + if( !aWorkRect.IsEmpty() ) + { + DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(), + aWorkRect.GetWidth(), aWorkRect.GetHeight(), + rWallpaper ); + } + + aWorkRect = tools::Rectangle( aPos.X() + aBmpSize.Width(), aPos.Y(), + aOutRect.Right(), aPos.Y() + aBmpSize.Height() - 1 ); + aWorkRect.Normalize(); + aWorkRect.Intersection( aColRect ); + if( !aWorkRect.IsEmpty() ) + { + DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(), + aWorkRect.GetWidth(), aWorkRect.GetHeight(), + rWallpaper ); + } + + aWorkRect = tools::Rectangle( 0, aPos.Y() + aBmpSize.Height(), + aOutRect.Right(), aOutRect.Bottom() ); + aWorkRect.Normalize(); + aWorkRect.Intersection( aColRect ); + if( !aWorkRect.IsEmpty() ) + { + DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(), + aWorkRect.GetWidth(), aWorkRect.GetHeight(), + rWallpaper ); + } + } + + DrawBitmapEx( aPos, aBmpEx ); + } + + rWallpaper.ImplSetCachedBitmap( aBmpEx ); + + Pop(); + EnableMapMode( bOldMap ); + mpMetaFile = pOldMetaFile; +} + +void OutputDevice::DrawGradientWallpaper( tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + const Wallpaper& rWallpaper ) +{ + assert(!is_double_buffered_window()); + + tools::Rectangle aBound; + GDIMetaFile* pOldMetaFile = mpMetaFile; + const bool bOldMap = mbMap; + + aBound = tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ); + + mpMetaFile = nullptr; + EnableMapMode( false ); + Push( vcl::PushFlags::CLIPREGION ); + IntersectClipRegion( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ) ); + + DrawGradient( aBound, rWallpaper.GetGradient() ); + + Pop(); + EnableMapMode( bOldMap ); + mpMetaFile = pOldMetaFile; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |