/* -*- 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 .
*/
// control point two
*pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
pCurrPoint++;
*pWinFlagAry++ = PT_BEZIERTO;
// end point
*pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
pCurrPoint++;
*pWinFlagAry++ = PT_BEZIERTO;
nCurrPoint += 3;
pCurrFlag += 3; continue;
}
}
// regular line point
*pWinPointAry++ = POINT { static_cast<LONG>(pCurrPoint->getX()), static_cast<LONG>(pCurrPoint->getY()) };
pCurrPoint++;
*pWinFlagAry++ = PT_LINETO;
++pCurrFlag;
++nCurrPoint;
}
// end figure
pWinFlagAry[-1] |= PT_CLOSEFIGURE;
}
}
}
void MakeInvisibleArea(const RECT& rSrcRect, int nLeft, int nTop, int nRight, int nBottom,
HRGN& rhInvalidateRgn)
{ if (!rhInvalidateRgn)
{
rhInvalidateRgn = CreateRectRgnIndirect(&rSrcRect);
}
// do we have to invalidate also the overlapping regions? if ( bWindowInvalidate && mrParent.isWindow() )
{ // compute and invalidate those parts that were either off-screen or covered by other windows // while performing the above BitBlt // those regions then have to be invalidated as they contain useless/wrong data
RECT aSrcRect;
RECT aClipRect;
RECT aTempRect;
RECT aTempRect2;
HRGN hTempRgn;
HWND hWnd;
// compute the parts that are off screen (ie invisible)
RECT theScreen;
ImplSalGetWorkArea( nullptr, &theScreen, nullptr ); // find the screen area taking multiple monitors into account
ImplCalcOutSideRgn( aSrcRect, theScreen.left, theScreen.top, theScreen.right, theScreen.bottom, hInvalidateRgn );
// calculate regions that are covered by other windows
HRGN hTempRgn2 = nullptr;
HWND hWndTopWindow = mrParent.gethWnd(); // Find the TopLevel Window, because only Windows which are in // in the foreground of our TopLevel window must be considered if ( GetWindowStyle( hWndTopWindow ) & WS_CHILD )
{
RECT aTempRect3 = aSrcRect; do
{
hWndTopWindow = ::GetParent( hWndTopWindow );
// Test if the Parent clips our window
GetClientRect( hWndTopWindow, &aTempRect );
POINT aPt2;
aPt2.x = 0;
aPt2.y = 0;
ClientToScreen( hWndTopWindow, &aPt2 );
aTempRect.left += aPt2.x;
aTempRect.top += aPt2.y;
aTempRect.right += aPt2.x;
aTempRect.bottom += aPt2.y;
IntersectRect( &aTempRect3, &aTempRect3, &aTempRect );
} while ( GetWindowStyle( hWndTopWindow ) & WS_CHILD );
// If one or more Parents clip our window, then we must // calculate the outside area if ( !EqualRect( &aSrcRect, &aTempRect3 ) )
{
ImplCalcOutSideRgn( aSrcRect,
aTempRect3.left, aTempRect3.top,
aTempRect3.right, aTempRect3.bottom,
hInvalidateRgn );
}
} // retrieve the top-most (z-order) child window
hWnd = GetWindow( GetDesktopWindow(), GW_CHILD ); while ( hWnd )
{ if ( hWnd == hWndTopWindow ) break; if ( IsWindowVisible( hWnd ) && !IsIconic( hWnd ) )
{
GetWindowRect( hWnd, &aTempRect ); if ( IntersectRect( &aTempRect2, &aSrcRect, &aTempRect ) )
{ // hWnd covers part or all of aSrcRect if ( !hInvalidateRgn )
hInvalidateRgn = CreateRectRgnIndirect( &aSrcRect );
// get full bounding box of hWnd
hTempRgn = CreateRectRgnIndirect( &aTempRect );
// get region of hWnd (the window may be shaped) if ( !hTempRgn2 )
hTempRgn2 = CreateRectRgn( 0, 0, 0, 0 ); int nRgnType = GetWindowRgn( hWnd, hTempRgn2 ); if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) )
{ // convert window region to screen coordinates
OffsetRgn( hTempRgn2, aTempRect.left, aTempRect.top ); // and intersect with the window's bounding box
CombineRgn( hTempRgn, hTempRgn, hTempRgn2, RGN_AND );
} // finally compute that part of aSrcRect which is not covered by any parts of hWnd
CombineRgn( hInvalidateRgn, hInvalidateRgn, hTempRgn, RGN_DIFF );
DeleteRegion( hTempRgn );
}
} // retrieve the next window in the z-order, i.e. the window below hwnd
hWnd = GetWindow( hWnd, GW_HWNDNEXT );
} if ( hTempRgn2 )
DeleteRegion( hTempRgn2 ); if ( hInvalidateRgn )
{ // hInvalidateRgn contains the fully visible parts of the original srcRect
hTempRgn = CreateRectRgnIndirect( &aSrcRect ); // subtract it from the original rect to get the occluded parts int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_DIFF );
DeleteRegion( hTempRgn );
if ( (nRgnType != ERROR) && (nRgnType != NULLREGION) )
{ // move the occluded parts to the destination pos int nOffX = static_cast<int>(nDestX-nSrcX); int nOffY = static_cast<int>(nDestY-nSrcY);
OffsetRgn( hInvalidateRgn, nOffX-aPt.x, nOffY-aPt.y );
// by excluding hInvalidateRgn from the system's clip region // we will prevent bitblt from copying useless data // especially now shadows from overlapping windows will appear (#i36344)
hOldClipRgn = CreateRectRgn( 0, 0, 0, 0 );
nOldClipRgnType = GetClipRgn( mrParent.getHDC(), hOldClipRgn );
bRestoreClipRgn = true; // indicate changed clipregion and force invalidate
ExtSelectClipRgn( mrParent.getHDC(), hInvalidateRgn, RGN_DIFF );
}
}
}
}
if ( bInvalidate )
{
InvalidateRgn( mrParent.gethWnd(), hInvalidateRgn, TRUE ); // here we only initiate an update if this is the MainThread, // so that there is no deadlock when handling the Paint event, // as the SolarMutex is already held by this Thread
SalData* pSalData = GetSalData();
DWORD nCurThreadId = GetCurrentThreadId(); if ( pSalData->mnAppThreadId == nCurThreadId )
UpdateWindow( mrParent.gethWnd() );
}
if(bTryDirectPaint)
{ // only paint direct when no scaling and no MapMode, else the // more expensive conversions may be done for short-time Bitmap/BitmapEx // used for buffering only if(rPosAry.mnSrcWidth == rPosAry.mnDestWidth && rPosAry.mnSrcHeight == rPosAry.mnDestHeight)
{
bTryDirectPaint = false;
}
}
// try to draw using GdiPlus directly if(bTryDirectPaint && TryDrawBitmapGDIPlus(rPosAry, rSalBitmap))
{ return;
}
// fall back old stuff
assert(dynamic_cast<const WinSalBitmap*>(&rSalBitmap));
// WIN/WNT seems to have a minor problem mapping the correct color of the // mask to the palette if we draw the DIB directly ==> draw DDB if( ( GetBitCount() <= 8 ) && rTransparentBitmap.ImplGethDIB() && rTransparentBitmap.GetBitCount() == 1 )
{
WinSalBitmap aTmp;
// hMemDC contains a 1x1 bitmap of the right color - stretch-blit // that to dest hdc bool bRet = GdiAlphaBlend(mrParent.getHDC(), nX, nY, nWidth, nHeight,
hMemDC.get(), 0,0,1,1,
aFunc ) == TRUE;
return bRet;
}
void WinSalGraphicsImpl::drawMask(const SalTwoRect& rPosAry, const SalBitmap& rSSalBitmap,
Color nMaskColor)
{
SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No transparency print possible!" );
// WIN/WNT seems to have a minor problem mapping the correct color of the // mask to the palette if we draw the DIB directly ==> draw DDB if( ( GetBitCount() <= 8 ) && rSalBitmap.ImplGethDIB() && rSalBitmap.GetBitCount() == 1 )
{
WinSalBitmap aTmp;
// #i122149# try to avoid usage of tools::PolyPolygon ClipRegions when tools::PolyPolygon is no curve // and only contains horizontal/vertical edges. In that case, use the fallback // in GetRegionRectangles which will use vcl::Region::GetAsRegionBand() which will do // the correct polygon-to-RegionBand transformation. // Background is that when using the same Rectangle as rectangle or as Polygon // clip region will lead to different results; the polygon-based one will be // one pixel less to the right and down (see GDI docu for CreatePolygonRgn). This // again is because of the polygon-nature and it's classic handling when filling. // This also means that all cases which use a 'true' polygon-based incarnation of // a vcl::Region should know what they do - it may lead to repaint errors. if(bUsePolygon && bTryToAvoidPolygon)
{ const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() );
if(bUsePolygon)
{ // #i122149# check the comment above to know that this may lead to potential repaint // problems. It may be solved (if needed) by scaling the polygon by one in X // and Y. Currently the workaround to only use it if really unavoidable will // solve most cases. When someone is really using polygon-based Regions he // should know what he is doing. // Added code to do that scaling to check if it works, testing it. const basegfx::B2DPolyPolygon aPolyPolygon( i_rClip.GetAsB2DPolyPolygon() ); const sal_uInt32 nCount(aPolyPolygon.count());
// tdf#40863 For CustomShapes there is a hack (see // f64ef72743e55389e446e0d4bc6febd475011023) that adds polygons // with a single point in top-left and bottom-right corner // of the BoundRect to be able to determine the correct BoundRect // in the slideshow. Unfortunately, CreatePolyPolygonRgn below // fails with polygons containing a single pixel, so clipping is // lost. For now, use only polygons with more than two points - the // ones that may have an area. // Note: polygons with one point which are curves may have an area, // but the polygon is already subdivided here, so no need to test // this. if(nPoints > 2)
{
aPolyCounts[nTargetCount] = nPoints;
nTargetCount++;
for( sal_uInt32 b = 0; b < nPoints; b++ )
{
basegfx::B2DPoint aPt(aPoly.getB2DPoint(b));
if(bExpandByOneInXandY)
{
aPt = aExpand * aPt;
}
POINT aPOINT; // #i122149# do correct rounding
aPOINT.x = basegfx::fround(aPt.getX());
aPOINT.y = basegfx::fround(aPt.getY());
aPolyPoints.push_back( aPOINT );
}
}
}
// create clip region from ClipRgnData if(0 == mrParent.mpClipRgnData->rdh.nCount)
{ // #i123585# region is empty; this may happen when e.g. a tools::PolyPolygon is given // that contains no polygons or only empty ones (no width/height). This is // perfectly fine and we are done, except setting it (see end of method)
} elseif(1 == mrParent.mpClipRgnData->rdh.nCount)
{
RECT* pRect = &(mrParent.mpClipRgnData->rdh.rcBound);
mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top,
pRect->right, pRect->bottom );
} elseif(mrParent.mpClipRgnData->rdh.nCount > 1)
{
sal_uLong nSize = mrParent.mpClipRgnData->rdh.nRgnSize+sizeof(RGNDATAHEADER);
mrParent.mhRegion = ExtCreateRegion( nullptr, nSize, mrParent.mpClipRgnData );
// if ExtCreateRegion(...) is not supported if( !mrParent.mhRegion )
{
RGNDATAHEADER const & pHeader = mrParent.mpClipRgnData->rdh;
// debug code if you want to check range of the newly applied ClipRegion //RECT aBound; //const int aRegionType = GetRgnBox(mrParent.mhRegion, &aBound);
} else
{ // #i123585# See above, this is a valid case, execute it
SelectClipRgn( mrParent.getHDC(), nullptr );
}
}
// set new data
mnPenColor = nPenColor;
maLineColor = nColor;
mbPen = true;
mbStockPen = bStockPen;
}
HPEN WinSalGraphicsImpl::SearchStockPen(COLORREF nPenColor)
{ // Only screen, because printer has problems, when we use stock objects. if (mrParent.isPrinter()) return nullptr;
const SalData* pSalData = GetSalData();
for (sal_uInt16 i = 0; i < pSalData->mnStockPenCount; i++)
{ if (nPenColor == pSalData->maStockPenColorAry[i]) return pSalData->mhStockPenAry[i];
}
// set new data
mnBrushColor = nBrushColor;
maFillColor = nColor;
mbBrush = true;
mbStockBrush = bStockBrush;
}
HBRUSH WinSalGraphicsImpl::SearchStockBrush(COLORREF nBrushColor)
{ // Only screen, because printer has problems, when we use stock objects. if (!mrParent.isPrinter())
{ const SalData* pSalData = GetSalData();
for (sal_uInt16 i = 0; i < pSalData->mnStockBrushCount; i++)
{ if (nBrushColor == pSalData->maStockBrushColorAry[i]) return pSalData->mhStockBrushAry[i];
}
}
// Polyline seems to uses LineTo, which doesn't paint the last pixel (see 87eb8f8ee) if ( !mrParent.isPrinter() )
DrawPixelImpl( pWinPtAry[nPoints-1].x, pWinPtAry[nPoints-1].y, mnPenColor );
}
if ( pWinPointAry != aWinPointAry ) delete [] pWinPointAry; if ( pWinPointAryAry != aWinPointAryAry ) delete [] pWinPointAryAry;
}
bool WinSalGraphicsImpl::drawPolyLineBezier( sal_uInt32 nPoints, const Point* pPtAry, const PolyFlags* pFlgAry )
{ // #100127# draw an array of points which might also contain bezier control points if (!nPoints) returntrue;
const HDC hdc = mrParent.getHDC();
// TODO: profile whether the following options are faster: // a) look ahead and draw consecutive bezier or line segments by PolyBezierTo/PolyLineTo resp. // b) convert our flag array to window's and use PolyDraw
MoveToEx(hdc, static_cast<LONG>(pPtAry->getX()), static_cast<LONG>(pPtAry->getY()), nullptr);
++pPtAry;
++pFlgAry;
for(sal_uInt32 i = 1; i < nPoints; ++i)
{ if(*pFlgAry != PolyFlags::Control)
{
LineTo(hdc, pPtAry->getX(), pPtAry->getY());
} elseif(nPoints - i > 2)
{
POINT bezierPoints[] = {
POINT { static_cast<LONG>(pPtAry[0].getX()), static_cast<LONG>(pPtAry[0].getY()) },
POINT { static_cast<LONG>(pPtAry[1].getX()), static_cast<LONG>(pPtAry[1].getY()) },
POINT { static_cast<LONG>(pPtAry[2].getX()), static_cast<LONG>(pPtAry[2].getY()) },
};
PolyBezierTo(hdc, bezierPoints, 3);
i += 2;
pPtAry += 2;
pFlgAry += 2;
}
// tdf#99165 MS Gdiplus cannot handle creating correct extra geometry for fat lines // with LineCap or LineJoin when a bezier segment starts or ends trivial, e.g. has // no 1st or 2nd control point, despite that these are mathematically correct definitions // (basegfx can handle that). // Caution: This error (and it's correction) might be necessary for other graphical // sub-systems in a similar way. // tdf#101026 The 1st attempt to create a mathematically correct replacement control // vector was wrong. Best alternative is one as close as possible which means short. if(!b1stControlPointUsed)
{
aCa = aCurr + ((aCb - aCurr) * 0.0005);
} elseif(!b2ndControlPointUsed)
{
aCb = aNext + ((aCa - aNext) * 0.0005);
}
class SystemDependentData_GraphicsPath : public basegfx::SystemDependentData
{ private: // the path data itself
std::shared_ptr<Gdiplus::GraphicsPath> mpGraphicsPath;
// all other values the triangulation is based on and // need to be compared with to check for data validity bool mbNoLineJoin;
std::vector< double > maStroke;
// Set full (Object-to-Device) transformation - if used if(rObjectToDevice.isIdentity())
{
aGraphics.ResetTransform();
} else
{
Gdiplus::Matrix aMatrix;
// prepare local instance of Gdiplus::GraphicsPath
std::shared_ptr<Gdiplus::GraphicsPath> pGraphicsPath;
// try to access buffered data
std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath(
rPolyPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>(basegfx::SDD_Type::SDDType_GraphicsPath));
if(pSystemDependentData_GraphicsPath)
{ // copy buffered data
pGraphicsPath = pSystemDependentData_GraphicsPath->getGraphicsPath();
} else
{ // Note: In principle we could use the same buffered geometry at line // and fill polygons. Checked that in a first try, used // GraphicsPath::AddPath from Gdiplus combined with below used // StartFigure/CloseFigure, worked well (thus the line-draw version // may create non-closed partial Polygon data). // // But in current reality it gets not used due to e.g. // SdrPathPrimitive2D::create2DDecomposition creating transformed // line and fill polygon-primitives (what could be changed). // // There will probably be more hindrances here in other rendering paths // which could all be found - intention to do this would be: Use more // transformations, less modifications of B2DPolygons/B2DPolyPolygons. // // A fix for SdrPathPrimitive2D would be to create the sub-geometry // and embed into a TransformPrimitive2D containing the transformation. // // A 2nd problem is that the NoLineJoin mode (basegfx::B2DLineJoin::NONE // && !bIsHairline) creates polygon fill infos that are not reusable // for the fill case (see ::drawPolyLine below) - thus we would need a // bool and/or two system-dependent paths buffered - doable, but complicated. // // All in all: Make B2DPolyPolygon a SystemDependentDataProvider and buffer // the whole to-be-filled PolyPolygon independent from evtl. line-polygon // (at least for now...)
// create data
pGraphicsPath = std::make_shared<Gdiplus::GraphicsPath>();
for(sal_uInt32 a(0); a < nCount; a++)
{ if(0 != a)
{ // #i101491# not needed for first run
pGraphicsPath->StartFigure();
}
impAddB2DPolygonToGDIPlusGraphicsPathReal(
*pGraphicsPath,
rPolyPolygon.getB2DPolygon(a),
rObjectToDevice, // not used due to the two 'false' values below, but to not forget later false, false);
pGraphicsPath->CloseFigure();
}
// add to buffering mechanism
rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>(
pGraphicsPath, false,
nullptr);
}
if(mrParent.isPrinter())
{ // #i121591# // Normally GdiPlus should not be used for printing at all since printers cannot // print transparent filled polygon geometry and normally this does not happen // since OutputDevice::RemoveTransparenciesFromMetaFile is used as preparation // and no transparent parts should remain for printing. But this can be overridden // by the user and thus happens. This call can only come (currently) from // OutputDevice::DrawTransparent, see comments there with the same TaskID. // If it is used, the mapping for the printer is wrong and needs to be corrected. I // checked that there is *no* transformation set and estimated that a stable factor // dependent of the printer's DPI is used. Create and set a transformation here to // correct this. const Gdiplus::REAL aDpiX(aGraphics.GetDpiX()); const Gdiplus::REAL aDpiY(aGraphics.GetDpiY());
// Now the transformation maybe/is already used (see above), so do // modify it without resetting to not destroy it. // I double-checked with MS docu that Gdiplus::MatrixOrderAppend does what // we need - in our notation, would be a multiply from left to execute // current transform first and this scale last. // I tried to trigger this code using Print from the menu and various // targets, but got no hit, thus maybe obsolete anyways. If someone knows // more, feel free to remove it. // One more hint: This *may* also be needed now in ::drawPolyLine below // since it also uses transformations now. // // aGraphics.ResetTransform();
// need to check/handle LineWidth when ObjectToDevice transformation is used constbool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity()); constbool bIsHairline(fLineWidth == 0);
// tdf#124848 calculate-back logical LineWidth for a hairline // since this implementation hands over the transformation to // the graphic sub-system if(bIsHairline)
{
fLineWidth = 1.0;
switch(eLineJoin)
{ case basegfx::B2DLineJoin::NONE :
{ if(!bIsHairline)
{
bNoLineJoin = true;
} break;
} case basegfx::B2DLineJoin::Bevel :
{
aPen.SetLineJoin(Gdiplus::LineJoinBevel); break;
} case basegfx::B2DLineJoin::Miter :
{ const Gdiplus::REAL aMiterLimit(1.0/sin(fMiterMinimumAngle/2.0));
aPen.SetMiterLimit(aMiterLimit); // tdf#99165 MS's LineJoinMiter creates non standard conform miter additional // graphics, somewhere clipped in some distance from the edge point, dependent // of MiterLimit. The more default-like option is LineJoinMiterClipped, so use // that instead
aPen.SetLineJoin(Gdiplus::LineJoinMiterClipped); break;
} case basegfx::B2DLineJoin::Round :
{
aPen.SetLineJoin(Gdiplus::LineJoinRound); break;
}
}
switch(eLineCap)
{ default: /*css::drawing::LineCap_BUTT*/
{ // nothing to do break;
} case css::drawing::LineCap_ROUND:
{
aPen.SetStartCap(Gdiplus::LineCapRound);
aPen.SetEndCap(Gdiplus::LineCapRound); break;
} case css::drawing::LineCap_SQUARE:
{
aPen.SetStartCap(Gdiplus::LineCapSquare);
aPen.SetEndCap(Gdiplus::LineCapSquare); break;
}
}
// prepare local instance of Gdiplus::GraphicsPath
std::shared_ptr<Gdiplus::GraphicsPath> pGraphicsPath;
// try to access buffered data
std::shared_ptr<SystemDependentData_GraphicsPath> pSystemDependentData_GraphicsPath(
rPolygon.getSystemDependentData<SystemDependentData_GraphicsPath>(basegfx::SDD_Type::SDDType_GraphicsPath));
// MM01 need to do line dashing as fallback stuff here now constdouble fDotDashLength(nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0); constbool bStrokeUsed(0.0 != fDotDashLength);
assert(!bStrokeUsed || (bStrokeUsed && pStroke));
// MM01 decide if to stroke directly staticbool bDoDirectGDIPlusStroke(true);
// activate to stroke directly if(bDoDirectGDIPlusStroke && bStrokeUsed)
{ // tdf#124848 the fix of tdf#130478 that was needed here before // gets much easier when already handling the hairline case above, // the back-calculated logical linewidth is already here, just use it. // Still be careful - a zero LineWidth *should* not happen, but...
std::vector<Gdiplus::REAL> aDashArray(pStroke->size()); constdouble fFactor(fLineWidth == 0 ? 1.0 : 1.0 / fLineWidth);
// tdf#134128. ODF adds caps to the dashes and dots, but GDI makes caps from the // dash or dot themselves. We tweak aDashArray to look the same in GDI (e.g. Impress edit mode) // and other renders (e.g. Impress slide show), while keeping the total length of the // pattern. // Patterns are always a sequence dash space dash space ... if (eLineCap != css::drawing::LineCap_BUTT)
{
size_t nSize = pStroke->size(); // We want to treat dash and space in pairs. There should be no odd size. If so, we ignore // last item.
nSize /= 2; for(size_t a(0); a < nSize; a++)
{ double fDashLengthRel = (*pStroke)[2 * a] * fFactor; double fSpaceLengthRel = (*pStroke)[2 * a + 1] * fFactor; // GDI allows only positive lengths for space, Skia negative lengths too. Thus the // appearance is different, in case space is too small. double fCorrect = fSpaceLengthRel - 1.0 <= 0 ? fSpaceLengthRel - 0.01 : 1.0;
aDashArray[2 * a] = Gdiplus::REAL(fDashLengthRel + fCorrect);
aDashArray[2 * a + 1] = Gdiplus::REAL(fSpaceLengthRel - fCorrect);
}
} else
{ for(size_t a(0); a < pStroke->size(); a++)
{
aDashArray[a] = Gdiplus::REAL((*pStroke)[a] * fFactor);
}
} if (eLineCap == css::drawing::LineCap_ROUND)
aPen.SetDashCap(Gdiplus::DashCapRound); else
aPen.SetDashCap(Gdiplus::DashCapFlat); // "square" doesn't exist in Gdiplus
aPen.SetDashOffset(Gdiplus::REAL(0.0));
aPen.SetDashPattern(aDashArray.data(), aDashArray.size());
}
if(!bDoDirectGDIPlusStroke && pSystemDependentData_GraphicsPath)
{ // MM01 - check on stroke change. Used against not used, or if oth used, // equal or different? Triangulation geometry creation depends heavily // on stroke, independent of being transformation independent constbool bStrokeWasUsed(!pSystemDependentData_GraphicsPath->getStroke().empty());
if(pSystemDependentData_GraphicsPath)
{ // check data validity if (pSystemDependentData_GraphicsPath->getNoLineJoin() != bNoLineJoin
|| bPixelSnapHairline /*tdf#124700*/)
{ // data invalid, forget
pSystemDependentData_GraphicsPath.reset();
}
}
if(pSystemDependentData_GraphicsPath)
{ // copy buffered data
pGraphicsPath = pSystemDependentData_GraphicsPath->getGraphicsPath();
} else
{ // fill data of buffered data
pGraphicsPath = std::make_shared<Gdiplus::GraphicsPath>();
if(!bDoDirectGDIPlusStroke && bStrokeUsed)
{ // MM01 need to do line dashing as fallback stuff here now
basegfx::B2DPolyPolygon aPolyPolygonLine;
// apply LineStyle
basegfx::utils::applyLineDashing(
rPolygon, // source
*pStroke, // pattern
&aPolyPolygonLine, // target for lines
nullptr, // target for gaps
fDotDashLength); // full length if available
// MM01 checked/verified, ok for(sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
{ const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
pGraphicsPath->StartFigure();
impAddB2DPolygonToGDIPlusGraphicsPathReal(
*pGraphicsPath,
aPolyLine,
rObjectToDevice,
bNoLineJoin,
bPixelSnapHairline);
}
} else
{ // no line dashing or direct stroke, just copy
impAddB2DPolygonToGDIPlusGraphicsPathReal(
*pGraphicsPath,
rPolygon,
rObjectToDevice,
bNoLineJoin,
bPixelSnapHairline);
if(rPolygon.isClosed() && !bNoLineJoin)
{ // #i101491# needed to create the correct line joins
pGraphicsPath->CloseFigure();
}
}
// add to buffering mechanism if (!bPixelSnapHairline /*tdf#124700*/)
{
rPolygon.addOrReplaceSystemDependentData<SystemDependentData_GraphicsPath>(
pGraphicsPath,
bNoLineJoin,
pStroke);
}
}
if(mrParent.isPrinter())
{ // tdf#122384 As mentioned above in WinSalGraphicsImpl::drawPolyPolygon // (look for 'One more hint: This *may* also be needed now in'...). // See comments in same spot above *urgently* before doing changes here, // these comments are *still fully valid* at this place (!) const Gdiplus::REAL aDpiX(aGraphics.GetDpiX()); const Gdiplus::REAL aDpiY(aGraphics.GetDpiY());
staticvoid paintToGdiPlus(
Gdiplus::Graphics& rGraphics, const SalTwoRect& rTR,
Gdiplus::Bitmap& rBitmap)
{ // only parts of source are used
Gdiplus::PointF aDestPoints[3];
Gdiplus::ImageAttributes aAttributes;
// this mode is only capable of drawing the whole bitmap to a parallelogram
aDestPoints[0].X = Gdiplus::REAL(rNull.getX());
aDestPoints[0].Y = Gdiplus::REAL(rNull.getY());
aDestPoints[1].X = Gdiplus::REAL(rX.getX());
aDestPoints[1].Y = Gdiplus::REAL(rX.getY());
aDestPoints[2].X = Gdiplus::REAL(rY.getX());
aDestPoints[2].Y = Gdiplus::REAL(rY.getY());
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.