diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /vcl/source/outdev | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/outdev')
-rw-r--r-- | vcl/source/outdev/bitmap.cxx | 1788 | ||||
-rw-r--r-- | vcl/source/outdev/clipping.cxx | 226 | ||||
-rw-r--r-- | vcl/source/outdev/curvedshapes.cxx | 210 | ||||
-rw-r--r-- | vcl/source/outdev/font.cxx | 1435 | ||||
-rw-r--r-- | vcl/source/outdev/gradient.cxx | 1045 | ||||
-rw-r--r-- | vcl/source/outdev/hatch.cxx | 409 | ||||
-rw-r--r-- | vcl/source/outdev/line.cxx | 304 | ||||
-rw-r--r-- | vcl/source/outdev/map.cxx | 1936 | ||||
-rw-r--r-- | vcl/source/outdev/mask.cxx | 154 | ||||
-rw-r--r-- | vcl/source/outdev/nativecontrols.cxx | 356 | ||||
-rw-r--r-- | vcl/source/outdev/outdev.cxx | 707 | ||||
-rw-r--r-- | vcl/source/outdev/outdevstate.cxx | 609 | ||||
-rw-r--r-- | vcl/source/outdev/pixel.cxx | 116 | ||||
-rw-r--r-- | vcl/source/outdev/polygon.cxx | 518 | ||||
-rw-r--r-- | vcl/source/outdev/polyline.cxx | 386 | ||||
-rw-r--r-- | vcl/source/outdev/rect.cxx | 415 | ||||
-rw-r--r-- | vcl/source/outdev/text.cxx | 2510 | ||||
-rw-r--r-- | vcl/source/outdev/textline.cxx | 1026 | ||||
-rw-r--r-- | vcl/source/outdev/transparent.cxx | 859 | ||||
-rw-r--r-- | vcl/source/outdev/vclreferencebase.cxx | 44 | ||||
-rw-r--r-- | vcl/source/outdev/wallpaper.cxx | 396 |
21 files changed, 15449 insertions, 0 deletions
diff --git a/vcl/source/outdev/bitmap.cxx b/vcl/source/outdev/bitmap.cxx new file mode 100644 index 000000000..dcb9a6b1d --- /dev/null +++ b/vcl/source/outdev/bitmap.cxx @@ -0,0 +1,1788 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cassert> + +#include <vcl/bitmap.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/BitmapFilterStackBlur.hxx> +#include <vcl/bitmapaccess.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <config_features.h> +#if HAVE_FEATURE_OPENGL +#include <vcl/opengl/OpenGLHelper.hxx> +#endif +#include <vcl/skia/SkiaHelper.hxx> +#include <vcl/outdev.hxx> +#include <vcl/virdev.hxx> +#include <vcl/image.hxx> +#include <vcl/BitmapMonochromeFilter.hxx> + +#include <bmpfast.hxx> +#include <salgdi.hxx> +#include <salbmp.hxx> + +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <memory> +#include <comphelper/lok.hxx> +#include <bitmapwriteaccess.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/helpers.hxx> +#include <tools/debug.hxx> +#include <rtl/math.hxx> + +#include <vcl/dibtools.hxx> +#include <tools/stream.hxx> + +void OutputDevice::DrawBitmap( const Point& rDestPt, const Bitmap& rBitmap ) +{ + assert(!is_double_buffered_window()); + + const Size aSizePix( rBitmap.GetSizePixel() ); + DrawBitmap( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmap, MetaActionType::BMP ); +} + +void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize, const Bitmap& rBitmap ) +{ + assert(!is_double_buffered_window()); + + DrawBitmap( rDestPt, rDestSize, Point(), rBitmap.GetSizePixel(), rBitmap, MetaActionType::BMPSCALE ); +} + + +void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel, + const Bitmap& rBitmap, const MetaActionType nAction ) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if ( RasterOp::Invert == meRasterOp ) + { + DrawRect( tools::Rectangle( rDestPt, rDestSize ) ); + return; + } + + Bitmap aBmp( rBitmap ); + + if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | + DrawModeFlags::GrayBitmap ) ) + { + if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap ) ) + { + sal_uInt8 cCmpVal; + + if ( mnDrawMode & DrawModeFlags::BlackBitmap ) + cCmpVal = 0; + else + cCmpVal = 255; + + Color aCol( cCmpVal, cCmpVal, cCmpVal ); + Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); + SetLineColor( aCol ); + SetFillColor( aCol ); + DrawRect( tools::Rectangle( rDestPt, rDestSize ) ); + Pop(); + return; + } + else if( !!aBmp ) + { + if ( mnDrawMode & DrawModeFlags::GrayBitmap ) + aBmp.Convert( BmpConversion::N8BitGreys ); + } + } + + if ( mpMetaFile ) + { + switch( nAction ) + { + case MetaActionType::BMP: + mpMetaFile->AddAction( new MetaBmpAction( rDestPt, aBmp ) ); + break; + + case MetaActionType::BMPSCALE: + mpMetaFile->AddAction( new MetaBmpScaleAction( rDestPt, rDestSize, aBmp ) ); + break; + + case MetaActionType::BMPSCALEPART: + mpMetaFile->AddAction( new MetaBmpScalePartAction( + rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmp ) ); + break; + + default: break; + } + } + + if ( !IsDeviceOutputNecessary() ) + return; + + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if( !aBmp.IsEmpty() ) + { + SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(), + ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()), + ImplLogicWidthToDevicePixel(rDestSize.Width()), + ImplLogicHeightToDevicePixel(rDestSize.Height())); + + if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight ) + { + const BmpMirrorFlags nMirrFlags = AdjustTwoRect( aPosAry, aBmp.GetSizePixel() ); + + if ( nMirrFlags != BmpMirrorFlags::NONE ) + aBmp.Mirror( nMirrFlags ); + + if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight ) + { + if ( nAction == MetaActionType::BMPSCALE ) + ScaleBitmap (aBmp, aPosAry); + + mpGraphics->DrawBitmap( aPosAry, *aBmp.ImplGetSalBitmap(), this ); + } + } + } + + if( mpAlphaVDev ) + { + // #i32109#: Make bitmap area opaque + mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) ); + } +} + +Bitmap OutputDevice::GetDownsampledBitmap( const Size& rDstSz, + const Point& rSrcPt, const Size& rSrcSz, + const Bitmap& rBmp, long nMaxBmpDPIX, long nMaxBmpDPIY ) +{ + Bitmap aBmp( rBmp ); + + if( !aBmp.IsEmpty() ) + { + const tools::Rectangle aBmpRect( Point(), aBmp.GetSizePixel() ); + tools::Rectangle aSrcRect( rSrcPt, rSrcSz ); + + // do cropping if necessary + if( aSrcRect.Intersection( aBmpRect ) != aBmpRect ) + { + if( !aSrcRect.IsEmpty() ) + aBmp.Crop( aSrcRect ); + else + aBmp.SetEmpty(); + } + + if( !aBmp.IsEmpty() ) + { + // do downsampling if necessary + Size aDstSizeTwip( PixelToLogic(LogicToPixel(rDstSz), MapMode(MapUnit::MapTwip)) ); + + // #103209# Normalize size (mirroring has to happen outside of this method) + aDstSizeTwip = Size( labs(aDstSizeTwip.Width()), labs(aDstSizeTwip.Height()) ); + + const Size aBmpSize( aBmp.GetSizePixel() ); + const double fBmpPixelX = aBmpSize.Width(); + const double fBmpPixelY = aBmpSize.Height(); + const double fMaxPixelX = aDstSizeTwip.Width() * nMaxBmpDPIX / 1440.0; + const double fMaxPixelY = aDstSizeTwip.Height() * nMaxBmpDPIY / 1440.0; + + // check, if the bitmap DPI exceeds the maximum DPI (allow 4 pixel rounding tolerance) + if( ( ( fBmpPixelX > ( fMaxPixelX + 4 ) ) || + ( fBmpPixelY > ( fMaxPixelY + 4 ) ) ) && + ( fBmpPixelY > 0.0 ) && ( fMaxPixelY > 0.0 ) ) + { + // do scaling + Size aNewBmpSize; + const double fBmpWH = fBmpPixelX / fBmpPixelY; + const double fMaxWH = fMaxPixelX / fMaxPixelY; + + if( fBmpWH < fMaxWH ) + { + aNewBmpSize.setWidth( FRound( fMaxPixelY * fBmpWH ) ); + aNewBmpSize.setHeight( FRound( fMaxPixelY ) ); + } + else if( fBmpWH > 0.0 ) + { + aNewBmpSize.setWidth( FRound( fMaxPixelX ) ); + aNewBmpSize.setHeight( FRound( fMaxPixelX / fBmpWH) ); + } + + if( aNewBmpSize.Width() && aNewBmpSize.Height() ) + aBmp.Scale( aNewBmpSize ); + else + aBmp.SetEmpty(); + } + } + } + + return aBmp; +} + +void OutputDevice::DrawBitmapEx( const Point& rDestPt, + const BitmapEx& rBitmapEx ) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if( TransparentType::NONE == rBitmapEx.GetTransparentType() ) + { + DrawBitmap( rDestPt, rBitmapEx.GetBitmap() ); + } + else + { + const Size aSizePix( rBitmapEx.GetSizePixel() ); + DrawBitmapEx( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmapEx, MetaActionType::BMPEX ); + } +} + +void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize, + const BitmapEx& rBitmapEx ) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if ( TransparentType::NONE == rBitmapEx.GetTransparentType() ) + { + DrawBitmap( rDestPt, rDestSize, rBitmapEx.GetBitmap() ); + } + else + { + DrawBitmapEx( rDestPt, rDestSize, Point(), rBitmapEx.GetSizePixel(), rBitmapEx, MetaActionType::BMPEXSCALE ); + } +} + + +void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel, + const BitmapEx& rBitmapEx, const MetaActionType nAction ) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if( TransparentType::NONE == rBitmapEx.GetTransparentType() ) + { + DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx.GetBitmap() ); + } + else + { + if ( RasterOp::Invert == meRasterOp ) + { + DrawRect( tools::Rectangle( rDestPt, rDestSize ) ); + return; + } + + BitmapEx aBmpEx( rBitmapEx ); + + if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | + DrawModeFlags::GrayBitmap ) ) + { + if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap ) ) + { + Bitmap aColorBmp( aBmpEx.GetSizePixel(), 1 ); + sal_uInt8 cCmpVal; + + if ( mnDrawMode & DrawModeFlags::BlackBitmap ) + cCmpVal = 0; + else + cCmpVal = 255; + + aColorBmp.Erase( Color( cCmpVal, cCmpVal, cCmpVal ) ); + + if( aBmpEx.IsAlpha() ) + { + // Create one-bit mask out of alpha channel, by + // thresholding it at alpha=0.5. As + // DRAWMODE_BLACK/WHITEBITMAP requires monochrome + // output, having alpha-induced grey levels is not + // acceptable. + BitmapEx aMaskEx(aBmpEx.GetAlpha().GetBitmap()); + BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(129)); + aBmpEx = BitmapEx(aColorBmp, aMaskEx.GetBitmap()); + } + else + { + aBmpEx = BitmapEx( aColorBmp, aBmpEx.GetMask() ); + } + } + else if( !!aBmpEx ) + { + if ( mnDrawMode & DrawModeFlags::GrayBitmap ) + aBmpEx.Convert( BmpConversion::N8BitGreys ); + } + } + + if ( mpMetaFile ) + { + switch( nAction ) + { + case MetaActionType::BMPEX: + mpMetaFile->AddAction( new MetaBmpExAction( rDestPt, aBmpEx ) ); + break; + + case MetaActionType::BMPEXSCALE: + mpMetaFile->AddAction( new MetaBmpExScaleAction( rDestPt, rDestSize, aBmpEx ) ); + break; + + case MetaActionType::BMPEXSCALEPART: + mpMetaFile->AddAction( new MetaBmpExScalePartAction( rDestPt, rDestSize, + rSrcPtPixel, rSrcSizePixel, aBmpEx ) ); + break; + + default: break; + } + } + + if ( !IsDeviceOutputNecessary() ) + return; + + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + DrawDeviceBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmpEx ); + } +} + +Bitmap OutputDevice::GetBitmap( const Point& rSrcPt, const Size& rSize ) const +{ + Bitmap aBmp; + long nX = ImplLogicXToDevicePixel( rSrcPt.X() ); + long nY = ImplLogicYToDevicePixel( rSrcPt.Y() ); + long nWidth = ImplLogicWidthToDevicePixel( rSize.Width() ); + long nHeight = ImplLogicHeightToDevicePixel( rSize.Height() ); + + if ( mpGraphics || AcquireGraphics() ) + { + if ( nWidth > 0 && nHeight > 0 && nX <= (mnOutWidth + mnOutOffX) && nY <= (mnOutHeight + mnOutOffY)) + { + tools::Rectangle aRect( Point( nX, nY ), Size( nWidth, nHeight ) ); + bool bClipped = false; + + // X-Coordinate outside of draw area? + if ( nX < mnOutOffX ) + { + nWidth -= ( mnOutOffX - nX ); + nX = mnOutOffX; + bClipped = true; + } + + // Y-Coordinate outside of draw area? + if ( nY < mnOutOffY ) + { + nHeight -= ( mnOutOffY - nY ); + nY = mnOutOffY; + bClipped = true; + } + + // Width outside of draw area? + if ( (nWidth + nX) > (mnOutWidth + mnOutOffX) ) + { + nWidth = mnOutOffX + mnOutWidth - nX; + bClipped = true; + } + + // Height outside of draw area? + if ( (nHeight + nY) > (mnOutHeight + mnOutOffY) ) + { + nHeight = mnOutOffY + mnOutHeight - nY; + bClipped = true; + } + + if ( bClipped ) + { + // If the visible part has been clipped, we have to create a + // Bitmap with the correct size in which we copy the clipped + // Bitmap to the correct position. + ScopedVclPtrInstance< VirtualDevice > aVDev( *this ); + + if ( aVDev->SetOutputSizePixel( aRect.GetSize() ) ) + { + if ( aVDev->mpGraphics || aVDev->AcquireGraphics() ) + { + if ( (nWidth > 0) && (nHeight > 0) ) + { + SalTwoRect aPosAry(nX, nY, nWidth, nHeight, + (aRect.Left() < mnOutOffX) ? (mnOutOffX - aRect.Left()) : 0L, + (aRect.Top() < mnOutOffY) ? (mnOutOffY - aRect.Top()) : 0L, + nWidth, nHeight); + aVDev->mpGraphics->CopyBits( aPosAry, mpGraphics, this, this ); + } + else + { + OSL_ENSURE(false, "CopyBits with zero or negative width or height"); + } + + aBmp = aVDev->GetBitmap( Point(), aVDev->GetOutputSizePixel() ); + } + else + bClipped = false; + } + else + bClipped = false; + } + + if ( !bClipped ) + { + std::shared_ptr<SalBitmap> pSalBmp = mpGraphics->GetBitmap( nX, nY, nWidth, nHeight, this ); + + if( pSalBmp ) + { + aBmp.ImplSetSalBitmap(pSalBmp); + } + } + } + } + + return aBmp; +} + +BitmapEx OutputDevice::GetBitmapEx( const Point& rSrcPt, const Size& rSize ) const +{ + + // #110958# Extract alpha value from VDev, if any + if( mpAlphaVDev ) + { + Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( rSrcPt, rSize ) ); + + // ensure 8 bit alpha + if( aAlphaBitmap.GetBitCount() > 8 ) + aAlphaBitmap.Convert( BmpConversion::N8BitNoConversion ); + + return BitmapEx(GetBitmap( rSrcPt, rSize ), AlphaMask( aAlphaBitmap ) ); + } + else + return BitmapEx(GetBitmap( rSrcPt, rSize )); +} + +void OutputDevice::DrawDeviceBitmap( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel, + BitmapEx& rBitmapEx ) +{ + assert(!is_double_buffered_window()); + + if (rBitmapEx.IsAlpha()) + { + DrawDeviceAlphaBitmap(rBitmapEx.GetBitmap(), rBitmapEx.GetAlpha(), rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel); + } + else if (!!rBitmapEx) + { + SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(), + ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()), + ImplLogicWidthToDevicePixel(rDestSize.Width()), + ImplLogicHeightToDevicePixel(rDestSize.Height())); + + const BmpMirrorFlags nMirrFlags = AdjustTwoRect(aPosAry, rBitmapEx.GetSizePixel()); + + if (aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight) + { + + if (nMirrFlags != BmpMirrorFlags::NONE) + rBitmapEx.Mirror(nMirrFlags); + + const SalBitmap* pSalSrcBmp = rBitmapEx.ImplGetBitmapSalBitmap().get(); + std::shared_ptr<SalBitmap> xMaskBmp = rBitmapEx.ImplGetMaskSalBitmap(); + + if (xMaskBmp) + { + bool bTryDirectPaint(pSalSrcBmp); + + if (bTryDirectPaint && mpGraphics->DrawAlphaBitmap(aPosAry, *pSalSrcBmp, *xMaskBmp, this)) + { + // tried to paint as alpha directly. If this worked, we are done (except + // alpha, see below) + } + else + { + // #4919452# reduce operation area to bounds of + // cliprect. since masked transparency involves + // creation of a large vdev and copying the screen + // content into that (slooow read from framebuffer), + // that should considerably increase performance for + // large bitmaps and small clippings. + + // Note that this optimization is a workaround for a + // Writer peculiarity, namely, to decompose background + // graphics into myriads of disjunct, tiny + // rectangles. That otherwise kills us here, since for + // transparent output, SAL always prepares the whole + // bitmap, if aPosAry contains the whole bitmap (and + // it's _not_ to blame for that). + + // Note the call to ImplPixelToDevicePixel(), since + // aPosAry already contains the mnOutOff-offsets, they + // also have to be applied to the region + tools::Rectangle aClipRegionBounds( ImplPixelToDevicePixel(maRegion).GetBoundRect() ); + + // TODO: Also respect scaling (that's a bit tricky, + // since the source points have to move fractional + // amounts (which is not possible, thus has to be + // emulated by increases copy area) + // const double nScaleX( aPosAry.mnDestWidth / aPosAry.mnSrcWidth ); + // const double nScaleY( aPosAry.mnDestHeight / aPosAry.mnSrcHeight ); + + // for now, only identity scales allowed + if (!aClipRegionBounds.IsEmpty() && + aPosAry.mnDestWidth == aPosAry.mnSrcWidth && + aPosAry.mnDestHeight == aPosAry.mnSrcHeight) + { + // now intersect dest rect with clip region + aClipRegionBounds.Intersection(tools::Rectangle(aPosAry.mnDestX, + aPosAry.mnDestY, + aPosAry.mnDestX + aPosAry.mnDestWidth - 1, + aPosAry.mnDestY + aPosAry.mnDestHeight - 1)); + + // Note: I could theoretically optimize away the + // DrawBitmap below, if the region is empty + // here. Unfortunately, cannot rule out that + // somebody relies on the side effects. + if (!aClipRegionBounds.IsEmpty()) + { + aPosAry.mnSrcX += aClipRegionBounds.Left() - aPosAry.mnDestX; + aPosAry.mnSrcY += aClipRegionBounds.Top() - aPosAry.mnDestY; + aPosAry.mnSrcWidth = aClipRegionBounds.GetWidth(); + aPosAry.mnSrcHeight = aClipRegionBounds.GetHeight(); + + aPosAry.mnDestX = aClipRegionBounds.Left(); + aPosAry.mnDestY = aClipRegionBounds.Top(); + aPosAry.mnDestWidth = aClipRegionBounds.GetWidth(); + aPosAry.mnDestHeight = aClipRegionBounds.GetHeight(); + } + } + + mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, *xMaskBmp, this); + } + + // #110958# Paint mask to alpha channel. Luckily, the + // black and white representation of the mask maps to + // the alpha channel + + // #i25167# Restrict mask painting to _opaque_ areas + // of the mask, otherwise we spoil areas where no + // bitmap content was ever visible. Interestingly + // enough, this can be achieved by taking the mask as + // the transparency mask of itself + if (mpAlphaVDev) + mpAlphaVDev->DrawBitmapEx(rDestPt, + rDestSize, + BitmapEx(rBitmapEx.GetMask(), + rBitmapEx.GetMask())); + } + else + { + mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, this); + + if (mpAlphaVDev) + { + // #i32109#: Make bitmap area opaque + mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) ); + } + } + } + } +} + +void OutputDevice::DrawDeviceAlphaBitmap( const Bitmap& rBmp, const AlphaMask& rAlpha, + const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel ) +{ + assert(!is_double_buffered_window()); + + Point aOutPt(LogicToPixel(rDestPt)); + Size aOutSz(LogicToPixel(rDestSize)); + tools::Rectangle aDstRect(Point(), GetOutputSizePixel()); + + const bool bHMirr = aOutSz.Width() < 0; + const bool bVMirr = aOutSz.Height() < 0; + + ClipToPaintRegion(aDstRect); + + if (bHMirr) + { + aOutSz.setWidth( -aOutSz.Width() ); + aOutPt.AdjustX( -(aOutSz.Width() - 1) ); + } + + if (bVMirr) + { + aOutSz.setHeight( -aOutSz.Height() ); + aOutPt.AdjustY( -(aOutSz.Height() - 1) ); + } + + if (!aDstRect.Intersection(tools::Rectangle(aOutPt, aOutSz)).IsEmpty()) + { + static const char* pDisableNative = getenv( "SAL_DISABLE_NATIVE_ALPHA"); + bool bTryDirectPaint(!pDisableNative && !bHMirr && !bVMirr); + + if (bTryDirectPaint) + { + Point aRelPt = aOutPt + Point(mnOutOffX, mnOutOffY); + SalTwoRect aTR( + rSrcPtPixel.X(), rSrcPtPixel.Y(), + rSrcSizePixel.Width(), rSrcSizePixel.Height(), + aRelPt.X(), aRelPt.Y(), + aOutSz.Width(), aOutSz.Height()); + + SalBitmap* pSalSrcBmp = rBmp.ImplGetSalBitmap().get(); + SalBitmap* pSalAlphaBmp = rAlpha.ImplGetSalBitmap().get(); + + // #i83087# Naturally, system alpha blending (SalGraphics::DrawAlphaBitmap) cannot work + // with separate alpha VDev + + // try to blend the alpha bitmap with the alpha virtual device + if (mpAlphaVDev) + { + Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aRelPt, aOutSz ) ); + if (SalBitmap* pSalAlphaBmp2 = aAlphaBitmap.ImplGetSalBitmap().get()) + { + if (mpGraphics->BlendAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, *pSalAlphaBmp2, this)) + { + mpAlphaVDev->BlendBitmap(aTR, rAlpha); + return; + } + } + } + else + { + if (mpGraphics->DrawAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, this)) + return; + } + } + + // we need to make sure OpenGL never reaches this slow code path + + assert(!SkiaHelper::isVCLSkiaEnabled()); +#if HAVE_FEATURE_OPENGL + assert(!OpenGLHelper::isVCLOpenGLEnabled()); +#endif + tools::Rectangle aBmpRect(Point(), rBmp.GetSizePixel()); + if (!aBmpRect.Intersection(tools::Rectangle(rSrcPtPixel, rSrcSizePixel)).IsEmpty()) + { + Point auxOutPt(LogicToPixel(rDestPt)); + Size auxOutSz(LogicToPixel(rDestSize)); + + DrawDeviceAlphaBitmapSlowPath(rBmp, rAlpha, aDstRect, aBmpRect, auxOutSz, auxOutPt); + } + } +} + +namespace +{ + +struct LinearScaleContext +{ + std::unique_ptr<long[]> mpMapX; + std::unique_ptr<long[]> mpMapY; + + std::unique_ptr<long[]> mpMapXOffset; + std::unique_ptr<long[]> mpMapYOffset; + + LinearScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect, + Size const & aOutSize, long nOffX, long nOffY) + + : mpMapX(new long[aDstRect.GetWidth()]) + , mpMapY(new long[aDstRect.GetHeight()]) + , mpMapXOffset(new long[aDstRect.GetWidth()]) + , mpMapYOffset(new long[aDstRect.GetHeight()]) + { + const long nSrcWidth = aBitmapRect.GetWidth(); + const long nSrcHeight = aBitmapRect.GetHeight(); + + generateSimpleMap( + nSrcWidth, aDstRect.GetWidth(), aBitmapRect.Left(), + aOutSize.Width(), nOffX, mpMapX.get(), mpMapXOffset.get()); + + generateSimpleMap( + nSrcHeight, aDstRect.GetHeight(), aBitmapRect.Top(), + aOutSize.Height(), nOffY, mpMapY.get(), mpMapYOffset.get()); + } + +private: + + static void generateSimpleMap(long nSrcDimension, long nDstDimension, long nDstLocation, + long nOutDimention, long nOffset, long* pMap, long* pMapOffset) + { + + const double fReverseScale = (std::abs(nOutDimention) > 1) ? (nSrcDimension - 1) / double(std::abs(nOutDimention) - 1) : 0.0; + + long nSampleRange = std::max(0L, nSrcDimension - 2); + + for (long i = 0; i < nDstDimension; i++) + { + double fTemp = std::abs((nOffset + i) * fReverseScale); + + pMap[i] = MinMax(nDstLocation + long(fTemp), 0, nSampleRange); + pMapOffset[i] = static_cast<long>((fTemp - pMap[i]) * 128.0); + } + } + +public: + bool blendBitmap( + const BitmapWriteAccess* pDestination, + const BitmapReadAccess* pSource, + const BitmapReadAccess* pSourceAlpha, + const long nDstWidth, + const long nDstHeight) + { + if (pSource && pSourceAlpha && pDestination) + { + ScanlineFormat nSourceFormat = pSource->GetScanlineFormat(); + ScanlineFormat nDestinationFormat = pDestination->GetScanlineFormat(); + + switch (nSourceFormat) + { + case ScanlineFormat::N24BitTcRgb: + case ScanlineFormat::N24BitTcBgr: + { + if ( (nSourceFormat == ScanlineFormat::N24BitTcBgr && nDestinationFormat == ScanlineFormat::N32BitTcBgra) + || (nSourceFormat == ScanlineFormat::N24BitTcRgb && nDestinationFormat == ScanlineFormat::N32BitTcRgba)) + { + blendBitmap24(pDestination, pSource, pSourceAlpha, nDstWidth, nDstHeight); + return true; + } + } + break; + default: break; + } + } + return false; + } + + void blendBitmap24( + const BitmapWriteAccess* pDestination, + const BitmapReadAccess* pSource, + const BitmapReadAccess* pSourceAlpha, + const long nDstWidth, + const long nDstHeight) + { + Scanline pLine0, pLine1; + Scanline pLineAlpha0, pLineAlpha1; + Scanline pColorSample1, pColorSample2; + Scanline pDestScanline; + + long nColor1Line1, nColor2Line1, nColor3Line1; + long nColor1Line2, nColor2Line2, nColor3Line2; + long nAlphaLine1, nAlphaLine2; + + sal_uInt8 nColor1, nColor2, nColor3, nAlpha; + + for (long nY = 0; nY < nDstHeight; nY++) + { + const long nMapY = mpMapY[nY]; + const long nMapFY = mpMapYOffset[nY]; + + pLine0 = pSource->GetScanline(nMapY); + // tdf#95481 guard nMapY + 1 to be within bounds + pLine1 = (nMapY + 1 < pSource->Height()) ? pSource->GetScanline(nMapY + 1) : pLine0; + + pLineAlpha0 = pSourceAlpha->GetScanline(nMapY); + // tdf#95481 guard nMapY + 1 to be within bounds + pLineAlpha1 = (nMapY + 1 < pSourceAlpha->Height()) ? pSourceAlpha->GetScanline(nMapY + 1) : pLineAlpha0; + + pDestScanline = pDestination->GetScanline(nY); + + for (long nX = 0; nX < nDstWidth; nX++) + { + const long nMapX = mpMapX[nX]; + const long nMapFX = mpMapXOffset[nX]; + + pColorSample1 = pLine0 + 3 * nMapX; + pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1; + nColor1Line1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + pColorSample1++; + pColorSample2++; + nColor2Line1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + pColorSample1++; + pColorSample2++; + nColor3Line1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + pColorSample1 = pLine1 + 3 * nMapX; + pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1; + nColor1Line2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + pColorSample1++; + pColorSample2++; + nColor2Line2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + pColorSample1++; + pColorSample2++; + nColor3Line2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + pColorSample1 = pLineAlpha0 + nMapX; + pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1; + nAlphaLine1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + pColorSample1 = pLineAlpha1 + nMapX; + pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1; + nAlphaLine2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1); + + nColor1 = (nColor1Line1 + nMapFY * ((nColor1Line2 >> 7) - (nColor1Line1 >> 7))) >> 7; + nColor2 = (nColor2Line1 + nMapFY * ((nColor2Line2 >> 7) - (nColor2Line1 >> 7))) >> 7; + nColor3 = (nColor3Line1 + nMapFY * ((nColor3Line2 >> 7) - (nColor3Line1 >> 7))) >> 7; + + nAlpha = (nAlphaLine1 + nMapFY * ((nAlphaLine2 >> 7) - (nAlphaLine1 >> 7))) >> 7; + + *pDestScanline = ColorChannelMerge(*pDestScanline, nColor1, nAlpha); + pDestScanline++; + *pDestScanline = ColorChannelMerge(*pDestScanline, nColor2, nAlpha); + pDestScanline++; + *pDestScanline = ColorChannelMerge(*pDestScanline, nColor3, nAlpha); + pDestScanline++; + pDestScanline++; + } + } + } +}; + +struct TradScaleContext +{ + std::unique_ptr<long[]> mpMapX; + std::unique_ptr<long[]> mpMapY; + + TradScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect, + Size const & aOutSize, long nOffX, long nOffY) + + : mpMapX(new long[aDstRect.GetWidth()]) + , mpMapY(new long[aDstRect.GetHeight()]) + { + const long nSrcWidth = aBitmapRect.GetWidth(); + const long nSrcHeight = aBitmapRect.GetHeight(); + + const bool bHMirr = aOutSize.Width() < 0; + const bool bVMirr = aOutSize.Height() < 0; + + generateSimpleMap( + nSrcWidth, aDstRect.GetWidth(), aBitmapRect.Left(), + aOutSize.Width(), nOffX, bHMirr, mpMapX.get()); + + generateSimpleMap( + nSrcHeight, aDstRect.GetHeight(), aBitmapRect.Top(), + aOutSize.Height(), nOffY, bVMirr, mpMapY.get()); + } + +private: + + static void generateSimpleMap(long nSrcDimension, long nDstDimension, long nDstLocation, + long nOutDimention, long nOffset, bool bMirror, long* pMap) + { + long nMirrorOffset = 0; + + if (bMirror) + nMirrorOffset = (nDstLocation << 1) + nSrcDimension - 1; + + for (long i = 0; i < nDstDimension; ++i, ++nOffset) + { + pMap[i] = nDstLocation + nOffset * nSrcDimension / nOutDimention; + if (bMirror) + pMap[i] = nMirrorOffset - pMap[i]; + } + } +}; + + +} // end anonymous namespace + +void OutputDevice::DrawDeviceAlphaBitmapSlowPath(const Bitmap& rBitmap, + const AlphaMask& rAlpha, tools::Rectangle aDstRect, tools::Rectangle aBmpRect, Size const & aOutSize, Point const & aOutPoint) +{ + assert(!is_double_buffered_window()); + + VirtualDevice* pOldVDev = mpAlphaVDev; + + const bool bHMirr = aOutSize.Width() < 0; + const bool bVMirr = aOutSize.Height() < 0; + + // The scaling in this code path produces really ugly results - it + // does the most trivial scaling with no smoothing. + GDIMetaFile* pOldMetaFile = mpMetaFile; + const bool bOldMap = mbMap; + + mpMetaFile = nullptr; // fdo#55044 reset before GetBitmap! + mbMap = false; + + Bitmap aBmp(GetBitmap(aDstRect.TopLeft(), aDstRect.GetSize())); + + // #109044# The generated bitmap need not necessarily be + // of aDstRect dimensions, it's internally clipped to + // window bounds. Thus, we correct the dest size here, + // since we later use it (in nDstWidth/Height) for pixel + // access) + // #i38887# reading from screen may sometimes fail + if (aBmp.ImplGetSalBitmap()) + { + aDstRect.SetSize(aBmp.GetSizePixel()); + } + + const long nDstWidth = aDstRect.GetWidth(); + const long nDstHeight = aDstRect.GetHeight(); + + // calculate offset in original bitmap + // in RTL case this is a little more complicated since the contents of the + // bitmap is not mirrored (it never is), however the paint region and bmp region + // are in mirrored coordinates, so the intersection of (aOutPt,aOutSz) with these + // is content wise somewhere else and needs to take mirroring into account + const long nOffX = IsRTLEnabled() + ? aOutSize.Width() - aDstRect.GetWidth() - (aDstRect.Left() - aOutPoint.X()) + : aDstRect.Left() - aOutPoint.X(); + + const long nOffY = aDstRect.Top() - aOutPoint.Y(); + + TradScaleContext aTradContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY); + + Bitmap::ScopedReadAccess pBitmapReadAccess(const_cast<Bitmap&>(rBitmap)); + AlphaMask::ScopedReadAccess pAlphaReadAccess(const_cast<AlphaMask&>(rAlpha)); + + DBG_ASSERT( pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal || + pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitTcMask, + "OutputDevice::ImplDrawAlpha(): non-8bit alpha no longer supported!" ); + + // #i38887# reading from screen may sometimes fail + if (aBmp.ImplGetSalBitmap()) + { + Bitmap aNewBitmap; + + if (mpAlphaVDev) + { + aNewBitmap = BlendBitmapWithAlpha( + aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(), + aDstRect, + nOffY, nDstHeight, + nOffX, nDstWidth, + aTradContext.mpMapX.get(), aTradContext.mpMapY.get() ); + } + else + { + LinearScaleContext aLinearContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY); + + if (aLinearContext.blendBitmap( BitmapScopedWriteAccess(aBmp).get(), pBitmapReadAccess.get(), pAlphaReadAccess.get(), + nDstWidth, nDstHeight)) + { + aNewBitmap = aBmp; + } + else + { + aNewBitmap = BlendBitmap( + aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(), + nOffY, nDstHeight, + nOffX, nDstWidth, + aBmpRect, aOutSize, + bHMirr, bVMirr, + aTradContext.mpMapX.get(), aTradContext.mpMapY.get() ); + } + } + + // #110958# Disable alpha VDev, we're doing the necessary + // stuff explicitly further below + if (mpAlphaVDev) + mpAlphaVDev = nullptr; + + DrawBitmap(aDstRect.TopLeft(), aNewBitmap); + + // #110958# Enable alpha VDev again + mpAlphaVDev = pOldVDev; + } + + mbMap = bOldMap; + mpMetaFile = pOldMetaFile; +} + +void OutputDevice::ScaleBitmap (Bitmap &rBmp, SalTwoRect &rPosAry) +{ + const double nScaleX = rPosAry.mnDestWidth / static_cast<double>( rPosAry.mnSrcWidth ); + const double nScaleY = rPosAry.mnDestHeight / static_cast<double>( rPosAry.mnSrcHeight ); + + // If subsampling, use Bitmap::Scale for subsampling for better quality. + if ( nScaleX < 1.0 || nScaleY < 1.0 ) + { + rBmp.Scale ( nScaleX, nScaleY ); + rPosAry.mnSrcWidth = rPosAry.mnDestWidth; + rPosAry.mnSrcHeight = rPosAry.mnDestHeight; + } +} + +bool OutputDevice::DrawTransformBitmapExDirect( + const basegfx::B2DHomMatrix& aFullTransform, + const BitmapEx& rBitmapEx) +{ + assert(!is_double_buffered_window()); + + bool bDone = false; + + // try to paint directly + const basegfx::B2DPoint aNull(aFullTransform * basegfx::B2DPoint(0.0, 0.0)); + const basegfx::B2DPoint aTopX(aFullTransform * basegfx::B2DPoint(1.0, 0.0)); + const basegfx::B2DPoint aTopY(aFullTransform * basegfx::B2DPoint(0.0, 1.0)); + SalBitmap* pSalSrcBmp = rBitmapEx.GetBitmap().ImplGetSalBitmap().get(); + Bitmap aAlphaBitmap; + + if(rBitmapEx.IsTransparent()) + { + if(rBitmapEx.IsAlpha()) + { + aAlphaBitmap = rBitmapEx.GetAlpha(); + } + else + { + aAlphaBitmap = rBitmapEx.GetMask(); + } + } + else if (mpAlphaVDev) + { + aAlphaBitmap = Bitmap(rBitmapEx.GetSizePixel(), 1); + aAlphaBitmap.Erase(COL_BLACK); + } + + SalBitmap* pSalAlphaBmp = aAlphaBitmap.ImplGetSalBitmap().get(); + + bDone = mpGraphics->DrawTransformedBitmap( + aNull, + aTopX, + aTopY, + *pSalSrcBmp, + pSalAlphaBmp, + this); + + if (mpAlphaVDev) + { + // Merge bitmap alpha to alpha device + Bitmap aBlack(rBitmapEx.GetSizePixel(), 1); + aBlack.Erase(COL_BLACK); + mpAlphaVDev->DrawTransformBitmapExDirect(aFullTransform, BitmapEx(aBlack, aAlphaBitmap)); + } + + return bDone; +}; + +bool OutputDevice::TransformAndReduceBitmapExToTargetRange( + const basegfx::B2DHomMatrix& aFullTransform, + basegfx::B2DRange &aVisibleRange, + double &fMaximumArea) +{ + // limit TargetRange to existing pixels (if pixel device) + // first get discrete range of object + basegfx::B2DRange aFullPixelRange(aVisibleRange); + + aFullPixelRange.transform(aFullTransform); + + if(basegfx::fTools::equalZero(aFullPixelRange.getWidth()) || basegfx::fTools::equalZero(aFullPixelRange.getHeight())) + { + // object is outside of visible area + return false; + } + + // now get discrete target pixels; start with OutDev pixel size and evtl. + // intersect with active clipping area + basegfx::B2DRange aOutPixel( + 0.0, + 0.0, + GetOutputSizePixel().Width(), + GetOutputSizePixel().Height()); + + if(IsClipRegion()) + { + tools::Rectangle aRegionRectangle(GetActiveClipRegion().GetBoundRect()); + + // caution! Range from rectangle, one too much (!) + aRegionRectangle.AdjustRight(-1); + aRegionRectangle.AdjustBottom(-1); + aOutPixel.intersect( vcl::unotools::b2DRectangleFromRectangle(aRegionRectangle) ); + } + + if(aOutPixel.isEmpty()) + { + // no active output area + return false; + } + + // if aFullPixelRange is not completely inside of aOutPixel, + // reduction of target pixels is possible + basegfx::B2DRange aVisiblePixelRange(aFullPixelRange); + + if(!aOutPixel.isInside(aFullPixelRange)) + { + aVisiblePixelRange.intersect(aOutPixel); + + if(aVisiblePixelRange.isEmpty()) + { + // nothing in visible part, reduces to nothing + return false; + } + + // aVisiblePixelRange contains the reduced output area in + // discrete coordinates. To make it useful everywhere, make it relative to + // the object range + basegfx::B2DHomMatrix aMakeVisibleRangeRelative; + + aVisibleRange = aVisiblePixelRange; + aMakeVisibleRangeRelative.translate( + -aFullPixelRange.getMinX(), + -aFullPixelRange.getMinY()); + aMakeVisibleRangeRelative.scale( + 1.0 / aFullPixelRange.getWidth(), + 1.0 / aFullPixelRange.getHeight()); + aVisibleRange.transform(aMakeVisibleRangeRelative); + } + + // for pixel devices, do *not* limit size, else OutputDevice::DrawDeviceAlphaBitmap + // will create another, badly scaled bitmap to do the job. Nonetheless, do a + // maximum clipping of something big (1600x1280x2). Add 1.0 to avoid rounding + // errors in rough estimations + const double fNewMaxArea(aVisiblePixelRange.getWidth() * aVisiblePixelRange.getHeight()); + + fMaximumArea = std::min(4096000.0, fNewMaxArea + 1.0); + + return true; +} + +// MM02 add some test class to get a simple timer-based output to be able +// to check if it gets faster - and how much. Uncomment next line or set +// DO_TIME_TEST for compile time if you want to use it +// #define DO_TIME_TEST +#ifdef DO_TIME_TEST +#include <tools/time.hxx> +struct LocalTimeTest +{ + const sal_uInt64 nStartTime; + LocalTimeTest() : nStartTime(tools::Time::GetSystemTicks()) {} + ~LocalTimeTest() + { + const sal_uInt64 nEndTime(tools::Time::GetSystemTicks()); + const sal_uInt64 nDiffTime(nEndTime - nStartTime); + + if(nDiffTime > 0) + { + OStringBuffer aOutput("Time: "); + OString aNumber(OString::number(nDiffTime)); + aOutput.append(aNumber); + OSL_FAIL(aOutput.getStr()); + } + } +}; +#endif + +void OutputDevice::DrawTransformedBitmapEx( + const basegfx::B2DHomMatrix& rTransformation, + const BitmapEx& rBitmapEx) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if(rBitmapEx.IsEmpty()) + return; + + // MM02 compared to other public methods of OutputDevice + // this test was missing and led to zero-ptr-accesses + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + const bool bMetafile(nullptr != mpMetaFile); + /* + tdf#135325 typically in these OutputDevice methods the in + record-to-metafile case MetaFile is already written to before the test + against mbOutputClipped to determine that output to the current device + would result in no visual output. In this case the metafile is written + after the test, so we must continue past mbOutputClipped if recording to + a metafile. It's typical to record with a device of nominal size and + play back later against something of a totally different size. + */ + if (mbOutputClipped && !bMetafile) + return; + +#ifdef DO_TIME_TEST + // MM02 start time test when some data (not for trivial stuff). Will + // trigger and show data when leaving this method by destructing helper + static const char* pEnableBitmapDrawTimerTimer(getenv("SAL_ENABLE_TIMER_BITMAPDRAW")); + static bool bUseTimer(nullptr != pEnableBitmapDrawTimerTimer); + std::unique_ptr<LocalTimeTest> aTimeTest( + bUseTimer && rBitmapEx.GetSizeBytes() > 10000 + ? new LocalTimeTest() + : nullptr); +#endif + + // MM02 reorganize order: Prefer DrawTransformBitmapExDirect due + // to this having evolved and is improved on quite some systems. + // Check for exclusion parameters that may prevent using it + static bool bAllowPreferDirectPaint(true); + const bool bInvert(RasterOp::Invert == meRasterOp); + const bool bBitmapChangedColor(mnDrawMode & (DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | DrawModeFlags::GrayBitmap )); + const bool bTryDirectPaint(!bInvert && !bBitmapChangedColor && !bMetafile); + + if(bAllowPreferDirectPaint && bTryDirectPaint) + { + // tdf#130768 CAUTION(!) using GetViewTransformation() is *not* enough here, it may + // be that mnOutOffX/mnOutOffY is used - see AOO bug 75163, mentioned at + // ImplGetDeviceTransformation declaration + const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rTransformation); + + if(DrawTransformBitmapExDirect(aFullTransform, rBitmapEx)) + { + // we are done + return; + } + } + + // decompose matrix to check rotation and shear + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + rTransformation.decompose(aScale, aTranslate, fRotate, fShearX); + const bool bRotated(!basegfx::fTools::equalZero(fRotate)); + const bool bSheared(!basegfx::fTools::equalZero(fShearX)); + const bool bMirroredX(basegfx::fTools::less(aScale.getX(), 0.0)); + const bool bMirroredY(basegfx::fTools::less(aScale.getY(), 0.0)); + + if(!bRotated && !bSheared && !bMirroredX && !bMirroredY) + { + // with no rotation, shear or mirroring it can be mapped to DrawBitmapEx + // do *not* execute the mirroring here, it's done in the fallback + // #i124580# the correct DestSize needs to be calculated based on MaxXY values + Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY())); + const Size aDestSize( + basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(), + basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y()); + const Point aOrigin = GetMapMode().GetOrigin(); + if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel) + { + aDestPt.Move(aOrigin.getX(), aOrigin.getY()); + EnableMapMode(false); + } + + DrawBitmapEx(aDestPt, aDestSize, rBitmapEx); + if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel) + { + EnableMapMode(); + aDestPt.Move(-aOrigin.getX(), -aOrigin.getY()); + } + return; + } + + // MM02 bAllowPreferDirectPaint may have been false to allow + // to specify order of executions, so give bTryDirectPaint a call + if(bTryDirectPaint) + { + // tdf#130768 CAUTION(!) using GetViewTransformation() is *not* enough here, it may + // be that mnOutOffX/mnOutOffY is used - see AOO bug 75163, mentioned at + // ImplGetDeviceTransformation declaration + const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rTransformation); + + if(DrawTransformBitmapExDirect(aFullTransform, rBitmapEx)) + { + // we are done + return; + } + } + + // take the fallback when no rotate and shear, but mirror (else we would have done this above) + if(!bRotated && !bSheared) + { + // with no rotation or shear it can be mapped to DrawBitmapEx + // do *not* execute the mirroring here, it's done in the fallback + // #i124580# the correct DestSize needs to be calculated based on MaxXY values + const Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY())); + const Size aDestSize( + basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(), + basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y()); + + DrawBitmapEx(aDestPt, aDestSize, rBitmapEx); + return; + } + + // at this point we are either sheared or rotated or both + assert(bSheared || bRotated); + + // fallback; create transformed bitmap the hard way (back-transform + // the pixels) and paint + basegfx::B2DRange aVisibleRange(0.0, 0.0, 1.0, 1.0); + + // limit maximum area to something looking good for non-pixel-based targets (metafile, printer) + // by using a fixed minimum (allow at least, but no need to utilize) for good smoothing and an area + // dependent of original size for good quality when e.g. rotated/sheared. Still, limit to a maximum + // to avoid crashes/resource problems (ca. 1500x3000 here) + const Size& rOriginalSizePixel(rBitmapEx.GetSizePixel()); + const double fOrigArea(rOriginalSizePixel.Width() * rOriginalSizePixel.Height() * 0.5); + const double fOrigAreaScaled(fOrigArea * 1.44); + double fMaximumArea(std::min(4500000.0, std::max(1000000.0, fOrigAreaScaled))); + // tdf#130768 CAUTION(!) using GetViewTransformation() is *not* enough here, it may + // be that mnOutOffX/mnOutOffY is used - see AOO bug 75163, mentioned at + // ImplGetDeviceTransformation declaration + basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rTransformation); + + if(!bMetafile) + { + if ( !TransformAndReduceBitmapExToTargetRange( aFullTransform, aVisibleRange, fMaximumArea ) ) + return; + } + + if(!aVisibleRange.isEmpty()) + { + BitmapEx aTransformed(rBitmapEx); + + // #122923# when the result needs an alpha channel due to being rotated or sheared + // and thus uncovering areas, add these channels so that the own transformer (used + // in getTransformed) also creates a transformed alpha channel + if(!aTransformed.IsTransparent() && (bSheared || bRotated)) + { + // parts will be uncovered, extend aTransformed with a mask bitmap + const Bitmap aContent(aTransformed.GetBitmap()); + + AlphaMask aMaskBmp(aContent.GetSizePixel()); + aMaskBmp.Erase(0); + + aTransformed = BitmapEx(aContent, aMaskBmp); + } + + // Remove scaling from aFulltransform: we transform due to shearing or rotation, scaling + // will happen according to aDestSize. + basegfx::B2DVector aFullScale, aFullTranslate; + double fFullRotate, fFullShearX; + aFullTransform.decompose(aFullScale, aFullTranslate, fFullRotate, fFullShearX); + // Require positive scaling, negative scaling would loose horizontal or vertical flip. + if (aFullScale.getX() > 0 && aFullScale.getY() > 0) + { + basegfx::B2DHomMatrix aTransform = basegfx::utils::createScaleB2DHomMatrix( + rOriginalSizePixel.getWidth() / aFullScale.getX(), + rOriginalSizePixel.getHeight() / aFullScale.getY()); + aFullTransform *= aTransform; + } + + double fSourceRatio = 1.0; + if (rOriginalSizePixel.getHeight() != 0) + { + fSourceRatio = rOriginalSizePixel.getWidth() / rOriginalSizePixel.getHeight(); + } + double fTargetRatio = 1.0; + if (aFullScale.getY() != 0) + { + fTargetRatio = aFullScale.getX() / aFullScale.getY(); + } + bool bAspectRatioKept = rtl::math::approxEqual(fSourceRatio, fTargetRatio); + if (bSheared || !bAspectRatioKept) + { + // Not only rotation, or scaling does not keep aspect ratio. + aTransformed = aTransformed.getTransformed( + aFullTransform, + aVisibleRange, + fMaximumArea); + } + else + { + // Just rotation, can do that directly. + fFullRotate = fmod(fFullRotate * -1, F_2PI); + if (fFullRotate < 0) + { + fFullRotate += F_2PI; + } + long nAngle10 = basegfx::fround(basegfx::rad2deg(fFullRotate) * 10); + aTransformed.Rotate(nAngle10, COL_TRANSPARENT); + } + basegfx::B2DRange aTargetRange(0.0, 0.0, 1.0, 1.0); + + // get logic object target range + aTargetRange.transform(rTransformation); + + // get from unified/relative VisibleRange to logoc one + aVisibleRange.transform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aTargetRange.getRange(), + aTargetRange.getMinimum())); + + // extract point and size; do not remove size, the bitmap may have been prepared reduced by purpose + // #i124580# the correct DestSize needs to be calculated based on MaxXY values + const Point aDestPt(basegfx::fround(aVisibleRange.getMinX()), basegfx::fround(aVisibleRange.getMinY())); + const Size aDestSize( + basegfx::fround(aVisibleRange.getMaxX()) - aDestPt.X(), + basegfx::fround(aVisibleRange.getMaxY()) - aDestPt.Y()); + + DrawBitmapEx(aDestPt, aDestSize, aTransformed); + } +} + +void OutputDevice::DrawShadowBitmapEx( + const BitmapEx& rBitmapEx, + ::Color aShadowColor) +{ + Bitmap::ScopedReadAccess pReadAccess(const_cast<Bitmap&>(rBitmapEx.maBitmap)); + + if(!pReadAccess) + return; + + for(long y(0); y < pReadAccess->Height(); y++) + { + for(long x(0); x < pReadAccess->Width(); x++) + { + const BitmapColor aColor = pReadAccess->GetColor(y, x); + sal_uInt16 nLuminance(static_cast<sal_uInt16>(aColor.GetLuminance()) + 1); + const Color aDestColor( + static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetRed())) >> 8), + static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetGreen())) >> 8), + static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetBlue())) >> 8)); + DrawPixel(Point(x,y), aDestColor); + } + } +} + +void OutputDevice::DrawImage( const Point& rPos, const Image& rImage, DrawImageFlags nStyle ) +{ + assert(!is_double_buffered_window()); + + DrawImage( rPos, Size(), rImage, nStyle ); +} + +void OutputDevice::DrawImage( const Point& rPos, const Size& rSize, + const Image& rImage, DrawImageFlags nStyle ) +{ + assert(!is_double_buffered_window()); + + bool bIsSizeValid = !rSize.IsEmpty(); + + if (!ImplIsRecordLayout()) + { + Image& rNonConstImage = const_cast<Image&>(rImage); + if (bIsSizeValid) + rNonConstImage.Draw(this, rPos, nStyle, &rSize); + else + rNonConstImage.Draw(this, rPos, nStyle); + } +} + +namespace +{ + // Co = Cs + Cd*(1-As) premultiplied alpha -or- + // Co = (AsCs + AdCd*(1-As)) / Ao + sal_uInt8 CalcColor( const sal_uInt8 nSourceColor, const sal_uInt8 nSourceAlpha, + const sal_uInt8 nDstAlpha, const sal_uInt8 nResAlpha, const sal_uInt8 nDestColor ) + { + int c = nResAlpha ? ( static_cast<int>(nSourceAlpha)*nSourceColor + static_cast<int>(nDstAlpha)*nDestColor - + static_cast<int>(nDstAlpha)*nDestColor*nSourceAlpha/255 ) / static_cast<int>(nResAlpha) : 0; + return sal_uInt8( c ); + } + + BitmapColor AlphaBlend( int nX, int nY, + const long nMapX, + const long nMapY, + BitmapReadAccess const * pP, + BitmapReadAccess const * pA, + BitmapReadAccess const * pB, + BitmapWriteAccess const * pAlphaW, + sal_uInt8& nResAlpha ) + { + BitmapColor aDstCol,aSrcCol; + aSrcCol = pP->GetColor( nMapY, nMapX ); + aDstCol = pB->GetColor( nY, nX ); + + // vcl stores transparency, not alpha - invert it + const sal_uInt8 nSrcAlpha = 255 - pA->GetPixelIndex( nMapY, nMapX ); + const sal_uInt8 nDstAlpha = 255 - pAlphaW->GetPixelIndex( nY, nX ); + + // Perform porter-duff compositing 'over' operation + + // Co = Cs + Cd*(1-As) + // Ad = As + Ad*(1-As) + nResAlpha = static_cast<int>(nSrcAlpha) + static_cast<int>(nDstAlpha) - static_cast<int>(nDstAlpha)*nSrcAlpha/255; + + aDstCol.SetRed( CalcColor( aSrcCol.GetRed(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetRed() ) ); + aDstCol.SetBlue( CalcColor( aSrcCol.GetBlue(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetBlue() ) ); + aDstCol.SetGreen( CalcColor( aSrcCol.GetGreen(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetGreen() ) ); + + return aDstCol; + } +} + +void OutputDevice::BlendBitmap( + const SalTwoRect& rPosAry, + const Bitmap& rBmp ) +{ + mpGraphics->BlendBitmap( rPosAry, *rBmp.ImplGetSalBitmap(), this ); +} + +Bitmap OutputDevice::BlendBitmapWithAlpha( + Bitmap& aBmp, + BitmapReadAccess const * pP, + BitmapReadAccess const * pA, + const tools::Rectangle& aDstRect, + const sal_Int32 nOffY, + const sal_Int32 nDstHeight, + const sal_Int32 nOffX, + const sal_Int32 nDstWidth, + const long* pMapX, + const long* pMapY ) + +{ + BitmapColor aDstCol; + Bitmap res; + int nX, nY; + sal_uInt8 nResAlpha; + + SAL_WARN_IF( !mpAlphaVDev, "vcl.gdi", "BlendBitmapWithAlpha(): call me only with valid alpha VirtualDevice!" ); + + bool bOldMapMode( mpAlphaVDev->IsMapModeEnabled() ); + mpAlphaVDev->EnableMapMode(false); + + Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aDstRect.TopLeft(), aDstRect.GetSize() ) ); + BitmapScopedWriteAccess pAlphaW(aAlphaBitmap); + + if( GetBitCount() <= 8 ) + { + Bitmap aDither( aBmp.GetSizePixel(), 8 ); + BitmapColor aIndex( 0 ); + Bitmap::ScopedReadAccess pB(aBmp); + BitmapScopedWriteAccess pW(aDither); + + if (pB && pP && pA && pW && pAlphaW) + { + int nOutY; + + for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ ) + { + const long nMapY = pMapY[ nY ]; + const long nModY = ( nOutY & 0x0FL ) << 4; + int nOutX; + + Scanline pScanline = pW->GetScanline(nY); + Scanline pScanlineAlpha = pAlphaW->GetScanline(nY); + for( nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ ) + { + const long nMapX = pMapX[ nX ]; + const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ]; + + aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha ); + + aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] + + nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] + + nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) ); + pW->SetPixelOnData( pScanline, nX, aIndex ); + + aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] + + nVCLGLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] + + nVCLBLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] ) ); + pAlphaW->SetPixelOnData( pScanlineAlpha, nX, aIndex ); + } + } + } + pB.reset(); + pW.reset(); + res = aDither; + } + else + { + BitmapScopedWriteAccess pB(aBmp); + if (pB && pP && pA && pAlphaW) + { + for( nY = 0; nY < nDstHeight; nY++ ) + { + const long nMapY = pMapY[ nY ]; + Scanline pScanlineB = pB->GetScanline(nY); + Scanline pScanlineAlpha = pAlphaW->GetScanline(nY); + + for( nX = 0; nX < nDstWidth; nX++ ) + { + const long nMapX = pMapX[ nX ]; + aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha ); + + pB->SetPixelOnData(pScanlineB, nX, pB->GetBestMatchingColor(aDstCol)); + pAlphaW->SetPixelOnData(pScanlineAlpha, nX, pB->GetBestMatchingColor(Color(255L-nResAlpha, 255L-nResAlpha, 255L-nResAlpha))); + } + } + } + pB.reset(); + res = aBmp; + } + + pAlphaW.reset(); + mpAlphaVDev->DrawBitmap( aDstRect.TopLeft(), aAlphaBitmap ); + mpAlphaVDev->EnableMapMode( bOldMapMode ); + + return res; +} + +Bitmap OutputDevice::BlendBitmap( + Bitmap& aBmp, + BitmapReadAccess const * pP, + BitmapReadAccess const * pA, + const sal_Int32 nOffY, + const sal_Int32 nDstHeight, + const sal_Int32 nOffX, + const sal_Int32 nDstWidth, + const tools::Rectangle& aBmpRect, + const Size& aOutSz, + const bool bHMirr, + const bool bVMirr, + const long* pMapX, + const long* pMapY ) +{ + BitmapColor aDstCol; + Bitmap res; + int nX, nY; + + if( GetBitCount() <= 8 ) + { + Bitmap aDither( aBmp.GetSizePixel(), 8 ); + BitmapColor aIndex( 0 ); + Bitmap::ScopedReadAccess pB(aBmp); + BitmapScopedWriteAccess pW(aDither); + + if( pB && pP && pA && pW ) + { + int nOutY; + + for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ ) + { + long nMapY = pMapY[ nY ]; + if (bVMirr) + { + nMapY = aBmpRect.Bottom() - nMapY; + } + const long nModY = ( nOutY & 0x0FL ) << 4; + int nOutX; + + Scanline pScanline = pW->GetScanline(nY); + Scanline pScanlineAlpha = pA->GetScanline(nMapY); + for( nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ ) + { + long nMapX = pMapX[ nX ]; + if (bHMirr) + { + nMapX = aBmpRect.Right() - nMapX; + } + const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ]; + + aDstCol = pB->GetColor( nY, nX ); + aDstCol.Merge( pP->GetColor( nMapY, nMapX ), pA->GetIndexFromData( pScanlineAlpha, nMapX ) ); + aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] + + nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] + + nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) ); + pW->SetPixelOnData( pScanline, nX, aIndex ); + } + } + } + + pB.reset(); + pW.reset(); + res = aDither; + } + else + { + BitmapScopedWriteAccess pB(aBmp); + + bool bFastBlend = false; + if( pP && pA && pB && !bHMirr && !bVMirr ) + { + SalTwoRect aTR(aBmpRect.Left(), aBmpRect.Top(), aBmpRect.GetWidth(), aBmpRect.GetHeight(), + nOffX, nOffY, aOutSz.Width(), aOutSz.Height()); + + bFastBlend = ImplFastBitmapBlending( *pB,*pP,*pA, aTR ); + } + + if( pP && pA && pB && !bFastBlend ) + { + switch( pP->GetScanlineFormat() ) + { + case ScanlineFormat::N8BitPal: + { + for( nY = 0; nY < nDstHeight; nY++ ) + { + long nMapY = pMapY[ nY ]; + if ( bVMirr ) + { + nMapY = aBmpRect.Bottom() - nMapY; + } + Scanline pPScan = pP->GetScanline( nMapY ); + Scanline pAScan = pA->GetScanline( nMapY ); + Scanline pBScan = pB->GetScanline( nY ); + + for( nX = 0; nX < nDstWidth; nX++ ) + { + long nMapX = pMapX[ nX ]; + + if ( bHMirr ) + { + nMapX = aBmpRect.Right() - nMapX; + } + aDstCol = pB->GetPixelFromData( pBScan, nX ); + aDstCol.Merge( pP->GetPaletteColor( pPScan[ nMapX ] ), pAScan[ nMapX ] ); + pB->SetPixelOnData( pBScan, nX, aDstCol ); + } + } + } + break; + + default: + { + + for( nY = 0; nY < nDstHeight; nY++ ) + { + long nMapY = pMapY[ nY ]; + + if ( bVMirr ) + { + nMapY = aBmpRect.Bottom() - nMapY; + } + Scanline pAScan = pA->GetScanline( nMapY ); + Scanline pBScan = pB->GetScanline(nY); + for( nX = 0; nX < nDstWidth; nX++ ) + { + long nMapX = pMapX[ nX ]; + + if ( bHMirr ) + { + nMapX = aBmpRect.Right() - nMapX; + } + aDstCol = pB->GetPixelFromData( pBScan, nX ); + aDstCol.Merge( pP->GetColor( nMapY, nMapX ), pAScan[ nMapX ] ); + pB->SetPixelOnData( pBScan, nX, aDstCol ); + } + } + } + break; + } + } + + pB.reset(); + res = aBmp; + } + + return res; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/clipping.cxx b/vcl/source/outdev/clipping.cxx new file mode 100644 index 000000000..1c6e4551c --- /dev/null +++ b/vcl/source/outdev/clipping.cxx @@ -0,0 +1,226 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <osl/diagnose.h> + +#include <tools/debug.hxx> +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/outdev.hxx> + +#include <salgdi.hxx> + +void OutputDevice::SaveBackground(VirtualDevice& rSaveDevice, + const Point& rPos, const Size& rSize, const Size& rBackgroundSize) const +{ + rSaveDevice.DrawOutDev(Point(), rBackgroundSize, rPos, rSize, *this); +} + +vcl::Region OutputDevice::GetClipRegion() const +{ + + return PixelToLogic( maRegion ); +} + +void OutputDevice::SetClipRegion() +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaClipRegionAction( vcl::Region(), false ) ); + + SetDeviceClipRegion( nullptr ); + + if( mpAlphaVDev ) + mpAlphaVDev->SetClipRegion(); +} + +void OutputDevice::SetClipRegion( const vcl::Region& rRegion ) +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaClipRegionAction( rRegion, true ) ); + + if ( rRegion.IsNull() ) + { + SetDeviceClipRegion( nullptr ); + } + else + { + vcl::Region aRegion = LogicToPixel( rRegion ); + SetDeviceClipRegion( &aRegion ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetClipRegion( rRegion ); +} + +bool OutputDevice::SelectClipRegion( const vcl::Region& rRegion, SalGraphics* pGraphics ) +{ + DBG_TESTSOLARMUTEX(); + + if( !pGraphics ) + { + if( !mpGraphics && !AcquireGraphics() ) + return false; + pGraphics = mpGraphics; + } + + bool bClipRegion = pGraphics->SetClipRegion( rRegion, this ); + OSL_ENSURE( bClipRegion, "OutputDevice::SelectClipRegion() - can't create region" ); + return bClipRegion; +} + +void OutputDevice::MoveClipRegion( long nHorzMove, long nVertMove ) +{ + + if ( mbClipRegion ) + { + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaMoveClipRegionAction( nHorzMove, nVertMove ) ); + + maRegion.Move( ImplLogicWidthToDevicePixel( nHorzMove ), + ImplLogicHeightToDevicePixel( nVertMove ) ); + mbInitClipRegion = true; + } + + if( mpAlphaVDev ) + mpAlphaVDev->MoveClipRegion( nHorzMove, nVertMove ); +} + +void OutputDevice::IntersectClipRegion( const tools::Rectangle& rRect ) +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaISectRectClipRegionAction( rRect ) ); + + tools::Rectangle aRect = LogicToPixel( rRect ); + maRegion.Intersect( aRect ); + mbClipRegion = true; + mbInitClipRegion = true; + + if( mpAlphaVDev ) + mpAlphaVDev->IntersectClipRegion( rRect ); +} + +void OutputDevice::IntersectClipRegion( const vcl::Region& rRegion ) +{ + + if(!rRegion.IsNull()) + { + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaISectRegionClipRegionAction( rRegion ) ); + + vcl::Region aRegion = LogicToPixel( rRegion ); + maRegion.Intersect( aRegion ); + mbClipRegion = true; + mbInitClipRegion = true; + } + + if( mpAlphaVDev ) + mpAlphaVDev->IntersectClipRegion( rRegion ); +} + +void OutputDevice::InitClipRegion() +{ + DBG_TESTSOLARMUTEX(); + + if ( mbClipRegion ) + { + if ( maRegion.IsEmpty() ) + mbOutputClipped = true; + else + { + mbOutputClipped = false; + + // #102532# Respect output offset also for clip region + vcl::Region aRegion = ClipToDeviceBounds(ImplPixelToDevicePixel(maRegion)); + + if ( aRegion.IsEmpty() ) + { + mbOutputClipped = true; + } + else + { + mbOutputClipped = false; + SelectClipRegion( aRegion ); + } + } + + mbClipRegionSet = true; + } + else + { + if ( mbClipRegionSet ) + { + if (mpGraphics) + mpGraphics->ResetClipRegion(); + mbClipRegionSet = false; + } + + mbOutputClipped = false; + } + + mbInitClipRegion = false; +} + +vcl::Region OutputDevice::ClipToDeviceBounds(vcl::Region aRegion) const +{ + aRegion.Intersect(tools::Rectangle{mnOutOffX, + mnOutOffY, + mnOutOffX + GetOutputWidthPixel() - 1, + mnOutOffY + GetOutputHeightPixel() - 1 + }); + return aRegion; +} + +vcl::Region OutputDevice::GetActiveClipRegion() const +{ + return GetClipRegion(); +} + +void OutputDevice::ClipToPaintRegion(tools::Rectangle& /*rDstRect*/) +{ + // this is only used in Window, but we still need it as it's called + // on in other clipping functions +} + +void OutputDevice::SetDeviceClipRegion( const vcl::Region* pRegion ) +{ + DBG_TESTSOLARMUTEX(); + + if ( !pRegion ) + { + if ( mbClipRegion ) + { + maRegion = vcl::Region(true); + mbClipRegion = false; + mbInitClipRegion = true; + } + } + else + { + maRegion = *pRegion; + mbClipRegion = true; + mbInitClipRegion = true; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/curvedshapes.cxx b/vcl/source/outdev/curvedshapes.cxx new file mode 100644 index 000000000..d25d69ad1 --- /dev/null +++ b/vcl/source/outdev/curvedshapes.cxx @@ -0,0 +1,210 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cassert> + +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <vcl/outdev.hxx> +#include <vcl/virdev.hxx> + +#include <salgdi.hxx> + +void OutputDevice::DrawEllipse( const tools::Rectangle& rRect ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaEllipseAction( rRect ) ); + + if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() ) + return; + + tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) ); + if ( aRect.IsEmpty() ) + return; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + tools::Polygon aRectPoly( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 ); + if ( aRectPoly.GetSize() >= 2 ) + { + SalPoint* pPtAry = reinterpret_cast<SalPoint*>(aRectPoly.GetPointAry()); + if ( !mbFillColor ) + mpGraphics->DrawPolyLine( aRectPoly.GetSize(), pPtAry, this ); + else + { + if ( mbInitFillColor ) + InitFillColor(); + mpGraphics->DrawPolygon( aRectPoly.GetSize(), pPtAry, this ); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawEllipse( rRect ); +} + +void OutputDevice::DrawArc( const tools::Rectangle& rRect, + const Point& rStartPt, const Point& rEndPt ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaArcAction( rRect, rStartPt, rEndPt ) ); + + if ( !IsDeviceOutputNecessary() || !mbLineColor || ImplIsRecordLayout() ) + return; + + tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) ); + if ( aRect.IsEmpty() ) + return; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + const Point aStart( ImplLogicToDevicePixel( rStartPt ) ); + const Point aEnd( ImplLogicToDevicePixel( rEndPt ) ); + tools::Polygon aArcPoly( aRect, aStart, aEnd, PolyStyle::Arc ); + + if ( aArcPoly.GetSize() >= 2 ) + { + SalPoint* pPtAry = reinterpret_cast<SalPoint*>(aArcPoly.GetPointAry()); + mpGraphics->DrawPolyLine( aArcPoly.GetSize(), pPtAry, this ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawArc( rRect, rStartPt, rEndPt ); +} + +void OutputDevice::DrawPie( const tools::Rectangle& rRect, + const Point& rStartPt, const Point& rEndPt ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaPieAction( rRect, rStartPt, rEndPt ) ); + + if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() ) + return; + + tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) ); + if ( aRect.IsEmpty() ) + return; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + const Point aStart( ImplLogicToDevicePixel( rStartPt ) ); + const Point aEnd( ImplLogicToDevicePixel( rEndPt ) ); + tools::Polygon aPiePoly( aRect, aStart, aEnd, PolyStyle::Pie ); + + if ( aPiePoly.GetSize() >= 2 ) + { + SalPoint* pPtAry = reinterpret_cast<SalPoint*>(aPiePoly.GetPointAry()); + if ( !mbFillColor ) + mpGraphics->DrawPolyLine( aPiePoly.GetSize(), pPtAry, this ); + else + { + if ( mbInitFillColor ) + InitFillColor(); + mpGraphics->DrawPolygon( aPiePoly.GetSize(), pPtAry, this ); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawPie( rRect, rStartPt, rEndPt ); +} + +void OutputDevice::DrawChord( const tools::Rectangle& rRect, + const Point& rStartPt, const Point& rEndPt ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaChordAction( rRect, rStartPt, rEndPt ) ); + + if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() ) + return; + + tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) ); + if ( aRect.IsEmpty() ) + return; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + const Point aStart( ImplLogicToDevicePixel( rStartPt ) ); + const Point aEnd( ImplLogicToDevicePixel( rEndPt ) ); + tools::Polygon aChordPoly( aRect, aStart, aEnd, PolyStyle::Chord ); + + if ( aChordPoly.GetSize() >= 2 ) + { + SalPoint* pPtAry = reinterpret_cast<SalPoint*>(aChordPoly.GetPointAry()); + if ( !mbFillColor ) + mpGraphics->DrawPolyLine( aChordPoly.GetSize(), pPtAry, this ); + else + { + if ( mbInitFillColor ) + InitFillColor(); + mpGraphics->DrawPolygon( aChordPoly.GetSize(), pPtAry, this ); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawChord( rRect, rStartPt, rEndPt ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/font.cxx b/vcl/source/outdev/font.cxx new file mode 100644 index 000000000..cf50830dc --- /dev/null +++ b/vcl/source/outdev/font.cxx @@ -0,0 +1,1435 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <i18nlangtag/mslangid.hxx> +#include <i18nlangtag/lang.h> + +#include <unotools/configmgr.hxx> +#include <vcl/metric.hxx> +#include <vcl/virdev.hxx> +#include <vcl/print.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/fontcharmap.hxx> +#include <vcl/event.hxx> +#include <font/FeatureCollector.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <tools/debug.hxx> + +#include <sallayout.hxx> +#include <salgdi.hxx> +#include <svdata.hxx> +#include <impglyphitem.hxx> + +#include <outdev.h> +#include <window.h> + +#include <PhysicalFontCollection.hxx> + +#include <strings.hrc> + +FontMetric OutputDevice::GetDevFont( int nDevFontIndex ) const +{ + FontMetric aFontMetric; + + ImplInitFontList(); + + int nCount = GetDevFontCount(); + if( nDevFontIndex < nCount ) + { + const PhysicalFontFace& rData = *mpDeviceFontList->Get( nDevFontIndex ); + aFontMetric.SetFamilyName( rData.GetFamilyName() ); + aFontMetric.SetStyleName( rData.GetStyleName() ); + aFontMetric.SetCharSet( rData.GetCharSet() ); + aFontMetric.SetFamily( rData.GetFamilyType() ); + aFontMetric.SetPitch( rData.GetPitch() ); + aFontMetric.SetWeight( rData.GetWeight() ); + aFontMetric.SetItalic( rData.GetItalic() ); + aFontMetric.SetAlignment( TextAlign::ALIGN_TOP ); + aFontMetric.SetWidthType( rData.GetWidthType() ); + aFontMetric.SetQuality( rData.GetQuality() ); + } + + return aFontMetric; +} + +int OutputDevice::GetDevFontCount() const +{ + if( !mpDeviceFontList ) + { + if (!mxFontCollection) + { + return 0; + } + + mpDeviceFontList = mxFontCollection->GetDeviceFontList(); + + if (!mpDeviceFontList->Count()) + { + mpDeviceFontList.reset(); + return 0; + } + } + return mpDeviceFontList->Count(); +} + +bool OutputDevice::IsFontAvailable( const OUString& rFontName ) const +{ + ImplInitFontList(); + PhysicalFontFamily* pFound = mxFontCollection->FindFontFamily( rFontName ); + return (pFound != nullptr); +} + +int OutputDevice::GetDevFontSizeCount( const vcl::Font& rFont ) const +{ + mpDeviceFontSizeList.reset(); + + ImplInitFontList(); + mpDeviceFontSizeList = mxFontCollection->GetDeviceFontSizeList( rFont.GetFamilyName() ); + return mpDeviceFontSizeList->Count(); +} + +Size OutputDevice::GetDevFontSize( const vcl::Font& rFont, int nSizeIndex ) const +{ + // check range + int nCount = GetDevFontSizeCount( rFont ); + if ( nSizeIndex >= nCount ) + return Size(); + + // when mapping is enabled round to .5 points + Size aSize( 0, mpDeviceFontSizeList->Get( nSizeIndex ) ); + if ( mbMap ) + { + aSize.setHeight( aSize.Height() * 10 ); + MapMode aMap( MapUnit::Map10thInch, Point(), Fraction( 1, 72 ), Fraction( 1, 72 ) ); + aSize = PixelToLogic( aSize, aMap ); + aSize.AdjustHeight(5 ); + aSize.setHeight( aSize.Height() / 10 ); + long nRound = aSize.Height() % 5; + if ( nRound >= 3 ) + aSize.AdjustHeight(5-nRound); + else + aSize.AdjustHeight( -nRound ); + aSize.setHeight( aSize.Height() * 10 ); + aSize = LogicToPixel( aSize, aMap ); + aSize = PixelToLogic( aSize ); + aSize.AdjustHeight(5 ); + aSize.setHeight( aSize.Height() / 10 ); + } + return aSize; +} + +bool OutputDevice::AddTempDevFont( const OUString& rFileURL, const OUString& rFontName ) +{ + ImplInitFontList(); + + if( !mpGraphics && !AcquireGraphics() ) + return false; + + bool bRC = mpGraphics->AddTempDevFont( mxFontCollection.get(), rFileURL, rFontName ); + if( !bRC ) + return false; + + if( mpAlphaVDev ) + mpAlphaVDev->AddTempDevFont( rFileURL, rFontName ); + + return true; +} + +bool OutputDevice::GetFontFeatures(std::vector<vcl::font::Feature>& rFontFeatures) const +{ + if (!ImplNewFont()) + return false; + + LogicalFontInstance* pFontInstance = mpFontInstance.get(); + if (!pFontInstance) + return false; + + hb_font_t* pHbFont = pFontInstance->GetHbFont(); + if (!pHbFont) + return false; + + hb_face_t* pHbFace = hb_font_get_face(pHbFont); + if (!pHbFace) + return false; + + const LanguageType eOfficeLanguage = Application::GetSettings().GetLanguageTag().getLanguageType(); + + vcl::font::FeatureCollector aFeatureCollector(pHbFace, rFontFeatures, eOfficeLanguage); + aFeatureCollector.collect(); + + return true; +} + +FontMetric OutputDevice::GetFontMetric() const +{ + FontMetric aMetric; + if (!ImplNewFont()) + return aMetric; + + LogicalFontInstance* pFontInstance = mpFontInstance.get(); + ImplFontMetricDataRef xFontMetric = pFontInstance->mxFontMetric; + + // prepare metric + aMetric = maFont; + + // set aMetric with info from font + aMetric.SetFamilyName( maFont.GetFamilyName() ); + aMetric.SetStyleName( xFontMetric->GetStyleName() ); + aMetric.SetFontSize( PixelToLogic( Size( xFontMetric->GetWidth(), xFontMetric->GetAscent() + xFontMetric->GetDescent() - xFontMetric->GetInternalLeading() ) ) ); + aMetric.SetCharSet( xFontMetric->IsSymbolFont() ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); + aMetric.SetFamily( xFontMetric->GetFamilyType() ); + aMetric.SetPitch( xFontMetric->GetPitch() ); + aMetric.SetWeight( xFontMetric->GetWeight() ); + aMetric.SetItalic( xFontMetric->GetItalic() ); + aMetric.SetAlignment( TextAlign::ALIGN_TOP ); + aMetric.SetWidthType( xFontMetric->GetWidthType() ); + if ( pFontInstance->mnOwnOrientation ) + aMetric.SetOrientation( pFontInstance->mnOwnOrientation ); + else + aMetric.SetOrientation( xFontMetric->GetOrientation() ); + + // set remaining metric fields + aMetric.SetFullstopCenteredFlag( xFontMetric->IsFullstopCentered() ); + aMetric.SetBulletOffset( xFontMetric->GetBulletOffset() ); + aMetric.SetAscent( ImplDevicePixelToLogicHeight( xFontMetric->GetAscent() + mnEmphasisAscent ) ); + aMetric.SetDescent( ImplDevicePixelToLogicHeight( xFontMetric->GetDescent() + mnEmphasisDescent ) ); + aMetric.SetInternalLeading( ImplDevicePixelToLogicHeight( xFontMetric->GetInternalLeading() + mnEmphasisAscent ) ); + // OutputDevice has its own external leading function due to #i60945# + aMetric.SetExternalLeading( ImplDevicePixelToLogicHeight( GetFontExtLeading() ) ); + aMetric.SetLineHeight( ImplDevicePixelToLogicHeight( xFontMetric->GetAscent() + xFontMetric->GetDescent() + mnEmphasisAscent + mnEmphasisDescent ) ); + aMetric.SetSlant( ImplDevicePixelToLogicHeight( xFontMetric->GetSlant() ) ); + + // get miscellaneous data + aMetric.SetQuality( xFontMetric->GetQuality() ); + + SAL_INFO("vcl.gdi.fontmetric", "OutputDevice::GetFontMetric:" << aMetric); + + xFontMetric = nullptr; + + return aMetric; +} + +FontMetric OutputDevice::GetFontMetric( const vcl::Font& rFont ) const +{ + // select font, query metrics, select original font again + vcl::Font aOldFont = GetFont(); + const_cast<OutputDevice*>(this)->SetFont( rFont ); + FontMetric aMetric( GetFontMetric() ); + const_cast<OutputDevice*>(this)->SetFont( aOldFont ); + return aMetric; +} + +bool OutputDevice::GetFontCharMap( FontCharMapRef& rxFontCharMap ) const +{ + if (!InitFont()) + return false; + + FontCharMapRef xFontCharMap ( mpGraphics->GetFontCharMap() ); + if (!xFontCharMap.is()) + { + FontCharMapRef xDefaultMap( new FontCharMap() ); + rxFontCharMap = xDefaultMap; + } + else + rxFontCharMap = xFontCharMap; + + return !rxFontCharMap->IsDefaultMap(); +} + +bool OutputDevice::GetFontCapabilities( vcl::FontCapabilities& rFontCapabilities ) const +{ + if (!InitFont()) + return false; + return mpGraphics->GetFontCapabilities(rFontCapabilities); +} + +#if ENABLE_CAIRO_CANVAS + +SystemFontData OutputDevice::GetSysFontData(int nFallbacklevel) const +{ + SystemFontData aSysFontData; + + if (!mpGraphics) + (void) AcquireGraphics(); + + if (mpGraphics) + aSysFontData = mpGraphics->GetSysFontData(nFallbacklevel); + + return aSysFontData; +} + +#endif // ENABLE_CAIRO_CANVAS + +void OutputDevice::ImplGetEmphasisMark( tools::PolyPolygon& rPolyPoly, bool& rPolyLine, + tools::Rectangle& rRect1, tools::Rectangle& rRect2, + long& rYOff, long& rWidth, + FontEmphasisMark eEmphasis, + long nHeight ) +{ + static const PolyFlags aAccentPolyFlags[24] = + { + PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control, + PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control, + PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control, + PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control, + PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control, + PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control, + PolyFlags::Normal, PolyFlags::Normal, PolyFlags::Control, + PolyFlags::Normal, PolyFlags::Control, PolyFlags::Control + }; + + static const long aAccentPos[48] = + { + 78, 0, + 348, 79, + 599, 235, + 843, 469, + 938, 574, + 990, 669, + 990, 773, + 990, 843, + 964, 895, + 921, 947, + 886, 982, + 860, 999, + 825, 999, + 764, 999, + 721, 964, + 686, 895, + 625, 791, + 556, 660, + 469, 504, + 400, 400, + 261, 252, + 61, 61, + 0, 27, + 9, 0 + }; + + rWidth = 0; + rYOff = 0; + rPolyLine = false; + + if ( !nHeight ) + return; + + FontEmphasisMark nEmphasisStyle = eEmphasis & FontEmphasisMark::Style; + long nDotSize = 0; + switch ( nEmphasisStyle ) + { + case FontEmphasisMark::Dot: + // Dot has 55% of the height + nDotSize = (nHeight*550)/1000; + if ( !nDotSize ) + nDotSize = 1; + if ( nDotSize <= 2 ) + rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) ); + else + { + long nRad = nDotSize/2; + tools::Polygon aPoly( Point( nRad, nRad ), nRad, nRad ); + rPolyPoly.Insert( aPoly ); + } + rYOff = ((nHeight*250)/1000)/2; // Center to the another EmphasisMarks + rWidth = nDotSize; + break; + + case FontEmphasisMark::Circle: + // Dot has 80% of the height + nDotSize = (nHeight*800)/1000; + if ( !nDotSize ) + nDotSize = 1; + if ( nDotSize <= 2 ) + rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) ); + else + { + long nRad = nDotSize/2; + tools::Polygon aPoly( Point( nRad, nRad ), nRad, nRad ); + rPolyPoly.Insert( aPoly ); + // BorderWidth is 15% + long nBorder = (nDotSize*150)/1000; + if ( nBorder <= 1 ) + rPolyLine = true; + else + { + tools::Polygon aPoly2( Point( nRad, nRad ), + nRad-nBorder, nRad-nBorder ); + rPolyPoly.Insert( aPoly2 ); + } + } + rWidth = nDotSize; + break; + + case FontEmphasisMark::Disc: + // Dot has 80% of the height + nDotSize = (nHeight*800)/1000; + if ( !nDotSize ) + nDotSize = 1; + if ( nDotSize <= 2 ) + rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) ); + else + { + long nRad = nDotSize/2; + tools::Polygon aPoly( Point( nRad, nRad ), nRad, nRad ); + rPolyPoly.Insert( aPoly ); + } + rWidth = nDotSize; + break; + + case FontEmphasisMark::Accent: + // Dot has 80% of the height + nDotSize = (nHeight*800)/1000; + if ( !nDotSize ) + nDotSize = 1; + if ( nDotSize <= 2 ) + { + if ( nDotSize == 1 ) + { + rRect1 = tools::Rectangle( Point(), Size( nDotSize, nDotSize ) ); + rWidth = nDotSize; + } + else + { + rRect1 = tools::Rectangle( Point(), Size( 1, 1 ) ); + rRect2 = tools::Rectangle( Point( 1, 1 ), Size( 1, 1 ) ); + } + } + else + { + tools::Polygon aPoly( SAL_N_ELEMENTS( aAccentPos ) / 2, + reinterpret_cast<const Point*>(aAccentPos), + aAccentPolyFlags ); + double dScale = static_cast<double>(nDotSize)/1000.0; + aPoly.Scale( dScale, dScale ); + tools::Polygon aTemp; + aPoly.AdaptiveSubdivide( aTemp ); + tools::Rectangle aBoundRect = aTemp.GetBoundRect(); + rWidth = aBoundRect.GetWidth(); + nDotSize = aBoundRect.GetHeight(); + rPolyPoly.Insert( aTemp ); + } + break; + default: break; + } + + // calculate position + long nOffY = 1+(mnDPIY/300); // one visible pixel space + long nSpaceY = nHeight-nDotSize; + if ( nSpaceY >= nOffY*2 ) + rYOff += nOffY; + if ( !(eEmphasis & FontEmphasisMark::PosBelow) ) + rYOff += nDotSize; +} + +FontEmphasisMark OutputDevice::ImplGetEmphasisMarkStyle( const vcl::Font& rFont ) +{ + FontEmphasisMark nEmphasisMark = rFont.GetEmphasisMark(); + + // If no Position is set, then calculate the default position, which + // depends on the language + if ( !(nEmphasisMark & (FontEmphasisMark::PosAbove | FontEmphasisMark::PosBelow)) ) + { + LanguageType eLang = rFont.GetLanguage(); + // In Chinese Simplified the EmphasisMarks are below/left + if (MsLangId::isSimplifiedChinese(eLang)) + nEmphasisMark |= FontEmphasisMark::PosBelow; + else + { + eLang = rFont.GetCJKContextLanguage(); + // In Chinese Simplified the EmphasisMarks are below/left + if (MsLangId::isSimplifiedChinese(eLang)) + nEmphasisMark |= FontEmphasisMark::PosBelow; + else + nEmphasisMark |= FontEmphasisMark::PosAbove; + } + } + + return nEmphasisMark; +} + +long OutputDevice::GetFontExtLeading() const +{ + return mpFontInstance->mxFontMetric->GetExternalLeading(); +} + +void OutputDevice::ImplClearFontData( const bool bNewFontLists ) +{ + // the currently selected logical font is no longer needed + mpFontInstance.clear(); + + mbInitFont = true; + mbNewFont = true; + + if ( bNewFontLists ) + { + mpDeviceFontList.reset(); + mpDeviceFontSizeList.reset(); + + // release all physically selected fonts on this device + if( AcquireGraphics() ) + mpGraphics->ReleaseFonts(); + } + + ImplSVData* pSVData = ImplGetSVData(); + + if (mxFontCache && mxFontCache != pSVData->maGDIData.mxScreenFontCache) + mxFontCache->Invalidate(); + + if (bNewFontLists && AcquireGraphics()) + { + if (mxFontCollection && mxFontCollection != pSVData->maGDIData.mxScreenFontList) + mxFontCollection->Clear(); + } +} + +void OutputDevice::RefreshFontData( const bool bNewFontLists ) +{ + ImplRefreshFontData( bNewFontLists ); +} + +void OutputDevice::ImplRefreshFontData( const bool bNewFontLists ) +{ + if (bNewFontLists && AcquireGraphics()) + mpGraphics->GetDevFontList( mxFontCollection.get() ); +} + +void OutputDevice::ImplUpdateFontData() +{ + ImplClearFontData( true/*bNewFontLists*/ ); + ImplRefreshFontData( true/*bNewFontLists*/ ); +} + +void OutputDevice::ImplClearAllFontData(bool bNewFontLists) +{ + ImplSVData* pSVData = ImplGetSVData(); + + ImplUpdateFontDataForAllFrames( &OutputDevice::ImplClearFontData, bNewFontLists ); + + // clear global font lists to have them updated + pSVData->maGDIData.mxScreenFontCache->Invalidate(); + if ( bNewFontLists ) + { + pSVData->maGDIData.mxScreenFontList->Clear(); + vcl::Window * pFrame = pSVData->maFrameData.mpFirstFrame; + if ( pFrame ) + { + if ( pFrame->AcquireGraphics() ) + { + OutputDevice *pDevice = pFrame; + pDevice->mpGraphics->ClearDevFontCache(); + pDevice->mpGraphics->GetDevFontList(pFrame->mpWindowImpl->mpFrameData->mxFontCollection.get()); + } + } + } +} + +void OutputDevice::ImplRefreshAllFontData(bool bNewFontLists) +{ + ImplUpdateFontDataForAllFrames( &OutputDevice::ImplRefreshFontData, bNewFontLists ); +} + +void OutputDevice::ImplUpdateAllFontData(bool bNewFontLists) +{ + OutputDevice::ImplClearAllFontData(bNewFontLists); + OutputDevice::ImplRefreshAllFontData(bNewFontLists); +} + +void OutputDevice::ImplUpdateFontDataForAllFrames( const FontUpdateHandler_t pHdl, const bool bNewFontLists ) +{ + ImplSVData* const pSVData = ImplGetSVData(); + + // update all windows + vcl::Window* pFrame = pSVData->maFrameData.mpFirstFrame; + while ( pFrame ) + { + ( pFrame->*pHdl )( bNewFontLists ); + + vcl::Window* pSysWin = pFrame->mpWindowImpl->mpFrameData->mpFirstOverlap; + while ( pSysWin ) + { + ( pSysWin->*pHdl )( bNewFontLists ); + pSysWin = pSysWin->mpWindowImpl->mpNextOverlap; + } + + pFrame = pFrame->mpWindowImpl->mpFrameData->mpNextFrame; + } + + // update all virtual devices + VirtualDevice* pVirDev = pSVData->maGDIData.mpFirstVirDev; + while ( pVirDev ) + { + ( pVirDev->*pHdl )( bNewFontLists ); + pVirDev = pVirDev->mpNext; + } + + // update all printers + Printer* pPrinter = pSVData->maGDIData.mpFirstPrinter; + while ( pPrinter ) + { + ( pPrinter->*pHdl )( bNewFontLists ); + pPrinter = pPrinter->mpNext; + } +} + +void OutputDevice::BeginFontSubstitution() +{ + ImplSVData* pSVData = ImplGetSVData(); + pSVData->maGDIData.mbFontSubChanged = false; +} + +void OutputDevice::EndFontSubstitution() +{ + ImplSVData* pSVData = ImplGetSVData(); + if ( pSVData->maGDIData.mbFontSubChanged ) + { + ImplUpdateAllFontData( false ); + + DataChangedEvent aDCEvt( DataChangedEventType::FONTSUBSTITUTION ); + Application::ImplCallEventListenersApplicationDataChanged(&aDCEvt); + Application::NotifyAllWindows( aDCEvt ); + pSVData->maGDIData.mbFontSubChanged = false; + } +} + +void OutputDevice::AddFontSubstitute( const OUString& rFontName, + const OUString& rReplaceFontName, + AddFontSubstituteFlags nFlags ) +{ + ImplDirectFontSubstitution*& rpSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst; + if( !rpSubst ) + rpSubst = new ImplDirectFontSubstitution; + rpSubst->AddFontSubstitute( rFontName, rReplaceFontName, nFlags ); + ImplGetSVData()->maGDIData.mbFontSubChanged = true; +} + +void ImplDirectFontSubstitution::AddFontSubstitute( const OUString& rFontName, + const OUString& rSubstFontName, AddFontSubstituteFlags nFlags ) +{ + maFontSubstList.emplace_back( rFontName, rSubstFontName, nFlags ); +} + +ImplFontSubstEntry::ImplFontSubstEntry( const OUString& rFontName, + const OUString& rSubstFontName, AddFontSubstituteFlags nSubstFlags ) +: mnFlags( nSubstFlags ) +{ + maSearchName = GetEnglishSearchFontName( rFontName ); + maSearchReplaceName = GetEnglishSearchFontName( rSubstFontName ); +} + +void OutputDevice::RemoveFontsSubstitute() +{ + ImplDirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst; + if( pSubst ) + pSubst->RemoveFontsSubstitute(); +} + +void ImplDirectFontSubstitution::RemoveFontsSubstitute() +{ + maFontSubstList.clear(); +} + +bool ImplDirectFontSubstitution::FindFontSubstitute( OUString& rSubstName, + const OUString& rSearchName ) const +{ + // TODO: get rid of O(N) searches + std::vector<ImplFontSubstEntry>::const_iterator it = std::find_if ( + maFontSubstList.begin(), maFontSubstList.end(), + [&] (const ImplFontSubstEntry& s) { return (s.mnFlags & AddFontSubstituteFlags::ALWAYS) + && (s.maSearchName == rSearchName); } ); + if (it != maFontSubstList.end()) + { + rSubstName = it->maSearchReplaceName; + return true; + } + return false; +} + +void ImplFontSubstitute( OUString& rFontName ) +{ + // must be canonicalised + assert( GetEnglishSearchFontName( rFontName ) == rFontName ); + + OUString aSubstFontName; + + // apply user-configurable font replacement (eg, from the list in Tools->Options) + const ImplDirectFontSubstitution* pSubst = ImplGetSVData()->maGDIData.mpDirectFontSubst; + if( pSubst && pSubst->FindFontSubstitute( aSubstFontName, rFontName ) ) + { + rFontName = aSubstFontName; + return; + } +} + +//hidpi TODO: This routine has hard-coded font-sizes that break places such as DialControl +vcl::Font OutputDevice::GetDefaultFont( DefaultFontType nType, LanguageType eLang, + GetDefaultFontFlags nFlags, const OutputDevice* pOutDev ) +{ + if (!pOutDev && !utl::ConfigManager::IsFuzzing()) // default is NULL + pOutDev = Application::GetDefaultDevice(); + + OUString aSearch; + if (!utl::ConfigManager::IsFuzzing()) + { + LanguageTag aLanguageTag( + ( eLang == LANGUAGE_NONE || eLang == LANGUAGE_SYSTEM || eLang == LANGUAGE_DONTKNOW ) ? + Application::GetSettings().GetUILanguageTag() : + LanguageTag( eLang )); + + utl::DefaultFontConfiguration& rDefaults = utl::DefaultFontConfiguration::get(); + OUString aDefault = rDefaults.getDefaultFont( aLanguageTag, nType ); + + if( !aDefault.isEmpty() ) + aSearch = aDefault; + else + aSearch = rDefaults.getUserInterfaceFont( aLanguageTag ); // use the UI font as a fallback + } + else + aSearch = "Liberation Serif"; + + vcl::Font aFont; + aFont.SetPitch( PITCH_VARIABLE ); + + switch ( nType ) + { + case DefaultFontType::SANS_UNICODE: + case DefaultFontType::UI_SANS: + aFont.SetFamily( FAMILY_SWISS ); + break; + + case DefaultFontType::SANS: + case DefaultFontType::LATIN_HEADING: + case DefaultFontType::LATIN_SPREADSHEET: + case DefaultFontType::LATIN_DISPLAY: + aFont.SetFamily( FAMILY_SWISS ); + break; + + case DefaultFontType::SERIF: + case DefaultFontType::LATIN_TEXT: + case DefaultFontType::LATIN_PRESENTATION: + aFont.SetFamily( FAMILY_ROMAN ); + break; + + case DefaultFontType::FIXED: + case DefaultFontType::LATIN_FIXED: + case DefaultFontType::UI_FIXED: + aFont.SetPitch( PITCH_FIXED ); + aFont.SetFamily( FAMILY_MODERN ); + break; + + case DefaultFontType::SYMBOL: + aFont.SetCharSet( RTL_TEXTENCODING_SYMBOL ); + break; + + case DefaultFontType::CJK_TEXT: + case DefaultFontType::CJK_PRESENTATION: + case DefaultFontType::CJK_SPREADSHEET: + case DefaultFontType::CJK_HEADING: + case DefaultFontType::CJK_DISPLAY: + aFont.SetFamily( FAMILY_SYSTEM ); // don't care, but don't use font subst config later... + break; + + case DefaultFontType::CTL_TEXT: + case DefaultFontType::CTL_PRESENTATION: + case DefaultFontType::CTL_SPREADSHEET: + case DefaultFontType::CTL_HEADING: + case DefaultFontType::CTL_DISPLAY: + aFont.SetFamily( FAMILY_SYSTEM ); // don't care, but don't use font subst config later... + break; + } + + if ( !aSearch.isEmpty() ) + { + aFont.SetFontHeight( 12 ); // corresponds to nDefaultHeight + aFont.SetWeight( WEIGHT_NORMAL ); + aFont.SetLanguage( eLang ); + + if ( aFont.GetCharSet() == RTL_TEXTENCODING_DONTKNOW ) + aFont.SetCharSet( osl_getThreadTextEncoding() ); + + // Should we only return available fonts on the given device + if ( pOutDev ) + { + pOutDev->ImplInitFontList(); + + // Search Font in the FontList + OUString aName; + sal_Int32 nIndex = 0; + do + { + PhysicalFontFamily* pFontFamily = pOutDev->mxFontCollection->FindFontFamily( GetNextFontToken( aSearch, nIndex ) ); + if( pFontFamily ) + { + AddTokenFontName( aName, pFontFamily->GetFamilyName() ); + if( nFlags & GetDefaultFontFlags::OnlyOne ) + break; + } + } + while ( nIndex != -1 ); + aFont.SetFamilyName( aName ); + } + + // No Name, then set all names + if ( aFont.GetFamilyName().isEmpty() ) + { + if ( nFlags & GetDefaultFontFlags::OnlyOne ) + { + if( !pOutDev ) + { + SAL_WARN_IF(!utl::ConfigManager::IsFuzzing(), "vcl.gdi", "No default window has been set for the application - we really shouldn't be able to get here"); + aFont.SetFamilyName( aSearch.getToken( 0, ';' ) ); + } + else + { + pOutDev->ImplInitFontList(); + + aFont.SetFamilyName( aSearch ); + + // convert to pixel height + Size aSize = pOutDev->ImplLogicToDevicePixel( aFont.GetFontSize() ); + if ( !aSize.Height() ) + { + // use default pixel height only when logical height is zero + if ( aFont.GetFontHeight() ) + aSize.setHeight( 1 ); + else + aSize.setHeight( (12*pOutDev->mnDPIY)/72 ); + } + + // use default width only when logical width is zero + if( (0 == aSize.Width()) && (0 != aFont.GetFontSize().Width()) ) + aSize.setWidth( 1 ); + + // get the name of the first available font + float fExactHeight = static_cast<float>(aSize.Height()); + rtl::Reference<LogicalFontInstance> pFontInstance = pOutDev->mxFontCache->GetFontInstance( pOutDev->mxFontCollection.get(), aFont, aSize, fExactHeight ); + if (pFontInstance) + { + assert(pFontInstance->GetFontFace()); + aFont.SetFamilyName(pFontInstance->GetFontFace()->GetFamilyName()); + } + } + } + else + aFont.SetFamilyName( aSearch ); + } + } + +#if OSL_DEBUG_LEVEL > 2 + const char* s = "SANS_UNKNOWN"; + switch ( nType ) + { + case DefaultFontType::SANS_UNICODE: s = "SANS_UNICODE"; break; + case DefaultFontType::UI_SANS: s = "UI_SANS"; break; + + case DefaultFontType::SANS: s = "SANS"; break; + case DefaultFontType::LATIN_HEADING: s = "LATIN_HEADING"; break; + case DefaultFontType::LATIN_SPREADSHEET: s = "LATIN_SPREADSHEET"; break; + case DefaultFontType::LATIN_DISPLAY: s = "LATIN_DISPLAY"; break; + + case DefaultFontType::SERIF: s = "SERIF"; break; + case DefaultFontType::LATIN_TEXT: s = "LATIN_TEXT"; break; + case DefaultFontType::LATIN_PRESENTATION: s = "LATIN_PRESENTATION"; break; + + case DefaultFontType::FIXED: s = "FIXED"; break; + case DefaultFontType::LATIN_FIXED: s = "LATIN_FIXED"; break; + case DefaultFontType::UI_FIXED: s = "UI_FIXED"; break; + + case DefaultFontType::SYMBOL: s = "SYMBOL"; break; + + case DefaultFontType::CJK_TEXT: s = "CJK_TEXT"; break; + case DefaultFontType::CJK_PRESENTATION: s = "CJK_PRESENTATION"; break; + case DefaultFontType::CJK_SPREADSHEET: s = "CJK_SPREADSHEET"; break; + case DefaultFontType::CJK_HEADING: s = "CJK_HEADING"; break; + case DefaultFontType::CJK_DISPLAY: s = "CJK_DISPLAY"; break; + + case DefaultFontType::CTL_TEXT: s = "CTL_TEXT"; break; + case DefaultFontType::CTL_PRESENTATION: s = "CTL_PRESENTATION"; break; + case DefaultFontType::CTL_SPREADSHEET: s = "CTL_SPREADSHEET"; break; + case DefaultFontType::CTL_HEADING: s = "CTL_HEADING"; break; + case DefaultFontType::CTL_DISPLAY: s = "CTL_DISPLAY"; break; + } + SAL_INFO("vcl.gdi", + "OutputDevice::GetDefaultFont() Type=" << s + << " lang=" << eLang + << " flags=" << static_cast<int>(nFlags) + << " family=\"" << aFont.GetFamilyName() << "\""); +#endif + + return aFont; +} + +void OutputDevice::ImplInitFontList() const +{ + if( !mxFontCollection->Count() ) + { + if( mpGraphics || AcquireGraphics() ) + { + SAL_INFO( "vcl.gdi", "OutputDevice::ImplInitFontList()" ); + mpGraphics->GetDevFontList(mxFontCollection.get()); + + // There is absolutely no way there should be no fonts available on the device + if( !mxFontCollection->Count() ) + { + OUString aError( "Application error: no fonts and no vcl resource found on your system" ); + OUString aResStr(VclResId(SV_ACCESSERROR_NO_FONTS)); + if (!aResStr.isEmpty()) + aError = aResStr; + Application::Abort(aError); + } + } + } +} + +bool OutputDevice::InitFont() const +{ + DBG_TESTSOLARMUTEX(); + + if (!ImplNewFont()) + return false; + if (!mpFontInstance) + return false; + if (!mpGraphics) + { + if (!AcquireGraphics()) + return false; + } + else if (!mbInitFont) + return true; + + mpGraphics->SetFont(mpFontInstance.get(), 0); + mbInitFont = false; + return true; +} + +const LogicalFontInstance* OutputDevice::GetFontInstance() const +{ + if (!InitFont()) + return nullptr; + return mpFontInstance.get(); +} + +bool OutputDevice::ImplNewFont() const +{ + DBG_TESTSOLARMUTEX(); + + // get correct font list on the PDF writer if necessary + if (GetOutDevType() == OUTDEV_PDF) + { + const ImplSVData* pSVData = ImplGetSVData(); + if( mxFontCollection == pSVData->maGDIData.mxScreenFontList + || mxFontCache == pSVData->maGDIData.mxScreenFontCache ) + const_cast<OutputDevice&>(*this).ImplUpdateFontData(); + } + + if ( !mbNewFont ) + return true; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + { + SAL_WARN("vcl.gdi", "OutputDevice::ImplNewFont(): no Graphics, no Font"); + return false; + } + + ImplInitFontList(); + + // convert to pixel height + // TODO: replace integer based aSize completely with subpixel accurate type + float fExactHeight = ImplFloatLogicHeightToDevicePixel( static_cast<float>(maFont.GetFontHeight()) ); + Size aSize = ImplLogicToDevicePixel( maFont.GetFontSize() ); + if ( !aSize.Height() ) + { + // use default pixel height only when logical height is zero + if ( maFont.GetFontSize().Height() ) + aSize.setHeight( 1 ); + else + aSize.setHeight( (12*mnDPIY)/72 ); + fExactHeight = static_cast<float>(aSize.Height()); + } + + // select the default width only when logical width is zero + if( (0 == aSize.Width()) && (0 != maFont.GetFontSize().Width()) ) + aSize.setWidth( 1 ); + + // decide if antialiasing is appropriate + bool bNonAntialiased(GetAntialiasing() & AntialiasingFlags::DisableText); + if (!utl::ConfigManager::IsFuzzing()) + { + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + bNonAntialiased |= bool(rStyleSettings.GetDisplayOptions() & DisplayOptions::AADisable); + bNonAntialiased |= (int(rStyleSettings.GetAntialiasingMinPixelHeight()) > maFont.GetFontSize().Height()); + } + + // get font entry + rtl::Reference<LogicalFontInstance> pOldFontInstance = mpFontInstance; + mpFontInstance = mxFontCache->GetFontInstance(mxFontCollection.get(), maFont, aSize, fExactHeight, bNonAntialiased); + const bool bNewFontInstance = pOldFontInstance.get() != mpFontInstance.get(); + pOldFontInstance.clear(); + + LogicalFontInstance* pFontInstance = mpFontInstance.get(); + + if (!pFontInstance) + { + SAL_WARN("vcl.gdi", "OutputDevice::ImplNewFont(): no LogicalFontInstance, no Font"); + return false; + } + + // mark when lower layers need to get involved + mbNewFont = false; + if( bNewFontInstance ) + mbInitFont = true; + + // select font when it has not been initialized yet + if (!pFontInstance->mbInit && InitFont()) + { + // get metric data from device layers + pFontInstance->mbInit = true; + + pFontInstance->mxFontMetric->SetOrientation( sal::static_int_cast<short>(mpFontInstance->GetFontSelectPattern().mnOrientation) ); + mpGraphics->GetFontMetric( pFontInstance->mxFontMetric, 0 ); + + pFontInstance->mxFontMetric->ImplInitTextLineSize( this ); + pFontInstance->mxFontMetric->ImplInitAboveTextLineSize(); + pFontInstance->mxFontMetric->ImplInitFlags( this ); + + pFontInstance->mnLineHeight = pFontInstance->mxFontMetric->GetAscent() + pFontInstance->mxFontMetric->GetDescent(); + + SetFontOrientation( pFontInstance ); + } + + // calculate EmphasisArea + mnEmphasisAscent = 0; + mnEmphasisDescent = 0; + if ( maFont.GetEmphasisMark() & FontEmphasisMark::Style ) + { + FontEmphasisMark nEmphasisMark = ImplGetEmphasisMarkStyle( maFont ); + long nEmphasisHeight = (pFontInstance->mnLineHeight*250)/1000; + if ( nEmphasisHeight < 1 ) + nEmphasisHeight = 1; + if ( nEmphasisMark & FontEmphasisMark::PosBelow ) + mnEmphasisDescent = nEmphasisHeight; + else + mnEmphasisAscent = nEmphasisHeight; + } + + // calculate text offset depending on TextAlignment + TextAlign eAlign = maFont.GetAlignment(); + if ( eAlign == ALIGN_BASELINE ) + { + mnTextOffX = 0; + mnTextOffY = 0; + } + else if ( eAlign == ALIGN_TOP ) + { + mnTextOffX = 0; + mnTextOffY = +pFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent; + if ( pFontInstance->mnOrientation ) + { + Point aOriginPt(0, 0); + aOriginPt.RotateAround( mnTextOffX, mnTextOffY, pFontInstance->mnOrientation ); + } + } + else // eAlign == ALIGN_BOTTOM + { + mnTextOffX = 0; + mnTextOffY = -pFontInstance->mxFontMetric->GetDescent() + mnEmphasisDescent; + if ( pFontInstance->mnOrientation ) + { + Point aOriginPt(0, 0); + aOriginPt.RotateAround( mnTextOffX, mnTextOffY, pFontInstance->mnOrientation ); + } + } + + mbTextLines = ((maFont.GetUnderline() != LINESTYLE_NONE) && (maFont.GetUnderline() != LINESTYLE_DONTKNOW)) || + ((maFont.GetOverline() != LINESTYLE_NONE) && (maFont.GetOverline() != LINESTYLE_DONTKNOW)) || + ((maFont.GetStrikeout() != STRIKEOUT_NONE) && (maFont.GetStrikeout() != STRIKEOUT_DONTKNOW)); + mbTextSpecial = maFont.IsShadow() || maFont.IsOutline() || + (maFont.GetRelief() != FontRelief::NONE); + + + bool bRet = true; + + // #95414# fix for OLE objects which use scale factors very creatively + if( mbMap && !aSize.Width() ) + { + int nOrigWidth = pFontInstance->mxFontMetric->GetWidth(); + float fStretch = static_cast<float>(maMapRes.mnMapScNumX) * maMapRes.mnMapScDenomY; + fStretch /= static_cast<float>(maMapRes.mnMapScNumY) * maMapRes.mnMapScDenomX; + int nNewWidth = static_cast<int>(nOrigWidth * fStretch + 0.5); + if( (nNewWidth != nOrigWidth) && (nNewWidth != 0) ) + { + Size aOrigSize = maFont.GetFontSize(); + const_cast<vcl::Font&>(maFont).SetFontSize( Size( nNewWidth, aSize.Height() ) ); + mbMap = false; + mbNewFont = true; + bRet = ImplNewFont(); // recurse once using stretched width + mbMap = true; + const_cast<vcl::Font&>(maFont).SetFontSize( aOrigSize ); + } + } + + return bRet; +} + +void OutputDevice::SetFontOrientation( LogicalFontInstance* const pFontInstance ) const +{ + if( pFontInstance->GetFontSelectPattern().mnOrientation && !pFontInstance->mxFontMetric->GetOrientation() ) + { + pFontInstance->mnOwnOrientation = sal::static_int_cast<short>(pFontInstance->GetFontSelectPattern().mnOrientation); + pFontInstance->mnOrientation = pFontInstance->mnOwnOrientation; + } + else + { + pFontInstance->mnOrientation = pFontInstance->mxFontMetric->GetOrientation(); + } +} + +void OutputDevice::ImplDrawEmphasisMark( long nBaseX, long nX, long nY, + const tools::PolyPolygon& rPolyPoly, bool bPolyLine, + const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 ) +{ + if( IsRTLEnabled() ) + nX = nBaseX - (nX - nBaseX - 1); + + nX -= mnOutOffX; + nY -= mnOutOffY; + + if ( rPolyPoly.Count() ) + { + if ( bPolyLine ) + { + tools::Polygon aPoly = rPolyPoly.GetObject( 0 ); + aPoly.Move( nX, nY ); + DrawPolyLine( aPoly ); + } + else + { + tools::PolyPolygon aPolyPoly = rPolyPoly; + aPolyPoly.Move( nX, nY ); + DrawPolyPolygon( aPolyPoly ); + } + } + + if ( !rRect1.IsEmpty() ) + { + tools::Rectangle aRect( Point( nX+rRect1.Left(), + nY+rRect1.Top() ), rRect1.GetSize() ); + DrawRect( aRect ); + } + + if ( !rRect2.IsEmpty() ) + { + tools::Rectangle aRect( Point( nX+rRect2.Left(), + nY+rRect2.Top() ), rRect2.GetSize() ); + + DrawRect( aRect ); + } +} + +void OutputDevice::ImplDrawEmphasisMarks( SalLayout& rSalLayout ) +{ + Color aOldLineColor = GetLineColor(); + Color aOldFillColor = GetFillColor(); + bool bOldMap = mbMap; + GDIMetaFile* pOldMetaFile = mpMetaFile; + mpMetaFile = nullptr; + EnableMapMode( false ); + + FontEmphasisMark nEmphasisMark = ImplGetEmphasisMarkStyle( maFont ); + tools::PolyPolygon aPolyPoly; + tools::Rectangle aRect1; + tools::Rectangle aRect2; + long nEmphasisYOff; + long nEmphasisWidth; + long nEmphasisHeight; + bool bPolyLine; + + if ( nEmphasisMark & FontEmphasisMark::PosBelow ) + nEmphasisHeight = mnEmphasisDescent; + else + nEmphasisHeight = mnEmphasisAscent; + + ImplGetEmphasisMark( aPolyPoly, bPolyLine, + aRect1, aRect2, + nEmphasisYOff, nEmphasisWidth, + nEmphasisMark, + nEmphasisHeight ); + + if ( bPolyLine ) + { + SetLineColor( GetTextColor() ); + SetFillColor(); + } + else + { + SetLineColor(); + SetFillColor( GetTextColor() ); + } + + Point aOffset(0,0); + + if ( nEmphasisMark & FontEmphasisMark::PosBelow ) + aOffset.AdjustY(mpFontInstance->mxFontMetric->GetDescent() + nEmphasisYOff ); + else + aOffset.AdjustY( -(mpFontInstance->mxFontMetric->GetAscent() + nEmphasisYOff) ); + + long nEmphasisWidth2 = nEmphasisWidth / 2; + long nEmphasisHeight2 = nEmphasisHeight / 2; + aOffset += Point( nEmphasisWidth2, nEmphasisHeight2 ); + + Point aOutPoint; + tools::Rectangle aRectangle; + const GlyphItem* pGlyph; + int nStart = 0; + while (rSalLayout.GetNextGlyph(&pGlyph, aOutPoint, nStart)) + { + if (!pGlyph->GetGlyphBoundRect(aRectangle)) + continue; + + if (!pGlyph->IsSpacing()) + { + Point aAdjPoint = aOffset; + aAdjPoint.AdjustX(aRectangle.Left() + (aRectangle.GetWidth() - nEmphasisWidth) / 2 ); + if ( mpFontInstance->mnOrientation ) + { + Point aOriginPt(0, 0); + aOriginPt.RotateAround( aAdjPoint, mpFontInstance->mnOrientation ); + } + aOutPoint += aAdjPoint; + aOutPoint -= Point( nEmphasisWidth2, nEmphasisHeight2 ); + ImplDrawEmphasisMark( rSalLayout.DrawBase().X(), + aOutPoint.X(), aOutPoint.Y(), + aPolyPoly, bPolyLine, aRect1, aRect2 ); + } + } + + SetLineColor( aOldLineColor ); + SetFillColor( aOldFillColor ); + EnableMapMode( bOldMap ); + mpMetaFile = pOldMetaFile; +} + +std::unique_ptr<SalLayout> OutputDevice::getFallbackLayout( + LogicalFontInstance* pLogicalFont, int nFallbackLevel, + ImplLayoutArgs& rLayoutArgs) const +{ + // we need a graphics + if (!mpGraphics && !AcquireGraphics()) + return nullptr; + + assert(mpGraphics != nullptr); + mpGraphics->SetFont( pLogicalFont, nFallbackLevel ); + + rLayoutArgs.ResetPos(); + std::unique_ptr<GenericSalLayout> pFallback = mpGraphics->GetTextLayout(nFallbackLevel); + + if (!pFallback) + return nullptr; + + if (!pFallback->LayoutText(rLayoutArgs, nullptr)) + { + // there is no need for a font that couldn't resolve anything + return nullptr; + } + + return pFallback; +} + +std::unique_ptr<SalLayout> OutputDevice::ImplGlyphFallbackLayout( std::unique_ptr<SalLayout> pSalLayout, ImplLayoutArgs& rLayoutArgs ) const +{ + // This function relies on a valid mpFontInstance, if it doesn't exist bail out + // - we'd have crashed later on anyway. At least here we can catch the error in debug + // mode. + if ( !mpFontInstance ) + { + SAL_WARN ("vcl.gdi", "No font entry set in OutputDevice"); + assert(mpFontInstance); + return nullptr; + } + + // prepare multi level glyph fallback + std::unique_ptr<MultiSalLayout> pMultiSalLayout; + ImplLayoutRuns aLayoutRuns = rLayoutArgs.maRuns; + rLayoutArgs.PrepareFallback(); + rLayoutArgs.mnFlags |= SalLayoutFlags::ForFallback; + + // get list of code units that need glyph fallback + int nCharPos = -1; + bool bRTL = false; + OUStringBuffer aMissingCodeBuf(512); + while (rLayoutArgs.GetNextPos( &nCharPos, &bRTL)) + aMissingCodeBuf.append(rLayoutArgs.mrStr[nCharPos]); + rLayoutArgs.ResetPos(); + OUString aMissingCodes = aMissingCodeBuf.makeStringAndClear(); + + FontSelectPattern aFontSelData(mpFontInstance->GetFontSelectPattern()); + + // try if fallback fonts support the missing code units + for( int nFallbackLevel = 1; nFallbackLevel < MAX_FALLBACK; ++nFallbackLevel ) + { + // find a font family suited for glyph fallback + // GetGlyphFallbackFont() needs a valid FontInstance + // if the system-specific glyph fallback is active + rtl::Reference<LogicalFontInstance> pFallbackFont = mxFontCache->GetGlyphFallbackFont( mxFontCollection.get(), + aFontSelData, mpFontInstance.get(), nFallbackLevel, aMissingCodes ); + if( !pFallbackFont ) + break; + + if( nFallbackLevel < MAX_FALLBACK-1) + { + // ignore fallback font if it is the same as the original font + // unless we are looking for a substitution for 0x202F, in which + // case we'll just use a normal space + if( mpFontInstance->GetFontFace() == pFallbackFont->GetFontFace() && + aMissingCodes.indexOf(0x202F) == -1 ) + { + continue; + } + } + + // create and add glyph fallback layout to multilayout + std::unique_ptr<SalLayout> pFallback = getFallbackLayout(pFallbackFont.get(), + nFallbackLevel, rLayoutArgs); + if (pFallback) + { + if( !pMultiSalLayout ) + pMultiSalLayout.reset( new MultiSalLayout( std::move(pSalLayout) ) ); + pMultiSalLayout->AddFallback(std::move(pFallback), rLayoutArgs.maRuns); + if (nFallbackLevel == MAX_FALLBACK-1) + pMultiSalLayout->SetIncomplete(true); + } + + // break when this fallback was sufficient + if( !rLayoutArgs.PrepareFallback() ) + break; + } + + if( pMultiSalLayout && pMultiSalLayout->LayoutText( rLayoutArgs, nullptr ) ) + pSalLayout = std::move(pMultiSalLayout); + + // restore orig font settings + pSalLayout->InitFont(); + rLayoutArgs.maRuns = aLayoutRuns; + + return pSalLayout; +} + +long OutputDevice::GetMinKashida() const +{ + if (!ImplNewFont()) + return 0; + + return ImplDevicePixelToLogicWidth( mpFontInstance->mxFontMetric->GetMinKashida() ); +} + +sal_Int32 OutputDevice::ValidateKashidas ( const OUString& rTxt, + sal_Int32 nIdx, sal_Int32 nLen, + sal_Int32 nKashCount, + const sal_Int32* pKashidaPos, + sal_Int32* pKashidaPosDropped ) const +{ + // do layout + std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rTxt, nIdx, nLen ); + if( !pSalLayout ) + return 0; + sal_Int32 nDropped = 0; + for( int i = 0; i < nKashCount; ++i ) + { + if( !pSalLayout->IsKashidaPosValid( pKashidaPos[ i ] )) + { + pKashidaPosDropped[ nDropped ] = pKashidaPos [ i ]; + ++nDropped; + } + } + return nDropped; +} + +bool OutputDevice::GetGlyphBoundRects( const Point& rOrigin, const OUString& rStr, + int nIndex, int nLen, MetricVector& rVector ) +{ + rVector.clear(); + + if( nIndex >= rStr.getLength() ) + return false; + + if( nLen < 0 || nIndex + nLen >= rStr.getLength() ) + { + nLen = rStr.getLength() - nIndex; + } + + tools::Rectangle aRect; + for( int i = 0; i < nLen; i++ ) + { + if( !GetTextBoundRect( aRect, rStr, nIndex, nIndex + i, 1 ) ) + break; + aRect.Move( rOrigin.X(), rOrigin.Y() ); + rVector.push_back( aRect ); + } + + return (nLen == static_cast<int>(rVector.size())); +} + +sal_Int32 OutputDevice::HasGlyphs( const vcl::Font& rTempFont, const OUString& rStr, + sal_Int32 nIndex, sal_Int32 nLen ) const +{ + if( nIndex >= rStr.getLength() ) + return nIndex; + sal_Int32 nEnd; + if( nLen == -1 ) + nEnd = rStr.getLength(); + else + nEnd = std::min( rStr.getLength(), nIndex + nLen ); + + SAL_WARN_IF( nIndex >= nEnd, "vcl.gdi", "StartPos >= EndPos?" ); + SAL_WARN_IF( nEnd > rStr.getLength(), "vcl.gdi", "String too short" ); + + // to get the map temporarily set font + const vcl::Font aOrigFont = GetFont(); + const_cast<OutputDevice&>(*this).SetFont( rTempFont ); + FontCharMapRef xFontCharMap; + bool bRet = GetFontCharMap( xFontCharMap ); + const_cast<OutputDevice&>(*this).SetFont( aOrigFont ); + + // if fontmap is unknown assume it doesn't have the glyphs + if( !bRet ) + return nIndex; + + for( sal_Int32 i = nIndex; nIndex < nEnd; ++i, ++nIndex ) + if( ! xFontCharMap->HasChar( rStr[i] ) ) + return nIndex; + + return -1; +} + +void OutputDevice::ReleaseFontCache() { mxFontCache.reset(); } + +void OutputDevice::ReleaseFontCollection() { mxFontCollection.reset(); } + +void OutputDevice::SetFontCollectionFromSVData() +{ + mxFontCollection = ImplGetSVData()->maGDIData.mxScreenFontList->Clone(); +} + +void OutputDevice::ResetNewFontCache() +{ + mxFontCache = std::make_shared<ImplFontCache>(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/gradient.cxx b/vcl/source/outdev/gradient.cxx new file mode 100644 index 000000000..2e8406714 --- /dev/null +++ b/vcl/source/outdev/gradient.cxx @@ -0,0 +1,1045 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <cassert> + +#include <tools/poly.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/gradient.hxx> +#include <vcl/metaact.hxx> +#include <vcl/settings.hxx> +#include <vcl/outdev.hxx> +#include <vcl/virdev.hxx> +#include <vcl/window.hxx> + +#include <salgdi.hxx> + +#define GRADIENT_DEFAULT_STEPCOUNT 0 + +void OutputDevice::DrawGradient( const tools::Rectangle& rRect, + const Gradient& rGradient ) +{ + assert(!is_double_buffered_window()); + + // Convert rectangle to a tools::PolyPolygon by first converting to a Polygon + tools::Polygon aPolygon ( rRect ); + tools::PolyPolygon aPolyPoly ( aPolygon ); + + DrawGradient ( aPolyPoly, rGradient ); +} + +void OutputDevice::DrawGradient( const tools::PolyPolygon& rPolyPoly, + const Gradient& rGradient ) +{ + assert(!is_double_buffered_window()); + + if (mbInitClipRegion) + InitClipRegion(); + // don't return on mbOutputClipped here, as we may need to draw the clipped metafile, even if the output is clipped + + if ( rPolyPoly.Count() && rPolyPoly[ 0 ].GetSize() ) + { + if ( mnDrawMode & ( DrawModeFlags::BlackGradient | DrawModeFlags::WhiteGradient | DrawModeFlags::SettingsGradient) ) + { + Color aColor = GetSingleColorGradientFill(); + + Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR ); + SetLineColor( aColor ); + SetFillColor( aColor ); + DrawPolyPolygon( rPolyPoly ); + Pop(); + return; + } + + Gradient aGradient( rGradient ); + + if ( mnDrawMode & DrawModeFlags::GrayGradient ) + { + SetGrayscaleColors( aGradient ); + } + + DrawGradientToMetafile( rPolyPoly, rGradient ); + + if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + // Clip and then draw the gradient + if( !tools::Rectangle( PixelToLogic( Point() ), GetOutputSize() ).IsEmpty() ) + { + const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() ); + + // convert rectangle to pixels + tools::Rectangle aRect( ImplLogicToDevicePixel( aBoundRect ) ); + aRect.Justify(); + + // do nothing if the rectangle is empty + if ( !aRect.IsEmpty() ) + { + tools::PolyPolygon aClixPolyPoly( ImplLogicToDevicePixel( rPolyPoly ) ); + bool bDrawn = false; + + if( !mpGraphics && !AcquireGraphics() ) + return; + + // secure clip region + Push( PushFlags::CLIPREGION ); + IntersectClipRegion( aBoundRect ); + + if (mbInitClipRegion) + InitClipRegion(); + + // try to draw gradient natively + bDrawn = mpGraphics->DrawGradient( aClixPolyPoly, aGradient ); + + if (!bDrawn && !mbOutputClipped) + { + // draw gradients without border + if( mbLineColor || mbInitLineColor ) + { + mpGraphics->SetLineColor(); + mbInitLineColor = true; + } + + mbInitFillColor = true; + + // calculate step count if necessary + if ( !aGradient.GetSteps() ) + aGradient.SetSteps( GRADIENT_DEFAULT_STEPCOUNT ); + + if ( rPolyPoly.IsRect() ) + { + // because we draw with no border line, we have to expand gradient + // rect to avoid missing lines on the right and bottom edge + aRect.AdjustLeft( -1 ); + aRect.AdjustTop( -1 ); + aRect.AdjustRight( 1 ); + aRect.AdjustBottom( 1 ); + } + + // if the clipping polypolygon is a rectangle, then it's the same size as the bounding of the + // polypolygon, so pass in a NULL for the clipping parameter + if( aGradient.GetStyle() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial ) + DrawLinearGradient( aRect, aGradient, aClixPolyPoly.IsRect() ? nullptr : &aClixPolyPoly ); + else + DrawComplexGradient( aRect, aGradient, aClixPolyPoly.IsRect() ? nullptr : &aClixPolyPoly ); + } + + Pop(); + } + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawPolyPolygon( rPolyPoly ); +} + +void OutputDevice::ClipAndDrawGradientMetafile ( const Gradient &rGradient, const tools::PolyPolygon &rPolyPoly ) +{ + const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() ); + const bool bOldOutput = IsOutputEnabled(); + + EnableOutput( false ); + Push( PushFlags::RASTEROP ); + SetRasterOp( RasterOp::Xor ); + DrawGradient( aBoundRect, rGradient ); + SetFillColor( COL_BLACK ); + SetRasterOp( RasterOp::N0 ); + DrawPolyPolygon( rPolyPoly ); + SetRasterOp( RasterOp::Xor ); + DrawGradient( aBoundRect, rGradient ); + Pop(); + EnableOutput( bOldOutput ); +} + +void OutputDevice::DrawGradientToMetafile ( const tools::PolyPolygon& rPolyPoly, + const Gradient& rGradient ) +{ + assert(!is_double_buffered_window()); + + if ( !mpMetaFile ) + return; + + if ( rPolyPoly.Count() && rPolyPoly[ 0 ].GetSize() ) + { + Gradient aGradient( rGradient ); + + if ( mnDrawMode & DrawModeFlags::GrayGradient ) + { + SetGrayscaleColors( aGradient ); + } + + const tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() ); + + if ( rPolyPoly.IsRect() ) + { + mpMetaFile->AddAction( new MetaGradientAction( aBoundRect, aGradient ) ); + } + else + { + mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_BEGIN" ) ); + mpMetaFile->AddAction( new MetaGradientExAction( rPolyPoly, rGradient ) ); + + ClipAndDrawGradientMetafile ( rGradient, rPolyPoly ); + + mpMetaFile->AddAction( new MetaCommentAction( "XGRAD_SEQ_END" ) ); + } + + if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + // Clip and then draw the gradient + if( !tools::Rectangle( PixelToLogic( Point() ), GetOutputSize() ).IsEmpty() ) + { + // convert rectangle to pixels + tools::Rectangle aRect( ImplLogicToDevicePixel( aBoundRect ) ); + aRect.Justify(); + + // do nothing if the rectangle is empty + if ( !aRect.IsEmpty() ) + { + if( !mbOutputClipped ) + { + // calculate step count if necessary + if ( !aGradient.GetSteps() ) + aGradient.SetSteps( GRADIENT_DEFAULT_STEPCOUNT ); + + if ( rPolyPoly.IsRect() ) + { + // because we draw with no border line, we have to expand gradient + // rect to avoid missing lines on the right and bottom edge + aRect.AdjustLeft( -1 ); + aRect.AdjustTop( -1 ); + aRect.AdjustRight( 1 ); + aRect.AdjustBottom( 1 ); + } + + // if the clipping polypolygon is a rectangle, then it's the same size as the bounding of the + // polypolygon, so pass in a NULL for the clipping parameter + if( aGradient.GetStyle() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial ) + DrawLinearGradientToMetafile( aRect, aGradient ); + else + DrawComplexGradientToMetafile( aRect, aGradient ); + } + } + } + } +} + +namespace +{ + sal_uInt8 GetGradientColorValue( long nValue ) + { + if ( nValue < 0 ) + return 0; + else if ( nValue > 0xFF ) + return 0xFF; + else + return static_cast<sal_uInt8>(nValue); + } +} + +void OutputDevice::DrawLinearGradient( const tools::Rectangle& rRect, + const Gradient& rGradient, + const tools::PolyPolygon* pClixPolyPoly ) +{ + assert(!is_double_buffered_window()); + + // get BoundRect of rotated rectangle + tools::Rectangle aRect; + Point aCenter; + sal_uInt16 nAngle = rGradient.GetAngle() % 3600; + + rGradient.GetBoundRect( rRect, aRect, aCenter ); + + bool bLinear = (rGradient.GetStyle() == GradientStyle::Linear); + double fBorder = rGradient.GetBorder() * aRect.GetHeight() / 100.0; + if ( !bLinear ) + { + fBorder /= 2.0; + } + tools::Rectangle aMirrorRect = aRect; // used in style axial + aMirrorRect.SetTop( ( aRect.Top() + aRect.Bottom() ) / 2 ); + if ( !bLinear ) + { + aRect.SetBottom( aMirrorRect.Top() ); + } + + // colour-intensities of start- and finish; change if needed + long nFactor; + Color aStartCol = rGradient.GetStartColor(); + Color aEndCol = rGradient.GetEndColor(); + long nStartRed = aStartCol.GetRed(); + long nStartGreen = aStartCol.GetGreen(); + long nStartBlue = aStartCol.GetBlue(); + long nEndRed = aEndCol.GetRed(); + long nEndGreen = aEndCol.GetGreen(); + long nEndBlue = aEndCol.GetBlue(); + nFactor = rGradient.GetStartIntensity(); + nStartRed = (nStartRed * nFactor) / 100; + nStartGreen = (nStartGreen * nFactor) / 100; + nStartBlue = (nStartBlue * nFactor) / 100; + nFactor = rGradient.GetEndIntensity(); + nEndRed = (nEndRed * nFactor) / 100; + nEndGreen = (nEndGreen * nFactor) / 100; + nEndBlue = (nEndBlue * nFactor) / 100; + + // gradient style axial has exchanged start and end colors + if ( !bLinear) + { + long nTempColor = nStartRed; + nStartRed = nEndRed; + nEndRed = nTempColor; + nTempColor = nStartGreen; + nStartGreen = nEndGreen; + nEndGreen = nTempColor; + nTempColor = nStartBlue; + nStartBlue = nEndBlue; + nEndBlue = nTempColor; + } + + sal_uInt8 nRed; + sal_uInt8 nGreen; + sal_uInt8 nBlue; + + // Create border + tools::Rectangle aBorderRect = aRect; + tools::Polygon aPoly( 4 ); + if (fBorder > 0.0) + { + nRed = static_cast<sal_uInt8>(nStartRed); + nGreen = static_cast<sal_uInt8>(nStartGreen); + nBlue = static_cast<sal_uInt8>(nStartBlue); + + mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) ); + + aBorderRect.SetBottom( static_cast<long>( aBorderRect.Top() + fBorder ) ); + aRect.SetTop( aBorderRect.Bottom() ); + aPoly[0] = aBorderRect.TopLeft(); + aPoly[1] = aBorderRect.TopRight(); + aPoly[2] = aBorderRect.BottomRight(); + aPoly[3] = aBorderRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + ImplDrawPolygon( aPoly, pClixPolyPoly ); + + if ( !bLinear) + { + aBorderRect = aMirrorRect; + aBorderRect.SetTop( static_cast<long>( aBorderRect.Bottom() - fBorder ) ); + aMirrorRect.SetBottom( aBorderRect.Top() ); + aPoly[0] = aBorderRect.TopLeft(); + aPoly[1] = aBorderRect.TopRight(); + aPoly[2] = aBorderRect.BottomRight(); + aPoly[3] = aBorderRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + ImplDrawPolygon( aPoly, pClixPolyPoly ); + } + } + + // calculate step count + long nStepCount = GetGradientSteps( rGradient, aRect, false/*bMtf*/ ); + + // minimal three steps and maximal as max color steps + long nAbsRedSteps = std::abs( nEndRed - nStartRed ); + long nAbsGreenSteps = std::abs( nEndGreen - nStartGreen ); + long nAbsBlueSteps = std::abs( nEndBlue - nStartBlue ); + long nMaxColorSteps = std::max( nAbsRedSteps , nAbsGreenSteps ); + nMaxColorSteps = std::max( nMaxColorSteps, nAbsBlueSteps ); + long nSteps = std::min( nStepCount, nMaxColorSteps ); + if ( nSteps < 3) + { + nSteps = 3; + } + + double fScanInc = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps); + double fGradientLine = static_cast<double>(aRect.Top()); + double fMirrorGradientLine = static_cast<double>(aMirrorRect.Bottom()); + + const double fStepsMinus1 = static_cast<double>(nSteps) - 1.0; + if ( !bLinear) + { + nSteps -= 1; // draw middle polygons as one polygon after loop to avoid gap + } + for ( long i = 0; i < nSteps; i++ ) + { + // linear interpolation of color + const double fAlpha = static_cast<double>(i) / fStepsMinus1; + double fTempColor = static_cast<double>(nStartRed) * (1.0-fAlpha) + static_cast<double>(nEndRed) * fAlpha; + nRed = GetGradientColorValue(static_cast<long>(fTempColor)); + fTempColor = static_cast<double>(nStartGreen) * (1.0-fAlpha) + static_cast<double>(nEndGreen) * fAlpha; + nGreen = GetGradientColorValue(static_cast<long>(fTempColor)); + fTempColor = static_cast<double>(nStartBlue) * (1.0-fAlpha) + static_cast<double>(nEndBlue) * fAlpha; + nBlue = GetGradientColorValue(static_cast<long>(fTempColor)); + + mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) ); + + // Polygon for this color step + aRect.SetTop( static_cast<long>( fGradientLine + static_cast<double>(i) * fScanInc ) ); + aRect.SetBottom( static_cast<long>( fGradientLine + ( static_cast<double>(i) + 1.0 ) * fScanInc ) ); + aPoly[0] = aRect.TopLeft(); + aPoly[1] = aRect.TopRight(); + aPoly[2] = aRect.BottomRight(); + aPoly[3] = aRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + ImplDrawPolygon( aPoly, pClixPolyPoly ); + + if ( !bLinear ) + { + aMirrorRect.SetBottom( static_cast<long>( fMirrorGradientLine - static_cast<double>(i) * fScanInc ) ); + aMirrorRect.SetTop( static_cast<long>( fMirrorGradientLine - (static_cast<double>(i) + 1.0)* fScanInc ) ); + aPoly[0] = aMirrorRect.TopLeft(); + aPoly[1] = aMirrorRect.TopRight(); + aPoly[2] = aMirrorRect.BottomRight(); + aPoly[3] = aMirrorRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + ImplDrawPolygon( aPoly, pClixPolyPoly ); + } + } + if ( bLinear) + return; + + // draw middle polygon with end color + nRed = GetGradientColorValue(nEndRed); + nGreen = GetGradientColorValue(nEndGreen); + nBlue = GetGradientColorValue(nEndBlue); + + mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) ); + + aRect.SetTop( static_cast<long>( fGradientLine + static_cast<double>(nSteps) * fScanInc ) ); + aRect.SetBottom( static_cast<long>( fMirrorGradientLine - static_cast<double>(nSteps) * fScanInc ) ); + aPoly[0] = aRect.TopLeft(); + aPoly[1] = aRect.TopRight(); + aPoly[2] = aRect.BottomRight(); + aPoly[3] = aRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + ImplDrawPolygon( aPoly, pClixPolyPoly ); + +} + +bool OutputDevice::is_double_buffered_window() const +{ + const vcl::Window *pWindow = dynamic_cast<const vcl::Window*>(this); + return pWindow && pWindow->SupportsDoubleBuffering(); +} + +void OutputDevice::DrawComplexGradient( const tools::Rectangle& rRect, + const Gradient& rGradient, + const tools::PolyPolygon* pClixPolyPoly ) +{ + assert(!is_double_buffered_window()); + + // Determine if we output via Polygon or PolyPolygon + // For all rasteroperations other than Overpaint always use PolyPolygon, + // as we will get wrong results if we output multiple times on top of each other. + // Also for printers always use PolyPolygon, as not all printers + // can print polygons on top of each other. + + std::unique_ptr<tools::PolyPolygon> xPolyPoly; + tools::Rectangle aRect; + Point aCenter; + Color aStartCol( rGradient.GetStartColor() ); + Color aEndCol( rGradient.GetEndColor() ); + long nStartRed = ( static_cast<long>(aStartCol.GetRed()) * rGradient.GetStartIntensity() ) / 100; + long nStartGreen = ( static_cast<long>(aStartCol.GetGreen()) * rGradient.GetStartIntensity() ) / 100; + long nStartBlue = ( static_cast<long>(aStartCol.GetBlue()) * rGradient.GetStartIntensity() ) / 100; + long nEndRed = ( static_cast<long>(aEndCol.GetRed()) * rGradient.GetEndIntensity() ) / 100; + long nEndGreen = ( static_cast<long>(aEndCol.GetGreen()) * rGradient.GetEndIntensity() ) / 100; + long nEndBlue = ( static_cast<long>(aEndCol.GetBlue()) * rGradient.GetEndIntensity() ) / 100; + long nRedSteps = nEndRed - nStartRed; + long nGreenSteps = nEndGreen - nStartGreen; + long nBlueSteps = nEndBlue - nStartBlue; + sal_uInt16 nAngle = rGradient.GetAngle() % 3600; + + rGradient.GetBoundRect( rRect, aRect, aCenter ); + + if ( UsePolyPolygonForComplexGradient() ) + xPolyPoly.reset(new tools::PolyPolygon( 2 )); + + long nStepCount = GetGradientSteps( rGradient, rRect, false/*bMtf*/, true/*bComplex*/ ); + + // at least three steps and at most the number of colour differences + long nSteps = std::max( nStepCount, 2L ); + long nCalcSteps = std::abs( nRedSteps ); + long nTempSteps = std::abs( nGreenSteps ); + if ( nTempSteps > nCalcSteps ) + nCalcSteps = nTempSteps; + nTempSteps = std::abs( nBlueSteps ); + if ( nTempSteps > nCalcSteps ) + nCalcSteps = nTempSteps; + if ( nCalcSteps < nSteps ) + nSteps = nCalcSteps; + if ( !nSteps ) + nSteps = 1; + + // determine output limits and stepsizes for all directions + tools::Polygon aPoly; + double fScanLeft = aRect.Left(); + double fScanTop = aRect.Top(); + double fScanRight = aRect.Right(); + double fScanBottom = aRect.Bottom(); + double fScanIncX = static_cast<double>(aRect.GetWidth()) / static_cast<double>(nSteps) * 0.5; + double fScanIncY = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps) * 0.5; + + // all gradients are rendered as nested rectangles which shrink + // equally in each dimension - except for 'square' gradients + // which shrink to a central vertex but are not per-se square. + if( rGradient.GetStyle() != GradientStyle::Square ) + { + fScanIncY = std::min( fScanIncY, fScanIncX ); + fScanIncX = fScanIncY; + } + sal_uInt8 nRed = static_cast<sal_uInt8>(nStartRed), nGreen = static_cast<sal_uInt8>(nStartGreen), nBlue = static_cast<sal_uInt8>(nStartBlue); + bool bPaintLastPolygon( false ); // #107349# Paint last polygon only if loop has generated any output + + mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) ); + + if( xPolyPoly ) + { + aPoly = rRect; + xPolyPoly->Insert( aPoly ); + xPolyPoly->Insert( aPoly ); + } + else + { + // extend rect, to avoid missing bounding line + tools::Rectangle aExtRect( rRect ); + + aExtRect.AdjustLeft( -1 ); + aExtRect.AdjustTop( -1 ); + aExtRect.AdjustRight(1 ); + aExtRect.AdjustBottom(1 ); + + aPoly = aExtRect; + ImplDrawPolygon( aPoly, pClixPolyPoly ); + } + + // loop to output Polygon/PolyPolygon sequentially + for( long i = 1; i < nSteps; i++ ) + { + // calculate new Polygon + fScanLeft += fScanIncX; + aRect.SetLeft( static_cast<long>( fScanLeft ) ); + fScanTop += fScanIncY; + aRect.SetTop( static_cast<long>( fScanTop ) ); + fScanRight -= fScanIncX; + aRect.SetRight( static_cast<long>( fScanRight ) ); + fScanBottom -= fScanIncY; + aRect.SetBottom( static_cast<long>( fScanBottom ) ); + + if( ( aRect.GetWidth() < 2 ) || ( aRect.GetHeight() < 2 ) ) + break; + + if( rGradient.GetStyle() == GradientStyle::Radial || rGradient.GetStyle() == GradientStyle::Elliptical ) + aPoly = tools::Polygon( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 ); + else + aPoly = tools::Polygon( aRect ); + + aPoly.Rotate( aCenter, nAngle ); + + // adapt colour accordingly + const long nStepIndex = ( xPolyPoly ? i : ( i + 1 ) ); + nRed = GetGradientColorValue( nStartRed + ( ( nRedSteps * nStepIndex ) / nSteps ) ); + nGreen = GetGradientColorValue( nStartGreen + ( ( nGreenSteps * nStepIndex ) / nSteps ) ); + nBlue = GetGradientColorValue( nStartBlue + ( ( nBlueSteps * nStepIndex ) / nSteps ) ); + + // either slow tools::PolyPolygon output or fast Polygon-Painting + if( xPolyPoly ) + { + bPaintLastPolygon = true; // #107349# Paint last polygon only if loop has generated any output + + xPolyPoly->Replace( xPolyPoly->GetObject( 1 ), 0 ); + xPolyPoly->Replace( aPoly, 1 ); + + ImplDrawPolyPolygon( *xPolyPoly, pClixPolyPoly ); + + // #107349# Set fill color _after_ geometry painting: + // xPolyPoly's geometry is the band from last iteration's + // aPoly to current iteration's aPoly. The window outdev + // path (see else below), on the other hand, paints the + // full aPoly. Thus, here, we're painting the band before + // the one painted in the window outdev path below. To get + // matching colors, have to delay color setting here. + mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) ); + } + else + { + // #107349# Set fill color _before_ geometry painting + mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) ); + + ImplDrawPolygon( aPoly, pClixPolyPoly ); + } + } + + // we should draw last inner Polygon if we output PolyPolygon + if( xPolyPoly ) + { + const tools::Polygon& rPoly = xPolyPoly->GetObject( 1 ); + + if( !rPoly.GetBoundRect().IsEmpty() ) + { + // #107349# Paint last polygon with end color only if loop + // has generated output. Otherwise, the current + // (i.e. start) color is taken, to generate _any_ output. + if( bPaintLastPolygon ) + { + nRed = GetGradientColorValue( nEndRed ); + nGreen = GetGradientColorValue( nEndGreen ); + nBlue = GetGradientColorValue( nEndBlue ); + } + + mpGraphics->SetFillColor( Color( nRed, nGreen, nBlue ) ); + ImplDrawPolygon( rPoly, pClixPolyPoly ); + } + } +} + +void OutputDevice::DrawLinearGradientToMetafile( const tools::Rectangle& rRect, + const Gradient& rGradient ) +{ + assert(!is_double_buffered_window()); + + // get BoundRect of rotated rectangle + tools::Rectangle aRect; + Point aCenter; + sal_uInt16 nAngle = rGradient.GetAngle() % 3600; + + rGradient.GetBoundRect( rRect, aRect, aCenter ); + + bool bLinear = (rGradient.GetStyle() == GradientStyle::Linear); + double fBorder = rGradient.GetBorder() * aRect.GetHeight() / 100.0; + if ( !bLinear ) + { + fBorder /= 2.0; + } + tools::Rectangle aMirrorRect = aRect; // used in style axial + aMirrorRect.SetTop( ( aRect.Top() + aRect.Bottom() ) / 2 ); + if ( !bLinear ) + { + aRect.SetBottom( aMirrorRect.Top() ); + } + + // colour-intensities of start- and finish; change if needed + long nFactor; + Color aStartCol = rGradient.GetStartColor(); + Color aEndCol = rGradient.GetEndColor(); + long nStartRed = aStartCol.GetRed(); + long nStartGreen = aStartCol.GetGreen(); + long nStartBlue = aStartCol.GetBlue(); + long nEndRed = aEndCol.GetRed(); + long nEndGreen = aEndCol.GetGreen(); + long nEndBlue = aEndCol.GetBlue(); + nFactor = rGradient.GetStartIntensity(); + nStartRed = (nStartRed * nFactor) / 100; + nStartGreen = (nStartGreen * nFactor) / 100; + nStartBlue = (nStartBlue * nFactor) / 100; + nFactor = rGradient.GetEndIntensity(); + nEndRed = (nEndRed * nFactor) / 100; + nEndGreen = (nEndGreen * nFactor) / 100; + nEndBlue = (nEndBlue * nFactor) / 100; + + // gradient style axial has exchanged start and end colors + if ( !bLinear) + { + long nTempColor = nStartRed; + nStartRed = nEndRed; + nEndRed = nTempColor; + nTempColor = nStartGreen; + nStartGreen = nEndGreen; + nEndGreen = nTempColor; + nTempColor = nStartBlue; + nStartBlue = nEndBlue; + nEndBlue = nTempColor; + } + + sal_uInt8 nRed; + sal_uInt8 nGreen; + sal_uInt8 nBlue; + + // Create border + tools::Rectangle aBorderRect = aRect; + tools::Polygon aPoly( 4 ); + if (fBorder > 0.0) + { + nRed = static_cast<sal_uInt8>(nStartRed); + nGreen = static_cast<sal_uInt8>(nStartGreen); + nBlue = static_cast<sal_uInt8>(nStartBlue); + + mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); + + aBorderRect.SetBottom( static_cast<long>( aBorderRect.Top() + fBorder ) ); + aRect.SetTop( aBorderRect.Bottom() ); + aPoly[0] = aBorderRect.TopLeft(); + aPoly[1] = aBorderRect.TopRight(); + aPoly[2] = aBorderRect.BottomRight(); + aPoly[3] = aBorderRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) ); + + if ( !bLinear) + { + aBorderRect = aMirrorRect; + aBorderRect.SetTop( static_cast<long>( aBorderRect.Bottom() - fBorder ) ); + aMirrorRect.SetBottom( aBorderRect.Top() ); + aPoly[0] = aBorderRect.TopLeft(); + aPoly[1] = aBorderRect.TopRight(); + aPoly[2] = aBorderRect.BottomRight(); + aPoly[3] = aBorderRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) ); + } + } + + long nStepCount = GetGradientSteps( rGradient, aRect, true/*bMtf*/ ); + + // minimal three steps and maximal as max color steps + long nAbsRedSteps = std::abs( nEndRed - nStartRed ); + long nAbsGreenSteps = std::abs( nEndGreen - nStartGreen ); + long nAbsBlueSteps = std::abs( nEndBlue - nStartBlue ); + long nMaxColorSteps = std::max( nAbsRedSteps , nAbsGreenSteps ); + nMaxColorSteps = std::max( nMaxColorSteps, nAbsBlueSteps ); + long nSteps = std::min( nStepCount, nMaxColorSteps ); + if ( nSteps < 3) + { + nSteps = 3; + } + + double fScanInc = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps); + double fGradientLine = static_cast<double>(aRect.Top()); + double fMirrorGradientLine = static_cast<double>(aMirrorRect.Bottom()); + + const double fStepsMinus1 = static_cast<double>(nSteps) - 1.0; + if ( !bLinear) + { + nSteps -= 1; // draw middle polygons as one polygon after loop to avoid gap + } + for ( long i = 0; i < nSteps; i++ ) + { + // linear interpolation of color + double fAlpha = static_cast<double>(i) / fStepsMinus1; + double fTempColor = static_cast<double>(nStartRed) * (1.0-fAlpha) + static_cast<double>(nEndRed) * fAlpha; + nRed = GetGradientColorValue(static_cast<long>(fTempColor)); + fTempColor = static_cast<double>(nStartGreen) * (1.0-fAlpha) + static_cast<double>(nEndGreen) * fAlpha; + nGreen = GetGradientColorValue(static_cast<long>(fTempColor)); + fTempColor = static_cast<double>(nStartBlue) * (1.0-fAlpha) + static_cast<double>(nEndBlue) * fAlpha; + nBlue = GetGradientColorValue(static_cast<long>(fTempColor)); + + mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); + + // Polygon for this color step + aRect.SetTop( static_cast<long>( fGradientLine + static_cast<double>(i) * fScanInc ) ); + aRect.SetBottom( static_cast<long>( fGradientLine + ( static_cast<double>(i) + 1.0 ) * fScanInc ) ); + aPoly[0] = aRect.TopLeft(); + aPoly[1] = aRect.TopRight(); + aPoly[2] = aRect.BottomRight(); + aPoly[3] = aRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) ); + + if ( !bLinear ) + { + aMirrorRect.SetBottom( static_cast<long>( fMirrorGradientLine - static_cast<double>(i) * fScanInc ) ); + aMirrorRect.SetTop( static_cast<long>( fMirrorGradientLine - (static_cast<double>(i) + 1.0)* fScanInc ) ); + aPoly[0] = aMirrorRect.TopLeft(); + aPoly[1] = aMirrorRect.TopRight(); + aPoly[2] = aMirrorRect.BottomRight(); + aPoly[3] = aMirrorRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) ); + } + } + if ( bLinear) + return; + + // draw middle polygon with end color + nRed = GetGradientColorValue(nEndRed); + nGreen = GetGradientColorValue(nEndGreen); + nBlue = GetGradientColorValue(nEndBlue); + + mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); + + aRect.SetTop( static_cast<long>( fGradientLine + static_cast<double>(nSteps) * fScanInc ) ); + aRect.SetBottom( static_cast<long>( fMirrorGradientLine - static_cast<double>(nSteps) * fScanInc ) ); + aPoly[0] = aRect.TopLeft(); + aPoly[1] = aRect.TopRight(); + aPoly[2] = aRect.BottomRight(); + aPoly[3] = aRect.BottomLeft(); + aPoly.Rotate( aCenter, nAngle ); + + mpMetaFile->AddAction( new MetaPolygonAction( aPoly ) ); + +} + +void OutputDevice::DrawComplexGradientToMetafile( const tools::Rectangle& rRect, + const Gradient& rGradient ) +{ + assert(!is_double_buffered_window()); + + // Determine if we output via Polygon or PolyPolygon + // For all rasteroperations other than Overpaint always use PolyPolygon, + // as we will get wrong results if we output multiple times on top of each other. + // Also for printers always use PolyPolygon, as not all printers + // can print polygons on top of each other. + + std::unique_ptr<tools::PolyPolygon> xPolyPoly; + tools::Rectangle aRect; + Point aCenter; + Color aStartCol( rGradient.GetStartColor() ); + Color aEndCol( rGradient.GetEndColor() ); + long nStartRed = ( static_cast<long>(aStartCol.GetRed()) * rGradient.GetStartIntensity() ) / 100; + long nStartGreen = ( static_cast<long>(aStartCol.GetGreen()) * rGradient.GetStartIntensity() ) / 100; + long nStartBlue = ( static_cast<long>(aStartCol.GetBlue()) * rGradient.GetStartIntensity() ) / 100; + long nEndRed = ( static_cast<long>(aEndCol.GetRed()) * rGradient.GetEndIntensity() ) / 100; + long nEndGreen = ( static_cast<long>(aEndCol.GetGreen()) * rGradient.GetEndIntensity() ) / 100; + long nEndBlue = ( static_cast<long>(aEndCol.GetBlue()) * rGradient.GetEndIntensity() ) / 100; + long nRedSteps = nEndRed - nStartRed; + long nGreenSteps = nEndGreen - nStartGreen; + long nBlueSteps = nEndBlue - nStartBlue; + sal_uInt16 nAngle = rGradient.GetAngle() % 3600; + + rGradient.GetBoundRect( rRect, aRect, aCenter ); + + xPolyPoly.reset(new tools::PolyPolygon( 2 )); + + // last parameter - true if complex gradient, false if linear + long nStepCount = GetGradientSteps( rGradient, rRect, true, true ); + + // at least three steps and at most the number of colour differences + long nSteps = std::max( nStepCount, 2L ); + long nCalcSteps = std::abs( nRedSteps ); + long nTempSteps = std::abs( nGreenSteps ); + if ( nTempSteps > nCalcSteps ) + nCalcSteps = nTempSteps; + nTempSteps = std::abs( nBlueSteps ); + if ( nTempSteps > nCalcSteps ) + nCalcSteps = nTempSteps; + if ( nCalcSteps < nSteps ) + nSteps = nCalcSteps; + if ( !nSteps ) + nSteps = 1; + + // determine output limits and stepsizes for all directions + tools::Polygon aPoly; + double fScanLeft = aRect.Left(); + double fScanTop = aRect.Top(); + double fScanRight = aRect.Right(); + double fScanBottom = aRect.Bottom(); + double fScanIncX = static_cast<double>(aRect.GetWidth()) / static_cast<double>(nSteps) * 0.5; + double fScanIncY = static_cast<double>(aRect.GetHeight()) / static_cast<double>(nSteps) * 0.5; + + // all gradients are rendered as nested rectangles which shrink + // equally in each dimension - except for 'square' gradients + // which shrink to a central vertex but are not per-se square. + if( rGradient.GetStyle() != GradientStyle::Square ) + { + fScanIncY = std::min( fScanIncY, fScanIncX ); + fScanIncX = fScanIncY; + } + sal_uInt8 nRed = static_cast<sal_uInt8>(nStartRed), nGreen = static_cast<sal_uInt8>(nStartGreen), nBlue = static_cast<sal_uInt8>(nStartBlue); + bool bPaintLastPolygon( false ); // #107349# Paint last polygon only if loop has generated any output + + mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); + + aPoly = rRect; + xPolyPoly->Insert( aPoly ); + xPolyPoly->Insert( aPoly ); + + // loop to output Polygon/PolyPolygon sequentially + for( long i = 1; i < nSteps; i++ ) + { + // calculate new Polygon + fScanLeft += fScanIncX; + aRect.SetLeft( static_cast<long>( fScanLeft ) ); + fScanTop += fScanIncY; + aRect.SetTop( static_cast<long>( fScanTop ) ); + fScanRight -= fScanIncX; + aRect.SetRight( static_cast<long>( fScanRight ) ); + fScanBottom -= fScanIncY; + aRect.SetBottom( static_cast<long>( fScanBottom ) ); + + if( ( aRect.GetWidth() < 2 ) || ( aRect.GetHeight() < 2 ) ) + break; + + if( rGradient.GetStyle() == GradientStyle::Radial || rGradient.GetStyle() == GradientStyle::Elliptical ) + aPoly = tools::Polygon( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 ); + else + aPoly = tools::Polygon( aRect ); + + aPoly.Rotate( aCenter, nAngle ); + + // adapt colour accordingly + const long nStepIndex = ( xPolyPoly ? i : ( i + 1 ) ); + nRed = GetGradientColorValue( nStartRed + ( ( nRedSteps * nStepIndex ) / nSteps ) ); + nGreen = GetGradientColorValue( nStartGreen + ( ( nGreenSteps * nStepIndex ) / nSteps ) ); + nBlue = GetGradientColorValue( nStartBlue + ( ( nBlueSteps * nStepIndex ) / nSteps ) ); + + bPaintLastPolygon = true; // #107349# Paint last polygon only if loop has generated any output + + xPolyPoly->Replace( xPolyPoly->GetObject( 1 ), 0 ); + xPolyPoly->Replace( aPoly, 1 ); + + mpMetaFile->AddAction( new MetaPolyPolygonAction( *xPolyPoly ) ); + + // #107349# Set fill color _after_ geometry painting: + // xPolyPoly's geometry is the band from last iteration's + // aPoly to current iteration's aPoly. The window outdev + // path (see else below), on the other hand, paints the + // full aPoly. Thus, here, we're painting the band before + // the one painted in the window outdev path below. To get + // matching colors, have to delay color setting here. + mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); + } + + const tools::Polygon& rPoly = xPolyPoly->GetObject( 1 ); + + if( !rPoly.GetBoundRect().IsEmpty() ) + { + // #107349# Paint last polygon with end color only if loop + // has generated output. Otherwise, the current + // (i.e. start) color is taken, to generate _any_ output. + if( bPaintLastPolygon ) + { + nRed = GetGradientColorValue( nEndRed ); + nGreen = GetGradientColorValue( nEndGreen ); + nBlue = GetGradientColorValue( nEndBlue ); + } + + mpMetaFile->AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); + mpMetaFile->AddAction( new MetaPolygonAction( rPoly ) ); + } +} + +long OutputDevice::GetGradientStepCount( long nMinRect ) +{ + long nInc = (nMinRect < 50) ? 2 : 4; + + return nInc; +} + +long OutputDevice::GetGradientSteps( const Gradient& rGradient, const tools::Rectangle& rRect, bool bMtf, bool bComplex ) +{ + // calculate step count + long nStepCount = rGradient.GetSteps(); + long nMinRect; + + // generate nStepCount, if not passed + if (bComplex) + nMinRect = std::min( rRect.GetWidth(), rRect.GetHeight() ); + else + nMinRect = rRect.GetHeight(); + + if ( !nStepCount ) + { + long nInc; + + nInc = GetGradientStepCount (nMinRect); + if ( !nInc || bMtf ) + nInc = 1; + nStepCount = nMinRect / nInc; + } + + return nStepCount; +} + +Color OutputDevice::GetSingleColorGradientFill() +{ + Color aColor; + + // we should never call on this function if any of these aren't set! + assert( mnDrawMode & ( DrawModeFlags::BlackGradient | DrawModeFlags::WhiteGradient | DrawModeFlags::SettingsGradient) ); + + if ( mnDrawMode & DrawModeFlags::BlackGradient ) + aColor = COL_BLACK; + else if ( mnDrawMode & DrawModeFlags::WhiteGradient ) + aColor = COL_WHITE; + else if ( mnDrawMode & DrawModeFlags::SettingsGradient ) + aColor = GetSettings().GetStyleSettings().GetWindowColor(); + + return aColor; +} + +void OutputDevice::SetGrayscaleColors( Gradient &rGradient ) +{ + // this should only be called with the drawing mode is for grayscale gradients + assert ( mnDrawMode & DrawModeFlags::GrayGradient ); + + Color aStartCol( rGradient.GetStartColor() ); + Color aEndCol( rGradient.GetEndColor() ); + + if ( mnDrawMode & DrawModeFlags::GrayGradient ) + { + sal_uInt8 cStartLum = aStartCol.GetLuminance(), cEndLum = aEndCol.GetLuminance(); + aStartCol = Color( cStartLum, cStartLum, cStartLum ); + aEndCol = Color( cEndLum, cEndLum, cEndLum ); + } + + rGradient.SetStartColor( aStartCol ); + rGradient.SetEndColor( aEndCol ); +} + +void OutputDevice::AddGradientActions( const tools::Rectangle& rRect, const Gradient& rGradient, + GDIMetaFile& rMtf ) +{ + + tools::Rectangle aRect( rRect ); + + aRect.Justify(); + + // do nothing if the rectangle is empty + if ( aRect.IsEmpty() ) + return; + + Gradient aGradient( rGradient ); + GDIMetaFile* pOldMtf = mpMetaFile; + + mpMetaFile = &rMtf; + mpMetaFile->AddAction( new MetaPushAction( PushFlags::ALL ) ); + mpMetaFile->AddAction( new MetaISectRectClipRegionAction( aRect ) ); + mpMetaFile->AddAction( new MetaLineColorAction( Color(), false ) ); + + // because we draw with no border line, we have to expand gradient + // rect to avoid missing lines on the right and bottom edge + aRect.AdjustLeft( -1 ); + aRect.AdjustTop( -1 ); + aRect.AdjustRight( 1 ); + aRect.AdjustBottom( 1 ); + + // calculate step count if necessary + if ( !aGradient.GetSteps() ) + aGradient.SetSteps( GRADIENT_DEFAULT_STEPCOUNT ); + + if( aGradient.GetStyle() == GradientStyle::Linear || aGradient.GetStyle() == GradientStyle::Axial ) + DrawLinearGradientToMetafile( aRect, aGradient ); + else + DrawComplexGradientToMetafile( aRect, aGradient ); + + mpMetaFile->AddAction( new MetaPopAction() ); + mpMetaFile = pOldMtf; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/hatch.cxx b/vcl/source/outdev/hatch.cxx new file mode 100644 index 000000000..c5cba1277 --- /dev/null +++ b/vcl/source/outdev/hatch.cxx @@ -0,0 +1,409 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cassert> + +#include <osl/diagnose.h> +#include <tools/line.hxx> +#include <tools/helpers.hxx> + +#include <vcl/hatch.hxx> +#include <vcl/metaact.hxx> +#include <vcl/settings.hxx> +#include <vcl/outdev.hxx> +#include <vcl/virdev.hxx> + +#include <salgdi.hxx> + +#include <memory> + +#define HATCH_MAXPOINTS 1024 + +extern "C" { + +static int HatchCmpFnc( const void* p1, const void* p2 ) +{ + const long nX1 = static_cast<Point const *>(p1)->X(); + const long nX2 = static_cast<Point const *>(p2)->X(); + const long nY1 = static_cast<Point const *>(p1)->Y(); + const long nY2 = static_cast<Point const *>(p2)->Y(); + + return ( nX1 > nX2 ? 1 : nX1 == nX2 ? nY1 > nY2 ? 1: nY1 == nY2 ? 0 : -1 : -1 ); +} + +} + +void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch ) +{ + assert(!is_double_buffered_window()); + + Hatch aHatch( rHatch ); + + if ( mnDrawMode & ( DrawModeFlags::BlackLine | DrawModeFlags::WhiteLine | + DrawModeFlags::GrayLine | + DrawModeFlags::SettingsLine ) ) + { + Color aColor( rHatch.GetColor() ); + + if ( mnDrawMode & DrawModeFlags::BlackLine ) + aColor = COL_BLACK; + else if ( mnDrawMode & DrawModeFlags::WhiteLine ) + aColor = COL_WHITE; + else if ( mnDrawMode & DrawModeFlags::GrayLine ) + { + const sal_uInt8 cLum = aColor.GetLuminance(); + aColor = Color( cLum, cLum, cLum ); + } + else if( mnDrawMode & DrawModeFlags::SettingsLine ) + { + aColor = GetSettings().GetStyleSettings().GetFontColor(); + } + + aHatch.SetColor( aColor ); + } + + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaHatchAction( rPolyPoly, aHatch ) ); + + if( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + if( !mpGraphics && !AcquireGraphics() ) + return; + + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return; + + if( rPolyPoly.Count() ) + { + tools::PolyPolygon aPolyPoly( LogicToPixel( rPolyPoly ) ); + GDIMetaFile* pOldMetaFile = mpMetaFile; + bool bOldMap = mbMap; + + aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME ); + aHatch.SetDistance( ImplLogicWidthToDevicePixel( aHatch.GetDistance() ) ); + + mpMetaFile = nullptr; + EnableMapMode( false ); + Push( PushFlags::LINECOLOR ); + SetLineColor( aHatch.GetColor() ); + InitLineColor(); + DrawHatch( aPolyPoly, aHatch, false ); + Pop(); + EnableMapMode( bOldMap ); + mpMetaFile = pOldMetaFile; + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawHatch( rPolyPoly, rHatch ); +} + +void OutputDevice::AddHatchActions( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch, + GDIMetaFile& rMtf ) +{ + + tools::PolyPolygon aPolyPoly( rPolyPoly ); + aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME | PolyOptimizeFlags::CLOSE ); + + if( aPolyPoly.Count() ) + { + GDIMetaFile* pOldMtf = mpMetaFile; + + mpMetaFile = &rMtf; + mpMetaFile->AddAction( new MetaPushAction( PushFlags::ALL ) ); + mpMetaFile->AddAction( new MetaLineColorAction( rHatch.GetColor(), true ) ); + DrawHatch( aPolyPoly, rHatch, true ); + mpMetaFile->AddAction( new MetaPopAction() ); + mpMetaFile = pOldMtf; + } +} + +void OutputDevice::DrawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch, bool bMtf ) +{ + assert(!is_double_buffered_window()); + + if(rPolyPoly.Count()) + { + // #i115630# DrawHatch does not work with beziers included in the polypolygon, take care of that + bool bIsCurve(false); + + for(sal_uInt16 a(0); !bIsCurve && a < rPolyPoly.Count(); a++) + { + if(rPolyPoly[a].HasFlags()) + { + bIsCurve = true; + } + } + + if(bIsCurve) + { + OSL_ENSURE(false, "DrawHatch does *not* support curves, falling back to AdaptiveSubdivide()..."); + tools::PolyPolygon aPolyPoly; + + rPolyPoly.AdaptiveSubdivide(aPolyPoly); + DrawHatch(aPolyPoly, rHatch, bMtf); + } + else + { + tools::Rectangle aRect( rPolyPoly.GetBoundRect() ); + const long nLogPixelWidth = ImplDevicePixelToLogicWidth( 1 ); + const long nWidth = ImplDevicePixelToLogicWidth( std::max( ImplLogicWidthToDevicePixel( rHatch.GetDistance() ), 3L ) ); + std::unique_ptr<Point[]> pPtBuffer(new Point[ HATCH_MAXPOINTS ]); + Point aPt1, aPt2, aEndPt1; + Size aInc; + + // Single hatch + aRect.AdjustLeft( -nLogPixelWidth ); aRect.AdjustTop( -nLogPixelWidth ); aRect.AdjustRight(nLogPixelWidth ); aRect.AdjustBottom(nLogPixelWidth ); + CalcHatchValues( aRect, nWidth, rHatch.GetAngle(), aPt1, aPt2, aInc, aEndPt1 ); + do + { + DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf ); + aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() ); + aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() ); + } + while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) ); + + if( ( rHatch.GetStyle() == HatchStyle::Double ) || ( rHatch.GetStyle() == HatchStyle::Triple ) ) + { + // Double hatch + CalcHatchValues( aRect, nWidth, rHatch.GetAngle() + 900, aPt1, aPt2, aInc, aEndPt1 ); + do + { + DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf ); + aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() ); + aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() ); + } + while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) ); + + if( rHatch.GetStyle() == HatchStyle::Triple ) + { + // Triple hatch + CalcHatchValues( aRect, nWidth, rHatch.GetAngle() + 450, aPt1, aPt2, aInc, aEndPt1 ); + do + { + DrawHatchLine( tools::Line( aPt1, aPt2 ), rPolyPoly, pPtBuffer.get(), bMtf ); + aPt1.AdjustX(aInc.Width() ); aPt1.AdjustY(aInc.Height() ); + aPt2.AdjustX(aInc.Width() ); aPt2.AdjustY(aInc.Height() ); + } + while( ( aPt1.X() <= aEndPt1.X() ) && ( aPt1.Y() <= aEndPt1.Y() ) ); + } + } + } + } +} + +void OutputDevice::CalcHatchValues( const tools::Rectangle& rRect, long nDist, sal_uInt16 nAngle10, + Point& rPt1, Point& rPt2, Size& rInc, Point& rEndPt1 ) +{ + Point aRef; + long nAngle = nAngle10 % 1800; + long nOffset = 0; + + if( nAngle > 900 ) + nAngle -= 1800; + + aRef = ( !IsRefPoint() ? rRect.TopLeft() : GetRefPoint() ); + + if( 0 == nAngle ) + { + rInc = Size( 0, nDist ); + rPt1 = rRect.TopLeft(); + rPt2 = rRect.TopRight(); + rEndPt1 = rRect.BottomLeft(); + + if( aRef.Y() <= rRect.Top() ) + nOffset = ( ( rRect.Top() - aRef.Y() ) % nDist ); + else + nOffset = ( nDist - ( ( aRef.Y() - rRect.Top() ) % nDist ) ); + + rPt1.AdjustY( -nOffset ); + rPt2.AdjustY( -nOffset ); + } + else if( 900 == nAngle ) + { + rInc = Size( nDist, 0 ); + rPt1 = rRect.TopLeft(); + rPt2 = rRect.BottomLeft(); + rEndPt1 = rRect.TopRight(); + + if( aRef.X() <= rRect.Left() ) + nOffset = ( rRect.Left() - aRef.X() ) % nDist; + else + nOffset = nDist - ( ( aRef.X() - rRect.Left() ) % nDist ); + + rPt1.AdjustX( -nOffset ); + rPt2.AdjustX( -nOffset ); + } + else if( nAngle >= -450 && nAngle <= 450 ) + { + const double fAngle = F_PI1800 * labs( nAngle ); + const double fTan = tan( fAngle ); + const long nYOff = FRound( ( rRect.Right() - rRect.Left() ) * fTan ); + long nPY; + + nDist = FRound( nDist / cos( fAngle ) ); + rInc = Size( 0, nDist ); + + if( nAngle > 0 ) + { + rPt1 = rRect.TopLeft(); + rPt2 = Point( rRect.Right(), rRect.Top() - nYOff ); + rEndPt1 = Point( rRect.Left(), rRect.Bottom() + nYOff ); + nPY = FRound( aRef.Y() - ( ( rPt1.X() - aRef.X() ) * fTan ) ); + } + else + { + rPt1 = rRect.TopRight(); + rPt2 = Point( rRect.Left(), rRect.Top() - nYOff ); + rEndPt1 = Point( rRect.Right(), rRect.Bottom() + nYOff ); + nPY = FRound( aRef.Y() + ( ( rPt1.X() - aRef.X() ) * fTan ) ); + } + + if( nPY <= rPt1.Y() ) + nOffset = ( rPt1.Y() - nPY ) % nDist; + else + nOffset = nDist - ( ( nPY - rPt1.Y() ) % nDist ); + + rPt1.AdjustY( -nOffset ); + rPt2.AdjustY( -nOffset ); + } + else + { + const double fAngle = F_PI1800 * labs( nAngle ); + const double fTan = tan( fAngle ); + const long nXOff = FRound( ( rRect.Bottom() - rRect.Top() ) / fTan ); + long nPX; + + nDist = FRound( nDist / sin( fAngle ) ); + rInc = Size( nDist, 0 ); + + if( nAngle > 0 ) + { + rPt1 = rRect.TopLeft(); + rPt2 = Point( rRect.Left() - nXOff, rRect.Bottom() ); + rEndPt1 = Point( rRect.Right() + nXOff, rRect.Top() ); + nPX = FRound( aRef.X() - ( ( rPt1.Y() - aRef.Y() ) / fTan ) ); + } + else + { + rPt1 = rRect.BottomLeft(); + rPt2 = Point( rRect.Left() - nXOff, rRect.Top() ); + rEndPt1 = Point( rRect.Right() + nXOff, rRect.Bottom() ); + nPX = FRound( aRef.X() + ( ( rPt1.Y() - aRef.Y() ) / fTan ) ); + } + + if( nPX <= rPt1.X() ) + nOffset = ( rPt1.X() - nPX ) % nDist; + else + nOffset = nDist - ( ( nPX - rPt1.X() ) % nDist ); + + rPt1.AdjustX( -nOffset ); + rPt2.AdjustX( -nOffset ); + } +} + +void OutputDevice::DrawHatchLine( const tools::Line& rLine, const tools::PolyPolygon& rPolyPoly, + Point* pPtBuffer, bool bMtf ) +{ + assert(!is_double_buffered_window()); + + double fX, fY; + long nAdd, nPCounter = 0; + + for( long nPoly = 0, nPolyCount = rPolyPoly.Count(); nPoly < nPolyCount; nPoly++ ) + { + const tools::Polygon& rPoly = rPolyPoly[ static_cast<sal_uInt16>(nPoly) ]; + + if( rPoly.GetSize() > 1 ) + { + tools::Line aCurSegment( rPoly[ 0 ], Point() ); + + for( long i = 1, nCount = rPoly.GetSize(); i <= nCount; i++ ) + { + aCurSegment.SetEnd( rPoly[ static_cast<sal_uInt16>( i % nCount ) ] ); + nAdd = 0; + + if( rLine.Intersection( aCurSegment, fX, fY ) ) + { + if( ( fabs( fX - aCurSegment.GetStart().X() ) <= 0.0000001 ) && + ( fabs( fY - aCurSegment.GetStart().Y() ) <= 0.0000001 ) ) + { + const tools::Line aPrevSegment( rPoly[ static_cast<sal_uInt16>( ( i > 1 ) ? ( i - 2 ) : ( nCount - 1 ) ) ], aCurSegment.GetStart() ); + const double fPrevDistance = rLine.GetDistance( aPrevSegment.GetStart() ); + const double fCurDistance = rLine.GetDistance( aCurSegment.GetEnd() ); + + if( ( fPrevDistance <= 0.0 && fCurDistance > 0.0 ) || + ( fPrevDistance > 0.0 && fCurDistance < 0.0 ) ) + { + nAdd = 1; + } + } + else if( ( fabs( fX - aCurSegment.GetEnd().X() ) <= 0.0000001 ) && + ( fabs( fY - aCurSegment.GetEnd().Y() ) <= 0.0000001 ) ) + { + const tools::Line aNextSegment( aCurSegment.GetEnd(), rPoly[ static_cast<sal_uInt16>( ( i + 1 ) % nCount ) ] ); + + if( ( fabs( rLine.GetDistance( aNextSegment.GetEnd() ) ) <= 0.0000001 ) && + ( rLine.GetDistance( aCurSegment.GetStart() ) > 0.0 ) ) + { + nAdd = 1; + } + } + else + nAdd = 1; + + if( nAdd ) + pPtBuffer[ nPCounter++ ] = Point( FRound( fX ), FRound( fY ) ); + } + + aCurSegment.SetStart( aCurSegment.GetEnd() ); + } + } + } + + if( nPCounter > 1 ) + { + qsort( pPtBuffer, nPCounter, sizeof( Point ), HatchCmpFnc ); + + if( nPCounter & 1 ) + nPCounter--; + + if( bMtf ) + { + for( long i = 0; i < nPCounter; i += 2 ) + mpMetaFile->AddAction( new MetaLineAction( pPtBuffer[ i ], pPtBuffer[ i + 1 ] ) ); + } + else + { + for( long i = 0; i < nPCounter; i += 2 ) + DrawHatchLine_DrawLine(pPtBuffer[i], pPtBuffer[i+1]); + } + } +} + +void OutputDevice::DrawHatchLine_DrawLine(const Point& rStartPoint, const Point& rEndPoint) +{ + Point aPt1{ImplLogicToDevicePixel(rStartPoint)}, aPt2{ImplLogicToDevicePixel(rEndPoint)}; + mpGraphics->DrawLine(aPt1.X(), aPt1.Y(), aPt2.X(), aPt2.Y(), this); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/line.cxx b/vcl/source/outdev/line.cxx new file mode 100644 index 000000000..f451b6e1f --- /dev/null +++ b/vcl/source/outdev/line.cxx @@ -0,0 +1,304 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cassert> +#include <numeric> + +#include <vcl/gdimtf.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/metaact.hxx> +#include <vcl/outdev.hxx> +#include <vcl/virdev.hxx> + +#include <salgdi.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dlinegeometry.hxx> + +void OutputDevice::DrawLine( const Point& rStartPt, const Point& rEndPt, + const LineInfo& rLineInfo ) +{ + assert(!is_double_buffered_window()); + + if ( rLineInfo.IsDefault() ) + { + DrawLine( rStartPt, rEndPt ); + return; + } + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaLineAction( rStartPt, rEndPt, rLineInfo ) ); + + if ( !IsDeviceOutputNecessary() || !mbLineColor || ( LineStyle::NONE == rLineInfo.GetStyle() ) || ImplIsRecordLayout() ) + return; + + if( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + const Point aStartPt( ImplLogicToDevicePixel( rStartPt ) ); + const Point aEndPt( ImplLogicToDevicePixel( rEndPt ) ); + const LineInfo aInfo( ImplLogicToDevicePixel( rLineInfo ) ); + const bool bDashUsed(LineStyle::Dash == aInfo.GetStyle()); + const bool bLineWidthUsed(aInfo.GetWidth() > 1); + + if ( mbInitLineColor ) + InitLineColor(); + + if(bDashUsed || bLineWidthUsed) + { + basegfx::B2DPolygon aLinePolygon; + aLinePolygon.append(basegfx::B2DPoint(aStartPt.X(), aStartPt.Y())); + aLinePolygon.append(basegfx::B2DPoint(aEndPt.X(), aEndPt.Y())); + + drawLine( basegfx::B2DPolyPolygon(aLinePolygon), aInfo ); + } + else + { + mpGraphics->DrawLine( aStartPt.X(), aStartPt.Y(), aEndPt.X(), aEndPt.Y(), this ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawLine( rStartPt, rEndPt, rLineInfo ); +} + +void OutputDevice::DrawLine( const Point& rStartPt, const Point& rEndPt ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaLineAction( rStartPt, rEndPt ) ); + + if ( !IsDeviceOutputNecessary() || !mbLineColor || ImplIsRecordLayout() ) + return; + + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + // #i101598# support AA and snap for lines, too + if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) + && mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) + && RasterOp::OverPaint == GetRasterOp() + && IsLineColor()) + { + // at least transform with double precision to device coordinates; this will + // avoid pixel snap of single, appended lines + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation()); + basegfx::B2DPolygon aB2DPolyLine; + + aB2DPolyLine.append(basegfx::B2DPoint(rStartPt.X(), rStartPt.Y())); + aB2DPolyLine.append(basegfx::B2DPoint(rEndPt.X(), rEndPt.Y())); + aB2DPolyLine.transform( aTransform ); + + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + if( mpGraphics->DrawPolyLine( + basegfx::B2DHomMatrix(), + aB2DPolyLine, + 0.0, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + this)) + { + return; + } + } + + const Point aStartPt(ImplLogicToDevicePixel(rStartPt)); + const Point aEndPt(ImplLogicToDevicePixel(rEndPt)); + + mpGraphics->DrawLine( aStartPt.X(), aStartPt.Y(), aEndPt.X(), aEndPt.Y(), this ); + + if( mpAlphaVDev ) + mpAlphaVDev->DrawLine( rStartPt, rEndPt ); +} + +void OutputDevice::drawLine( basegfx::B2DPolyPolygon aLinePolyPolygon, const LineInfo& rInfo ) +{ + const bool bTryAA((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) + && mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) + && RasterOp::OverPaint == GetRasterOp() + && IsLineColor()); + basegfx::B2DPolyPolygon aFillPolyPolygon; + const bool bDashUsed(LineStyle::Dash == rInfo.GetStyle()); + const bool bLineWidthUsed(rInfo.GetWidth() > 1); + + if(bDashUsed && aLinePolyPolygon.count()) + { + ::std::vector< double > fDotDashArray; + const double fDashLen(rInfo.GetDashLen()); + const double fDotLen(rInfo.GetDotLen()); + const double fDistance(rInfo.GetDistance()); + + for(sal_uInt16 a(0); a < rInfo.GetDashCount(); a++) + { + fDotDashArray.push_back(fDashLen); + fDotDashArray.push_back(fDistance); + } + + for(sal_uInt16 b(0); b < rInfo.GetDotCount(); b++) + { + fDotDashArray.push_back(fDotLen); + fDotDashArray.push_back(fDistance); + } + + const double fAccumulated(::std::accumulate(fDotDashArray.begin(), fDotDashArray.end(), 0.0)); + + if(fAccumulated > 0.0) + { + basegfx::B2DPolyPolygon aResult; + + for(auto const& rPolygon : aLinePolyPolygon) + { + basegfx::B2DPolyPolygon aLineTarget; + basegfx::utils::applyLineDashing( + rPolygon, + fDotDashArray, + &aLineTarget); + aResult.append(aLineTarget); + } + + aLinePolyPolygon = aResult; + } + } + + if(bLineWidthUsed && aLinePolyPolygon.count()) + { + const double fHalfLineWidth((rInfo.GetWidth() * 0.5) + 0.5); + + if(aLinePolyPolygon.areControlPointsUsed()) + { + // #i110768# When area geometry has to be created, do not + // use the fallback bezier decomposition inside createAreaGeometry, + // but one that is at least as good as ImplSubdivideBezier was. + // There, Polygon::AdaptiveSubdivide was used with default parameter + // 1.0 as quality index. + aLinePolyPolygon = basegfx::utils::adaptiveSubdivideByDistance(aLinePolyPolygon, 1.0); + } + + for(auto const& rPolygon : aLinePolyPolygon) + { + aFillPolyPolygon.append(basegfx::utils::createAreaGeometry( + rPolygon, + fHalfLineWidth, + rInfo.GetLineJoin(), + rInfo.GetLineCap())); + } + + aLinePolyPolygon.clear(); + } + + GDIMetaFile* pOldMetaFile = mpMetaFile; + mpMetaFile = nullptr; + + if(aLinePolyPolygon.count()) + { + for(auto const& rB2DPolygon : aLinePolyPolygon) + { + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + bool bDone(false); + + if(bTryAA) + { + bDone = mpGraphics->DrawPolyLine( + basegfx::B2DHomMatrix(), + rB2DPolygon, + 0.0, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + this); + } + + if(!bDone) + { + tools::Polygon aPolygon(rB2DPolygon); + mpGraphics->DrawPolyLine( + aPolygon.GetSize(), + reinterpret_cast<SalPoint*>(aPolygon.GetPointAry()), + this); + } + } + } + + if(aFillPolyPolygon.count()) + { + const Color aOldLineColor( maLineColor ); + const Color aOldFillColor( maFillColor ); + + SetLineColor(); + InitLineColor(); + SetFillColor( aOldLineColor ); + InitFillColor(); + + bool bDone(false); + + if(bTryAA) + { + bDone = mpGraphics->DrawPolyPolygon( + basegfx::B2DHomMatrix(), + aFillPolyPolygon, + 0.0, + this); + } + + if(!bDone) + { + for(auto const& rB2DPolygon : aFillPolyPolygon) + { + tools::Polygon aPolygon(rB2DPolygon); + + // need to subdivide, mpGraphics->DrawPolygon ignores curves + aPolygon.AdaptiveSubdivide(aPolygon); + mpGraphics->DrawPolygon(aPolygon.GetSize(), reinterpret_cast<const SalPoint*>(aPolygon.GetConstPointAry()), this); + } + } + + SetFillColor( aOldFillColor ); + SetLineColor( aOldLineColor ); + } + + mpMetaFile = pOldMetaFile; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/map.cxx b/vcl/source/outdev/map.cxx new file mode 100644 index 000000000..92d9e6322 --- /dev/null +++ b/vcl/source/outdev/map.cxx @@ -0,0 +1,1936 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <tools/bigint.hxx> +#include <tools/debug.hxx> +#include <vcl/cursor.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> +#include <vcl/wrkwin.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <svdata.hxx> +#include <window.h> +#include <outdev.h> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <o3tl/enumarray.hxx> + +// we don't actually handle units beyond, hence the zeros in the arrays +static const MapUnit s_MaxValidUnit = MapUnit::MapPixel; +static const o3tl::enumarray<MapUnit,long> aImplNumeratorAry = + { 1, 1, 5, 50, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 }; +static const o3tl::enumarray<MapUnit,long> aImplDenominatorAry = + { 2540, 254, 127, 127, 1000, 100, 10, 1, 72, 1440, 1, 0, 0, 0 }; + +/* +Reduces accuracy until it is a fraction (should become +ctor fraction once); we could also do this with BigInts +*/ + +static Fraction ImplMakeFraction( long nN1, long nN2, long nD1, long nD2 ) +{ + if( nD1 == 0 || nD2 == 0 ) //under these bad circumstances the following while loop will be endless + { + SAL_WARN("vcl.gdi", "Invalid parameter for ImplMakeFraction"); + return Fraction( 1, 1 ); + } + + long i = 1; + + if ( nN1 < 0 ) { i = -i; nN1 = -nN1; } + if ( nN2 < 0 ) { i = -i; nN2 = -nN2; } + if ( nD1 < 0 ) { i = -i; nD1 = -nD1; } + if ( nD2 < 0 ) { i = -i; nD2 = -nD2; } + // all positive; i sign + + Fraction aF = Fraction( i*nN1, nD1 ) * Fraction( nN2, nD2 ); + + while ( !aF.IsValid() ) { + if ( nN1 > nN2 ) + nN1 = (nN1 + 1) / 2; + else + nN2 = (nN2 + 1) / 2; + if ( nD1 > nD2 ) + nD1 = (nD1 + 1) / 2; + else + nD2 = (nD2 + 1) / 2; + + aF = Fraction( i*nN1, nD1 ) * Fraction( nN2, nD2 ); + } + + aF.ReduceInaccurate(32); + return aF; +} + +// Fraction.GetNumerator() +// Fraction.GetDenominator() > 0 +// rOutRes.nPixPerInch? > 0 +// rMapRes.nMapScNum? +// rMapRes.nMapScDenom? > 0 + +static void ImplCalcBigIntThreshold( long nDPIX, long nDPIY, + const ImplMapRes& rMapRes, + ImplThresholdRes& rThresRes ) +{ + if ( nDPIX && (LONG_MAX / nDPIX < std::abs( rMapRes.mnMapScNumX ) ) ) // #111139# avoid div by zero + { + rThresRes.mnThresLogToPixX = 0; + rThresRes.mnThresPixToLogX = 0; + } + else + { + // calculate thresholds for BigInt arithmetic + long nDenomHalfX = rMapRes.mnMapScDenomX / 2; + sal_uLong nDenomX = rMapRes.mnMapScDenomX; + long nProductX = nDPIX * rMapRes.mnMapScNumX; + + if ( !nProductX ) + rThresRes.mnThresLogToPixX = LONG_MAX; + else + rThresRes.mnThresLogToPixX = std::abs( (LONG_MAX - nDenomHalfX) / nProductX ); + + if ( !nDenomX ) + rThresRes.mnThresPixToLogX = LONG_MAX; + else if ( nProductX >= 0 ) + rThresRes.mnThresPixToLogX = static_cast<long>((sal_uLong(LONG_MAX) - static_cast<sal_uLong>( nProductX/2)) / nDenomX); + else + rThresRes.mnThresPixToLogX = static_cast<long>((sal_uLong(LONG_MAX) + static_cast<sal_uLong>(-nProductX/2)) / nDenomX); + } + + if ( nDPIY && (LONG_MAX / nDPIY < std::abs( rMapRes.mnMapScNumY ) ) ) // #111139# avoid div by zero + { + rThresRes.mnThresLogToPixY = 0; + rThresRes.mnThresPixToLogY = 0; + } + else + { + // calculate thresholds for BigInt arithmetic + long nDenomHalfY = rMapRes.mnMapScDenomY / 2; + sal_uLong nDenomY = rMapRes.mnMapScDenomY; + long nProductY = nDPIY * rMapRes.mnMapScNumY; + + if ( !nProductY ) + rThresRes.mnThresLogToPixY = LONG_MAX; + else + rThresRes.mnThresLogToPixY = std::abs( (LONG_MAX - nDenomHalfY) / nProductY ); + + if ( !nDenomY ) + rThresRes.mnThresPixToLogY = LONG_MAX; + else if ( nProductY >= 0 ) + rThresRes.mnThresPixToLogY = static_cast<long>((sal_uLong(LONG_MAX) - static_cast<sal_uLong>( nProductY/2)) / nDenomY); + else + rThresRes.mnThresPixToLogY = static_cast<long>((sal_uLong(LONG_MAX) + static_cast<sal_uLong>(-nProductY/2)) / nDenomY); + } + + rThresRes.mnThresLogToPixX /= 2; + rThresRes.mnThresLogToPixY /= 2; + rThresRes.mnThresPixToLogX /= 2; + rThresRes.mnThresPixToLogY /= 2; +} + +static void ImplCalcMapResolution( const MapMode& rMapMode, + long nDPIX, long nDPIY, ImplMapRes& rMapRes ) +{ + switch ( rMapMode.GetMapUnit() ) + { + case MapUnit::MapRelative: + break; + case MapUnit::Map100thMM: + rMapRes.mnMapScNumX = 1; + rMapRes.mnMapScDenomX = 2540; + rMapRes.mnMapScNumY = 1; + rMapRes.mnMapScDenomY = 2540; + break; + case MapUnit::Map10thMM: + rMapRes.mnMapScNumX = 1; + rMapRes.mnMapScDenomX = 254; + rMapRes.mnMapScNumY = 1; + rMapRes.mnMapScDenomY = 254; + break; + case MapUnit::MapMM: + rMapRes.mnMapScNumX = 5; // 10 + rMapRes.mnMapScDenomX = 127; // 254 + rMapRes.mnMapScNumY = 5; // 10 + rMapRes.mnMapScDenomY = 127; // 254 + break; + case MapUnit::MapCM: + rMapRes.mnMapScNumX = 50; // 100 + rMapRes.mnMapScDenomX = 127; // 254 + rMapRes.mnMapScNumY = 50; // 100 + rMapRes.mnMapScDenomY = 127; // 254 + break; + case MapUnit::Map1000thInch: + rMapRes.mnMapScNumX = 1; + rMapRes.mnMapScDenomX = 1000; + rMapRes.mnMapScNumY = 1; + rMapRes.mnMapScDenomY = 1000; + break; + case MapUnit::Map100thInch: + rMapRes.mnMapScNumX = 1; + rMapRes.mnMapScDenomX = 100; + rMapRes.mnMapScNumY = 1; + rMapRes.mnMapScDenomY = 100; + break; + case MapUnit::Map10thInch: + rMapRes.mnMapScNumX = 1; + rMapRes.mnMapScDenomX = 10; + rMapRes.mnMapScNumY = 1; + rMapRes.mnMapScDenomY = 10; + break; + case MapUnit::MapInch: + rMapRes.mnMapScNumX = 1; + rMapRes.mnMapScDenomX = 1; + rMapRes.mnMapScNumY = 1; + rMapRes.mnMapScDenomY = 1; + break; + case MapUnit::MapPoint: + rMapRes.mnMapScNumX = 1; + rMapRes.mnMapScDenomX = 72; + rMapRes.mnMapScNumY = 1; + rMapRes.mnMapScDenomY = 72; + break; + case MapUnit::MapTwip: + rMapRes.mnMapScNumX = 1; + rMapRes.mnMapScDenomX = 1440; + rMapRes.mnMapScNumY = 1; + rMapRes.mnMapScDenomY = 1440; + break; + case MapUnit::MapPixel: + rMapRes.mnMapScNumX = 1; + rMapRes.mnMapScDenomX = nDPIX; + rMapRes.mnMapScNumY = 1; + rMapRes.mnMapScDenomY = nDPIY; + break; + case MapUnit::MapSysFont: + case MapUnit::MapAppFont: + { + ImplSVData* pSVData = ImplGetSVData(); + if ( !pSVData->maGDIData.mnAppFontX ) + { + if (pSVData->maFrameData.mpFirstFrame) + vcl::Window::ImplInitAppFontData(pSVData->maFrameData.mpFirstFrame); + else + { + ScopedVclPtrInstance<WorkWindow> pWin( nullptr, 0 ); + vcl::Window::ImplInitAppFontData( pWin ); + } + } + rMapRes.mnMapScNumX = pSVData->maGDIData.mnAppFontX; + rMapRes.mnMapScDenomX = nDPIX * 40; + rMapRes.mnMapScNumY = pSVData->maGDIData.mnAppFontY; + rMapRes.mnMapScDenomY = nDPIY * 80; + } + break; + default: + OSL_FAIL( "unhandled MapUnit" ); + break; + } + + const Fraction& aScaleX = rMapMode.GetScaleX(); + const Fraction& aScaleY = rMapMode.GetScaleY(); + + // set offset according to MapMode + Point aOrigin = rMapMode.GetOrigin(); + if ( rMapMode.GetMapUnit() != MapUnit::MapRelative ) + { + rMapRes.mnMapOfsX = aOrigin.X(); + rMapRes.mnMapOfsY = aOrigin.Y(); + } + else + { + auto nXNumerator = aScaleX.GetNumerator(); + auto nYNumerator = aScaleY.GetNumerator(); + assert(nXNumerator != 0 && nYNumerator != 0); + + BigInt aX( rMapRes.mnMapOfsX ); + aX *= BigInt( aScaleX.GetDenominator() ); + if ( rMapRes.mnMapOfsX >= 0 ) + { + if (nXNumerator >= 0) + aX += BigInt(nXNumerator / 2); + else + aX -= BigInt((nXNumerator + 1) / 2); + } + else + { + if (nXNumerator >= 0 ) + aX -= BigInt((nXNumerator - 1) / 2); + else + aX += BigInt(nXNumerator / 2); + } + aX /= BigInt(nXNumerator); + rMapRes.mnMapOfsX = static_cast<long>(aX) + aOrigin.X(); + BigInt aY( rMapRes.mnMapOfsY ); + aY *= BigInt( aScaleY.GetDenominator() ); + if( rMapRes.mnMapOfsY >= 0 ) + { + if (nYNumerator >= 0) + aY += BigInt(nYNumerator / 2); + else + aY -= BigInt((nYNumerator + 1) / 2); + } + else + { + if (nYNumerator >= 0) + aY -= BigInt((nYNumerator - 1) / 2); + else + aY += BigInt(nYNumerator / 2); + } + aY /= BigInt(nYNumerator); + rMapRes.mnMapOfsY = static_cast<long>(aY) + aOrigin.Y(); + } + + // calculate scaling factor according to MapMode + // aTemp? = rMapRes.mnMapSc? * aScale? + Fraction aTempX = ImplMakeFraction( rMapRes.mnMapScNumX, + aScaleX.GetNumerator(), + rMapRes.mnMapScDenomX, + aScaleX.GetDenominator() ); + Fraction aTempY = ImplMakeFraction( rMapRes.mnMapScNumY, + aScaleY.GetNumerator(), + rMapRes.mnMapScDenomY, + aScaleY.GetDenominator() ); + rMapRes.mnMapScNumX = aTempX.GetNumerator(); + rMapRes.mnMapScDenomX = aTempX.GetDenominator(); + rMapRes.mnMapScNumY = aTempY.GetNumerator(); + rMapRes.mnMapScDenomY = aTempY.GetDenominator(); +} + +static void ImplCalcMapResolution( const MapMode& rMapMode, + long nDPIX, long nDPIY, + ImplMapRes& rMapRes, + ImplThresholdRes& rThresRes ) +{ + ImplCalcMapResolution( rMapMode, nDPIX, nDPIY, rMapRes ); + ImplCalcBigIntThreshold( nDPIX, nDPIY, rMapRes, rThresRes ); +} + +// #i75163# +void OutputDevice::ImplInvalidateViewTransform() +{ + if(!mpOutDevData) + return; + + if(mpOutDevData->mpViewTransform) + { + delete mpOutDevData->mpViewTransform; + mpOutDevData->mpViewTransform = nullptr; + } + + if(mpOutDevData->mpInverseViewTransform) + { + delete mpOutDevData->mpInverseViewTransform; + mpOutDevData->mpInverseViewTransform = nullptr; + } +} + +static long ImplLogicToPixel( long n, long nDPI, long nMapNum, long nMapDenom, + long nThres ) +{ + assert(nDPI > 0); +#if (SAL_TYPES_SIZEOFLONG < 8) + if( (+n < nThres) && (-n < nThres) ) + { + n *= nMapNum * nDPI; + if( nMapDenom != 1 ) + { + n = (2 * n) / nMapDenom; + if( n < 0 ) --n; else ++n; + n /= 2; + } + } + else +#else + (void) nThres; + assert(nMapNum >= 0); + assert(nMapNum == 0 || std::abs(n) < std::numeric_limits<long>::max() / nMapNum / nDPI); //detect overflows +#endif + { + sal_Int64 n64 = n; + n64 *= nMapNum; + n64 *= nDPI; + if( nMapDenom == 1 ) + n = static_cast<long>(n64); + else + { + n = static_cast<long>(2 * n64 / nMapDenom); + if( n < 0 ) --n; else ++n; + n /= 2; + } + } + return n; +} + +static long ImplPixelToLogic( long n, long nDPI, long nMapNum, long nMapDenom, + long nThres ) +{ + assert(nDPI > 0); + long nDenom = nDPI * nMapNum; + if (nDenom == 0) + { + return 0; + } + +#if (SAL_TYPES_SIZEOFLONG < 8) + if( (+n < nThres) && (-n < nThres) ) + n = (2 * n * nMapDenom) / nDenom; + else +#else + (void) nThres; +#endif + { + sal_Int64 n64 = n; + n64 *= nMapDenom; + n = static_cast<long>(2 * n64 / nDenom); + } + if( n < 0 ) --n; else ++n; + return (n / 2); +} + +long OutputDevice::ImplLogicXToDevicePixel( long nX ) const +{ + if ( !mbMap ) + return nX+mnOutOffX; + + return ImplLogicToPixel( nX + maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresLogToPixX )+mnOutOffX+mnOutOffOrigX; +} + +long OutputDevice::ImplLogicYToDevicePixel( long nY ) const +{ + if ( !mbMap ) + return nY+mnOutOffY; + + return ImplLogicToPixel( nY + maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresLogToPixY )+mnOutOffY+mnOutOffOrigY; +} + +long OutputDevice::ImplLogicWidthToDevicePixel( long nWidth ) const +{ + if ( !mbMap ) + return nWidth; + + return ImplLogicToPixel( nWidth, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresLogToPixX ); +} + +long OutputDevice::ImplLogicHeightToDevicePixel( long nHeight ) const +{ + if ( !mbMap ) + return nHeight; + + return ImplLogicToPixel( nHeight, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresLogToPixY ); +} + +float OutputDevice::ImplFloatLogicHeightToDevicePixel( float fLogicHeight) const +{ + if( !mbMap) + return fLogicHeight; + float fPixelHeight = (fLogicHeight * mnDPIY * maMapRes.mnMapScNumY) / maMapRes.mnMapScDenomY; + return fPixelHeight; +} + +long OutputDevice::ImplDevicePixelToLogicWidth( long nWidth ) const +{ + if ( !mbMap ) + return nWidth; + + return ImplPixelToLogic( nWidth, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresPixToLogX ); +} + +long OutputDevice::ImplDevicePixelToLogicHeight( long nHeight ) const +{ + if ( !mbMap ) + return nHeight; + + return ImplPixelToLogic( nHeight, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresPixToLogY ); +} + +Point OutputDevice::ImplLogicToDevicePixel( const Point& rLogicPt ) const +{ + if ( !mbMap ) + return Point( rLogicPt.X()+mnOutOffX, rLogicPt.Y()+mnOutOffY ); + + return Point( ImplLogicToPixel( rLogicPt.X() + maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresLogToPixX )+mnOutOffX+mnOutOffOrigX, + ImplLogicToPixel( rLogicPt.Y() + maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresLogToPixY )+mnOutOffY+mnOutOffOrigY ); +} + +Size OutputDevice::ImplLogicToDevicePixel( const Size& rLogicSize ) const +{ + if ( !mbMap ) + return rLogicSize; + + return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresLogToPixX ), + ImplLogicToPixel( rLogicSize.Height(), mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresLogToPixY ) ); +} + +tools::Rectangle OutputDevice::ImplLogicToDevicePixel( const tools::Rectangle& rLogicRect ) const +{ + if ( rLogicRect.IsEmpty() ) + return rLogicRect; + + if ( !mbMap ) + { + return tools::Rectangle( rLogicRect.Left()+mnOutOffX, rLogicRect.Top()+mnOutOffY, + rLogicRect.Right()+mnOutOffX, rLogicRect.Bottom()+mnOutOffY ); + } + + return tools::Rectangle( ImplLogicToPixel( rLogicRect.Left()+maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresLogToPixX )+mnOutOffX+mnOutOffOrigX, + ImplLogicToPixel( rLogicRect.Top()+maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresLogToPixY )+mnOutOffY+mnOutOffOrigY, + ImplLogicToPixel( rLogicRect.Right()+maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresLogToPixX )+mnOutOffX+mnOutOffOrigX, + ImplLogicToPixel( rLogicRect.Bottom()+maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresLogToPixY )+mnOutOffY+mnOutOffOrigY ); +} + +tools::Polygon OutputDevice::ImplLogicToDevicePixel( const tools::Polygon& rLogicPoly ) const +{ + if ( !mbMap && !mnOutOffX && !mnOutOffY ) + return rLogicPoly; + + sal_uInt16 i; + sal_uInt16 nPoints = rLogicPoly.GetSize(); + tools::Polygon aPoly( rLogicPoly ); + + // get pointer to Point-array (copy data) + const Point* pPointAry = aPoly.GetConstPointAry(); + + if ( mbMap ) + { + for ( i = 0; i < nPoints; i++ ) + { + const Point* pPt = &(pPointAry[i]); + Point aPt; + aPt.setX( ImplLogicToPixel( pPt->X()+maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresLogToPixX )+mnOutOffX+mnOutOffOrigX ); + aPt.setY( ImplLogicToPixel( pPt->Y()+maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresLogToPixY )+mnOutOffY+mnOutOffOrigY ); + aPoly[i] = aPt; + } + } + else + { + for ( i = 0; i < nPoints; i++ ) + { + Point aPt = pPointAry[i]; + aPt.AdjustX(mnOutOffX ); + aPt.AdjustY(mnOutOffY ); + aPoly[i] = aPt; + } + } + + return aPoly; +} + +tools::PolyPolygon OutputDevice::ImplLogicToDevicePixel( const tools::PolyPolygon& rLogicPolyPoly ) const +{ + if ( !mbMap && !mnOutOffX && !mnOutOffY ) + return rLogicPolyPoly; + + tools::PolyPolygon aPolyPoly( rLogicPolyPoly ); + sal_uInt16 nPoly = aPolyPoly.Count(); + for( sal_uInt16 i = 0; i < nPoly; i++ ) + { + tools::Polygon& rPoly = aPolyPoly[i]; + rPoly = ImplLogicToDevicePixel( rPoly ); + } + return aPolyPoly; +} + +LineInfo OutputDevice::ImplLogicToDevicePixel( const LineInfo& rLineInfo ) const +{ + LineInfo aInfo( rLineInfo ); + + if( aInfo.GetStyle() == LineStyle::Dash ) + { + if( aInfo.GetDotCount() && aInfo.GetDotLen() ) + aInfo.SetDotLen( std::max( ImplLogicWidthToDevicePixel( aInfo.GetDotLen() ), 1L ) ); + else + aInfo.SetDotCount( 0 ); + + if( aInfo.GetDashCount() && aInfo.GetDashLen() ) + aInfo.SetDashLen( std::max( ImplLogicWidthToDevicePixel( aInfo.GetDashLen() ), 1L ) ); + else + aInfo.SetDashCount( 0 ); + + aInfo.SetDistance( ImplLogicWidthToDevicePixel( aInfo.GetDistance() ) ); + + if( ( !aInfo.GetDashCount() && !aInfo.GetDotCount() ) || !aInfo.GetDistance() ) + aInfo.SetStyle( LineStyle::Solid ); + } + + aInfo.SetWidth( ImplLogicWidthToDevicePixel( aInfo.GetWidth() ) ); + + return aInfo; +} + +tools::Rectangle OutputDevice::ImplDevicePixelToLogic( const tools::Rectangle& rPixelRect ) const +{ + if ( rPixelRect.IsEmpty() ) + return rPixelRect; + + if ( !mbMap ) + { + return tools::Rectangle( rPixelRect.Left()-mnOutOffX, rPixelRect.Top()-mnOutOffY, + rPixelRect.Right()-mnOutOffX, rPixelRect.Bottom()-mnOutOffY ); + } + + return tools::Rectangle( ImplPixelToLogic( rPixelRect.Left()-mnOutOffX-mnOutOffOrigX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresPixToLogX )-maMapRes.mnMapOfsX, + ImplPixelToLogic( rPixelRect.Top()-mnOutOffY-mnOutOffOrigY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresPixToLogY )-maMapRes.mnMapOfsY, + ImplPixelToLogic( rPixelRect.Right()-mnOutOffX-mnOutOffOrigX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresPixToLogX )-maMapRes.mnMapOfsX, + ImplPixelToLogic( rPixelRect.Bottom()-mnOutOffY-mnOutOffOrigY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresPixToLogY )-maMapRes.mnMapOfsY ); +} + +vcl::Region OutputDevice::ImplPixelToDevicePixel( const vcl::Region& rRegion ) const +{ + if ( !mnOutOffX && !mnOutOffY ) + return rRegion; + + vcl::Region aRegion( rRegion ); + aRegion.Move( mnOutOffX+mnOutOffOrigX, mnOutOffY+mnOutOffOrigY ); + return aRegion; +} + +void OutputDevice::EnableMapMode( bool bEnable ) +{ + mbMap = bEnable; + + if( mpAlphaVDev ) + mpAlphaVDev->EnableMapMode( bEnable ); +} + +void OutputDevice::SetMapMode() +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaMapModeAction( MapMode() ) ); + + if ( mbMap || !maMapMode.IsDefault() ) + { + mbMap = false; + maMapMode = MapMode(); + + // create new objects (clip region are not re-scaled) + mbNewFont = true; + mbInitFont = true; + ImplInitMapModeObjects(); + + // #106426# Adapt logical offset when changing mapmode + mnOutOffLogicX = mnOutOffOrigX; // no mapping -> equal offsets + mnOutOffLogicY = mnOutOffOrigY; + + // #i75163# + ImplInvalidateViewTransform(); + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetMapMode(); +} + +void OutputDevice::SetMapMode( const MapMode& rNewMapMode ) +{ + + bool bRelMap = (rNewMapMode.GetMapUnit() == MapUnit::MapRelative); + + if ( mpMetaFile ) + { + mpMetaFile->AddAction( new MetaMapModeAction( rNewMapMode ) ); + } + + // do nothing if MapMode was not changed + if ( maMapMode == rNewMapMode ) + return; + + if( mpAlphaVDev ) + mpAlphaVDev->SetMapMode( rNewMapMode ); + + // if default MapMode calculate nothing + bool bOldMap = mbMap; + mbMap = !rNewMapMode.IsDefault(); + if ( mbMap ) + { + // if only the origin is converted, do not scale new + if ( (rNewMapMode.GetMapUnit() == maMapMode.GetMapUnit()) && + (rNewMapMode.GetScaleX() == maMapMode.GetScaleX()) && + (rNewMapMode.GetScaleY() == maMapMode.GetScaleY()) && + (bOldMap == mbMap) ) + { + // set offset + Point aOrigin = rNewMapMode.GetOrigin(); + maMapRes.mnMapOfsX = aOrigin.X(); + maMapRes.mnMapOfsY = aOrigin.Y(); + maMapMode = rNewMapMode; + + // #i75163# + ImplInvalidateViewTransform(); + + return; + } + if ( !bOldMap && bRelMap ) + { + maMapRes.mnMapScNumX = 1; + maMapRes.mnMapScNumY = 1; + maMapRes.mnMapScDenomX = mnDPIX; + maMapRes.mnMapScDenomY = mnDPIY; + maMapRes.mnMapOfsX = 0; + maMapRes.mnMapOfsY = 0; + } + + // calculate new MapMode-resolution + ImplCalcMapResolution( rNewMapMode, mnDPIX, mnDPIY, maMapRes, maThresRes ); + } + + // set new MapMode + if ( bRelMap ) + { + Point aOrigin( maMapRes.mnMapOfsX, maMapRes.mnMapOfsY ); + // aScale? = maMapMode.GetScale?() * rNewMapMode.GetScale?() + Fraction aScaleX = ImplMakeFraction( maMapMode.GetScaleX().GetNumerator(), + rNewMapMode.GetScaleX().GetNumerator(), + maMapMode.GetScaleX().GetDenominator(), + rNewMapMode.GetScaleX().GetDenominator() ); + Fraction aScaleY = ImplMakeFraction( maMapMode.GetScaleY().GetNumerator(), + rNewMapMode.GetScaleY().GetNumerator(), + maMapMode.GetScaleY().GetDenominator(), + rNewMapMode.GetScaleY().GetDenominator() ); + maMapMode.SetOrigin( aOrigin ); + maMapMode.SetScaleX( aScaleX ); + maMapMode.SetScaleY( aScaleY ); + } + else + maMapMode = rNewMapMode; + + // create new objects (clip region are not re-scaled) + mbNewFont = true; + mbInitFont = true; + ImplInitMapModeObjects(); + + // #106426# Adapt logical offset when changing mapmode + mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresPixToLogX ); + mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresPixToLogY ); + + // #i75163# + ImplInvalidateViewTransform(); +} + +void OutputDevice::ImplInitMapModeObjects() {} + +void OutputDevice::SetRelativeMapMode( const MapMode& rNewMapMode ) +{ + // do nothing if MapMode did not change + if ( maMapMode == rNewMapMode ) + return; + + MapUnit eOld = maMapMode.GetMapUnit(); + MapUnit eNew = rNewMapMode.GetMapUnit(); + + // a?F = rNewMapMode.GetScale?() / maMapMode.GetScale?() + Fraction aXF = ImplMakeFraction( rNewMapMode.GetScaleX().GetNumerator(), + maMapMode.GetScaleX().GetDenominator(), + rNewMapMode.GetScaleX().GetDenominator(), + maMapMode.GetScaleX().GetNumerator() ); + Fraction aYF = ImplMakeFraction( rNewMapMode.GetScaleY().GetNumerator(), + maMapMode.GetScaleY().GetDenominator(), + rNewMapMode.GetScaleY().GetDenominator(), + maMapMode.GetScaleY().GetNumerator() ); + + Point aPt( LogicToLogic( Point(), nullptr, &rNewMapMode ) ); + if ( eNew != eOld ) + { + if ( eOld > MapUnit::MapPixel ) + { + SAL_WARN( "vcl.gdi", "Not implemented MapUnit" ); + } + else if ( eNew > MapUnit::MapPixel ) + { + SAL_WARN( "vcl.gdi", "Not implemented MapUnit" ); + } + else + { + Fraction aF( aImplNumeratorAry[eNew] * aImplDenominatorAry[eOld], + aImplNumeratorAry[eOld] * aImplDenominatorAry[eNew] ); + + // a?F = a?F * aF + aXF = ImplMakeFraction( aXF.GetNumerator(), aF.GetNumerator(), + aXF.GetDenominator(), aF.GetDenominator() ); + aYF = ImplMakeFraction( aYF.GetNumerator(), aF.GetNumerator(), + aYF.GetDenominator(), aF.GetDenominator() ); + if ( eOld == MapUnit::MapPixel ) + { + aXF *= Fraction( mnDPIX, 1 ); + aYF *= Fraction( mnDPIY, 1 ); + } + else if ( eNew == MapUnit::MapPixel ) + { + aXF *= Fraction( 1, mnDPIX ); + aYF *= Fraction( 1, mnDPIY ); + } + } + } + + MapMode aNewMapMode( MapUnit::MapRelative, Point( -aPt.X(), -aPt.Y() ), aXF, aYF ); + SetMapMode( aNewMapMode ); + + if ( eNew != eOld ) + maMapMode = rNewMapMode; + + // #106426# Adapt logical offset when changing MapMode + mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresPixToLogX ); + mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresPixToLogY ); + + if( mpAlphaVDev ) + mpAlphaVDev->SetRelativeMapMode( rNewMapMode ); +} + +// #i75163# +basegfx::B2DHomMatrix OutputDevice::GetViewTransformation() const +{ + if(mbMap && mpOutDevData) + { + if(!mpOutDevData->mpViewTransform) + { + mpOutDevData->mpViewTransform = new basegfx::B2DHomMatrix; + + const double fScaleFactorX(static_cast<double>(mnDPIX) * static_cast<double>(maMapRes.mnMapScNumX) / static_cast<double>(maMapRes.mnMapScDenomX)); + const double fScaleFactorY(static_cast<double>(mnDPIY) * static_cast<double>(maMapRes.mnMapScNumY) / static_cast<double>(maMapRes.mnMapScDenomY)); + const double fZeroPointX((static_cast<double>(maMapRes.mnMapOfsX) * fScaleFactorX) + static_cast<double>(mnOutOffOrigX)); + const double fZeroPointY((static_cast<double>(maMapRes.mnMapOfsY) * fScaleFactorY) + static_cast<double>(mnOutOffOrigY)); + + mpOutDevData->mpViewTransform->set(0, 0, fScaleFactorX); + mpOutDevData->mpViewTransform->set(1, 1, fScaleFactorY); + mpOutDevData->mpViewTransform->set(0, 2, fZeroPointX); + mpOutDevData->mpViewTransform->set(1, 2, fZeroPointY); + } + + return *mpOutDevData->mpViewTransform; + } + else + { + return basegfx::B2DHomMatrix(); + } +} + +// #i75163# +basegfx::B2DHomMatrix OutputDevice::GetInverseViewTransformation() const +{ + if(mbMap && mpOutDevData) + { + if(!mpOutDevData->mpInverseViewTransform) + { + GetViewTransformation(); + mpOutDevData->mpInverseViewTransform = new basegfx::B2DHomMatrix(*mpOutDevData->mpViewTransform); + mpOutDevData->mpInverseViewTransform->invert(); + } + + return *mpOutDevData->mpInverseViewTransform; + } + else + { + return basegfx::B2DHomMatrix(); + } +} + +// #i75163# +basegfx::B2DHomMatrix OutputDevice::GetViewTransformation( const MapMode& rMapMode ) const +{ + // #i82615# + ImplMapRes aMapRes; + ImplThresholdRes aThresRes; + ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes ); + + basegfx::B2DHomMatrix aTransform; + + const double fScaleFactorX(static_cast<double>(mnDPIX) * static_cast<double>(aMapRes.mnMapScNumX) / static_cast<double>(aMapRes.mnMapScDenomX)); + const double fScaleFactorY(static_cast<double>(mnDPIY) * static_cast<double>(aMapRes.mnMapScNumY) / static_cast<double>(aMapRes.mnMapScDenomY)); + const double fZeroPointX((static_cast<double>(aMapRes.mnMapOfsX) * fScaleFactorX) + static_cast<double>(mnOutOffOrigX)); + const double fZeroPointY((static_cast<double>(aMapRes.mnMapOfsY) * fScaleFactorY) + static_cast<double>(mnOutOffOrigY)); + + aTransform.set(0, 0, fScaleFactorX); + aTransform.set(1, 1, fScaleFactorY); + aTransform.set(0, 2, fZeroPointX); + aTransform.set(1, 2, fZeroPointY); + + return aTransform; +} + +// #i75163# +basegfx::B2DHomMatrix OutputDevice::GetInverseViewTransformation( const MapMode& rMapMode ) const +{ + basegfx::B2DHomMatrix aMatrix( GetViewTransformation( rMapMode ) ); + aMatrix.invert(); + return aMatrix; +} + +basegfx::B2DHomMatrix OutputDevice::ImplGetDeviceTransformation() const +{ + basegfx::B2DHomMatrix aTransformation = GetViewTransformation(); + // TODO: is it worth to cache the transformed result? + if( mnOutOffX || mnOutOffY ) + aTransformation.translate( mnOutOffX, mnOutOffY ); + return aTransformation; +} + +Point OutputDevice::LogicToPixel( const Point& rLogicPt ) const +{ + + if ( !mbMap ) + return rLogicPt; + + return Point( ImplLogicToPixel( rLogicPt.X() + maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresLogToPixX )+mnOutOffOrigX, + ImplLogicToPixel( rLogicPt.Y() + maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresLogToPixY )+mnOutOffOrigY ); +} + +Size OutputDevice::LogicToPixel( const Size& rLogicSize ) const +{ + + if ( !mbMap ) + return rLogicSize; + + return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresLogToPixX ), + ImplLogicToPixel( rLogicSize.Height(), mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresLogToPixY ) ); +} + +tools::Rectangle OutputDevice::LogicToPixel( const tools::Rectangle& rLogicRect ) const +{ + + if ( !mbMap || rLogicRect.IsEmpty() ) + return rLogicRect; + + return tools::Rectangle( ImplLogicToPixel( rLogicRect.Left() + maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresLogToPixX )+mnOutOffOrigX, + ImplLogicToPixel( rLogicRect.Top() + maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresLogToPixY )+mnOutOffOrigY, + ImplLogicToPixel( rLogicRect.Right() + maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresLogToPixX )+mnOutOffOrigX, + ImplLogicToPixel( rLogicRect.Bottom() + maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresLogToPixY )+mnOutOffOrigY ); +} + +tools::Polygon OutputDevice::LogicToPixel( const tools::Polygon& rLogicPoly ) const +{ + + if ( !mbMap ) + return rLogicPoly; + + sal_uInt16 i; + sal_uInt16 nPoints = rLogicPoly.GetSize(); + tools::Polygon aPoly( rLogicPoly ); + + // get pointer to Point-array (copy data) + const Point* pPointAry = aPoly.GetConstPointAry(); + + for ( i = 0; i < nPoints; i++ ) + { + const Point* pPt = &(pPointAry[i]); + Point aPt; + aPt.setX( ImplLogicToPixel( pPt->X() + maMapRes.mnMapOfsX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresLogToPixX )+mnOutOffOrigX ); + aPt.setY( ImplLogicToPixel( pPt->Y() + maMapRes.mnMapOfsY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresLogToPixY )+mnOutOffOrigY ); + aPoly[i] = aPt; + } + + return aPoly; +} + +tools::PolyPolygon OutputDevice::LogicToPixel( const tools::PolyPolygon& rLogicPolyPoly ) const +{ + + if ( !mbMap ) + return rLogicPolyPoly; + + tools::PolyPolygon aPolyPoly( rLogicPolyPoly ); + sal_uInt16 nPoly = aPolyPoly.Count(); + for( sal_uInt16 i = 0; i < nPoly; i++ ) + { + tools::Polygon& rPoly = aPolyPoly[i]; + rPoly = LogicToPixel( rPoly ); + } + return aPolyPoly; +} + +basegfx::B2DPolyPolygon OutputDevice::LogicToPixel( const basegfx::B2DPolyPolygon& rLogicPolyPoly ) const +{ + basegfx::B2DPolyPolygon aTransformedPoly = rLogicPolyPoly; + const basegfx::B2DHomMatrix& rTransformationMatrix = GetViewTransformation(); + aTransformedPoly.transform( rTransformationMatrix ); + return aTransformedPoly; +} + +vcl::Region OutputDevice::LogicToPixel( const vcl::Region& rLogicRegion ) const +{ + + if(!mbMap || rLogicRegion.IsNull() || rLogicRegion.IsEmpty()) + { + return rLogicRegion; + } + + vcl::Region aRegion; + + if(rLogicRegion.getB2DPolyPolygon()) + { + aRegion = vcl::Region(LogicToPixel(*rLogicRegion.getB2DPolyPolygon())); + } + else if(rLogicRegion.getPolyPolygon()) + { + aRegion = vcl::Region(LogicToPixel(*rLogicRegion.getPolyPolygon())); + } + else if(rLogicRegion.getRegionBand()) + { + RectangleVector aRectangles; + rLogicRegion.GetRegionRectangles(aRectangles); + const RectangleVector& rRectangles(aRectangles); // needed to make the '!=' work + + // make reverse run to fill new region bottom-up, this will speed it up due to the used data structuring + for(RectangleVector::const_reverse_iterator aRectIter(rRectangles.rbegin()); aRectIter != rRectangles.rend(); ++aRectIter) + { + aRegion.Union(LogicToPixel(*aRectIter)); + } + } + + return aRegion; +} + +Point OutputDevice::LogicToPixel( const Point& rLogicPt, + const MapMode& rMapMode ) const +{ + + if ( rMapMode.IsDefault() ) + return rLogicPt; + + // convert MapMode resolution and convert + ImplMapRes aMapRes; + ImplThresholdRes aThresRes; + ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes ); + + return Point( ImplLogicToPixel( rLogicPt.X() + aMapRes.mnMapOfsX, mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX, + aThresRes.mnThresLogToPixX )+mnOutOffOrigX, + ImplLogicToPixel( rLogicPt.Y() + aMapRes.mnMapOfsY, mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY, + aThresRes.mnThresLogToPixY )+mnOutOffOrigY ); +} + +Size OutputDevice::LogicToPixel( const Size& rLogicSize, + const MapMode& rMapMode ) const +{ + + if ( rMapMode.IsDefault() ) + return rLogicSize; + + // convert MapMode resolution and convert + ImplMapRes aMapRes; + ImplThresholdRes aThresRes; + ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes ); + + return Size( ImplLogicToPixel( rLogicSize.Width(), mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX, + aThresRes.mnThresLogToPixX ), + ImplLogicToPixel( rLogicSize.Height(), mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY, + aThresRes.mnThresLogToPixY ) ); +} + +tools::Rectangle OutputDevice::LogicToPixel( const tools::Rectangle& rLogicRect, + const MapMode& rMapMode ) const +{ + + if ( rMapMode.IsDefault() || rLogicRect.IsEmpty() ) + return rLogicRect; + + // convert MapMode resolution and convert + ImplMapRes aMapRes; + ImplThresholdRes aThresRes; + ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes ); + + return tools::Rectangle( ImplLogicToPixel( rLogicRect.Left() + aMapRes.mnMapOfsX, mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX, + aThresRes.mnThresLogToPixX )+mnOutOffOrigX, + ImplLogicToPixel( rLogicRect.Top() + aMapRes.mnMapOfsY, mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY, + aThresRes.mnThresLogToPixY )+mnOutOffOrigY, + ImplLogicToPixel( rLogicRect.Right() + aMapRes.mnMapOfsX, mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX, + aThresRes.mnThresLogToPixX )+mnOutOffOrigX, + ImplLogicToPixel( rLogicRect.Bottom() + aMapRes.mnMapOfsY, mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY, + aThresRes.mnThresLogToPixY )+mnOutOffOrigY ); +} + +tools::Polygon OutputDevice::LogicToPixel( const tools::Polygon& rLogicPoly, + const MapMode& rMapMode ) const +{ + + if ( rMapMode.IsDefault() ) + return rLogicPoly; + + // convert MapMode resolution and convert + ImplMapRes aMapRes; + ImplThresholdRes aThresRes; + ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes ); + + sal_uInt16 i; + sal_uInt16 nPoints = rLogicPoly.GetSize(); + tools::Polygon aPoly( rLogicPoly ); + + // get pointer to Point-array (copy data) + const Point* pPointAry = aPoly.GetConstPointAry(); + + for ( i = 0; i < nPoints; i++ ) + { + const Point* pPt = &(pPointAry[i]); + Point aPt; + aPt.setX( ImplLogicToPixel( pPt->X() + aMapRes.mnMapOfsX, mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX, + aThresRes.mnThresLogToPixX )+mnOutOffOrigX ); + aPt.setY( ImplLogicToPixel( pPt->Y() + aMapRes.mnMapOfsY, mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY, + aThresRes.mnThresLogToPixY )+mnOutOffOrigY ); + aPoly[i] = aPt; + } + + return aPoly; +} + +basegfx::B2DPolyPolygon OutputDevice::LogicToPixel( const basegfx::B2DPolyPolygon& rLogicPolyPoly, + const MapMode& rMapMode ) const +{ + basegfx::B2DPolyPolygon aTransformedPoly = rLogicPolyPoly; + const basegfx::B2DHomMatrix& rTransformationMatrix = GetViewTransformation( rMapMode ); + aTransformedPoly.transform( rTransformationMatrix ); + return aTransformedPoly; +} + +Point OutputDevice::PixelToLogic( const Point& rDevicePt ) const +{ + + if ( !mbMap ) + return rDevicePt; + + return Point( ImplPixelToLogic( rDevicePt.X(), mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresPixToLogX ) - maMapRes.mnMapOfsX - mnOutOffLogicX, + ImplPixelToLogic( rDevicePt.Y(), mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresPixToLogY ) - maMapRes.mnMapOfsY - mnOutOffLogicY ); +} + +Size OutputDevice::PixelToLogic( const Size& rDeviceSize ) const +{ + + if ( !mbMap ) + return rDeviceSize; + + return Size( ImplPixelToLogic( rDeviceSize.Width(), mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresPixToLogX ), + ImplPixelToLogic( rDeviceSize.Height(), mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresPixToLogY ) ); +} + +tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect ) const +{ + + if ( !mbMap || rDeviceRect.IsEmpty() ) + return rDeviceRect; + + return tools::Rectangle( ImplPixelToLogic( rDeviceRect.Left(), mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresPixToLogX ) - maMapRes.mnMapOfsX - mnOutOffLogicX, + ImplPixelToLogic( rDeviceRect.Top(), mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresPixToLogY ) - maMapRes.mnMapOfsY - mnOutOffLogicY, + ImplPixelToLogic( rDeviceRect.Right(), mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresPixToLogX ) - maMapRes.mnMapOfsX - mnOutOffLogicX, + ImplPixelToLogic( rDeviceRect.Bottom(), mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresPixToLogY ) - maMapRes.mnMapOfsY - mnOutOffLogicY ); +} + +tools::Polygon OutputDevice::PixelToLogic( const tools::Polygon& rDevicePoly ) const +{ + + if ( !mbMap ) + return rDevicePoly; + + sal_uInt16 i; + sal_uInt16 nPoints = rDevicePoly.GetSize(); + tools::Polygon aPoly( rDevicePoly ); + + // get pointer to Point-array (copy data) + const Point* pPointAry = aPoly.GetConstPointAry(); + + for ( i = 0; i < nPoints; i++ ) + { + const Point* pPt = &(pPointAry[i]); + Point aPt; + aPt.setX( ImplPixelToLogic( pPt->X(), mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresPixToLogX ) - maMapRes.mnMapOfsX - mnOutOffLogicX ); + aPt.setY( ImplPixelToLogic( pPt->Y(), mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresPixToLogY ) - maMapRes.mnMapOfsY - mnOutOffLogicY ); + aPoly[i] = aPt; + } + + return aPoly; +} + +tools::PolyPolygon OutputDevice::PixelToLogic( const tools::PolyPolygon& rDevicePolyPoly ) const +{ + + if ( !mbMap ) + return rDevicePolyPoly; + + tools::PolyPolygon aPolyPoly( rDevicePolyPoly ); + sal_uInt16 nPoly = aPolyPoly.Count(); + for( sal_uInt16 i = 0; i < nPoly; i++ ) + { + tools::Polygon& rPoly = aPolyPoly[i]; + rPoly = PixelToLogic( rPoly ); + } + return aPolyPoly; +} + +basegfx::B2DPolyPolygon OutputDevice::PixelToLogic( const basegfx::B2DPolyPolygon& rPixelPolyPoly ) const +{ + basegfx::B2DPolyPolygon aTransformedPoly = rPixelPolyPoly; + const basegfx::B2DHomMatrix& rTransformationMatrix = GetInverseViewTransformation(); + aTransformedPoly.transform( rTransformationMatrix ); + return aTransformedPoly; +} + +vcl::Region OutputDevice::PixelToLogic( const vcl::Region& rDeviceRegion ) const +{ + + if(!mbMap || rDeviceRegion.IsNull() || rDeviceRegion.IsEmpty()) + { + return rDeviceRegion; + } + + vcl::Region aRegion; + + if(rDeviceRegion.getB2DPolyPolygon()) + { + aRegion = vcl::Region(PixelToLogic(*rDeviceRegion.getB2DPolyPolygon())); + } + else if(rDeviceRegion.getPolyPolygon()) + { + aRegion = vcl::Region(PixelToLogic(*rDeviceRegion.getPolyPolygon())); + } + else if(rDeviceRegion.getRegionBand()) + { + RectangleVector aRectangles; + rDeviceRegion.GetRegionRectangles(aRectangles); + const RectangleVector& rRectangles(aRectangles); // needed to make the '!=' work + + // make reverse run to fill new region bottom-up, this will speed it up due to the used data structuring + for(RectangleVector::const_reverse_iterator aRectIter(rRectangles.rbegin()); aRectIter != rRectangles.rend(); ++aRectIter) + { + aRegion.Union(PixelToLogic(*aRectIter)); + } + } + + return aRegion; +} + +Point OutputDevice::PixelToLogic( const Point& rDevicePt, + const MapMode& rMapMode ) const +{ + + // calculate nothing if default-MapMode + if ( rMapMode.IsDefault() ) + return rDevicePt; + + // calculate MapMode-resolution and convert + ImplMapRes aMapRes; + ImplThresholdRes aThresRes; + ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes ); + + return Point( ImplPixelToLogic( rDevicePt.X(), mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX, + aThresRes.mnThresPixToLogX ) - aMapRes.mnMapOfsX - mnOutOffLogicX, + ImplPixelToLogic( rDevicePt.Y(), mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY, + aThresRes.mnThresPixToLogY ) - aMapRes.mnMapOfsY - mnOutOffLogicY ); +} + +Size OutputDevice::PixelToLogic( const Size& rDeviceSize, + const MapMode& rMapMode ) const +{ + + // calculate nothing if default-MapMode + if ( rMapMode.IsDefault() ) + return rDeviceSize; + + // calculate MapMode-resolution and convert + ImplMapRes aMapRes; + ImplThresholdRes aThresRes; + ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes ); + + return Size( ImplPixelToLogic( rDeviceSize.Width(), mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX, + aThresRes.mnThresPixToLogX ), + ImplPixelToLogic( rDeviceSize.Height(), mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY, + aThresRes.mnThresPixToLogY ) ); +} + +tools::Rectangle OutputDevice::PixelToLogic( const tools::Rectangle& rDeviceRect, + const MapMode& rMapMode ) const +{ + + // calculate nothing if default-MapMode + if ( rMapMode.IsDefault() || rDeviceRect.IsEmpty() ) + return rDeviceRect; + + // calculate MapMode-resolution and convert + ImplMapRes aMapRes; + ImplThresholdRes aThresRes; + ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes ); + + return tools::Rectangle( ImplPixelToLogic( rDeviceRect.Left(), mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX, + aThresRes.mnThresPixToLogX ) - aMapRes.mnMapOfsX - mnOutOffLogicX, + ImplPixelToLogic( rDeviceRect.Top(), mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY, + aThresRes.mnThresPixToLogY ) - aMapRes.mnMapOfsY - mnOutOffLogicY, + ImplPixelToLogic( rDeviceRect.Right(), mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX, + aThresRes.mnThresPixToLogX ) - aMapRes.mnMapOfsX - mnOutOffLogicX, + ImplPixelToLogic( rDeviceRect.Bottom(), mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY, + aThresRes.mnThresPixToLogY ) - aMapRes.mnMapOfsY - mnOutOffLogicY ); +} + +tools::Polygon OutputDevice::PixelToLogic( const tools::Polygon& rDevicePoly, + const MapMode& rMapMode ) const +{ + + // calculate nothing if default-MapMode + if ( rMapMode.IsDefault() ) + return rDevicePoly; + + // calculate MapMode-resolution and convert + ImplMapRes aMapRes; + ImplThresholdRes aThresRes; + ImplCalcMapResolution( rMapMode, mnDPIX, mnDPIY, aMapRes, aThresRes ); + + sal_uInt16 i; + sal_uInt16 nPoints = rDevicePoly.GetSize(); + tools::Polygon aPoly( rDevicePoly ); + + // get pointer to Point-array (copy data) + const Point* pPointAry = aPoly.GetConstPointAry(); + + for ( i = 0; i < nPoints; i++ ) + { + const Point* pPt = &(pPointAry[i]); + Point aPt; + aPt.setX( ImplPixelToLogic( pPt->X(), mnDPIX, + aMapRes.mnMapScNumX, aMapRes.mnMapScDenomX, + aThresRes.mnThresPixToLogX ) - aMapRes.mnMapOfsX - mnOutOffLogicX ); + aPt.setY( ImplPixelToLogic( pPt->Y(), mnDPIY, + aMapRes.mnMapScNumY, aMapRes.mnMapScDenomY, + aThresRes.mnThresPixToLogY ) - aMapRes.mnMapOfsY - mnOutOffLogicY ); + aPoly[i] = aPt; + } + + return aPoly; +} + +basegfx::B2DPolygon OutputDevice::PixelToLogic( const basegfx::B2DPolygon& rPixelPoly, + const MapMode& rMapMode ) const +{ + basegfx::B2DPolygon aTransformedPoly = rPixelPoly; + const basegfx::B2DHomMatrix& rTransformationMatrix = GetInverseViewTransformation( rMapMode ); + aTransformedPoly.transform( rTransformationMatrix ); + return aTransformedPoly; +} + +basegfx::B2DPolyPolygon OutputDevice::PixelToLogic( const basegfx::B2DPolyPolygon& rPixelPolyPoly, + const MapMode& rMapMode ) const +{ + basegfx::B2DPolyPolygon aTransformedPoly = rPixelPolyPoly; + const basegfx::B2DHomMatrix& rTransformationMatrix = GetInverseViewTransformation( rMapMode ); + aTransformedPoly.transform( rTransformationMatrix ); + return aTransformedPoly; +} + +#define ENTER1( rSource, pMapModeSource, pMapModeDest ) \ + if ( !pMapModeSource ) \ + pMapModeSource = &maMapMode; \ + if ( !pMapModeDest ) \ + pMapModeDest = &maMapMode; \ + if ( *pMapModeSource == *pMapModeDest ) \ + return rSource; \ + \ + ImplMapRes aMapResSource; \ + aMapResSource.mnMapOfsX = 0; \ + aMapResSource.mnMapOfsY = 0; \ + aMapResSource.mnMapScNumX = 1; \ + aMapResSource.mnMapScNumY = 1; \ + aMapResSource.mnMapScDenomX = 1; \ + aMapResSource.mnMapScDenomY = 1; \ + ImplMapRes aMapResDest(aMapResSource); \ + \ + if ( !mbMap || pMapModeSource != &maMapMode ) \ + { \ + if ( pMapModeSource->GetMapUnit() == MapUnit::MapRelative ) \ + aMapResSource = maMapRes; \ + ImplCalcMapResolution( *pMapModeSource, \ + mnDPIX, mnDPIY, aMapResSource ); \ + } \ + else \ + aMapResSource = maMapRes; \ + if ( !mbMap || pMapModeDest != &maMapMode ) \ + { \ + if ( pMapModeDest->GetMapUnit() == MapUnit::MapRelative ) \ + aMapResDest = maMapRes; \ + ImplCalcMapResolution( *pMapModeDest, \ + mnDPIX, mnDPIY, aMapResDest ); \ + } \ + else \ + aMapResDest = maMapRes + +static void verifyUnitSourceDest( MapUnit eUnitSource, MapUnit eUnitDest ) +{ + DBG_ASSERT( eUnitSource != MapUnit::MapSysFont + && eUnitSource != MapUnit::MapAppFont + && eUnitSource != MapUnit::MapRelative, + "Source MapUnit is not permitted" ); + DBG_ASSERT( eUnitDest != MapUnit::MapSysFont + && eUnitDest != MapUnit::MapAppFont + && eUnitDest != MapUnit::MapRelative, + "Destination MapUnit is not permitted" ); +} + +#define ENTER3( eUnitSource, eUnitDest ) \ + long nNumerator = 1; \ + long nDenominator = 1; \ + SAL_WARN_IF( eUnitSource > s_MaxValidUnit, "vcl.gdi", "Invalid source map unit"); \ + SAL_WARN_IF( eUnitDest > s_MaxValidUnit, "vcl.gdi", "Invalid destination map unit"); \ + if( (eUnitSource <= s_MaxValidUnit) && (eUnitDest <= s_MaxValidUnit) ) \ + { \ + nNumerator = aImplNumeratorAry[eUnitSource] * \ + aImplDenominatorAry[eUnitDest]; \ + nDenominator = aImplNumeratorAry[eUnitDest] * \ + aImplDenominatorAry[eUnitSource]; \ + } \ + if ( eUnitSource == MapUnit::MapPixel ) \ + nDenominator *= 72; \ + else if( eUnitDest == MapUnit::MapPixel ) \ + nNumerator *= 72 + +#define ENTER4( rMapModeSource, rMapModeDest ) \ + ImplMapRes aMapResSource; \ + aMapResSource.mnMapOfsX = 0; \ + aMapResSource.mnMapOfsY = 0; \ + aMapResSource.mnMapScNumX = 1; \ + aMapResSource.mnMapScNumY = 1; \ + aMapResSource.mnMapScDenomX = 1; \ + aMapResSource.mnMapScDenomY = 1; \ + ImplMapRes aMapResDest(aMapResSource); \ + \ + ImplCalcMapResolution( rMapModeSource, 72, 72, aMapResSource ); \ + ImplCalcMapResolution( rMapModeDest, 72, 72, aMapResDest ) + +// return (n1 * n2 * n3) / (n4 * n5) +static long fn5( const long n1, + const long n2, + const long n3, + const long n4, + const long n5 ) +{ + if ( n1 == 0 || n2 == 0 || n3 == 0 || n4 == 0 || n5 == 0 ) + return 0; + if ( LONG_MAX / std::abs(n2) < std::abs(n3) ) + { + // a6 is skipped + BigInt a7 = n2; + a7 *= n3; + a7 *= n1; + + if ( LONG_MAX / std::abs(n4) < std::abs(n5) ) + { + BigInt a8 = n4; + a8 *= n5; + + BigInt a9 = a8; + a9 /= 2; + if ( a7.IsNeg() ) + a7 -= a9; + else + a7 += a9; + + a7 /= a8; + } // of if + else + { + long n8 = n4 * n5; + + if ( a7.IsNeg() ) + a7 -= n8 / 2; + else + a7 += n8 / 2; + + a7 /= n8; + } // of else + return static_cast<long>(a7); + } // of if + else + { + long n6 = n2 * n3; + + if ( LONG_MAX / std::abs(n1) < std::abs(n6) ) + { + BigInt a7 = n1; + a7 *= n6; + + if ( LONG_MAX / std::abs(n4) < std::abs(n5) ) + { + BigInt a8 = n4; + a8 *= n5; + + BigInt a9 = a8; + a9 /= 2; + if ( a7.IsNeg() ) + a7 -= a9; + else + a7 += a9; + + a7 /= a8; + } // of if + else + { + long n8 = n4 * n5; + + if ( a7.IsNeg() ) + a7 -= n8 / 2; + else + a7 += n8 / 2; + + a7 /= n8; + } // of else + return static_cast<long>(a7); + } // of if + else + { + long n7 = n1 * n6; + + if ( LONG_MAX / std::abs(n4) < std::abs(n5) ) + { + BigInt a7 = n7; + BigInt a8 = n4; + a8 *= n5; + + BigInt a9 = a8; + a9 /= 2; + if ( a7.IsNeg() ) + a7 -= a9; + else + a7 += a9; + + a7 /= a8; + return static_cast<long>(a7); + } // of if + else + { + const long n8 = n4 * n5; + const long n8_2 = n8 / 2; + + if( n7 < 0 ) + { + if( ( n7 - LONG_MIN ) >= n8_2 ) + n7 -= n8_2; + } + else if( ( LONG_MAX - n7 ) >= n8_2 ) + n7 += n8_2; + + return n7 / n8; + } // of else + } // of else + } // of else +} + +// return (n1 * n2) / n3 +static long fn3( const long n1, const long n2, const long n3 ) +{ + if ( n1 == 0 || n2 == 0 || n3 == 0 ) + return 0; + if ( LONG_MAX / std::abs(n1) < std::abs(n2) ) + { + BigInt a4 = n1; + a4 *= n2; + + if ( a4.IsNeg() ) + a4 -= n3 / 2; + else + a4 += n3 / 2; + + a4 /= n3; + return static_cast<long>(a4); + } // of if + else + { + long n4 = n1 * n2; + const long n3_2 = n3 / 2; + + if( n4 < 0 ) + { + if( ( n4 - LONG_MIN ) >= n3_2 ) + n4 -= n3_2; + } + else if( ( LONG_MAX - n4 ) >= n3_2 ) + n4 += n3_2; + + return n4 / n3; + } // of else +} + +Point OutputDevice::LogicToLogic( const Point& rPtSource, + const MapMode* pMapModeSource, + const MapMode* pMapModeDest ) const +{ + ENTER1( rPtSource, pMapModeSource, pMapModeDest ); + + return Point( fn5( rPtSource.X() + aMapResSource.mnMapOfsX, + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) - + aMapResDest.mnMapOfsX, + fn5( rPtSource.Y() + aMapResSource.mnMapOfsY, + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) - + aMapResDest.mnMapOfsY ); +} + +Size OutputDevice::LogicToLogic( const Size& rSzSource, + const MapMode* pMapModeSource, + const MapMode* pMapModeDest ) const +{ + ENTER1( rSzSource, pMapModeSource, pMapModeDest ); + + return Size( fn5( rSzSource.Width(), + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ), + fn5( rSzSource.Height(), + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) ); +} + +tools::Rectangle OutputDevice::LogicToLogic( const tools::Rectangle& rRectSource, + const MapMode* pMapModeSource, + const MapMode* pMapModeDest ) const +{ + ENTER1( rRectSource, pMapModeSource, pMapModeDest ); + + return tools::Rectangle( fn5( rRectSource.Left() + aMapResSource.mnMapOfsX, + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) - + aMapResDest.mnMapOfsX, + fn5( rRectSource.Top() + aMapResSource.mnMapOfsY, + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) - + aMapResDest.mnMapOfsY, + fn5( rRectSource.Right() + aMapResSource.mnMapOfsX, + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) - + aMapResDest.mnMapOfsX, + fn5( rRectSource.Bottom() + aMapResSource.mnMapOfsY, + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) - + aMapResDest.mnMapOfsY ); +} + +Point OutputDevice::LogicToLogic( const Point& rPtSource, + const MapMode& rMapModeSource, + const MapMode& rMapModeDest ) +{ + if ( rMapModeSource == rMapModeDest ) + return rPtSource; + + MapUnit eUnitSource = rMapModeSource.GetMapUnit(); + MapUnit eUnitDest = rMapModeDest.GetMapUnit(); + verifyUnitSourceDest( eUnitSource, eUnitDest ); + + if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple()) + { + ENTER3( eUnitSource, eUnitDest ); + + return Point( fn3( rPtSource.X(), nNumerator, nDenominator ), + fn3( rPtSource.Y(), nNumerator, nDenominator ) ); + } + else + { + ENTER4( rMapModeSource, rMapModeDest ); + + return Point( fn5( rPtSource.X() + aMapResSource.mnMapOfsX, + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) - + aMapResDest.mnMapOfsX, + fn5( rPtSource.Y() + aMapResSource.mnMapOfsY, + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) - + aMapResDest.mnMapOfsY ); + } +} + +Size OutputDevice::LogicToLogic( const Size& rSzSource, + const MapMode& rMapModeSource, + const MapMode& rMapModeDest ) +{ + if ( rMapModeSource == rMapModeDest ) + return rSzSource; + + MapUnit eUnitSource = rMapModeSource.GetMapUnit(); + MapUnit eUnitDest = rMapModeDest.GetMapUnit(); + verifyUnitSourceDest( eUnitSource, eUnitDest ); + + if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple()) + { + ENTER3( eUnitSource, eUnitDest ); + + return Size( fn3( rSzSource.Width(), nNumerator, nDenominator ), + fn3( rSzSource.Height(), nNumerator, nDenominator ) ); + } + else + { + ENTER4( rMapModeSource, rMapModeDest ); + + return Size( fn5( rSzSource.Width(), + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ), + fn5( rSzSource.Height(), + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) ); + } +} + +basegfx::B2DPolygon OutputDevice::LogicToLogic( const basegfx::B2DPolygon& rPolySource, + const MapMode& rMapModeSource, + const MapMode& rMapModeDest ) +{ + if(rMapModeSource == rMapModeDest) + { + return rPolySource; + } + + const basegfx::B2DHomMatrix aTransform(LogicToLogic(rMapModeSource, rMapModeDest)); + basegfx::B2DPolygon aPoly(rPolySource); + + aPoly.transform(aTransform); + return aPoly; +} + +basegfx::B2DHomMatrix OutputDevice::LogicToLogic(const MapMode& rMapModeSource, const MapMode& rMapModeDest) +{ + basegfx::B2DHomMatrix aTransform; + + if(rMapModeSource == rMapModeDest) + { + return aTransform; + } + + MapUnit eUnitSource = rMapModeSource.GetMapUnit(); + MapUnit eUnitDest = rMapModeDest.GetMapUnit(); + verifyUnitSourceDest(eUnitSource, eUnitDest); + + if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple()) + { + ENTER3(eUnitSource, eUnitDest); + + const double fScaleFactor(static_cast<double>(nNumerator) / static_cast<double>(nDenominator)); + aTransform.set(0, 0, fScaleFactor); + aTransform.set(1, 1, fScaleFactor); + } + else + { + ENTER4(rMapModeSource, rMapModeDest); + + const double fScaleFactorX((double(aMapResSource.mnMapScNumX) * double(aMapResDest.mnMapScDenomX)) / (double(aMapResSource.mnMapScDenomX) * double(aMapResDest.mnMapScNumX))); + const double fScaleFactorY((double(aMapResSource.mnMapScNumY) * double(aMapResDest.mnMapScDenomY)) / (double(aMapResSource.mnMapScDenomY) * double(aMapResDest.mnMapScNumY))); + const double fZeroPointX(double(aMapResSource.mnMapOfsX) * fScaleFactorX - double(aMapResDest.mnMapOfsX)); + const double fZeroPointY(double(aMapResSource.mnMapOfsY) * fScaleFactorY - double(aMapResDest.mnMapOfsY)); + + aTransform.set(0, 0, fScaleFactorX); + aTransform.set(1, 1, fScaleFactorY); + aTransform.set(0, 2, fZeroPointX); + aTransform.set(1, 2, fZeroPointY); + } + + return aTransform; +} + +tools::Rectangle OutputDevice::LogicToLogic( const tools::Rectangle& rRectSource, + const MapMode& rMapModeSource, + const MapMode& rMapModeDest ) +{ + if ( rMapModeSource == rMapModeDest ) + return rRectSource; + + MapUnit eUnitSource = rMapModeSource.GetMapUnit(); + MapUnit eUnitDest = rMapModeDest.GetMapUnit(); + verifyUnitSourceDest( eUnitSource, eUnitDest ); + + if (rMapModeSource.IsSimple() && rMapModeDest.IsSimple()) + { + ENTER3( eUnitSource, eUnitDest ); + + auto left = fn3( rRectSource.Left(), nNumerator, nDenominator ); + auto top = fn3( rRectSource.Top(), nNumerator, nDenominator ); + if (rRectSource.IsEmpty()) + return tools::Rectangle( left, top ); + + auto right = fn3( rRectSource.Right(), nNumerator, nDenominator ); + auto bottom = fn3( rRectSource.Bottom(), nNumerator, nDenominator ); + return tools::Rectangle(left, top, right, bottom); + } + else + { + ENTER4( rMapModeSource, rMapModeDest ); + + auto left = fn5( rRectSource.Left() + aMapResSource.mnMapOfsX, + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) - + aMapResDest.mnMapOfsX; + auto top = fn5( rRectSource.Top() + aMapResSource.mnMapOfsY, + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) - + aMapResDest.mnMapOfsY; + if (rRectSource.IsEmpty()) + return tools::Rectangle(left, top); + + auto right = fn5( rRectSource.Right() + aMapResSource.mnMapOfsX, + aMapResSource.mnMapScNumX, aMapResDest.mnMapScDenomX, + aMapResSource.mnMapScDenomX, aMapResDest.mnMapScNumX ) - + aMapResDest.mnMapOfsX; + auto bottom = fn5( rRectSource.Bottom() + aMapResSource.mnMapOfsY, + aMapResSource.mnMapScNumY, aMapResDest.mnMapScDenomY, + aMapResSource.mnMapScDenomY, aMapResDest.mnMapScNumY ) - + aMapResDest.mnMapOfsY; + return tools::Rectangle(left, top, right, bottom); + } +} + +long OutputDevice::LogicToLogic( long nLongSource, + MapUnit eUnitSource, MapUnit eUnitDest ) +{ + if ( eUnitSource == eUnitDest ) + return nLongSource; + + verifyUnitSourceDest( eUnitSource, eUnitDest ); + ENTER3( eUnitSource, eUnitDest ); + + return fn3( nLongSource, nNumerator, nDenominator ); +} + +void OutputDevice::SetPixelOffset( const Size& rOffset ) +{ + mnOutOffOrigX = rOffset.Width(); + mnOutOffOrigY = rOffset.Height(); + + mnOutOffLogicX = ImplPixelToLogic( mnOutOffOrigX, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresPixToLogX ); + mnOutOffLogicY = ImplPixelToLogic( mnOutOffOrigY, mnDPIY, + maMapRes.mnMapScNumY, maMapRes.mnMapScDenomY, + maThresRes.mnThresPixToLogY ); + + if( mpAlphaVDev ) + mpAlphaVDev->SetPixelOffset( rOffset ); +} + + +DeviceCoordinate OutputDevice::LogicWidthToDeviceCoordinate( long nWidth ) const +{ + if ( !mbMap ) + return static_cast<DeviceCoordinate>(nWidth); + +#if VCL_FLOAT_DEVICE_PIXEL + return (double)nWidth * maMapRes.mfScaleX * mnDPIX; +#else + + return ImplLogicToPixel( nWidth, mnDPIX, + maMapRes.mnMapScNumX, maMapRes.mnMapScDenomX, + maThresRes.mnThresLogToPixX ); +#endif +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/mask.cxx b/vcl/source/outdev/mask.cxx new file mode 100644 index 000000000..209389cfb --- /dev/null +++ b/vcl/source/outdev/mask.cxx @@ -0,0 +1,154 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cassert> + +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <vcl/outdev.hxx> +#include <vcl/virdev.hxx> + +#include <salgdi.hxx> +#include <salbmp.hxx> + +void OutputDevice::DrawMask( const Point& rDestPt, + const Bitmap& rBitmap, const Color& rMaskColor ) +{ + assert(!is_double_buffered_window()); + + const Size aSizePix( rBitmap.GetSizePixel() ); + DrawMask( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmap, rMaskColor, MetaActionType::MASK ); +} + +void OutputDevice::DrawMask( const Point& rDestPt, const Size& rDestSize, + const Bitmap& rBitmap, const Color& rMaskColor ) +{ + assert(!is_double_buffered_window()); + + DrawMask( rDestPt, rDestSize, Point(), rBitmap.GetSizePixel(), rBitmap, rMaskColor, MetaActionType::MASKSCALE ); +} + +void OutputDevice::DrawMask( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel, + const Bitmap& rBitmap, const Color& rMaskColor, + const MetaActionType nAction ) +{ + assert(!is_double_buffered_window()); + + if( ImplIsRecordLayout() ) + return; + + if( RasterOp::Invert == meRasterOp ) + { + DrawRect( tools::Rectangle( rDestPt, rDestSize ) ); + return; + } + + if ( mpMetaFile ) + { + switch( nAction ) + { + case MetaActionType::MASK: + mpMetaFile->AddAction( new MetaMaskAction( rDestPt, + rBitmap, rMaskColor ) ); + break; + + case MetaActionType::MASKSCALE: + mpMetaFile->AddAction( new MetaMaskScaleAction( rDestPt, + rDestSize, rBitmap, rMaskColor ) ); + break; + + case MetaActionType::MASKSCALEPART: + mpMetaFile->AddAction( new MetaMaskScalePartAction( rDestPt, rDestSize, + rSrcPtPixel, rSrcSizePixel, rBitmap, rMaskColor ) ); + break; + + default: break; + } + } + + if ( !IsDeviceOutputNecessary() ) + return; + + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + DrawDeviceMask( rBitmap, rMaskColor, rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel ); + +} + +void OutputDevice::DrawDeviceMask( const Bitmap& rMask, const Color& rMaskColor, + const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel ) +{ + assert(!is_double_buffered_window()); + + const std::shared_ptr<SalBitmap>& xImpBmp = rMask.ImplGetSalBitmap(); + if (xImpBmp) + { + SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(), + ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()), + ImplLogicWidthToDevicePixel(rDestSize.Width()), + ImplLogicHeightToDevicePixel(rDestSize.Height())); + + // we don't want to mirror via coordinates + const BmpMirrorFlags nMirrFlags = AdjustTwoRect( aPosAry, xImpBmp->GetSize() ); + + // check if output is necessary + if( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight ) + { + + if( nMirrFlags != BmpMirrorFlags::NONE ) + { + Bitmap aTmp( rMask ); + aTmp.Mirror( nMirrFlags ); + mpGraphics->DrawMask( aPosAry, *aTmp.ImplGetSalBitmap(), + rMaskColor, this); + } + else + mpGraphics->DrawMask( aPosAry, *xImpBmp, rMaskColor, this ); + + } + } + + // TODO: Use mask here + if( mpAlphaVDev ) + { + const Bitmap& rAlphaMask( rMask.CreateMask( rMaskColor ) ); + + // #i25167# Restrict mask painting to _opaque_ areas + // of the mask, otherwise we spoil areas where no + // bitmap content was ever visible. Interestingly + // enough, this can be achieved by taking the mask as + // the transparency mask of itself + mpAlphaVDev->DrawBitmapEx( rDestPt, + rDestSize, + rSrcPtPixel, + rSrcSizePixel, + BitmapEx( rAlphaMask, rMask ) ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/nativecontrols.cxx b/vcl/source/outdev/nativecontrols.cxx new file mode 100644 index 000000000..14ad647db --- /dev/null +++ b/vcl/source/outdev/nativecontrols.cxx @@ -0,0 +1,356 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cassert> + +#include <vcl/outdev.hxx> +#include <vcl/virdev.hxx> +#include <vcl/window.hxx> +#include <sal/log.hxx> + +#include <vcl/salnativewidgets.hxx> +#include <vcl/pdfextoutdevdata.hxx> + +#include <salgdi.hxx> + +static bool EnableNativeWidget( const OutputDevice& i_rDevice ) +{ + const OutDevType eType( i_rDevice.GetOutDevType() ); + switch ( eType ) + { + + case OUTDEV_WINDOW: + { + const vcl::Window* pWindow = dynamic_cast< const vcl::Window* >( &i_rDevice ); + if (pWindow) + { + return pWindow->IsNativeWidgetEnabled(); + } + else + { + SAL_WARN ("vcl.gdi", "Could not cast i_rDevice to Window"); + assert (pWindow); + return false; + } + } + + case OUTDEV_PDF: + [[fallthrough]]; + case OUTDEV_VIRDEV: + { + const vcl::ExtOutDevData* pOutDevData( i_rDevice.GetExtOutDevData() ); + const vcl::PDFExtOutDevData* pPDFData( dynamic_cast< const vcl::PDFExtOutDevData* >( pOutDevData ) ); + return pPDFData == nullptr; + } + + default: + return false; + } +} + +ImplControlValue::~ImplControlValue() +{ +} + +ImplControlValue* ImplControlValue::clone() const +{ + assert( typeid( const ImplControlValue ) == typeid( *this )); + return new ImplControlValue( *this ); +} + +ScrollbarValue::~ScrollbarValue() +{ +} + +ScrollbarValue* ScrollbarValue::clone() const +{ + assert( typeid( const ScrollbarValue ) == typeid( *this )); + return new ScrollbarValue( *this ); +} + +SliderValue::~SliderValue() +{ +} + +SliderValue* SliderValue::clone() const +{ + assert( typeid( const SliderValue ) == typeid( *this )); + return new SliderValue( *this ); +} + +int TabPaneValue::m_nOverlap = 0; + +TabPaneValue* TabPaneValue::clone() const +{ + assert(typeid(const TabPaneValue) == typeid(*this)); + return new TabPaneValue(*this); +} + +TabitemValue::~TabitemValue() +{ +} + +TabitemValue* TabitemValue::clone() const +{ + assert( typeid( const TabitemValue ) == typeid( *this )); + return new TabitemValue( *this ); +} + +SpinbuttonValue::~SpinbuttonValue() +{ +} + +SpinbuttonValue* SpinbuttonValue::clone() const +{ + assert( typeid( const SpinbuttonValue ) == typeid( *this )); + return new SpinbuttonValue( *this ); +} + +ToolbarValue::~ToolbarValue() +{ +} + +ToolbarValue* ToolbarValue::clone() const +{ + assert( typeid( const ToolbarValue ) == typeid( *this )); + return new ToolbarValue( *this ); +} + +MenubarValue::~MenubarValue() +{ +} + +MenubarValue* MenubarValue::clone() const +{ + assert( typeid( const MenubarValue ) == typeid( *this )); + return new MenubarValue( *this ); +} + +MenupopupValue::~MenupopupValue() +{ +} + +MenupopupValue* MenupopupValue::clone() const +{ + assert( typeid( const MenupopupValue ) == typeid( *this )); + return new MenupopupValue( *this ); +} + +PushButtonValue::~PushButtonValue() +{ +} + +PushButtonValue* PushButtonValue::clone() const +{ + assert( typeid( const PushButtonValue ) == typeid( *this )); + return new PushButtonValue( *this ); +} + +// These functions are mainly passthrough functions that allow access to +// the SalFrame behind a Window object for native widget rendering purposes. + +bool OutputDevice::IsNativeControlSupported( ControlType nType, ControlPart nPart ) const +{ + if( !EnableNativeWidget( *this ) ) + return false; + + if ( !mpGraphics && !AcquireGraphics() ) + return false; + + return mpGraphics->IsNativeControlSupported(nType, nPart); +} + +bool OutputDevice::HitTestNativeScrollbar( + ControlPart nPart, + const tools::Rectangle& rControlRegion, + const Point& aPos, + bool& rIsInside ) const +{ + if( !EnableNativeWidget( *this ) ) + return false; + + if ( !mpGraphics && !AcquireGraphics() ) + return false; + + Point aWinOffs( mnOutOffX, mnOutOffY ); + tools::Rectangle screenRegion( rControlRegion ); + screenRegion.Move( aWinOffs.X(), aWinOffs.Y()); + + return mpGraphics->HitTestNativeScrollbar( nPart, screenRegion, Point( aPos.X() + mnOutOffX, aPos.Y() + mnOutOffY ), + rIsInside, this ); +} + +static std::shared_ptr< ImplControlValue > TransformControlValue( const ImplControlValue& rVal, const OutputDevice& rDev ) +{ + std::shared_ptr< ImplControlValue > aResult; + switch( rVal.getType() ) + { + case ControlType::Slider: + { + const SliderValue* pSlVal = static_cast<const SliderValue*>(&rVal); + SliderValue* pNew = new SliderValue( *pSlVal ); + aResult.reset( pNew ); + pNew->maThumbRect = rDev.ImplLogicToDevicePixel( pSlVal->maThumbRect ); + } + break; + case ControlType::Scrollbar: + { + const ScrollbarValue* pScVal = static_cast<const ScrollbarValue*>(&rVal); + ScrollbarValue* pNew = new ScrollbarValue( *pScVal ); + aResult.reset( pNew ); + pNew->maThumbRect = rDev.ImplLogicToDevicePixel( pScVal->maThumbRect ); + pNew->maButton1Rect = rDev.ImplLogicToDevicePixel( pScVal->maButton1Rect ); + pNew->maButton2Rect = rDev.ImplLogicToDevicePixel( pScVal->maButton2Rect ); + } + break; + case ControlType::SpinButtons: + { + const SpinbuttonValue* pSpVal = static_cast<const SpinbuttonValue*>(&rVal); + SpinbuttonValue* pNew = new SpinbuttonValue( *pSpVal ); + aResult.reset( pNew ); + pNew->maUpperRect = rDev.ImplLogicToDevicePixel( pSpVal->maUpperRect ); + pNew->maLowerRect = rDev.ImplLogicToDevicePixel( pSpVal->maLowerRect ); + } + break; + case ControlType::Toolbar: + { + const ToolbarValue* pTVal = static_cast<const ToolbarValue*>(&rVal); + ToolbarValue* pNew = new ToolbarValue( *pTVal ); + aResult.reset( pNew ); + pNew->maGripRect = rDev.ImplLogicToDevicePixel( pTVal->maGripRect ); + } + break; + case ControlType::TabPane: + { + const TabPaneValue* pTIVal = static_cast<const TabPaneValue*>(&rVal); + TabPaneValue* pNew = new TabPaneValue(*pTIVal); + pNew->m_aTabHeaderRect = rDev.ImplLogicToDevicePixel(pTIVal->m_aTabHeaderRect); + pNew->m_aSelectedTabRect = rDev.ImplLogicToDevicePixel(pTIVal->m_aSelectedTabRect); + aResult.reset(pNew); + } + break; + case ControlType::TabItem: + { + const TabitemValue* pTIVal = static_cast<const TabitemValue*>(&rVal); + TabitemValue* pNew = new TabitemValue( *pTIVal ); + pNew->maContentRect = rDev.ImplLogicToDevicePixel(pTIVal->maContentRect); + aResult.reset( pNew ); + } + break; + case ControlType::Menubar: + { + const MenubarValue* pMVal = static_cast<const MenubarValue*>(&rVal); + MenubarValue* pNew = new MenubarValue( *pMVal ); + aResult.reset( pNew ); + } + break; + case ControlType::Pushbutton: + { + const PushButtonValue* pBVal = static_cast<const PushButtonValue*>(&rVal); + PushButtonValue* pNew = new PushButtonValue( *pBVal ); + aResult.reset( pNew ); + } + break; + case ControlType::Generic: + aResult = std::make_shared<ImplControlValue>( rVal ); + break; + case ControlType::MenuPopup: + { + const MenupopupValue* pMVal = static_cast<const MenupopupValue*>(&rVal); + MenupopupValue* pNew = new MenupopupValue( *pMVal ); + pNew->maItemRect = rDev.ImplLogicToDevicePixel( pMVal->maItemRect ); + aResult.reset( pNew ); + } + break; + default: + std::abort(); + break; + } + return aResult; +} +bool OutputDevice::DrawNativeControl( ControlType nType, + ControlPart nPart, + const tools::Rectangle& rControlRegion, + ControlState nState, + const ImplControlValue& aValue, + const OUString& aCaption, + const Color& rBackgroundColor ) +{ + assert(!is_double_buffered_window()); + + if( !EnableNativeWidget( *this ) ) + return false; + + // make sure the current clip region is initialized correctly + if ( !mpGraphics && !AcquireGraphics() ) + return false; + + if ( mbInitClipRegion ) + InitClipRegion(); + if ( mbOutputClipped ) + return true; + + if ( mbInitLineColor ) + InitLineColor(); + if ( mbInitFillColor ) + InitFillColor(); + + // Convert the coordinates from relative to Window-absolute, so we draw + // in the correct place in platform code + std::shared_ptr< ImplControlValue > aScreenCtrlValue( TransformControlValue( aValue, *this ) ); + tools::Rectangle screenRegion( ImplLogicToDevicePixel( rControlRegion ) ); + + bool bRet = mpGraphics->DrawNativeControl(nType, nPart, screenRegion, nState, *aScreenCtrlValue, aCaption, this, rBackgroundColor ); + + return bRet; +} + +bool OutputDevice::GetNativeControlRegion( ControlType nType, + ControlPart nPart, + const tools::Rectangle& rControlRegion, + ControlState nState, + const ImplControlValue& aValue, + tools::Rectangle &rNativeBoundingRegion, + tools::Rectangle &rNativeContentRegion ) const +{ + if( !EnableNativeWidget( *this ) ) + return false; + + if ( !mpGraphics && !AcquireGraphics() ) + return false; + + // Convert the coordinates from relative to Window-absolute, so we draw + // in the correct place in platform code + std::shared_ptr< ImplControlValue > aScreenCtrlValue( TransformControlValue( aValue, *this ) ); + tools::Rectangle screenRegion( ImplLogicToDevicePixel( rControlRegion ) ); + + bool bRet = mpGraphics->GetNativeControlRegion(nType, nPart, screenRegion, nState, *aScreenCtrlValue, + rNativeBoundingRegion, + rNativeContentRegion, this ); + if( bRet ) + { + // transform back native regions + rNativeBoundingRegion = ImplDevicePixelToLogic( rNativeBoundingRegion ); + rNativeContentRegion = ImplDevicePixelToLogic( rNativeContentRegion ); + } + + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/outdev.cxx b/vcl/source/outdev/outdev.cxx new file mode 100644 index 000000000..020a57a6a --- /dev/null +++ b/vcl/source/outdev/outdev.cxx @@ -0,0 +1,707 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <tools/debug.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/graph.hxx> +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> +#include <vcl/outdev.hxx> +#include <vcl/toolkit/unowrap.hxx> +#include <vcl/svapp.hxx> +#include <vcl/sysdata.hxx> + +#include <salgdi.hxx> +#include <window.h> +#include <outdev.h> + +#ifdef DISABLE_DYNLOADING +// Linking all needed LO code into one .so/executable, these already +// exist in the tools library, so put them in the anonymous namespace +// here to avoid clash... +namespace { +#endif +#ifdef DISABLE_DYNLOADING +} +#endif + +// Begin initializer and accessor public functions + +OutputDevice::OutputDevice(OutDevType eOutDevType) : + meOutDevType(eOutDevType), + maRegion(true), + maFillColor( COL_WHITE ), + maTextLineColor( COL_TRANSPARENT ), + mxSettings( new AllSettings(Application::GetSettings()) ) +{ + mpGraphics = nullptr; + mpUnoGraphicsList = nullptr; + mpPrevGraphics = nullptr; + mpNextGraphics = nullptr; + mpMetaFile = nullptr; + mpFontInstance = nullptr; + mpDeviceFontList = nullptr; + mpDeviceFontSizeList = nullptr; + mpAlphaVDev = nullptr; + mpExtOutDevData = nullptr; + mnOutOffX = 0; + mnOutOffY = 0; + mnOutWidth = 0; + mnOutHeight = 0; + mnDPIX = 0; + mnDPIY = 0; + mnDPIScalePercentage = 100; + mnTextOffX = 0; + mnTextOffY = 0; + mnOutOffOrigX = 0; + mnOutOffLogicX = 0; + mnOutOffOrigY = 0; + mnOutOffLogicY = 0; + mnEmphasisAscent = 0; + mnEmphasisDescent = 0; + mnDrawMode = DrawModeFlags::Default; + mnTextLayoutMode = ComplexTextLayoutFlags::Default; + + if( AllSettings::GetLayoutRTL() ) //#i84553# tip BiDi preference to RTL + mnTextLayoutMode = ComplexTextLayoutFlags::BiDiRtl | ComplexTextLayoutFlags::TextOriginLeft; + + meOutDevViewType = OutDevViewType::DontKnow; + mbMap = false; + mbClipRegion = false; + mbBackground = false; + mbOutput = true; + mbDevOutput = false; + mbOutputClipped = false; + maTextColor = COL_BLACK; + maOverlineColor = COL_TRANSPARENT; + meRasterOp = RasterOp::OverPaint; + mnAntialiasing = AntialiasingFlags::NONE; + meTextLanguage = LANGUAGE_SYSTEM; // TODO: get default from configuration? + mbLineColor = true; + mbFillColor = true; + mbInitLineColor = true; + mbInitFillColor = true; + mbInitFont = true; + mbInitTextColor = true; + mbInitClipRegion = true; + mbClipRegionSet = false; + mbNewFont = true; + mbTextLines = false; + mbTextSpecial = false; + mbRefPoint = false; + mbEnableRTL = false; // mirroring must be explicitly allowed (typically for windows only) + + // struct ImplMapRes + maMapRes.mnMapOfsX = 0; + maMapRes.mnMapOfsY = 0; + maMapRes.mnMapScNumX = 1; + maMapRes.mnMapScNumY = 1; + maMapRes.mnMapScDenomX = 1; + maMapRes.mnMapScDenomY = 1; + // struct ImplThresholdRes + maThresRes.mnThresLogToPixX = 0; + maThresRes.mnThresLogToPixY = 0; + maThresRes.mnThresPixToLogX = 0; + maThresRes.mnThresPixToLogY = 0; + + // struct ImplOutDevData- see #i82615# + mpOutDevData.reset(new ImplOutDevData); + mpOutDevData->mpRotateDev = nullptr; + mpOutDevData->mpRecordLayout = nullptr; + + // #i75163# + mpOutDevData->mpViewTransform = nullptr; + mpOutDevData->mpInverseViewTransform = nullptr; +} + +OutputDevice::~OutputDevice() +{ + disposeOnce(); +} + +void OutputDevice::dispose() +{ + if ( GetUnoGraphicsList() ) + { + UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper( false ); + if ( pWrapper ) + pWrapper->ReleaseAllGraphics( this ); + delete mpUnoGraphicsList; + mpUnoGraphicsList = nullptr; + } + + mpOutDevData->mpRotateDev.disposeAndClear(); + + // #i75163# + ImplInvalidateViewTransform(); + + mpOutDevData.reset(); + + // for some reason, we haven't removed state from the stack properly + if ( !maOutDevStateStack.empty() ) + SAL_WARN( "vcl.gdi", "OutputDevice::~OutputDevice(): OutputDevice::Push() calls != OutputDevice::Pop() calls" ); + maOutDevStateStack.clear(); + + // release the active font instance + mpFontInstance.clear(); + + // remove cached results of GetDevFontList/GetDevSizeList + mpDeviceFontList.reset(); + mpDeviceFontSizeList.reset(); + + // release ImplFontCache specific to this OutputDevice + mxFontCache.reset(); + + // release ImplFontList specific to this OutputDevice + mxFontCollection.reset(); + + mpAlphaVDev.disposeAndClear(); + mpPrevGraphics.clear(); + mpNextGraphics.clear(); + VclReferenceBase::dispose(); +} + +bool OutputDevice::IsVirtual() const +{ + return false; +} + +SalGraphics* OutputDevice::GetGraphics() +{ + DBG_TESTSOLARMUTEX(); + + if (!mpGraphics && !AcquireGraphics()) + SAL_WARN("vcl.gdi", "No mpGraphics set"); + + return mpGraphics; +} + +SalGraphics const *OutputDevice::GetGraphics() const +{ + DBG_TESTSOLARMUTEX(); + + if (!mpGraphics && !AcquireGraphics()) + SAL_WARN("vcl.gdi", "No mpGraphics set"); + + return mpGraphics; +} + +void OutputDevice::SetConnectMetaFile( GDIMetaFile* pMtf ) +{ + mpMetaFile = pMtf; +} + +void OutputDevice::SetSettings( const AllSettings& rSettings ) +{ + *mxSettings = rSettings; + + if( mpAlphaVDev ) + mpAlphaVDev->SetSettings( rSettings ); +} + +SystemGraphicsData OutputDevice::GetSystemGfxData() const +{ + if (!mpGraphics && !AcquireGraphics()) + return SystemGraphicsData(); + + return mpGraphics->GetGraphicsData(); +} + +#if ENABLE_CAIRO_CANVAS + +bool OutputDevice::SupportsCairo() const +{ + if (!mpGraphics && !AcquireGraphics()) + return false; + + return mpGraphics->SupportsCairo(); +} + +cairo::SurfaceSharedPtr OutputDevice::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const +{ + if (!mpGraphics && !AcquireGraphics()) + return cairo::SurfaceSharedPtr(); + return mpGraphics->CreateSurface(rSurface); +} + +cairo::SurfaceSharedPtr OutputDevice::CreateSurface(int x, int y, int width, int height) const +{ + if (!mpGraphics && !AcquireGraphics()) + return cairo::SurfaceSharedPtr(); + return mpGraphics->CreateSurface(*this, x, y, width, height); +} + +cairo::SurfaceSharedPtr OutputDevice::CreateBitmapSurface(const BitmapSystemData& rData, const Size& rSize) const +{ + if (!mpGraphics && !AcquireGraphics()) + return cairo::SurfaceSharedPtr(); + return mpGraphics->CreateBitmapSurface(*this, rData, rSize); +} + +css::uno::Any OutputDevice::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& rSurface, const basegfx::B2ISize& rSize) const +{ + if (!mpGraphics && !AcquireGraphics()) + return css::uno::Any(); + return mpGraphics->GetNativeSurfaceHandle(rSurface, rSize); +} + +#endif // ENABLE_CAIRO_CANVAS + +css::uno::Any OutputDevice::GetSystemGfxDataAny() const +{ + const SystemGraphicsData aSysData = GetSystemGfxData(); + css::uno::Sequence< sal_Int8 > aSeq( reinterpret_cast<sal_Int8 const *>(&aSysData), + aSysData.nSize ); + + return css::uno::makeAny(aSeq); +} + +void OutputDevice::SetRefPoint() +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaRefPointAction( Point(), false ) ); + + mbRefPoint = false; + maRefPoint.setX(0); + maRefPoint.setY(0); + + if( mpAlphaVDev ) + mpAlphaVDev->SetRefPoint(); +} + +void OutputDevice::SetRefPoint( const Point& rRefPoint ) +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaRefPointAction( rRefPoint, true ) ); + + mbRefPoint = true; + maRefPoint = rRefPoint; + + if( mpAlphaVDev ) + mpAlphaVDev->SetRefPoint( rRefPoint ); +} + +sal_uInt16 OutputDevice::GetBitCount() const +{ + // we need a graphics instance + if ( !mpGraphics && !AcquireGraphics() ) + return 0; + + return mpGraphics->GetBitCount(); +} + +void OutputDevice::SetOutOffXPixel(long nOutOffX) +{ + mnOutOffX = nOutOffX; +} + +void OutputDevice::SetOutOffYPixel(long nOutOffY) +{ + mnOutOffY = nOutOffY; +} + +css::uno::Reference< css::awt::XGraphics > OutputDevice::CreateUnoGraphics() +{ + UnoWrapperBase* pWrapper = UnoWrapperBase::GetUnoWrapper(); + return pWrapper ? pWrapper->CreateGraphics( this ) : css::uno::Reference< css::awt::XGraphics >(); +} + +std::vector< VCLXGraphics* > *OutputDevice::CreateUnoGraphicsList() +{ + mpUnoGraphicsList = new std::vector< VCLXGraphics* >; + return mpUnoGraphicsList; +} + +// Helper public function + +bool OutputDevice::SupportsOperation( OutDevSupportType eType ) const +{ + if( !mpGraphics && !AcquireGraphics() ) + return false; + const bool bHasSupport = mpGraphics->supportsOperation( eType ); + return bHasSupport; +} + +// Direct OutputDevice drawing public functions + +void OutputDevice::DrawOutDev( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPt, const Size& rSrcSize ) +{ + if( ImplIsRecordLayout() ) + return; + + if ( RasterOp::Invert == meRasterOp ) + { + DrawRect( tools::Rectangle( rDestPt, rDestSize ) ); + return; + } + + if ( mpMetaFile ) + { + const Bitmap aBmp( GetBitmap( rSrcPt, rSrcSize ) ); + mpMetaFile->AddAction( new MetaBmpScaleAction( rDestPt, rDestSize, aBmp ) ); + } + + if ( !IsDeviceOutputNecessary() ) + return; + + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + long nSrcWidth = ImplLogicWidthToDevicePixel( rSrcSize.Width() ); + long nSrcHeight = ImplLogicHeightToDevicePixel( rSrcSize.Height() ); + long nDestWidth = ImplLogicWidthToDevicePixel( rDestSize.Width() ); + long nDestHeight = ImplLogicHeightToDevicePixel( rDestSize.Height() ); + + if (nSrcWidth && nSrcHeight && nDestWidth && nDestHeight) + { + SalTwoRect aPosAry(ImplLogicXToDevicePixel(rSrcPt.X()), ImplLogicYToDevicePixel(rSrcPt.Y()), + nSrcWidth, nSrcHeight, + ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()), + nDestWidth, nDestHeight); + + const tools::Rectangle aSrcOutRect( Point( mnOutOffX, mnOutOffY ), + Size( mnOutWidth, mnOutHeight ) ); + + AdjustTwoRect( aPosAry, aSrcOutRect ); + + if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight ) + mpGraphics->CopyBits( aPosAry, nullptr, this, nullptr ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawOutDev( rDestPt, rDestSize, rSrcPt, rSrcSize ); +} + +void OutputDevice::DrawOutDev( const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPt, const Size& rSrcSize, + const OutputDevice& rOutDev ) +{ + if ( ImplIsRecordLayout() ) + return; + + if ( RasterOp::Invert == meRasterOp ) + { + DrawRect( tools::Rectangle( rDestPt, rDestSize ) ); + return; + } + + if ( mpMetaFile ) + { + if (rOutDev.mpAlphaVDev) + { + const BitmapEx aBmpEx(rOutDev.GetBitmapEx(rSrcPt, rSrcSize)); + mpMetaFile->AddAction(new MetaBmpExScaleAction(rDestPt, rDestSize, aBmpEx)); + } + else + { + const Bitmap aBmp(rOutDev.GetBitmap(rSrcPt, rSrcSize)); + mpMetaFile->AddAction(new MetaBmpScaleAction(rDestPt, rDestSize, aBmp)); + } + } + + if ( !IsDeviceOutputNecessary() ) + return; + + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if (rOutDev.mpAlphaVDev) + { + // alpha-blend source over destination + DrawBitmapEx(rDestPt, rDestSize, rOutDev.GetBitmapEx(rSrcPt, rSrcSize)); + } + else + { + SalTwoRect aPosAry(rOutDev.ImplLogicXToDevicePixel(rSrcPt.X()), + rOutDev.ImplLogicYToDevicePixel(rSrcPt.Y()), + rOutDev.ImplLogicWidthToDevicePixel(rSrcSize.Width()), + rOutDev.ImplLogicHeightToDevicePixel(rSrcSize.Height()), + ImplLogicXToDevicePixel(rDestPt.X()), + ImplLogicYToDevicePixel(rDestPt.Y()), + ImplLogicWidthToDevicePixel(rDestSize.Width()), + ImplLogicHeightToDevicePixel(rDestSize.Height())); + + drawOutDevDirect(&rOutDev, aPosAry); + + // #i32109#: make destination rectangle opaque - source has no alpha + if (mpAlphaVDev) + mpAlphaVDev->ImplFillOpaqueRectangle(tools::Rectangle(rDestPt, rDestSize)); + } +} + +void OutputDevice::CopyArea( const Point& rDestPt, + const Point& rSrcPt, const Size& rSrcSize, + bool bWindowInvalidate ) +{ + if ( ImplIsRecordLayout() ) + return; + + RasterOp eOldRop = GetRasterOp(); + SetRasterOp( RasterOp::OverPaint ); + + if ( !IsDeviceOutputNecessary() ) + return; + + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + long nSrcWidth = ImplLogicWidthToDevicePixel( rSrcSize.Width() ); + long nSrcHeight = ImplLogicHeightToDevicePixel( rSrcSize.Height() ); + if (nSrcWidth && nSrcHeight) + { + SalTwoRect aPosAry(ImplLogicXToDevicePixel(rSrcPt.X()), ImplLogicYToDevicePixel(rSrcPt.Y()), + nSrcWidth, nSrcHeight, + ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()), + nSrcWidth, nSrcHeight); + + const tools::Rectangle aSrcOutRect( Point( mnOutOffX, mnOutOffY ), + Size( mnOutWidth, mnOutHeight ) ); + + AdjustTwoRect( aPosAry, aSrcOutRect ); + + CopyDeviceArea( aPosAry, bWindowInvalidate ); + } + + SetRasterOp( eOldRop ); + + if( mpAlphaVDev ) + mpAlphaVDev->CopyArea( rDestPt, rSrcPt, rSrcSize, bWindowInvalidate ); +} + +// Direct OutputDevice drawing protected function + +void OutputDevice::CopyDeviceArea( SalTwoRect& aPosAry, bool /*bWindowInvalidate*/) +{ + if (aPosAry.mnSrcWidth == 0 || aPosAry.mnSrcHeight == 0 || aPosAry.mnDestWidth == 0 || aPosAry.mnDestHeight == 0) + return; + + aPosAry.mnDestWidth = aPosAry.mnSrcWidth; + aPosAry.mnDestHeight = aPosAry.mnSrcHeight; + mpGraphics->CopyBits(aPosAry, nullptr, this, nullptr); +} + +// Direct OutputDevice drawing private function + +void OutputDevice::drawOutDevDirect( const OutputDevice* pSrcDev, SalTwoRect& rPosAry ) +{ + SalGraphics* pSrcGraphics; + if (const OutputDevice* pCheckedSrc = DrawOutDevDirectCheck(pSrcDev)) + { + if (!pCheckedSrc->mpGraphics && !pCheckedSrc->AcquireGraphics()) + return; + pSrcGraphics = pCheckedSrc->mpGraphics; + } + else + pSrcGraphics = nullptr; + + if (!mpGraphics && !AcquireGraphics()) + return; + + // #102532# Offset only has to be pseudo window offset + const tools::Rectangle aSrcOutRect( Point( pSrcDev->mnOutOffX, pSrcDev->mnOutOffY ), + Size( pSrcDev->mnOutWidth, pSrcDev->mnOutHeight ) ); + + AdjustTwoRect( rPosAry, aSrcOutRect ); + + if ( rPosAry.mnSrcWidth && rPosAry.mnSrcHeight && rPosAry.mnDestWidth && rPosAry.mnDestHeight ) + { + // if this is no window, but pSrcDev is a window + // mirroring may be required + // because only windows have a SalGraphicsLayout + // mirroring is performed here + DrawOutDevDirectProcess( pSrcDev, rPosAry, pSrcGraphics); + } +} + +const OutputDevice* OutputDevice::DrawOutDevDirectCheck(const OutputDevice* pSrcDev) const +{ + return this == pSrcDev ? nullptr : pSrcDev; +} + +void OutputDevice::DrawOutDevDirectProcess( const OutputDevice* pSrcDev, SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) +{ + if( pSrcGraphics && (pSrcGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) ) + { + SalTwoRect aPosAry2 = rPosAry; + pSrcGraphics->mirror( aPosAry2.mnSrcX, aPosAry2.mnSrcWidth, pSrcDev ); + mpGraphics->CopyBits( aPosAry2, pSrcGraphics, this, pSrcDev ); + } + else + mpGraphics->CopyBits( rPosAry, pSrcGraphics, this, pSrcDev ); +} + +tools::Rectangle OutputDevice::GetBackgroundComponentBounds() const +{ + return tools::Rectangle( Point( 0, 0 ), GetOutputSizePixel() ); +} + +// Layout public functions + +void OutputDevice::EnableRTL( bool bEnable ) +{ + mbEnableRTL = bEnable; + + if( mpAlphaVDev ) + mpAlphaVDev->EnableRTL( bEnable ); +} + +bool OutputDevice::ImplIsAntiparallel() const +{ + bool bRet = false; + if( AcquireGraphics() ) + { + if( ( (mpGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) && ! IsRTLEnabled() ) || + ( ! (mpGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) && IsRTLEnabled() ) ) + { + bRet = true; + } + } + return bRet; +} + +// note: the coordinates to be remirrored are in frame coordinates ! + +void OutputDevice::ReMirror( Point &rPoint ) const +{ + rPoint.setX( mnOutOffX + mnOutWidth - 1 - rPoint.X() + mnOutOffX ); +} +void OutputDevice::ReMirror( tools::Rectangle &rRect ) const +{ + long nWidth = rRect.Right() - rRect.Left(); + + //long lc_x = rRect.nLeft - mnOutOffX; // normalize + //lc_x = mnOutWidth - nWidth - 1 - lc_x; // mirror + //rRect.nLeft = lc_x + mnOutOffX; // re-normalize + + rRect.SetLeft( mnOutOffX + mnOutWidth - nWidth - 1 - rRect.Left() + mnOutOffX ); + rRect.SetRight( rRect.Left() + nWidth ); +} + +void OutputDevice::ReMirror( vcl::Region &rRegion ) const +{ + RectangleVector aRectangles; + rRegion.GetRegionRectangles(aRectangles); + vcl::Region aMirroredRegion; + + for (auto & rectangle : aRectangles) + { + ReMirror(rectangle); + aMirroredRegion.Union(rectangle); + } + + rRegion = aMirroredRegion; + +} + +bool OutputDevice::HasMirroredGraphics() const +{ + return ( AcquireGraphics() && (mpGraphics->GetLayout() & SalLayoutFlags::BiDiRtl) ); +} + +bool OutputDevice::ImplIsRecordLayout() const +{ + if (!mpOutDevData) + return false; + + return mpOutDevData->mpRecordLayout; +} + +// EPS public function + +bool OutputDevice::DrawEPS( const Point& rPoint, const Size& rSize, + const GfxLink& rGfxLink, GDIMetaFile* pSubst ) +{ + bool bDrawn(true); + + if ( mpMetaFile ) + { + GDIMetaFile aSubst; + + if( pSubst ) + aSubst = *pSubst; + + mpMetaFile->AddAction( new MetaEPSAction( rPoint, rSize, rGfxLink, aSubst ) ); + } + + if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return bDrawn; + + if( mbOutputClipped ) + return bDrawn; + + tools::Rectangle aRect( ImplLogicToDevicePixel( tools::Rectangle( rPoint, rSize ) ) ); + + if( !aRect.IsEmpty() ) + { + // draw the real EPS graphics + if( rGfxLink.GetData() && rGfxLink.GetDataSize() ) + { + if( !mpGraphics && !AcquireGraphics() ) + return bDrawn; + + if( mbInitClipRegion ) + InitClipRegion(); + + aRect.Justify(); + bDrawn = mpGraphics->DrawEPS( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), + const_cast<sal_uInt8*>(rGfxLink.GetData()), rGfxLink.GetDataSize(), this ); + } + + // else draw the substitution graphics + if( !bDrawn && pSubst ) + { + GDIMetaFile* pOldMetaFile = mpMetaFile; + + mpMetaFile = nullptr; + Graphic( *pSubst ).Draw( this, rPoint, rSize ); + mpMetaFile = pOldMetaFile; + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawEPS( rPoint, rSize, rGfxLink, pSubst ); + + return bDrawn; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/outdevstate.cxx b/vcl/source/outdev/outdevstate.cxx new file mode 100644 index 000000000..a9ca52d94 --- /dev/null +++ b/vcl/source/outdev/outdevstate.cxx @@ -0,0 +1,609 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <tools/debug.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <vcl/outdevstate.hxx> +#include <vcl/virdev.hxx> +#include <vcl/settings.hxx> + +#include <outdev.h> +#include <outdata.hxx> +#include <salgdi.hxx> + +OutDevState::OutDevState() + : mbMapActive(false) + , meTextAlign(ALIGN_TOP) + , meRasterOp(RasterOp::OverPaint) + , mnTextLayoutMode(ComplexTextLayoutFlags::Default) + , meTextLanguage(0) + , mnFlags(PushFlags::NONE) +{ +} + +OutDevState::OutDevState(OutDevState&&) = default; + +OutDevState::~OutDevState() +{ + mpLineColor.reset(); + mpFillColor.reset(); + mpFont.reset(); + mpTextColor.reset(); + mpTextFillColor.reset(); + mpTextLineColor.reset(); + mpOverlineColor.reset(); + mpMapMode.reset(); + mpClipRegion.reset(); + mpRefPoint.reset(); +} + +void OutputDevice::Push( PushFlags nFlags ) +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaPushAction( nFlags ) ); + + maOutDevStateStack.emplace_back(); + OutDevState& rState = maOutDevStateStack.back(); + + rState.mnFlags = nFlags; + + if (nFlags & PushFlags::LINECOLOR && mbLineColor) + { + rState.mpLineColor = maLineColor; + } + if (nFlags & PushFlags::FILLCOLOR && mbFillColor) + { + rState.mpFillColor = maFillColor; + } + if ( nFlags & PushFlags::FONT ) + rState.mpFont.reset( new vcl::Font( maFont ) ); + if ( nFlags & PushFlags::TEXTCOLOR ) + rState.mpTextColor = GetTextColor(); + if (nFlags & PushFlags::TEXTFILLCOLOR && IsTextFillColor()) + { + rState.mpTextFillColor = GetTextFillColor(); + } + if (nFlags & PushFlags::TEXTLINECOLOR && IsTextLineColor()) + { + rState.mpTextLineColor = GetTextLineColor(); + } + if (nFlags & PushFlags::OVERLINECOLOR && IsOverlineColor()) + { + rState.mpOverlineColor = GetOverlineColor(); + } + if ( nFlags & PushFlags::TEXTALIGN ) + rState.meTextAlign = GetTextAlign(); + if( nFlags & PushFlags::TEXTLAYOUTMODE ) + rState.mnTextLayoutMode = GetLayoutMode(); + if( nFlags & PushFlags::TEXTLANGUAGE ) + rState.meTextLanguage = GetDigitLanguage(); + if ( nFlags & PushFlags::RASTEROP ) + rState.meRasterOp = GetRasterOp(); + if ( nFlags & PushFlags::MAPMODE ) + { + rState.mpMapMode = maMapMode; + rState.mbMapActive = mbMap; + } + if (nFlags & PushFlags::CLIPREGION && mbClipRegion) + { + rState.mpClipRegion.reset( new vcl::Region( maRegion ) ); + } + if (nFlags & PushFlags::REFPOINT && mbRefPoint) + { + rState.mpRefPoint = maRefPoint; + } + + if( mpAlphaVDev ) + mpAlphaVDev->Push(); +} + +void OutputDevice::Pop() +{ + + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaPopAction() ); + + GDIMetaFile* pOldMetaFile = mpMetaFile; + mpMetaFile = nullptr; + + if ( maOutDevStateStack.empty() ) + { + SAL_WARN( "vcl.gdi", "OutputDevice::Pop() without OutputDevice::Push()" ); + return; + } + const OutDevState& rState = maOutDevStateStack.back(); + + if( mpAlphaVDev ) + mpAlphaVDev->Pop(); + + if ( rState.mnFlags & PushFlags::LINECOLOR ) + { + if ( rState.mpLineColor ) + SetLineColor( *rState.mpLineColor ); + else + SetLineColor(); + } + + if ( rState.mnFlags & PushFlags::FILLCOLOR ) + { + if ( rState.mpFillColor ) + SetFillColor( *rState.mpFillColor ); + else + SetFillColor(); + } + + if ( rState.mnFlags & PushFlags::FONT ) + SetFont( *rState.mpFont ); + + if ( rState.mnFlags & PushFlags::TEXTCOLOR ) + SetTextColor( *rState.mpTextColor ); + + if ( rState.mnFlags & PushFlags::TEXTFILLCOLOR ) + { + if ( rState.mpTextFillColor ) + SetTextFillColor( *rState.mpTextFillColor ); + else + SetTextFillColor(); + } + + if ( rState.mnFlags & PushFlags::TEXTLINECOLOR ) + { + if ( rState.mpTextLineColor ) + SetTextLineColor( *rState.mpTextLineColor ); + else + SetTextLineColor(); + } + + if ( rState.mnFlags & PushFlags::OVERLINECOLOR ) + { + if ( rState.mpOverlineColor ) + SetOverlineColor( *rState.mpOverlineColor ); + else + SetOverlineColor(); + } + + if ( rState.mnFlags & PushFlags::TEXTALIGN ) + SetTextAlign( rState.meTextAlign ); + + if( rState.mnFlags & PushFlags::TEXTLAYOUTMODE ) + SetLayoutMode( rState.mnTextLayoutMode ); + + if( rState.mnFlags & PushFlags::TEXTLANGUAGE ) + SetDigitLanguage( rState.meTextLanguage ); + + if ( rState.mnFlags & PushFlags::RASTEROP ) + SetRasterOp( rState.meRasterOp ); + + if ( rState.mnFlags & PushFlags::MAPMODE ) + { + if ( rState.mpMapMode ) + SetMapMode( *rState.mpMapMode ); + else + SetMapMode(); + mbMap = rState.mbMapActive; + } + + if ( rState.mnFlags & PushFlags::CLIPREGION ) + SetDeviceClipRegion( rState.mpClipRegion.get() ); + + if ( rState.mnFlags & PushFlags::REFPOINT ) + { + if ( rState.mpRefPoint ) + SetRefPoint( *rState.mpRefPoint ); + else + SetRefPoint(); + } + + maOutDevStateStack.pop_back(); + + mpMetaFile = pOldMetaFile; +} + +sal_uInt32 OutputDevice::GetGCStackDepth() const +{ + return maOutDevStateStack.size(); +} + +void OutputDevice::ClearStack() +{ + sal_uInt32 nCount = GetGCStackDepth(); + while( nCount-- ) + Pop(); +} + +void OutputDevice::EnableOutput( bool bEnable ) +{ + mbOutput = bEnable; + + if( mpAlphaVDev ) + mpAlphaVDev->EnableOutput( bEnable ); +} + +void OutputDevice::SetAntialiasing( AntialiasingFlags nMode ) +{ + if ( mnAntialiasing != nMode ) + { + mnAntialiasing = nMode; + mbInitFont = true; + + if(mpGraphics) + { + mpGraphics->setAntiAliasB2DDraw(bool(mnAntialiasing & AntialiasingFlags::EnableB2dDraw)); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetAntialiasing( nMode ); +} + +void OutputDevice::SetDrawMode( DrawModeFlags nDrawMode ) +{ + + mnDrawMode = nDrawMode; + + if( mpAlphaVDev ) + mpAlphaVDev->SetDrawMode( nDrawMode ); +} + +void OutputDevice::SetLayoutMode( ComplexTextLayoutFlags nTextLayoutMode ) +{ + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaLayoutModeAction( nTextLayoutMode ) ); + + mnTextLayoutMode = nTextLayoutMode; + + if( mpAlphaVDev ) + mpAlphaVDev->SetLayoutMode( nTextLayoutMode ); +} + +void OutputDevice::SetDigitLanguage( LanguageType eTextLanguage ) +{ + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextLanguageAction( eTextLanguage ) ); + + meTextLanguage = eTextLanguage; + + if( mpAlphaVDev ) + mpAlphaVDev->SetDigitLanguage( eTextLanguage ); +} + +void OutputDevice::SetRasterOp( RasterOp eRasterOp ) +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaRasterOpAction( eRasterOp ) ); + + if ( meRasterOp != eRasterOp ) + { + meRasterOp = eRasterOp; + mbInitLineColor = mbInitFillColor = true; + + if( mpGraphics || AcquireGraphics() ) + mpGraphics->SetXORMode( (RasterOp::Invert == meRasterOp) || (RasterOp::Xor == meRasterOp), RasterOp::Invert == meRasterOp ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetRasterOp( eRasterOp ); +} + + +void OutputDevice::SetFillColor() +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaFillColorAction( Color(), false ) ); + + if ( mbFillColor ) + { + mbInitFillColor = true; + mbFillColor = false; + maFillColor = COL_TRANSPARENT; + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetFillColor(); +} + +void OutputDevice::SetFillColor( const Color& rColor ) +{ + + Color aColor( rColor ); + + if( mnDrawMode & ( DrawModeFlags::BlackFill | DrawModeFlags::WhiteFill | + DrawModeFlags::GrayFill | DrawModeFlags::NoFill | + DrawModeFlags::SettingsFill ) ) + { + if( !ImplIsColorTransparent( aColor ) ) + { + if( mnDrawMode & DrawModeFlags::BlackFill ) + { + aColor = COL_BLACK; + } + else if( mnDrawMode & DrawModeFlags::WhiteFill ) + { + aColor = COL_WHITE; + } + else if( mnDrawMode & DrawModeFlags::GrayFill ) + { + const sal_uInt8 cLum = aColor.GetLuminance(); + aColor = Color( cLum, cLum, cLum ); + } + else if( mnDrawMode & DrawModeFlags::NoFill ) + { + aColor = COL_TRANSPARENT; + } + else if( mnDrawMode & DrawModeFlags::SettingsFill ) + { + aColor = GetSettings().GetStyleSettings().GetWindowColor(); + } + } + } + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaFillColorAction( aColor, true ) ); + + if ( ImplIsColorTransparent( aColor ) ) + { + if ( mbFillColor ) + { + mbInitFillColor = true; + mbFillColor = false; + maFillColor = COL_TRANSPARENT; + } + } + else + { + if ( maFillColor != aColor ) + { + mbInitFillColor = true; + mbFillColor = true; + maFillColor = aColor; + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetFillColor( COL_BLACK ); +} + +void OutputDevice::SetLineColor() +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaLineColorAction( Color(), false ) ); + + if ( mbLineColor ) + { + mbInitLineColor = true; + mbLineColor = false; + maLineColor = COL_TRANSPARENT; + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetLineColor(); +} + +void OutputDevice::SetLineColor( const Color& rColor ) +{ + + Color aColor = ImplDrawModeToColor( rColor ); + + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaLineColorAction( aColor, true ) ); + + if( ImplIsColorTransparent( aColor ) ) + { + if ( mbLineColor ) + { + mbInitLineColor = true; + mbLineColor = false; + maLineColor = COL_TRANSPARENT; + } + } + else + { + if( maLineColor != aColor ) + { + mbInitLineColor = true; + mbLineColor = true; + maLineColor = aColor; + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetLineColor( COL_BLACK ); +} + +void OutputDevice::SetBackground() +{ + + maBackground = Wallpaper(); + mbBackground = false; + + if( mpAlphaVDev ) + mpAlphaVDev->SetBackground(); +} + +void OutputDevice::SetBackground( const Wallpaper& rBackground ) +{ + + maBackground = rBackground; + + if( rBackground.GetStyle() == WallpaperStyle::NONE ) + mbBackground = false; + else + mbBackground = true; + + if( mpAlphaVDev ) + mpAlphaVDev->SetBackground( rBackground ); +} + +void OutputDevice::SetFont( const vcl::Font& rNewFont ) +{ + + vcl::Font aFont( rNewFont ); + if ( mnDrawMode & (DrawModeFlags::BlackText | DrawModeFlags::WhiteText | DrawModeFlags::GrayText | DrawModeFlags::SettingsText | + DrawModeFlags::BlackFill | DrawModeFlags::WhiteFill | DrawModeFlags::GrayFill | DrawModeFlags::NoFill | + DrawModeFlags::SettingsFill ) ) + { + Color aTextColor( aFont.GetColor() ); + + if ( mnDrawMode & DrawModeFlags::BlackText ) + aTextColor = COL_BLACK; + else if ( mnDrawMode & DrawModeFlags::WhiteText ) + aTextColor = COL_WHITE; + else if ( mnDrawMode & DrawModeFlags::GrayText ) + { + const sal_uInt8 cLum = aTextColor.GetLuminance(); + aTextColor = Color( cLum, cLum, cLum ); + } + else if ( mnDrawMode & DrawModeFlags::SettingsText ) + aTextColor = GetSettings().GetStyleSettings().GetFontColor(); + + aFont.SetColor( aTextColor ); + + bool bTransFill = aFont.IsTransparent(); + if ( !bTransFill ) + { + Color aTextFillColor( aFont.GetFillColor() ); + + if ( mnDrawMode & DrawModeFlags::BlackFill ) + aTextFillColor = COL_BLACK; + else if ( mnDrawMode & DrawModeFlags::WhiteFill ) + aTextFillColor = COL_WHITE; + else if ( mnDrawMode & DrawModeFlags::GrayFill ) + { + const sal_uInt8 cLum = aTextFillColor.GetLuminance(); + aTextFillColor = Color( cLum, cLum, cLum ); + } + else if( mnDrawMode & DrawModeFlags::SettingsFill ) + aTextFillColor = GetSettings().GetStyleSettings().GetWindowColor(); + else if ( mnDrawMode & DrawModeFlags::NoFill ) + { + aTextFillColor = COL_TRANSPARENT; + } + + aFont.SetFillColor( aTextFillColor ); + } + } + + if ( mpMetaFile ) + { + mpMetaFile->AddAction( new MetaFontAction( aFont ) ); + // the color and alignment actions don't belong here + // TODO: get rid of them without breaking anything... + mpMetaFile->AddAction( new MetaTextAlignAction( aFont.GetAlignment() ) ); + mpMetaFile->AddAction( new MetaTextFillColorAction( aFont.GetFillColor(), !aFont.IsTransparent() ) ); + } + + if ( !maFont.IsSameInstance( aFont ) ) + { + // Optimization MT/HDU: COL_TRANSPARENT means SetFont should ignore the font color, + // because SetTextColor() is used for this. + // #i28759# maTextColor might have been changed behind our back, commit then, too. + if( aFont.GetColor() != COL_TRANSPARENT + && (aFont.GetColor() != maFont.GetColor() || aFont.GetColor() != maTextColor ) ) + { + maTextColor = aFont.GetColor(); + mbInitTextColor = true; + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextColorAction( aFont.GetColor() ) ); + } + maFont = aFont; + mbNewFont = true; + + if( mpAlphaVDev ) + { + // #i30463# + // Since SetFont might change the text color, apply that only + // selectively to alpha vdev (which normally paints opaque text + // with COL_BLACK) + if( aFont.GetColor() != COL_TRANSPARENT ) + { + mpAlphaVDev->SetTextColor( COL_BLACK ); + aFont.SetColor( COL_TRANSPARENT ); + } + + mpAlphaVDev->SetFont( aFont ); + } + } +} + + +void OutputDevice::InitLineColor() +{ + DBG_TESTSOLARMUTEX(); + + if( mbLineColor ) + { + if( RasterOp::N0 == meRasterOp ) + mpGraphics->SetROPLineColor( SalROPColor::N0 ); + else if( RasterOp::N1 == meRasterOp ) + mpGraphics->SetROPLineColor( SalROPColor::N1 ); + else if( RasterOp::Invert == meRasterOp ) + mpGraphics->SetROPLineColor( SalROPColor::Invert ); + else + mpGraphics->SetLineColor( maLineColor ); + } + else + mpGraphics->SetLineColor(); + + mbInitLineColor = false; +} + + +void OutputDevice::InitFillColor() +{ + DBG_TESTSOLARMUTEX(); + + if( mbFillColor ) + { + if( RasterOp::N0 == meRasterOp ) + mpGraphics->SetROPFillColor( SalROPColor::N0 ); + else if( RasterOp::N1 == meRasterOp ) + mpGraphics->SetROPFillColor( SalROPColor::N1 ); + else if( RasterOp::Invert == meRasterOp ) + mpGraphics->SetROPFillColor( SalROPColor::Invert ); + else + mpGraphics->SetFillColor( maFillColor ); + } + else + mpGraphics->SetFillColor(); + + mbInitFillColor = false; +} + +void OutputDevice::ImplReleaseFonts() +{ + mpGraphics->ReleaseFonts(); + + mbNewFont = true; + mbInitFont = true; + + mpFontInstance.clear(); + mpDeviceFontList.reset(); + mpDeviceFontSizeList.reset(); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/pixel.cxx b/vcl/source/outdev/pixel.cxx new file mode 100644 index 000000000..0a80a8f1e --- /dev/null +++ b/vcl/source/outdev/pixel.cxx @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cassert> + +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <vcl/outdev.hxx> +#include <vcl/virdev.hxx> + +#include <salgdi.hxx> + +Color OutputDevice::GetPixel(const Point& rPoint) const +{ + Color aColor; + + if (mpGraphics || AcquireGraphics()) + { + if (mbInitClipRegion) + const_cast<OutputDevice*>(this)->InitClipRegion(); + + if (!mbOutputClipped) + { + const long nX = ImplLogicXToDevicePixel(rPoint.X()); + const long nY = ImplLogicYToDevicePixel(rPoint.Y()); + aColor = mpGraphics->GetPixel(nX, nY, this); + + if (mpAlphaVDev) + { + Color aAlphaColor = mpAlphaVDev->GetPixel(rPoint); + aColor.SetTransparency(aAlphaColor.GetBlue()); + } + } + } + return aColor; +} + +void OutputDevice::DrawPixel( const Point& rPt ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaPointAction( rPt ) ); + + if ( !IsDeviceOutputNecessary() || !mbLineColor || ImplIsRecordLayout() ) + return; + + Point aPt = ImplLogicToDevicePixel( rPt ); + + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + mpGraphics->DrawPixel( aPt.X(), aPt.Y(), this ); + + if( mpAlphaVDev ) + mpAlphaVDev->DrawPixel( rPt ); +} + +void OutputDevice::DrawPixel( const Point& rPt, const Color& rColor ) +{ + assert(!is_double_buffered_window()); + + Color aColor = ImplDrawModeToColor( rColor ); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaPixelAction( rPt, aColor ) ); + + if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + Point aPt = ImplLogicToDevicePixel( rPt ); + + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + mpGraphics->DrawPixel( aPt.X(), aPt.Y(), aColor, this ); + + if (mpAlphaVDev) + { + Color aAlphaColor(rColor.GetTransparency(), rColor.GetTransparency(), rColor.GetTransparency()); + mpAlphaVDev->DrawPixel(rPt, aAlphaColor); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/polygon.cxx b/vcl/source/outdev/polygon.cxx new file mode 100644 index 000000000..e031fb059 --- /dev/null +++ b/vcl/source/outdev/polygon.cxx @@ -0,0 +1,518 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cassert> + +#include <sal/types.h> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <memory> +#include <tools/poly.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <vcl/outdev.hxx> +#include <vcl/virdev.hxx> + +#include <salgdi.hxx> + +#define OUTDEV_POLYPOLY_STACKBUF 32 + +void OutputDevice::DrawPolyPolygon( const tools::PolyPolygon& rPolyPoly ) +{ + assert(!is_double_buffered_window()); + + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaPolyPolygonAction( rPolyPoly ) ); + + sal_uInt16 nPoly = rPolyPoly.Count(); + + if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || !nPoly || ImplIsRecordLayout() ) + return; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + if ( mbInitFillColor ) + InitFillColor(); + + // use b2dpolygon drawing if possible + if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) && + mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) && + RasterOp::OverPaint == GetRasterOp() && + (IsLineColor() || IsFillColor())) + { + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation()); + basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon()); + bool bSuccess(true); + + // ensure closed - may be asserted, will prevent buffering + if(!aB2DPolyPolygon.isClosed()) + { + aB2DPolyPolygon.setClosed(true); + } + + if(IsFillColor()) + { + bSuccess = mpGraphics->DrawPolyPolygon( + aTransform, + aB2DPolyPolygon, + 0.0, + this); + } + + if(bSuccess && IsLineColor()) + { + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + for(auto const& rPolygon : aB2DPolyPolygon) + { + bSuccess = mpGraphics->DrawPolyLine( + aTransform, + rPolygon, + 0.0, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + this); + if (!bSuccess) + break; + } + } + + if(bSuccess) + { + if( mpAlphaVDev ) + mpAlphaVDev->DrawPolyPolygon( rPolyPoly ); + return; + } + } + + if ( nPoly == 1 ) + { + // #100127# Map to DrawPolygon + const tools::Polygon& aPoly = rPolyPoly.GetObject( 0 ); + if( aPoly.GetSize() >= 2 ) + { + GDIMetaFile* pOldMF = mpMetaFile; + mpMetaFile = nullptr; + + DrawPolygon( aPoly ); + + mpMetaFile = pOldMF; + } + } + else + { + // #100127# moved real tools::PolyPolygon draw to separate method, + // have to call recursively, avoiding duplicate + // ImplLogicToDevicePixel calls + ImplDrawPolyPolygon( nPoly, ImplLogicToDevicePixel( rPolyPoly ) ); + } + if( mpAlphaVDev ) + mpAlphaVDev->DrawPolyPolygon( rPolyPoly ); +} + +void OutputDevice::DrawPolygon( const basegfx::B2DPolygon& rB2DPolygon) +{ + assert(!is_double_buffered_window()); + + // AW: Do NOT paint empty polygons + if(rB2DPolygon.count()) + { + basegfx::B2DPolyPolygon aPP( rB2DPolygon ); + DrawPolyPolygon( aPP ); + } +} + +void OutputDevice::DrawPolygon( const tools::Polygon& rPoly ) +{ + assert(!is_double_buffered_window()); + + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaPolygonAction( rPoly ) ); + + sal_uInt16 nPoints = rPoly.GetSize(); + + if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || (nPoints < 2) || ImplIsRecordLayout() ) + return; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + if ( mbInitFillColor ) + InitFillColor(); + + // use b2dpolygon drawing if possible + if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) && + mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) && + RasterOp::OverPaint == GetRasterOp() && + (IsLineColor() || IsFillColor())) + { + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation()); + basegfx::B2DPolygon aB2DPolygon(rPoly.getB2DPolygon()); + bool bSuccess(true); + + // ensure closed - maybe assert, hinders buffering + if(!aB2DPolygon.isClosed()) + { + aB2DPolygon.setClosed(true); + } + + if(IsFillColor()) + { + bSuccess = mpGraphics->DrawPolyPolygon( + aTransform, + basegfx::B2DPolyPolygon(aB2DPolygon), + 0.0, + this); + } + + if(bSuccess && IsLineColor()) + { + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + bSuccess = mpGraphics->DrawPolyLine( + aTransform, + aB2DPolygon, + 0.0, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + this); + } + + if(bSuccess) + { + if( mpAlphaVDev ) + mpAlphaVDev->DrawPolygon( rPoly ); + return; + } + } + + tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly ); + const SalPoint* pPtAry = reinterpret_cast<const SalPoint*>(aPoly.GetConstPointAry()); + + // #100127# Forward beziers to sal, if any + if( aPoly.HasFlags() ) + { + const PolyFlags* pFlgAry = aPoly.GetConstFlagAry(); + if( !mpGraphics->DrawPolygonBezier( nPoints, pPtAry, pFlgAry, this ) ) + { + aPoly = tools::Polygon::SubdivideBezier(aPoly); + pPtAry = reinterpret_cast<const SalPoint*>(aPoly.GetConstPointAry()); + mpGraphics->DrawPolygon( aPoly.GetSize(), pPtAry, this ); + } + } + else + { + mpGraphics->DrawPolygon( nPoints, pPtAry, this ); + } + if( mpAlphaVDev ) + mpAlphaVDev->DrawPolygon( rPoly ); +} + +// Caution: This method is nearly the same as +// OutputDevice::DrawTransparent( const basegfx::B2DPolyPolygon& rB2DPolyPoly, double fTransparency), +// so when changes are made here do not forget to make changes there, too + +void OutputDevice::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rB2DPolyPoly ) +{ + assert(!is_double_buffered_window()); + + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaPolyPolygonAction( tools::PolyPolygon( rB2DPolyPoly ) ) ); + + // call helper + ImplDrawPolyPolygonWithB2DPolyPolygon(rB2DPolyPoly); +} + +void OutputDevice::ImplDrawPolyPolygonWithB2DPolyPolygon(const basegfx::B2DPolyPolygon& rB2DPolyPoly) +{ + // Do not paint empty PolyPolygons + if(!rB2DPolyPoly.count() || !IsDeviceOutputNecessary()) + return; + + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return; + + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return; + + if( mbInitLineColor ) + InitLineColor(); + + if( mbInitFillColor ) + InitFillColor(); + + bool bSuccess(false); + + if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) && + mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) && + RasterOp::OverPaint == GetRasterOp() && + (IsLineColor() || IsFillColor())) + { + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation()); + basegfx::B2DPolyPolygon aB2DPolyPolygon(rB2DPolyPoly); + bSuccess = true; + + // ensure closed - maybe assert, hinders buffering + if(!aB2DPolyPolygon.isClosed()) + { + aB2DPolyPolygon.setClosed(true); + } + + if(IsFillColor()) + { + bSuccess = mpGraphics->DrawPolyPolygon( + aTransform, + aB2DPolyPolygon, + 0.0, + this); + } + + if(bSuccess && IsLineColor()) + { + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + for(auto const& rPolygon : aB2DPolyPolygon) + { + bSuccess = mpGraphics->DrawPolyLine( + aTransform, + rPolygon, + 0.0, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + this); + if (!bSuccess) + break; + } + } + } + + if (!bSuccess) + { + // fallback to old polygon drawing if needed + const tools::PolyPolygon aToolsPolyPolygon(rB2DPolyPoly); + const tools::PolyPolygon aPixelPolyPolygon = ImplLogicToDevicePixel(aToolsPolyPolygon); + ImplDrawPolyPolygon(aPixelPolyPolygon.Count(), aPixelPolyPolygon); + } + + if (mpAlphaVDev) + mpAlphaVDev->ImplDrawPolyPolygonWithB2DPolyPolygon(rB2DPolyPoly); +} + +// #100127# Extracted from OutputDevice::DrawPolyPolygon() +void OutputDevice::ImplDrawPolyPolygon( sal_uInt16 nPoly, const tools::PolyPolygon& rPolyPoly ) +{ + // AW: This crashes on empty PolyPolygons, avoid that + if(!nPoly) + return; + + sal_uInt32 aStackAry1[OUTDEV_POLYPOLY_STACKBUF]; + PCONSTSALPOINT aStackAry2[OUTDEV_POLYPOLY_STACKBUF]; + PolyFlags* aStackAry3[OUTDEV_POLYPOLY_STACKBUF]; + sal_uInt32* pPointAry; + PCONSTSALPOINT* pPointAryAry; + const PolyFlags** pFlagAryAry; + sal_uInt16 i = 0; + sal_uInt16 j = 0; + sal_uInt16 last = 0; + bool bHaveBezier = false; + if ( nPoly > OUTDEV_POLYPOLY_STACKBUF ) + { + pPointAry = new sal_uInt32[nPoly]; + pPointAryAry = new PCONSTSALPOINT[nPoly]; + pFlagAryAry = new const PolyFlags*[nPoly]; + } + else + { + pPointAry = aStackAry1; + pPointAryAry = aStackAry2; + pFlagAryAry = const_cast<const PolyFlags**>(aStackAry3); + } + + do + { + const tools::Polygon& rPoly = rPolyPoly.GetObject( i ); + sal_uInt16 nSize = rPoly.GetSize(); + if ( nSize ) + { + pPointAry[j] = nSize; + pPointAryAry[j] = reinterpret_cast<PCONSTSALPOINT>(rPoly.GetConstPointAry()); + pFlagAryAry[j] = rPoly.GetConstFlagAry(); + last = i; + + if( pFlagAryAry[j] ) + bHaveBezier = true; + + ++j; + } + ++i; + } + while ( i < nPoly ); + + if ( j == 1 ) + { + // #100127# Forward beziers to sal, if any + if( bHaveBezier ) + { + if( !mpGraphics->DrawPolygonBezier( *pPointAry, *pPointAryAry, *pFlagAryAry, this ) ) + { + tools::Polygon aPoly = tools::Polygon::SubdivideBezier( rPolyPoly.GetObject( last ) ); + mpGraphics->DrawPolygon( aPoly.GetSize(), reinterpret_cast<const SalPoint*>(aPoly.GetConstPointAry()), this ); + } + } + else + { + mpGraphics->DrawPolygon( *pPointAry, *pPointAryAry, this ); + } + } + else + { + // #100127# Forward beziers to sal, if any + if( bHaveBezier ) + { + if( !mpGraphics->DrawPolyPolygonBezier( j, pPointAry, pPointAryAry, pFlagAryAry, this ) ) + { + tools::PolyPolygon aPolyPoly = tools::PolyPolygon::SubdivideBezier( rPolyPoly ); + ImplDrawPolyPolygon( aPolyPoly.Count(), aPolyPoly ); + } + } + else + { + mpGraphics->DrawPolyPolygon( j, pPointAry, pPointAryAry, this ); + } + } + + if ( pPointAry != aStackAry1 ) + { + delete[] pPointAry; + delete[] pPointAryAry; + delete[] pFlagAryAry; + } +} + +void OutputDevice::ImplDrawPolygon( const tools::Polygon& rPoly, const tools::PolyPolygon* pClipPolyPoly ) +{ + if( pClipPolyPoly ) + { + ImplDrawPolyPolygon( rPoly, pClipPolyPoly ); + } + else + { + sal_uInt16 nPoints = rPoly.GetSize(); + + if ( nPoints < 2 ) + return; + + const SalPoint* pPtAry = reinterpret_cast<const SalPoint*>(rPoly.GetConstPointAry()); + mpGraphics->DrawPolygon( nPoints, pPtAry, this ); + } +} + +void OutputDevice::ImplDrawPolyPolygon( const tools::PolyPolygon& rPolyPoly, const tools::PolyPolygon* pClipPolyPoly ) +{ + tools::PolyPolygon* pPolyPoly; + + if( pClipPolyPoly ) + { + pPolyPoly = new tools::PolyPolygon; + rPolyPoly.GetIntersection( *pClipPolyPoly, *pPolyPoly ); + } + else + { + pPolyPoly = const_cast<tools::PolyPolygon*>(&rPolyPoly); + } + if( pPolyPoly->Count() == 1 ) + { + const tools::Polygon& rPoly = pPolyPoly->GetObject( 0 ); + sal_uInt16 nSize = rPoly.GetSize(); + + if( nSize >= 2 ) + { + const SalPoint* pPtAry = reinterpret_cast<const SalPoint*>(rPoly.GetConstPointAry()); + mpGraphics->DrawPolygon( nSize, pPtAry, this ); + } + } + else if( pPolyPoly->Count() ) + { + sal_uInt16 nCount = pPolyPoly->Count(); + std::unique_ptr<sal_uInt32[]> pPointAry(new sal_uInt32[nCount]); + std::unique_ptr<PCONSTSALPOINT[]> pPointAryAry(new PCONSTSALPOINT[nCount]); + sal_uInt16 i = 0; + do + { + const tools::Polygon& rPoly = pPolyPoly->GetObject( i ); + sal_uInt16 nSize = rPoly.GetSize(); + if ( nSize ) + { + pPointAry[i] = nSize; + pPointAryAry[i] = reinterpret_cast<PCONSTSALPOINT>(rPoly.GetConstPointAry()); + i++; + } + else + nCount--; + } + while( i < nCount ); + + if( nCount == 1 ) + mpGraphics->DrawPolygon( pPointAry[0], pPointAryAry[0], this ); + else + mpGraphics->DrawPolyPolygon( nCount, pPointAry.get(), pPointAryAry.get(), this ); + } + + if( pClipPolyPoly ) + delete pPolyPoly; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/polyline.cxx b/vcl/source/outdev/polyline.cxx new file mode 100644 index 000000000..672854a9e --- /dev/null +++ b/vcl/source/outdev/polyline.cxx @@ -0,0 +1,386 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cassert> + +#include <sal/types.h> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dlinegeometry.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <vcl/outdev.hxx> +#include <vcl/virdev.hxx> + +#include <salgdi.hxx> + +void OutputDevice::DrawPolyLine( const tools::Polygon& rPoly ) +{ + assert(!is_double_buffered_window()); + + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaPolyLineAction( rPoly ) ); + + sal_uInt16 nPoints = rPoly.GetSize(); + + if ( !IsDeviceOutputNecessary() || !mbLineColor || (nPoints < 2) || ImplIsRecordLayout() ) + return; + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + // use b2dpolygon drawing if possible + if(DrawPolyLineDirect( + basegfx::B2DHomMatrix(), + rPoly.getB2DPolygon())) + { + return; + } + + const basegfx::B2DPolygon aB2DPolyLine(rPoly.getB2DPolygon()); + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation()); + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + if(mpGraphics->DrawPolyLine( + aTransform, + aB2DPolyLine, + 0.0, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0) /*default fMiterMinimumAngle, not used*/, + bPixelSnapHairline, + this)) + { + return; + } + + tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly ); + SalPoint* pPtAry = reinterpret_cast<SalPoint*>(aPoly.GetPointAry()); + + // #100127# Forward beziers to sal, if any + if( aPoly.HasFlags() ) + { + const PolyFlags* pFlgAry = aPoly.GetConstFlagAry(); + if( !mpGraphics->DrawPolyLineBezier( nPoints, pPtAry, pFlgAry, this ) ) + { + aPoly = tools::Polygon::SubdivideBezier(aPoly); + pPtAry = reinterpret_cast<SalPoint*>(aPoly.GetPointAry()); + mpGraphics->DrawPolyLine( aPoly.GetSize(), pPtAry, this ); + } + } + else + { + mpGraphics->DrawPolyLine( nPoints, pPtAry, this ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawPolyLine( rPoly ); +} + +void OutputDevice::DrawPolyLine( const tools::Polygon& rPoly, const LineInfo& rLineInfo ) +{ + assert(!is_double_buffered_window()); + + if ( rLineInfo.IsDefault() ) + { + DrawPolyLine( rPoly ); + return; + } + + // #i101491# + // Try direct Fallback to B2D-Version of DrawPolyLine + if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) && + LineStyle::Solid == rLineInfo.GetStyle()) + { + DrawPolyLine( + rPoly.getB2DPolygon(), + static_cast< double >(rLineInfo.GetWidth()), + rLineInfo.GetLineJoin(), + rLineInfo.GetLineCap(), + basegfx::deg2rad(15.0) /* default fMiterMinimumAngle, value not available in LineInfo */); + return; + } + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaPolyLineAction( rPoly, rLineInfo ) ); + + drawPolyLine(rPoly, rLineInfo); +} + +void OutputDevice::DrawPolyLine( const basegfx::B2DPolygon& rB2DPolygon, + double fLineWidth, + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle) +{ + assert(!is_double_buffered_window()); + + if( mpMetaFile ) + { + LineInfo aLineInfo; + if( fLineWidth != 0.0 ) + aLineInfo.SetWidth( static_cast<long>(fLineWidth+0.5) ); + + const tools::Polygon aToolsPolygon( rB2DPolygon ); + mpMetaFile->AddAction( new MetaPolyLineAction( aToolsPolygon, aLineInfo ) ); + } + + // Do not paint empty PolyPolygons + if(!rB2DPolygon.count() || !IsDeviceOutputNecessary()) + return; + + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return; + + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return; + + if( mbInitLineColor ) + InitLineColor(); + + // use b2dpolygon drawing if possible + if(DrawPolyLineDirect( + basegfx::B2DHomMatrix(), + rB2DPolygon, + fLineWidth, + 0.0, + nullptr, // MM01 + eLineJoin, + eLineCap, + fMiterMinimumAngle)) + { + return; + } + + // #i101491# + // no output yet; fallback to geometry decomposition and use filled polygon paint + // when line is fat and not too complex. ImplDrawPolyPolygonWithB2DPolyPolygon + // will do internal needed AA checks etc. + if(fLineWidth >= 2.5 && + rB2DPolygon.count() && + rB2DPolygon.count() <= 1000) + { + const double fHalfLineWidth((fLineWidth * 0.5) + 0.5); + const basegfx::B2DPolyPolygon aAreaPolyPolygon( + basegfx::utils::createAreaGeometry( rB2DPolygon, + fHalfLineWidth, + eLineJoin, + eLineCap, + fMiterMinimumAngle)); + const Color aOldLineColor(maLineColor); + const Color aOldFillColor(maFillColor); + + SetLineColor(); + InitLineColor(); + SetFillColor(aOldLineColor); + InitFillColor(); + + // draw using a loop; else the topology will paint a PolyPolygon + for(auto const& rPolygon : aAreaPolyPolygon) + { + ImplDrawPolyPolygonWithB2DPolyPolygon( + basegfx::B2DPolyPolygon(rPolygon)); + } + + SetLineColor(aOldLineColor); + InitLineColor(); + SetFillColor(aOldFillColor); + InitFillColor(); + + const bool bTryAA((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) && + mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) && + RasterOp::OverPaint == GetRasterOp() && + IsLineColor()); + + // when AA it is necessary to also paint the filled polygon's outline + // to avoid optical gaps + for(auto const& rPolygon : aAreaPolyPolygon) + { + (void)DrawPolyLineDirect( + basegfx::B2DHomMatrix(), + rPolygon, + 0.0, + 0.0, + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0) /*default, not used*/, + bTryAA); + } + } + else + { + // fallback to old polygon drawing if needed + const tools::Polygon aToolsPolygon( rB2DPolygon ); + LineInfo aLineInfo; + if( fLineWidth != 0.0 ) + aLineInfo.SetWidth( static_cast<long>(fLineWidth+0.5) ); + + drawPolyLine( aToolsPolygon, aLineInfo ); + } +} + +void OutputDevice::drawPolyLine(const tools::Polygon& rPoly, const LineInfo& rLineInfo) +{ + sal_uInt16 nPoints(rPoly.GetSize()); + + if ( !IsDeviceOutputNecessary() || !mbLineColor || ( nPoints < 2 ) || ( LineStyle::NONE == rLineInfo.GetStyle() ) || ImplIsRecordLayout() ) + return; + + tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly ); + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + const LineInfo aInfo( ImplLogicToDevicePixel( rLineInfo ) ); + const bool bDashUsed(LineStyle::Dash == aInfo.GetStyle()); + const bool bLineWidthUsed(aInfo.GetWidth() > 1); + + if(bDashUsed || bLineWidthUsed) + { + drawLine ( basegfx::B2DPolyPolygon(aPoly.getB2DPolygon()), aInfo ); + } + else + { + // #100127# the subdivision HAS to be done here since only a pointer + // to an array of points is given to the DrawPolyLine method, there is + // NO way to find out there that it's a curve. + if( aPoly.HasFlags() ) + { + aPoly = tools::Polygon::SubdivideBezier( aPoly ); + nPoints = aPoly.GetSize(); + } + + mpGraphics->DrawPolyLine(nPoints, reinterpret_cast<SalPoint*>(aPoly.GetPointAry()), this); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawPolyLine( rPoly, rLineInfo ); +} + +bool OutputDevice::DrawPolyLineDirect( + const basegfx::B2DHomMatrix& rObjectTransform, + const basegfx::B2DPolygon& rB2DPolygon, + double fLineWidth, + double fTransparency, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool bBypassAACheck) +{ + assert(!is_double_buffered_window()); + + // AW: Do NOT paint empty PolyPolygons + if(!rB2DPolygon.count()) + return true; + + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return false; + + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return true; + + if( mbInitLineColor ) + InitLineColor(); + + const bool bTryAA( bBypassAACheck || + ((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) && + mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) && + RasterOp::OverPaint == GetRasterOp() && + IsLineColor())); + + if(bTryAA) + { + // combine rObjectTransform with WorldToDevice + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation() * rObjectTransform); + const bool bPixelSnapHairline((mnAntialiasing & AntialiasingFlags::PixelSnapHairline) && rB2DPolygon.count() < 1000); + + const double fAdjustedTransparency = mpAlphaVDev ? 0 : fTransparency; + // draw the polyline + bool bDrawSuccess = mpGraphics->DrawPolyLine( + aTransform, + rB2DPolygon, + fAdjustedTransparency, + fLineWidth, // tdf#124848 use LineWidth direct, do not try to solve for zero-case (aka hairline) + pStroke, // MM01 + eLineJoin, + eLineCap, + fMiterMinimumAngle, + bPixelSnapHairline, + this); + + if( bDrawSuccess ) + { + // worked, add metafile action (if recorded) and return true + if( mpMetaFile ) + { + LineInfo aLineInfo; + if( fLineWidth != 0.0 ) + aLineInfo.SetWidth( static_cast<long>(fLineWidth+0.5) ); + // Transport known information, might be needed + aLineInfo.SetLineJoin(eLineJoin); + aLineInfo.SetLineCap(eLineCap); + // MiterMinimumAngle does not exist yet in LineInfo + const tools::Polygon aToolsPolygon( rB2DPolygon ); + mpMetaFile->AddAction( new MetaPolyLineAction( aToolsPolygon, aLineInfo ) ); + } + + if (mpAlphaVDev) + mpAlphaVDev->DrawPolyLineDirect(rObjectTransform, rB2DPolygon, fLineWidth, + fTransparency, pStroke, eLineJoin, eLineCap, + fMiterMinimumAngle, bBypassAACheck); + + return true; + } + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/rect.cxx b/vcl/source/outdev/rect.cxx new file mode 100644 index 000000000..ecbeb12fb --- /dev/null +++ b/vcl/source/outdev/rect.cxx @@ -0,0 +1,415 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cassert> + +#include <sal/types.h> + +#include <tools/poly.hxx> +#include <tools/helpers.hxx> +#include <vcl/metaact.hxx> +#include <vcl/outdev.hxx> +#include <vcl/virdev.hxx> + +#include <salgdi.hxx> + +void OutputDevice::DrawRect( const tools::Rectangle& rRect ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaRectAction( rRect ) ); + + if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() ) + return; + + tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) ); + + if ( aRect.IsEmpty() ) + return; + + aRect.Justify(); + + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + if ( mbInitFillColor ) + InitFillColor(); + + mpGraphics->DrawRect( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), this ); + + if( mpAlphaVDev ) + mpAlphaVDev->DrawRect( rRect ); +} + +void OutputDevice::DrawRect( const tools::Rectangle& rRect, + sal_uLong nHorzRound, sal_uLong nVertRound ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaRoundRectAction( rRect, nHorzRound, nVertRound ) ); + + if ( !IsDeviceOutputNecessary() || (!mbLineColor && !mbFillColor) || ImplIsRecordLayout() ) + return; + + const tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) ); + + if ( aRect.IsEmpty() ) + return; + + nHorzRound = ImplLogicWidthToDevicePixel( nHorzRound ); + nVertRound = ImplLogicHeightToDevicePixel( nVertRound ); + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if ( mbInitLineColor ) + InitLineColor(); + + if ( mbInitFillColor ) + InitFillColor(); + + if ( !nHorzRound && !nVertRound ) + { + mpGraphics->DrawRect( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), this ); + } + else + { + tools::Polygon aRoundRectPoly( aRect, nHorzRound, nVertRound ); + + if ( aRoundRectPoly.GetSize() >= 2 ) + { + SalPoint* pPtAry = reinterpret_cast<SalPoint*>(aRoundRectPoly.GetPointAry()); + + if ( !mbFillColor ) + mpGraphics->DrawPolyLine( aRoundRectPoly.GetSize(), pPtAry, this ); + else + mpGraphics->DrawPolygon( aRoundRectPoly.GetSize(), pPtAry, this ); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawRect( rRect, nHorzRound, nVertRound ); +} + +void OutputDevice::Invert( const tools::Rectangle& rRect, InvertFlags nFlags ) +{ + assert(!is_double_buffered_window()); + if ( !IsDeviceOutputNecessary() ) + return; + + tools::Rectangle aRect( ImplLogicToDevicePixel( rRect ) ); + + if ( aRect.IsEmpty() ) + return; + aRect.Justify(); + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + SalInvert nSalFlags = SalInvert::NONE; + if ( nFlags & InvertFlags::N50 ) + nSalFlags |= SalInvert::N50; + if ( nFlags & InvertFlags::TrackFrame ) + nSalFlags |= SalInvert::TrackFrame; + mpGraphics->Invert( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), nSalFlags, this ); +} + +void OutputDevice::Invert( const tools::Polygon& rPoly, InvertFlags nFlags ) +{ + assert(!is_double_buffered_window()); + if ( !IsDeviceOutputNecessary() ) + return; + + sal_uInt16 nPoints = rPoly.GetSize(); + + if ( nPoints < 2 ) + return; + + tools::Polygon aPoly( ImplLogicToDevicePixel( rPoly ) ); + + // we need a graphics + if ( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + SalInvert nSalFlags = SalInvert::NONE; + if ( nFlags & InvertFlags::N50 ) + nSalFlags |= SalInvert::N50; + if ( nFlags & InvertFlags::TrackFrame ) + nSalFlags |= SalInvert::TrackFrame; + const SalPoint* pPtAry = reinterpret_cast<const SalPoint*>(aPoly.GetConstPointAry()); + mpGraphics->Invert( nPoints, pPtAry, nSalFlags, this ); +} + +void OutputDevice::DrawCheckered(const Point& rPos, const Size& rSize, sal_uInt32 nLen, Color aStart, Color aEnd) +{ + assert(!is_double_buffered_window()); + + const sal_uInt32 nMaxX(rPos.X() + rSize.Width()); + const sal_uInt32 nMaxY(rPos.Y() + rSize.Height()); + + Push(PushFlags::LINECOLOR|PushFlags::FILLCOLOR); + SetLineColor(); + + for(sal_uInt32 x(0), nX(rPos.X()); nX < nMaxX; x++, nX += nLen) + { + const sal_uInt32 nRight(std::min(nMaxX, nX + nLen)); + + for(sal_uInt32 y(0), nY(rPos.Y()); nY < nMaxY; y++, nY += nLen) + { + const sal_uInt32 nBottom(std::min(nMaxY, nY + nLen)); + + SetFillColor(((x & 0x0001) ^ (y & 0x0001)) ? aStart : aEnd); + DrawRect(tools::Rectangle(nX, nY, nRight, nBottom)); + } + } + + Pop(); +} + +void OutputDevice::DrawGrid( const tools::Rectangle& rRect, const Size& rDist, DrawGridFlags nFlags ) +{ + assert(!is_double_buffered_window()); + + tools::Rectangle aDstRect( PixelToLogic( Point() ), GetOutputSize() ); + aDstRect.Intersection( rRect ); + + if( aDstRect.IsEmpty() || ImplIsRecordLayout() ) + return; + + if( !mpGraphics && !AcquireGraphics() ) + return; + + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return; + + const long nDistX = std::max( rDist.Width(), 1L ); + const long nDistY = std::max( rDist.Height(), 1L ); + long nX = ( rRect.Left() >= aDstRect.Left() ) ? rRect.Left() : ( rRect.Left() + ( ( aDstRect.Left() - rRect.Left() ) / nDistX ) * nDistX ); + long nY = ( rRect.Top() >= aDstRect.Top() ) ? rRect.Top() : ( rRect.Top() + ( ( aDstRect.Top() - rRect.Top() ) / nDistY ) * nDistY ); + const long nRight = aDstRect.Right(); + const long nBottom = aDstRect.Bottom(); + const long nStartX = ImplLogicXToDevicePixel( nX ); + const long nEndX = ImplLogicXToDevicePixel( nRight ); + const long nStartY = ImplLogicYToDevicePixel( nY ); + const long nEndY = ImplLogicYToDevicePixel( nBottom ); + long nHorzCount = 0; + long nVertCount = 0; + + std::vector< sal_Int32 > aVertBuf; + std::vector< sal_Int32 > aHorzBuf; + + if( ( nFlags & DrawGridFlags::Dots ) || ( nFlags & DrawGridFlags::HorzLines ) ) + { + aVertBuf.resize( aDstRect.GetHeight() / nDistY + 2 ); + aVertBuf[ nVertCount++ ] = nStartY; + while( ( nY += nDistY ) <= nBottom ) + { + aVertBuf[ nVertCount++ ] = ImplLogicYToDevicePixel( nY ); + } + } + + if( ( nFlags & DrawGridFlags::Dots ) || ( nFlags & DrawGridFlags::VertLines ) ) + { + aHorzBuf.resize( aDstRect.GetWidth() / nDistX + 2 ); + aHorzBuf[ nHorzCount++ ] = nStartX; + while( ( nX += nDistX ) <= nRight ) + { + aHorzBuf[ nHorzCount++ ] = ImplLogicXToDevicePixel( nX ); + } + } + + if( mbInitLineColor ) + InitLineColor(); + + if( mbInitFillColor ) + InitFillColor(); + + const bool bOldMap = mbMap; + EnableMapMode( false ); + + if( nFlags & DrawGridFlags::Dots ) + { + for( long i = 0; i < nVertCount; i++ ) + { + for( long j = 0, Y = aVertBuf[ i ]; j < nHorzCount; j++ ) + { + mpGraphics->DrawPixel( aHorzBuf[ j ], Y, this ); + } + } + } + else + { + if( nFlags & DrawGridFlags::HorzLines ) + { + for( long i = 0; i < nVertCount; i++ ) + { + nY = aVertBuf[ i ]; + mpGraphics->DrawLine( nStartX, nY, nEndX, nY, this ); + } + } + + if( nFlags & DrawGridFlags::VertLines ) + { + for( long i = 0; i < nHorzCount; i++ ) + { + nX = aHorzBuf[ i ]; + mpGraphics->DrawLine( nX, nStartY, nX, nEndY, this ); + } + } + } + + EnableMapMode( bOldMap ); + + if( mpAlphaVDev ) + mpAlphaVDev->DrawGrid( rRect, rDist, nFlags ); +} + +BmpMirrorFlags AdjustTwoRect( SalTwoRect& rTwoRect, const Size& rSizePix ) +{ + BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE; + + if ( rTwoRect.mnDestWidth < 0 ) + { + rTwoRect.mnSrcX = rSizePix.Width() - rTwoRect.mnSrcX - rTwoRect.mnSrcWidth; + rTwoRect.mnDestWidth = -rTwoRect.mnDestWidth; + rTwoRect.mnDestX -= rTwoRect.mnDestWidth-1; + nMirrFlags |= BmpMirrorFlags::Horizontal; + } + + if ( rTwoRect.mnDestHeight < 0 ) + { + rTwoRect.mnSrcY = rSizePix.Height() - rTwoRect.mnSrcY - rTwoRect.mnSrcHeight; + rTwoRect.mnDestHeight = -rTwoRect.mnDestHeight; + rTwoRect.mnDestY -= rTwoRect.mnDestHeight-1; + nMirrFlags |= BmpMirrorFlags::Vertical; + } + + if( ( rTwoRect.mnSrcX < 0 ) || ( rTwoRect.mnSrcX >= rSizePix.Width() ) || + ( rTwoRect.mnSrcY < 0 ) || ( rTwoRect.mnSrcY >= rSizePix.Height() ) || + ( ( rTwoRect.mnSrcX + rTwoRect.mnSrcWidth ) > rSizePix.Width() ) || + ( ( rTwoRect.mnSrcY + rTwoRect.mnSrcHeight ) > rSizePix.Height() ) ) + { + const tools::Rectangle aSourceRect( Point( rTwoRect.mnSrcX, rTwoRect.mnSrcY ), + Size( rTwoRect.mnSrcWidth, rTwoRect.mnSrcHeight ) ); + tools::Rectangle aCropRect( aSourceRect ); + + aCropRect.Intersection( tools::Rectangle( Point(), rSizePix ) ); + + if( aCropRect.IsEmpty() ) + { + rTwoRect.mnSrcWidth = rTwoRect.mnSrcHeight = rTwoRect.mnDestWidth = rTwoRect.mnDestHeight = 0; + } + else + { + const double fFactorX = ( rTwoRect.mnSrcWidth > 1 ) ? static_cast<double>( rTwoRect.mnDestWidth - 1 ) / ( rTwoRect.mnSrcWidth - 1 ) : 0.0; + const double fFactorY = ( rTwoRect.mnSrcHeight > 1 ) ? static_cast<double>( rTwoRect.mnDestHeight - 1 ) / ( rTwoRect.mnSrcHeight - 1 ) : 0.0; + + const long nDstX1 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Left() - rTwoRect.mnSrcX ) ); + const long nDstY1 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Top() - rTwoRect.mnSrcY ) ); + const long nDstX2 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Right() - rTwoRect.mnSrcX ) ); + const long nDstY2 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Bottom() - rTwoRect.mnSrcY ) ); + + rTwoRect.mnSrcX = aCropRect.Left(); + rTwoRect.mnSrcY = aCropRect.Top(); + rTwoRect.mnSrcWidth = aCropRect.GetWidth(); + rTwoRect.mnSrcHeight = aCropRect.GetHeight(); + rTwoRect.mnDestX = nDstX1; + rTwoRect.mnDestY = nDstY1; + rTwoRect.mnDestWidth = nDstX2 - nDstX1 + 1; + rTwoRect.mnDestHeight = nDstY2 - nDstY1 + 1; + } + } + + return nMirrFlags; +} + +void AdjustTwoRect( SalTwoRect& rTwoRect, const tools::Rectangle& rValidSrcRect ) +{ + if( ( rTwoRect.mnSrcX < rValidSrcRect.Left() ) || ( rTwoRect.mnSrcX >= rValidSrcRect.Right() ) || + ( rTwoRect.mnSrcY < rValidSrcRect.Top() ) || ( rTwoRect.mnSrcY >= rValidSrcRect.Bottom() ) || + ( ( rTwoRect.mnSrcX + rTwoRect.mnSrcWidth ) > rValidSrcRect.Right() ) || + ( ( rTwoRect.mnSrcY + rTwoRect.mnSrcHeight ) > rValidSrcRect.Bottom() ) ) + { + const tools::Rectangle aSourceRect( Point( rTwoRect.mnSrcX, rTwoRect.mnSrcY ), + Size( rTwoRect.mnSrcWidth, rTwoRect.mnSrcHeight ) ); + tools::Rectangle aCropRect( aSourceRect ); + + aCropRect.Intersection( rValidSrcRect ); + + if( aCropRect.IsEmpty() ) + { + rTwoRect.mnSrcWidth = rTwoRect.mnSrcHeight = rTwoRect.mnDestWidth = rTwoRect.mnDestHeight = 0; + } + else + { + const double fFactorX = ( rTwoRect.mnSrcWidth > 1 ) ? static_cast<double>( rTwoRect.mnDestWidth - 1 ) / ( rTwoRect.mnSrcWidth - 1 ) : 0.0; + const double fFactorY = ( rTwoRect.mnSrcHeight > 1 ) ? static_cast<double>( rTwoRect.mnDestHeight - 1 ) / ( rTwoRect.mnSrcHeight - 1 ) : 0.0; + + const long nDstX1 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Left() - rTwoRect.mnSrcX ) ); + const long nDstY1 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Top() - rTwoRect.mnSrcY ) ); + const long nDstX2 = rTwoRect.mnDestX + FRound( fFactorX * ( aCropRect.Right() - rTwoRect.mnSrcX ) ); + const long nDstY2 = rTwoRect.mnDestY + FRound( fFactorY * ( aCropRect.Bottom() - rTwoRect.mnSrcY ) ); + + rTwoRect.mnSrcX = aCropRect.Left(); + rTwoRect.mnSrcY = aCropRect.Top(); + rTwoRect.mnSrcWidth = aCropRect.GetWidth(); + rTwoRect.mnSrcHeight = aCropRect.GetHeight(); + rTwoRect.mnDestX = nDstX1; + rTwoRect.mnDestY = nDstY1; + rTwoRect.mnDestWidth = nDstX2 - nDstX1 + 1; + rTwoRect.mnDestHeight = nDstY2 - nDstY1 + 1; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/text.cxx b/vcl/source/outdev/text.cxx new file mode 100644 index 000000000..34db8e629 --- /dev/null +++ b/vcl/source/outdev/text.cxx @@ -0,0 +1,2510 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <basegfx/matrix/b2dhommatrix.hxx> + +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/linguistic2/LinguServiceManager.hpp> + +#include <comphelper/processfactory.hxx> +#include <osl/file.h> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <tools/lineend.hxx> +#include <tools/debug.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <vcl/metric.hxx> +#include <vcl/textrectinfo.hxx> +#include <vcl/virdev.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/unohelp.hxx> +#include <vcl/toolkit/controllayout.hxx> +#ifdef MACOSX +# include <vcl/opengl/OpenGLHelper.hxx> +#endif + +#include <outdata.hxx> +#include <outdev.h> +#include <salgdi.hxx> +#include <svdata.hxx> +#include <textlayout.hxx> +#include <textlineinfo.hxx> +#include <impglyphitem.hxx> +#include <optional> + +#define TEXT_DRAW_ELLIPSIS (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) + +ImplMultiTextLineInfo::ImplMultiTextLineInfo() +{ +} + +ImplMultiTextLineInfo::~ImplMultiTextLineInfo() +{ +} + +void ImplMultiTextLineInfo::AddLine( ImplTextLineInfo* pLine ) +{ + mvLines.push_back(std::unique_ptr<ImplTextLineInfo>(pLine)); +} + +void ImplMultiTextLineInfo::Clear() +{ + mvLines.clear(); +} + +void OutputDevice::ImplInitTextColor() +{ + DBG_TESTSOLARMUTEX(); + + if ( mbInitTextColor ) + { + mpGraphics->SetTextColor( GetTextColor() ); + mbInitTextColor = false; + } +} + +void OutputDevice::ImplDrawTextRect( long nBaseX, long nBaseY, + long nDistX, long nDistY, long nWidth, long nHeight ) +{ + long nX = nDistX; + long nY = nDistY; + + short nOrientation = mpFontInstance->mnOrientation; + if ( nOrientation ) + { + // Rotate rect without rounding problems for 90 degree rotations + if ( !(nOrientation % 900) ) + { + if ( nOrientation == 900 ) + { + long nTemp = nX; + nX = nY; + nY = -nTemp; + nTemp = nWidth; + nWidth = nHeight; + nHeight = nTemp; + nY -= nHeight; + } + else if ( nOrientation == 1800 ) + { + nX = -nX; + nY = -nY; + nX -= nWidth; + nY -= nHeight; + } + else /* ( nOrientation == 2700 ) */ + { + long nTemp = nX; + nX = -nY; + nY = nTemp; + nTemp = nWidth; + nWidth = nHeight; + nHeight = nTemp; + nX -= nWidth; + } + } + else + { + nX += nBaseX; + nY += nBaseY; + // inflate because polygons are drawn smaller + tools::Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) ); + tools::Polygon aPoly( aRect ); + aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontInstance->mnOrientation ); + ImplDrawPolygon( aPoly ); + return; + } + } + + nX += nBaseX; + nY += nBaseY; + mpGraphics->DrawRect( nX, nY, nWidth, nHeight, this ); // original code + +} + +void OutputDevice::ImplDrawTextBackground( const SalLayout& rSalLayout ) +{ + const long nWidth = rSalLayout.GetTextWidth() / rSalLayout.GetUnitsPerPixel(); + const Point aBase = rSalLayout.DrawBase(); + const long nX = aBase.X(); + const long nY = aBase.Y(); + + if ( mbLineColor || mbInitLineColor ) + { + mpGraphics->SetLineColor(); + mbInitLineColor = true; + } + mpGraphics->SetFillColor( GetTextFillColor() ); + mbInitFillColor = true; + + ImplDrawTextRect( nX, nY, 0, -(mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent), + nWidth, + mpFontInstance->mnLineHeight+mnEmphasisAscent+mnEmphasisDescent ); +} + +tools::Rectangle OutputDevice::ImplGetTextBoundRect( const SalLayout& rSalLayout ) +{ + Point aPoint = rSalLayout.GetDrawPosition(); + long nX = aPoint.X(); + long nY = aPoint.Y(); + + long nWidth = rSalLayout.GetTextWidth(); + long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent; + + nY -= mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent; + + if ( mpFontInstance->mnOrientation ) + { + long nBaseX = nX, nBaseY = nY; + if ( !(mpFontInstance->mnOrientation % 900) ) + { + long nX2 = nX+nWidth; + long nY2 = nY+nHeight; + + Point aBasePt( nBaseX, nBaseY ); + aBasePt.RotateAround( nX, nY, mpFontInstance->mnOrientation ); + aBasePt.RotateAround( nX2, nY2, mpFontInstance->mnOrientation ); + nWidth = nX2-nX; + nHeight = nY2-nY; + } + else + { + // inflate by +1+1 because polygons are drawn smaller + tools::Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) ); + tools::Polygon aPoly( aRect ); + aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontInstance->mnOrientation ); + return aPoly.GetBoundRect(); + } + } + + return tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ); +} + +bool OutputDevice::ImplDrawRotateText( SalLayout& rSalLayout ) +{ + long nX = rSalLayout.DrawBase().X(); + long nY = rSalLayout.DrawBase().Y(); + + tools::Rectangle aBoundRect; + rSalLayout.DrawBase() = Point( 0, 0 ); + rSalLayout.DrawOffset() = Point( 0, 0 ); + if (!rSalLayout.GetBoundRect(aBoundRect)) + { + // guess vertical text extents if GetBoundRect failed + long nRight = rSalLayout.GetTextWidth(); + long nTop = mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent; + long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent; + aBoundRect = tools::Rectangle( 0, -nTop, nRight, nHeight - nTop ); + } + + // cache virtual device for rotation + if (!mpOutDevData->mpRotateDev) + mpOutDevData->mpRotateDev = VclPtr<VirtualDevice>::Create(*this, DeviceFormat::BITMASK); + VirtualDevice* pVDev = mpOutDevData->mpRotateDev; + + // size it accordingly + if( !pVDev->SetOutputSizePixel( aBoundRect.GetSize() ) ) + return false; + + const FontSelectPattern& rPattern = mpFontInstance->GetFontSelectPattern(); + vcl::Font aFont( GetFont() ); + aFont.SetOrientation( 0 ); + aFont.SetFontSize( Size( rPattern.mnWidth, rPattern.mnHeight ) ); + pVDev->SetFont( aFont ); + pVDev->SetTextColor( COL_BLACK ); + pVDev->SetTextFillColor(); + if (!pVDev->InitFont()) + return false; + pVDev->ImplInitTextColor(); + + // draw text into upper left corner + rSalLayout.DrawBase() -= aBoundRect.TopLeft(); + rSalLayout.DrawText( *pVDev->mpGraphics ); + + Bitmap aBmp = pVDev->GetBitmap( Point(), aBoundRect.GetSize() ); + if ( !aBmp || !aBmp.Rotate( mpFontInstance->mnOwnOrientation, COL_WHITE ) ) + return false; + + // calculate rotation offset + tools::Polygon aPoly( aBoundRect ); + aPoly.Rotate( Point(), mpFontInstance->mnOwnOrientation ); + Point aPoint = aPoly.GetBoundRect().TopLeft(); + aPoint += Point( nX, nY ); + + // mask output with text colored bitmap + GDIMetaFile* pOldMetaFile = mpMetaFile; + long nOldOffX = mnOutOffX; + long nOldOffY = mnOutOffY; + bool bOldMap = mbMap; + + mnOutOffX = 0; + mnOutOffY = 0; + mpMetaFile = nullptr; + EnableMapMode( false ); + + DrawMask( aPoint, aBmp, GetTextColor() ); + + EnableMapMode( bOldMap ); + mnOutOffX = nOldOffX; + mnOutOffY = nOldOffY; + mpMetaFile = pOldMetaFile; + + return true; +} + +void OutputDevice::ImplDrawTextDirect( SalLayout& rSalLayout, + bool bTextLines) +{ + if( mpFontInstance->mnOwnOrientation ) + if( ImplDrawRotateText( rSalLayout ) ) + return; + + long nOldX = rSalLayout.DrawBase().X(); + if( HasMirroredGraphics() ) + { + long w = IsVirtual() ? mnOutWidth : mpGraphics->GetGraphicsWidth(); + long x = rSalLayout.DrawBase().X(); + rSalLayout.DrawBase().setX( w - 1 - x ); + if( !IsRTLEnabled() ) + { + OutputDevice *pOutDevRef = this; + // mirror this window back + long devX = w-pOutDevRef->mnOutWidth-pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX + rSalLayout.DrawBase().setX( devX + ( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().X() - devX) ) ) ; + } + } + else if( IsRTLEnabled() ) + { + OutputDevice *pOutDevRef = this; + + // mirror this window back + long devX = pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX + rSalLayout.DrawBase().setX( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().X() - devX) + devX ); + } + + rSalLayout.DrawText( *mpGraphics ); + rSalLayout.DrawBase().setX( nOldX ); + + if( bTextLines ) + ImplDrawTextLines( rSalLayout, + maFont.GetStrikeout(), maFont.GetUnderline(), maFont.GetOverline(), + maFont.IsWordLineMode(), maFont.IsUnderlineAbove() ); + + // emphasis marks + if( maFont.GetEmphasisMark() & FontEmphasisMark::Style ) + ImplDrawEmphasisMarks( rSalLayout ); +} + +void OutputDevice::ImplDrawSpecialText( SalLayout& rSalLayout ) +{ + Color aOldColor = GetTextColor(); + Color aOldTextLineColor = GetTextLineColor(); + Color aOldOverlineColor = GetOverlineColor(); + FontRelief eRelief = maFont.GetRelief(); + + Point aOrigPos = rSalLayout.DrawBase(); + if ( eRelief != FontRelief::NONE ) + { + Color aReliefColor( COL_LIGHTGRAY ); + Color aTextColor( aOldColor ); + + Color aTextLineColor( aOldTextLineColor ); + Color aOverlineColor( aOldOverlineColor ); + + // we don't have an automatic color, so black is always drawn on white + if ( aTextColor == COL_BLACK ) + aTextColor = COL_WHITE; + if ( aTextLineColor == COL_BLACK ) + aTextLineColor = COL_WHITE; + if ( aOverlineColor == COL_BLACK ) + aOverlineColor = COL_WHITE; + + // relief-color is black for white text, in all other cases + // we set this to LightGray + // coverity[copy_paste_error: FALSE] - this is intentional + if ( aTextColor == COL_WHITE ) + aReliefColor = COL_BLACK; + SetTextLineColor( aReliefColor ); + SetOverlineColor( aReliefColor ); + SetTextColor( aReliefColor ); + ImplInitTextColor(); + + // calculate offset - for high resolution printers the offset + // should be greater so that the effect is visible + long nOff = 1; + nOff += mnDPIX/300; + + if ( eRelief == FontRelief::Engraved ) + nOff = -nOff; + rSalLayout.DrawOffset() += Point( nOff, nOff); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawOffset() -= Point( nOff, nOff); + + SetTextLineColor( aTextLineColor ); + SetOverlineColor( aOverlineColor ); + SetTextColor( aTextColor ); + ImplInitTextColor(); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + + SetTextLineColor( aOldTextLineColor ); + SetOverlineColor( aOldOverlineColor ); + + if ( aTextColor != aOldColor ) + { + SetTextColor( aOldColor ); + ImplInitTextColor(); + } + } + else + { + if ( maFont.IsShadow() ) + { + long nOff = 1 + ((mpFontInstance->mnLineHeight-24)/24); + if ( maFont.IsOutline() ) + nOff++; + SetTextLineColor(); + SetOverlineColor(); + if ( (GetTextColor() == COL_BLACK) + || (GetTextColor().GetLuminance() < 8) ) + SetTextColor( COL_LIGHTGRAY ); + else + SetTextColor( COL_BLACK ); + ImplInitTextColor(); + rSalLayout.DrawBase() += Point( nOff, nOff ); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() -= Point( nOff, nOff ); + SetTextColor( aOldColor ); + SetTextLineColor( aOldTextLineColor ); + SetOverlineColor( aOldOverlineColor ); + ImplInitTextColor(); + + if ( !maFont.IsOutline() ) + ImplDrawTextDirect( rSalLayout, mbTextLines ); + } + + if ( maFont.IsOutline() ) + { + rSalLayout.DrawBase() = aOrigPos + Point(-1,-1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + Point(+1,+1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + Point(-1,+0); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + Point(-1,+1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + Point(+0,+1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + Point(+0,-1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + Point(+1,-1); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos + Point(+1,+0); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + rSalLayout.DrawBase() = aOrigPos; + + SetTextColor( COL_WHITE ); + SetTextLineColor( COL_WHITE ); + SetOverlineColor( COL_WHITE ); + ImplInitTextColor(); + ImplDrawTextDirect( rSalLayout, mbTextLines ); + SetTextColor( aOldColor ); + SetTextLineColor( aOldTextLineColor ); + SetOverlineColor( aOldOverlineColor ); + ImplInitTextColor(); + } + } +} + +void OutputDevice::ImplDrawText( SalLayout& rSalLayout ) +{ + + if( mbInitClipRegion ) + InitClipRegion(); + if( mbOutputClipped ) + return; + if( mbInitTextColor ) + ImplInitTextColor(); + + rSalLayout.DrawBase() += Point( mnTextOffX, mnTextOffY ); + + if( IsTextFillColor() ) + ImplDrawTextBackground( rSalLayout ); + + if( mbTextSpecial ) + ImplDrawSpecialText( rSalLayout ); + else + ImplDrawTextDirect( rSalLayout, mbTextLines ); +} + +long OutputDevice::ImplGetTextLines( ImplMultiTextLineInfo& rLineInfo, + long nWidth, const OUString& rStr, + DrawTextFlags nStyle, const vcl::ITextLayout& _rLayout ) +{ + SAL_WARN_IF( nWidth <= 0, "vcl", "ImplGetTextLines: nWidth <= 0!" ); + + if ( nWidth <= 0 ) + nWidth = 1; + + long nMaxLineWidth = 0; + rLineInfo.Clear(); + if (!rStr.isEmpty()) + { + const bool bHyphenate = (nStyle & DrawTextFlags::WordBreakHyphenation) == DrawTextFlags::WordBreakHyphenation; + css::uno::Reference< css::linguistic2::XHyphenator > xHyph; + if (bHyphenate) + { + // get service provider + css::uno::Reference<css::uno::XComponentContext> xContext(comphelper::getProcessComponentContext()); + css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLinguMgr = css::linguistic2::LinguServiceManager::create(xContext); + xHyph = xLinguMgr->getHyphenator(); + } + + css::uno::Reference<css::i18n::XBreakIterator> xBI; + sal_Int32 nPos = 0; + sal_Int32 nLen = rStr.getLength(); + while ( nPos < nLen ) + { + sal_Int32 nBreakPos = nPos; + + while ( ( nBreakPos < nLen ) && ( rStr[ nBreakPos ] != '\r' ) && ( rStr[ nBreakPos ] != '\n' ) ) + nBreakPos++; + + long nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos ); + if ( ( nLineWidth > nWidth ) && ( nStyle & DrawTextFlags::WordBreak ) ) + { + if ( !xBI.is() ) + xBI = vcl::unohelper::CreateBreakIterator(); + + if ( xBI.is() ) + { + const css::lang::Locale& rDefLocale(Application::GetSettings().GetUILanguageTag().getLocale()); + sal_Int32 nSoftBreak = _rLayout.GetTextBreak( rStr, nWidth, nPos, nBreakPos - nPos ); + if (nSoftBreak == -1) + { + nSoftBreak = nPos; + } + SAL_WARN_IF( nSoftBreak >= nBreakPos, "vcl", "Break?!" ); + css::i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, css::uno::Sequence <css::beans::PropertyValue>(), 1 ); + css::i18n::LineBreakUserOptions aUserOptions; + css::i18n::LineBreakResults aLBR = xBI->getLineBreak( rStr, nSoftBreak, rDefLocale, nPos, aHyphOptions, aUserOptions ); + nBreakPos = aLBR.breakIndex; + if ( nBreakPos <= nPos ) + nBreakPos = nSoftBreak; + if ( bHyphenate ) + { + // Whether hyphen or not: Put the word after the hyphen through + // word boundary. + + // nMaxBreakPos the last char that fits into the line + // nBreakPos is the word's start + + // We run into a problem if the doc is so narrow, that a word + // is broken into more than two lines ... + if ( xHyph.is() ) + { + sal_Unicode cAlternateReplChar = 0; + css::i18n::Boundary aBoundary = xBI->getWordBoundary( rStr, nBreakPos, rDefLocale, css::i18n::WordType::DICTIONARY_WORD, true ); + sal_Int32 nWordStart = nPos; + sal_Int32 nWordEnd = aBoundary.endPos; + SAL_WARN_IF( nWordEnd <= nWordStart, "vcl", "ImpBreakLine: Start >= End?" ); + + sal_Int32 nWordLen = nWordEnd - nWordStart; + if ( ( nWordEnd >= nSoftBreak ) && ( nWordLen > 3 ) ) + { + // #104415# May happen, because getLineBreak may differ from getWordBoudary with DICTIONARY_WORD + // SAL_WARN_IF( nWordEnd < nMaxBreakPos, "vcl", "Hyph: Break?" ); + OUString aWord = rStr.copy( nWordStart, nWordLen ); + sal_Int32 nMinTrail = nWordEnd-nSoftBreak+1; //+1: Before the "broken off" char + css::uno::Reference< css::linguistic2::XHyphenatedWord > xHyphWord; + if (xHyph.is()) + xHyphWord = xHyph->hyphenate( aWord, rDefLocale, aWord.getLength() - nMinTrail, css::uno::Sequence< css::beans::PropertyValue >() ); + if (xHyphWord.is()) + { + bool bAlternate = xHyphWord->isAlternativeSpelling(); + sal_Int32 _nWordLen = 1 + xHyphWord->getHyphenPos(); + + if ( ( _nWordLen >= 2 ) && ( (nWordStart+_nWordLen) >= 2 ) ) + { + if ( !bAlternate ) + { + nBreakPos = nWordStart + _nWordLen; + } + else + { + OUString aAlt( xHyphWord->getHyphenatedWord() ); + + // We can have two cases: + // 1) "packen" turns into "pak-ken" + // 2) "Schiffahrt" turns into "Schiff-fahrt" + + // In case 1 we need to replace a char + // In case 2 we add a char + + // Correct recognition is made harder by words such as + // "Schiffahrtsbrennesseln", as the Hyphenator splits all + // positions of the word and comes up with "Schifffahrtsbrennnesseln" + // Thus, we cannot infer the aWord from the AlternativeWord's + // index. + // TODO: The whole junk will be made easier by a function in + // the Hyphenator, as soon as AMA adds it. + sal_Int32 nAltStart = _nWordLen - 1; + sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength()); + sal_Int32 nTxtEnd = nTxtStart; + sal_Int32 nAltEnd = nAltStart; + + // The area between nStart and nEnd is the difference + // between AlternativeString and OriginalString + while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() && + aWord[nTxtEnd] != aAlt[nAltEnd] ) + { + ++nTxtEnd; + ++nAltEnd; + } + + // If a char was added, we notice it now: + if( nAltEnd > nTxtEnd && nAltStart == nAltEnd && + aWord[ nTxtEnd ] == aAlt[nAltEnd] ) + { + ++nAltEnd; + ++nTxtStart; + ++nTxtEnd; + } + + SAL_WARN_IF( ( nAltEnd - nAltStart ) != 1, "vcl", "Alternate: Wrong assumption!" ); + + if ( nTxtEnd > nTxtStart ) + cAlternateReplChar = aAlt[ nAltStart ]; + + nBreakPos = nWordStart + nTxtStart; + if ( cAlternateReplChar ) + nBreakPos++; + } + } + } + } + } + } + nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos ); + } + else + { + // fallback to something really simple + sal_Int32 nSpacePos = rStr.getLength(); + long nW = 0; + do + { + nSpacePos = rStr.lastIndexOf( ' ', nSpacePos ); + if( nSpacePos != -1 ) + { + if( nSpacePos > nPos ) + nSpacePos--; + nW = _rLayout.GetTextWidth( rStr, nPos, nSpacePos-nPos ); + } + } while( nW > nWidth ); + + if( nSpacePos != -1 ) + { + nBreakPos = nSpacePos; + nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos ); + if( nBreakPos < rStr.getLength()-1 ) + nBreakPos++; + } + } + } + + if ( nLineWidth > nMaxLineWidth ) + nMaxLineWidth = nLineWidth; + + rLineInfo.AddLine( new ImplTextLineInfo( nLineWidth, nPos, nBreakPos-nPos ) ); + + if ( nBreakPos == nPos ) + nBreakPos++; + nPos = nBreakPos; + + if ( nPos < nLen && ( ( rStr[ nPos ] == '\r' ) || ( rStr[ nPos ] == '\n' ) ) ) + { + nPos++; + // CR/LF? + if ( ( nPos < nLen ) && ( rStr[ nPos ] == '\n' ) && ( rStr[ nPos-1 ] == '\r' ) ) + nPos++; + } + } + } +#ifdef DBG_UTIL + for ( sal_Int32 nL = 0; nL < rLineInfo.Count(); nL++ ) + { + ImplTextLineInfo* pLine = rLineInfo.GetLine( nL ); + OUString aLine = rStr.copy( pLine->GetIndex(), pLine->GetLen() ); + SAL_WARN_IF( aLine.indexOf( '\r' ) != -1, "vcl", "ImplGetTextLines - Found CR!" ); + SAL_WARN_IF( aLine.indexOf( '\n' ) != -1, "vcl", "ImplGetTextLines - Found LF!" ); + } +#endif + + return nMaxLineWidth; +} + +void OutputDevice::SetTextColor( const Color& rColor ) +{ + + Color aColor( rColor ); + + if ( mnDrawMode & ( DrawModeFlags::BlackText | DrawModeFlags::WhiteText | + DrawModeFlags::GrayText | + DrawModeFlags::SettingsText ) ) + { + if ( mnDrawMode & DrawModeFlags::BlackText ) + aColor = COL_BLACK; + else if ( mnDrawMode & DrawModeFlags::WhiteText ) + aColor = COL_WHITE; + else if ( mnDrawMode & DrawModeFlags::GrayText ) + { + const sal_uInt8 cLum = aColor.GetLuminance(); + aColor = Color( cLum, cLum, cLum ); + } + else if ( mnDrawMode & DrawModeFlags::SettingsText ) + aColor = GetSettings().GetStyleSettings().GetFontColor(); + } + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextColorAction( aColor ) ); + + if ( maTextColor != aColor ) + { + maTextColor = aColor; + mbInitTextColor = true; + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextColor( COL_BLACK ); +} + +void OutputDevice::SetTextFillColor() +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextFillColorAction( Color(), false ) ); + + if ( maFont.GetColor() != COL_TRANSPARENT ) { + maFont.SetFillColor( COL_TRANSPARENT ); + } + if ( !maFont.IsTransparent() ) + maFont.SetTransparent( true ); + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextFillColor(); +} + +void OutputDevice::SetTextFillColor( const Color& rColor ) +{ + Color aColor( rColor ); + bool bTransFill = ImplIsColorTransparent( aColor ); + + if ( !bTransFill ) + { + if ( mnDrawMode & ( DrawModeFlags::BlackFill | DrawModeFlags::WhiteFill | + DrawModeFlags::GrayFill | DrawModeFlags::NoFill | + DrawModeFlags::SettingsFill ) ) + { + if ( mnDrawMode & DrawModeFlags::BlackFill ) + aColor = COL_BLACK; + else if ( mnDrawMode & DrawModeFlags::WhiteFill ) + aColor = COL_WHITE; + else if ( mnDrawMode & DrawModeFlags::GrayFill ) + { + const sal_uInt8 cLum = aColor.GetLuminance(); + aColor = Color( cLum, cLum, cLum ); + } + else if( mnDrawMode & DrawModeFlags::SettingsFill ) + aColor = GetSettings().GetStyleSettings().GetWindowColor(); + else if ( mnDrawMode & DrawModeFlags::NoFill ) + { + aColor = COL_TRANSPARENT; + bTransFill = true; + } + } + } + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextFillColorAction( aColor, true ) ); + + if ( maFont.GetFillColor() != aColor ) + maFont.SetFillColor( aColor ); + if ( maFont.IsTransparent() != bTransFill ) + maFont.SetTransparent( bTransFill ); + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextFillColor( COL_BLACK ); +} + +Color OutputDevice::GetTextFillColor() const +{ + if ( maFont.IsTransparent() ) + return COL_TRANSPARENT; + else + return maFont.GetFillColor(); +} + +void OutputDevice::SetTextAlign( TextAlign eAlign ) +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextAlignAction( eAlign ) ); + + if ( maFont.GetAlignment() != eAlign ) + { + maFont.SetAlignment( eAlign ); + mbNewFont = true; + } + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextAlign( eAlign ); +} + +vcl::Region OutputDevice::GetOutputBoundsClipRegion() const +{ + return GetClipRegion(); +} + +void OutputDevice::DrawText( const Point& rStartPt, const OUString& rStr, + sal_Int32 nIndex, sal_Int32 nLen, + MetricVector* pVector, OUString* pDisplayText, + const SalLayoutGlyphs* pLayoutCache + ) +{ + assert(!is_double_buffered_window()); + + if( (nLen < 0) || (nIndex + nLen >= rStr.getLength())) + { + nLen = rStr.getLength() - nIndex; + } + + if (mpOutDevData->mpRecordLayout) + { + pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects; + pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText; + } + +#if OSL_DEBUG_LEVEL > 2 + SAL_INFO("vcl.gdi", "OutputDevice::DrawText(\"" << rStr << "\")"); +#endif + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextAction( rStartPt, rStr, nIndex, nLen ) ); + if( pVector ) + { + vcl::Region aClip(GetOutputBoundsClipRegion()); + + if (mpOutDevData->mpRecordLayout) + { + mpOutDevData->mpRecordLayout->m_aLineIndices.push_back( mpOutDevData->mpRecordLayout->m_aDisplayText.getLength() ); + aClip.Intersect( mpOutDevData->maRecordRect ); + } + if( ! aClip.IsNull() ) + { + MetricVector aTmp; + GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, aTmp ); + + bool bInserted = false; + for( MetricVector::const_iterator it = aTmp.begin(); it != aTmp.end(); ++it, nIndex++ ) + { + bool bAppend = false; + + if( aClip.IsOver( *it ) ) + bAppend = true; + else if( rStr[ nIndex ] == ' ' && bInserted ) + { + MetricVector::const_iterator next = it; + ++next; + if( next != aTmp.end() && aClip.IsOver( *next ) ) + bAppend = true; + } + + if( bAppend ) + { + pVector->push_back( *it ); + if( pDisplayText ) + *pDisplayText += OUStringChar(rStr[ nIndex ]); + bInserted = true; + } + } + } + else + { + GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, *pVector ); + if( pDisplayText ) + *pDisplayText += rStr.copy( nIndex, nLen ); + } + } + + if ( !IsDeviceOutputNecessary() || pVector ) + return; + + if(mpFontInstance) + // do not use cache with modified string + if(mpFontInstance->mpConversion) + pLayoutCache = nullptr; + +#ifdef MACOSX + // FIXME: tdf#112990 + // Cache text layout crashes on mac with OpenGL enabled + // Force it to not use the cache + if(OpenGLHelper::isVCLOpenGLEnabled()) + pLayoutCache = nullptr; +#endif + + std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, nullptr, SalLayoutFlags::NONE, nullptr, pLayoutCache); + if(pSalLayout) + { + ImplDrawText( *pSalLayout ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawText( rStartPt, rStr, nIndex, nLen, pVector, pDisplayText ); +} + +long OutputDevice::GetTextWidth( const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen, + vcl::TextLayoutCache const*const pLayoutCache, + SalLayoutGlyphs const*const pSalLayoutCache) const +{ + + long nWidth = GetTextArray( rStr, nullptr, nIndex, + nLen, pLayoutCache, pSalLayoutCache ); + + return nWidth; +} + +long OutputDevice::GetTextHeight() const +{ + if (!InitFont()) + return 0; + + long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent; + + if ( mbMap ) + nHeight = ImplDevicePixelToLogicHeight( nHeight ); + + return nHeight; +} + +float OutputDevice::approximate_char_width() const +{ + //note pango uses "The quick brown fox jumps over the lazy dog." for english + //and has a bunch of per-language strings which corresponds somewhat with + //makeRepresentativeText in include/svtools/sampletext.hxx + return GetTextWidth("aemnnxEM") / 8.0; +} + +float OutputDevice::approximate_digit_width() const +{ + return GetTextWidth("0123456789") / 10.0; +} + +void OutputDevice::DrawTextArray( const Point& rStartPt, const OUString& rStr, + const long* pDXAry, + sal_Int32 nIndex, sal_Int32 nLen, SalLayoutFlags flags, + const SalLayoutGlyphs* pSalLayoutCache ) +{ + assert(!is_double_buffered_window()); + + if( nLen < 0 || nIndex + nLen >= rStr.getLength() ) + { + nLen = rStr.getLength() - nIndex; + } + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, nIndex, nLen ) ); + + if ( !IsDeviceOutputNecessary() ) + return; + if( !mpGraphics && !AcquireGraphics() ) + return; + if( mbInitClipRegion ) + InitClipRegion(); + if( mbOutputClipped ) + return; + + std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry, flags, nullptr, pSalLayoutCache); + if( pSalLayout ) + { + ImplDrawText( *pSalLayout ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawTextArray( rStartPt, rStr, pDXAry, nIndex, nLen, flags ); +} + +long OutputDevice::GetTextArray( const OUString& rStr, long* pDXAry, + sal_Int32 nIndex, sal_Int32 nLen, + vcl::TextLayoutCache const*const pLayoutCache, + SalLayoutGlyphs const*const pSalLayoutCache) const +{ + if( nIndex >= rStr.getLength() ) + return 0; // TODO: this looks like a buggy caller? + + if( nLen < 0 || nIndex + nLen >= rStr.getLength() ) + { + nLen = rStr.getLength() - nIndex; + } + + // do layout + std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, + Point(0,0), 0, nullptr, SalLayoutFlags::NONE, pLayoutCache, pSalLayoutCache); + if( !pSalLayout ) + { + // The caller expects this to init the elements of pDXAry. + // Adapting all the callers to check that GetTextArray succeeded seems + // too much work. + // Init here to 0 only in the (rare) error case, so that any missing + // element init in the happy case will still be found by tools, + // and hope that is sufficient. + if (pDXAry) + { + memset(pDXAry, 0, nLen * sizeof(*pDXAry)); + } + return 0; + } + +#if VCL_FLOAT_DEVICE_PIXEL + std::unique_ptr<DeviceCoordinate[]> pDXPixelArray; + if(pDXAry) + { + pDXPixelArray.reset(new DeviceCoordinate[nLen]); + } + DeviceCoordinate nWidth = pSalLayout->FillDXArray( pDXPixelArray.get() ); + int nWidthFactor = pSalLayout->GetUnitsPerPixel(); + + // convert virtual char widths to virtual absolute positions + if( pDXPixelArray ) + { + for( int i = 1; i < nLen; ++i ) + { + pDXPixelArray[ i ] += pDXPixelArray[ i-1 ]; + } + } + if( mbMap ) + { + if( pDXPixelArray ) + { + for( int i = 0; i < nLen; ++i ) + { + pDXPixelArray[i] = ImplDevicePixelToLogicWidth( pDXPixelArray[i] ); + } + } + nWidth = ImplDevicePixelToLogicWidth( nWidth ); + } + if( nWidthFactor > 1 ) + { + if( pDXPixelArray ) + { + for( int i = 0; i < nLen; ++i ) + { + pDXPixelArray[i] /= nWidthFactor; + } + } + nWidth /= nWidthFactor; + } + if(pDXAry) + { + for( int i = 0; i < nLen; ++i ) + { + pDXAry[i] = basegfx::fround(pDXPixelArray[i]); + } + } + return basegfx::fround(nWidth); + +#else /* ! VCL_FLOAT_DEVICE_PIXEL */ + + long nWidth = pSalLayout->FillDXArray( pDXAry ); + int nWidthFactor = pSalLayout->GetUnitsPerPixel(); + + // convert virtual char widths to virtual absolute positions + if( pDXAry ) + for( int i = 1; i < nLen; ++i ) + pDXAry[ i ] += pDXAry[ i-1 ]; + + // convert from font units to logical units + if( mbMap ) + { + if( pDXAry ) + for( int i = 0; i < nLen; ++i ) + pDXAry[i] = ImplDevicePixelToLogicWidth( pDXAry[i] ); + nWidth = ImplDevicePixelToLogicWidth( nWidth ); + } + + if( nWidthFactor > 1 ) + { + if( pDXAry ) + for( int i = 0; i < nLen; ++i ) + pDXAry[i] /= nWidthFactor; + nWidth /= nWidthFactor; + } + return nWidth; +#endif /* VCL_FLOAT_DEVICE_PIXEL */ +} + +void OutputDevice::GetCaretPositions( const OUString& rStr, long* pCaretXArray, + sal_Int32 nIndex, sal_Int32 nLen, + const SalLayoutGlyphs* pGlyphs ) const +{ + + if( nIndex >= rStr.getLength() ) + return; + if( nIndex+nLen >= rStr.getLength() ) + nLen = rStr.getLength() - nIndex; + + // layout complex text + std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, Point(0, 0), 0, nullptr, + SalLayoutFlags::NONE, nullptr, pGlyphs); + if( !pSalLayout ) + return; + + int nWidthFactor = pSalLayout->GetUnitsPerPixel(); + pSalLayout->GetCaretPositions( 2*nLen, pCaretXArray ); + long nWidth = pSalLayout->GetTextWidth(); + + // fixup unknown caret positions + int i; + for( i = 0; i < 2 * nLen; ++i ) + if( pCaretXArray[ i ] >= 0 ) + break; + long nXPos = pCaretXArray[ i ]; + for( i = 0; i < 2 * nLen; ++i ) + { + if( pCaretXArray[ i ] >= 0 ) + nXPos = pCaretXArray[ i ]; + else + pCaretXArray[ i ] = nXPos; + } + + // handle window mirroring + if( IsRTLEnabled() ) + { + for( i = 0; i < 2 * nLen; ++i ) + pCaretXArray[i] = nWidth - pCaretXArray[i] - 1; + } + + // convert from font units to logical units + if( mbMap ) + { + for( i = 0; i < 2*nLen; ++i ) + pCaretXArray[i] = ImplDevicePixelToLogicWidth( pCaretXArray[i] ); + } + + if( nWidthFactor != 1 ) + { + for( i = 0; i < 2*nLen; ++i ) + pCaretXArray[i] /= nWidthFactor; + } +} + +void OutputDevice::DrawStretchText( const Point& rStartPt, sal_uLong nWidth, + const OUString& rStr, + sal_Int32 nIndex, sal_Int32 nLen) +{ + assert(!is_double_buffered_window()); + + if( (nLen < 0) || (nIndex + nLen >= rStr.getLength())) + { + nLen = rStr.getLength() - nIndex; + } + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaStretchTextAction( rStartPt, nWidth, rStr, nIndex, nLen ) ); + + if ( !IsDeviceOutputNecessary() ) + return; + + std::unique_ptr<SalLayout> pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, nWidth); + if( pSalLayout ) + { + ImplDrawText( *pSalLayout ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawStretchText( rStartPt, nWidth, rStr, nIndex, nLen ); +} + +ImplLayoutArgs OutputDevice::ImplPrepareLayoutArgs( OUString& rStr, + const sal_Int32 nMinIndex, const sal_Int32 nLen, + DeviceCoordinate nPixelWidth, const DeviceCoordinate* pDXArray, + SalLayoutFlags nLayoutFlags, + vcl::TextLayoutCache const*const pLayoutCache) const +{ + assert(nMinIndex >= 0); + assert(nLen >= 0); + + // get string length for calculating extents + sal_Int32 nEndIndex = rStr.getLength(); + if( nMinIndex + nLen < nEndIndex ) + nEndIndex = nMinIndex + nLen; + + // don't bother if there is nothing to do + if( nEndIndex < nMinIndex ) + nEndIndex = nMinIndex; + + if( mnTextLayoutMode & ComplexTextLayoutFlags::BiDiRtl ) + nLayoutFlags |= SalLayoutFlags::BiDiRtl; + if( mnTextLayoutMode & ComplexTextLayoutFlags::BiDiStrong ) + nLayoutFlags |= SalLayoutFlags::BiDiStrong; + else if( !(mnTextLayoutMode & ComplexTextLayoutFlags::BiDiRtl) ) + { + // Disable Bidi if no RTL hint and only known LTR codes used. + bool bAllLtr = true; + for (sal_Int32 i = nMinIndex; i < nEndIndex; i++) + { + // [0x0000, 0x052F] are Latin, Greek and Cyrillic. + // [0x0370, 0x03FF] has a few holes as if Unicode 10.0.0, but + // hopefully no RTL character will be encoded there. + if (rStr[i] > 0x052F) + { + bAllLtr = false; + break; + } + } + if (bAllLtr) + nLayoutFlags |= SalLayoutFlags::BiDiStrong; + } + + if( !maFont.IsKerning() ) + nLayoutFlags |= SalLayoutFlags::DisableKerning; + if( maFont.GetKerning() & FontKerning::Asian ) + nLayoutFlags |= SalLayoutFlags::KerningAsian; + if( maFont.IsVertical() ) + nLayoutFlags |= SalLayoutFlags::Vertical; + + if( meTextLanguage ) //TODO: (mnTextLayoutMode & ComplexTextLayoutFlags::SubstituteDigits) + { + // disable character localization when no digits used + const sal_Unicode* pBase = rStr.getStr(); + const sal_Unicode* pStr = pBase + nMinIndex; + const sal_Unicode* pEnd = pBase + nEndIndex; + std::optional<OUStringBuffer> xTmpStr; + for( ; pStr < pEnd; ++pStr ) + { + // TODO: are there non-digit localizations? + if( (*pStr >= '0') && (*pStr <= '9') ) + { + // translate characters to local preference + sal_UCS4 cChar = GetLocalizedChar( *pStr, meTextLanguage ); + if( cChar != *pStr ) + { + if (!xTmpStr) + xTmpStr = OUStringBuffer(rStr); + // TODO: are the localized digit surrogates? + (*xTmpStr)[pStr - pBase] = cChar; + } + } + } + if (xTmpStr) + rStr = (*xTmpStr).makeStringAndClear(); + } + + // right align for RTL text, DRAWPOS_REVERSED, RTL window style + bool bRightAlign = bool(mnTextLayoutMode & ComplexTextLayoutFlags::BiDiRtl); + if( mnTextLayoutMode & ComplexTextLayoutFlags::TextOriginLeft ) + bRightAlign = false; + else if ( mnTextLayoutMode & ComplexTextLayoutFlags::TextOriginRight ) + bRightAlign = true; + // SSA: hack for western office, ie text get right aligned + // for debugging purposes of mirrored UI + bool bRTLWindow = IsRTLEnabled(); + bRightAlign ^= bRTLWindow; + if( bRightAlign ) + nLayoutFlags |= SalLayoutFlags::RightAlign; + + // set layout options + ImplLayoutArgs aLayoutArgs(rStr, nMinIndex, nEndIndex, nLayoutFlags, maFont.GetLanguageTag(), pLayoutCache); + + int nOrientation = mpFontInstance ? mpFontInstance->mnOrientation : 0; + aLayoutArgs.SetOrientation( nOrientation ); + + aLayoutArgs.SetLayoutWidth( nPixelWidth ); + aLayoutArgs.SetDXArray( pDXArray ); + + return aLayoutArgs; +} + +std::unique_ptr<SalLayout> OutputDevice::ImplLayout(const OUString& rOrigStr, + sal_Int32 nMinIndex, sal_Int32 nLen, + const Point& rLogicalPos, long nLogicalWidth, + const long* pDXArray, SalLayoutFlags flags, + vcl::TextLayoutCache const* pLayoutCache, + const SalLayoutGlyphs* pGlyphs) const +{ + if (pGlyphs && !pGlyphs->IsValid()) + { + SAL_WARN("vcl", "Trying to setup invalid cached glyphs - falling back to relayout!"); + pGlyphs = nullptr; + } + + if (!InitFont()) + return nullptr; + + // check string index and length + if( -1 == nLen || nMinIndex + nLen > rOrigStr.getLength() ) + { + const sal_Int32 nNewLen = rOrigStr.getLength() - nMinIndex; + if( nNewLen <= 0 ) + return nullptr; + nLen = nNewLen; + } + + OUString aStr = rOrigStr; + + // convert from logical units to physical units + // recode string if needed + if( mpFontInstance->mpConversion ) { + mpFontInstance->mpConversion->RecodeString( aStr, 0, aStr.getLength() ); + pLayoutCache = nullptr; // don't use cache with modified string! + pGlyphs = nullptr; + } + + DeviceCoordinate nPixelWidth = static_cast<DeviceCoordinate>(nLogicalWidth); + if( nLogicalWidth && mbMap ) + { + nPixelWidth = LogicWidthToDeviceCoordinate( nLogicalWidth ); + } + + std::unique_ptr<DeviceCoordinate[]> xDXPixelArray; + DeviceCoordinate* pDXPixelArray(nullptr); + if( pDXArray) + { + if(mbMap) + { + // convert from logical units to font units using a temporary array + xDXPixelArray.reset(new DeviceCoordinate[nLen]); + pDXPixelArray = xDXPixelArray.get(); + // using base position for better rounding a.k.a. "dancing characters" + DeviceCoordinate nPixelXOfs = LogicWidthToDeviceCoordinate( rLogicalPos.X() ); + for( int i = 0; i < nLen; ++i ) + { + pDXPixelArray[i] = LogicWidthToDeviceCoordinate( rLogicalPos.X() + pDXArray[i] ) - nPixelXOfs; + } + } + else + { +#if VCL_FLOAT_DEVICE_PIXEL + xDXPixelArray.reset(new DeviceCoordinate[nLen]); + pDXPixelArray = xDXPixelArray.get(); + for( int i = 0; i < nLen; ++i ) + { + pDXPixelArray[i] = pDXArray[i]; + } +#else /* !VCL_FLOAT_DEVICE_PIXEL */ + pDXPixelArray = const_cast<DeviceCoordinate*>(pDXArray); +#endif /* !VCL_FLOAT_DEVICE_PIXEL */ + } + } + + ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nMinIndex, nLen, + nPixelWidth, pDXPixelArray, flags, pLayoutCache); + + // get matching layout object for base font + std::unique_ptr<SalLayout> pSalLayout = mpGraphics->GetTextLayout(0); + + // layout text + if( pSalLayout && !pSalLayout->LayoutText( aLayoutArgs, pGlyphs ) ) + { + pSalLayout.reset(); + } + + if( !pSalLayout ) + return nullptr; + + // do glyph fallback if needed + // #105768# avoid fallback for very small font sizes + if (aLayoutArgs.NeedFallback() && mpFontInstance->GetFontSelectPattern().mnHeight >= 3) + pSalLayout = ImplGlyphFallbackLayout(std::move(pSalLayout), aLayoutArgs); + + if (flags & SalLayoutFlags::GlyphItemsOnly) + // Return glyph items only after fallback handling. Otherwise they may + // contain invalid glyph IDs. + return pSalLayout; + + // position, justify, etc. the layout + pSalLayout->AdjustLayout( aLayoutArgs ); + pSalLayout->DrawBase() = ImplLogicToDevicePixel( rLogicalPos ); + // adjust to right alignment if necessary + if( aLayoutArgs.mnFlags & SalLayoutFlags::RightAlign ) + { + DeviceCoordinate nRTLOffset; + if( pDXPixelArray ) + nRTLOffset = pDXPixelArray[ nLen - 1 ]; + else if( nPixelWidth ) + nRTLOffset = nPixelWidth; + else + nRTLOffset = pSalLayout->GetTextWidth() / pSalLayout->GetUnitsPerPixel(); + pSalLayout->DrawOffset().setX( 1 - nRTLOffset ); + } + + return pSalLayout; +} + +std::shared_ptr<vcl::TextLayoutCache> OutputDevice::CreateTextLayoutCache( + OUString const& rString) +{ + return GenericSalLayout::CreateTextLayoutCache(rString); +} + +bool OutputDevice::GetTextIsRTL( const OUString& rString, sal_Int32 nIndex, sal_Int32 nLen ) const +{ + OUString aStr( rString ); + ImplLayoutArgs aArgs = ImplPrepareLayoutArgs( aStr, nIndex, nLen, 0, nullptr ); + bool bRTL = false; + int nCharPos = -1; + if (!aArgs.GetNextPos(&nCharPos, &bRTL)) + return false; + return (nCharPos != nIndex); +} + +sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, long nTextWidth, + sal_Int32 nIndex, sal_Int32 nLen, + long nCharExtra, + vcl::TextLayoutCache const*const pLayoutCache, + const SalLayoutGlyphs* pGlyphs) const +{ + std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen, + Point(0,0), 0, nullptr, SalLayoutFlags::NONE, pLayoutCache, pGlyphs); + sal_Int32 nRetVal = -1; + if( pSalLayout ) + { + // convert logical widths into layout units + // NOTE: be very careful to avoid rounding errors for nCharExtra case + // problem with rounding errors especially for small nCharExtras + // TODO: remove when layout units have subpixel granularity + long nWidthFactor = pSalLayout->GetUnitsPerPixel(); + long nSubPixelFactor = (nWidthFactor < 64 ) ? 64 : 1; + nTextWidth *= nWidthFactor * nSubPixelFactor; + DeviceCoordinate nTextPixelWidth = LogicWidthToDeviceCoordinate( nTextWidth ); + DeviceCoordinate nExtraPixelWidth = 0; + if( nCharExtra != 0 ) + { + nCharExtra *= nWidthFactor * nSubPixelFactor; + nExtraPixelWidth = LogicWidthToDeviceCoordinate( nCharExtra ); + } + nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor ); + } + + return nRetVal; +} + +sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, long nTextWidth, + sal_Unicode nHyphenChar, sal_Int32& rHyphenPos, + sal_Int32 nIndex, sal_Int32 nLen, + long nCharExtra, + vcl::TextLayoutCache const*const pLayoutCache) const +{ + rHyphenPos = -1; + + std::unique_ptr<SalLayout> pSalLayout = ImplLayout( rStr, nIndex, nLen, + Point(0,0), 0, nullptr, SalLayoutFlags::NONE, pLayoutCache); + sal_Int32 nRetVal = -1; + if( pSalLayout ) + { + // convert logical widths into layout units + // NOTE: be very careful to avoid rounding errors for nCharExtra case + // problem with rounding errors especially for small nCharExtras + // TODO: remove when layout units have subpixel granularity + long nWidthFactor = pSalLayout->GetUnitsPerPixel(); + long nSubPixelFactor = (nWidthFactor < 64 ) ? 64 : 1; + + nTextWidth *= nWidthFactor * nSubPixelFactor; + DeviceCoordinate nTextPixelWidth = LogicWidthToDeviceCoordinate( nTextWidth ); + DeviceCoordinate nExtraPixelWidth = 0; + if( nCharExtra != 0 ) + { + nCharExtra *= nWidthFactor * nSubPixelFactor; + nExtraPixelWidth = LogicWidthToDeviceCoordinate( nCharExtra ); + } + + // calculate un-hyphenated break position + nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor ); + + // calculate hyphenated break position + OUString aHyphenStr(nHyphenChar); + std::unique_ptr<SalLayout> pHyphenLayout = ImplLayout( aHyphenStr, 0, 1 ); + if( pHyphenLayout ) + { + // calculate subpixel width of hyphenation character + long nHyphenPixelWidth = pHyphenLayout->GetTextWidth() * nSubPixelFactor; + + // calculate hyphenated break position + nTextPixelWidth -= nHyphenPixelWidth; + if( nExtraPixelWidth > 0 ) + nTextPixelWidth -= nExtraPixelWidth; + + rHyphenPos = pSalLayout->GetTextBreak(nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor); + + if( rHyphenPos > nRetVal ) + rHyphenPos = nRetVal; + } + } + + return nRetVal; +} + +void OutputDevice::ImplDrawText( OutputDevice& rTargetDevice, const tools::Rectangle& rRect, + const OUString& rOrigStr, DrawTextFlags nStyle, + MetricVector* pVector, OUString* pDisplayText, + vcl::ITextLayout& _rLayout ) +{ + + Color aOldTextColor; + Color aOldTextFillColor; + bool bRestoreFillColor = false; + if ( (nStyle & DrawTextFlags::Disable) && ! pVector ) + { + bool bHighContrastBlack = false; + bool bHighContrastWhite = false; + const StyleSettings& rStyleSettings( rTargetDevice.GetSettings().GetStyleSettings() ); + if( rStyleSettings.GetHighContrastMode() ) + { + Color aCol; + if( rTargetDevice.IsBackground() ) + aCol = rTargetDevice.GetBackground().GetColor(); + else + // best guess is the face color here + // but it may be totally wrong. the background color + // was typically already reset + aCol = rStyleSettings.GetFaceColor(); + + bHighContrastBlack = aCol.IsDark(); + bHighContrastWhite = aCol.IsBright(); + } + + aOldTextColor = rTargetDevice.GetTextColor(); + if ( rTargetDevice.IsTextFillColor() ) + { + bRestoreFillColor = true; + aOldTextFillColor = rTargetDevice.GetTextFillColor(); + } + if( bHighContrastBlack ) + rTargetDevice.SetTextColor( COL_GREEN ); + else if( bHighContrastWhite ) + rTargetDevice.SetTextColor( COL_LIGHTGREEN ); + else + { + // draw disabled text always without shadow + // as it fits better with native look + rTargetDevice.SetTextColor( rTargetDevice.GetSettings().GetStyleSettings().GetDisableColor() ); + } + } + + long nWidth = rRect.GetWidth(); + long nHeight = rRect.GetHeight(); + + if ( ((nWidth <= 0) || (nHeight <= 0)) && (nStyle & DrawTextFlags::Clip) ) + return; + + Point aPos = rRect.TopLeft(); + + long nTextHeight = rTargetDevice.GetTextHeight(); + TextAlign eAlign = rTargetDevice.GetTextAlign(); + sal_Int32 nMnemonicPos = -1; + + OUString aStr = rOrigStr; + if ( nStyle & DrawTextFlags::Mnemonic ) + aStr = GetNonMnemonicString( aStr, nMnemonicPos ); + + const bool bDrawMnemonics = !(rTargetDevice.GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector; + + // We treat multiline text differently + if ( nStyle & DrawTextFlags::MultiLine ) + { + + OUString aLastLine; + ImplMultiTextLineInfo aMultiLineInfo; + ImplTextLineInfo* pLineInfo; + sal_Int32 i; + sal_Int32 nLines; + sal_Int32 nFormatLines; + + if ( nTextHeight ) + { + long nMaxTextWidth = ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, _rLayout ); + nLines = static_cast<sal_Int32>(nHeight/nTextHeight); + nFormatLines = aMultiLineInfo.Count(); + if (nLines <= 0) + nLines = 1; + if ( nFormatLines > nLines ) + { + if ( nStyle & DrawTextFlags::EndEllipsis ) + { + // Create last line and shorten it + nFormatLines = nLines-1; + + pLineInfo = aMultiLineInfo.GetLine( nFormatLines ); + aLastLine = convertLineEnd(aStr.copy(pLineInfo->GetIndex()), LINEEND_LF); + // Replace all LineFeeds with Spaces + OUStringBuffer aLastLineBuffer(aLastLine); + sal_Int32 nLastLineLen = aLastLineBuffer.getLength(); + for ( i = 0; i < nLastLineLen; i++ ) + { + if ( aLastLineBuffer[ i ] == '\n' ) + aLastLineBuffer[ i ] = ' '; + } + aLastLine = aLastLineBuffer.makeStringAndClear(); + aLastLine = ImplGetEllipsisString( rTargetDevice, aLastLine, nWidth, nStyle, _rLayout ); + nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom); + nStyle |= DrawTextFlags::Top; + } + } + else + { + if ( nMaxTextWidth <= nWidth ) + nStyle &= ~DrawTextFlags::Clip; + } + + // Do we need to clip the height? + if ( nFormatLines*nTextHeight > nHeight ) + nStyle |= DrawTextFlags::Clip; + + // Set clipping + if ( nStyle & DrawTextFlags::Clip ) + { + rTargetDevice.Push( PushFlags::CLIPREGION ); + rTargetDevice.IntersectClipRegion( rRect ); + } + + // Vertical alignment + if ( nStyle & DrawTextFlags::Bottom ) + aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) ); + else if ( nStyle & DrawTextFlags::VCenter ) + aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 ); + + // Font alignment + if ( eAlign == ALIGN_BOTTOM ) + aPos.AdjustY(nTextHeight ); + else if ( eAlign == ALIGN_BASELINE ) + aPos.AdjustY(rTargetDevice.GetFontMetric().GetAscent() ); + + // Output all lines except for the last one + for ( i = 0; i < nFormatLines; i++ ) + { + pLineInfo = aMultiLineInfo.GetLine( i ); + if ( nStyle & DrawTextFlags::Right ) + aPos.AdjustX(nWidth-pLineInfo->GetWidth() ); + else if ( nStyle & DrawTextFlags::Center ) + aPos.AdjustX((nWidth-pLineInfo->GetWidth())/2 ); + sal_Int32 nIndex = pLineInfo->GetIndex(); + sal_Int32 nLineLen = pLineInfo->GetLen(); + _rLayout.DrawText( aPos, aStr, nIndex, nLineLen, pVector, pDisplayText ); + if ( bDrawMnemonics ) + { + if ( (nMnemonicPos >= nIndex) && (nMnemonicPos < nIndex+nLineLen) ) + { + long nMnemonicX; + long nMnemonicY; + DeviceCoordinate nMnemonicWidth; + + std::unique_ptr<long[]> const pCaretXArray(new long[2 * nLineLen]); + /*sal_Bool bRet =*/ _rLayout.GetCaretPositions( aStr, pCaretXArray.get(), + nIndex, nLineLen ); + long lc_x1 = pCaretXArray[2*(nMnemonicPos - nIndex)]; + long lc_x2 = pCaretXArray[2*(nMnemonicPos - nIndex)+1]; + nMnemonicWidth = rTargetDevice.LogicWidthToDeviceCoordinate( std::abs(lc_x1 - lc_x2) ); + + Point aTempPos = rTargetDevice.LogicToPixel( aPos ); + nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min( lc_x1, lc_x2 ) ); + nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() ); + rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); + } + } + aPos.AdjustY(nTextHeight ); + aPos.setX( rRect.Left() ); + } + + // If there still is a last line, we output it left-aligned as the line would be clipped + if ( !aLastLine.isEmpty() ) + _rLayout.DrawText( aPos, aLastLine, 0, aLastLine.getLength(), pVector, pDisplayText ); + + // Reset clipping + if ( nStyle & DrawTextFlags::Clip ) + rTargetDevice.Pop(); + } + } + else + { + long nTextWidth = _rLayout.GetTextWidth( aStr, 0, -1 ); + + // Clip text if needed + if ( nTextWidth > nWidth ) + { + if ( nStyle & TEXT_DRAW_ELLIPSIS ) + { + aStr = ImplGetEllipsisString( rTargetDevice, aStr, nWidth, nStyle, _rLayout ); + nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right); + nStyle |= DrawTextFlags::Left; + nTextWidth = _rLayout.GetTextWidth( aStr, 0, aStr.getLength() ); + } + } + else + { + if ( nTextHeight <= nHeight ) + nStyle &= ~DrawTextFlags::Clip; + } + + // horizontal text alignment + if ( nStyle & DrawTextFlags::Right ) + aPos.AdjustX(nWidth-nTextWidth ); + else if ( nStyle & DrawTextFlags::Center ) + aPos.AdjustX((nWidth-nTextWidth)/2 ); + + // vertical font alignment + if ( eAlign == ALIGN_BOTTOM ) + aPos.AdjustY(nTextHeight ); + else if ( eAlign == ALIGN_BASELINE ) + aPos.AdjustY(rTargetDevice.GetFontMetric().GetAscent() ); + + if ( nStyle & DrawTextFlags::Bottom ) + aPos.AdjustY(nHeight-nTextHeight ); + else if ( nStyle & DrawTextFlags::VCenter ) + aPos.AdjustY((nHeight-nTextHeight)/2 ); + + long nMnemonicX = 0; + long nMnemonicY = 0; + DeviceCoordinate nMnemonicWidth = 0; + if ( nMnemonicPos != -1 ) + { + std::unique_ptr<long[]> const pCaretXArray(new long[2 * aStr.getLength()]); + /*sal_Bool bRet =*/ _rLayout.GetCaretPositions( aStr, pCaretXArray.get(), 0, aStr.getLength() ); + long lc_x1 = pCaretXArray[2*nMnemonicPos]; + long lc_x2 = pCaretXArray[2*nMnemonicPos+1]; + nMnemonicWidth = rTargetDevice.LogicWidthToDeviceCoordinate( std::abs(lc_x1 - lc_x2) ); + + Point aTempPos = rTargetDevice.LogicToPixel( aPos ); + nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min(lc_x1, lc_x2) ); + nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() ); + } + + if ( nStyle & DrawTextFlags::Clip ) + { + rTargetDevice.Push( PushFlags::CLIPREGION ); + rTargetDevice.IntersectClipRegion( rRect ); + _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText ); + if ( bDrawMnemonics && nMnemonicPos != -1 ) + rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); + rTargetDevice.Pop(); + } + else + { + _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText ); + if ( bDrawMnemonics && nMnemonicPos != -1 ) + rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); + } + } + + if ( nStyle & DrawTextFlags::Disable && !pVector ) + { + rTargetDevice.SetTextColor( aOldTextColor ); + if ( bRestoreFillColor ) + rTargetDevice.SetTextFillColor( aOldTextFillColor ); + } +} + +void OutputDevice::AddTextRectActions( const tools::Rectangle& rRect, + const OUString& rOrigStr, + DrawTextFlags nStyle, + GDIMetaFile& rMtf ) +{ + + if ( rOrigStr.isEmpty() || rRect.IsEmpty() ) + return; + + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return; + if( mbInitClipRegion ) + InitClipRegion(); + + // temporarily swap in passed mtf for action generation, and + // disable output generation. + const bool bOutputEnabled( IsOutputEnabled() ); + GDIMetaFile* pMtf = mpMetaFile; + + mpMetaFile = &rMtf; + EnableOutput( false ); + + // #i47157# Factored out to ImplDrawTextRect(), to be shared + // between us and DrawText() + vcl::DefaultTextLayout aLayout( *this ); + ImplDrawText( *this, rRect, rOrigStr, nStyle, nullptr, nullptr, aLayout ); + + // and restore again + EnableOutput( bOutputEnabled ); + mpMetaFile = pMtf; +} + +void OutputDevice::DrawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle, + MetricVector* pVector, OUString* pDisplayText, + vcl::ITextLayout* _pTextLayout ) +{ + assert(!is_double_buffered_window()); + + if (mpOutDevData->mpRecordLayout) + { + pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects; + pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText; + } + + bool bDecomposeTextRectAction = ( _pTextLayout != nullptr ) && _pTextLayout->DecomposeTextRectAction(); + if ( mpMetaFile && !bDecomposeTextRectAction ) + mpMetaFile->AddAction( new MetaTextRectAction( rRect, rOrigStr, nStyle ) ); + + if ( ( !IsDeviceOutputNecessary() && !pVector && !bDecomposeTextRectAction ) || rOrigStr.isEmpty() || rRect.IsEmpty() ) + return; + + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return; + if( mbInitClipRegion ) + InitClipRegion(); + if( mbOutputClipped && !bDecomposeTextRectAction ) + return; + + // temporarily disable mtf action generation (ImplDrawText _does_ + // create MetaActionType::TEXTs otherwise) + GDIMetaFile* pMtf = mpMetaFile; + if ( !bDecomposeTextRectAction ) + mpMetaFile = nullptr; + + // #i47157# Factored out to ImplDrawText(), to be used also + // from AddTextRectActions() + vcl::DefaultTextLayout aDefaultLayout( *this ); + ImplDrawText( *this, rRect, rOrigStr, nStyle, pVector, pDisplayText, _pTextLayout ? *_pTextLayout : aDefaultLayout ); + + // and enable again + mpMetaFile = pMtf; + + if( mpAlphaVDev ) + mpAlphaVDev->DrawText( rRect, rOrigStr, nStyle, pVector, pDisplayText ); +} + +tools::Rectangle OutputDevice::GetTextRect( const tools::Rectangle& rRect, + const OUString& rStr, DrawTextFlags nStyle, + TextRectInfo* pInfo, + const vcl::ITextLayout* _pTextLayout ) const +{ + + tools::Rectangle aRect = rRect; + sal_Int32 nLines; + long nWidth = rRect.GetWidth(); + long nMaxWidth; + long nTextHeight = GetTextHeight(); + + OUString aStr = rStr; + if ( nStyle & DrawTextFlags::Mnemonic ) + aStr = GetNonMnemonicString( aStr ); + + if ( nStyle & DrawTextFlags::MultiLine ) + { + ImplMultiTextLineInfo aMultiLineInfo; + ImplTextLineInfo* pLineInfo; + sal_Int32 nFormatLines; + sal_Int32 i; + + nMaxWidth = 0; + vcl::DefaultTextLayout aDefaultLayout( *const_cast< OutputDevice* >( this ) ); + ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, _pTextLayout ? *_pTextLayout : aDefaultLayout ); + nFormatLines = aMultiLineInfo.Count(); + if ( !nTextHeight ) + nTextHeight = 1; + nLines = static_cast<sal_uInt16>(aRect.GetHeight()/nTextHeight); + if ( pInfo ) + pInfo->mnLineCount = nFormatLines; + if ( !nLines ) + nLines = 1; + if ( nFormatLines <= nLines ) + nLines = nFormatLines; + else + { + if ( !(nStyle & DrawTextFlags::EndEllipsis) ) + nLines = nFormatLines; + else + { + if ( pInfo ) + pInfo->mbEllipsis = true; + nMaxWidth = nWidth; + } + } + if ( pInfo ) + { + bool bMaxWidth = nMaxWidth == 0; + pInfo->mnMaxWidth = 0; + for ( i = 0; i < nLines; i++ ) + { + pLineInfo = aMultiLineInfo.GetLine( i ); + if ( bMaxWidth && (pLineInfo->GetWidth() > nMaxWidth) ) + nMaxWidth = pLineInfo->GetWidth(); + if ( pLineInfo->GetWidth() > pInfo->mnMaxWidth ) + pInfo->mnMaxWidth = pLineInfo->GetWidth(); + } + } + else if ( !nMaxWidth ) + { + for ( i = 0; i < nLines; i++ ) + { + pLineInfo = aMultiLineInfo.GetLine( i ); + if ( pLineInfo->GetWidth() > nMaxWidth ) + nMaxWidth = pLineInfo->GetWidth(); + } + } + } + else + { + nLines = 1; + nMaxWidth = _pTextLayout ? _pTextLayout->GetTextWidth( aStr, 0, aStr.getLength() ) : GetTextWidth( aStr ); + + if ( pInfo ) + { + pInfo->mnLineCount = 1; + pInfo->mnMaxWidth = nMaxWidth; + } + + if ( (nMaxWidth > nWidth) && (nStyle & TEXT_DRAW_ELLIPSIS) ) + { + if ( pInfo ) + pInfo->mbEllipsis = true; + nMaxWidth = nWidth; + } + } + + if ( nStyle & DrawTextFlags::Right ) + aRect.SetLeft( aRect.Right()-nMaxWidth+1 ); + else if ( nStyle & DrawTextFlags::Center ) + { + aRect.AdjustLeft((nWidth-nMaxWidth)/2 ); + aRect.SetRight( aRect.Left()+nMaxWidth-1 ); + } + else + aRect.SetRight( aRect.Left()+nMaxWidth-1 ); + + if ( nStyle & DrawTextFlags::Bottom ) + aRect.SetTop( aRect.Bottom()-(nTextHeight*nLines)+1 ); + else if ( nStyle & DrawTextFlags::VCenter ) + { + aRect.AdjustTop((aRect.GetHeight()-(nTextHeight*nLines))/2 ); + aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 ); + } + else + aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 ); + + // #99188# get rid of rounding problems when using this rect later + if (nStyle & DrawTextFlags::Right) + aRect.AdjustLeft( -1 ); + else + aRect.AdjustRight( 1 ); + return aRect; +} + +static bool ImplIsCharIn( sal_Unicode c, const char* pStr ) +{ + while ( *pStr ) + { + if ( *pStr == c ) + return true; + pStr++; + } + + return false; +} + +OUString OutputDevice::GetEllipsisString( const OUString& rOrigStr, long nMaxWidth, + DrawTextFlags nStyle ) const +{ + vcl::DefaultTextLayout aTextLayout( *const_cast< OutputDevice* >( this ) ); + return ImplGetEllipsisString( *this, rOrigStr, nMaxWidth, nStyle, aTextLayout ); +} + +OUString OutputDevice::ImplGetEllipsisString( const OutputDevice& rTargetDevice, const OUString& rOrigStr, long nMaxWidth, + DrawTextFlags nStyle, const vcl::ITextLayout& _rLayout ) +{ + OUString aStr = rOrigStr; + sal_Int32 nIndex = _rLayout.GetTextBreak( aStr, nMaxWidth, 0, aStr.getLength() ); + + if ( nIndex != -1 ) + { + if( (nStyle & DrawTextFlags::CenterEllipsis) == DrawTextFlags::CenterEllipsis ) + { + OUStringBuffer aTmpStr( aStr ); + // speed it up by removing all but 1.33x as many as the break pos. + sal_Int32 nEraseChars = std::max<sal_Int32>(4, aStr.getLength() - (nIndex*4)/3); + while( nEraseChars < aStr.getLength() && _rLayout.GetTextWidth( aTmpStr.toString(), 0, aTmpStr.getLength() ) > nMaxWidth ) + { + aTmpStr = aStr; + sal_Int32 i = (aTmpStr.getLength() - nEraseChars)/2; + aTmpStr.remove(i, nEraseChars++); + aTmpStr.insert(i, "..."); + } + aStr = aTmpStr.makeStringAndClear(); + } + else if ( nStyle & DrawTextFlags::EndEllipsis ) + { + aStr = aStr.copy(0, nIndex); + if ( nIndex > 1 ) + { + aStr += "..."; + while ( !aStr.isEmpty() && (_rLayout.GetTextWidth( aStr, 0, aStr.getLength() ) > nMaxWidth) ) + { + if ( (nIndex > 1) || (nIndex == aStr.getLength()) ) + nIndex--; + aStr = aStr.replaceAt( nIndex, 1, ""); + } + } + + if ( aStr.isEmpty() && (nStyle & DrawTextFlags::Clip) ) + aStr += OUStringChar(rOrigStr[ 0 ]); + } + else if ( nStyle & DrawTextFlags::PathEllipsis ) + { + OUString aPath( rOrigStr ); + OUString aAbbreviatedPath; + osl_abbreviateSystemPath( aPath.pData, &aAbbreviatedPath.pData, nIndex, nullptr ); + aStr = aAbbreviatedPath; + } + else if ( nStyle & DrawTextFlags::NewsEllipsis ) + { + static char const pSepChars[] = "."; + // Determine last section + sal_Int32 nLastContent = aStr.getLength(); + while ( nLastContent ) + { + nLastContent--; + if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) ) + break; + } + while ( nLastContent && + ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) ) + nLastContent--; + + OUString aLastStr = aStr.copy(nLastContent); + OUString aTempLastStr1 = "..." + aLastStr; + if ( _rLayout.GetTextWidth( aTempLastStr1, 0, aTempLastStr1.getLength() ) > nMaxWidth ) + aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout ); + else + { + sal_Int32 nFirstContent = 0; + while ( nFirstContent < nLastContent ) + { + nFirstContent++; + if ( ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) ) + break; + } + while ( (nFirstContent < nLastContent) && + ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) ) + nFirstContent++; + // MEM continue here + if ( nFirstContent >= nLastContent ) + aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout ); + else + { + if ( nFirstContent > 4 ) + nFirstContent = 4; + OUString aFirstStr = aStr.copy( 0, nFirstContent ) + "..."; + OUString aTempStr = aFirstStr + aLastStr; + if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth ) + aStr = OutputDevice::ImplGetEllipsisString( rTargetDevice, aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout ); + else + { + do + { + aStr = aTempStr; + if( nLastContent > aStr.getLength() ) + nLastContent = aStr.getLength(); + while ( nFirstContent < nLastContent ) + { + nLastContent--; + if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) ) + break; + + } + while ( (nFirstContent < nLastContent) && + ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) ) + nLastContent--; + + if ( nFirstContent < nLastContent ) + { + OUString aTempLastStr = aStr.copy( nLastContent ); + aTempStr = aFirstStr + aTempLastStr; + + if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth ) + break; + } + } + while ( nFirstContent < nLastContent ); + } + } + } + } + } + + return aStr; +} + +void OutputDevice::DrawCtrlText( const Point& rPos, const OUString& rStr, + sal_Int32 nIndex, sal_Int32 nLen, + DrawTextFlags nStyle, MetricVector* pVector, OUString* pDisplayText, + const SalLayoutGlyphs* pGlyphs ) +{ + assert(!is_double_buffered_window()); + + if( (nLen < 0) || (nIndex + nLen >= rStr.getLength())) + { + nLen = rStr.getLength() - nIndex; + } + + if ( !IsDeviceOutputNecessary() || (nIndex >= rStr.getLength()) ) + return; + + // better get graphics here because ImplDrawMnemonicLine() will not + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return; + if( mbInitClipRegion ) + InitClipRegion(); + if ( mbOutputClipped ) + return; + + if( nIndex >= rStr.getLength() ) + return; + + if( (nLen < 0) || (nIndex + nLen >= rStr.getLength())) + { + nLen = rStr.getLength() - nIndex; + } + OUString aStr = rStr; + sal_Int32 nMnemonicPos = -1; + + long nMnemonicX = 0; + long nMnemonicY = 0; + long nMnemonicWidth = 0; + if ( (nStyle & DrawTextFlags::Mnemonic) && nLen > 1 ) + { + aStr = GetNonMnemonicString( aStr, nMnemonicPos ); + if ( nMnemonicPos != -1 ) + { + if( nMnemonicPos < nIndex ) + { + --nIndex; + } + else + { + if( nMnemonicPos < (nIndex+nLen) ) + --nLen; + SAL_WARN_IF( nMnemonicPos >= (nIndex+nLen), "vcl", "Mnemonic underline marker after last character" ); + } + bool bInvalidPos = false; + + if( nMnemonicPos >= nLen ) + { + // may occur in BiDi-Strings: the '~' is sometimes found behind the last char + // due to some strange BiDi text editors + // -> place the underline behind the string to indicate a failure + bInvalidPos = true; + nMnemonicPos = nLen-1; + } + + std::unique_ptr<long[]> const pCaretXArray(new long[2 * nLen]); + /*sal_Bool bRet =*/ GetCaretPositions( aStr, pCaretXArray.get(), nIndex, nLen, pGlyphs ); + long lc_x1 = pCaretXArray[ 2*(nMnemonicPos - nIndex) ]; + long lc_x2 = pCaretXArray[ 2*(nMnemonicPos - nIndex)+1 ]; + nMnemonicWidth = ::abs(static_cast<int>(lc_x1 - lc_x2)); + + Point aTempPos( std::min(lc_x1,lc_x2), GetFontMetric().GetAscent() ); + if( bInvalidPos ) // #106952#, place behind the (last) character + aTempPos = Point( std::max(lc_x1,lc_x2), GetFontMetric().GetAscent() ); + + aTempPos += rPos; + aTempPos = LogicToPixel( aTempPos ); + nMnemonicX = mnOutOffX + aTempPos.X(); + nMnemonicY = mnOutOffY + aTempPos.Y(); + } + } + + bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel; + + if ( nStyle & DrawTextFlags::Disable && ! pVector ) + { + Color aOldTextColor; + Color aOldTextFillColor; + bool bRestoreFillColor; + bool bHighContrastBlack = false; + bool bHighContrastWhite = false; + const StyleSettings& rStyleSettings( GetSettings().GetStyleSettings() ); + if( rStyleSettings.GetHighContrastMode() ) + { + if( IsBackground() ) + { + Wallpaper aWall = GetBackground(); + Color aCol = aWall.GetColor(); + bHighContrastBlack = aCol.IsDark(); + bHighContrastWhite = aCol.IsBright(); + } + } + + aOldTextColor = GetTextColor(); + if ( IsTextFillColor() ) + { + bRestoreFillColor = true; + aOldTextFillColor = GetTextFillColor(); + } + else + bRestoreFillColor = false; + + if( bHighContrastBlack ) + SetTextColor( COL_GREEN ); + else if( bHighContrastWhite ) + SetTextColor( COL_LIGHTGREEN ); + else + SetTextColor( GetSettings().GetStyleSettings().GetDisableColor() ); + + DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText ); + if (!(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) + && (!autoacc || !(nStyle & DrawTextFlags::HideMnemonic)) ) + { + if ( nMnemonicPos != -1 ) + ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); + } + SetTextColor( aOldTextColor ); + if ( bRestoreFillColor ) + SetTextFillColor( aOldTextFillColor ); + } + else + { + DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText, pGlyphs ); + if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector + && (!autoacc || !(nStyle & DrawTextFlags::HideMnemonic)) ) + { + if ( nMnemonicPos != -1 ) + ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawCtrlText( rPos, rStr, nIndex, nLen, nStyle, pVector, pDisplayText ); +} + +long OutputDevice::GetCtrlTextWidth( const OUString& rStr, const SalLayoutGlyphs* pGlyphs ) const +{ + sal_Int32 nLen = rStr.getLength(); + sal_Int32 nIndex = 0; + + sal_Int32 nMnemonicPos; + OUString aStr = GetNonMnemonicString( rStr, nMnemonicPos ); + if ( nMnemonicPos != -1 ) + { + if ( nMnemonicPos < nIndex ) + nIndex--; + else if (static_cast<sal_uLong>(nMnemonicPos) < static_cast<sal_uLong>(nIndex+nLen)) + nLen--; + } + return GetTextWidth( aStr, nIndex, nLen, nullptr, pGlyphs ); +} + +OUString OutputDevice::GetNonMnemonicString( const OUString& rStr, sal_Int32& rMnemonicPos ) +{ + OUString aStr = rStr; + sal_Int32 nLen = aStr.getLength(); + sal_Int32 i = 0; + + rMnemonicPos = -1; + while ( i < nLen ) + { + if ( aStr[ i ] == '~' ) + { + if ( nLen <= i+1 ) + break; + + if ( aStr[ i+1 ] != '~' ) + { + if ( rMnemonicPos == -1 ) + rMnemonicPos = i; + aStr = aStr.replaceAt( i, 1, "" ); + nLen--; + } + else + { + aStr = aStr.replaceAt( i, 1, "" ); + nLen--; + i++; + } + } + else + i++; + } + + return aStr; +} + +/** OutputDevice::GetSysTextLayoutData + * + * @param rStartPt Start point of the text + * @param rStr Text string that will be transformed into layout of glyphs + * @param nIndex Position in the string from where layout will be done + * @param nLen Length of the string + * @param pDXAry Custom layout adjustment data + * + * Export finalized glyph layout data as platform independent SystemTextLayoutData + * (see vcl/inc/vcl/sysdata.hxx) + * + * Only parameters rStartPt and rStr are mandatory, the rest is optional + * (default values will be used) + * + * @return SystemTextLayoutData + **/ +SystemTextLayoutData OutputDevice::GetSysTextLayoutData(const Point& rStartPt, const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen, + const long* pDXAry) const +{ + if( (nLen < 0) || (nIndex + nLen >= rStr.getLength())) + { + nLen = rStr.getLength() - nIndex; + } + + SystemTextLayoutData aSysLayoutData; + aSysLayoutData.rGlyphData.reserve( 256 ); + aSysLayoutData.orientation = 0; + + if ( mpMetaFile ) + { + if (pDXAry) + mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, nIndex, nLen ) ); + else + mpMetaFile->AddAction( new MetaTextAction( rStartPt, rStr, nIndex, nLen ) ); + } + + if ( !IsDeviceOutputNecessary() ) return aSysLayoutData; + + std::unique_ptr<SalLayout> pLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry); + + if ( !pLayout ) return aSysLayoutData; + + // setup glyphs + Point aPos; + const GlyphItem* pGlyph; + int nStart = 0; + SystemGlyphData aSystemGlyph; + while (pLayout->GetNextGlyph(&pGlyph, aPos, nStart, nullptr, &aSystemGlyph.fallbacklevel)) + { + aSystemGlyph.index = pGlyph->glyphId(); + aSystemGlyph.x = aPos.X(); + aSystemGlyph.y = aPos.Y(); + aSysLayoutData.rGlyphData.push_back(aSystemGlyph); + } + + // Get font data + aSysLayoutData.orientation = pLayout->GetOrientation(); + + return aSysLayoutData; +} + +bool OutputDevice::GetTextBoundRect( tools::Rectangle& rRect, + const OUString& rStr, sal_Int32 nBase, + sal_Int32 nIndex, sal_Int32 nLen, + sal_uLong nLayoutWidth, const long* pDXAry, + const SalLayoutGlyphs* pGlyphs ) const +{ + bool bRet = false; + rRect.SetEmpty(); + + std::unique_ptr<SalLayout> pSalLayout; + const Point aPoint; + // calculate offset when nBase!=nIndex + long nXOffset = 0; + if( nBase != nIndex ) + { + sal_Int32 nStart = std::min( nBase, nIndex ); + sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart; + pSalLayout = ImplLayout( rStr, nStart, nOfsLen, aPoint, nLayoutWidth, pDXAry ); + if( pSalLayout ) + { + nXOffset = pSalLayout->GetTextWidth(); + nXOffset /= pSalLayout->GetUnitsPerPixel(); + // TODO: fix offset calculation for Bidi case + if( nBase < nIndex) + nXOffset = -nXOffset; + } + } + + pSalLayout = ImplLayout(rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry, SalLayoutFlags::NONE, + nullptr, pGlyphs); + tools::Rectangle aPixelRect; + if( pSalLayout ) + { + bRet = pSalLayout->GetBoundRect(aPixelRect); + + if( bRet ) + { + int nWidthFactor = pSalLayout->GetUnitsPerPixel(); + + if( nWidthFactor > 1 ) + { + double fFactor = 1.0 / nWidthFactor; + aPixelRect.SetLeft( + static_cast< long >(aPixelRect.Left() * fFactor) ); + aPixelRect.SetRight( + static_cast< long >(aPixelRect.Right() * fFactor) ); + aPixelRect.SetTop( + static_cast< long >(aPixelRect.Top() * fFactor) ); + aPixelRect.SetBottom( + static_cast< long >(aPixelRect.Bottom() * fFactor) ); + } + + Point aRotatedOfs( mnTextOffX, mnTextOffY ); + aRotatedOfs -= pSalLayout->GetDrawPosition( Point( nXOffset, 0 ) ); + aPixelRect += aRotatedOfs; + rRect = PixelToLogic( aPixelRect ); + if( mbMap ) + rRect += Point( maMapRes.mnMapOfsX, maMapRes.mnMapOfsY ); + } + } + + return bRet; +} + +bool OutputDevice::GetTextOutlines( basegfx::B2DPolyPolygonVector& rVector, + const OUString& rStr, sal_Int32 nBase, + sal_Int32 nIndex, sal_Int32 nLen, + sal_uLong nLayoutWidth, const long* pDXArray ) const +{ + if (!InitFont()) + return false; + + bool bRet = false; + rVector.clear(); + if( nLen < 0 ) + { + nLen = rStr.getLength() - nIndex; + } + rVector.reserve( nLen ); + + // we want to get the Rectangle in logical units, so to + // avoid rounding errors we just size the font in logical units + bool bOldMap = mbMap; + if( bOldMap ) + { + const_cast<OutputDevice&>(*this).mbMap = false; + const_cast<OutputDevice&>(*this).mbNewFont = true; + } + + std::unique_ptr<SalLayout> pSalLayout; + + // calculate offset when nBase!=nIndex + long nXOffset = 0; + if( nBase != nIndex ) + { + sal_Int32 nStart = std::min( nBase, nIndex ); + sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart; + pSalLayout = ImplLayout( rStr, nStart, nOfsLen, Point(0,0), nLayoutWidth, pDXArray ); + if( pSalLayout ) + { + nXOffset = pSalLayout->GetTextWidth(); + pSalLayout.reset(); + // TODO: fix offset calculation for Bidi case + if( nBase > nIndex) + nXOffset = -nXOffset; + } + } + + pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), nLayoutWidth, pDXArray ); + if( pSalLayout ) + { + bRet = pSalLayout->GetOutline(rVector); + if( bRet ) + { + // transform polygon to pixel units + basegfx::B2DHomMatrix aMatrix; + + int nWidthFactor = pSalLayout->GetUnitsPerPixel(); + if( nXOffset | mnTextOffX | mnTextOffY ) + { + Point aRotatedOfs( mnTextOffX*nWidthFactor, mnTextOffY*nWidthFactor ); + aRotatedOfs -= pSalLayout->GetDrawPosition( Point( nXOffset, 0 ) ); + aMatrix.translate( aRotatedOfs.X(), aRotatedOfs.Y() ); + } + + if( nWidthFactor > 1 ) + { + double fFactor = 1.0 / nWidthFactor; + aMatrix.scale( fFactor, fFactor ); + } + + if( !aMatrix.isIdentity() ) + { + for (auto & elem : rVector) + elem.transform( aMatrix ); + } + } + + pSalLayout.reset(); + } + + if( bOldMap ) + { + // restore original font size and map mode + const_cast<OutputDevice&>(*this).mbMap = bOldMap; + const_cast<OutputDevice&>(*this).mbNewFont = true; + } + + return bRet; +} + +bool OutputDevice::GetTextOutlines( PolyPolyVector& rResultVector, + const OUString& rStr, sal_Int32 nBase, + sal_Int32 nIndex, sal_Int32 nLen, + sal_uLong nTWidth, const long* pDXArray ) const +{ + rResultVector.clear(); + + // get the basegfx polypolygon vector + basegfx::B2DPolyPolygonVector aB2DPolyPolyVector; + if( !GetTextOutlines( aB2DPolyPolyVector, rStr, nBase, nIndex, nLen, + nTWidth, pDXArray ) ) + return false; + + // convert to a tool polypolygon vector + rResultVector.reserve( aB2DPolyPolyVector.size() ); + for (auto const& elem : aB2DPolyPolyVector) + rResultVector.emplace_back(elem); // #i76339# + + return true; +} + +bool OutputDevice::GetTextOutline( tools::PolyPolygon& rPolyPoly, const OUString& rStr, + sal_Int32 nLen, + sal_uLong nTWidth, const long* pDXArray ) const +{ + rPolyPoly.Clear(); + + // get the basegfx polypolygon vector + basegfx::B2DPolyPolygonVector aB2DPolyPolyVector; + if( !GetTextOutlines( aB2DPolyPolyVector, rStr, 0/*nBase*/, 0/*nIndex*/, nLen, + nTWidth, pDXArray ) ) + return false; + + // convert and merge into a tool polypolygon + for (auto const& elem : aB2DPolyPolyVector) + for(auto const& rB2DPolygon : elem) + rPolyPoly.Insert(tools::Polygon(rB2DPolygon)); // #i76339# + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/textline.cxx b/vcl/source/outdev/textline.cxx new file mode 100644 index 000000000..6ade6113f --- /dev/null +++ b/vcl/source/outdev/textline.cxx @@ -0,0 +1,1026 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cassert> + +#include <sal/types.h> +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <vcl/outdev.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> + +#include <tools/helpers.hxx> + +#include <salgdi.hxx> +#include <impglyphitem.hxx> + +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/WaveLine.hxx> + +#define UNDERLINE_LAST LINESTYLE_BOLDWAVE +#define STRIKEOUT_LAST STRIKEOUT_X + +void OutputDevice::ImplInitTextLineSize() +{ + mpFontInstance->mxFontMetric->ImplInitTextLineSize( this ); +} + +void OutputDevice::ImplInitAboveTextLineSize() +{ + mpFontInstance->mxFontMetric->ImplInitAboveTextLineSize(); +} + +void OutputDevice::ImplDrawWavePixel( long nOriginX, long nOriginY, + long nCurX, long nCurY, + short nOrientation, + SalGraphics* pGraphics, + OutputDevice const * pOutDev, + bool bDrawPixAsRect, + long nPixWidth, long nPixHeight ) +{ + if ( nOrientation ) + { + Point aPoint( nOriginX, nOriginY ); + aPoint.RotateAround( nCurX, nCurY, nOrientation ); + } + + if ( bDrawPixAsRect ) + { + + pGraphics->DrawRect( nCurX, nCurY, nPixWidth, nPixHeight, pOutDev ); + } + else + { + pGraphics->DrawPixel( nCurX, nCurY, pOutDev ); + } +} + +void OutputDevice::ImplDrawWaveLine( long nBaseX, long nBaseY, + long nDistX, long nDistY, + long nWidth, long nHeight, + long nLineWidth, short nOrientation, + const Color& rColor ) +{ + if ( !nHeight ) + return; + + long nStartX = nBaseX + nDistX; + long nStartY = nBaseY + nDistY; + + // If the height is 1 pixel, it's enough output a line + if ( (nLineWidth == 1) && (nHeight == 1) ) + { + mpGraphics->SetLineColor( rColor ); + mbInitLineColor = true; + + long nEndX = nStartX+nWidth; + long nEndY = nStartY; + if ( nOrientation ) + { + Point aOriginPt( nBaseX, nBaseY ); + aOriginPt.RotateAround( nStartX, nStartY, nOrientation ); + aOriginPt.RotateAround( nEndX, nEndY, nOrientation ); + } + mpGraphics->DrawLine( nStartX, nStartY, nEndX, nEndY, this ); + } + else + { + long nCurX = nStartX; + long nCurY = nStartY; + long nDiffX = 2; + long nDiffY = nHeight-1; + long nCount = nWidth; + long nOffY = -1; + long nPixWidth; + long nPixHeight; + bool bDrawPixAsRect; + // On printers that output pixel via DrawRect() + if ( (GetOutDevType() == OUTDEV_PRINTER) || (nLineWidth > 1) ) + { + if ( mbLineColor || mbInitLineColor ) + { + mpGraphics->SetLineColor(); + mbInitLineColor = true; + } + mpGraphics->SetFillColor( rColor ); + mbInitFillColor = true; + bDrawPixAsRect = true; + nPixWidth = nLineWidth; + nPixHeight = ((nLineWidth*mnDPIX)+(mnDPIY/2))/mnDPIY; + } + else + { + mpGraphics->SetLineColor( rColor ); + mbInitLineColor = true; + nPixWidth = 1; + nPixHeight = 1; + bDrawPixAsRect = false; + } + + if ( !nDiffY ) + { + while ( nWidth ) + { + ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation, + mpGraphics, this, + bDrawPixAsRect, nPixWidth, nPixHeight ); + nCurX++; + nWidth--; + } + } + else + { + nCurY += nDiffY; + long nFreq = nCount / (nDiffX+nDiffY); + while ( nFreq-- ) + { + for( long i = nDiffY; i; --i ) + { + ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation, + mpGraphics, this, + bDrawPixAsRect, nPixWidth, nPixHeight ); + nCurX++; + nCurY += nOffY; + } + for( long i = nDiffX; i; --i ) + { + ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation, + mpGraphics, this, + bDrawPixAsRect, nPixWidth, nPixHeight ); + nCurX++; + } + nOffY = -nOffY; + } + nFreq = nCount % (nDiffX+nDiffY); + if ( nFreq ) + { + for( long i = nDiffY; i && nFreq; --i, --nFreq ) + { + ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation, + mpGraphics, this, + bDrawPixAsRect, nPixWidth, nPixHeight ); + nCurX++; + nCurY += nOffY; + + } + for( long i = nDiffX; i && nFreq; --i, --nFreq ) + { + ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nOrientation, + mpGraphics, this, + bDrawPixAsRect, nPixWidth, nPixHeight ); + nCurX++; + } + } + } + } +} + +void OutputDevice::ImplDrawWaveTextLine( long nBaseX, long nBaseY, + long nDistX, long nDistY, long nWidth, + FontLineStyle eTextLine, + Color aColor, + bool bIsAbove ) +{ + LogicalFontInstance* pFontInstance = mpFontInstance.get(); + long nLineHeight; + long nLinePos; + + if ( bIsAbove ) + { + nLineHeight = pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize(); + nLinePos = pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset(); + } + else + { + nLineHeight = pFontInstance->mxFontMetric->GetWavelineUnderlineSize(); + nLinePos = pFontInstance->mxFontMetric->GetWavelineUnderlineOffset(); + } + if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) ) + nLineHeight = 3; + + long nLineWidth = mnDPIX / 300; + if ( !nLineWidth ) + nLineWidth = 1; + + if ( eTextLine == LINESTYLE_BOLDWAVE ) + nLineWidth *= 2; + + nLinePos += nDistY - (nLineHeight / 2); + + long nLineWidthHeight = ((nLineWidth * mnDPIX) + (mnDPIY / 2)) / mnDPIY; + if ( eTextLine == LINESTYLE_DOUBLEWAVE ) + { + long nOrgLineHeight = nLineHeight; + nLineHeight /= 3; + if ( nLineHeight < 2 ) + { + if ( nOrgLineHeight > 1 ) + nLineHeight = 2; + else + nLineHeight = 1; + } + + long nLineDY = nOrgLineHeight-(nLineHeight*2); + if ( nLineDY < nLineWidthHeight ) + nLineDY = nLineWidthHeight; + + long nLineDY2 = nLineDY/2; + if ( !nLineDY2 ) + nLineDY2 = 1; + + nLinePos -= nLineWidthHeight-nLineDY2; + ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight, + nLineWidth, mpFontInstance->mnOrientation, aColor ); + nLinePos += nLineWidthHeight+nLineDY; + ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight, + nLineWidth, mpFontInstance->mnOrientation, aColor ); + } + else + { + nLinePos -= nLineWidthHeight/2; + ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight, + nLineWidth, mpFontInstance->mnOrientation, aColor ); + } +} + +void OutputDevice::ImplDrawStraightTextLine( long nBaseX, long nBaseY, + long nDistX, long nDistY, long nWidth, + FontLineStyle eTextLine, + Color aColor, + bool bIsAbove ) +{ + LogicalFontInstance* pFontInstance = mpFontInstance.get(); + long nLineHeight = 0; + long nLinePos = 0; + long nLinePos2 = 0; + + const long nY = nDistY; + + if ( eTextLine > UNDERLINE_LAST ) + eTextLine = LINESTYLE_SINGLE; + + switch ( eTextLine ) + { + case LINESTYLE_SINGLE: + case LINESTYLE_DOTTED: + case LINESTYLE_DASH: + case LINESTYLE_LONGDASH: + case LINESTYLE_DASHDOT: + case LINESTYLE_DASHDOTDOT: + if ( bIsAbove ) + { + nLineHeight = pFontInstance->mxFontMetric->GetAboveUnderlineSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetAboveUnderlineOffset(); + } + else + { + nLineHeight = pFontInstance->mxFontMetric->GetUnderlineSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetUnderlineOffset(); + } + break; + case LINESTYLE_BOLD: + case LINESTYLE_BOLDDOTTED: + case LINESTYLE_BOLDDASH: + case LINESTYLE_BOLDLONGDASH: + case LINESTYLE_BOLDDASHDOT: + case LINESTYLE_BOLDDASHDOTDOT: + if ( bIsAbove ) + { + nLineHeight = pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset(); + } + else + { + nLineHeight = pFontInstance->mxFontMetric->GetBoldUnderlineSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetBoldUnderlineOffset(); + } + break; + case LINESTYLE_DOUBLE: + if ( bIsAbove ) + { + nLineHeight = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1(); + nLinePos2 = nY + pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2(); + } + else + { + nLineHeight = pFontInstance->mxFontMetric->GetDoubleUnderlineSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1(); + nLinePos2 = nY + pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2(); + } + break; + default: + break; + } + + if ( nLineHeight ) + { + if ( mbLineColor || mbInitLineColor ) + { + mpGraphics->SetLineColor(); + mbInitLineColor = true; + } + mpGraphics->SetFillColor( aColor ); + mbInitFillColor = true; + + long nLeft = nDistX; + + switch ( eTextLine ) + { + case LINESTYLE_SINGLE: + case LINESTYLE_BOLD: + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight ); + break; + case LINESTYLE_DOUBLE: + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight ); + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos2, nWidth, nLineHeight ); + break; + case LINESTYLE_DOTTED: + case LINESTYLE_BOLDDOTTED: + { + long nDotWidth = nLineHeight*mnDPIY; + nDotWidth += mnDPIY/2; + nDotWidth /= mnDPIY; + + long nTempWidth = nDotWidth; + long nEnd = nLeft+nWidth; + while ( nLeft < nEnd ) + { + if ( nLeft+nTempWidth > nEnd ) + nTempWidth = nEnd-nLeft; + + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempWidth, nLineHeight ); + nLeft += nDotWidth*2; + } + } + break; + case LINESTYLE_DASH: + case LINESTYLE_LONGDASH: + case LINESTYLE_BOLDDASH: + case LINESTYLE_BOLDLONGDASH: + { + long nDotWidth = nLineHeight*mnDPIY; + nDotWidth += mnDPIY/2; + nDotWidth /= mnDPIY; + + long nMinDashWidth; + long nMinSpaceWidth; + long nSpaceWidth; + long nDashWidth; + if ( (eTextLine == LINESTYLE_LONGDASH) || + (eTextLine == LINESTYLE_BOLDLONGDASH) ) + { + nMinDashWidth = nDotWidth*6; + nMinSpaceWidth = nDotWidth*2; + nDashWidth = 200; + nSpaceWidth = 100; + } + else + { + nMinDashWidth = nDotWidth*4; + nMinSpaceWidth = (nDotWidth*150)/100; + nDashWidth = 100; + nSpaceWidth = 50; + } + nDashWidth = ((nDashWidth*mnDPIX)+1270)/2540; + nSpaceWidth = ((nSpaceWidth*mnDPIX)+1270)/2540; + // DashWidth will be increased if the line is getting too thick + // in proportion to the line's length + if ( nDashWidth < nMinDashWidth ) + nDashWidth = nMinDashWidth; + if ( nSpaceWidth < nMinSpaceWidth ) + nSpaceWidth = nMinSpaceWidth; + + long nTempWidth = nDashWidth; + long nEnd = nLeft+nWidth; + while ( nLeft < nEnd ) + { + if ( nLeft+nTempWidth > nEnd ) + nTempWidth = nEnd-nLeft; + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempWidth, nLineHeight ); + nLeft += nDashWidth+nSpaceWidth; + } + } + break; + case LINESTYLE_DASHDOT: + case LINESTYLE_BOLDDASHDOT: + { + long nDotWidth = nLineHeight*mnDPIY; + nDotWidth += mnDPIY/2; + nDotWidth /= mnDPIY; + + long nDashWidth = ((100*mnDPIX)+1270)/2540; + long nMinDashWidth = nDotWidth*4; + // DashWidth will be increased if the line is getting too thick + // in proportion to the line's length + if ( nDashWidth < nMinDashWidth ) + nDashWidth = nMinDashWidth; + + long nTempDotWidth = nDotWidth; + long nTempDashWidth = nDashWidth; + long nEnd = nLeft+nWidth; + while ( nLeft < nEnd ) + { + if ( nLeft+nTempDotWidth > nEnd ) + nTempDotWidth = nEnd-nLeft; + + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight ); + nLeft += nDotWidth*2; + if ( nLeft > nEnd ) + break; + + if ( nLeft+nTempDashWidth > nEnd ) + nTempDashWidth = nEnd-nLeft; + + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDashWidth, nLineHeight ); + nLeft += nDashWidth+nDotWidth; + } + } + break; + case LINESTYLE_DASHDOTDOT: + case LINESTYLE_BOLDDASHDOTDOT: + { + long nDotWidth = nLineHeight*mnDPIY; + nDotWidth += mnDPIY/2; + nDotWidth /= mnDPIY; + + long nDashWidth = ((100*mnDPIX)+1270)/2540; + long nMinDashWidth = nDotWidth*4; + // DashWidth will be increased if the line is getting too thick + // in proportion to the line's length + if ( nDashWidth < nMinDashWidth ) + nDashWidth = nMinDashWidth; + + long nTempDotWidth = nDotWidth; + long nTempDashWidth = nDashWidth; + long nEnd = nLeft+nWidth; + while ( nLeft < nEnd ) + { + if ( nLeft+nTempDotWidth > nEnd ) + nTempDotWidth = nEnd-nLeft; + + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight ); + nLeft += nDotWidth*2; + if ( nLeft > nEnd ) + break; + + if ( nLeft+nTempDotWidth > nEnd ) + nTempDotWidth = nEnd-nLeft; + + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight ); + nLeft += nDotWidth*2; + if ( nLeft > nEnd ) + break; + + if ( nLeft+nTempDashWidth > nEnd ) + nTempDashWidth = nEnd-nLeft; + + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDashWidth, nLineHeight ); + nLeft += nDashWidth+nDotWidth; + } + } + break; + default: + break; + } + } +} + +void OutputDevice::ImplDrawStrikeoutLine( long nBaseX, long nBaseY, + long nDistX, long nDistY, long nWidth, + FontStrikeout eStrikeout, + Color aColor ) +{ + LogicalFontInstance* pFontInstance = mpFontInstance.get(); + long nLineHeight = 0; + long nLinePos = 0; + long nLinePos2 = 0; + + long nY = nDistY; + + if ( eStrikeout > STRIKEOUT_LAST ) + eStrikeout = STRIKEOUT_SINGLE; + + switch ( eStrikeout ) + { + case STRIKEOUT_SINGLE: + nLineHeight = pFontInstance->mxFontMetric->GetStrikeoutSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetStrikeoutOffset(); + break; + case STRIKEOUT_BOLD: + nLineHeight = pFontInstance->mxFontMetric->GetBoldStrikeoutSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetBoldStrikeoutOffset(); + break; + case STRIKEOUT_DOUBLE: + nLineHeight = pFontInstance->mxFontMetric->GetDoubleStrikeoutSize(); + nLinePos = nY + pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1(); + nLinePos2 = nY + pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2(); + break; + default: + break; + } + + if ( nLineHeight ) + { + if ( mbLineColor || mbInitLineColor ) + { + mpGraphics->SetLineColor(); + mbInitLineColor = true; + } + mpGraphics->SetFillColor( aColor ); + mbInitFillColor = true; + + const long& nLeft = nDistX; + + switch ( eStrikeout ) + { + case STRIKEOUT_SINGLE: + case STRIKEOUT_BOLD: + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight ); + break; + case STRIKEOUT_DOUBLE: + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight ); + ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos2, nWidth, nLineHeight ); + break; + default: + break; + } + } +} + +void OutputDevice::ImplDrawStrikeoutChar( long nBaseX, long nBaseY, + long nDistX, long nDistY, long nWidth, + FontStrikeout eStrikeout, + Color aColor ) +{ + // See qadevOOo/testdocs/StrikeThrough.odt for examples if you need + // to tweak this + if (!nWidth) + return; + + // prepare string for strikeout measurement + const char cStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? '/' : 'X'; + static const int nTestStrLen = 4; + static const int nMaxStrikeStrLen = 2048; + sal_Unicode aChars[nMaxStrikeStrLen+1]; // +1 for valgrind... + + for( int i = 0; i < nTestStrLen; ++i) + aChars[i] = cStrikeoutChar; + + const OUString aStrikeoutTest(aChars, nTestStrLen); + + // calculate approximation of strikeout atom size + long nStrikeoutWidth = 0; + std::unique_ptr<SalLayout> pLayout = ImplLayout( aStrikeoutTest, 0, nTestStrLen ); + if( pLayout ) + { + nStrikeoutWidth = pLayout->GetTextWidth() / (nTestStrLen * pLayout->GetUnitsPerPixel()); + } + if( nStrikeoutWidth <= 0 ) // sanity check + return; + + int nStrikeStrLen = (nWidth+(nStrikeoutWidth-1)) / nStrikeoutWidth; + if( nStrikeStrLen > nMaxStrikeStrLen ) + nStrikeStrLen = nMaxStrikeStrLen; + + // build the strikeout string + for( int i = nTestStrLen; i < nStrikeStrLen; ++i) + aChars[i] = cStrikeoutChar; + + const OUString aStrikeoutText(aChars, nStrikeStrLen); + + if( mpFontInstance->mnOrientation ) + { + Point aOriginPt(0, 0); + aOriginPt.RotateAround( nDistX, nDistY, mpFontInstance->mnOrientation ); + } + + nBaseX += nDistX; + nBaseY += nDistY; + + // strikeout text has to be left aligned + ComplexTextLayoutFlags nOrigTLM = mnTextLayoutMode; + mnTextLayoutMode = ComplexTextLayoutFlags::BiDiStrong; + pLayout = ImplLayout( aStrikeoutText, 0, aStrikeoutText.getLength() ); + mnTextLayoutMode = nOrigTLM; + + if( !pLayout ) + return; + + // draw the strikeout text + const Color aOldColor = GetTextColor(); + SetTextColor( aColor ); + ImplInitTextColor(); + + pLayout->DrawBase() = Point( nBaseX+mnTextOffX, nBaseY+mnTextOffY ); + + tools::Rectangle aPixelRect; + aPixelRect.SetLeft( nBaseX+mnTextOffX ); + aPixelRect.SetRight( aPixelRect.Left()+nWidth ); + aPixelRect.SetBottom( nBaseY+mpFontInstance->mxFontMetric->GetDescent() ); + aPixelRect.SetTop( nBaseY-mpFontInstance->mxFontMetric->GetAscent() ); + + if (mpFontInstance->mnOrientation) + { + tools::Polygon aPoly( aPixelRect ); + aPoly.Rotate( Point(nBaseX+mnTextOffX, nBaseY+mnTextOffY), mpFontInstance->mnOrientation); + aPixelRect = aPoly.GetBoundRect(); + } + + Push( PushFlags::CLIPREGION ); + IntersectClipRegion( PixelToLogic(aPixelRect) ); + if( mbInitClipRegion ) + InitClipRegion(); + + pLayout->DrawText( *mpGraphics ); + + Pop(); + + SetTextColor( aOldColor ); + ImplInitTextColor(); +} + +void OutputDevice::ImplDrawTextLine( long nX, long nY, + long nDistX, DeviceCoordinate nWidth, + FontStrikeout eStrikeout, + FontLineStyle eUnderline, + FontLineStyle eOverline, + bool bUnderlineAbove ) +{ + if ( !nWidth ) + return; + + Color aStrikeoutColor = GetTextColor(); + Color aUnderlineColor = GetTextLineColor(); + Color aOverlineColor = GetOverlineColor(); + bool bStrikeoutDone = false; + bool bUnderlineDone = false; + bool bOverlineDone = false; + + if ( IsRTLEnabled() ) + { + long nXAdd = nWidth - nDistX; + if( mpFontInstance->mnOrientation ) + nXAdd = FRound( nXAdd * cos( mpFontInstance->mnOrientation * F_PI1800 ) ); + + nX += nXAdd - 1; + } + + if ( !IsTextLineColor() ) + aUnderlineColor = GetTextColor(); + + if ( !IsOverlineColor() ) + aOverlineColor = GetTextColor(); + + if ( (eUnderline == LINESTYLE_SMALLWAVE) || + (eUnderline == LINESTYLE_WAVE) || + (eUnderline == LINESTYLE_DOUBLEWAVE) || + (eUnderline == LINESTYLE_BOLDWAVE) ) + { + ImplDrawWaveTextLine( nX, nY, nDistX, 0, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); + bUnderlineDone = true; + } + if ( (eOverline == LINESTYLE_SMALLWAVE) || + (eOverline == LINESTYLE_WAVE) || + (eOverline == LINESTYLE_DOUBLEWAVE) || + (eOverline == LINESTYLE_BOLDWAVE) ) + { + ImplDrawWaveTextLine( nX, nY, nDistX, 0, nWidth, eOverline, aOverlineColor, true ); + bOverlineDone = true; + } + + if ( (eStrikeout == STRIKEOUT_SLASH) || + (eStrikeout == STRIKEOUT_X) ) + { + ImplDrawStrikeoutChar( nX, nY, nDistX, 0, nWidth, eStrikeout, aStrikeoutColor ); + bStrikeoutDone = true; + } + + if ( !bUnderlineDone ) + ImplDrawStraightTextLine( nX, nY, nDistX, 0, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); + + if ( !bOverlineDone ) + ImplDrawStraightTextLine( nX, nY, nDistX, 0, nWidth, eOverline, aOverlineColor, true ); + + if ( !bStrikeoutDone ) + ImplDrawStrikeoutLine( nX, nY, nDistX, 0, nWidth, eStrikeout, aStrikeoutColor ); +} + +void OutputDevice::ImplDrawTextLines( SalLayout& rSalLayout, FontStrikeout eStrikeout, + FontLineStyle eUnderline, FontLineStyle eOverline, + bool bWordLine, bool bUnderlineAbove ) +{ + if( bWordLine ) + { + // draw everything relative to the layout base point + const Point aStartPt = rSalLayout.DrawBase(); + + // calculate distance of each word from the base point + Point aPos; + DeviceCoordinate nDist = 0; + DeviceCoordinate nWidth = 0; + const GlyphItem* pGlyph; + int nStart = 0; + while (rSalLayout.GetNextGlyph(&pGlyph, aPos, nStart)) + { + // calculate the boundaries of each word + if (!pGlyph->IsSpacing()) + { + if( !nWidth ) + { + // get the distance to the base point (as projected to baseline) + nDist = aPos.X() - aStartPt.X(); + if( mpFontInstance->mnOrientation ) + { + const long nDY = aPos.Y() - aStartPt.Y(); + const double fRad = mpFontInstance->mnOrientation * F_PI1800; + nDist = FRound( nDist*cos(fRad) - nDY*sin(fRad) ); + } + } + + // update the length of the textline + nWidth += pGlyph->m_nNewWidth; + } + else if( nWidth > 0 ) + { + // draw the textline for each word + ImplDrawTextLine( aStartPt.X(), aStartPt.Y(), nDist, nWidth, + eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + nWidth = 0; + } + } + + // draw textline for the last word + if( nWidth > 0 ) + { + ImplDrawTextLine( aStartPt.X(), aStartPt.Y(), nDist, nWidth, + eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + } + } + else + { + Point aStartPt = rSalLayout.GetDrawPosition(); + ImplDrawTextLine( aStartPt.X(), aStartPt.Y(), 0, + rSalLayout.GetTextWidth() / rSalLayout.GetUnitsPerPixel(), + eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + } +} + +void OutputDevice::ImplDrawMnemonicLine( long nX, long nY, long nWidth ) +{ + long nBaseX = nX; + if( /*HasMirroredGraphics() &&*/ IsRTLEnabled() ) + { + // add some strange offset + nX += 2; + // revert the hack that will be done later in ImplDrawTextLine + nX = nBaseX - nWidth - (nX - nBaseX - 1); + } + + ImplDrawTextLine( nX, nY, 0, nWidth, STRIKEOUT_NONE, LINESTYLE_SINGLE, LINESTYLE_NONE, false ); +} + +void OutputDevice::SetTextLineColor() +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextLineColorAction( Color(), false ) ); + + maTextLineColor = COL_TRANSPARENT; + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextLineColor(); +} + +void OutputDevice::SetTextLineColor( const Color& rColor ) +{ + + Color aColor( rColor ); + + if ( mnDrawMode & ( DrawModeFlags::BlackText | DrawModeFlags::WhiteText | + DrawModeFlags::GrayText | + DrawModeFlags::SettingsText ) ) + { + if ( mnDrawMode & DrawModeFlags::BlackText ) + { + aColor = COL_BLACK; + } + else if ( mnDrawMode & DrawModeFlags::WhiteText ) + { + aColor = COL_WHITE; + } + else if ( mnDrawMode & DrawModeFlags::GrayText ) + { + const sal_uInt8 cLum = aColor.GetLuminance(); + aColor = Color( cLum, cLum, cLum ); + } + else if ( mnDrawMode & DrawModeFlags::SettingsText ) + { + aColor = GetSettings().GetStyleSettings().GetFontColor(); + } + } + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextLineColorAction( aColor, true ) ); + + maTextLineColor = aColor; + + if( mpAlphaVDev ) + mpAlphaVDev->SetTextLineColor( COL_BLACK ); +} + +void OutputDevice::SetOverlineColor() +{ + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaOverlineColorAction( Color(), false ) ); + + maOverlineColor = COL_TRANSPARENT; + + if( mpAlphaVDev ) + mpAlphaVDev->SetOverlineColor(); +} + +void OutputDevice::SetOverlineColor( const Color& rColor ) +{ + + Color aColor( rColor ); + + if ( mnDrawMode & ( DrawModeFlags::BlackText | DrawModeFlags::WhiteText | + DrawModeFlags::GrayText | + DrawModeFlags::SettingsText ) ) + { + if ( mnDrawMode & DrawModeFlags::BlackText ) + { + aColor = COL_BLACK; + } + else if ( mnDrawMode & DrawModeFlags::WhiteText ) + { + aColor = COL_WHITE; + } + else if ( mnDrawMode & DrawModeFlags::GrayText ) + { + const sal_uInt8 cLum = aColor.GetLuminance(); + aColor = Color( cLum, cLum, cLum ); + } + else if ( mnDrawMode & DrawModeFlags::SettingsText ) + { + aColor = GetSettings().GetStyleSettings().GetFontColor(); + } + } + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaOverlineColorAction( aColor, true ) ); + + maOverlineColor = aColor; + + if( mpAlphaVDev ) + mpAlphaVDev->SetOverlineColor( COL_BLACK ); +} + +void OutputDevice::DrawTextLine( const Point& rPos, long nWidth, + FontStrikeout eStrikeout, + FontLineStyle eUnderline, + FontLineStyle eOverline, + bool bUnderlineAbove ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaTextLineAction( rPos, nWidth, eStrikeout, eUnderline, eOverline ) ); + + if ( ((eUnderline == LINESTYLE_NONE) || (eUnderline == LINESTYLE_DONTKNOW)) && + ((eOverline == LINESTYLE_NONE) || (eOverline == LINESTYLE_DONTKNOW)) && + ((eStrikeout == STRIKEOUT_NONE) || (eStrikeout == STRIKEOUT_DONTKNOW)) ) + { + return; + } + if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return; + + // initialize font if needed to get text offsets + // TODO: only needed for mnTextOff!=(0,0) + if (!InitFont()) + return; + + Point aPos = ImplLogicToDevicePixel( rPos ); + DeviceCoordinate fWidth; + fWidth = LogicWidthToDeviceCoordinate( nWidth ); + aPos += Point( mnTextOffX, mnTextOffY ); + ImplDrawTextLine( aPos.X(), aPos.X(), 0, fWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + + if( mpAlphaVDev ) + mpAlphaVDev->DrawTextLine( rPos, nWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove ); +} + +void OutputDevice::DrawWaveLine(const Point& rStartPos, const Point& rEndPos, long nLineWidth) +{ + assert(!is_double_buffered_window()); + + if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return; + + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbOutputClipped ) + return; + + if (!InitFont()) + return; + + Point aStartPt = ImplLogicToDevicePixel(rStartPos); + Point aEndPt = ImplLogicToDevicePixel(rEndPos); + + long nStartX = aStartPt.X(); + long nStartY = aStartPt.Y(); + long nEndX = aEndPt.X(); + long nEndY = aEndPt.Y(); + double fOrientation = 0.0; + + // handle rotation + if (nStartY != nEndY || nStartX > nEndX) + { + long nLengthX = nEndX - nStartX; + fOrientation = std::atan2(nStartY - nEndY, (nLengthX == 0 ? 0.000000001 : nLengthX)); + fOrientation /= F_PI180; + // un-rotate the end point + aStartPt.RotateAround(nEndX, nEndY, -fOrientation * 10.0); + } + + long nWaveHeight = 3; + + // Handle HiDPI + float fScaleFactor = GetDPIScaleFactor(); + if (fScaleFactor > 1.0f) + { + nWaveHeight *= fScaleFactor; + + nStartY += fScaleFactor - 1; // Shift down additional pixel(s) to create more visual separation. + + // odd heights look better than even + if (nWaveHeight % 2 == 0) + { + nWaveHeight--; + } + } + + // #109280# make sure the waveline does not exceed the descent to avoid paint problems + LogicalFontInstance* pFontInstance = mpFontInstance.get(); + if (nWaveHeight > pFontInstance->mxFontMetric->GetWavelineUnderlineSize()) + { + nWaveHeight = pFontInstance->mxFontMetric->GetWavelineUnderlineSize(); + // tdf#124848 hairline + nLineWidth = 0; + } + + const basegfx::B2DRectangle aWaveLineRectangle(nStartX, nStartY, nEndX, nEndY + nWaveHeight); + const basegfx::B2DPolygon aWaveLinePolygon = basegfx::createWaveLinePolygon(aWaveLineRectangle); + const basegfx::B2DHomMatrix aRotationMatrix = basegfx::utils::createRotateAroundPoint(nStartX, nStartY, basegfx::deg2rad(-fOrientation)); + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + mpGraphics->SetLineColor(GetLineColor()); + mpGraphics->DrawPolyLine( + aRotationMatrix, + aWaveLinePolygon, + 0.0, + nLineWidth, + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), + bPixelSnapHairline, + this); + + if( mpAlphaVDev ) + mpAlphaVDev->DrawWaveLine( rStartPos, rEndPos, nLineWidth ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/transparent.cxx b/vcl/source/outdev/transparent.cxx new file mode 100644 index 000000000..56e4f2d03 --- /dev/null +++ b/vcl/source/outdev/transparent.cxx @@ -0,0 +1,859 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cassert> + +#include <sal/types.h> +#include <tools/helpers.hxx> +#include <rtl/math.hxx> + +#include <memory> + +#include <vcl/bitmapaccess.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <vcl/outdev.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> + +#include <outdata.hxx> +#include <salgdi.hxx> +#include <bitmapwriteaccess.hxx> + +namespace +{ + /** + * Perform a safe approximation of a polygon from double-precision + * coordinates to integer coordinates, to ensure that it has at least 2 + * pixels in both X and Y directions. + */ + tools::Polygon toPolygon( const basegfx::B2DPolygon& rPoly ) + { + basegfx::B2DRange aRange = rPoly.getB2DRange(); + double fW = aRange.getWidth(), fH = aRange.getHeight(); + if (0.0 < fW && 0.0 < fH && (fW <= 1.0 || fH <= 1.0)) + { + // This polygon not empty but is too small to display. Approximate it + // with a rectangle large enough to be displayed. + double nX = aRange.getMinX(), nY = aRange.getMinY(); + double nW = std::max<double>(1.0, rtl::math::round(fW)); + double nH = std::max<double>(1.0, rtl::math::round(fH)); + + tools::Polygon aTarget; + aTarget.Insert(0, Point(nX, nY)); + aTarget.Insert(1, Point(nX+nW, nY)); + aTarget.Insert(2, Point(nX+nW, nY+nH)); + aTarget.Insert(3, Point(nX, nY+nH)); + aTarget.Insert(4, Point(nX, nY)); + return aTarget; + } + return tools::Polygon(rPoly); + } + + tools::PolyPolygon toPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly ) + { + tools::PolyPolygon aTarget; + for (auto const& rB2DPolygon : rPolyPoly) + aTarget.Insert(toPolygon(rB2DPolygon)); + + return aTarget; + } +} + +Color OutputDevice::ImplDrawModeToColor( const Color& rColor ) const +{ + Color aColor( rColor ); + DrawModeFlags nDrawMode = GetDrawMode(); + + if( nDrawMode & ( DrawModeFlags::BlackLine | DrawModeFlags::WhiteLine | + DrawModeFlags::GrayLine | + DrawModeFlags::SettingsLine ) ) + { + if( !ImplIsColorTransparent( aColor ) ) + { + if( nDrawMode & DrawModeFlags::BlackLine ) + { + aColor = COL_BLACK; + } + else if( nDrawMode & DrawModeFlags::WhiteLine ) + { + aColor = COL_WHITE; + } + else if( nDrawMode & DrawModeFlags::GrayLine ) + { + const sal_uInt8 cLum = aColor.GetLuminance(); + aColor = Color( cLum, cLum, cLum ); + } + else if( nDrawMode & DrawModeFlags::SettingsLine ) + { + aColor = GetSettings().GetStyleSettings().GetFontColor(); + } + } + } + return aColor; +} + +void OutputDevice::ImplPrintTransparent( const Bitmap& rBmp, const Bitmap& rMask, + const Point& rDestPt, const Size& rDestSize, + const Point& rSrcPtPixel, const Size& rSrcSizePixel ) +{ + Point aDestPt( LogicToPixel( rDestPt ) ); + Size aDestSz( LogicToPixel( rDestSize ) ); + tools::Rectangle aSrcRect( rSrcPtPixel, rSrcSizePixel ); + + aSrcRect.Justify(); + + if( rBmp.IsEmpty() || !aSrcRect.GetWidth() || !aSrcRect.GetHeight() || !aDestSz.Width() || !aDestSz.Height() ) + return; + + Bitmap aPaint( rBmp ), aMask( rMask ); + BmpMirrorFlags nMirrFlags = BmpMirrorFlags::NONE; + + if( aMask.GetBitCount() > 1 ) + aMask.Convert( BmpConversion::N1BitThreshold ); + + // mirrored horizontically + if( aDestSz.Width() < 0 ) + { + aDestSz.setWidth( -aDestSz.Width() ); + aDestPt.AdjustX( -( aDestSz.Width() - 1 ) ); + nMirrFlags |= BmpMirrorFlags::Horizontal; + } + + // mirrored vertically + if( aDestSz.Height() < 0 ) + { + aDestSz.setHeight( -aDestSz.Height() ); + aDestPt.AdjustY( -( aDestSz.Height() - 1 ) ); + nMirrFlags |= BmpMirrorFlags::Vertical; + } + + // source cropped? + if( aSrcRect != tools::Rectangle( Point(), aPaint.GetSizePixel() ) ) + { + aPaint.Crop( aSrcRect ); + aMask.Crop( aSrcRect ); + } + + // destination mirrored + if( nMirrFlags != BmpMirrorFlags::NONE ) + { + aPaint.Mirror( nMirrFlags ); + aMask.Mirror( nMirrFlags ); + } + + // we always want to have a mask + if( aMask.IsEmpty() ) + { + aMask = Bitmap( aSrcRect.GetSize(), 1 ); + aMask.Erase( COL_BLACK ); + } + + // do painting + const long nSrcWidth = aSrcRect.GetWidth(), nSrcHeight = aSrcRect.GetHeight(); + long nX, nY; // , nWorkX, nWorkY, nWorkWidth, nWorkHeight; + std::unique_ptr<long[]> pMapX(new long[ nSrcWidth + 1 ]); + std::unique_ptr<long[]> pMapY(new long[ nSrcHeight + 1 ]); + const bool bOldMap = mbMap; + + mbMap = false; + + // create forward mapping tables + for( nX = 0; nX <= nSrcWidth; nX++ ) + pMapX[ nX ] = aDestPt.X() + FRound( static_cast<double>(aDestSz.Width()) * nX / nSrcWidth ); + + for( nY = 0; nY <= nSrcHeight; nY++ ) + pMapY[ nY ] = aDestPt.Y() + FRound( static_cast<double>(aDestSz.Height()) * nY / nSrcHeight ); + + // walk through all rectangles of mask + const vcl::Region aWorkRgn(aMask.CreateRegion(COL_BLACK, tools::Rectangle(Point(), aMask.GetSizePixel()))); + RectangleVector aRectangles; + aWorkRgn.GetRegionRectangles(aRectangles); + + for (auto const& rectangle : aRectangles) + { + const Point aMapPt(pMapX[rectangle.Left()], pMapY[rectangle.Top()]); + const Size aMapSz( pMapX[rectangle.Right() + 1] - aMapPt.X(), // pMapX[L + W] -> L + ((R - L) + 1) -> R + 1 + pMapY[rectangle.Bottom() + 1] - aMapPt.Y()); // same for Y + Bitmap aBandBmp(aPaint); + + aBandBmp.Crop(rectangle); + DrawBitmap(aMapPt, aMapSz, Point(), aBandBmp.GetSizePixel(), aBandBmp); + } + + mbMap = bOldMap; + +} + +// Caution: This method is nearly the same as +// void OutputDevice::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rB2DPolyPoly ) +// so when changes are made here do not forget to make changes there, too + +void OutputDevice::DrawTransparent( + const basegfx::B2DHomMatrix& rObjectTransform, + const basegfx::B2DPolyPolygon& rB2DPolyPoly, + double fTransparency) +{ + assert(!is_double_buffered_window()); + + // AW: Do NOT paint empty PolyPolygons + if(!rB2DPolyPoly.count()) + return; + + // we need a graphics + if( !mpGraphics && !AcquireGraphics() ) + return; + + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return; + + if( mbInitLineColor ) + InitLineColor(); + + if( mbInitFillColor ) + InitFillColor(); + + if((mnAntialiasing & AntialiasingFlags::EnableB2dDraw) && + mpGraphics->supportsOperation(OutDevSupportType::B2DDraw) && + (RasterOp::OverPaint == GetRasterOp()) ) + { + // b2dpolygon support not implemented yet on non-UNX platforms + basegfx::B2DPolyPolygon aB2DPolyPolygon(rB2DPolyPoly); + + // ensure it is closed + if(!aB2DPolyPolygon.isClosed()) + { + // maybe assert, prevents buffering due to making a copy + aB2DPolyPolygon.setClosed( true ); + } + + // create ObjectToDevice transformation + const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rObjectTransform); + // TODO: this must not drop transparency for mpAlphaVDev case, but instead use premultiplied + // aplha... but that requires using premultiplied alpha also for already drawn data + const double fAdjustedTransparency = mpAlphaVDev ? 0 : fTransparency; + bool bDrawnOk(true); + + if( IsFillColor() ) + { + bDrawnOk = mpGraphics->DrawPolyPolygon( + aFullTransform, + aB2DPolyPolygon, + fAdjustedTransparency, + this); + } + + if( bDrawnOk && IsLineColor() ) + { + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + for(auto const& rPolygon : aB2DPolyPolygon) + { + mpGraphics->DrawPolyLine( + aFullTransform, + rPolygon, + fAdjustedTransparency, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + this ); + } + } + + if( bDrawnOk ) + { + if( mpMetaFile ) + { + // tdf#119843 need transformed Polygon here + basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly); + aB2DPolyPoly.transform(rObjectTransform); + mpMetaFile->AddAction( + new MetaTransparentAction( + tools::PolyPolygon(aB2DPolyPoly), + static_cast< sal_uInt16 >(fTransparency * 100.0))); + } + + if (mpAlphaVDev) + mpAlphaVDev->DrawTransparent(rObjectTransform, rB2DPolyPoly, fTransparency); + + return; + } + } + + // fallback to old polygon drawing if needed + // tdf#119843 need transformed Polygon here + basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly); + aB2DPolyPoly.transform(rObjectTransform); + DrawTransparent( + toPolyPolygon(aB2DPolyPoly), + static_cast<sal_uInt16>(fTransparency * 100.0)); +} + +void OutputDevice::DrawInvisiblePolygon( const tools::PolyPolygon& rPolyPoly ) +{ + assert(!is_double_buffered_window()); + + // short circuit if the polygon border is invisible too + if( !mbLineColor ) + return; + + // we assume that the border is NOT to be drawn transparently??? + Push( PushFlags::FILLCOLOR ); + SetFillColor(); + DrawPolyPolygon( rPolyPoly ); + Pop(); +} + +bool OutputDevice::DrawTransparentNatively ( const tools::PolyPolygon& rPolyPoly, + sal_uInt16 nTransparencePercent ) +{ + assert(!is_double_buffered_window()); + + bool bDrawn = false; + + // debug helper: + static const char* pDisableNative = getenv( "SAL_DISABLE_NATIVE_ALPHA"); + + if( !pDisableNative && + mpGraphics->supportsOperation( OutDevSupportType::B2DDraw ) +#if defined UNX && ! defined MACOSX && ! defined IOS + && GetBitCount() > 8 +#endif +#ifdef _WIN32 + // workaround bad dithering on remote displaying when using GDI+ with toolbar button highlighting + && !rPolyPoly.IsRect() +#endif + ) + { + // prepare the graphics device + if( mbInitClipRegion ) + InitClipRegion(); + + if( mbOutputClipped ) + return false; + + if( mbInitLineColor ) + InitLineColor(); + + if( mbInitFillColor ) + InitFillColor(); + + // get the polygon in device coordinates + basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon()); + const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation()); + + const double fTransparency = 0.01 * nTransparencePercent; + if( mbFillColor ) + { + // #i121591# + // CAUTION: Only non printing (pixel-renderer) VCL commands from OutputDevices + // should be used when printing. Normally this is avoided by the printer being + // non-AAed and thus e.g. on WIN GdiPlus calls are not used. It may be necessary + // to figure out a way of moving this code to its own function that is + // overridden by the Print class, which will mean we deliberately override the + // functionality and we use the fallback some lines below (which is not very good, + // though. For now, WinSalGraphics::drawPolyPolygon will detect printer usage and + // correct the wrong mapping (see there for details) + bDrawn = mpGraphics->DrawPolyPolygon( + aTransform, + aB2DPolyPolygon, + fTransparency, + this); + } + + if( mbLineColor ) + { + // disable the fill color for now + mpGraphics->SetFillColor(); + + // draw the border line + const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline); + + for(auto const& rPolygon : aB2DPolyPolygon) + { + bDrawn = mpGraphics->DrawPolyLine( + aTransform, + rPolygon, + fTransparency, + 0.0, // tdf#124848 hairline + nullptr, // MM01 + basegfx::B2DLineJoin::NONE, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default + bPixelSnapHairline, + this ); + } + + // prepare to restore the fill color + mbInitFillColor = mbFillColor; + } + } + + return bDrawn; +} + +void OutputDevice::EmulateDrawTransparent ( const tools::PolyPolygon& rPolyPoly, + sal_uInt16 nTransparencePercent ) +{ + // #110958# Disable alpha VDev, we perform the necessary + VirtualDevice* pOldAlphaVDev = mpAlphaVDev; + + // operation explicitly further below. + if( mpAlphaVDev ) + mpAlphaVDev = nullptr; + + GDIMetaFile* pOldMetaFile = mpMetaFile; + mpMetaFile = nullptr; + + tools::PolyPolygon aPolyPoly( LogicToPixel( rPolyPoly ) ); + tools::Rectangle aPolyRect( aPolyPoly.GetBoundRect() ); + tools::Rectangle aDstRect( Point(), GetOutputSizePixel() ); + + aDstRect.Intersection( aPolyRect ); + + ClipToPaintRegion( aDstRect ); + + if( !aDstRect.IsEmpty() ) + { + bool bDrawn = false; + + // debug helper: + static const char* pDisableNative = getenv( "SAL_DISABLE_NATIVE_ALPHA" ); + + // #i66849# Added fast path for exactly rectangular + // polygons + // #i83087# Naturally, system alpha blending cannot + // work with separate alpha VDev + if( !mpAlphaVDev && !pDisableNative && aPolyPoly.IsRect() ) + { + // setup Graphics only here (other cases delegate + // to basic OutDev methods) + if ( mbInitClipRegion ) + InitClipRegion(); + + if ( mbInitLineColor ) + InitLineColor(); + + if ( mbInitFillColor ) + InitFillColor(); + + tools::Rectangle aLogicPolyRect( rPolyPoly.GetBoundRect() ); + tools::Rectangle aPixelRect( ImplLogicToDevicePixel( aLogicPolyRect ) ); + + if( !mbOutputClipped ) + { + bDrawn = mpGraphics->DrawAlphaRect( aPixelRect.Left(), aPixelRect.Top(), + // #i98405# use methods with small g, else one pixel too much will be painted. + // This is because the source is a polygon which when painted would not paint + // the rightmost and lowest pixel line(s), so use one pixel less for the + // rectangle, too. + aPixelRect.getWidth(), aPixelRect.getHeight(), + sal::static_int_cast<sal_uInt8>(nTransparencePercent), + this ); + } + else + { + bDrawn = true; + } + } + + if( !bDrawn ) + { + ScopedVclPtrInstance< VirtualDevice > aVDev(*this, DeviceFormat::BITMASK); + const Size aDstSz( aDstRect.GetSize() ); + const sal_uInt8 cTrans = static_cast<sal_uInt8>(MinMax( FRound( nTransparencePercent * 2.55 ), 0, 255 )); + + if( aDstRect.Left() || aDstRect.Top() ) + aPolyPoly.Move( -aDstRect.Left(), -aDstRect.Top() ); + + if( aVDev->SetOutputSizePixel( aDstSz ) ) + { + const bool bOldMap = mbMap; + + EnableMapMode( false ); + + aVDev->SetLineColor( COL_BLACK ); + aVDev->SetFillColor( COL_BLACK ); + aVDev->DrawPolyPolygon( aPolyPoly ); + + Bitmap aPaint( GetBitmap( aDstRect.TopLeft(), aDstSz ) ); + Bitmap aPolyMask( aVDev->GetBitmap( Point(), aDstSz ) ); + + // #107766# check for non-empty bitmaps before accessing them + if( !!aPaint && !!aPolyMask ) + { + BitmapScopedWriteAccess pW(aPaint); + Bitmap::ScopedReadAccess pR(aPolyMask); + + if( pW && pR ) + { + BitmapColor aPixCol; + const BitmapColor aFillCol( GetFillColor() ); + const BitmapColor aBlack( pR->GetBestMatchingColor( COL_BLACK ) ); + const long nWidth = pW->Width(); + const long nHeight = pW->Height(); + const long nR = aFillCol.GetRed(); + const long nG = aFillCol.GetGreen(); + const long nB = aFillCol.GetBlue(); + long nX, nY; + + if( aPaint.GetBitCount() <= 8 ) + { + const BitmapPalette& rPal = pW->GetPalette(); + const sal_uInt16 nCount = rPal.GetEntryCount(); + BitmapColor* pMap = reinterpret_cast<BitmapColor*>(new sal_uInt8[ nCount * sizeof( BitmapColor ) ]); + + for( sal_uInt16 i = 0; i < nCount; i++ ) + { + BitmapColor aCol( rPal[ i ] ); + aCol.Merge( aFillCol, cTrans ); + pMap[ i ] = BitmapColor( static_cast<sal_uInt8>(rPal.GetBestIndex( aCol )) ); + } + + if( pR->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal && + pW->GetScanlineFormat() == ScanlineFormat::N8BitPal ) + { + const sal_uInt8 cBlack = aBlack.GetIndex(); + + for( nY = 0; nY < nHeight; nY++ ) + { + Scanline pWScan = pW->GetScanline( nY ); + Scanline pRScan = pR->GetScanline( nY ); + sal_uInt8 cBit = 128; + + for( nX = 0; nX < nWidth; nX++, cBit >>= 1, pWScan++ ) + { + if( !cBit ) + { + cBit = 128; + pRScan += 1; + } + if( ( *pRScan & cBit ) == cBlack ) + { + *pWScan = pMap[ *pWScan ].GetIndex(); + } + } + } + } + else + { + for( nY = 0; nY < nHeight; nY++ ) + { + Scanline pScanline = pW->GetScanline(nY); + Scanline pScanlineRead = pR->GetScanline(nY); + for( nX = 0; nX < nWidth; nX++ ) + { + if( pR->GetPixelFromData( pScanlineRead, nX ) == aBlack ) + { + pW->SetPixelOnData( pScanline, nX, pMap[ pW->GetIndexFromData( pScanline, nX ) ] ); + } + } + } + } + delete[] reinterpret_cast<sal_uInt8*>(pMap); + } + else + { + if( pR->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal && + pW->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr ) + { + const sal_uInt8 cBlack = aBlack.GetIndex(); + + for( nY = 0; nY < nHeight; nY++ ) + { + Scanline pWScan = pW->GetScanline( nY ); + Scanline pRScan = pR->GetScanline( nY ); + sal_uInt8 cBit = 128; + + for( nX = 0; nX < nWidth; nX++, cBit >>= 1, pWScan += 3 ) + { + if( !cBit ) + { + cBit = 128; + pRScan += 1; + } + if( ( *pRScan & cBit ) == cBlack ) + { + pWScan[ 0 ] = ColorChannelMerge( pWScan[ 0 ], nB, cTrans ); + pWScan[ 1 ] = ColorChannelMerge( pWScan[ 1 ], nG, cTrans ); + pWScan[ 2 ] = ColorChannelMerge( pWScan[ 2 ], nR, cTrans ); + } + } + } + } + else + { + for( nY = 0; nY < nHeight; nY++ ) + { + Scanline pScanline = pW->GetScanline(nY); + Scanline pScanlineRead = pR->GetScanline(nY); + for( nX = 0; nX < nWidth; nX++ ) + { + if( pR->GetPixelFromData( pScanlineRead, nX ) == aBlack ) + { + aPixCol = pW->GetColor( nY, nX ); + aPixCol.Merge(aFillCol, cTrans); + pW->SetPixelOnData(pScanline, nX, aPixCol); + } + } + } + } + } + } + + pR.reset(); + pW.reset(); + + DrawBitmap( aDstRect.TopLeft(), aPaint ); + + EnableMapMode( bOldMap ); + + if( mbLineColor ) + { + Push( PushFlags::FILLCOLOR ); + SetFillColor(); + DrawPolyPolygon( rPolyPoly ); + Pop(); + } + } + } + else + { + DrawPolyPolygon( rPolyPoly ); + } + } + } + + mpMetaFile = pOldMetaFile; + + // #110958# Restore disabled alpha VDev + mpAlphaVDev = pOldAlphaVDev; +} + +void OutputDevice::DrawTransparent( const tools::PolyPolygon& rPolyPoly, + sal_uInt16 nTransparencePercent ) +{ + assert(!is_double_buffered_window()); + + // short circuit for drawing an opaque polygon + if( (nTransparencePercent < 1) || (mnDrawMode & DrawModeFlags::NoTransparency) ) + { + DrawPolyPolygon( rPolyPoly ); + return; + } + + // short circuit for drawing an invisible polygon + if( !mbFillColor || (nTransparencePercent >= 100) ) + { + DrawInvisiblePolygon( rPolyPoly ); + return; // tdf#84294: do not record it in metafile + } + + // handle metafile recording + if( mpMetaFile ) + mpMetaFile->AddAction( new MetaTransparentAction( rPolyPoly, nTransparencePercent ) ); + + bool bDrawn = !IsDeviceOutputNecessary() || ImplIsRecordLayout(); + if( bDrawn ) + return; + + // get the device graphics as drawing target + if( !mpGraphics && !AcquireGraphics() ) + return; + + // try hard to draw it directly, because the emulation layers are slower + bDrawn = DrawTransparentNatively( rPolyPoly, nTransparencePercent ); + + if (!bDrawn) + EmulateDrawTransparent( rPolyPoly, nTransparencePercent ); + + // #110958# Apply alpha value also to VDev alpha channel + if( mpAlphaVDev ) + { + const Color aFillCol( mpAlphaVDev->GetFillColor() ); + mpAlphaVDev->SetFillColor( Color(sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100), + sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100), + sal::static_int_cast<sal_uInt8>(255*nTransparencePercent/100)) ); + + mpAlphaVDev->DrawTransparent( rPolyPoly, nTransparencePercent ); + + mpAlphaVDev->SetFillColor( aFillCol ); + } +} + +void OutputDevice::DrawTransparent( const GDIMetaFile& rMtf, const Point& rPos, + const Size& rSize, const Gradient& rTransparenceGradient ) +{ + assert(!is_double_buffered_window()); + + const Color aBlack( COL_BLACK ); + + if( mpMetaFile ) + { + // missing here is to map the data using the DeviceTransformation + mpMetaFile->AddAction( new MetaFloatTransparentAction( rMtf, rPos, rSize, rTransparenceGradient ) ); + } + + if ( !IsDeviceOutputNecessary() ) + return; + + if( ( rTransparenceGradient.GetStartColor() == aBlack && rTransparenceGradient.GetEndColor() == aBlack ) || + ( mnDrawMode & DrawModeFlags::NoTransparency ) ) + { + const_cast<GDIMetaFile&>(rMtf).WindStart(); + const_cast<GDIMetaFile&>(rMtf).Play( this, rPos, rSize ); + const_cast<GDIMetaFile&>(rMtf).WindStart(); + } + else + { + GDIMetaFile* pOldMetaFile = mpMetaFile; + tools::Rectangle aOutRect( LogicToPixel( rPos ), LogicToPixel( rSize ) ); + Point aPoint; + tools::Rectangle aDstRect( aPoint, GetOutputSizePixel() ); + + mpMetaFile = nullptr; + aDstRect.Intersection( aOutRect ); + + ClipToPaintRegion( aDstRect ); + + if( !aDstRect.IsEmpty() ) + { + ScopedVclPtrInstance< VirtualDevice > xVDev; + + xVDev->mnDPIX = mnDPIX; + xVDev->mnDPIY = mnDPIY; + + if( xVDev->SetOutputSizePixel( aDstRect.GetSize() ) ) + { + if(GetAntialiasing() != AntialiasingFlags::NONE) + { + // #i102109# + // For MetaFile replay (see task) it may now be necessary to take + // into account that the content is AntiAlialiased and needs to be masked + // like that. Instead of masking, i will use a copy-modify-paste cycle + // here (as i already use in the VclPrimiziveRenderer with success) + xVDev->SetAntialiasing(GetAntialiasing()); + + // create MapMode for buffer (offset needed) and set + MapMode aMap(GetMapMode()); + const Point aOutPos(PixelToLogic(aDstRect.TopLeft())); + aMap.SetOrigin(Point(-aOutPos.X(), -aOutPos.Y())); + xVDev->SetMapMode(aMap); + + // copy MapMode state and disable for target + const bool bOrigMapModeEnabled(IsMapModeEnabled()); + EnableMapMode(false); + + // copy MapMode state and disable for buffer + const bool bBufferMapModeEnabled(xVDev->IsMapModeEnabled()); + xVDev->EnableMapMode(false); + + // copy content from original to buffer + xVDev->DrawOutDev( aPoint, xVDev->GetOutputSizePixel(), // dest + aDstRect.TopLeft(), xVDev->GetOutputSizePixel(), // source + *this); + + // draw MetaFile to buffer + xVDev->EnableMapMode(bBufferMapModeEnabled); + const_cast<GDIMetaFile&>(rMtf).WindStart(); + const_cast<GDIMetaFile&>(rMtf).Play(xVDev.get(), rPos, rSize); + const_cast<GDIMetaFile&>(rMtf).WindStart(); + + // get content bitmap from buffer + xVDev->EnableMapMode(false); + + const Bitmap aPaint(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel())); + + // create alpha mask from gradient and get as Bitmap + xVDev->EnableMapMode(bBufferMapModeEnabled); + xVDev->SetDrawMode(DrawModeFlags::GrayGradient); + xVDev->DrawGradient(tools::Rectangle(rPos, rSize), rTransparenceGradient); + xVDev->SetDrawMode(DrawModeFlags::Default); + xVDev->EnableMapMode(false); + + const AlphaMask aAlpha(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel())); + + xVDev.disposeAndClear(); + + // draw masked content to target and restore MapMode + DrawBitmapEx(aDstRect.TopLeft(), BitmapEx(aPaint, aAlpha)); + EnableMapMode(bOrigMapModeEnabled); + } + else + { + Bitmap aPaint, aMask; + AlphaMask aAlpha; + MapMode aMap( GetMapMode() ); + Point aOutPos( PixelToLogic( aDstRect.TopLeft() ) ); + const bool bOldMap = mbMap; + + aMap.SetOrigin( Point( -aOutPos.X(), -aOutPos.Y() ) ); + xVDev->SetMapMode( aMap ); + const bool bVDevOldMap = xVDev->IsMapModeEnabled(); + + // create paint bitmap + const_cast<GDIMetaFile&>(rMtf).WindStart(); + const_cast<GDIMetaFile&>(rMtf).Play( xVDev.get(), rPos, rSize ); + const_cast<GDIMetaFile&>(rMtf).WindStart(); + xVDev->EnableMapMode( false ); + aPaint = xVDev->GetBitmap( Point(), xVDev->GetOutputSizePixel() ); + xVDev->EnableMapMode( bVDevOldMap ); // #i35331#: MUST NOT use EnableMapMode( sal_True ) here! + + // create mask bitmap + xVDev->SetLineColor( COL_BLACK ); + xVDev->SetFillColor( COL_BLACK ); + xVDev->DrawRect( tools::Rectangle( xVDev->PixelToLogic( Point() ), xVDev->GetOutputSize() ) ); + xVDev->SetDrawMode( DrawModeFlags::WhiteLine | DrawModeFlags::WhiteFill | DrawModeFlags::WhiteText | + DrawModeFlags::WhiteBitmap | DrawModeFlags::WhiteGradient ); + const_cast<GDIMetaFile&>(rMtf).WindStart(); + const_cast<GDIMetaFile&>(rMtf).Play( xVDev.get(), rPos, rSize ); + const_cast<GDIMetaFile&>(rMtf).WindStart(); + xVDev->EnableMapMode( false ); + aMask = xVDev->GetBitmap( Point(), xVDev->GetOutputSizePixel() ); + xVDev->EnableMapMode( bVDevOldMap ); // #i35331#: MUST NOT use EnableMapMode( sal_True ) here! + + // create alpha mask from gradient + xVDev->SetDrawMode( DrawModeFlags::GrayGradient ); + xVDev->DrawGradient( tools::Rectangle( rPos, rSize ), rTransparenceGradient ); + xVDev->SetDrawMode( DrawModeFlags::Default ); + xVDev->EnableMapMode( false ); + xVDev->DrawMask( Point(), xVDev->GetOutputSizePixel(), aMask, COL_WHITE ); + + aAlpha = xVDev->GetBitmap( Point(), xVDev->GetOutputSizePixel() ); + + xVDev.disposeAndClear(); + + EnableMapMode( false ); + DrawBitmapEx( aDstRect.TopLeft(), BitmapEx( aPaint, aAlpha ) ); + EnableMapMode( bOldMap ); + } + } + } + + mpMetaFile = pOldMetaFile; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/outdev/vclreferencebase.cxx b/vcl/source/outdev/vclreferencebase.cxx new file mode 100644 index 000000000..62dd0e67f --- /dev/null +++ b/vcl/source/outdev/vclreferencebase.cxx @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/vclreferencebase.hxx> + +VclReferenceBase::VclReferenceBase() : + mnRefCnt(1), // cf. VclPtrInstance and README.lifecycle + mbDisposed(false) +{ +} + +VclReferenceBase::~VclReferenceBase() +{ + disposeOnce(); +} + +void VclReferenceBase::disposeOnce() +{ + if ( mbDisposed ) + return; + mbDisposed = true; + dispose(); +} + +void VclReferenceBase::dispose() +{ +} + diff --git a/vcl/source/outdev/wallpaper.cxx b/vcl/source/outdev/wallpaper.cxx new file mode 100644 index 000000000..594fdbe05 --- /dev/null +++ b/vcl/source/outdev/wallpaper.cxx @@ -0,0 +1,396 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <cassert> + +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <vcl/outdev.hxx> +#include <vcl/virdev.hxx> + +Color OutputDevice::GetReadableFontColor(const Color& rFontColor, const Color& rBgColor) const +{ + if (rBgColor.IsDark() && rFontColor.IsDark()) + return COL_WHITE; + else if (rBgColor.IsBright() && rFontColor.IsBright()) + return COL_BLACK; + else + return rFontColor; +} + +Color OutputDevice::GetBackgroundColor() const +{ + return GetBackground().GetColor(); +} + +void OutputDevice::DrawWallpaper( const tools::Rectangle& rRect, + const Wallpaper& rWallpaper ) +{ + assert(!is_double_buffered_window()); + + if ( mpMetaFile ) + mpMetaFile->AddAction( new MetaWallpaperAction( rRect, rWallpaper ) ); + + if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + if ( rWallpaper.GetStyle() != WallpaperStyle::NONE ) + { + tools::Rectangle aRect = LogicToPixel( rRect ); + aRect.Justify(); + + if ( !aRect.IsEmpty() ) + { + DrawWallpaper( aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight(), + rWallpaper ); + } + } + + if( mpAlphaVDev ) + mpAlphaVDev->DrawWallpaper( rRect, rWallpaper ); +} + +void OutputDevice::DrawWallpaper( long nX, long nY, + long nWidth, long nHeight, + const Wallpaper& rWallpaper ) +{ + assert(!is_double_buffered_window()); + + if( rWallpaper.IsBitmap() ) + DrawBitmapWallpaper( nX, nY, nWidth, nHeight, rWallpaper ); + else if( rWallpaper.IsGradient() ) + DrawGradientWallpaper( nX, nY, nWidth, nHeight, rWallpaper ); + else + DrawColorWallpaper( nX, nY, nWidth, nHeight, rWallpaper ); +} + +void OutputDevice::DrawColorWallpaper( long nX, long nY, + long nWidth, long nHeight, + const Wallpaper& rWallpaper ) +{ + assert(!is_double_buffered_window()); + + // draw wallpaper without border + Color aOldLineColor = GetLineColor(); + Color aOldFillColor = GetFillColor(); + SetLineColor(); + SetFillColor( rWallpaper.GetColor() ); + + bool bMap = mbMap; + EnableMapMode( false ); + DrawRect( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ) ); + SetLineColor( aOldLineColor ); + SetFillColor( aOldFillColor ); + EnableMapMode( bMap ); +} + +void OutputDevice::Erase() +{ + if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() ) + return; + + if ( mbBackground ) + { + RasterOp eRasterOp = GetRasterOp(); + if ( eRasterOp != RasterOp::OverPaint ) + SetRasterOp( RasterOp::OverPaint ); + DrawWallpaper( 0, 0, mnOutWidth, mnOutHeight, maBackground ); + if ( eRasterOp != RasterOp::OverPaint ) + SetRasterOp( eRasterOp ); + } + + if( mpAlphaVDev ) + mpAlphaVDev->Erase(); +} + +void OutputDevice::Erase(const tools::Rectangle& rRect) +{ + const RasterOp eRasterOp = GetRasterOp(); + if ( eRasterOp != RasterOp::OverPaint ) + SetRasterOp( RasterOp::OverPaint ); + DrawWallpaper(rRect, GetBackground()); + if ( eRasterOp != RasterOp::OverPaint ) + SetRasterOp( eRasterOp ); + + if (mpAlphaVDev) + mpAlphaVDev->Erase(rRect); +} + +void OutputDevice::DrawBitmapWallpaper( long nX, long nY, + long nWidth, long nHeight, + const Wallpaper& rWallpaper ) +{ + assert(!is_double_buffered_window()); + + BitmapEx aBmpEx; + const BitmapEx* pCached = rWallpaper.ImplGetCachedBitmap(); + Point aPos; + Size aSize; + GDIMetaFile* pOldMetaFile = mpMetaFile; + const WallpaperStyle eStyle = rWallpaper.GetStyle(); + const bool bOldMap = mbMap; + bool bDrawn = false; + bool bDrawGradientBackground = false; + bool bDrawColorBackground = false; + + if( pCached ) + aBmpEx = *pCached; + else + aBmpEx = rWallpaper.GetBitmap(); + + const long nBmpWidth = aBmpEx.GetSizePixel().Width(); + const long nBmpHeight = aBmpEx.GetSizePixel().Height(); + const bool bTransparent = aBmpEx.IsTransparent(); + + // draw background + if( bTransparent ) + { + if( rWallpaper.IsGradient() ) + bDrawGradientBackground = true; + else + { + if( !pCached && !rWallpaper.GetColor().GetTransparency() ) + { + ScopedVclPtrInstance< VirtualDevice > aVDev( *this ); + aVDev->SetBackground( rWallpaper.GetColor() ); + aVDev->SetOutputSizePixel( Size( nBmpWidth, nBmpHeight ) ); + aVDev->DrawBitmapEx( Point(), aBmpEx ); + aBmpEx = aVDev->GetBitmapEx( Point(), aVDev->GetOutputSizePixel() ); + } + + bDrawColorBackground = true; + } + } + else if( eStyle != WallpaperStyle::Tile && eStyle != WallpaperStyle::Scale ) + { + if( rWallpaper.IsGradient() ) + bDrawGradientBackground = true; + else + bDrawColorBackground = true; + } + + // background of bitmap? + if( bDrawGradientBackground ) + DrawGradientWallpaper( nX, nY, nWidth, nHeight, rWallpaper ); + else if( bDrawColorBackground && bTransparent ) + { + DrawColorWallpaper( nX, nY, nWidth, nHeight, rWallpaper ); + bDrawColorBackground = false; + } + + // calc pos and size + if( rWallpaper.IsRect() ) + { + const tools::Rectangle aBound( LogicToPixel( rWallpaper.GetRect() ) ); + aPos = aBound.TopLeft(); + aSize = aBound.GetSize(); + } + else + { + aPos = Point( 0, 0 ); + aSize = Size( nWidth, nHeight ); + } + + mpMetaFile = nullptr; + EnableMapMode( false ); + Push( PushFlags::CLIPREGION ); + IntersectClipRegion( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ) ); + + switch( eStyle ) + { + case WallpaperStyle::Scale: + if( !pCached || ( pCached->GetSizePixel() != aSize ) ) + { + if( pCached ) + rWallpaper.ImplReleaseCachedBitmap(); + + aBmpEx = rWallpaper.GetBitmap(); + aBmpEx.Scale( aSize ); + aBmpEx = BitmapEx( aBmpEx.GetBitmap().CreateDisplayBitmap( this ), aBmpEx.GetMask() ); + } + break; + + case WallpaperStyle::TopLeft: + break; + + case WallpaperStyle::Top: + aPos.AdjustX(( aSize.Width() - nBmpWidth ) >> 1 ); + break; + + case WallpaperStyle::TopRight: + aPos.AdjustX( aSize.Width() - nBmpWidth); + break; + + case WallpaperStyle::Left: + aPos.AdjustY(( aSize.Height() - nBmpHeight ) >> 1 ); + break; + + case WallpaperStyle::Center: + aPos.AdjustX(( aSize.Width() - nBmpWidth ) >> 1 ); + aPos.AdjustY(( aSize.Height() - nBmpHeight ) >> 1 ); + break; + + case WallpaperStyle::Right: + aPos.AdjustX(aSize.Width() - nBmpWidth); + aPos.AdjustY(( aSize.Height() - nBmpHeight ) >> 1 ); + break; + + case WallpaperStyle::BottomLeft: + aPos.AdjustY( aSize.Height() - nBmpHeight ); + break; + + case WallpaperStyle::Bottom: + aPos.AdjustX(( aSize.Width() - nBmpWidth ) >> 1 ); + aPos.AdjustY( aSize.Height() - nBmpHeight ); + break; + + case WallpaperStyle::BottomRight: + aPos.AdjustX( aSize.Width() - nBmpWidth ); + aPos.AdjustY( aSize.Height() - nBmpHeight ); + break; + + default: + { + const long nRight = nX + nWidth - 1; + const long nBottom = nY + nHeight - 1; + long nFirstX; + long nFirstY; + + if( eStyle == WallpaperStyle::Tile ) + { + nFirstX = aPos.X(); + nFirstY = aPos.Y(); + } + else + { + nFirstX = aPos.X() + ( ( aSize.Width() - nBmpWidth ) >> 1 ); + nFirstY = aPos.Y() + ( ( aSize.Height() - nBmpHeight ) >> 1 ); + } + + const long nOffX = ( nFirstX - nX ) % nBmpWidth; + const long nOffY = ( nFirstY - nY ) % nBmpHeight; + long nStartX = nX + nOffX; + long nStartY = nY + nOffY; + + if( nOffX > 0 ) + nStartX -= nBmpWidth; + + if( nOffY > 0 ) + nStartY -= nBmpHeight; + + for( long nBmpY = nStartY; nBmpY <= nBottom; nBmpY += nBmpHeight ) + { + for( long nBmpX = nStartX; nBmpX <= nRight; nBmpX += nBmpWidth ) + { + DrawBitmapEx( Point( nBmpX, nBmpY ), aBmpEx ); + } + } + bDrawn = true; + } + break; + } + + if( !bDrawn ) + { + // optimized for non-transparent bitmaps + if( bDrawColorBackground ) + { + const Size aBmpSize( aBmpEx.GetSizePixel() ); + const Point aTmpPoint; + const tools::Rectangle aOutRect( aTmpPoint, GetOutputSizePixel() ); + const tools::Rectangle aColRect( Point( nX, nY ), Size( nWidth, nHeight ) ); + + tools::Rectangle aWorkRect( 0, 0, aOutRect.Right(), aPos.Y() - 1 ); + aWorkRect.Justify(); + aWorkRect.Intersection( aColRect ); + if( !aWorkRect.IsEmpty() ) + { + DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(), + aWorkRect.GetWidth(), aWorkRect.GetHeight(), + rWallpaper ); + } + + aWorkRect = tools::Rectangle( 0, aPos.Y(), aPos.X() - 1, aPos.Y() + aBmpSize.Height() - 1 ); + aWorkRect.Justify(); + aWorkRect.Intersection( aColRect ); + if( !aWorkRect.IsEmpty() ) + { + DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(), + aWorkRect.GetWidth(), aWorkRect.GetHeight(), + rWallpaper ); + } + + aWorkRect = tools::Rectangle( aPos.X() + aBmpSize.Width(), aPos.Y(), + aOutRect.Right(), aPos.Y() + aBmpSize.Height() - 1 ); + aWorkRect.Justify(); + aWorkRect.Intersection( aColRect ); + if( !aWorkRect.IsEmpty() ) + { + DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(), + aWorkRect.GetWidth(), aWorkRect.GetHeight(), + rWallpaper ); + } + + aWorkRect = tools::Rectangle( 0, aPos.Y() + aBmpSize.Height(), + aOutRect.Right(), aOutRect.Bottom() ); + aWorkRect.Justify(); + aWorkRect.Intersection( aColRect ); + if( !aWorkRect.IsEmpty() ) + { + DrawColorWallpaper( aWorkRect.Left(), aWorkRect.Top(), + aWorkRect.GetWidth(), aWorkRect.GetHeight(), + rWallpaper ); + } + } + + DrawBitmapEx( aPos, aBmpEx ); + } + + rWallpaper.ImplSetCachedBitmap( aBmpEx ); + + Pop(); + EnableMapMode( bOldMap ); + mpMetaFile = pOldMetaFile; +} + +void OutputDevice::DrawGradientWallpaper( long nX, long nY, + long nWidth, long nHeight, + const Wallpaper& rWallpaper ) +{ + assert(!is_double_buffered_window()); + + tools::Rectangle aBound; + GDIMetaFile* pOldMetaFile = mpMetaFile; + const bool bOldMap = mbMap; + + aBound = tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ); + + mpMetaFile = nullptr; + EnableMapMode( false ); + Push( PushFlags::CLIPREGION ); + IntersectClipRegion( tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ) ); + + DrawGradient( aBound, rWallpaper.GetGradient() ); + + Pop(); + EnableMapMode( bOldMap ); + mpMetaFile = pOldMetaFile; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |