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/win/gdi | |
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 '')
-rw-r--r-- | vcl/win/gdi/DWriteTextRenderer.cxx | 413 | ||||
-rw-r--r-- | vcl/win/gdi/gdiimpl.cxx | 2710 | ||||
-rw-r--r-- | vcl/win/gdi/gdiimpl.hxx | 250 | ||||
-rw-r--r-- | vcl/win/gdi/salbmp.cxx | 1067 | ||||
-rw-r--r-- | vcl/win/gdi/salfont.cxx | 1766 | ||||
-rw-r--r-- | vcl/win/gdi/salgdi.cxx | 1058 | ||||
-rw-r--r-- | vcl/win/gdi/salgdi2.cxx | 248 | ||||
-rw-r--r-- | vcl/win/gdi/salgdi_gdiplus.cxx | 98 | ||||
-rw-r--r-- | vcl/win/gdi/salnativewidgets-luna.cxx | 1559 | ||||
-rw-r--r-- | vcl/win/gdi/salprn.cxx | 1629 | ||||
-rw-r--r-- | vcl/win/gdi/salvd.cxx | 226 | ||||
-rw-r--r-- | vcl/win/gdi/winlayout.cxx | 635 |
12 files changed, 11659 insertions, 0 deletions
diff --git a/vcl/win/gdi/DWriteTextRenderer.cxx b/vcl/win/gdi/DWriteTextRenderer.cxx new file mode 100644 index 000000000..734b68b07 --- /dev/null +++ b/vcl/win/gdi/DWriteTextRenderer.cxx @@ -0,0 +1,413 @@ +/* -*- 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 <win/salgdi.h> +#include <win/saldata.hxx> +#include <outdev.h> + +#include <win/DWriteTextRenderer.hxx> + +#include <sft.hxx> +#include <sallayout.hxx> + +#include <shlwapi.h> +#include <winver.h> + +#include <comphelper/windowserrorstring.hxx> +#include <sal/log.hxx> + +namespace +{ + +D2DTextAntiAliasMode lclGetSystemTextAntiAliasMode() +{ + D2DTextAntiAliasMode eMode = D2DTextAntiAliasMode::Default; + + BOOL bFontSmoothing; + if (!SystemParametersInfoW(SPI_GETFONTSMOOTHING, 0, &bFontSmoothing, 0)) + return eMode; + + if (bFontSmoothing) + { + eMode = D2DTextAntiAliasMode::AntiAliased; + + UINT nType; + if (SystemParametersInfoW(SPI_GETFONTSMOOTHINGTYPE, 0, &nType, 0) && nType == FE_FONTSMOOTHINGCLEARTYPE) + eMode = D2DTextAntiAliasMode::ClearType; + } + else + { + eMode = D2DTextAntiAliasMode::Aliased; + } + + return eMode; +} + +IDWriteRenderingParams* lclSetRenderingMode(IDWriteFactory* pDWriteFactory, DWRITE_RENDERING_MODE eRenderingMode) +{ + IDWriteRenderingParams* pDefaultParameters = nullptr; + pDWriteFactory->CreateRenderingParams(&pDefaultParameters); + + IDWriteRenderingParams* pParameters = nullptr; + pDWriteFactory->CreateCustomRenderingParams( + pDefaultParameters->GetGamma(), + pDefaultParameters->GetEnhancedContrast(), + pDefaultParameters->GetClearTypeLevel(), + pDefaultParameters->GetPixelGeometry(), + eRenderingMode, + &pParameters); + return pParameters; +} + +#ifdef SAL_LOG_WARN +HRESULT checkResult(HRESULT hr, const char* file, size_t line) +{ + if (FAILED(hr)) + { + OUString sLocationString = OUString::createFromAscii(file) + ":" + OUString::number(line) + " "; + SAL_DETAIL_LOG_STREAM(SAL_DETAIL_ENABLE_LOG_WARN, ::SAL_DETAIL_LOG_LEVEL_WARN, + "vcl.gdi", sLocationString.toUtf8().getStr(), + "HRESULT failed with: 0x" << OUString::number(hr, 16) << ": " << WindowsErrorStringFromHRESULT(hr)); + } + return hr; +} + +#define CHECKHR(funct) checkResult(funct, __FILE__, __LINE__) +#else +#define CHECKHR(funct) (funct) +#endif + + +} // end anonymous namespace + +D2DWriteTextOutRenderer::D2DWriteTextOutRenderer() + : mpD2DFactory(nullptr), + mpDWriteFactory(nullptr), + mpGdiInterop(nullptr), + mpRT(nullptr), + mRTProps(D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), + 0, 0)), + mpFontFace(nullptr), + mlfEmHeight(0.0f), + mhDC(nullptr), + meTextAntiAliasMode(D2DTextAntiAliasMode::Default) +{ + HRESULT hr = S_OK; + hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), nullptr, reinterpret_cast<void **>(&mpD2DFactory)); + hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(&mpDWriteFactory)); + if (SUCCEEDED(hr)) + { + hr = mpDWriteFactory->GetGdiInterop(&mpGdiInterop); + hr = CreateRenderTarget(); + } + meTextAntiAliasMode = lclGetSystemTextAntiAliasMode(); +} + +D2DWriteTextOutRenderer::~D2DWriteTextOutRenderer() +{ + if (mpRT) + mpRT->Release(); + if (mpGdiInterop) + mpGdiInterop->Release(); + if (mpDWriteFactory) + mpDWriteFactory->Release(); + if (mpD2DFactory) + mpD2DFactory->Release(); +} + +void D2DWriteTextOutRenderer::applyTextAntiAliasMode() +{ + D2D1_TEXT_ANTIALIAS_MODE eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT; + DWRITE_RENDERING_MODE eRenderingMode = DWRITE_RENDERING_MODE_DEFAULT; + switch (meTextAntiAliasMode) + { + case D2DTextAntiAliasMode::Default: + eRenderingMode = DWRITE_RENDERING_MODE_DEFAULT; + eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT; + break; + case D2DTextAntiAliasMode::Aliased: + eRenderingMode = DWRITE_RENDERING_MODE_ALIASED; + eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_ALIASED; + break; + case D2DTextAntiAliasMode::AntiAliased: + eRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC; + eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE; + break; + case D2DTextAntiAliasMode::ClearType: + eRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC; + eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; + break; + default: + break; + } + mpRT->SetTextRenderingParams(lclSetRenderingMode(mpDWriteFactory, eRenderingMode)); + mpRT->SetTextAntialiasMode(eTextAAMode); +} + +HRESULT D2DWriteTextOutRenderer::CreateRenderTarget() +{ + if (mpRT) + { + mpRT->Release(); + mpRT = nullptr; + } + HRESULT hr = CHECKHR(mpD2DFactory->CreateDCRenderTarget(&mRTProps, &mpRT)); + if (SUCCEEDED(hr)) + applyTextAntiAliasMode(); + return hr; +} + +void D2DWriteTextOutRenderer::changeTextAntiAliasMode(D2DTextAntiAliasMode eMode) +{ + if (meTextAntiAliasMode != eMode) + { + meTextAntiAliasMode = eMode; + applyTextAntiAliasMode(); + } +} + +bool D2DWriteTextOutRenderer::Ready() const +{ + return mpGdiInterop && mpRT; +} + +HRESULT D2DWriteTextOutRenderer::BindDC(HDC hDC, tools::Rectangle const & rRect) +{ + RECT const rc = { rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom() }; + return CHECKHR(mpRT->BindDC(hDC, &rc)); +} + +bool D2DWriteTextOutRenderer::operator ()(GenericSalLayout const & rLayout, SalGraphics& rGraphics, HDC hDC) +{ + bool bRetry = false; + bool bResult = false; + int nCount = 0; + do + { + bRetry = false; + bResult = performRender(rLayout, rGraphics, hDC, bRetry); + nCount++; + } while (bRetry && nCount < 3); + return bResult; +} + +bool D2DWriteTextOutRenderer::performRender(GenericSalLayout const & rLayout, SalGraphics& rGraphics, HDC hDC, bool& bRetry) +{ + if (!Ready()) + return false; + + HRESULT hr = S_OK; + hr = BindDC(hDC); + + if (hr == D2DERR_RECREATE_TARGET) + { + CreateRenderTarget(); + bRetry = true; + return false; + } + if (FAILED(hr)) + { + // If for any reason we can't bind fallback to legacy APIs. + return ExTextOutRenderer()(rLayout, rGraphics, hDC); + } + + mlfEmHeight = 0; + if (!GetDWriteFaceFromHDC(hDC, &mpFontFace, &mlfEmHeight)) + return false; + + const WinFontInstance& rWinFont = static_cast<const WinFontInstance&>(rLayout.GetFont()); + float fHScale = rWinFont.getHScale(); + + tools::Rectangle bounds; + bool succeeded = rLayout.GetBoundRect(bounds); + if (succeeded) + { + hr = BindDC(hDC, bounds); // Update the bounding rect. + succeeded &= SUCCEEDED(hr); + } + + ID2D1SolidColorBrush* pBrush = nullptr; + if (succeeded) + { + COLORREF bgrTextColor = GetTextColor(hDC); + D2D1::ColorF aD2DColor(GetRValue(bgrTextColor) / 255.0f, GetGValue(bgrTextColor) / 255.0f, GetBValue(bgrTextColor) / 255.0f); + succeeded &= SUCCEEDED(CHECKHR(mpRT->CreateSolidColorBrush(aD2DColor, &pBrush))); + } + + if (succeeded) + { + mpRT->BeginDraw(); + + int nStart = 0; + Point aPos(0, 0); + const GlyphItem* pGlyph; + while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart)) + { + UINT16 glyphIndices[] = { pGlyph->glyphId() }; + FLOAT glyphAdvances[] = { static_cast<FLOAT>(pGlyph->m_nNewWidth) / fHScale }; + DWRITE_GLYPH_OFFSET glyphOffsets[] = { { 0.0f, 0.0f }, }; + D2D1_POINT_2F baseline = { static_cast<FLOAT>(aPos.X() - bounds.Left()) / fHScale, + static_cast<FLOAT>(aPos.Y() - bounds.Top()) }; + WinFontTransformGuard aTransformGuard(mpRT, fHScale, rLayout, baseline); + DWRITE_GLYPH_RUN glyphs = { + mpFontFace, + mlfEmHeight, + 1, + glyphIndices, + glyphAdvances, + glyphOffsets, + false, + 0 + }; + + mpRT->DrawGlyphRun(baseline, &glyphs, pBrush); + } + + hr = CHECKHR(mpRT->EndDraw()); + } + + if (pBrush) + pBrush->Release(); + + ReleaseFont(); + + if (hr == D2DERR_RECREATE_TARGET) + { + CreateRenderTarget(); + bRetry = true; + } + + return succeeded; +} + +bool D2DWriteTextOutRenderer::BindFont(HDC hDC) +{ + // A TextOutRender can only be bound to one font at a time, so the + assert(mpFontFace == nullptr); + if (mpFontFace) + { + ReleaseFont(); + return false; + } + + // Initially bind to an empty rectangle to get access to the font face, + // we'll update it once we've calculated a bounding rect in DrawGlyphs + if (FAILED(BindDC(mhDC = hDC))) + return false; + + mlfEmHeight = 0; + return GetDWriteFaceFromHDC(hDC, &mpFontFace, &mlfEmHeight); +} + +bool D2DWriteTextOutRenderer::ReleaseFont() +{ + mpFontFace->Release(); + mpFontFace = nullptr; + mhDC = nullptr; + + return true; +} + +// GetGlyphInkBoxes +// The inkboxes returned have their origin on the baseline, to a -ve value +// of Top() means the glyph extends abs(Top()) many pixels above the +// baseline, and +ve means the ink starts that many pixels below. +std::vector<tools::Rectangle> D2DWriteTextOutRenderer::GetGlyphInkBoxes(uint16_t const * pGid, uint16_t const * pGidEnd) const +{ + ptrdiff_t nGlyphs = pGidEnd - pGid; + if (nGlyphs < 0) + return std::vector<tools::Rectangle>(); + + DWRITE_FONT_METRICS aFontMetrics; + mpFontFace->GetMetrics(&aFontMetrics); + + std::vector<DWRITE_GLYPH_METRICS> metrics(nGlyphs); + if (!SUCCEEDED(CHECKHR(mpFontFace->GetDesignGlyphMetrics(pGid, nGlyphs, metrics.data())))) + return std::vector<tools::Rectangle>(); + + std::vector<tools::Rectangle> aOut(nGlyphs); + auto pOut = aOut.begin(); + for (auto &m : metrics) + { + const long left = m.leftSideBearing, + top = m.topSideBearing - m.verticalOriginY, + right = m.advanceWidth - m.rightSideBearing, + bottom = INT32(m.advanceHeight) - m.verticalOriginY - m.bottomSideBearing; + + // Scale to screen space. + pOut->SetLeft( std::floor(left * mlfEmHeight / aFontMetrics.designUnitsPerEm) ); + pOut->SetTop( std::floor(top * mlfEmHeight / aFontMetrics.designUnitsPerEm) ); + pOut->SetRight( std::ceil(right * mlfEmHeight / aFontMetrics.designUnitsPerEm) ); + pOut->SetBottom( std::ceil(bottom * mlfEmHeight / aFontMetrics.designUnitsPerEm) ); + + ++pOut; + } + + return aOut; +} + +bool D2DWriteTextOutRenderer::GetDWriteFaceFromHDC(HDC hDC, IDWriteFontFace ** ppFontFace, float * lfSize) const +{ + bool succeeded = SUCCEEDED(CHECKHR(mpGdiInterop->CreateFontFaceFromHdc(hDC, ppFontFace))); + + if (succeeded) + { + LOGFONTW aLogFont; + HFONT hFont = static_cast<HFONT>(::GetCurrentObject(hDC, OBJ_FONT)); + + GetObjectW(hFont, sizeof(LOGFONTW), &aLogFont); + float dpix, dpiy; + mpRT->GetDpi(&dpix, &dpiy); + *lfSize = aLogFont.lfHeight * 96.0f / dpiy; + + assert(*lfSize < 0); + *lfSize *= -1; + } + + return succeeded; +} + +WinFontTransformGuard::WinFontTransformGuard(ID2D1RenderTarget* pRenderTarget, float fHScale, + const GenericSalLayout& rLayout, + const D2D1_POINT_2F& rBaseline) + : mpRenderTarget(pRenderTarget) +{ + pRenderTarget->GetTransform(&maTransform); + D2D1::Matrix3x2F aTransform = maTransform; + if (fHScale != 1.0f) + { + aTransform + = aTransform * D2D1::Matrix3x2F::Scale(D2D1::Size(fHScale, 1.0f), D2D1::Point2F(0, 0)); + } + + if (rLayout.GetOrientation() != 0) + { + // DWrite angle is in clockwise degrees, our orientation is in counter-clockwise 10th + // degrees. + aTransform = aTransform + * D2D1::Matrix3x2F::Rotation( + -static_cast<FLOAT>(rLayout.GetOrientation()) / 10, rBaseline); + } + mpRenderTarget->SetTransform(aTransform); +} + +WinFontTransformGuard::~WinFontTransformGuard() { mpRenderTarget->SetTransform(maTransform); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/gdiimpl.cxx b/vcl/win/gdi/gdiimpl.cxx new file mode 100644 index 000000000..b01969272 --- /dev/null +++ b/vcl/win/gdi/gdiimpl.cxx @@ -0,0 +1,2710 @@ +/* -*- 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 <memory> +#include <numeric> + +#include <svsys.h> + +#include "gdiimpl.hxx" + +#include <string.h> +#include <rtl/strbuf.hxx> +#include <sal/log.hxx> +#include <tools/poly.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <win/wincomp.hxx> +#include <win/saldata.hxx> +#include <win/salgdi.h> +#include <win/salbmp.h> +#include <win/scoped_gdi.hxx> +#include <vcl/BitmapAccessMode.hxx> +#include <vcl/BitmapBuffer.hxx> +#include <vcl/BitmapPalette.hxx> +#include <win/salframe.h> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/utils/systemdependentdata.hxx> + +#include <outdata.hxx> +#include <win/salids.hrc> +#include <ControlCacheKey.hxx> + +#if defined _MSC_VER +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#endif + +#include <prewin.h> + +#include <gdiplus.h> +#include <gdiplusenums.h> +#include <gdipluscolor.h> + +#include <postwin.h> + +#define SAL_POLYPOLYCOUNT_STACKBUF 8 +#define SAL_POLYPOLYPOINTS_STACKBUF 64 + +#define SAL_POLY_STACKBUF 32 + +namespace { + +// #100127# Fill point and flag memory from array of points which +// might also contain bezier control points for the PolyDraw() GDI method +// Make sure pWinPointAry and pWinFlagAry are big enough +void ImplPreparePolyDraw( bool bCloseFigures, + sal_uLong nPoly, + const sal_uInt32* pPoints, + const SalPoint* const* pPtAry, + const PolyFlags* const* pFlgAry, + POINT* pWinPointAry, + BYTE* pWinFlagAry ) +{ + sal_uLong nCurrPoly; + for( nCurrPoly=0; nCurrPoly<nPoly; ++nCurrPoly ) + { + const POINT* pCurrPoint = reinterpret_cast<const POINT*>( *pPtAry++ ); + const PolyFlags* pCurrFlag = *pFlgAry++; + const sal_uInt32 nCurrPoints = *pPoints++; + const bool bHaveFlagArray( pCurrFlag ); + sal_uLong nCurrPoint; + + if( nCurrPoints ) + { + // start figure + *pWinPointAry++ = *pCurrPoint++; + *pWinFlagAry++ = PT_MOVETO; + ++pCurrFlag; + + for( nCurrPoint=1; nCurrPoint<nCurrPoints; ) + { + // #102067# Check existence of flag array + if( bHaveFlagArray && + ( nCurrPoint + 2 ) < nCurrPoints ) + { + PolyFlags P4( pCurrFlag[ 2 ] ); + + if( ( PolyFlags::Control == pCurrFlag[ 0 ] ) && + ( PolyFlags::Control == pCurrFlag[ 1 ] ) && + ( PolyFlags::Normal == P4 || PolyFlags::Smooth == P4 || PolyFlags::Symmetric == P4 ) ) + { + // control point one + *pWinPointAry++ = *pCurrPoint++; + *pWinFlagAry++ = PT_BEZIERTO; + + // control point two + *pWinPointAry++ = *pCurrPoint++; + *pWinFlagAry++ = PT_BEZIERTO; + + // end point + *pWinPointAry++ = *pCurrPoint++; + *pWinFlagAry++ = PT_BEZIERTO; + + nCurrPoint += 3; + pCurrFlag += 3; + continue; + } + } + + // regular line point + *pWinPointAry++ = *pCurrPoint++; + *pWinFlagAry++ = PT_LINETO; + ++pCurrFlag; + ++nCurrPoint; + } + + // end figure? + if( bCloseFigures ) + pWinFlagAry[-1] |= PT_CLOSEFIGURE; + } + } +} + +Color ImplGetROPColor( SalROPColor nROPColor ) +{ + Color nColor; + if ( nROPColor == SalROPColor::N0 ) + nColor = Color( 0, 0, 0 ); + else + nColor = Color( 255, 255, 255 ); + return nColor; +} + +bool IsDitherColor(BYTE nRed, BYTE nGreen, BYTE nBlue) +{ + constexpr sal_uInt8 DITHER_PAL_DELTA = 51; + + return !(nRed % DITHER_PAL_DELTA) && + !(nGreen % DITHER_PAL_DELTA) && + !(nBlue % DITHER_PAL_DELTA); +} + +bool IsPaletteColor(BYTE nRed, BYTE nGreen, BYTE nBlue) +{ + static const PALETTEENTRY aImplSalSysPalEntryAry[] = + { + { 0, 0, 0, 0 }, + { 0, 0, 0x80, 0 }, + { 0, 0x80, 0, 0 }, + { 0, 0x80, 0x80, 0 }, + { 0x80, 0, 0, 0 }, + { 0x80, 0, 0x80, 0 }, + { 0x80, 0x80, 0, 0 }, + { 0x80, 0x80, 0x80, 0 }, + { 0xC0, 0xC0, 0xC0, 0 }, + { 0, 0, 0xFF, 0 }, + { 0, 0xFF, 0, 0 }, + { 0, 0xFF, 0xFF, 0 }, + { 0xFF, 0, 0, 0 }, + { 0xFF, 0, 0xFF, 0 }, + { 0xFF, 0xFF, 0, 0 }, + { 0xFF, 0xFF, 0xFF, 0 } + }; + + for (const auto& rPalEntry : aImplSalSysPalEntryAry) + { + if(rPalEntry.peRed == nRed && + rPalEntry.peGreen == nGreen && + rPalEntry.peBlue == nBlue) + { + return true; + } + } + + return false; +} + +bool IsExtraColor(BYTE nRed, BYTE nGreen, BYTE nBlue) +{ + return (nRed == 0) && (nGreen == 184) && (nBlue == 255); +} + +bool ImplIsPaletteEntry(BYTE nRed, BYTE nGreen, BYTE nBlue) +{ + return IsDitherColor(nRed, nGreen, nBlue) || + IsPaletteColor(nRed, nGreen, nBlue) || + IsExtraColor(nRed, nGreen, nBlue); +} + +} // namespace + +WinSalGraphicsImpl::WinSalGraphicsImpl(WinSalGraphics& rParent): + mrParent(rParent), + mbXORMode(false), + mbPen(false), + mhPen(nullptr), + mbStockPen(false), + mbBrush(false), + mbStockBrush(false), + mhBrush(nullptr) +{ +} + +WinSalGraphicsImpl::~WinSalGraphicsImpl() +{ + if ( mhPen ) + { + if ( !mbStockPen ) + DeletePen( mhPen ); + } + + if ( mhBrush ) + { + if ( !mbStockBrush ) + DeleteBrush( mhBrush ); + } +} + +void WinSalGraphicsImpl::Init() +{ +} + +void WinSalGraphicsImpl::freeResources() +{ +} + +bool WinSalGraphicsImpl::drawEPS(long, long, long, long, void*, sal_uInt32) +{ + return false; +} + +void WinSalGraphicsImpl::copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) +{ + HDC hSrcDC; + DWORD nRop; + + if ( pSrcGraphics ) + hSrcDC = static_cast<WinSalGraphics*>(pSrcGraphics)->getHDC(); + else + hSrcDC = mrParent.getHDC(); + + if ( mbXORMode ) + nRop = SRCINVERT; + else + nRop = SRCCOPY; + + if ( (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) && + (rPosAry.mnSrcHeight == rPosAry.mnDestHeight) ) + { + BitBlt( mrParent.getHDC(), + static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY), + static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight), + hSrcDC, + static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY), + nRop ); + } + else + { + int nOldStretchMode = SetStretchBltMode( mrParent.getHDC(), STRETCH_DELETESCANS ); + StretchBlt( mrParent.getHDC(), + static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY), + static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight), + hSrcDC, + static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY), + static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight), + nRop ); + SetStretchBltMode( mrParent.getHDC(), nOldStretchMode ); + } +} + +namespace +{ + +void MakeInvisibleArea(const RECT& rSrcRect, + int nLeft, int nTop, int nRight, int nBottom, + HRGN& rhInvalidateRgn) +{ + if (!rhInvalidateRgn) + { + rhInvalidateRgn = CreateRectRgnIndirect(&rSrcRect); + } + + ScopedHRGN hTempRgn(CreateRectRgn(nLeft, nTop, nRight, nBottom)); + CombineRgn(rhInvalidateRgn, rhInvalidateRgn, hTempRgn.get(), RGN_DIFF); +} + +void ImplCalcOutSideRgn( const RECT& rSrcRect, + int nLeft, int nTop, int nRight, int nBottom, + HRGN& rhInvalidateRgn ) +{ + // calculate area outside the visible region + if (rSrcRect.left < nLeft) + { + MakeInvisibleArea(rSrcRect, -31999, 0, nLeft, 31999, rhInvalidateRgn); + } + if (rSrcRect.top < nTop) + { + MakeInvisibleArea(rSrcRect, 0, -31999, 31999, nTop, rhInvalidateRgn); + } + if (rSrcRect.right > nRight) + { + MakeInvisibleArea(rSrcRect, nRight, 0, 31999, 31999, rhInvalidateRgn); + } + if (rSrcRect.bottom > nBottom) + { + MakeInvisibleArea(rSrcRect, 0, nBottom, 31999, 31999, rhInvalidateRgn); + } +} + +} // namespace + +void WinSalGraphicsImpl::copyArea( long nDestX, long nDestY, + long nSrcX, long nSrcY, + long nSrcWidth, long nSrcHeight, + bool bWindowInvalidate ) +{ + bool bRestoreClipRgn = false; + HRGN hOldClipRgn = nullptr; + int nOldClipRgnType = ERROR; + HRGN hInvalidateRgn = nullptr; + + // 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; + + // restrict srcRect to this window (calc intersection) + aSrcRect.left = static_cast<int>(nSrcX); + aSrcRect.top = static_cast<int>(nSrcY); + aSrcRect.right = aSrcRect.left+static_cast<int>(nSrcWidth); + aSrcRect.bottom = aSrcRect.top+static_cast<int>(nSrcHeight); + GetClientRect( mrParent.gethWnd(), &aClipRect ); + if ( IntersectRect( &aSrcRect, &aSrcRect, &aClipRect ) ) + { + // transform srcRect to screen coordinates + POINT aPt; + aPt.x = 0; + aPt.y = 0; + ClientToScreen( mrParent.gethWnd(), &aPt ); + aSrcRect.left += aPt.x; + aSrcRect.top += aPt.y; + aSrcRect.right += aPt.x; + aSrcRect.bottom += aPt.y; + hInvalidateRgn = nullptr; + + // 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 ); + } + } + } + } + + BitBlt( mrParent.getHDC(), + static_cast<int>(nDestX), static_cast<int>(nDestY), + static_cast<int>(nSrcWidth), static_cast<int>(nSrcHeight), + mrParent.getHDC(), + static_cast<int>(nSrcX), static_cast<int>(nSrcY), + SRCCOPY ); + + if( bRestoreClipRgn ) + { + // restore old clip region + if( nOldClipRgnType != ERROR ) + SelectClipRgn( mrParent.getHDC(), hOldClipRgn); + DeleteRegion( hOldClipRgn ); + + // invalidate regions that were not copied + bool bInvalidate = true; + + // Combine Invalidate vcl::Region with existing ClipRegion + HRGN hTempRgn = CreateRectRgn( 0, 0, 0, 0 ); + if ( GetClipRgn( mrParent.getHDC(), hTempRgn ) == 1 ) + { + int nRgnType = CombineRgn( hInvalidateRgn, hTempRgn, hInvalidateRgn, RGN_AND ); + if ( (nRgnType == ERROR) || (nRgnType == NULLREGION) ) + bInvalidate = false; + } + DeleteRegion( hTempRgn ); + + 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() ); + } + + DeleteRegion( hInvalidateRgn ); + } + +} + +namespace { + +void ImplDrawBitmap( HDC hDC, const SalTwoRect& rPosAry, const WinSalBitmap& rSalBitmap, + bool bPrinter, int nDrawMode ) +{ + if( hDC ) + { + HGLOBAL hDrawDIB; + HBITMAP hDrawDDB = rSalBitmap.ImplGethDDB(); + std::unique_ptr<WinSalBitmap> xTmpSalBmp; + bool bPrintDDB = ( bPrinter && hDrawDDB ); + + if( bPrintDDB ) + { + xTmpSalBmp.reset(new WinSalBitmap); + xTmpSalBmp->Create( rSalBitmap, rSalBitmap.GetBitCount() ); + hDrawDIB = xTmpSalBmp->ImplGethDIB(); + } + else + hDrawDIB = rSalBitmap.ImplGethDIB(); + + if( hDrawDIB ) + { + PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( hDrawDIB )); + PBYTE pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize + + WinSalBitmap::ImplGetDIBColorCount( hDrawDIB ) * sizeof( RGBQUAD ); + const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS ); + + StretchDIBits( hDC, + static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY), + static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight), + static_cast<int>(rPosAry.mnSrcX), static_cast<int>(pBI->bmiHeader.biHeight - rPosAry.mnSrcHeight - rPosAry.mnSrcY), + static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight), + pBits, pBI, DIB_RGB_COLORS, nDrawMode ); + + GlobalUnlock( hDrawDIB ); + SetStretchBltMode( hDC, nOldStretchMode ); + } + else if( hDrawDDB && !bPrintDDB ) + { + ScopedCachedHDC<CACHED_HDC_DRAW> hBmpDC(hDrawDDB); + + COLORREF nOldBkColor = RGB(0xFF,0xFF,0xFF); + COLORREF nOldTextColor = RGB(0,0,0); + bool bMono = ( rSalBitmap.GetBitCount() == 1 ); + + if( bMono ) + { + COLORREF nBkColor = RGB( 0xFF, 0xFF, 0xFF ); + COLORREF nTextColor = RGB( 0x00, 0x00, 0x00 ); + //fdo#33455 handle 1 bit depth pngs with palette entries + //to set fore/back colors + if (BitmapBuffer* pBitmapBuffer = const_cast<WinSalBitmap&>(rSalBitmap).AcquireBuffer(BitmapAccessMode::Info)) + { + const BitmapPalette& rPalette = pBitmapBuffer->maPalette; + if (rPalette.GetEntryCount() == 2) + { + Color nCol = rPalette[0]; + nTextColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() ); + nCol = rPalette[1]; + nBkColor = RGB( nCol.GetRed(), nCol.GetGreen(), nCol.GetBlue() ); + } + const_cast<WinSalBitmap&>(rSalBitmap).ReleaseBuffer(pBitmapBuffer, BitmapAccessMode::Info); + } + nOldBkColor = SetBkColor( hDC, nBkColor ); + nOldTextColor = ::SetTextColor( hDC, nTextColor ); + } + + if ( (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) && + (rPosAry.mnSrcHeight == rPosAry.mnDestHeight) ) + { + BitBlt( hDC, + static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY), + static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight), + hBmpDC.get(), + static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY), + nDrawMode ); + } + else + { + const int nOldStretchMode = SetStretchBltMode( hDC, STRETCH_DELETESCANS ); + + StretchBlt( hDC, + static_cast<int>(rPosAry.mnDestX), static_cast<int>(rPosAry.mnDestY), + static_cast<int>(rPosAry.mnDestWidth), static_cast<int>(rPosAry.mnDestHeight), + hBmpDC.get(), + static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY), + static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight), + nDrawMode ); + + SetStretchBltMode( hDC, nOldStretchMode ); + } + + if( bMono ) + { + SetBkColor( hDC, nOldBkColor ); + ::SetTextColor( hDC, nOldTextColor ); + } + } + } +} + +} // namespace + +void WinSalGraphicsImpl::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap) +{ + bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode); + + 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)); + + ImplDrawBitmap(mrParent.getHDC(), rPosAry, static_cast<const WinSalBitmap&>(rSalBitmap), + mrParent.isPrinter(), + mbXORMode ? SRCINVERT : SRCCOPY ); +} + +void WinSalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rSSalBitmap, + const SalBitmap& rSTransparentBitmap ) +{ + SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No transparency print possible!" ); + bool bTryDirectPaint(!mrParent.isPrinter() && !mbXORMode); + + // try to draw using GdiPlus directly + if(bTryDirectPaint && drawAlphaBitmap(rPosAry, rSSalBitmap, rSTransparentBitmap)) + { + return; + } + + assert(dynamic_cast<const WinSalBitmap*>(&rSSalBitmap)); + assert(dynamic_cast<const WinSalBitmap*>(&rSTransparentBitmap)); + + const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap); + const WinSalBitmap& rTransparentBitmap = static_cast<const WinSalBitmap&>(rSTransparentBitmap); + + SalTwoRect aPosAry = rPosAry; + int nDstX = static_cast<int>(aPosAry.mnDestX); + int nDstY = static_cast<int>(aPosAry.mnDestY); + int nDstWidth = static_cast<int>(aPosAry.mnDestWidth); + int nDstHeight = static_cast<int>(aPosAry.mnDestHeight); + HDC hDC = mrParent.getHDC(); + + ScopedHBITMAP hMemBitmap; + ScopedHBITMAP hMaskBitmap; + + if( ( nDstWidth > CACHED_HDC_DEFEXT ) || ( nDstHeight > CACHED_HDC_DEFEXT ) ) + { + hMemBitmap.reset(CreateCompatibleBitmap(hDC, nDstWidth, nDstHeight)); + hMaskBitmap.reset(CreateCompatibleBitmap(hDC, nDstWidth, nDstHeight)); + } + + ScopedCachedHDC<CACHED_HDC_1> hMemDC(hMemBitmap.get()); + ScopedCachedHDC<CACHED_HDC_2> hMaskDC(hMaskBitmap.get()); + + aPosAry.mnDestX = aPosAry.mnDestY = 0; + BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hDC, nDstX, nDstY, SRCCOPY ); + + // 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; + + if( aTmp.Create( rTransparentBitmap, &mrParent ) ) + ImplDrawBitmap( hMaskDC.get(), aPosAry, aTmp, false, SRCCOPY ); + } + else + ImplDrawBitmap( hMaskDC.get(), aPosAry, rTransparentBitmap, false, SRCCOPY ); + + // now MemDC contains background, MaskDC the transparency mask + + // #105055# Respect XOR mode + if( mbXORMode ) + { + ImplDrawBitmap( hMaskDC.get(), aPosAry, rSalBitmap, false, SRCERASE ); + // now MaskDC contains the bitmap area with black background + BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCINVERT ); + // now MemDC contains background XORed bitmap area ontop + } + else + { + BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCAND ); + // now MemDC contains background with masked-out bitmap area + ImplDrawBitmap( hMaskDC.get(), aPosAry, rSalBitmap, false, SRCERASE ); + // now MaskDC contains the bitmap area with black background + BitBlt( hMemDC.get(), 0, 0, nDstWidth, nDstHeight, hMaskDC.get(), 0, 0, SRCPAINT ); + // now MemDC contains background and bitmap merged together + } + // copy to output DC + BitBlt( hDC, nDstX, nDstY, nDstWidth, nDstHeight, hMemDC.get(), 0, 0, SRCCOPY ); +} + +bool WinSalGraphicsImpl::drawAlphaRect( long nX, long nY, long nWidth, + long nHeight, sal_uInt8 nTransparency ) +{ + if( mbPen || !mbBrush || mbXORMode ) + return false; // can only perform solid fills without XOR. + + ScopedCachedHDC<CACHED_HDC_1> hMemDC(nullptr); + SetPixel( hMemDC.get(), int(0), int(0), mnBrushColor ); + + BLENDFUNCTION aFunc = { + AC_SRC_OVER, + 0, + sal::static_int_cast<sal_uInt8>(255 - 255L*nTransparency/100), + 0 + }; + + // 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!" ); + + assert(dynamic_cast<const WinSalBitmap*>(&rSSalBitmap)); + + const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap); + + SalTwoRect aPosAry = rPosAry; + const HDC hDC = mrParent.getHDC(); + + ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(RGB(nMaskColor.GetRed(), + nMaskColor.GetGreen(), + nMaskColor.GetBlue()))); + + // 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; + + if( aTmp.Create( rSalBitmap, &mrParent ) ) + ImplDrawBitmap( hDC, aPosAry, aTmp, false, 0x00B8074AUL ); + } + else + ImplDrawBitmap( hDC, aPosAry, rSalBitmap, false, 0x00B8074AUL ); +} + +std::shared_ptr<SalBitmap> WinSalGraphicsImpl::getBitmap( long nX, long nY, long nDX, long nDY ) +{ + SAL_WARN_IF( mrParent.isPrinter(), "vcl", "No ::GetBitmap() from printer possible!" ); + + std::shared_ptr<WinSalBitmap> pSalBitmap; + + nDX = labs( nDX ); + nDY = labs( nDY ); + + HDC hDC = mrParent.getHDC(); + HBITMAP hBmpBitmap = CreateCompatibleBitmap( hDC, nDX, nDY ); + bool bRet; + + { + ScopedCachedHDC<CACHED_HDC_1> hBmpDC(hBmpBitmap); + + bRet = BitBlt(hBmpDC.get(), 0, 0, + static_cast<int>(nDX), static_cast<int>(nDY), hDC, + static_cast<int>(nX), static_cast<int>(nY), SRCCOPY) ? TRUE : FALSE; + } + + if( bRet ) + { + pSalBitmap = std::make_shared<WinSalBitmap>(); + + if( !pSalBitmap->Create( hBmpBitmap, false, false ) ) + { + pSalBitmap.reset(); + } + } + else + { + // #124826# avoid resource leak! Happens when running without desktop access (remote desktop, service, may be screensavers) + DeleteBitmap( hBmpBitmap ); + } + + return pSalBitmap; +} + +Color WinSalGraphicsImpl::getPixel( long nX, long nY ) +{ + COLORREF aWinCol = ::GetPixel( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY) ); + + if ( CLR_INVALID == aWinCol ) + return Color( 0, 0, 0 ); + else + return Color( GetRValue( aWinCol ), + GetGValue( aWinCol ), + GetBValue( aWinCol ) ); +} + +namespace +{ + +HBRUSH Get50PercentBrush() +{ + SalData* pSalData = GetSalData(); + if ( !pSalData->mh50Brush ) + { + if ( !pSalData->mh50Bmp ) + pSalData->mh50Bmp = ImplLoadSalBitmap( SAL_RESID_BITMAP_50 ); + pSalData->mh50Brush = CreatePatternBrush( pSalData->mh50Bmp ); + } + + return pSalData->mh50Brush; +} + +} // namespace + +void WinSalGraphicsImpl::invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags ) +{ + if ( nFlags & SalInvert::TrackFrame ) + { + HPEN hDotPen = CreatePen( PS_DOT, 0, 0 ); + HPEN hOldPen = SelectPen( mrParent.getHDC(), hDotPen ); + HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), GetStockBrush( NULL_BRUSH ) ); + int nOldROP = SetROP2( mrParent.getHDC(), R2_NOT ); + + Rectangle( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nX+nWidth), static_cast<int>(nY+nHeight) ); + + SetROP2( mrParent.getHDC(), nOldROP ); + SelectPen( mrParent.getHDC(), hOldPen ); + SelectBrush( mrParent.getHDC(), hOldBrush ); + DeletePen( hDotPen ); + } + else if ( nFlags & SalInvert::N50 ) + { + COLORREF nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 ); + HBRUSH hOldBrush = SelectBrush( mrParent.getHDC(), Get50PercentBrush() ); + PatBlt( mrParent.getHDC(), nX, nY, nWidth, nHeight, PATINVERT ); + ::SetTextColor( mrParent.getHDC(), nOldTextColor ); + SelectBrush( mrParent.getHDC(), hOldBrush ); + } + else + { + RECT aRect; + aRect.left = static_cast<int>(nX); + aRect.top = static_cast<int>(nY); + aRect.right = static_cast<int>(nX)+nWidth; + aRect.bottom = static_cast<int>(nY)+nHeight; + ::InvertRect( mrParent.getHDC(), &aRect ); + } +} + +void WinSalGraphicsImpl::invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nSalFlags ) +{ + HPEN hPen; + HPEN hOldPen; + HBRUSH hBrush; + HBRUSH hOldBrush = nullptr; + COLORREF nOldTextColor RGB(0,0,0); + int nOldROP = SetROP2( mrParent.getHDC(), R2_NOT ); + + if ( nSalFlags & SalInvert::TrackFrame ) + hPen = CreatePen( PS_DOT, 0, 0 ); + else + { + + if ( nSalFlags & SalInvert::N50 ) + hBrush = Get50PercentBrush(); + else + hBrush = GetStockBrush( BLACK_BRUSH ); + + hPen = GetStockPen( NULL_PEN ); + nOldTextColor = ::SetTextColor( mrParent.getHDC(), 0 ); + hOldBrush = SelectBrush( mrParent.getHDC(), hBrush ); + } + hOldPen = SelectPen( mrParent.getHDC(), hPen ); + + POINT const * pWinPtAry; + // for NT, we can handover the array directly + static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" ); + + pWinPtAry = reinterpret_cast<POINT const *>(pPtAry); + // for Windows 95 and its maximum number of points + if ( nSalFlags & SalInvert::TrackFrame ) + { + if ( !Polyline( mrParent.getHDC(), pWinPtAry, static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) ) + Polyline( mrParent.getHDC(), pWinPtAry, MAX_64KSALPOINTS ); + } + else + { + if ( !Polygon( mrParent.getHDC(), pWinPtAry, static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) ) + Polygon( mrParent.getHDC(), pWinPtAry, MAX_64KSALPOINTS ); + } + + SetROP2( mrParent.getHDC(), nOldROP ); + SelectPen( mrParent.getHDC(), hOldPen ); + + if ( nSalFlags & SalInvert::TrackFrame ) + DeletePen( hPen ); + else + { + ::SetTextColor( mrParent.getHDC(), nOldTextColor ); + SelectBrush( mrParent.getHDC(), hOldBrush ); + } +} + +sal_uInt16 WinSalGraphicsImpl::GetBitCount() const +{ + return static_cast<sal_uInt16>(GetDeviceCaps( mrParent.getHDC(), BITSPIXEL )); +} + +long WinSalGraphicsImpl::GetGraphicsWidth() const +{ + if( mrParent.gethWnd() && IsWindow( mrParent.gethWnd() ) ) + { + WinSalFrame* pFrame = GetWindowPtr( mrParent.gethWnd() ); + if( pFrame ) + { + if( pFrame->maGeometry.nWidth ) + return pFrame->maGeometry.nWidth; + else + { + // TODO: perhaps not needed, maGeometry should always be up-to-date + RECT aRect; + GetClientRect( mrParent.gethWnd(), &aRect ); + return aRect.right; + } + } + } + + return 0; +} + +void WinSalGraphicsImpl::ResetClipRegion() +{ + if ( mrParent.mhRegion ) + { + DeleteRegion( mrParent.mhRegion ); + mrParent.mhRegion = nullptr; + } + + SelectClipRgn( mrParent.getHDC(), nullptr ); +} + +static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolygon& rCandidate) +{ + if(rCandidate.areControlPointsUsed()) + { + return false; + } + + const sal_uInt32 nPointCount(rCandidate.count()); + + if(nPointCount < 2) + { + return true; + } + + const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount + 1 : nPointCount); + basegfx::B2DPoint aLast(rCandidate.getB2DPoint(0)); + + for(sal_uInt32 a(1); a < nEdgeCount; a++) + { + const sal_uInt32 nNextIndex(a % nPointCount); + const basegfx::B2DPoint aCurrent(rCandidate.getB2DPoint(nNextIndex)); + + if(!basegfx::fTools::equal(aLast.getX(), aCurrent.getX()) && !basegfx::fTools::equal(aLast.getY(), aCurrent.getY())) + { + return false; + } + + aLast = aCurrent; + } + + return true; +} + +static bool containsOnlyHorizontalAndVerticalEdges(const basegfx::B2DPolyPolygon& rCandidate) +{ + if(rCandidate.areControlPointsUsed()) + { + return false; + } + + for(auto const& rPolygon : rCandidate) + { + if(!containsOnlyHorizontalAndVerticalEdges(rPolygon)) + { + return false; + } + } + + return true; +} + +bool WinSalGraphicsImpl::setClipRegion( const vcl::Region& i_rClip ) +{ + if ( mrParent.mhRegion ) + { + DeleteRegion( mrParent.mhRegion ); + mrParent.mhRegion = nullptr; + } + + bool bUsePolygon(i_rClip.HasPolyPolygonOrB2DPolyPolygon()); + static bool bTryToAvoidPolygon(true); + + // #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(!aPolyPolygon.areControlPointsUsed()) + { + if(containsOnlyHorizontalAndVerticalEdges(aPolyPolygon)) + { + bUsePolygon = false; + } + } + } + + 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()); + + if( nCount ) + { + std::vector< POINT > aPolyPoints; + aPolyPoints.reserve( 1024 ); + std::vector< INT > aPolyCounts( nCount, 0 ); + basegfx::B2DHomMatrix aExpand; + sal_uInt32 nTargetCount(0); + static bool bExpandByOneInXandY(true); + + if(bExpandByOneInXandY) + { + const basegfx::B2DRange aRangeS(aPolyPolygon.getB2DRange()); + const basegfx::B2DRange aRangeT(aRangeS.getMinimum(), aRangeS.getMaximum() + basegfx::B2DTuple(1.0, 1.0)); + aExpand = basegfx::utils::createSourceRangeTargetRangeTransform(aRangeS, aRangeT); + } + + for(auto const& rPolygon : aPolyPolygon) + { + const basegfx::B2DPolygon aPoly( + basegfx::utils::adaptiveSubdivideByDistance( + rPolygon, + 1)); + const sal_uInt32 nPoints(aPoly.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 ); + } + } + } + + if(nTargetCount) + { + mrParent.mhRegion = CreatePolyPolygonRgn( aPolyPoints.data(), aPolyCounts.data(), nTargetCount, ALTERNATE ); + } + } + } + else + { + RectangleVector aRectangles; + i_rClip.GetRegionRectangles(aRectangles); + + sal_uLong nRectBufSize = sizeof(RECT)*aRectangles.size(); + if ( aRectangles.size() < SAL_CLIPRECT_COUNT ) + { + if ( !mrParent.mpStdClipRgnData ) + mrParent.mpStdClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+(SAL_CLIPRECT_COUNT*sizeof(RECT))]); + mrParent.mpClipRgnData = mrParent.mpStdClipRgnData; + } + else + mrParent.mpClipRgnData = reinterpret_cast<RGNDATA*>(new BYTE[sizeof(RGNDATA)-1+nRectBufSize]); + mrParent.mpClipRgnData->rdh.dwSize = sizeof( RGNDATAHEADER ); + mrParent.mpClipRgnData->rdh.iType = RDH_RECTANGLES; + mrParent.mpClipRgnData->rdh.nCount = aRectangles.size(); + mrParent.mpClipRgnData->rdh.nRgnSize = nRectBufSize; + RECT* pBoundRect = &(mrParent.mpClipRgnData->rdh.rcBound); + SetRectEmpty( pBoundRect ); + RECT* pNextClipRect = reinterpret_cast<RECT*>(&(mrParent.mpClipRgnData->Buffer)); + bool bFirstClipRect = true; + + for (auto const& rectangle : aRectangles) + { + const long nW(rectangle.GetWidth()); + const long nH(rectangle.GetHeight()); + + if(nW && nH) + { + const long nRight(rectangle.Left() + nW); + const long nBottom(rectangle.Top() + nH); + + if(bFirstClipRect) + { + pBoundRect->left = rectangle.Left(); + pBoundRect->top = rectangle.Top(); + pBoundRect->right = nRight; + pBoundRect->bottom = nBottom; + bFirstClipRect = false; + } + else + { + if(rectangle.Left() < pBoundRect->left) + { + pBoundRect->left = static_cast<int>(rectangle.Left()); + } + + if(rectangle.Top() < pBoundRect->top) + { + pBoundRect->top = static_cast<int>(rectangle.Top()); + } + + if(nRight > pBoundRect->right) + { + pBoundRect->right = static_cast<int>(nRight); + } + + if(nBottom > pBoundRect->bottom) + { + pBoundRect->bottom = static_cast<int>(nBottom); + } + } + + pNextClipRect->left = static_cast<int>(rectangle.Left()); + pNextClipRect->top = static_cast<int>(rectangle.Top()); + pNextClipRect->right = static_cast<int>(nRight); + pNextClipRect->bottom = static_cast<int>(nBottom); + pNextClipRect++; + } + else + { + mrParent.mpClipRgnData->rdh.nCount--; + mrParent.mpClipRgnData->rdh.nRgnSize -= sizeof( RECT ); + } + } + + // 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) + } + else if(1 == mrParent.mpClipRgnData->rdh.nCount) + { + RECT* pRect = &(mrParent.mpClipRgnData->rdh.rcBound); + mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top, + pRect->right, pRect->bottom ); + } + else if(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; + + if( pHeader.nCount ) + { + RECT* pRect = reinterpret_cast<RECT*>(mrParent.mpClipRgnData->Buffer); + mrParent.mhRegion = CreateRectRgn( pRect->left, pRect->top, pRect->right, pRect->bottom ); + pRect++; + + for( sal_uLong n = 1; n < pHeader.nCount; n++, pRect++ ) + { + ScopedHRGN hRgn(CreateRectRgn(pRect->left, pRect->top, pRect->right, pRect->bottom)); + CombineRgn( mrParent.mhRegion, mrParent.mhRegion, hRgn.get(), RGN_OR ); + } + } + } + + if ( mrParent.mpClipRgnData != mrParent.mpStdClipRgnData ) + delete [] reinterpret_cast<BYTE*>(mrParent.mpClipRgnData); + } + } + + if( mrParent.mhRegion ) + { + SelectClipRgn( mrParent.getHDC(), mrParent.mhRegion ); + + // debug code if you want to check range of the newly applied ClipRegion + //RECT aBound; + //const int aRegionType = GetRgnBox(mrParent.mhRegion, &aBound); + + //bool bBla = true; + } + else + { + // #i123585# See above, this is a valid case, execute it + SelectClipRgn( mrParent.getHDC(), nullptr ); + } + + // #i123585# retval no longer dependent of mrParent.mhRegion, see TaskId comments above + return true; +} + +void WinSalGraphicsImpl::SetLineColor() +{ + ResetPen(GetStockPen(NULL_PEN)); + + // set new data + mbPen = false; + mbStockPen = true; +} + +void WinSalGraphicsImpl::SetLineColor(Color nColor) +{ + COLORREF nPenColor = PALETTERGB(nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue()); + bool bStockPen = false; + + HPEN hNewPen = SearchStockPen(nPenColor); + if (hNewPen) + bStockPen = true; + else + hNewPen = MakePen(nColor); + + ResetPen(hNewPen); + + // 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()) + { + const SalData* pSalData = GetSalData(); + + for (sal_uInt16 i = 0; i < pSalData->mnStockPenCount; i++) + { + if (nPenColor == pSalData->maStockPenColorAry[i]) + return pSalData->mhStockPenAry[i]; + } + } + + return nullptr; +} + +HPEN WinSalGraphicsImpl::MakePen(Color nColor) +{ + COLORREF nPenColor = PALETTERGB(nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue()); + + if (!mrParent.isPrinter()) + { + if (GetSalData()->mhDitherPal && ImplIsSysColorEntry(nColor)) + { + nPenColor = PALRGB_TO_RGB(nPenColor); + } + } + + return CreatePen(PS_SOLID, mrParent.mnPenWidth, nPenColor); +} + +void WinSalGraphicsImpl::ResetPen(HPEN hNewPen) +{ + HPEN hOldPen = SelectPen(mrParent.getHDC(), hNewPen); + + if (mhPen) + { + if (!mbStockPen) + { + DeletePen(mhPen); + } + } + else + { + mrParent.mhDefPen = hOldPen; + } + + mhPen = hNewPen; +} + +void WinSalGraphicsImpl::SetFillColor() +{ + ResetBrush(GetStockBrush(NULL_BRUSH)); + + // set new data + mbBrush = false; + mbStockBrush = true; +} + +void WinSalGraphicsImpl::SetFillColor(Color nColor) +{ + COLORREF nBrushColor = PALETTERGB(nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue()); + bool bStockBrush = false; + + HBRUSH hNewBrush = SearchStockBrush(nBrushColor); + if (hNewBrush) + bStockBrush = true; + else + hNewBrush = MakeBrush(nColor); + + ResetBrush(hNewBrush); + + // 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]; + } + } + + return nullptr; +} + +namespace +{ + +BYTE GetDitherMappingValue(BYTE nVal, BYTE nThres, const SalData* pSalData) +{ + return (pSalData->mpDitherDiff[nVal] > nThres) ? + pSalData->mpDitherHigh[nVal] : pSalData->mpDitherLow[nVal]; +} + +HBRUSH Make16BitDIBPatternBrush(Color nColor) +{ + const SalData* pSalData = GetSalData(); + + const BYTE nRed = nColor.GetRed(); + const BYTE nGreen = nColor.GetGreen(); + const BYTE nBlue = nColor.GetBlue(); + + static const BYTE aOrdDither16Bit[8][8] = + { + { 0, 6, 1, 7, 0, 6, 1, 7 }, + { 4, 2, 5, 3, 4, 2, 5, 3 }, + { 1, 7, 0, 6, 1, 7, 0, 6 }, + { 5, 3, 4, 2, 5, 3, 4, 2 }, + { 0, 6, 1, 7, 0, 6, 1, 7 }, + { 4, 2, 5, 3, 4, 2, 5, 3 }, + { 1, 7, 0, 6, 1, 7, 0, 6 }, + { 5, 3, 4, 2, 5, 3, 4, 2 } + }; + + BYTE* pTmp = pSalData->mpDitherDIBData; + + for(int nY = 0; nY < 8; ++nY) + { + for(int nX = 0; nX < 8; ++nX) + { + const BYTE nThres = aOrdDither16Bit[nY][nX]; + *pTmp++ = GetDitherMappingValue(nBlue, nThres, pSalData); + *pTmp++ = GetDitherMappingValue(nGreen, nThres, pSalData); + *pTmp++ = GetDitherMappingValue(nRed, nThres, pSalData); + } + } + + return CreateDIBPatternBrush(pSalData->mhDitherDIB, DIB_RGB_COLORS); +} + +HBRUSH Make8BitDIBPatternBrush(Color nColor) +{ + const SalData* pSalData = GetSalData(); + + const BYTE nRed = nColor.GetRed(); + const BYTE nGreen = nColor.GetGreen(); + const BYTE nBlue = nColor.GetBlue(); + + static const BYTE aOrdDither8Bit[8][8] = + { + { 0, 38, 9, 48, 2, 40, 12, 50 }, + { 25, 12, 35, 22, 28, 15, 37, 24 }, + { 6, 44, 3, 41, 8, 47, 5, 44 }, + { 32, 19, 28, 16, 34, 21, 31, 18 }, + { 1, 40, 11, 49, 0, 39, 10, 48 }, + { 27, 14, 36, 24, 26, 13, 36, 23 }, + { 8, 46, 4, 43, 7, 45, 4, 42 }, + { 33, 20, 30, 17, 32, 20, 29, 16 } + }; + + BYTE* pTmp = pSalData->mpDitherDIBData; + + for (int nY = 0; nY < 8; ++nY) + { + for (int nX = 0; nX < 8; ++nX) + { + const BYTE nThres = aOrdDither8Bit[nY][nX]; + *pTmp = GetDitherMappingValue(nRed, nThres, pSalData) + + GetDitherMappingValue(nGreen, nThres, pSalData) * 6 + + GetDitherMappingValue(nBlue, nThres, pSalData) * 36; + pTmp++; + } + } + + return CreateDIBPatternBrush(pSalData->mhDitherDIB, DIB_PAL_COLORS); +} + +} // namespace + +HBRUSH WinSalGraphicsImpl::MakeBrush(Color nColor) +{ + const SalData* pSalData = GetSalData(); + + const BYTE nRed = nColor.GetRed(); + const BYTE nGreen = nColor.GetGreen(); + const BYTE nBlue = nColor.GetBlue(); + const COLORREF nBrushColor = PALETTERGB(nRed, nGreen, nBlue); + + if (mrParent.isPrinter() || !pSalData->mhDitherDIB) + return CreateSolidBrush(nBrushColor); + + if (24 == reinterpret_cast<BITMAPINFOHEADER*>(pSalData->mpDitherDIB)->biBitCount) + return Make16BitDIBPatternBrush(nColor); + + if (ImplIsSysColorEntry(nColor)) + return CreateSolidBrush(PALRGB_TO_RGB(nBrushColor)); + + if (ImplIsPaletteEntry(nRed, nGreen, nBlue)) + return CreateSolidBrush(nBrushColor); + + return Make8BitDIBPatternBrush(nColor); +} + +void WinSalGraphicsImpl::ResetBrush(HBRUSH hNewBrush) +{ + HBRUSH hOldBrush = SelectBrush(mrParent.getHDC(), hNewBrush); + + if (mhBrush) + { + if (!mbStockBrush) + { + DeleteBrush(mhBrush); + } + } + else + { + mrParent.mhDefBrush = hOldBrush; + } + + mhBrush = hNewBrush; +} + +void WinSalGraphicsImpl::SetXORMode( bool bSet, bool ) +{ + mbXORMode = bSet; + ::SetROP2( mrParent.getHDC(), bSet ? R2_XORPEN : R2_COPYPEN ); +} + +void WinSalGraphicsImpl::SetROPLineColor( SalROPColor nROPColor ) +{ + SetLineColor( ImplGetROPColor( nROPColor ) ); +} + +void WinSalGraphicsImpl::SetROPFillColor( SalROPColor nROPColor ) +{ + SetFillColor( ImplGetROPColor( nROPColor ) ); +} + +void WinSalGraphicsImpl::DrawPixelImpl( long nX, long nY, COLORREF crColor ) +{ + const HDC hDC = mrParent.getHDC(); + + if (!mbXORMode) + { + SetPixel(hDC, static_cast<int>(nX), static_cast<int>(nY), crColor); + return; + } + + ScopedSelectedHBRUSH hBrush(hDC, CreateSolidBrush(crColor)); + PatBlt(hDC, static_cast<int>(nX), static_cast<int>(nY), int(1), int(1), PATINVERT); +} + +void WinSalGraphicsImpl::drawPixel( long nX, long nY ) +{ + DrawPixelImpl( nX, nY, mnPenColor ); +} + +void WinSalGraphicsImpl::drawPixel( long nX, long nY, Color nColor ) +{ + COLORREF nCol = PALETTERGB( nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue() ); + + if ( !mrParent.isPrinter() && + GetSalData()->mhDitherPal && + ImplIsSysColorEntry( nColor ) ) + nCol = PALRGB_TO_RGB( nCol ); + + DrawPixelImpl( nX, nY, nCol ); +} + +void WinSalGraphicsImpl::drawLine( long nX1, long nY1, long nX2, long nY2 ) +{ + MoveToEx( mrParent.getHDC(), static_cast<int>(nX1), static_cast<int>(nY1), nullptr ); + + LineTo( mrParent.getHDC(), static_cast<int>(nX2), static_cast<int>(nY2) ); + + // LineTo doesn't draw the last pixel + if ( !mrParent.isPrinter() ) + DrawPixelImpl( nX2, nY2, mnPenColor ); +} + +void WinSalGraphicsImpl::drawRect( long nX, long nY, long nWidth, long nHeight ) +{ + if ( !mbPen ) + { + if ( !mrParent.isPrinter() ) + { + PatBlt( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nWidth), static_cast<int>(nHeight), + mbXORMode ? PATINVERT : PATCOPY ); + } + else + { + RECT aWinRect; + aWinRect.left = nX; + aWinRect.top = nY; + aWinRect.right = nX+nWidth; + aWinRect.bottom = nY+nHeight; + ::FillRect( mrParent.getHDC(), &aWinRect, mhBrush ); + } + } + else + Rectangle( mrParent.getHDC(), static_cast<int>(nX), static_cast<int>(nY), static_cast<int>(nX+nWidth), static_cast<int>(nY+nHeight) ); +} + +void WinSalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) +{ + // for NT, we can handover the array directly + static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" ); + + POINT const * pWinPtAry = reinterpret_cast<POINT const *>(pPtAry); + + // for Windows 95 and its maximum number of points + if ( !Polyline( mrParent.getHDC(), pWinPtAry, static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) ) + Polyline( mrParent.getHDC(), pWinPtAry, MAX_64KSALPOINTS ); + + // 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 ); +} + +void WinSalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) +{ + // for NT, we can handover the array directly + static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" ); + + POINT const * pWinPtAry = reinterpret_cast<POINT const *>(pPtAry); + // for Windows 95 and its maximum number of points + if ( !Polygon( mrParent.getHDC(), pWinPtAry, static_cast<int>(nPoints) ) && (nPoints > MAX_64KSALPOINTS) ) + Polygon( mrParent.getHDC(), pWinPtAry, MAX_64KSALPOINTS ); +} + +void WinSalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, + PCONSTSALPOINT* pPtAry ) +{ + UINT aWinPointAry[SAL_POLYPOLYCOUNT_STACKBUF]; + UINT* pWinPointAry; + UINT nPolyPolyPoints = 0; + UINT nPoints; + UINT i; + + if ( nPoly <= SAL_POLYPOLYCOUNT_STACKBUF ) + pWinPointAry = aWinPointAry; + else + pWinPointAry = new UINT[nPoly]; + + for ( i = 0; i < static_cast<UINT>(nPoly); i++ ) + { + nPoints = static_cast<UINT>(pPoints[i])+1; + pWinPointAry[i] = nPoints; + nPolyPolyPoints += nPoints; + } + + POINT aWinPointAryAry[SAL_POLYPOLYPOINTS_STACKBUF]; + POINT* pWinPointAryAry; + if ( nPolyPolyPoints <= SAL_POLYPOLYPOINTS_STACKBUF ) + pWinPointAryAry = aWinPointAryAry; + else + pWinPointAryAry = new POINT[nPolyPolyPoints]; + // for NT, we can handover the array directly + static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" ); + UINT n = 0; + for ( i = 0; i < static_cast<UINT>(nPoly); i++ ) + { + nPoints = pWinPointAry[i]; + const SalPoint* pPolyAry = pPtAry[i]; + memcpy( pWinPointAryAry+n, pPolyAry, (nPoints-1)*sizeof(POINT) ); + pWinPointAryAry[n+nPoints-1] = pWinPointAryAry[n]; + n += nPoints; + } + + if ( !PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast<int*>(pWinPointAry), static_cast<UINT>(nPoly) ) && + (nPolyPolyPoints > MAX_64KSALPOINTS) ) + { + nPolyPolyPoints = 0; + nPoly = 0; + do + { + nPolyPolyPoints += pWinPointAry[static_cast<UINT>(nPoly)]; + nPoly++; + } + while ( nPolyPolyPoints < MAX_64KSALPOINTS ); + nPoly--; + if ( pWinPointAry[static_cast<UINT>(nPoly)] > MAX_64KSALPOINTS ) + pWinPointAry[static_cast<UINT>(nPoly)] = MAX_64KSALPOINTS; + if ( nPoly == 1 ) + Polygon( mrParent.getHDC(), pWinPointAryAry, *pWinPointAry ); + else + PolyPolygon( mrParent.getHDC(), pWinPointAryAry, reinterpret_cast<int*>(pWinPointAry), nPoly ); + } + + if ( pWinPointAry != aWinPointAry ) + delete [] pWinPointAry; + if ( pWinPointAryAry != aWinPointAryAry ) + delete [] pWinPointAryAry; +} + +bool WinSalGraphicsImpl::drawPolyLineBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry ) +{ + static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" ); + + // #100127# draw an array of points which might also contain bezier control points + if (!nPoints) + return true; + + 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, pPtAry->mnX, pPtAry->mnY, nullptr); + ++pPtAry; + ++pFlgAry; + + for(sal_uInt32 i = 1; i < nPoints; ++i) + { + if(*pFlgAry != PolyFlags::Control) + { + LineTo(hdc, pPtAry->mnX, pPtAry->mnY); + } + else if(nPoints - i > 2) + { + PolyBezierTo(hdc, reinterpret_cast<const POINT*>(pPtAry), 3); + i += 2; + pPtAry += 2; + pFlgAry += 2; + } + + ++pPtAry; + ++pFlgAry; + } + + return true; +} + +bool WinSalGraphicsImpl::drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry ) +{ + static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" ); + + POINT aStackAry1[SAL_POLY_STACKBUF]; + BYTE aStackAry2[SAL_POLY_STACKBUF]; + POINT* pWinPointAry; + BYTE* pWinFlagAry; + if( nPoints > SAL_POLY_STACKBUF ) + { + pWinPointAry = new POINT[ nPoints ]; + pWinFlagAry = new BYTE[ nPoints ]; + } + else + { + pWinPointAry = aStackAry1; + pWinFlagAry = aStackAry2; + } + + sal_uInt32 nPoints_i32(nPoints); + ImplPreparePolyDraw(true, 1, &nPoints_i32, &pPtAry, &pFlgAry, pWinPointAry, pWinFlagAry); + + bool bRet( false ); + + if( BeginPath( mrParent.getHDC() ) ) + { + PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nPoints); + + if( EndPath( mrParent.getHDC() ) ) + { + if( StrokeAndFillPath( mrParent.getHDC() ) ) + bRet = true; + } + } + + if( pWinPointAry != aStackAry1 ) + { + delete [] pWinPointAry; + delete [] pWinFlagAry; + } + + return bRet; +} + +bool WinSalGraphicsImpl::drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, + const SalPoint* const* pPtAry, const PolyFlags* const* pFlgAry ) +{ + static_assert( sizeof( POINT ) == sizeof( SalPoint ), "must be the same size" ); + + sal_uLong nCurrPoly, nTotalPoints; + const sal_uInt32* pCurrPoints = pPoints; + for( nCurrPoly=0, nTotalPoints=0; nCurrPoly<nPoly; ++nCurrPoly ) + nTotalPoints += *pCurrPoints++; + + POINT aStackAry1[SAL_POLY_STACKBUF]; + BYTE aStackAry2[SAL_POLY_STACKBUF]; + POINT* pWinPointAry; + BYTE* pWinFlagAry; + if( nTotalPoints > SAL_POLY_STACKBUF ) + { + pWinPointAry = new POINT[ nTotalPoints ]; + pWinFlagAry = new BYTE[ nTotalPoints ]; + } + else + { + pWinPointAry = aStackAry1; + pWinFlagAry = aStackAry2; + } + + ImplPreparePolyDraw(true, nPoly, pPoints, pPtAry, pFlgAry, pWinPointAry, pWinFlagAry); + + bool bRet( false ); + + if( BeginPath( mrParent.getHDC() ) ) + { + PolyDraw(mrParent.getHDC(), pWinPointAry, pWinFlagAry, nTotalPoints); + + if( EndPath( mrParent.getHDC() ) ) + { + if( StrokeAndFillPath( mrParent.getHDC() ) ) + bRet = true; + } + } + + if( pWinPointAry != aStackAry1 ) + { + delete [] pWinPointAry; + delete [] pWinFlagAry; + } + + return bRet; +} + +static basegfx::B2DPoint impPixelSnap( + const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, + basegfx::B2DHomMatrix& rObjectToDeviceInv, + sal_uInt32 nIndex) +{ + const sal_uInt32 nCount(rPolygon.count()); + + // get the data + const basegfx::B2ITuple aPrevTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount))); + const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex)); + const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint)); + const basegfx::B2ITuple aNextTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount))); + + // get the states + const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); + const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); + const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); + const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); + const bool bSnapX(bPrevVertical || bNextVertical); + const bool bSnapY(bPrevHorizontal || bNextHorizontal); + + if(bSnapX || bSnapY) + { + basegfx::B2DPoint aSnappedPoint( + bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(), + bSnapY ? aCurrTuple.getY() : aCurrPoint.getY()); + + if(rObjectToDeviceInv.isIdentity()) + { + rObjectToDeviceInv = rObjectToDevice; + rObjectToDeviceInv.invert(); + } + + aSnappedPoint *= rObjectToDeviceInv; + + return aSnappedPoint; + } + + return rPolygon.getB2DPoint(nIndex); +} + +static void impAddB2DPolygonToGDIPlusGraphicsPathReal( + Gdiplus::GraphicsPath& rGraphicsPath, + const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, + bool bNoLineJoin, + bool bPixelSnapHairline) +{ + sal_uInt32 nCount(rPolygon.count()); + + if(nCount) + { + const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nCount : nCount - 1); + + if(nEdgeCount) + { + const bool bControls(rPolygon.areControlPointsUsed()); + basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0)); + basegfx::B2DHomMatrix aObjectToDeviceInv; + + if(bPixelSnapHairline) + { + aCurr = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, 0); + } + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + const sal_uInt32 nNextIndex((a + 1) % nCount); + basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex)); + const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a)); + const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex)); + + if(bPixelSnapHairline) + { + aNext = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nNextIndex); + } + + if(b1stControlPointUsed || b2ndControlPointUsed) + { + basegfx::B2DPoint aCa(rPolygon.getNextControlPoint(a)); + basegfx::B2DPoint aCb(rPolygon.getPrevControlPoint(nNextIndex)); + + // 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); + } + else if(!b2ndControlPointUsed) + { + aCb = aNext + ((aCa - aNext) * 0.0005); + } + + rGraphicsPath.AddBezier( + static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()), + static_cast< Gdiplus::REAL >(aCa.getX()), static_cast< Gdiplus::REAL >(aCa.getY()), + static_cast< Gdiplus::REAL >(aCb.getX()), static_cast< Gdiplus::REAL >(aCb.getY()), + static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY())); + } + else + { + rGraphicsPath.AddLine( + static_cast< Gdiplus::REAL >(aCurr.getX()), static_cast< Gdiplus::REAL >(aCurr.getY()), + static_cast< Gdiplus::REAL >(aNext.getX()), static_cast< Gdiplus::REAL >(aNext.getY())); + } + + if(a + 1 < nEdgeCount) + { + aCurr = aNext; + + if(bNoLineJoin) + { + rGraphicsPath.StartFigure(); + } + } + } + } + } +} + +namespace { + +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; + +public: + SystemDependentData_GraphicsPath( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + std::shared_ptr<Gdiplus::GraphicsPath>& rpGraphicsPath, + bool bNoLineJoin, + const std::vector< double >* pStroke); // MM01 + + // read access + std::shared_ptr<Gdiplus::GraphicsPath>& getGraphicsPath() { return mpGraphicsPath; } + bool getNoLineJoin() const { return mbNoLineJoin; } + const std::vector< double >& getStroke() const { return maStroke; } + + virtual sal_Int64 estimateUsageInBytes() const override; +}; + +} + +SystemDependentData_GraphicsPath::SystemDependentData_GraphicsPath( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + std::shared_ptr<Gdiplus::GraphicsPath>& rpGraphicsPath, + bool bNoLineJoin, + const std::vector< double >* pStroke) +: basegfx::SystemDependentData(rSystemDependentDataManager), + mpGraphicsPath(rpGraphicsPath), + mbNoLineJoin(bNoLineJoin), + maStroke() +{ + if(nullptr != pStroke) + { + maStroke = *pStroke; + } +} + +sal_Int64 SystemDependentData_GraphicsPath::estimateUsageInBytes() const +{ + sal_Int64 nRetval(0); + + if(mpGraphicsPath) + { + const INT nPointCount(mpGraphicsPath->GetPointCount()); + + if(0 != nPointCount) + { + // Each point has + // - 2 x sizeof(Gdiplus::REAL) + // - 1 byte (see GetPathTypes in docu) + nRetval = nPointCount * ((2 * sizeof(Gdiplus::REAL)) + 1); + } + } + + return nRetval; +} + +bool WinSalGraphicsImpl::drawPolyPolygon( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fTransparency) +{ + const sal_uInt32 nCount(rPolyPolygon.count()); + + if(!mbBrush || 0 == nCount || fTransparency < 0.0 || fTransparency > 1.0) + { + return true; + } + + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + const sal_uInt8 aTrans(sal_uInt8(255) - static_cast<sal_uInt8>(basegfx::fround(fTransparency * 255.0))); + const Gdiplus::Color aTestColor(aTrans, maFillColor.GetRed(), maFillColor.GetGreen(), maFillColor.GetBlue()); + const Gdiplus::SolidBrush aSolidBrush(aTestColor.GetValue()); + + // Set full (Object-to-Device) transformation - if used + if(rObjectToDevice.isIdentity()) + { + aGraphics.ResetTransform(); + } + else + { + Gdiplus::Matrix aMatrix; + + aMatrix.SetElements( + rObjectToDevice.get(0, 0), + rObjectToDevice.get(1, 0), + rObjectToDevice.get(0, 1), + rObjectToDevice.get(1, 1), + rObjectToDevice.get(0, 2), + rObjectToDevice.get(1, 2)); + aGraphics.SetTransform(&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>()); + + 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>( + ImplGetSystemDependentDataManager(), + pGraphicsPath, + false, + nullptr); + } + + if(mrParent.getAntiAliasB2DDraw()) + { + aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + } + else + { + aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone); + } + + 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(); + + aGraphics.ScaleTransform( + Gdiplus::REAL(100.0) / aDpiX, + Gdiplus::REAL(100.0) / aDpiY, + Gdiplus::MatrixOrderAppend); + } + + // use created or buffered data + aGraphics.FillPath( + &aSolidBrush, + &(*pGraphicsPath)); + + return true; +} + +bool WinSalGraphicsImpl::drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolygon, + double fTransparency, + double fLineWidth, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) +{ + // MM01 check done for simple reasons + if(!mbPen || !rPolygon.count() || fTransparency < 0.0 || fTransparency > 1.0) + { + return true; + } + + // need to check/handle LineWidth when ObjectToDevice transformation is used + const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity()); + const bool 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; + + if(!bObjectToDeviceIsIdentity) + { + basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice); + aObjectToDeviceInv.invert(); + fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength(); + } + } + + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + const sal_uInt8 aTrans = static_cast<sal_uInt8>(basegfx::fround( 255 * (1.0 - fTransparency) )); + const Gdiplus::Color aTestColor(aTrans, maLineColor.GetRed(), maLineColor.GetGreen(), maLineColor.GetBlue()); + Gdiplus::Pen aPen(aTestColor.GetValue(), Gdiplus::REAL(fLineWidth)); + bool bNoLineJoin(false); + + // Set full (Object-to-Device) transformation - if used + if(bObjectToDeviceIsIdentity) + { + aGraphics.ResetTransform(); + } + else + { + Gdiplus::Matrix aMatrix; + + aMatrix.SetElements( + rObjectToDevice.get(0, 0), + rObjectToDevice.get(1, 0), + rObjectToDevice.get(0, 1), + rObjectToDevice.get(1, 1), + rObjectToDevice.get(0, 2), + rObjectToDevice.get(1, 2)); + aGraphics.SetTransform(&aMatrix); + } + + 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>()); + + // MM01 need to do line dashing as fallback stuff here now + const double fDotDashLength(nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0); + const bool bStrokeUsed(0.0 != fDotDashLength); + assert(!bStrokeUsed || (bStrokeUsed && pStroke)); + + // MM01 decide if to stroke directly + static bool 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()); + const double fFactor(fLineWidth == 0 ? 1.0 : 1.0 / fLineWidth); + + for(size_t a(0); a < pStroke->size(); a++) + { + aDashArray[a] = Gdiplus::REAL((*pStroke)[a] * fFactor); + } + + aPen.SetDashCap(Gdiplus::DashCapFlat); + 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 + const bool bStrokeWasUsed(!pSystemDependentData_GraphicsPath->getStroke().empty()); + + if(bStrokeWasUsed != bStrokeUsed + || (bStrokeUsed && *pStroke != pSystemDependentData_GraphicsPath->getStroke())) + { + // data invalid, forget + pSystemDependentData_GraphicsPath.reset(); + } + } + + 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>( + ImplGetSystemDependentDataManager(), + pGraphicsPath, + bNoLineJoin, + pStroke); + } + } + + if(mrParent.getAntiAliasB2DDraw()) + { + aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); + } + else + { + aGraphics.SetSmoothingMode(Gdiplus::SmoothingModeNone); + } + + 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()); + + aGraphics.ScaleTransform( + Gdiplus::REAL(100.0) / aDpiX, + Gdiplus::REAL(100.0) / aDpiY, + Gdiplus::MatrixOrderAppend); + } + + aGraphics.DrawPath( + &aPen, + &(*pGraphicsPath)); + + return true; +} + +static void paintToGdiPlus( + Gdiplus::Graphics& rGraphics, + const SalTwoRect& rTR, + Gdiplus::Bitmap& rBitmap) +{ + // only parts of source are used + Gdiplus::PointF aDestPoints[3]; + Gdiplus::ImageAttributes aAttributes; + + // define target region as parallelogram + aDestPoints[0].X = Gdiplus::REAL(rTR.mnDestX); + aDestPoints[0].Y = Gdiplus::REAL(rTR.mnDestY); + aDestPoints[1].X = Gdiplus::REAL(rTR.mnDestX + rTR.mnDestWidth); + aDestPoints[1].Y = Gdiplus::REAL(rTR.mnDestY); + aDestPoints[2].X = Gdiplus::REAL(rTR.mnDestX); + aDestPoints[2].Y = Gdiplus::REAL(rTR.mnDestY + rTR.mnDestHeight); + + aAttributes.SetWrapMode(Gdiplus::WrapModeTileFlipXY); + + rGraphics.DrawImage( + &rBitmap, + aDestPoints, + 3, + Gdiplus::REAL(rTR.mnSrcX), + Gdiplus::REAL(rTR.mnSrcY), + Gdiplus::REAL(rTR.mnSrcWidth), + Gdiplus::REAL(rTR.mnSrcHeight), + Gdiplus::UnitPixel, + &aAttributes); +} + +static void setInterpolationMode( + Gdiplus::Graphics& rGraphics, + long rSrcWidth, + long rDestWidth, + long rSrcHeight, + long rDestHeight) +{ + const bool bSameWidth(rSrcWidth == rDestWidth); + const bool bSameHeight(rSrcHeight == rDestHeight); + + if(bSameWidth && bSameHeight) + { + rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeInvalid); + } + else if(rDestWidth > rSrcWidth && rDestHeight > rSrcHeight) + { + rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeDefault); + } + else if(rDestWidth < rSrcWidth && rDestHeight < rSrcHeight) + { + rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeBicubic); + } + else + { + rGraphics.SetInterpolationMode(Gdiplus::InterpolationModeDefault); + } +} + +bool WinSalGraphicsImpl::TryDrawBitmapGDIPlus(const SalTwoRect& rTR, const SalBitmap& rSrcBitmap) +{ + if(rTR.mnSrcWidth && rTR.mnSrcHeight && rTR.mnDestWidth && rTR.mnDestHeight) + { + assert(dynamic_cast<const WinSalBitmap*>(&rSrcBitmap)); + + const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSrcBitmap); + std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap()); + + if(aARGB.get()) + { + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + + setInterpolationMode( + aGraphics, + rTR.mnSrcWidth, + rTR.mnDestWidth, + rTR.mnSrcHeight, + rTR.mnDestHeight); + + paintToGdiPlus( + aGraphics, + rTR, + *aARGB); + + return true; + } + } + + return false; +} + +bool WinSalGraphicsImpl::blendBitmap( + const SalTwoRect&, + const SalBitmap&) +{ + return false; +} + +bool WinSalGraphicsImpl::blendAlphaBitmap( + const SalTwoRect&, + const SalBitmap&, + const SalBitmap&, + const SalBitmap&) +{ + return false; +} + +bool WinSalGraphicsImpl::drawAlphaBitmap( + const SalTwoRect& rTR, + const SalBitmap& rSrcBitmap, + const SalBitmap& rAlphaBmp) +{ + if(rTR.mnSrcWidth && rTR.mnSrcHeight && rTR.mnDestWidth && rTR.mnDestHeight) + { + assert(dynamic_cast<const WinSalBitmap*>(&rSrcBitmap)); + assert(dynamic_cast<const WinSalBitmap*>(&rAlphaBmp)); + + const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSrcBitmap); + const WinSalBitmap& rSalAlpha = static_cast< const WinSalBitmap& >(rAlphaBmp); + std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap(&rSalAlpha)); + + if(aARGB.get()) + { + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + + setInterpolationMode( + aGraphics, + rTR.mnSrcWidth, + rTR.mnDestWidth, + rTR.mnSrcHeight, + rTR.mnDestHeight); + + paintToGdiPlus( + aGraphics, + rTR, + *aARGB); + + return true; + } + } + + return false; +} + +bool WinSalGraphicsImpl::drawTransformedBitmap( + const basegfx::B2DPoint& rNull, + const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY, + const SalBitmap& rSourceBitmap, + const SalBitmap* pAlphaBitmap) +{ + assert(dynamic_cast<const WinSalBitmap*>(&rSourceBitmap)); + assert(!pAlphaBitmap || dynamic_cast<const WinSalBitmap*>(pAlphaBitmap)); + + const WinSalBitmap& rSalBitmap = static_cast< const WinSalBitmap& >(rSourceBitmap); + const WinSalBitmap* pSalAlpha = static_cast< const WinSalBitmap* >(pAlphaBitmap); + std::shared_ptr< Gdiplus::Bitmap > aARGB(rSalBitmap.ImplGetGdiPlusBitmap(pSalAlpha)); + + if(aARGB.get()) + { + const long nSrcWidth(aARGB->GetWidth()); + const long nSrcHeight(aARGB->GetHeight()); + + if(nSrcWidth && nSrcHeight) + { + const long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength())); + const long nDestHeight(basegfx::fround(basegfx::B2DVector(rY - rNull).getLength())); + + if(nDestWidth && nDestHeight) + { + Gdiplus::Graphics aGraphics(mrParent.getHDC()); + Gdiplus::PointF aDestPoints[3]; + Gdiplus::ImageAttributes aAttributes; + + setInterpolationMode( + aGraphics, + nSrcWidth, + nDestWidth, + nSrcHeight, + nDestHeight); + + // 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()); + + aAttributes.SetWrapMode(Gdiplus::WrapModeTileFlipXY); + + aGraphics.DrawImage( + aARGB.get(), + aDestPoints, + 3, + Gdiplus::REAL(0.0), + Gdiplus::REAL(0.0), + Gdiplus::REAL(nSrcWidth), + Gdiplus::REAL(nSrcHeight), + Gdiplus::UnitPixel, + &aAttributes); + } + } + + return true; + } + + return false; +} + +bool WinSalGraphicsImpl::drawGradient(const tools::PolyPolygon& /*rPolygon*/, + const Gradient& /*rGradient*/) +{ + return false; +} + +bool WinSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const +{ + static bool bAllowForTest(true); + bool bRet = false; + + switch (eType) + { + case OutDevSupportType::TransparentRect: + bRet = mrParent.mbVirDev || mrParent.mbWindow; + break; + case OutDevSupportType::B2DDraw: + bRet = bAllowForTest; + break; + default: + break; + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/gdiimpl.hxx b/vcl/win/gdi/gdiimpl.hxx new file mode 100644 index 000000000..748afbcf0 --- /dev/null +++ b/vcl/win/gdi/gdiimpl.hxx @@ -0,0 +1,250 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_VCL_WIN_GDI_GDIIMPL_HXX +#define INCLUDED_VCL_WIN_GDI_GDIIMPL_HXX + +#include <salgdiimpl.hxx> +#include <win/salgdi.h> +#include <win/wingdiimpl.hxx> + +#include <vcl/gradient.hxx> + +#include <svsys.h> +#include <ControlCacheKey.hxx> + +class WinSalGraphics; + +class WinSalGraphicsImpl : public SalGraphicsImpl, public WinSalGraphicsImplBase +{ +private: + + WinSalGraphics& mrParent; + bool mbXORMode : 1; // _every_ output with RasterOp XOR + bool mbPen : 1; // is Pen (FALSE == NULL_PEN) + HPEN mhPen; // Pen + bool mbStockPen : 1; // is Pen a stockpen + bool mbBrush : 1; // is Brush (FALSE == NULL_BRUSH) + bool mbStockBrush : 1; // is Brush a stockbrush + HBRUSH mhBrush; // Brush + COLORREF mnPenColor; // PenColor + COLORREF mnBrushColor; // BrushColor + + // remember RGB values for SetLineColor/SetFillColor + Color maLineColor; + Color maFillColor; + + bool TryDrawBitmapGDIPlus(const SalTwoRect& rTR, const SalBitmap& rSrcBitmap); + void DrawPixelImpl(long nX, long nY, COLORREF crColor); + + HPEN SearchStockPen(COLORREF nPenColor); + HPEN MakePen(Color nColor); + void ResetPen(HPEN hNewPen); + + HBRUSH SearchStockBrush(COLORREF nBrushColor); + HBRUSH MakeBrush(Color nColor); + void ResetBrush(HBRUSH hNewBrush); +public: + + explicit WinSalGraphicsImpl(WinSalGraphics& rParent); + + virtual ~WinSalGraphicsImpl() override; + + virtual void Init() override; + + virtual void freeResources() override; + + virtual OUString getRenderBackendName() const override { return "gdi"; } + + virtual bool setClipRegion( const vcl::Region& ) override; + // + // get the depth of the device + virtual sal_uInt16 GetBitCount() const override; + + // get the width of the device + virtual long GetGraphicsWidth() const override; + + // set the clip region to empty + virtual void ResetClipRegion() override; + + // set the line color to transparent (= don't draw lines) + + virtual void SetLineColor() override; + + // set the line color to a specific color + virtual void SetLineColor( Color nColor ) override; + + // set the fill color to transparent (= don't fill) + virtual void SetFillColor() override; + + // set the fill color to a specific color, shapes will be + // filled accordingly + virtual void SetFillColor( Color nColor ) override; + + // enable/disable XOR drawing + virtual void SetXORMode( bool bSet, bool bInvertOnly ) override; + + // set line color for raster operations + virtual void SetROPLineColor( SalROPColor nROPColor ) override; + + // set fill color for raster operations + virtual void SetROPFillColor( SalROPColor nROPColor ) override; + + // draw --> LineColor and FillColor and RasterOp and ClipRegion + virtual void drawPixel( long nX, long nY ) override; + virtual void drawPixel( long nX, long nY, Color nColor ) override; + + virtual void drawLine( long nX1, long nY1, long nX2, long nY2 ) override; + + virtual void drawRect( long nX, long nY, long nWidth, long nHeight ) override; + + virtual void drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) override; + + virtual void drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) override; + + virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, PCONSTSALPOINT* pPtAry ) override; + + virtual bool drawPolyPolygon( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon&, + double fTransparency) override; + + virtual bool drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon&, + double fTransparency, + double fLineWidth, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin, + css::drawing::LineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) override; + + virtual bool drawPolyLineBezier( + sal_uInt32 nPoints, + const SalPoint* pPtAry, + const PolyFlags* pFlgAry ) override; + + virtual bool drawPolygonBezier( + sal_uInt32 nPoints, + const SalPoint* pPtAry, + const PolyFlags* pFlgAry ) override; + + virtual bool drawPolyPolygonBezier( + sal_uInt32 nPoly, + const sal_uInt32* pPoints, + const SalPoint* const* pPtAry, + const PolyFlags* const* pFlgAry ) override; + + // CopyArea --> No RasterOp, but ClipRegion + virtual void copyArea( + long nDestX, long nDestY, + long nSrcX, long nSrcY, + long nSrcWidth, long nSrcHeight, bool bWindowInvalidate ) override; + + // CopyBits and DrawBitmap --> RasterOp and ClipRegion + // CopyBits() --> pSrcGraphics == NULL, then CopyBits on same Graphics + virtual void copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) override; + + virtual void drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap ) override; + + virtual void drawBitmap( + const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + const SalBitmap& rMaskBitmap ) override; + + virtual void drawMask( + const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + Color nMaskColor ) override; + + virtual std::shared_ptr<SalBitmap> getBitmap( long nX, long nY, long nWidth, long nHeight ) override; + + virtual Color getPixel( long nX, long nY ) override; + + // invert --> ClipRegion (only Windows or VirDevs) + virtual void invert( + long nX, long nY, + long nWidth, long nHeight, + SalInvert nFlags) override; + + virtual void invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags ) override; + + virtual bool drawEPS( + long nX, long nY, + long nWidth, long nHeight, + void* pPtr, + sal_uInt32 nSize ) override; + + virtual bool blendBitmap( + const SalTwoRect&, + const SalBitmap& rBitmap ) override; + + virtual bool blendAlphaBitmap( + const SalTwoRect&, + const SalBitmap& rSrcBitmap, + const SalBitmap& rMaskBitmap, + const SalBitmap& rAlphaBitmap ) override; + + /** Render bitmap with alpha channel + + @param rSourceBitmap + Source bitmap to blit + + @param rAlphaBitmap + Alpha channel to use for blitting + + @return true, if the operation succeeded, and false + otherwise. In this case, clients should try to emulate alpha + compositing themselves + */ + virtual bool drawAlphaBitmap( + const SalTwoRect&, + const SalBitmap& rSourceBitmap, + const SalBitmap& rAlphaBitmap ) override; + + /** draw transformed bitmap (maybe with alpha) where Null, X, Y define the coordinate system */ + virtual bool drawTransformedBitmap( + const basegfx::B2DPoint& rNull, + const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY, + const SalBitmap& rSourceBitmap, + const SalBitmap* pAlphaBitmap) override; + + /** Render solid rectangle with given transparency + + @param nTransparency + Transparency value (0-255) to use. 0 blits and opaque, 255 a + fully transparent rectangle + */ + virtual bool drawAlphaRect( + long nX, long nY, + long nWidth, long nHeight, + sal_uInt8 nTransparency ) override; + + + virtual bool drawGradient(const tools::PolyPolygon& rPolygon, + const Gradient& rGradient) override; + + virtual bool supportsOperation(OutDevSupportType eType) const override; +}; + +#endif // INCLUDED_VCL_WIN_GDI_GDIIMPL_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/salbmp.cxx b/vcl/win/gdi/salbmp.cxx new file mode 100644 index 000000000..f2b21a666 --- /dev/null +++ b/vcl/win/gdi/salbmp.cxx @@ -0,0 +1,1067 @@ +/* + * 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 <svsys.h> +#include <vcl/bitmap.hxx> +#include <vcl/BitmapAccessMode.hxx> +#include <vcl/BitmapBuffer.hxx> +#include <vcl/BitmapPalette.hxx> +#include <vcl/ColorMask.hxx> +#include <vcl/Scanline.hxx> +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <win/wincomp.hxx> +#include <win/salgdi.h> +#include <win/saldata.hxx> +#include <win/salbmp.h> +#include <string.h> +#include <vcl/timer.hxx> +#include <cppuhelper/basemutex.hxx> +#include <sal/log.hxx> +#include <tools/helpers.hxx> +#include <map> + +#if defined _MSC_VER +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#endif + +#include <prewin.h> +#include <gdiplus.h> +#include <postwin.h> + +#if defined _MSC_VER +#undef min +#undef max +#endif + +static void ImplSetPixel4( sal_uInt8* pScanline, long nX, const BYTE cIndex ) +{ + BYTE& rByte = pScanline[ nX >> 1 ]; + + if ( nX & 1 ) + { + rByte &= 0xf0; + rByte |= cIndex & 0x0f; + } + else + { + rByte &= 0x0f; + rByte |= cIndex << 4; + } +} + +WinSalBitmap::WinSalBitmap() +: SalBitmap(), + basegfx::SystemDependentDataHolder(), + maSize(), + mhDIB(nullptr), + mhDDB(nullptr), + mnBitCount(0) +{ +} + +WinSalBitmap::~WinSalBitmap() +{ + Destroy(); +} + +void WinSalBitmap::Destroy() +{ + if( mhDIB ) + GlobalFree( mhDIB ); + else if( mhDDB ) + DeleteObject( mhDDB ); + + maSize = Size(); + mnBitCount = 0; +} + +namespace { + +class SystemDependentData_GdiPlusBitmap : public basegfx::SystemDependentData +{ +private: + std::shared_ptr<Gdiplus::Bitmap> mpGdiPlusBitmap; + const WinSalBitmap* mpAssociatedAlpha; + +public: + SystemDependentData_GdiPlusBitmap( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + const std::shared_ptr<Gdiplus::Bitmap>& rGdiPlusBitmap, + const WinSalBitmap* pAssociatedAlpha); + + const WinSalBitmap* getAssociatedAlpha() const { return mpAssociatedAlpha; } + const std::shared_ptr<Gdiplus::Bitmap>& getGdiPlusBitmap() const { return mpGdiPlusBitmap; } + + virtual sal_Int64 estimateUsageInBytes() const override; +}; + +} + +SystemDependentData_GdiPlusBitmap::SystemDependentData_GdiPlusBitmap( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + const std::shared_ptr<Gdiplus::Bitmap>& rGdiPlusBitmap, + const WinSalBitmap* pAssociatedAlpha) +: basegfx::SystemDependentData(rSystemDependentDataManager), + mpGdiPlusBitmap(rGdiPlusBitmap), + mpAssociatedAlpha(pAssociatedAlpha) +{ +} + +sal_Int64 SystemDependentData_GdiPlusBitmap::estimateUsageInBytes() const +{ + sal_Int64 nRetval(0); + + if(mpGdiPlusBitmap) + { + const UINT nWidth(mpGdiPlusBitmap->GetWidth()); + const UINT nHeight(mpGdiPlusBitmap->GetHeight()); + + if(0 != nWidth && 0 != nHeight) + { + nRetval = nWidth * nHeight; + + switch(mpGdiPlusBitmap->GetPixelFormat()) + { + case PixelFormat1bppIndexed: + nRetval /= 8; + break; + case PixelFormat4bppIndexed: + nRetval /= 4; + break; + case PixelFormat16bppGrayScale: + case PixelFormat16bppRGB555: + case PixelFormat16bppRGB565: + case PixelFormat16bppARGB1555: + nRetval *= 2; + break; + case PixelFormat24bppRGB: + nRetval *= 3; + break; + case PixelFormat32bppRGB: + case PixelFormat32bppARGB: + case PixelFormat32bppPARGB: + case PixelFormat32bppCMYK: + nRetval *= 4; + break; + case PixelFormat48bppRGB: + nRetval *= 6; + break; + case PixelFormat64bppARGB: + case PixelFormat64bppPARGB: + nRetval *= 8; + break; + default: + case PixelFormat8bppIndexed: + break; + } + } + } + + return nRetval; +} + +std::shared_ptr< Gdiplus::Bitmap > WinSalBitmap::ImplGetGdiPlusBitmap(const WinSalBitmap* pAlphaSource) const +{ + std::shared_ptr< Gdiplus::Bitmap > aRetval; + + // try to access buffered data + std::shared_ptr<SystemDependentData_GdiPlusBitmap> pSystemDependentData_GdiPlusBitmap( + getSystemDependentData<SystemDependentData_GdiPlusBitmap>()); + + if(pSystemDependentData_GdiPlusBitmap) + { + // check data validity + if(pSystemDependentData_GdiPlusBitmap->getAssociatedAlpha() != pAlphaSource + || 0 == maSize.Width() + || 0 == maSize.Height()) + { + // #122350# if associated alpha with which the GDIPlus was constructed has changed + // it is necessary to remove it from buffer, reset reference to it and reconstruct + // data invalid, forget + pSystemDependentData_GdiPlusBitmap.reset(); + } + } + + if(pSystemDependentData_GdiPlusBitmap) + { + // use from buffer + aRetval = pSystemDependentData_GdiPlusBitmap->getGdiPlusBitmap(); + } + else if(!maSize.IsEmpty()) + { + // create and set data + const WinSalBitmap* pAssociatedAlpha(nullptr); + + if(pAlphaSource) + { + aRetval.reset(const_cast< WinSalBitmap* >(this)->ImplCreateGdiPlusBitmap(*pAlphaSource)); + pAssociatedAlpha = pAlphaSource; + } + else + { + aRetval.reset(const_cast< WinSalBitmap* >(this)->ImplCreateGdiPlusBitmap()); + pAssociatedAlpha = nullptr; + } + + // add to buffering mechanism + addOrReplaceSystemDependentData<SystemDependentData_GdiPlusBitmap>( + ImplGetSystemDependentDataManager(), + aRetval, + pAssociatedAlpha); + } + + return aRetval; +} + +Gdiplus::Bitmap* WinSalBitmap::ImplCreateGdiPlusBitmap() +{ + Gdiplus::Bitmap* pRetval(nullptr); + WinSalBitmap* pSalRGB = this; + WinSalBitmap* pExtraWinSalRGB = nullptr; + + if(!pSalRGB->ImplGethDIB()) + { + // we need DIB for success with AcquireBuffer, create a replacement WinSalBitmap + pExtraWinSalRGB = new WinSalBitmap(); + pExtraWinSalRGB->Create(*pSalRGB, pSalRGB->GetBitCount()); + pSalRGB = pExtraWinSalRGB; + } + + BitmapBuffer* pRGB = pSalRGB->AcquireBuffer(BitmapAccessMode::Read); + std::unique_ptr<BitmapBuffer> pExtraRGB; + + if(pRGB && ScanlineFormat::N24BitTcBgr != RemoveScanline(pRGB->mnFormat)) + { + // convert source bitmap to BMP_FORMAT_24BIT_TC_BGR format if not yet in that format + SalTwoRect aSalTwoRect(0, 0, pRGB->mnWidth, pRGB->mnHeight, 0, 0, pRGB->mnWidth, pRGB->mnHeight); + pExtraRGB = StretchAndConvert( + *pRGB, + aSalTwoRect, + ScanlineFormat::N24BitTcBgr); + + pSalRGB->ReleaseBuffer(pRGB, BitmapAccessMode::Write); + pRGB = pExtraRGB.get(); + } + + if(pRGB + && pRGB->mnWidth > 0 + && pRGB->mnHeight > 0 + && ScanlineFormat::N24BitTcBgr == RemoveScanline(pRGB->mnFormat)) + { + const sal_uInt32 nW(pRGB->mnWidth); + const sal_uInt32 nH(pRGB->mnHeight); + + pRetval = new Gdiplus::Bitmap(nW, nH, PixelFormat24bppRGB); + + if ( pRetval->GetLastStatus() == Gdiplus::Ok ) + { + sal_uInt8* pSrcRGB(pRGB->mpBits); + const sal_uInt32 nExtraRGB(pRGB->mnScanlineSize - (nW * 3)); + const bool bTopDown(pRGB->mnFormat & ScanlineFormat::TopDown); + const Gdiplus::Rect aAllRect(0, 0, nW, nH); + Gdiplus::BitmapData aGdiPlusBitmapData; + pRetval->LockBits(&aAllRect, Gdiplus::ImageLockModeWrite, PixelFormat24bppRGB, &aGdiPlusBitmapData); + + // copy data to Gdiplus::Bitmap; format is BGR here in both cases, so memcpy is possible + for(sal_uInt32 y(0); y < nH; y++) + { + const sal_uInt32 nYInsert(bTopDown ? y : nH - y - 1); + sal_uInt8* targetPixels = static_cast<sal_uInt8*>(aGdiPlusBitmapData.Scan0) + (nYInsert * aGdiPlusBitmapData.Stride); + + memcpy(targetPixels, pSrcRGB, nW * 3); + pSrcRGB += nW * 3 + nExtraRGB; + } + + pRetval->UnlockBits(&aGdiPlusBitmapData); + } + else + { + delete pRetval; + pRetval = nullptr; + } + } + + if(pExtraRGB) + { + // #i123478# shockingly, BitmapBuffer does not free the memory it is controlling + // in its destructor, this *has to be done by hand*. Doing it here now + delete[] pExtraRGB->mpBits; + pExtraRGB.reset(); + } + else + { + pSalRGB->ReleaseBuffer(pRGB, BitmapAccessMode::Read); + } + + if(pExtraWinSalRGB) + { + delete pExtraWinSalRGB; + } + + return pRetval; +} + +Gdiplus::Bitmap* WinSalBitmap::ImplCreateGdiPlusBitmap(const WinSalBitmap& rAlphaSource) +{ + Gdiplus::Bitmap* pRetval(nullptr); + WinSalBitmap* pSalRGB = this; + WinSalBitmap* pExtraWinSalRGB = nullptr; + + if(!pSalRGB->ImplGethDIB()) + { + // we need DIB for success with AcquireBuffer, create a replacement WinSalBitmap + pExtraWinSalRGB = new WinSalBitmap(); + pExtraWinSalRGB->Create(*pSalRGB, pSalRGB->GetBitCount()); + pSalRGB = pExtraWinSalRGB; + } + + BitmapBuffer* pRGB = pSalRGB->AcquireBuffer(BitmapAccessMode::Read); + std::unique_ptr<BitmapBuffer> pExtraRGB; + + if(pRGB && ScanlineFormat::N24BitTcBgr != RemoveScanline(pRGB->mnFormat)) + { + // convert source bitmap to canlineFormat::N24BitTcBgr format if not yet in that format + SalTwoRect aSalTwoRect(0, 0, pRGB->mnWidth, pRGB->mnHeight, 0, 0, pRGB->mnWidth, pRGB->mnHeight); + pExtraRGB = StretchAndConvert( + *pRGB, + aSalTwoRect, + ScanlineFormat::N24BitTcBgr); + + pSalRGB->ReleaseBuffer(pRGB, BitmapAccessMode::Read); + pRGB = pExtraRGB.get(); + } + + WinSalBitmap* pSalA = const_cast< WinSalBitmap* >(&rAlphaSource); + WinSalBitmap* pExtraWinSalA = nullptr; + + if(!pSalA->ImplGethDIB()) + { + // we need DIB for success with AcquireBuffer, create a replacement WinSalBitmap + pExtraWinSalA = new WinSalBitmap(); + pExtraWinSalA->Create(*pSalA, pSalA->GetBitCount()); + pSalA = pExtraWinSalA; + } + + BitmapBuffer* pA = pSalA->AcquireBuffer(BitmapAccessMode::Read); + std::unique_ptr<BitmapBuffer> pExtraA; + + if(pA && ScanlineFormat::N8BitPal != RemoveScanline(pA->mnFormat)) + { + // convert alpha bitmap to ScanlineFormat::N8BitPal format if not yet in that format + SalTwoRect aSalTwoRect(0, 0, pA->mnWidth, pA->mnHeight, 0, 0, pA->mnWidth, pA->mnHeight); + const BitmapPalette& rTargetPalette = Bitmap::GetGreyPalette(256); + + pExtraA = StretchAndConvert( + *pA, + aSalTwoRect, + ScanlineFormat::N8BitPal, + &rTargetPalette); + + pSalA->ReleaseBuffer(pA, BitmapAccessMode::Read); + pA = pExtraA.get(); + } + + if(pRGB + && pA + && pRGB->mnWidth > 0 + && pRGB->mnHeight > 0 + && pRGB->mnWidth == pA->mnWidth + && pRGB->mnHeight == pA->mnHeight + && ScanlineFormat::N24BitTcBgr == RemoveScanline(pRGB->mnFormat) + && ScanlineFormat::N8BitPal == RemoveScanline(pA->mnFormat)) + { + // we have alpha and bitmap in known formats, create GdiPlus Bitmap as 32bit ARGB + const sal_uInt32 nW(pRGB->mnWidth); + const sal_uInt32 nH(pRGB->mnHeight); + + pRetval = new Gdiplus::Bitmap(nW, nH, PixelFormat32bppARGB); + + if ( pRetval->GetLastStatus() == Gdiplus::Ok ) // 2nd place to secure with new Gdiplus::Bitmap + { + sal_uInt8* pSrcRGB(pRGB->mpBits); + sal_uInt8* pSrcA(pA->mpBits); + const sal_uInt32 nExtraRGB(pRGB->mnScanlineSize - (nW * 3)); + const sal_uInt32 nExtraA(pA->mnScanlineSize - nW); + const bool bTopDown(pRGB->mnFormat & ScanlineFormat::TopDown); + const Gdiplus::Rect aAllRect(0, 0, nW, nH); + Gdiplus::BitmapData aGdiPlusBitmapData; + pRetval->LockBits(&aAllRect, Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &aGdiPlusBitmapData); + + // copy data to Gdiplus::Bitmap; format is BGRA; need to mix BGR from Bitmap and + // A from alpha, so inner loop is needed (who invented BitmapEx..?) + for(sal_uInt32 y(0); y < nH; y++) + { + const sal_uInt32 nYInsert(bTopDown ? y : nH - y - 1); + sal_uInt8* targetPixels = static_cast<sal_uInt8*>(aGdiPlusBitmapData.Scan0) + (nYInsert * aGdiPlusBitmapData.Stride); + + for(sal_uInt32 x(0); x < nW; x++) + { + *targetPixels++ = *pSrcRGB++; + *targetPixels++ = *pSrcRGB++; + *targetPixels++ = *pSrcRGB++; + *targetPixels++ = 0xff - *pSrcA++; + } + + pSrcRGB += nExtraRGB; + pSrcA += nExtraA; + } + + pRetval->UnlockBits(&aGdiPlusBitmapData); + } + else + { + delete pRetval; + pRetval = nullptr; + } + } + + if(pExtraA) + { + // #i123478# shockingly, BitmapBuffer does not free the memory it is controlling + // in its destructor, this *has to be done handish*. Doing it here now + delete[] pExtraA->mpBits; + pExtraA.reset(); + } + else + { + pSalA->ReleaseBuffer(pA, BitmapAccessMode::Read); + } + + if(pExtraWinSalA) + { + delete pExtraWinSalA; + } + + if(pExtraRGB) + { + // #i123478# shockingly, BitmapBuffer does not free the memory it is controlling + // in its destructor, this *has to be done by hand*. Doing it here now + delete[] pExtraRGB->mpBits; + pExtraRGB.reset(); + } + else + { + pSalRGB->ReleaseBuffer(pRGB, BitmapAccessMode::Read); + } + + if(pExtraWinSalRGB) + { + delete pExtraWinSalRGB; + } + + return pRetval; +} + +bool WinSalBitmap::Create( HANDLE hBitmap, bool bDIB, bool bCopyHandle ) +{ + bool bRet = true; + + if( bDIB ) + mhDIB = static_cast<HGLOBAL>( bCopyHandle ? ImplCopyDIBOrDDB( hBitmap, true ) : hBitmap ); + else + mhDDB = static_cast<HBITMAP>( bCopyHandle ? ImplCopyDIBOrDDB( hBitmap, false ) : hBitmap ); + + if( mhDIB ) + { + PBITMAPINFOHEADER pBIH = static_cast<PBITMAPINFOHEADER>(GlobalLock( mhDIB )); + + maSize = Size( pBIH->biWidth, pBIH->biHeight ); + mnBitCount = pBIH->biBitCount; + + if( mnBitCount ) + mnBitCount = ( mnBitCount <= 1 ) ? 1 : ( mnBitCount <= 4 ) ? 4 : ( mnBitCount <= 8 ) ? 8 : 24; + + GlobalUnlock( mhDIB ); + } + else if( mhDDB ) + { + BITMAP aDDBInfo; + + if( GetObjectW( mhDDB, sizeof( aDDBInfo ), &aDDBInfo ) ) + { + maSize = Size( aDDBInfo.bmWidth, aDDBInfo.bmHeight ); + mnBitCount = aDDBInfo.bmPlanes * aDDBInfo.bmBitsPixel; + + if( mnBitCount ) + { + mnBitCount = ( mnBitCount <= 1 ) ? 1 : + ( mnBitCount <= 4 ) ? 4 : + ( mnBitCount <= 8 ) ? 8 : 24; + } + } + else + { + mhDDB = nullptr; + bRet = false; + } + } + else + bRet = false; + + return bRet; +} + +bool WinSalBitmap::Create( const Size& rSize, sal_uInt16 nBitCount, const BitmapPalette& rPal ) +{ + bool bRet = false; + + mhDIB = ImplCreateDIB( rSize, nBitCount, rPal ); + + if( mhDIB ) + { + maSize = rSize; + mnBitCount = nBitCount; + bRet = true; + } + + return bRet; +} + +bool WinSalBitmap::Create( const SalBitmap& rSSalBitmap ) +{ + bool bRet = false; + const WinSalBitmap& rSalBitmap = static_cast<const WinSalBitmap&>(rSSalBitmap); + + if ( rSalBitmap.mhDIB || rSalBitmap.mhDDB ) + { + HANDLE hNewHdl = ImplCopyDIBOrDDB( rSalBitmap.mhDIB ? rSalBitmap.mhDIB : rSalBitmap.mhDDB, + rSalBitmap.mhDIB != nullptr ); + + if ( hNewHdl ) + { + if( rSalBitmap.mhDIB ) + mhDIB = static_cast<HGLOBAL>(hNewHdl); + else if( rSalBitmap.mhDDB ) + mhDDB = static_cast<HBITMAP>(hNewHdl); + + maSize = rSalBitmap.maSize; + mnBitCount = rSalBitmap.mnBitCount; + + bRet = true; + } + } + + return bRet; +} + +bool WinSalBitmap::Create( const SalBitmap& rSSalBmp, SalGraphics* pSGraphics ) +{ + bool bRet = false; + + const WinSalBitmap& rSalBmp = static_cast<const WinSalBitmap&>(rSSalBmp); + WinSalGraphics* pGraphics = static_cast<WinSalGraphics*>(pSGraphics); + + if( rSalBmp.mhDIB ) + { + PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( rSalBmp.mhDIB )); + HDC hDC = pGraphics->getHDC(); + HBITMAP hNewDDB; + BITMAP aDDBInfo; + PBYTE pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize + + ImplGetDIBColorCount( rSalBmp.mhDIB ) * sizeof( RGBQUAD ); + + if( pBI->bmiHeader.biBitCount == 1 ) + { + hNewDDB = CreateBitmap( pBI->bmiHeader.biWidth, pBI->bmiHeader.biHeight, 1, 1, nullptr ); + + if( hNewDDB ) + SetDIBits( hDC, hNewDDB, 0, pBI->bmiHeader.biHeight, pBits, pBI, DIB_RGB_COLORS ); + } + else + hNewDDB = CreateDIBitmap( hDC, &pBI->bmiHeader, CBM_INIT, pBits, pBI, DIB_RGB_COLORS ); + + GlobalUnlock( rSalBmp.mhDIB ); + + if( hNewDDB && GetObjectW( hNewDDB, sizeof( aDDBInfo ), &aDDBInfo ) ) + { + mhDDB = hNewDDB; + maSize = Size( aDDBInfo.bmWidth, aDDBInfo.bmHeight ); + mnBitCount = aDDBInfo.bmPlanes * aDDBInfo.bmBitsPixel; + + bRet = true; + } + else if( hNewDDB ) + DeleteObject( hNewDDB ); + } + + return bRet; +} + +bool WinSalBitmap::Create( const SalBitmap& rSSalBmp, sal_uInt16 nNewBitCount ) +{ + bool bRet = false; + + const WinSalBitmap& rSalBmp = static_cast<const WinSalBitmap&>(rSSalBmp); + + if( rSalBmp.mhDDB ) + { + mhDIB = ImplCreateDIB( rSalBmp.maSize, nNewBitCount, BitmapPalette() ); + + if( mhDIB ) + { + PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( mhDIB )); + const int nLines = static_cast<int>(rSalBmp.maSize.Height()); + HDC hDC = GetDC( nullptr ); + PBYTE pBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize + + ImplGetDIBColorCount( mhDIB ) * sizeof( RGBQUAD ); + SalData* pSalData = GetSalData(); + HPALETTE hOldPal = nullptr; + + if ( pSalData->mhDitherPal ) + { + hOldPal = SelectPalette( hDC, pSalData->mhDitherPal, TRUE ); + RealizePalette( hDC ); + } + + if( GetDIBits( hDC, rSalBmp.mhDDB, 0, nLines, pBits, pBI, DIB_RGB_COLORS ) == nLines ) + { + GlobalUnlock( mhDIB ); + maSize = rSalBmp.maSize; + mnBitCount = nNewBitCount; + bRet = true; + } + else + { + GlobalUnlock( mhDIB ); + GlobalFree( mhDIB ); + mhDIB = nullptr; + } + + if( hOldPal ) + SelectPalette( hDC, hOldPal, TRUE ); + + ReleaseDC( nullptr, hDC ); + } + } + + return bRet; +} + +bool WinSalBitmap::Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& rBitmapCanvas, Size& /*rSize*/, bool bMask ) +{ + css::uno::Reference< css::beans::XFastPropertySet > + xFastPropertySet( rBitmapCanvas, css::uno::UNO_QUERY ); + + if( xFastPropertySet.get() ) { + css::uno::Sequence< css::uno::Any > args; + + if( xFastPropertySet->getFastPropertyValue(bMask ? 2 : 1) >>= args ) { + sal_Int64 aHBmp64; + + if( args[0] >>= aHBmp64 ) { + return Create( HBITMAP(aHBmp64), false, false ); + } + } + } + return false; +} + +sal_uInt16 WinSalBitmap::ImplGetDIBColorCount( HGLOBAL hDIB ) +{ + sal_uInt16 nColors = 0; + + if( hDIB ) + { + PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( hDIB )); + + if ( pBI->bmiHeader.biSize != sizeof( BITMAPCOREHEADER ) ) + { + if( pBI->bmiHeader.biBitCount <= 8 ) + { + if ( pBI->bmiHeader.biClrUsed ) + nColors = static_cast<sal_uInt16>(pBI->bmiHeader.biClrUsed); + else + nColors = 1 << pBI->bmiHeader.biBitCount; + } + } + else if( reinterpret_cast<PBITMAPCOREHEADER>(pBI)->bcBitCount <= 8 ) + nColors = 1 << reinterpret_cast<PBITMAPCOREHEADER>(pBI)->bcBitCount; + + GlobalUnlock( hDIB ); + } + + return nColors; +} + +HGLOBAL WinSalBitmap::ImplCreateDIB( const Size& rSize, sal_uInt16 nBits, const BitmapPalette& rPal ) +{ + SAL_WARN_IF( nBits != 1 && nBits != 4 && nBits != 8 && nBits != 24, "vcl", "Unsupported BitCount!" ); + + HGLOBAL hDIB = nullptr; + + if( rSize.IsEmpty() ) + return hDIB; + + // calculate bitmap size in Bytes + const sal_uLong nAlignedWidth4Bytes = AlignedWidth4Bytes( nBits * rSize.Width() ); + const sal_uLong nImageSize = nAlignedWidth4Bytes * rSize.Height(); + bool bOverflow = (nImageSize / nAlignedWidth4Bytes) != static_cast<sal_uLong>(rSize.Height()); + if( bOverflow ) + return hDIB; + + // allocate bitmap memory including header and palette + const sal_uInt16 nColors = (nBits <= 8) ? (1 << nBits) : 0; + const sal_uLong nHeaderSize = sizeof( BITMAPINFOHEADER ) + nColors * sizeof( RGBQUAD ); + bOverflow = (nHeaderSize + nImageSize) < nImageSize; + if( bOverflow ) + return hDIB; + + hDIB = GlobalAlloc( GHND, nHeaderSize + nImageSize ); + if( !hDIB ) + return hDIB; + + PBITMAPINFO pBI = static_cast<PBITMAPINFO>( GlobalLock( hDIB ) ); + PBITMAPINFOHEADER pBIH = reinterpret_cast<PBITMAPINFOHEADER>( pBI ); + + pBIH->biSize = sizeof( BITMAPINFOHEADER ); + pBIH->biWidth = rSize.Width(); + pBIH->biHeight = rSize.Height(); + pBIH->biPlanes = 1; + pBIH->biBitCount = nBits; + pBIH->biCompression = BI_RGB; + pBIH->biSizeImage = nImageSize; + pBIH->biXPelsPerMeter = 0; + pBIH->biYPelsPerMeter = 0; + pBIH->biClrUsed = 0; + pBIH->biClrImportant = 0; + + if( nColors ) + { + // copy the palette entries if any + const sal_uInt16 nMinCount = std::min( nColors, rPal.GetEntryCount() ); + if( nMinCount ) + memcpy( pBI->bmiColors, rPal.ImplGetColorBuffer(), nMinCount * sizeof(RGBQUAD) ); + } + + GlobalUnlock( hDIB ); + + return hDIB; +} + +HANDLE WinSalBitmap::ImplCopyDIBOrDDB( HANDLE hHdl, bool bDIB ) +{ + HANDLE hCopy = nullptr; + + if ( bDIB && hHdl ) + { + const sal_uLong nSize = GlobalSize( hHdl ); + + if ( (hCopy = GlobalAlloc( GHND, nSize )) != nullptr ) + { + memcpy( GlobalLock( hCopy ), GlobalLock( hHdl ), nSize ); + + GlobalUnlock( hCopy ); + GlobalUnlock( hHdl ); + } + } + else if ( hHdl ) + { + BITMAP aBmp; + + // find out size of source bitmap + GetObjectW( hHdl, sizeof( aBmp ), &aBmp ); + + // create destination bitmap + if ( (hCopy = CreateBitmapIndirect( &aBmp )) != nullptr ) + { + HDC hBmpDC = CreateCompatibleDC( nullptr ); + HBITMAP hBmpOld = static_cast<HBITMAP>(SelectObject( hBmpDC, hHdl )); + HDC hCopyDC = CreateCompatibleDC( hBmpDC ); + HBITMAP hCopyOld = static_cast<HBITMAP>(SelectObject( hCopyDC, hCopy )); + + BitBlt( hCopyDC, 0, 0, aBmp.bmWidth, aBmp.bmHeight, hBmpDC, 0, 0, SRCCOPY ); + + SelectObject( hCopyDC, hCopyOld ); + DeleteDC( hCopyDC ); + + SelectObject( hBmpDC, hBmpOld ); + DeleteDC( hBmpDC ); + } + } + + return hCopy; +} + +BitmapBuffer* WinSalBitmap::AcquireBuffer( BitmapAccessMode /*nMode*/ ) +{ + BitmapBuffer* pBuffer = nullptr; + + if( mhDIB ) + { + PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( mhDIB )); + PBITMAPINFOHEADER pBIH = &pBI->bmiHeader; + + if( ( pBIH->biCompression == BI_RLE4 ) || ( pBIH->biCompression == BI_RLE8 ) ) + { + Size aSizePix( pBIH->biWidth, pBIH->biHeight ); + HGLOBAL hNewDIB = ImplCreateDIB( aSizePix, pBIH->biBitCount, BitmapPalette() ); + + if( hNewDIB ) + { + PBITMAPINFO pNewBI = static_cast<PBITMAPINFO>(GlobalLock( hNewDIB )); + PBITMAPINFOHEADER pNewBIH = &pNewBI->bmiHeader; + const sal_uInt16 nColorCount = ImplGetDIBColorCount( hNewDIB ); + const sal_uLong nOffset = pBI->bmiHeader.biSize + nColorCount * sizeof( RGBQUAD ); + BYTE* pOldBits = reinterpret_cast<PBYTE>(pBI) + nOffset; + BYTE* pNewBits = reinterpret_cast<PBYTE>(pNewBI) + nOffset; + + memcpy( pNewBI, pBI, nOffset ); + pNewBIH->biCompression = 0; + ImplDecodeRLEBuffer( pOldBits, pNewBits, aSizePix, pBIH->biCompression == BI_RLE4 ); + + GlobalUnlock( mhDIB ); + GlobalFree( mhDIB ); + mhDIB = hNewDIB; + pBI = pNewBI; + pBIH = pNewBIH; + } + } + + if( pBIH->biPlanes == 1 ) + { + pBuffer = new BitmapBuffer; + + pBuffer->mnFormat = pBIH->biBitCount == 1 ? ScanlineFormat::N1BitMsbPal : + pBIH->biBitCount == 4 ? ScanlineFormat::N4BitMsnPal : + pBIH->biBitCount == 8 ? ScanlineFormat::N8BitPal : + pBIH->biBitCount == 24 ? ScanlineFormat::N24BitTcBgr : + pBIH->biBitCount == 32 ? ScanlineFormat::N32BitTcMask : + ScanlineFormat::NONE; + + if( RemoveScanline( pBuffer->mnFormat ) != ScanlineFormat::NONE ) + { + pBuffer->mnWidth = maSize.Width(); + pBuffer->mnHeight = maSize.Height(); + pBuffer->mnScanlineSize = AlignedWidth4Bytes( maSize.Width() * pBIH->biBitCount ); + pBuffer->mnBitCount = static_cast<sal_uInt16>(pBIH->biBitCount); + + if( pBuffer->mnBitCount <= 8 ) + { + const sal_uInt16 nPalCount = ImplGetDIBColorCount( mhDIB ); + + pBuffer->maPalette.SetEntryCount( nPalCount ); + memcpy( pBuffer->maPalette.ImplGetColorBuffer(), pBI->bmiColors, nPalCount * sizeof( RGBQUAD ) ); + pBuffer->mpBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize + nPalCount * sizeof( RGBQUAD ); + } + else if( ( pBIH->biBitCount == 16 ) || ( pBIH->biBitCount == 32 ) ) + { + sal_uLong nOffset = 0; + + if( pBIH->biCompression == BI_BITFIELDS ) + { + nOffset = 3 * sizeof( RGBQUAD ); + ColorMaskElement aRedMask(*reinterpret_cast<UINT32*>(&pBI->bmiColors[ 0 ])); + aRedMask.CalcMaskShift(); + ColorMaskElement aGreenMask(*reinterpret_cast<UINT32*>(&pBI->bmiColors[ 1 ])); + aGreenMask.CalcMaskShift(); + ColorMaskElement aBlueMask(*reinterpret_cast<UINT32*>(&pBI->bmiColors[ 2 ])); + aBlueMask.CalcMaskShift(); + pBuffer->maColorMask = ColorMask(aRedMask, aGreenMask, aBlueMask); + } + else if( pBIH->biBitCount == 16 ) + { + ColorMaskElement aRedMask(0x00007c00UL); + aRedMask.CalcMaskShift(); + ColorMaskElement aGreenMask(0x000003e0UL); + aGreenMask.CalcMaskShift(); + ColorMaskElement aBlueMask(0x0000001fUL); + aBlueMask.CalcMaskShift(); + pBuffer->maColorMask = ColorMask(aRedMask, aGreenMask, aBlueMask); + } + else + { + ColorMaskElement aRedMask(0x00ff0000UL); + aRedMask.CalcMaskShift(); + ColorMaskElement aGreenMask(0x0000ff00UL); + aGreenMask.CalcMaskShift(); + ColorMaskElement aBlueMask(0x000000ffUL); + aBlueMask.CalcMaskShift(); + pBuffer->maColorMask = ColorMask(aRedMask, aGreenMask, aBlueMask); + } + + pBuffer->mpBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize + nOffset; + } + else + pBuffer->mpBits = reinterpret_cast<PBYTE>(pBI) + pBI->bmiHeader.biSize; + } + else + { + GlobalUnlock( mhDIB ); + delete pBuffer; + pBuffer = nullptr; + } + } + else + GlobalUnlock( mhDIB ); + } + + return pBuffer; +} + +void WinSalBitmap::ReleaseBuffer( BitmapBuffer* pBuffer, BitmapAccessMode nMode ) +{ + if( pBuffer ) + { + if( mhDIB ) + { + if( nMode == BitmapAccessMode::Write && !!pBuffer->maPalette ) + { + PBITMAPINFO pBI = static_cast<PBITMAPINFO>(GlobalLock( mhDIB )); + const sal_uInt16 nCount = pBuffer->maPalette.GetEntryCount(); + const sal_uInt16 nDIBColorCount = ImplGetDIBColorCount( mhDIB ); + memcpy( pBI->bmiColors, pBuffer->maPalette.ImplGetColorBuffer(), std::min( nDIBColorCount, nCount ) * sizeof( RGBQUAD ) ); + GlobalUnlock( mhDIB ); + } + + GlobalUnlock( mhDIB ); + } + + delete pBuffer; + } + if( nMode == BitmapAccessMode::Write ) + InvalidateChecksum(); +} + +void WinSalBitmap::ImplDecodeRLEBuffer( const BYTE* pSrcBuf, BYTE* pDstBuf, + const Size& rSizePixel, bool bRLE4 ) +{ + sal_uInt8 const * pRLE = pSrcBuf; + sal_uInt8* pDIB = pDstBuf; + sal_uInt8* pRow = pDstBuf; + sal_uLong nWidthAl = AlignedWidth4Bytes( rSizePixel.Width() * ( bRLE4 ? 4UL : 8UL ) ); + sal_uInt8* pLast = pDIB + rSizePixel.Height() * nWidthAl - 1; + sal_uLong nCountByte; + sal_uLong nRunByte; + sal_uLong i; + BYTE cTmp; + bool bEndDecoding = false; + + if( pRLE && pDIB ) + { + sal_uLong nX = 0; + do + { + if( ( nCountByte = *pRLE++ ) == 0 ) + { + nRunByte = *pRLE++; + + if( nRunByte > 2 ) + { + if( bRLE4 ) + { + nCountByte = nRunByte >> 1; + + for( i = 0; i < nCountByte; i++ ) + { + cTmp = *pRLE++; + ImplSetPixel4( pDIB, nX++, cTmp >> 4 ); + ImplSetPixel4( pDIB, nX++, cTmp & 0x0f ); + } + + if( nRunByte & 1 ) + ImplSetPixel4( pDIB, nX++, *pRLE++ >> 4 ); + + if( ( ( nRunByte + 1 ) >> 1 ) & 1 ) + pRLE++; + } + else + { + memcpy( &pDIB[ nX ], pRLE, nRunByte ); + pRLE += nRunByte; + nX += nRunByte; + + if( nRunByte & 1 ) + pRLE++; + } + } + else if( !nRunByte ) + { + pDIB = ( pRow += nWidthAl ); + nX = 0; + } + else if( nRunByte == 1 ) + bEndDecoding = true; + else + { + nX += *pRLE++; + pDIB = ( pRow += ( *pRLE++ ) * nWidthAl ); + } + } + else + { + cTmp = *pRLE++; + + if( bRLE4 ) + { + nRunByte = nCountByte >> 1; + + for( i = 0; i < nRunByte; i++ ) + { + ImplSetPixel4( pDIB, nX++, cTmp >> 4 ); + ImplSetPixel4( pDIB, nX++, cTmp & 0x0f ); + } + + if( nCountByte & 1 ) + ImplSetPixel4( pDIB, nX++, cTmp >> 4 ); + } + else + { + for( i = 0; i < nCountByte; i++ ) + pDIB[ nX++ ] = cTmp; + } + } + } + while( !bEndDecoding && ( pDIB <= pLast ) ); + } +} + +bool WinSalBitmap::GetSystemData( BitmapSystemData& rData ) +{ + bool bRet = false; + if( mhDIB || mhDDB ) + { + bRet = true; + rData.pDIB = mhDIB; + const Size& rSize = GetSize (); + rData.mnWidth = rSize.Width(); + rData.mnHeight = rSize.Height(); + } + return bRet; +} + +bool WinSalBitmap::ScalingSupported() const +{ + return false; +} + +bool WinSalBitmap::Scale( const double& /*rScaleX*/, const double& /*rScaleY*/, BmpScaleFlag /*nScaleFlag*/ ) +{ + return false; +} + +bool WinSalBitmap::Replace( const Color& /*rSearchColor*/, const Color& /*rReplaceColor*/, sal_uInt8 /*nTol*/ ) +{ + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/salfont.cxx b/vcl/win/gdi/salfont.cxx new file mode 100644 index 000000000..1ed293888 --- /dev/null +++ b/vcl/win/gdi/salfont.cxx @@ -0,0 +1,1766 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/types.h> +#include <config_folders.h> + +#include <algorithm> +#include <map> +#include <memory> +#include <mutex> +#include <set> +#include <string.h> +#include <svsys.h> +#include <vector> + +#include <o3tl/lru_map.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <i18nlangtag/mslangid.hxx> +#include <osl/file.hxx> +#include <osl/process.h> +#include <rtl/bootstrap.hxx> +#include <rtl/tencinfo.h> +#include <sal/log.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <tools/helpers.hxx> +#include <tools/stream.hxx> +#include <tools/urlobj.hxx> +#include <unotools/fontcfg.hxx> +#include <vcl/settings.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/metric.hxx> +#include <vcl/fontcharmap.hxx> +#include <vcl/opengl/OpenGLWrapper.hxx> +#include <comphelper/scopeguard.hxx> + +#include <fontsubset.hxx> +#include <outdev.h> +#include <PhysicalFontCollection.hxx> +#include <PhysicalFontFace.hxx> +#include <sft.hxx> +#include <win/saldata.hxx> +#include <win/salgdi.h> +#include <win/winlayout.hxx> +#include <win/wingdiimpl.hxx> +#include <impfontcharmap.hxx> +#include <impfontmetricdata.hxx> +#include <impglyphitem.hxx> + +using namespace vcl; + +static FIXED FixedFromDouble( double d ) +{ + const long l = static_cast<long>( d * 65536. ); + return *reinterpret_cast<FIXED const *>(&l); +} + +static int IntTimes256FromFixed(FIXED f) +{ + int nFixedTimes256 = (f.value << 8) + ((f.fract+0x80) >> 8); + return nFixedTimes256; +} + +namespace { + +// raw font data with a scoped lifetime +class RawFontData +{ +public: + explicit RawFontData( HDC, DWORD nTableTag=0 ); + const unsigned char* get() const { return mpRawBytes.get(); } + const unsigned char* steal() { return mpRawBytes.release(); } + int size() const { return mnByteCount; } + +private: + std::unique_ptr<unsigned char[]> mpRawBytes; + unsigned mnByteCount; +}; + +} + +RawFontData::RawFontData( HDC hDC, DWORD nTableTag ) +: mnByteCount( 0 ) +{ + // get required size in bytes + mnByteCount = ::GetFontData( hDC, nTableTag, 0, nullptr, 0 ); + if (mnByteCount == GDI_ERROR) + mnByteCount = 0; + if (!mnByteCount) + return; + + // allocate the array + mpRawBytes.reset(new unsigned char[ mnByteCount ]); + + // get raw data in chunks small enough for GetFontData() + unsigned nRawDataOfs = 0; + DWORD nMaxChunkSize = 0x100000; + for(;;) + { + // calculate remaining raw data to get + DWORD nFDGet = mnByteCount - nRawDataOfs; + if( nFDGet <= 0 ) + break; + // #i56745# limit GetFontData requests + if( nFDGet > nMaxChunkSize ) + nFDGet = nMaxChunkSize; + const DWORD nFDGot = ::GetFontData( hDC, nTableTag, nRawDataOfs, + mpRawBytes.get() + nRawDataOfs, nFDGet ); + if( !nFDGot ) + break; + else if( nFDGot != GDI_ERROR ) + nRawDataOfs += nFDGot; + else + { + // was the chunk too big? reduce it + nMaxChunkSize /= 2; + if( nMaxChunkSize < 0x10000 ) + break; + } + } + + // cleanup if the raw data is incomplete + if( nRawDataOfs != mnByteCount ) + { + mpRawBytes.reset(); + // mnByteCount must correspond to mpRawBytes length + SAL_WARN( "vcl", "Raw data of font is incomplete: " << nRawDataOfs << " byte(s) found whereas " << mnByteCount << " byte(s) expected!" ); + mnByteCount = 0; + } +} + +// platform specific font substitution hooks for glyph fallback enhancement + +namespace { + +class WinPreMatchFontSubstititution +: public ImplPreMatchFontSubstitution +{ +public: + bool FindFontSubstitute(FontSelectPattern&) const override; +}; + +class WinGlyphFallbackSubstititution +: public ImplGlyphFallbackFontSubstitution +{ +public: + explicit WinGlyphFallbackSubstititution() + : mhDC(GetDC(nullptr)) + { + }; + + ~WinGlyphFallbackSubstititution() override + { + ReleaseDC(nullptr, mhDC); + }; + + bool FindFontSubstitute(FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString& rMissingChars) const override; +private: + HDC mhDC; + bool HasMissingChars(PhysicalFontFace*, OUString& rMissingChars) const; +}; + +} + +// does a font face hold the given missing characters? +bool WinGlyphFallbackSubstititution::HasMissingChars(PhysicalFontFace* pFace, OUString& rMissingChars) const +{ + WinFontFace* pWinFont = static_cast< WinFontFace* >(pFace); + FontCharMapRef xFontCharMap = pWinFont->GetFontCharMap(); + if( !xFontCharMap.is() ) + { + // construct a Size structure as the parameter of constructor of class FontSelectPattern + const Size aSize( pFace->GetWidth(), pFace->GetHeight() ); + // create a FontSelectPattern object for getting s LOGFONT + const FontSelectPattern aFSD( *pFace, aSize, static_cast<float>(aSize.Height()), 0, false ); + // construct log font + LOGFONTW aLogFont; + ImplGetLogFontFromFontSelect( mhDC, aFSD, pFace, aLogFont ); + + // create HFONT from log font + HFONT hNewFont = ::CreateFontIndirectW( &aLogFont ); + // select the new font into device + HFONT hOldFont = ::SelectFont( mhDC, hNewFont ); + + // read CMAP table to update their xFontCharMap + pWinFont->UpdateFromHDC( mhDC ); + + // cleanup temporary font + ::SelectFont( mhDC, hOldFont ); + ::DeleteFont( hNewFont ); + + // get the new charmap + xFontCharMap = pWinFont->GetFontCharMap(); + } + + // avoid fonts with unknown CMAP subtables for glyph fallback + if( !xFontCharMap.is() || xFontCharMap->IsDefaultMap() ) + return false; + + int nMatchCount = 0; + std::vector<sal_UCS4> rRemainingCodes; + const sal_Int32 nStrLen = rMissingChars.getLength(); + sal_Int32 nStrIdx = 0; + while (nStrIdx < nStrLen) + { + const sal_UCS4 uChar = rMissingChars.iterateCodePoints( &nStrIdx ); + if (xFontCharMap->HasChar(uChar)) + nMatchCount++; + else + rRemainingCodes.push_back(uChar); + } + + xFontCharMap = nullptr; + + if (nMatchCount > 0) + rMissingChars = OUString(rRemainingCodes.data(), rRemainingCodes.size()); + + return nMatchCount > 0; +} + +namespace +{ + //used by 2-level font fallback + PhysicalFontFamily* findDevFontListByLocale(const PhysicalFontCollection &rFontCollection, + const LanguageTag& rLanguageTag ) + { + // get the default font for a specified locale + const utl::DefaultFontConfiguration& rDefaults = utl::DefaultFontConfiguration::get(); + const OUString aDefault = rDefaults.getUserInterfaceFont(rLanguageTag); + return rFontCollection.FindFontFamilyByTokenNames(aDefault); + } +} + +// These are Win 3.1 bitmap fonts using "FON" font format +// which is not supported with DirectWrite so let's substitute them +// with a font that is supported and always available. +// Based on: +// https://dxr.mozilla.org/mozilla-esr10/source/gfx/thebes/gfxDWriteFontList.cpp#1057 +static const std::map<OUString, OUString> aBitmapFontSubs = +{ + { "MS Sans Serif", "Microsoft Sans Serif" }, + { "MS Serif", "Times New Roman" }, + { "Small Fonts", "Arial" }, + { "Courier", "Courier New" }, + { "Roman", "Times New Roman" }, + { "Script", "Mistral" } +}; + +// TODO: See if Windows have API that we can use here to improve font fallback. +bool WinPreMatchFontSubstititution::FindFontSubstitute(FontSelectPattern& rFontSelData) const +{ + if (rFontSelData.IsSymbolFont() || IsStarSymbol(rFontSelData.maSearchName)) + return false; + + for (const auto& aSub : aBitmapFontSubs) + { + if (rFontSelData.maSearchName == GetEnglishSearchFontName(aSub.first)) + { + rFontSelData.maSearchName = aSub.second; + return true; + } + } + + return false; +} + +// find a fallback font for missing characters +// TODO: should stylistic matches be searched and preferred? +bool WinGlyphFallbackSubstititution::FindFontSubstitute(FontSelectPattern& rFontSelData, LogicalFontInstance* /*pLogicalFont*/, OUString& rMissingChars) const +{ + // guess a locale matching to the missing chars + LanguageType eLang = rFontSelData.meLanguage; + LanguageTag aLanguageTag( eLang); + + // fall back to default UI locale if the font language is inconclusive + if( eLang == LANGUAGE_DONTKNOW ) + aLanguageTag = Application::GetSettings().GetUILanguageTag(); + + // first level fallback: + // try use the locale specific default fonts defined in VCL.xcu + const PhysicalFontCollection* pFontCollection = ImplGetSVData()->maGDIData.mxScreenFontList.get(); + PhysicalFontFamily* pFontFamily = findDevFontListByLocale(*pFontCollection, aLanguageTag); + if( pFontFamily ) + { + PhysicalFontFace* pFace = pFontFamily->FindBestFontFace( rFontSelData ); + if( HasMissingChars( pFace, rMissingChars ) ) + { + rFontSelData.maSearchName = pFontFamily->GetSearchName(); + return true; + } + } + + // are the missing characters symbols? + pFontFamily = pFontCollection->FindFontFamilyByAttributes( ImplFontAttrs::Symbol, + rFontSelData.GetWeight(), + rFontSelData.GetWidthType(), + rFontSelData.GetItalic(), + rFontSelData.maSearchName ); + if( pFontFamily ) + { + PhysicalFontFace* pFace = pFontFamily->FindBestFontFace( rFontSelData ); + if( HasMissingChars( pFace, rMissingChars ) ) + { + rFontSelData.maSearchName = pFontFamily->GetSearchName(); + return true; + } + } + + // last level fallback, check each font type face one by one + std::unique_ptr<ImplDeviceFontList> pTestFontList = pFontCollection->GetDeviceFontList(); + // limit the count of fonts to be checked to prevent hangs + static const int MAX_GFBFONT_COUNT = 600; + int nTestFontCount = pTestFontList->Count(); + if( nTestFontCount > MAX_GFBFONT_COUNT ) + nTestFontCount = MAX_GFBFONT_COUNT; + + bool bFound = false; + for( int i = 0; i < nTestFontCount; ++i ) + { + PhysicalFontFace* pFace = pTestFontList->Get( i ); + bFound = HasMissingChars( pFace, rMissingChars ); + if( !bFound ) + continue; + rFontSelData.maSearchName = pFace->GetFamilyName(); + break; + } + + return bFound; +} + +namespace { + +struct ImplEnumInfo +{ + HDC mhDC; + PhysicalFontCollection* mpList; + OUString* mpName; + LOGFONTW* mpLogFont; + bool mbPrinter; + int mnFontCount; +}; + +} + +static rtl_TextEncoding ImplCharSetToSal( BYTE nCharSet ) +{ + rtl_TextEncoding eTextEncoding; + + if ( nCharSet == OEM_CHARSET ) + { + UINT nCP = static_cast<sal_uInt16>(GetOEMCP()); + switch ( nCP ) + { + // It is unclear why these two (undefined?) code page numbers are + // handled specially here: + case 1004: eTextEncoding = RTL_TEXTENCODING_MS_1252; break; + case 65400: eTextEncoding = RTL_TEXTENCODING_SYMBOL; break; + default: + eTextEncoding = rtl_getTextEncodingFromWindowsCodePage(nCP); + break; + } + } + else + { + if( nCharSet ) + eTextEncoding = rtl_getTextEncodingFromWindowsCharset( nCharSet ); + else + eTextEncoding = RTL_TEXTENCODING_UNICODE; + } + + return eTextEncoding; +} + +static FontFamily ImplFamilyToSal( BYTE nFamily ) +{ + switch ( nFamily & 0xF0 ) + { + case FF_DECORATIVE: + return FAMILY_DECORATIVE; + + case FF_MODERN: + return FAMILY_MODERN; + + case FF_ROMAN: + return FAMILY_ROMAN; + + case FF_SCRIPT: + return FAMILY_SCRIPT; + + case FF_SWISS: + return FAMILY_SWISS; + + default: + break; + } + + return FAMILY_DONTKNOW; +} + +static BYTE ImplFamilyToWin( FontFamily eFamily ) +{ + switch ( eFamily ) + { + case FAMILY_DECORATIVE: + return FF_DECORATIVE; + + case FAMILY_MODERN: + return FF_MODERN; + + case FAMILY_ROMAN: + return FF_ROMAN; + + case FAMILY_SCRIPT: + return FF_SCRIPT; + + case FAMILY_SWISS: + return FF_SWISS; + + case FAMILY_SYSTEM: + return FF_SWISS; + + default: + break; + } + + return FF_DONTCARE; +} + +static FontWeight ImplWeightToSal( int nWeight ) +{ + if ( nWeight <= FW_THIN ) + return WEIGHT_THIN; + else if ( nWeight <= FW_ULTRALIGHT ) + return WEIGHT_ULTRALIGHT; + else if ( nWeight <= FW_LIGHT ) + return WEIGHT_LIGHT; + else if ( nWeight < FW_MEDIUM ) + return WEIGHT_NORMAL; + else if ( nWeight == FW_MEDIUM ) + return WEIGHT_MEDIUM; + else if ( nWeight <= FW_SEMIBOLD ) + return WEIGHT_SEMIBOLD; + else if ( nWeight <= FW_BOLD ) + return WEIGHT_BOLD; + else if ( nWeight <= FW_ULTRABOLD ) + return WEIGHT_ULTRABOLD; + else + return WEIGHT_BLACK; +} + +static int ImplWeightToWin( FontWeight eWeight ) +{ + switch ( eWeight ) + { + case WEIGHT_THIN: + return FW_THIN; + + case WEIGHT_ULTRALIGHT: + return FW_ULTRALIGHT; + + case WEIGHT_LIGHT: + return FW_LIGHT; + + case WEIGHT_SEMILIGHT: + case WEIGHT_NORMAL: + return FW_NORMAL; + + case WEIGHT_MEDIUM: + return FW_MEDIUM; + + case WEIGHT_SEMIBOLD: + return FW_SEMIBOLD; + + case WEIGHT_BOLD: + return FW_BOLD; + + case WEIGHT_ULTRABOLD: + return FW_ULTRABOLD; + + case WEIGHT_BLACK: + return FW_BLACK; + + default: + break; + } + + return 0; +} + +static FontPitch ImplLogPitchToSal( BYTE nPitch ) +{ + if ( nPitch & FIXED_PITCH ) + return PITCH_FIXED; + else + return PITCH_VARIABLE; +} + +static FontPitch ImplMetricPitchToSal( BYTE nPitch ) +{ + // Grrrr! See NT help + if ( !(nPitch & TMPF_FIXED_PITCH) ) + return PITCH_FIXED; + else + return PITCH_VARIABLE; +} + +static BYTE ImplPitchToWin( FontPitch ePitch ) +{ + if ( ePitch == PITCH_FIXED ) + return FIXED_PITCH; + else if ( ePitch == PITCH_VARIABLE ) + return VARIABLE_PITCH; + else + return DEFAULT_PITCH; +} + +static FontAttributes WinFont2DevFontAttributes( const ENUMLOGFONTEXW& rEnumFont, + const NEWTEXTMETRICW& rMetric) +{ + FontAttributes aDFA; + + const LOGFONTW rLogFont = rEnumFont.elfLogFont; + + // get font face attributes + aDFA.SetFamilyType(ImplFamilyToSal( rLogFont.lfPitchAndFamily )); + aDFA.SetWidthType(WIDTH_DONTKNOW); + aDFA.SetWeight(ImplWeightToSal( rLogFont.lfWeight )); + aDFA.SetItalic((rLogFont.lfItalic) ? ITALIC_NORMAL : ITALIC_NONE); + aDFA.SetPitch(ImplLogPitchToSal( rLogFont.lfPitchAndFamily )); + aDFA.SetSymbolFlag(rLogFont.lfCharSet == SYMBOL_CHARSET); + + // get the font face name + aDFA.SetFamilyName(o3tl::toU(rLogFont.lfFaceName)); + + // use the face's style name only if it looks reasonable + const wchar_t* pStyleName = rEnumFont.elfStyle; + const wchar_t* pEnd = pStyleName + sizeof(rEnumFont.elfStyle)/sizeof(*rEnumFont.elfStyle); + const wchar_t* p = pStyleName; + for(; *p && (p < pEnd); ++p ) + if( *p < 0x0020 ) + break; + if( p < pEnd ) + aDFA.SetStyleName(o3tl::toU(pStyleName)); + + // heuristics for font quality + // - opentypeTT > truetype + aDFA.SetQuality( 0 ); + if( rMetric.tmPitchAndFamily & TMPF_TRUETYPE ) + aDFA.IncreaseQualityBy( 50 ); + if( 0 != (rMetric.ntmFlags & (NTM_TT_OPENTYPE | NTM_PS_OPENTYPE)) ) + aDFA.IncreaseQualityBy( 10 ); + + // TODO: add alias names + return aDFA; +} + + +static rtl::Reference<WinFontFace> ImplLogMetricToDevFontDataW( const ENUMLOGFONTEXW* pLogFont, + const NEWTEXTMETRICW* pMetric) +{ + rtl::Reference<WinFontFace> pData = new WinFontFace( + WinFont2DevFontAttributes(*pLogFont, *pMetric), + pLogFont->elfLogFont.lfCharSet, + pMetric->tmPitchAndFamily ); + + return pData; +} + +void ImplSalLogFontToFontW( HDC hDC, const LOGFONTW& rLogFont, Font& rFont ) +{ + OUString aFontName( o3tl::toU(rLogFont.lfFaceName) ); + if (!aFontName.isEmpty()) + { + rFont.SetFamilyName( aFontName ); + rFont.SetCharSet( ImplCharSetToSal( rLogFont.lfCharSet ) ); + rFont.SetFamily( ImplFamilyToSal( rLogFont.lfPitchAndFamily ) ); + rFont.SetPitch( ImplLogPitchToSal( rLogFont.lfPitchAndFamily ) ); + rFont.SetWeight( ImplWeightToSal( rLogFont.lfWeight ) ); + + long nFontHeight = rLogFont.lfHeight; + if ( nFontHeight < 0 ) + nFontHeight = -nFontHeight; + long nDPIY = GetDeviceCaps( hDC, LOGPIXELSY ); + if( !nDPIY ) + nDPIY = 600; + nFontHeight *= 72; + nFontHeight += nDPIY/2; + nFontHeight /= nDPIY; + rFont.SetFontSize( Size( 0, nFontHeight ) ); + rFont.SetOrientation( static_cast<short>(rLogFont.lfEscapement) ); + if ( rLogFont.lfItalic ) + rFont.SetItalic( ITALIC_NORMAL ); + else + rFont.SetItalic( ITALIC_NONE ); + if ( rLogFont.lfUnderline ) + rFont.SetUnderline( LINESTYLE_SINGLE ); + else + rFont.SetUnderline( LINESTYLE_NONE ); + if ( rLogFont.lfStrikeOut ) + rFont.SetStrikeout( STRIKEOUT_SINGLE ); + else + rFont.SetStrikeout( STRIKEOUT_NONE ); + } +} + +WinFontFace::WinFontFace( const FontAttributes& rDFS, + BYTE eWinCharSet, BYTE nPitchAndFamily ) +: PhysicalFontFace( rDFS ), + mnId( 0 ), + mbFontCapabilitiesRead( false ), + meWinCharSet( eWinCharSet ), + mnPitchAndFamily( nPitchAndFamily ), + mbAliasSymbolsHigh( false ), + mbAliasSymbolsLow( false ) +{ + if( eWinCharSet == SYMBOL_CHARSET ) + { + if( (nPitchAndFamily & TMPF_TRUETYPE) != 0 ) + { + // truetype fonts need their symbols as U+F0xx + mbAliasSymbolsHigh = true; + } + else if( (nPitchAndFamily & (TMPF_VECTOR|TMPF_DEVICE)) + == (TMPF_VECTOR|TMPF_DEVICE) ) + { + // scalable device fonts (e.g. builtin printer fonts) + // need their symbols as U+00xx + mbAliasSymbolsLow = true; + } + else if( (nPitchAndFamily & (TMPF_VECTOR|TMPF_TRUETYPE)) == 0 ) + { + // bitmap fonts need their symbols as U+F0xx + mbAliasSymbolsHigh = true; + } + } +} + +WinFontFace::~WinFontFace() +{ + mxUnicodeMap.clear(); +} + +sal_IntPtr WinFontFace::GetFontId() const +{ + return mnId; +} + +rtl::Reference<LogicalFontInstance> WinFontFace::CreateFontInstance(const FontSelectPattern& rFSD) const +{ + return new WinFontInstance(*this, rFSD); +} + +static DWORD CalcTag( const char p[5]) { return (p[0]+(p[1]<<8)+(p[2]<<16)+(p[3]<<24)); } + +void WinFontFace::UpdateFromHDC( HDC hDC ) const +{ + // short circuit if already initialized + if( mxUnicodeMap.is() ) + return; + + ReadCmapTable( hDC ); + GetFontCapabilities( hDC ); +} + +FontCharMapRef WinFontFace::GetFontCharMap() const +{ + return mxUnicodeMap; +} + +bool WinFontFace::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const +{ + rFontCapabilities = maFontCapabilities; + return rFontCapabilities.oUnicodeRange || rFontCapabilities.oCodePageRange; +} + +void WinFontFace::ReadCmapTable( HDC hDC ) const +{ + if( mxUnicodeMap.is() ) + return; + + bool bIsSymbolFont = (meWinCharSet == SYMBOL_CHARSET); + // get the CMAP table from the font which is selected into the DC + const DWORD nCmapTag = CalcTag( "cmap" ); + const RawFontData aRawFontData( hDC, nCmapTag ); + // parse the CMAP table if available + if( aRawFontData.get() ) { + CmapResult aResult; + ParseCMAP( aRawFontData.get(), aRawFontData.size(), aResult ); + aResult.mbSymbolic = bIsSymbolFont; + if( aResult.mnRangeCount > 0 ) + { + FontCharMapRef pUnicodeMap(new FontCharMap(aResult)); + mxUnicodeMap = pUnicodeMap; + } + } + + if( !mxUnicodeMap.is() ) + { + mxUnicodeMap = FontCharMap::GetDefaultMap( bIsSymbolFont ); + } +} + +void WinFontFace::GetFontCapabilities( HDC hDC ) const +{ + // read this only once per font + if( mbFontCapabilitiesRead ) + return; + + mbFontCapabilitiesRead = true; + + // OS/2 table + const DWORD OS2Tag = CalcTag( "OS/2" ); + DWORD nLength = ::GetFontData( hDC, OS2Tag, 0, nullptr, 0 ); + if( (nLength != GDI_ERROR) && nLength ) + { + std::vector<unsigned char> aTable( nLength ); + unsigned char* pTable = aTable.data(); + ::GetFontData( hDC, OS2Tag, 0, pTable, nLength ); + vcl::getTTCoverage(maFontCapabilities.oUnicodeRange, maFontCapabilities.oCodePageRange, pTable, nLength); + } +} + +void WinSalGraphics::SetTextColor( Color nColor ) +{ + COLORREF aCol = PALETTERGB( nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue() ); + + if( !mbPrinter && + GetSalData()->mhDitherPal && + ImplIsSysColorEntry( nColor ) ) + { + aCol = PALRGB_TO_RGB( aCol ); + } + + ::SetTextColor( getHDC(), aCol ); +} + +static int CALLBACK SalEnumQueryFontProcExW( const LOGFONTW*, + const TEXTMETRICW*, + DWORD, LPARAM lParam ) +{ + *reinterpret_cast<bool*>(lParam) = true; + return 0; +} + +void ImplGetLogFontFromFontSelect( HDC hDC, + const FontSelectPattern& rFont, + const PhysicalFontFace* pFontFace, + LOGFONTW& rLogFont ) +{ + OUString aName; + if (pFontFace) + aName = pFontFace->GetFamilyName(); + else + aName = rFont.GetFamilyName().getToken( 0, ';' ); + + UINT nNameLen = aName.getLength(); + if (nNameLen >= LF_FACESIZE) + nNameLen = LF_FACESIZE - 1; + memcpy( rLogFont.lfFaceName, aName.getStr(), nNameLen*sizeof( wchar_t ) ); + rLogFont.lfFaceName[nNameLen] = 0; + + if (pFontFace) + { + const WinFontFace* pWinFontData = static_cast<const WinFontFace*>(pFontFace); + rLogFont.lfCharSet = pWinFontData->GetCharSet(); + rLogFont.lfPitchAndFamily = pWinFontData->GetPitchAndFamily(); + } + else + { + rLogFont.lfCharSet = rFont.IsSymbolFont() ? SYMBOL_CHARSET : DEFAULT_CHARSET; + rLogFont.lfPitchAndFamily = ImplPitchToWin( rFont.GetPitch() ) + | ImplFamilyToWin( rFont.GetFamilyType() ); + } + + static BYTE nDefaultQuality = NONANTIALIASED_QUALITY; + if (nDefaultQuality == NONANTIALIASED_QUALITY) + { + if (OpenGLWrapper::isVCLOpenGLEnabled()) + nDefaultQuality = ANTIALIASED_QUALITY; + else + nDefaultQuality = DEFAULT_QUALITY; + } + + rLogFont.lfWeight = ImplWeightToWin( rFont.GetWeight() ); + rLogFont.lfHeight = static_cast<LONG>(-rFont.mnHeight); + rLogFont.lfWidth = static_cast<LONG>(rFont.mnWidth); + rLogFont.lfUnderline = 0; + rLogFont.lfStrikeOut = 0; + rLogFont.lfItalic = BYTE(rFont.GetItalic() != ITALIC_NONE); + rLogFont.lfEscapement = rFont.mnOrientation; + rLogFont.lfOrientation = rLogFont.lfEscapement; + rLogFont.lfClipPrecision = CLIP_DEFAULT_PRECIS; + rLogFont.lfQuality = nDefaultQuality; + rLogFont.lfOutPrecision = OUT_TT_PRECIS; + if ( rFont.mnOrientation ) + rLogFont.lfClipPrecision |= CLIP_LH_ANGLES; + + // disable antialiasing if requested + if ( rFont.mbNonAntialiased ) + rLogFont.lfQuality = NONANTIALIASED_QUALITY; + + // select vertical mode if requested and available + if ( rFont.mbVertical && nNameLen ) + { + // vertical fonts start with an '@' + memmove( &rLogFont.lfFaceName[1], &rLogFont.lfFaceName[0], + sizeof(rLogFont.lfFaceName)-sizeof(rLogFont.lfFaceName[0]) ); + rLogFont.lfFaceName[0] = '@'; + + // check availability of vertical mode for this font + bool bAvailable = false; + EnumFontFamiliesExW( hDC, &rLogFont, SalEnumQueryFontProcExW, + reinterpret_cast<LPARAM>(&bAvailable), 0 ); + + if( !bAvailable ) + { + // restore non-vertical name if not vertical mode isn't available + memcpy( &rLogFont.lfFaceName[0], aName.getStr(), nNameLen*sizeof(wchar_t) ); + rLogFont.lfFaceName[nNameLen] = '\0'; + // keep it upright and create the font for sideway glyphs later. + rLogFont.lfEscapement = rLogFont.lfEscapement - 2700; + rLogFont.lfOrientation = rLogFont.lfEscapement; + } + } +} + +HFONT WinSalGraphics::ImplDoSetFont(FontSelectPattern const & i_rFont, + const PhysicalFontFace * i_pFontFace, + HFONT& o_rOldFont) +{ + HFONT hNewFont = nullptr; + + LOGFONTW aLogFont; + ImplGetLogFontFromFontSelect( getHDC(), i_rFont, i_pFontFace, aLogFont ); + + hNewFont = ::CreateFontIndirectW( &aLogFont ); + + HDC hdcScreen = nullptr; + if( mbVirDev ) + // only required for virtual devices, see below for details + hdcScreen = GetDC(nullptr); + if( hdcScreen ) + { + // select font into screen hdc first to get an antialiased font + // and instantly restore the default font! + // see knowledge base article 305290: + // "PRB: Fonts Not Drawn Antialiased on Device Context for DirectDraw Surface" + SelectFont( hdcScreen, SelectFont( hdcScreen , hNewFont ) ); + } + o_rOldFont = ::SelectFont( getHDC(), hNewFont ); + + TEXTMETRICW aTextMetricW; + if( !::GetTextMetricsW( getHDC(), &aTextMetricW ) ) + { + // the selected font doesn't work => try a replacement + // TODO: use its font fallback instead + lstrcpynW( aLogFont.lfFaceName, L"Courier New", 12 ); + aLogFont.lfPitchAndFamily = FIXED_PITCH; + HFONT hNewFont2 = CreateFontIndirectW( &aLogFont ); + SelectFont( getHDC(), hNewFont2 ); + DeleteFont( hNewFont ); + hNewFont = hNewFont2; + } + + if( hdcScreen ) + ::ReleaseDC( nullptr, hdcScreen ); + + return hNewFont; +} + +void WinSalGraphics::SetFont(LogicalFontInstance* pFont, int nFallbackLevel) +{ + // return early if there is no new font + if( !pFont ) + { + if (!mpWinFontEntry[nFallbackLevel].is()) + return; + + // select original DC font + assert(mhDefFont); + ::SelectFont(getHDC(), mhDefFont); + mhDefFont = nullptr; + + // release no longer referenced font handles + for( int i = nFallbackLevel; i < MAX_FALLBACK; ++i ) + mpWinFontEntry[i] = nullptr; + return; + } + + WinFontInstance *pFontInstance = static_cast<WinFontInstance*>(pFont); + mpWinFontEntry[ nFallbackLevel ] = pFontInstance; + + HFONT hOldFont = nullptr; + HFONT hNewFont = pFontInstance->GetHFONT(); + if (!hNewFont) + { + pFontInstance->SetGraphics(this); + hNewFont = pFontInstance->GetHFONT(); + } + hOldFont = ::SelectFont(getHDC(), hNewFont); + + // keep default font + if( !mhDefFont ) + mhDefFont = hOldFont; + else + { + // release no longer referenced font handles + for( int i = nFallbackLevel + 1; i < MAX_FALLBACK && mpWinFontEntry[i].is(); ++i ) + mpWinFontEntry[i] = nullptr; + } + + // now the font is live => update font face + const WinFontFace* pFontFace = pFontInstance->GetFontFace(); + pFontFace->UpdateFromHDC(getHDC()); +} + +void WinSalGraphics::GetFontMetric( ImplFontMetricDataRef& rxFontMetric, int nFallbackLevel ) +{ + // temporarily change the HDC to the font in the fallback level + rtl::Reference<WinFontInstance> pFontInstance = mpWinFontEntry[nFallbackLevel]; + const HFONT hOldFont = SelectFont(getHDC(), pFontInstance->GetHFONT()); + + wchar_t aFaceName[LF_FACESIZE+60]; + if( GetTextFaceW( getHDC(), SAL_N_ELEMENTS(aFaceName), aFaceName ) ) + rxFontMetric->SetFamilyName(o3tl::toU(aFaceName)); + + rxFontMetric->SetMinKashida(pFontInstance->GetKashidaWidth()); + rxFontMetric->ImplCalcLineSpacing(pFontInstance.get()); + + // get the font metric + OUTLINETEXTMETRICW aOutlineMetric; + const bool bOK = GetOutlineTextMetricsW(getHDC(), sizeof(aOutlineMetric), &aOutlineMetric); + // restore the HDC to the font in the base level + SelectFont( getHDC(), hOldFont ); + if( !bOK ) + return; + + TEXTMETRICW aWinMetric = aOutlineMetric.otmTextMetrics; + + // device independent font attributes + rxFontMetric->SetFamilyType(ImplFamilyToSal( aWinMetric.tmPitchAndFamily )); + rxFontMetric->SetSymbolFlag(aWinMetric.tmCharSet == SYMBOL_CHARSET); + rxFontMetric->SetWeight(ImplWeightToSal( aWinMetric.tmWeight )); + rxFontMetric->SetPitch(ImplMetricPitchToSal( aWinMetric.tmPitchAndFamily )); + rxFontMetric->SetItalic(aWinMetric.tmItalic ? ITALIC_NORMAL : ITALIC_NONE); + rxFontMetric->SetSlant( 0 ); + + // transformation dependent font metrics + rxFontMetric->SetWidth(static_cast<int>(pFontInstance->GetScale() * aWinMetric.tmAveCharWidth)); +} + +FontCharMapRef WinSalGraphics::GetFontCharMap() const +{ + if (!mpWinFontEntry[0]) + { + return FontCharMapRef( new FontCharMap() ); + } + return mpWinFontEntry[0]->GetFontFace()->GetFontCharMap(); +} + +bool WinSalGraphics::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const +{ + if (!mpWinFontEntry[0]) + return false; + return mpWinFontEntry[0]->GetFontFace()->GetFontCapabilities(rFontCapabilities); +} + +static int CALLBACK SalEnumFontsProcExW( const LOGFONTW* lpelfe, + const TEXTMETRICW* lpntme, + DWORD nFontType, LPARAM lParam ) +{ + ENUMLOGFONTEXW const * pLogFont + = reinterpret_cast<ENUMLOGFONTEXW const *>(lpelfe); + NEWTEXTMETRICEXW const * pMetric + = reinterpret_cast<NEWTEXTMETRICEXW const *>(lpntme); + ImplEnumInfo* pInfo = reinterpret_cast<ImplEnumInfo*>(lParam); + if ( !pInfo->mpName ) + { + // Ignore vertical fonts + if ( pLogFont->elfLogFont.lfFaceName[0] != '@' ) + { + OUString aName = o3tl::toU(pLogFont->elfLogFont.lfFaceName); + pInfo->mpName = &aName; + memcpy(pInfo->mpLogFont->lfFaceName, pLogFont->elfLogFont.lfFaceName, (aName.getLength()+1)*sizeof(wchar_t)); + pInfo->mpLogFont->lfCharSet = pLogFont->elfLogFont.lfCharSet; + EnumFontFamiliesExW(pInfo->mhDC, pInfo->mpLogFont, SalEnumFontsProcExW, + reinterpret_cast<LPARAM>(pInfo), 0); + pInfo->mpLogFont->lfFaceName[0] = '\0'; + pInfo->mpLogFont->lfCharSet = DEFAULT_CHARSET; + pInfo->mpName = nullptr; + } + } + else + { + // Ignore non-device fonts on printers. + if (pInfo->mbPrinter) + { + if ((nFontType & RASTER_FONTTYPE) && !(nFontType & DEVICE_FONTTYPE)) + { + SAL_INFO("vcl.fonts", "Unsupported printer font ignored: " << OUString(o3tl::toU(pLogFont->elfLogFont.lfFaceName))); + return 1; + } + } + // Only SFNT fonts are supported, ignore anything else. + else if (!(nFontType & TRUETYPE_FONTTYPE) && + !(pMetric->ntmTm.ntmFlags & NTM_PS_OPENTYPE) && + !(pMetric->ntmTm.ntmFlags & NTM_TT_OPENTYPE)) + { + SAL_INFO("vcl.fonts", "Unsupported font ignored: " << OUString(o3tl::toU(pLogFont->elfLogFont.lfFaceName))); + return 1; + } + + rtl::Reference<WinFontFace> pData = ImplLogMetricToDevFontDataW(pLogFont, &(pMetric->ntmTm)); + pData->SetFontId( sal_IntPtr( pInfo->mnFontCount++ ) ); + + pInfo->mpList->Add( pData.get() ); + SAL_INFO("vcl.fonts", "SalEnumFontsProcExW: font added: " << pData->GetFamilyName() << " " << pData->GetStyleName()); + } + + return 1; +} + +struct TempFontItem +{ + OUString maFontResourcePath; + TempFontItem* mpNextItem; +}; + +static int lcl_AddFontResource(SalData& rSalData, const OUString& rFontFileURL, bool bShared) +{ + OUString aFontSystemPath; + OSL_VERIFY(!osl::FileBase::getSystemPathFromFileURL(rFontFileURL, aFontSystemPath)); + + int nRet = AddFontResourceExW(o3tl::toW(aFontSystemPath.getStr()), FR_PRIVATE, nullptr); + SAL_WARN_IF(nRet <= 0, "vcl.fonts", "AddFontResourceExW failed for " << rFontFileURL); + if (nRet > 0) + { + TempFontItem* pNewItem = new TempFontItem; + pNewItem->maFontResourcePath = aFontSystemPath; + if (bShared) + { + pNewItem->mpNextItem = rSalData.mpSharedTempFontItem; + rSalData.mpSharedTempFontItem = pNewItem; + } + else + { + pNewItem->mpNextItem = rSalData.mpOtherTempFontItem; + rSalData.mpOtherTempFontItem = pNewItem; + } + } + return nRet; +} + +void ImplReleaseTempFonts(SalData& rSalData, bool bAll) +{ + while (TempFontItem* p = rSalData.mpOtherTempFontItem) + { + RemoveFontResourceExW(o3tl::toW(p->maFontResourcePath.getStr()), FR_PRIVATE, nullptr); + rSalData.mpOtherTempFontItem = p->mpNextItem; + delete p; + } + + if (!bAll) + return; + + while (TempFontItem* p = rSalData.mpSharedTempFontItem) + { + RemoveFontResourceExW(o3tl::toW(p->maFontResourcePath.getStr()), FR_PRIVATE, nullptr); + rSalData.mpSharedTempFontItem = p->mpNextItem; + delete p; + } +} + +static OUString lcl_GetFontFamilyName(const OUString& rFontFileURL) +{ + // Create temporary file name + OUString aTempFileURL; + if (osl::File::E_None != osl::File::createTempFile(nullptr, nullptr, &aTempFileURL)) + return OUString(); + osl::File::remove(aTempFileURL); + OUString aResSystemPath; + osl::FileBase::getSystemPathFromFileURL(aTempFileURL, aResSystemPath); + + // Create font resource file (.fot) + // There is a limit of 127 characters for the full path passed via lpszFile, so we have to + // split the font URL and pass it as two parameters. As a result we can't use + // CreateScalableFontResource for renaming, as it now expects the font in the system path. + // But it's still good to use it for family name extraction, we're currently after. + // BTW: it doesn't help to prefix the lpszFile with \\?\ to support larger paths. + // TODO: use TTLoadEmbeddedFont (needs an EOT as input, so we have to add a header to the TTF) + // TODO: forward the EOT from the AddTempDevFont call side, if VCL supports it + INetURLObject aTTFUrl(rFontFileURL); + // GetBase() strips the extension + OUString aFilename = aTTFUrl.GetLastName(INetURLObject::DecodeMechanism::WithCharset); + if (!CreateScalableFontResourceW(0, o3tl::toW(aResSystemPath.getStr()), + o3tl::toW(aFilename.getStr()), o3tl::toW(aTTFUrl.GetPath().getStr()))) + { + sal_uInt32 nError = GetLastError(); + SAL_WARN("vcl.fonts", "CreateScalableFontResource failed for " << aResSystemPath << " " + << aFilename << " " << aTTFUrl.GetPath() << " " << nError); + return OUString(); + } + + // Open and read the font resource file + osl::File aFotFile(aTempFileURL); + if (osl::FileBase::E_None != aFotFile.open(osl_File_OpenFlag_Read)) + return OUString(); + + sal_uInt64 nBytesRead = 0; + char aBuffer[4096]; + aFotFile.read( aBuffer, sizeof( aBuffer ), nBytesRead ); + // clean up temporary resource file + aFotFile.close(); + osl::File::remove(aTempFileURL); + + // retrieve font family name from byte offset 0x4F6 + static const sal_uInt64 nNameOfs = 0x4F6; + sal_uInt64 nPos = nNameOfs; + for (; (nPos < nBytesRead) && (aBuffer[nPos] != 0); nPos++); + if (nPos >= nBytesRead || (nPos == nNameOfs)) + return OUString(); + + return OUString(aBuffer + nNameOfs, nPos - nNameOfs, osl_getThreadTextEncoding()); +} + +bool WinSalGraphics::AddTempDevFont(PhysicalFontCollection* pFontCollection, + const OUString& rFontFileURL, const OUString& rFontName) +{ + OUString aFontFamily = lcl_GetFontFamilyName(rFontFileURL); + if (aFontFamily.isEmpty()) + { + SAL_WARN("vcl.fonts", "error extracting font family from " << rFontFileURL); + return false; + } + + if (rFontName != aFontFamily) + { + SAL_WARN("vcl.fonts", "font family renaming not implemented; skipping embedded " << rFontName); + return false; + } + + int nFonts = lcl_AddFontResource(*GetSalData(), rFontFileURL, false); + if (nFonts <= 0) + return false; + + ImplEnumInfo aInfo; + aInfo.mhDC = getHDC(); + aInfo.mpList = pFontCollection; + aInfo.mpName = &aFontFamily; + aInfo.mbPrinter = mbPrinter; + aInfo.mnFontCount = pFontCollection->Count(); + const int nExpectedFontCount = aInfo.mnFontCount + nFonts; + + LOGFONTW aLogFont = {}; + aLogFont.lfCharSet = DEFAULT_CHARSET; + aInfo.mpLogFont = &aLogFont; + + // add the font to the PhysicalFontCollection + EnumFontFamiliesExW(getHDC(), &aLogFont, + SalEnumFontsProcExW, reinterpret_cast<LPARAM>(&aInfo), 0); + + SAL_WARN_IF(nExpectedFontCount != pFontCollection->Count(), "vcl.fonts", + "temp font was registered but is not in enumeration: " << rFontFileURL); + + return true; +} + +void WinSalGraphics::GetDevFontList( PhysicalFontCollection* pFontCollection ) +{ + // make sure all LO shared fonts are registered temporarily + static std::once_flag init; + std::call_once(init, []() + { + auto registerFontsIn = [](const OUString& dir) { + // collect fonts in font path that could not be registered + osl::Directory aFontDir(dir); + osl::FileBase::RC rcOSL = aFontDir.open(); + if (rcOSL == osl::FileBase::E_None) + { + osl::DirectoryItem aDirItem; + SalData* pSalData = GetSalData(); + assert(pSalData); + + while (aFontDir.getNextItem(aDirItem, 10) == osl::FileBase::E_None) + { + osl::FileStatus aFileStatus(osl_FileStatus_Mask_FileURL); + rcOSL = aDirItem.getFileStatus(aFileStatus); + if (rcOSL == osl::FileBase::E_None) + lcl_AddFontResource(*pSalData, aFileStatus.getFileURL(), true); + } + } + }; + + // determine font path + // since we are only interested in fonts that could not be + // registered before because of missing administration rights + // only the font path of the user installation is needed + OUString aPath("$BRAND_BASE_DIR"); + rtl_bootstrap_expandMacros(&aPath.pData); + + // internal font resources, required for normal operation, like OpenSymbol + registerFontsIn(aPath + "/" LIBO_SHARE_RESOURCE_FOLDER "/common/fonts"); + + // collect fonts in font path that could not be registered + registerFontsIn(aPath + "/" LIBO_SHARE_FOLDER "/fonts/truetype"); + + return true; + }); + + ImplEnumInfo aInfo; + aInfo.mhDC = getHDC(); + aInfo.mpList = pFontCollection; + aInfo.mpName = nullptr; + aInfo.mbPrinter = mbPrinter; + aInfo.mnFontCount = 0; + + LOGFONTW aLogFont = {}; + aLogFont.lfCharSet = DEFAULT_CHARSET; + aInfo.mpLogFont = &aLogFont; + + // fill the PhysicalFontCollection + EnumFontFamiliesExW( getHDC(), &aLogFont, + SalEnumFontsProcExW, reinterpret_cast<LPARAM>(&aInfo), 0 ); + + // set glyph fallback hook + static WinGlyphFallbackSubstititution aSubstFallback; + static WinPreMatchFontSubstititution aPreMatchFont; + pFontCollection->SetFallbackHook( &aSubstFallback ); + pFontCollection->SetPreMatchHook(&aPreMatchFont); +} + +void WinSalGraphics::ClearDevFontCache() +{ + WinSalGraphicsImplBase* pImpl = dynamic_cast<WinSalGraphicsImplBase*>(GetImpl()); + assert(pImpl != nullptr); + pImpl->ClearDevFontCache(); + ImplReleaseTempFonts(*GetSalData(), false); +} + +bool WinFontInstance::ImplGetGlyphBoundRect(sal_GlyphId nId, tools::Rectangle& rRect, bool) const +{ + assert(m_pGraphics); + HDC hDC = m_pGraphics->getHDC(); + const HFONT hOrigFont = static_cast<HFONT>(GetCurrentObject(hDC, OBJ_FONT)); + const HFONT hFont = GetHFONT(); + if (hFont != hOrigFont) + SelectObject(hDC, hFont); + + const ::comphelper::ScopeGuard aFontRestoreScopeGuard([hFont, hOrigFont, hDC]() + { if (hFont != hOrigFont) SelectObject(hDC, hOrigFont); }); + const float fFontScale = GetScale(); + + // use unity matrix + MAT2 aMat; + aMat.eM11 = aMat.eM22 = FixedFromDouble( 1.0 ); + aMat.eM12 = aMat.eM21 = FixedFromDouble( 0.0 ); + + UINT nGGOFlags = GGO_METRICS; + nGGOFlags |= GGO_GLYPH_INDEX; + + GLYPHMETRICS aGM; + aGM.gmptGlyphOrigin.x = aGM.gmptGlyphOrigin.y = 0; + aGM.gmBlackBoxX = aGM.gmBlackBoxY = 0; + DWORD nSize = ::GetGlyphOutlineW(hDC, nId, nGGOFlags, &aGM, 0, nullptr, &aMat); + if (nSize == GDI_ERROR) + return false; + + rRect = tools::Rectangle( Point( +aGM.gmptGlyphOrigin.x, -aGM.gmptGlyphOrigin.y ), + Size( aGM.gmBlackBoxX, aGM.gmBlackBoxY ) ); + rRect.SetLeft(static_cast<int>( fFontScale * rRect.Left() )); + rRect.SetRight(static_cast<int>( fFontScale * rRect.Right() ) + 1); + rRect.SetTop(static_cast<int>( fFontScale * rRect.Top() )); + rRect.SetBottom(static_cast<int>( fFontScale * rRect.Bottom() ) + 1); + return true; +} + +bool WinFontInstance::GetGlyphOutline(sal_GlyphId nId, basegfx::B2DPolyPolygon& rB2DPolyPoly, bool) const +{ + rB2DPolyPoly.clear(); + + assert(m_pGraphics); + HDC hDC = m_pGraphics->getHDC(); + const HFONT hOrigFont = static_cast<HFONT>(GetCurrentObject(hDC, OBJ_FONT)); + const HFONT hFont = GetHFONT(); + if (hFont != hOrigFont) + SelectObject(hDC, hFont); + + const ::comphelper::ScopeGuard aFontRestoreScopeGuard([hFont, hOrigFont, hDC]() + { if (hFont != hOrigFont) SelectObject(hDC, hOrigFont); }); + + // use unity matrix + MAT2 aMat; + aMat.eM11 = aMat.eM22 = FixedFromDouble( 1.0 ); + aMat.eM12 = aMat.eM21 = FixedFromDouble( 0.0 ); + + UINT nGGOFlags = GGO_NATIVE; + nGGOFlags |= GGO_GLYPH_INDEX; + + GLYPHMETRICS aGlyphMetrics; + const DWORD nSize1 = ::GetGlyphOutlineW(hDC, nId, nGGOFlags, &aGlyphMetrics, 0, nullptr, &aMat); + if( !nSize1 ) // blank glyphs are ok + return true; + else if( nSize1 == GDI_ERROR ) + return false; + + BYTE* pData = new BYTE[ nSize1 ]; + const DWORD nSize2 = ::GetGlyphOutlineW(hDC, nId, nGGOFlags, + &aGlyphMetrics, nSize1, pData, &aMat ); + + if( nSize1 != nSize2 ) + return false; + + // TODO: avoid tools polygon by creating B2DPolygon directly + int nPtSize = 512; + Point* pPoints = new Point[ nPtSize ]; + PolyFlags* pFlags = new PolyFlags[ nPtSize ]; + + TTPOLYGONHEADER* pHeader = reinterpret_cast<TTPOLYGONHEADER*>(pData); + while( reinterpret_cast<BYTE*>(pHeader) < pData+nSize2 ) + { + // only outline data is interesting + if( pHeader->dwType != TT_POLYGON_TYPE ) + break; + + // get start point; next start points are end points + // of previous segment + sal_uInt16 nPnt = 0; + + long nX = IntTimes256FromFixed( pHeader->pfxStart.x ); + long nY = IntTimes256FromFixed( pHeader->pfxStart.y ); + pPoints[ nPnt ] = Point( nX, nY ); + pFlags[ nPnt++ ] = PolyFlags::Normal; + + bool bHasOfflinePoints = false; + TTPOLYCURVE* pCurve = reinterpret_cast<TTPOLYCURVE*>( pHeader + 1 ); + pHeader = reinterpret_cast<TTPOLYGONHEADER*>( reinterpret_cast<BYTE*>(pHeader) + pHeader->cb ); + while( reinterpret_cast<BYTE*>(pCurve) < reinterpret_cast<BYTE*>(pHeader) ) + { + int nNeededSize = nPnt + 16 + 3 * pCurve->cpfx; + if( nPtSize < nNeededSize ) + { + Point* pOldPoints = pPoints; + PolyFlags* pOldFlags = pFlags; + nPtSize = 2 * nNeededSize; + pPoints = new Point[ nPtSize ]; + pFlags = new PolyFlags[ nPtSize ]; + for( sal_uInt16 i = 0; i < nPnt; ++i ) + { + pPoints[ i ] = pOldPoints[ i ]; + pFlags[ i ] = pOldFlags[ i ]; + } + delete[] pOldPoints; + delete[] pOldFlags; + } + + int i = 0; + if( TT_PRIM_LINE == pCurve->wType ) + { + while( i < pCurve->cpfx ) + { + nX = IntTimes256FromFixed( pCurve->apfx[ i ].x ); + nY = IntTimes256FromFixed( pCurve->apfx[ i ].y ); + ++i; + pPoints[ nPnt ] = Point( nX, nY ); + pFlags[ nPnt ] = PolyFlags::Normal; + ++nPnt; + } + } + else if( TT_PRIM_QSPLINE == pCurve->wType ) + { + bHasOfflinePoints = true; + while( i < pCurve->cpfx ) + { + // get control point of quadratic bezier spline + nX = IntTimes256FromFixed( pCurve->apfx[ i ].x ); + nY = IntTimes256FromFixed( pCurve->apfx[ i ].y ); + ++i; + Point aControlP( nX, nY ); + + // calculate first cubic control point + // P0 = 1/3 * (PBeg + 2 * PQControl) + nX = pPoints[ nPnt-1 ].X() + 2 * aControlP.X(); + nY = pPoints[ nPnt-1 ].Y() + 2 * aControlP.Y(); + pPoints[ nPnt+0 ] = Point( (2*nX+3)/6, (2*nY+3)/6 ); + pFlags[ nPnt+0 ] = PolyFlags::Control; + + // calculate endpoint of segment + nX = IntTimes256FromFixed( pCurve->apfx[ i ].x ); + nY = IntTimes256FromFixed( pCurve->apfx[ i ].y ); + + if ( i+1 >= pCurve->cpfx ) + { + // endpoint is either last point in segment => advance + ++i; + } + else + { + // or endpoint is the middle of two control points + nX += IntTimes256FromFixed( pCurve->apfx[ i-1 ].x ); + nY += IntTimes256FromFixed( pCurve->apfx[ i-1 ].y ); + nX = (nX + 1) / 2; + nY = (nY + 1) / 2; + // no need to advance, because the current point + // is the control point in next bezier spline + } + + pPoints[ nPnt+2 ] = Point( nX, nY ); + pFlags[ nPnt+2 ] = PolyFlags::Normal; + + // calculate second cubic control point + // P1 = 1/3 * (PEnd + 2 * PQControl) + nX = pPoints[ nPnt+2 ].X() + 2 * aControlP.X(); + nY = pPoints[ nPnt+2 ].Y() + 2 * aControlP.Y(); + pPoints[ nPnt+1 ] = Point( (2*nX+3)/6, (2*nY+3)/6 ); + pFlags[ nPnt+1 ] = PolyFlags::Control; + + nPnt += 3; + } + } + + // next curve segment + pCurve = reinterpret_cast<TTPOLYCURVE*>(&pCurve->apfx[ i ]); + } + + // end point is start point for closed contour + // disabled, because Polygon class closes the contour itself + // pPoints[nPnt++] = pPoints[0]; + // #i35928# + // Added again, but add only when not yet closed + if(pPoints[nPnt - 1] != pPoints[0]) + { + if( bHasOfflinePoints ) + pFlags[nPnt] = pFlags[0]; + + pPoints[nPnt++] = pPoints[0]; + } + + // convert y-coordinates W32 -> VCL + for( int i = 0; i < nPnt; ++i ) + pPoints[i].setY(-pPoints[i].Y()); + + // insert into polypolygon + tools::Polygon aPoly( nPnt, pPoints, (bHasOfflinePoints ? pFlags : nullptr) ); + // convert to B2DPolyPolygon + // TODO: get rid of the intermediate PolyPolygon + rB2DPolyPoly.append( aPoly.getB2DPolygon() ); + } + + delete[] pPoints; + delete[] pFlags; + + delete[] pData; + + // rescaling needed for the tools::PolyPolygon conversion + if( rB2DPolyPoly.count() ) + { + const double fFactor(GetScale()/256); + rB2DPolyPoly.transform(basegfx::utils::createScaleB2DHomMatrix(fFactor, fFactor)); + } + + return true; +} + +class ScopedFont +{ +public: + explicit ScopedFont(WinSalGraphics & rData); + + ~ScopedFont(); + +private: + WinSalGraphics & m_rData; + HFONT m_hOrigFont; +}; + +ScopedFont::ScopedFont(WinSalGraphics & rData): m_rData(rData), m_hOrigFont(nullptr) +{ + if (m_rData.mpWinFontEntry[0]) + { + m_hOrigFont = m_rData.mpWinFontEntry[0]->GetHFONT(); + m_rData.mpWinFontEntry[0]->SetHFONT(nullptr); + } +} + +ScopedFont::~ScopedFont() +{ + if( m_hOrigFont ) + { + // restore original font, destroy temporary font + HFONT hTempFont = m_rData.mpWinFontEntry[0]->GetHFONT(); + m_rData.mpWinFontEntry[0]->SetHFONT(m_hOrigFont); + SelectObject( m_rData.getHDC(), m_hOrigFont ); + DeleteObject( hTempFont ); + } +} + +namespace { + +class ScopedTrueTypeFont +{ +public: + ScopedTrueTypeFont(): m_pFont(nullptr) {} + + ~ScopedTrueTypeFont(); + + SFErrCodes open(void const * pBuffer, sal_uInt32 nLen, sal_uInt32 nFaceNum); + + TrueTypeFont * get() const { return m_pFont; } + +private: + TrueTypeFont * m_pFont; +}; + +} + +ScopedTrueTypeFont::~ScopedTrueTypeFont() +{ + if (m_pFont != nullptr) + CloseTTFont(m_pFont); +} + +SFErrCodes ScopedTrueTypeFont::open(void const * pBuffer, sal_uInt32 nLen, + sal_uInt32 nFaceNum) +{ + OSL_ENSURE(m_pFont == nullptr, "already open"); + return OpenTTFontBuffer(pBuffer, nLen, nFaceNum, &m_pFont); +} + +bool WinSalGraphics::CreateFontSubset( const OUString& rToFile, + const PhysicalFontFace* pFont, const sal_GlyphId* pGlyphIds, const sal_uInt8* pEncoding, + sal_Int32* pGlyphWidths, int nGlyphCount, FontSubsetInfo& rInfo ) +{ + // TODO: use more of the central font-subsetting code, move stuff there if needed + + // create matching FontSelectPattern + // we need just enough to get to the font file data + // use height=1000 for easier debugging (to match psprint's font units) + FontSelectPattern aIFSD( *pFont, Size(0,1000), 1000.0, 0, false ); + + // TODO: much better solution: move SetFont and restoration of old font to caller + ScopedFont aOldFont(*this); + HFONT hOldFont = nullptr; + ImplDoSetFont(aIFSD, pFont, hOldFont); + + WinFontFace const * pWinFontData = static_cast<WinFontFace const *>(pFont); + +#if OSL_DEBUG_LEVEL > 1 + // get font metrics + TEXTMETRICW aWinMetric; + if( !::GetTextMetricsW( getHDC(), &aWinMetric ) ) + return FALSE; + + SAL_WARN_IF( (aWinMetric.tmPitchAndFamily & TMPF_DEVICE), "vcl", "cannot subset device font" ); + SAL_WARN_IF( !(aWinMetric.tmPitchAndFamily & TMPF_TRUETYPE), "vcl", "can only subset TT font" ); +#endif + + OUString aSysPath; + if( osl_File_E_None != osl_getSystemPathFromFileURL( rToFile.pData, &aSysPath.pData ) ) + return false; + const rtl_TextEncoding aThreadEncoding = osl_getThreadTextEncoding(); + const OString aToFile(OUStringToOString(aSysPath, aThreadEncoding)); + + // check if the font has a CFF-table + const DWORD nCffTag = CalcTag( "CFF " ); + const RawFontData aRawCffData( getHDC(), nCffTag ); + if( aRawCffData.get() ) + { + pWinFontData->UpdateFromHDC( getHDC() ); + + // provide a font subset from the CFF-table + FILE* pOutFile = fopen( aToFile.getStr(), "wb" ); + rInfo.LoadFont( FontType::CFF_FONT, aRawCffData.get(), aRawCffData.size() ); + bool bRC = rInfo.CreateFontSubset( FontType::TYPE1_PFB, pOutFile, nullptr, + pGlyphIds, pEncoding, nGlyphCount, pGlyphWidths ); + fclose( pOutFile ); + return bRC; + } + + // get raw font file data + const RawFontData xRawFontData( getHDC(), 0 ); + if( !xRawFontData.get() ) + return false; + + // open font file + sal_uInt32 nFaceNum = 0; + if( !*xRawFontData.get() ) // TTC candidate + nFaceNum = ~0U; // indicate "TTC font extracts only" + + ScopedTrueTypeFont aSftTTF; + SFErrCodes nRC = aSftTTF.open( xRawFontData.get(), xRawFontData.size(), nFaceNum ); + if( nRC != SFErrCodes::Ok ) + return false; + + TTGlobalFontInfo aTTInfo; + ::GetTTGlobalFontInfo( aSftTTF.get(), &aTTInfo ); + rInfo.m_nFontType = FontType::SFNT_TTF; + rInfo.m_aPSName = ImplSalGetUniString( aTTInfo.psname ); + rInfo.m_nAscent = aTTInfo.winAscent; + rInfo.m_nDescent = aTTInfo.winDescent; + rInfo.m_aFontBBox = tools::Rectangle( Point( aTTInfo.xMin, aTTInfo.yMin ), + Point( aTTInfo.xMax, aTTInfo.yMax ) ); + rInfo.m_nCapHeight = aTTInfo.yMax; // Well ... + + // subset TTF-glyphs and get their properties + // take care that subset fonts require the NotDef glyph in pos 0 + int nOrigCount = nGlyphCount; + sal_uInt16 aShortIDs[ 256 ]; + sal_uInt8 aTempEncs[ 256 ]; + + int nNotDef=-1, i; + for( i = 0; i < nGlyphCount; ++i ) + { + aTempEncs[i] = pEncoding[i]; + aShortIDs[i] = static_cast<sal_uInt16>(pGlyphIds[i]); + if (!aShortIDs[i]) + if( nNotDef < 0 ) + nNotDef = i; // first NotDef glyph found + } + + if( nNotDef != 0 ) + { + // add fake NotDef glyph if needed + if( nNotDef < 0 ) + nNotDef = nGlyphCount++; + + // NotDef glyph must be in pos 0 => swap glyphids + aShortIDs[ nNotDef ] = aShortIDs[0]; + aTempEncs[ nNotDef ] = aTempEncs[0]; + aShortIDs[0] = 0; + aTempEncs[0] = 0; + } + SAL_WARN_IF( nGlyphCount >= 257, "vcl", "too many glyphs for subsetting" ); + + // fill pWidth array + std::unique_ptr<sal_uInt16[]> pMetrics = + ::GetTTSimpleGlyphMetrics( aSftTTF.get(), aShortIDs, nGlyphCount, aIFSD.mbVertical ); + if( !pMetrics ) + return false; + sal_uInt16 nNotDefAdv = pMetrics[0]; + pMetrics[0] = pMetrics[nNotDef]; + pMetrics[nNotDef] = nNotDefAdv; + for( i = 0; i < nOrigCount; ++i ) + pGlyphWidths[i] = pMetrics[i]; + pMetrics.reset(); + + // write subset into destination file + nRC = ::CreateTTFromTTGlyphs( aSftTTF.get(), aToFile.getStr(), aShortIDs, + aTempEncs, nGlyphCount ); + return (nRC == SFErrCodes::Ok); +} + +const void* WinSalGraphics::GetEmbedFontData(const PhysicalFontFace* pFont, long* pDataLen) +{ + // create matching FontSelectPattern + // we need just enough to get to the font file data + FontSelectPattern aIFSD( *pFont, Size(0,1000), 1000.0, 0, false ); + + ScopedFont aOldFont(*this); + + HFONT hOldFont = nullptr; + ImplDoSetFont(aIFSD, pFont, hOldFont); + + // get the raw font file data + RawFontData aRawFontData( getHDC() ); + *pDataLen = aRawFontData.size(); + if( !aRawFontData.get() ) + return nullptr; + + const unsigned char* pData = aRawFontData.steal(); + return pData; +} + +void WinSalGraphics::FreeEmbedFontData( const void* pData, long /*nLen*/ ) +{ + delete[] static_cast<char const *>(pData); +} + +void WinSalGraphics::GetGlyphWidths( const PhysicalFontFace* pFont, + bool bVertical, + std::vector< sal_Int32 >& rWidths, + Ucs2UIntMap& rUnicodeEnc ) +{ + // create matching FontSelectPattern + // we need just enough to get to the font file data + FontSelectPattern aIFSD( *pFont, Size(0,1000), 1000.0, 0, false ); + + // TODO: much better solution: move SetFont and restoration of old font to caller + ScopedFont aOldFont(*this); + + HFONT hOldFont = nullptr; + ImplDoSetFont(aIFSD, pFont, hOldFont); + + // get raw font file data + const RawFontData xRawFontData( getHDC() ); + if( !xRawFontData.get() ) + return; + + // open font file + sal_uInt32 nFaceNum = 0; + if( !*xRawFontData.get() ) // TTC candidate + nFaceNum = ~0U; // indicate "TTC font extracts only" + + ScopedTrueTypeFont aSftTTF; + SFErrCodes nRC = aSftTTF.open( xRawFontData.get(), xRawFontData.size(), nFaceNum ); + if( nRC != SFErrCodes::Ok ) + return; + + int nGlyphs = GetTTGlyphCount( aSftTTF.get() ); + if( nGlyphs > 0 ) + { + rWidths.resize(nGlyphs); + std::vector<sal_uInt16> aGlyphIds(nGlyphs); + for( int i = 0; i < nGlyphs; i++ ) + aGlyphIds[i] = sal_uInt16(i); + std::unique_ptr<sal_uInt16[]> pMetrics = ::GetTTSimpleGlyphMetrics( aSftTTF.get(), + aGlyphIds.data(), + nGlyphs, + bVertical ); + if( pMetrics ) + { + for( int i = 0; i< nGlyphs; i++ ) + rWidths[i] = pMetrics[i]; + pMetrics.reset(); + rUnicodeEnc.clear(); + } + const WinFontFace* pWinFont = static_cast<const WinFontFace*>(pFont); + FontCharMapRef xFCMap = pWinFont->GetFontCharMap(); + SAL_WARN_IF( !xFCMap.is() || !xFCMap->GetCharCount(), "vcl", "no map" ); + + int nCharCount = xFCMap->GetCharCount(); + sal_uInt32 nChar = xFCMap->GetFirstChar(); + for( int i = 0; i < nCharCount; i++ ) + { + if( nChar < 0x00010000 ) + { + sal_uInt16 nGlyph = ::MapChar( aSftTTF.get(), + static_cast<sal_Ucs>(nChar)); + if( nGlyph ) + rUnicodeEnc[ static_cast<sal_Unicode>(nChar) ] = nGlyph; + } + nChar = xFCMap->GetNextChar( nChar ); + } + + xFCMap = nullptr; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/salgdi.cxx b/vcl/win/gdi/salgdi.cxx new file mode 100644 index 000000000..4b47b10b8 --- /dev/null +++ b/vcl/win/gdi/salgdi.cxx @@ -0,0 +1,1058 @@ +/* -*- 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 <string.h> +#include <svsys.h> +#include <rtl/strbuf.hxx> +#include <tools/poly.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <win/wincomp.hxx> +#include <win/saldata.hxx> +#include <win/salgdi.h> +#include <win/salframe.h> +#include <win/salvd.h> +#include <win/winlayout.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +#include <salgdiimpl.hxx> +#include "gdiimpl.hxx" +#include <opengl/win/gdiimpl.hxx> + +#include <vcl/opengl/OpenGLHelper.hxx> + +#include <config_features.h> +#include <vcl/skia/SkiaHelper.hxx> +#if HAVE_FEATURE_SKIA +#include <skia/win/gdiimpl.hxx> +#endif + + +#define DITHER_PAL_DELTA 51 +#define DITHER_PAL_STEPS 6 +#define DITHER_PAL_COUNT (DITHER_PAL_STEPS*DITHER_PAL_STEPS*DITHER_PAL_STEPS) +#define DITHER_MAX_SYSCOLOR 16 +#define DITHER_EXTRA_COLORS 1 + +namespace +{ + +struct SysColorEntry +{ + DWORD nRGB; + SysColorEntry* pNext; +}; + +SysColorEntry* pFirstSysColor = nullptr; +SysColorEntry* pActSysColor = nullptr; + +void DeleteSysColorList() +{ + SysColorEntry* pEntry = pFirstSysColor; + pActSysColor = pFirstSysColor = nullptr; + + while( pEntry ) + { + SysColorEntry* pTmp = pEntry->pNext; + delete pEntry; + pEntry = pTmp; + } +} + +} // namespace + +// Blue7 +static PALETTEENTRY aImplExtraColor1 = +{ + 0, 184, 255, 0 +}; + +static PALETTEENTRY aImplSalSysPalEntryAry[ DITHER_MAX_SYSCOLOR ] = +{ +{ 0, 0, 0, 0 }, +{ 0, 0, 0x80, 0 }, +{ 0, 0x80, 0, 0 }, +{ 0, 0x80, 0x80, 0 }, +{ 0x80, 0, 0, 0 }, +{ 0x80, 0, 0x80, 0 }, +{ 0x80, 0x80, 0, 0 }, +{ 0x80, 0x80, 0x80, 0 }, +{ 0xC0, 0xC0, 0xC0, 0 }, +{ 0, 0, 0xFF, 0 }, +{ 0, 0xFF, 0, 0 }, +{ 0, 0xFF, 0xFF, 0 }, +{ 0xFF, 0, 0, 0 }, +{ 0xFF, 0, 0xFF, 0 }, +{ 0xFF, 0xFF, 0, 0 }, +{ 0xFF, 0xFF, 0xFF, 0 } +}; + +// we must create pens with 1-pixel width; otherwise the S3-graphics card +// map has many paint problems when drawing polygons/polyLines and a +// complex is set +#define GSL_PEN_WIDTH 1 + +void ImplInitSalGDI() +{ + SalData* pSalData = GetSalData(); + + pSalData->mbResourcesAlreadyFreed = false; + + // init stock brushes + pSalData->maStockPenColorAry[0] = PALETTERGB( 0, 0, 0 ); + pSalData->maStockPenColorAry[1] = PALETTERGB( 0xFF, 0xFF, 0xFF ); + pSalData->maStockPenColorAry[2] = PALETTERGB( 0xC0, 0xC0, 0xC0 ); + pSalData->maStockPenColorAry[3] = PALETTERGB( 0x80, 0x80, 0x80 ); + pSalData->mhStockPenAry[0] = CreatePen( PS_SOLID, GSL_PEN_WIDTH, pSalData->maStockPenColorAry[0] ); + pSalData->mhStockPenAry[1] = CreatePen( PS_SOLID, GSL_PEN_WIDTH, pSalData->maStockPenColorAry[1] ); + pSalData->mhStockPenAry[2] = CreatePen( PS_SOLID, GSL_PEN_WIDTH, pSalData->maStockPenColorAry[2] ); + pSalData->mhStockPenAry[3] = CreatePen( PS_SOLID, GSL_PEN_WIDTH, pSalData->maStockPenColorAry[3] ); + pSalData->mnStockPenCount = 4; + + pSalData->maStockBrushColorAry[0] = PALETTERGB( 0, 0, 0 ); + pSalData->maStockBrushColorAry[1] = PALETTERGB( 0xFF, 0xFF, 0xFF ); + pSalData->maStockBrushColorAry[2] = PALETTERGB( 0xC0, 0xC0, 0xC0 ); + pSalData->maStockBrushColorAry[3] = PALETTERGB( 0x80, 0x80, 0x80 ); + pSalData->mhStockBrushAry[0] = CreateSolidBrush( pSalData->maStockBrushColorAry[0] ); + pSalData->mhStockBrushAry[1] = CreateSolidBrush( pSalData->maStockBrushColorAry[1] ); + pSalData->mhStockBrushAry[2] = CreateSolidBrush( pSalData->maStockBrushColorAry[2] ); + pSalData->mhStockBrushAry[3] = CreateSolidBrush( pSalData->maStockBrushColorAry[3] ); + pSalData->mnStockBrushCount = 4; + + // initialize cache of device contexts + pSalData->mpHDCCache = new HDCCache[ CACHESIZE_HDC ]; + memset( pSalData->mpHDCCache, 0, CACHESIZE_HDC * sizeof( HDCCache ) ); + + // initialize temporary font lists + pSalData->mpSharedTempFontItem = nullptr; + pSalData->mpOtherTempFontItem = nullptr; + + // support palettes for 256 color displays + HDC hDC = GetDC( nullptr ); + int nBitsPixel = GetDeviceCaps( hDC, BITSPIXEL ); + int nPlanes = GetDeviceCaps( hDC, PLANES ); + int nRasterCaps = GetDeviceCaps( hDC, RASTERCAPS ); + int nBitCount = nBitsPixel * nPlanes; + + if ( (nBitCount > 8) && (nBitCount < 24) ) + { + // test if we have to dither + HDC hMemDC = ::CreateCompatibleDC( hDC ); + HBITMAP hMemBmp = ::CreateCompatibleBitmap( hDC, 8, 8 ); + HBITMAP hBmpOld = static_cast<HBITMAP>(::SelectObject( hMemDC, hMemBmp )); + HBRUSH hMemBrush = ::CreateSolidBrush( PALETTERGB( 175, 171, 169 ) ); + HBRUSH hBrushOld = static_cast<HBRUSH>(::SelectObject( hMemDC, hMemBrush )); + bool bDither16 = true; + + ::PatBlt( hMemDC, 0, 0, 8, 8, PATCOPY ); + const COLORREF aCol( ::GetPixel( hMemDC, 0, 0 ) ); + + for( int nY = 0; ( nY < 8 ) && bDither16; nY++ ) + for( int nX = 0; ( nX < 8 ) && bDither16; nX++ ) + if( ::GetPixel( hMemDC, nX, nY ) != aCol ) + bDither16 = false; + + ::SelectObject( hMemDC, hBrushOld ); + ::DeleteObject( hMemBrush ); + ::SelectObject( hMemDC, hBmpOld ); + ::DeleteObject( hMemBmp ); + ::DeleteDC( hMemDC ); + + if( bDither16 ) + { + // create DIBPattern for 16Bit dithering + long n; + + pSalData->mhDitherDIB = GlobalAlloc( GMEM_FIXED, sizeof( BITMAPINFOHEADER ) + 192 ); + pSalData->mpDitherDIB = static_cast<BYTE*>(GlobalLock( pSalData->mhDitherDIB )); + pSalData->mpDitherDiff = new long[ 256 ]; + pSalData->mpDitherLow = new BYTE[ 256 ]; + pSalData->mpDitherHigh = new BYTE[ 256 ]; + pSalData->mpDitherDIBData = pSalData->mpDitherDIB + sizeof( BITMAPINFOHEADER ); + memset( pSalData->mpDitherDIB, 0, sizeof( BITMAPINFOHEADER ) ); + + BITMAPINFOHEADER* pBIH = reinterpret_cast<BITMAPINFOHEADER*>(pSalData->mpDitherDIB); + + pBIH->biSize = sizeof( BITMAPINFOHEADER ); + pBIH->biWidth = 8; + pBIH->biHeight = 8; + pBIH->biPlanes = 1; + pBIH->biBitCount = 24; + + for( n = 0; n < 256; n++ ) + pSalData->mpDitherDiff[ n ] = n - ( n & 248L ); + + for( n = 0; n < 256; n++ ) + pSalData->mpDitherLow[ n ] = static_cast<BYTE>( n & 248 ); + + for( n = 0; n < 256; n++ ) + pSalData->mpDitherHigh[ n ] = static_cast<BYTE>(std::min( pSalData->mpDitherLow[ n ] + 8, 255 )); + } + } + else if ( (nRasterCaps & RC_PALETTE) && (nBitCount == 8) ) + { + BYTE nRed, nGreen, nBlue; + BYTE nR, nG, nB; + PALETTEENTRY* pPalEntry; + LOGPALETTE* pLogPal; + const sal_uInt16 nDitherPalCount = DITHER_PAL_COUNT; + sal_uLong nTotalCount = DITHER_MAX_SYSCOLOR + nDitherPalCount + DITHER_EXTRA_COLORS; + + // create logical palette + pLogPal = reinterpret_cast<LOGPALETTE*>(new char[ sizeof( LOGPALETTE ) + ( nTotalCount * sizeof( PALETTEENTRY ) ) ]); + pLogPal->palVersion = 0x0300; + pLogPal->palNumEntries = static_cast<sal_uInt16>(nTotalCount); + pPalEntry = pLogPal->palPalEntry; + + // Standard colors + memcpy( pPalEntry, aImplSalSysPalEntryAry, DITHER_MAX_SYSCOLOR * sizeof( PALETTEENTRY ) ); + pPalEntry += DITHER_MAX_SYSCOLOR; + + // own palette (6/6/6) + for( nB=0, nBlue=0; nB < DITHER_PAL_STEPS; nB++, nBlue += DITHER_PAL_DELTA ) + { + for( nG=0, nGreen=0; nG < DITHER_PAL_STEPS; nG++, nGreen += DITHER_PAL_DELTA ) + { + for( nR=0, nRed=0; nR < DITHER_PAL_STEPS; nR++, nRed += DITHER_PAL_DELTA ) + { + pPalEntry->peRed = nRed; + pPalEntry->peGreen = nGreen; + pPalEntry->peBlue = nBlue; + pPalEntry->peFlags = 0; + pPalEntry++; + } + } + } + + // insert special 'Blue' as standard drawing color + *pPalEntry++ = aImplExtraColor1; + + // create palette + pSalData->mhDitherPal = CreatePalette( pLogPal ); + delete[] reinterpret_cast<char*>(pLogPal); + + if( pSalData->mhDitherPal ) + { + // create DIBPattern for 8Bit dithering + long const nSize = sizeof( BITMAPINFOHEADER ) + ( 256 * sizeof( short ) ) + 64; + long n; + + pSalData->mhDitherDIB = GlobalAlloc( GMEM_FIXED, nSize ); + pSalData->mpDitherDIB = static_cast<BYTE*>(GlobalLock( pSalData->mhDitherDIB )); + pSalData->mpDitherDiff = new long[ 256 ]; + pSalData->mpDitherLow = new BYTE[ 256 ]; + pSalData->mpDitherHigh = new BYTE[ 256 ]; + pSalData->mpDitherDIBData = pSalData->mpDitherDIB + sizeof( BITMAPINFOHEADER ) + ( 256 * sizeof( short ) ); + memset( pSalData->mpDitherDIB, 0, sizeof( BITMAPINFOHEADER ) ); + + BITMAPINFOHEADER* pBIH = reinterpret_cast<BITMAPINFOHEADER*>(pSalData->mpDitherDIB); + short* pColors = reinterpret_cast<short*>( pSalData->mpDitherDIB + sizeof( BITMAPINFOHEADER ) ); + + pBIH->biSize = sizeof( BITMAPINFOHEADER ); + pBIH->biWidth = 8; + pBIH->biHeight = 8; + pBIH->biPlanes = 1; + pBIH->biBitCount = 8; + + for( n = 0; n < nDitherPalCount; n++ ) + pColors[ n ] = static_cast<short>( n + DITHER_MAX_SYSCOLOR ); + + for( n = 0; n < 256; n++ ) + pSalData->mpDitherDiff[ n ] = n % 51; + + for( n = 0; n < 256; n++ ) + pSalData->mpDitherLow[ n ] = static_cast<BYTE>( n / 51 ); + + for( n = 0; n < 256; n++ ) + pSalData->mpDitherHigh[ n ] = static_cast<BYTE>(std::min( pSalData->mpDitherLow[ n ] + 1, 5 )); + } + + // get system color entries + ImplUpdateSysColorEntries(); + } + + ReleaseDC( nullptr, hDC ); +} + +void ImplFreeSalGDI() +{ + SalData* pSalData = GetSalData(); + + if (pSalData->mbResourcesAlreadyFreed) + return; + + // destroy stock objects + int i; + for ( i = 0; i < pSalData->mnStockPenCount; i++ ) + DeletePen( pSalData->mhStockPenAry[i] ); + for ( i = 0; i < pSalData->mnStockBrushCount; i++ ) + DeleteBrush( pSalData->mhStockBrushAry[i] ); + + // delete 50% Brush + if ( pSalData->mh50Brush ) + { + DeleteBrush( pSalData->mh50Brush ); + pSalData->mh50Brush = nullptr; + } + + // delete 50% Bitmap + if ( pSalData->mh50Bmp ) + { + DeleteBitmap( pSalData->mh50Bmp ); + pSalData->mh50Bmp = nullptr; + } + + ImplClearHDCCache( pSalData ); + delete[] pSalData->mpHDCCache; + + // delete Ditherpalette, if existing + if ( pSalData->mhDitherPal ) + { + DeleteObject( pSalData->mhDitherPal ); + pSalData->mhDitherPal = nullptr; + } + + // delete buffers for dithering DIB patterns, if necessary + if ( pSalData->mhDitherDIB ) + { + GlobalUnlock( pSalData->mhDitherDIB ); + GlobalFree( pSalData->mhDitherDIB ); + pSalData->mhDitherDIB = nullptr; + delete[] pSalData->mpDitherDiff; + delete[] pSalData->mpDitherLow; + delete[] pSalData->mpDitherHigh; + } + + DeleteSysColorList(); + + // delete icon cache + SalIcon* pIcon = pSalData->mpFirstIcon; + pSalData->mpFirstIcon = nullptr; + while( pIcon ) + { + SalIcon* pTmp = pIcon->pNext; + DestroyIcon( pIcon->hIcon ); + DestroyIcon( pIcon->hSmallIcon ); + delete pIcon; + pIcon = pTmp; + } + + // delete temporary font list + ImplReleaseTempFonts(*pSalData, true); + + pSalData->mbResourcesAlreadyFreed = true; +} + +int ImplIsSysColorEntry( Color nColor ) +{ + SysColorEntry* pEntry = pFirstSysColor; + const DWORD nTestRGB = static_cast<DWORD>(RGB( nColor.GetRed(), + nColor.GetGreen(), + nColor.GetBlue() )); + + while ( pEntry ) + { + if ( pEntry->nRGB == nTestRGB ) + return TRUE; + pEntry = pEntry->pNext; + } + + return FALSE; +} + +static int ImplIsPaletteEntry( BYTE nRed, BYTE nGreen, BYTE nBlue ) +{ + // dither color? + if ( !(nRed % DITHER_PAL_DELTA) && !(nGreen % DITHER_PAL_DELTA) && !(nBlue % DITHER_PAL_DELTA) ) + return TRUE; + + PALETTEENTRY* pPalEntry = aImplSalSysPalEntryAry; + + // standard palette color? + for ( sal_uInt16 i = 0; i < DITHER_MAX_SYSCOLOR; i++, pPalEntry++ ) + { + if( pPalEntry->peRed == nRed && pPalEntry->peGreen == nGreen && pPalEntry->peBlue == nBlue ) + return TRUE; + } + + // extra color? + if ( aImplExtraColor1.peRed == nRed && + aImplExtraColor1.peGreen == nGreen && + aImplExtraColor1.peBlue == nBlue ) + { + return TRUE; + } + + return FALSE; +} + +static void ImplInsertSysColorEntry( int nSysIndex ) +{ + const DWORD nRGB = GetSysColor( nSysIndex ); + + if ( !ImplIsPaletteEntry( GetRValue( nRGB ), GetGValue( nRGB ), GetBValue( nRGB ) ) ) + { + if ( !pFirstSysColor ) + { + pActSysColor = pFirstSysColor = new SysColorEntry; + pFirstSysColor->nRGB = nRGB; + pFirstSysColor->pNext = nullptr; + } + else + { + pActSysColor = pActSysColor->pNext = new SysColorEntry; + pActSysColor->nRGB = nRGB; + pActSysColor->pNext = nullptr; + } + } +} + +void ImplUpdateSysColorEntries() +{ + DeleteSysColorList(); + + // create new sys color list + ImplInsertSysColorEntry( COLOR_ACTIVEBORDER ); + ImplInsertSysColorEntry( COLOR_INACTIVEBORDER ); + ImplInsertSysColorEntry( COLOR_GRADIENTACTIVECAPTION ); + ImplInsertSysColorEntry( COLOR_GRADIENTINACTIVECAPTION ); + ImplInsertSysColorEntry( COLOR_3DFACE ); + ImplInsertSysColorEntry( COLOR_3DHILIGHT ); + ImplInsertSysColorEntry( COLOR_3DLIGHT ); + ImplInsertSysColorEntry( COLOR_3DSHADOW ); + ImplInsertSysColorEntry( COLOR_3DDKSHADOW ); + ImplInsertSysColorEntry( COLOR_INFOBK ); + ImplInsertSysColorEntry( COLOR_INFOTEXT ); + ImplInsertSysColorEntry( COLOR_BTNTEXT ); + ImplInsertSysColorEntry( COLOR_WINDOW ); + ImplInsertSysColorEntry( COLOR_WINDOWTEXT ); + ImplInsertSysColorEntry( COLOR_HIGHLIGHT ); + ImplInsertSysColorEntry( COLOR_HIGHLIGHTTEXT ); + ImplInsertSysColorEntry( COLOR_MENU ); + ImplInsertSysColorEntry( COLOR_MENUTEXT ); + ImplInsertSysColorEntry( COLOR_ACTIVECAPTION ); + ImplInsertSysColorEntry( COLOR_CAPTIONTEXT ); + ImplInsertSysColorEntry( COLOR_INACTIVECAPTION ); + ImplInsertSysColorEntry( COLOR_INACTIVECAPTIONTEXT ); +} + +void WinSalGraphics::InitGraphics() +{ + // calculate the minimal line width for the printer + if ( isPrinter() ) + { + int nDPIX = GetDeviceCaps( getHDC(), LOGPIXELSX ); + if ( nDPIX <= 300 ) + mnPenWidth = 0; + else + mnPenWidth = nDPIX/300; + } + + ::SetTextAlign( getHDC(), TA_BASELINE | TA_LEFT | TA_NOUPDATECP ); + ::SetBkMode( getHDC(), TRANSPARENT ); + ::SetROP2( getHDC(), R2_COPYPEN ); + + mpImpl->Init(); +} + +void WinSalGraphics::DeInitGraphics() +{ + // clear clip region + SelectClipRgn( getHDC(), nullptr ); + // select default objects + if ( mhDefPen ) + SelectPen( getHDC(), mhDefPen ); + if ( mhDefBrush ) + SelectBrush( getHDC(), mhDefBrush ); + if ( mhDefFont ) + SelectFont( getHDC(), mhDefFont ); + + mpImpl->DeInit(); +} + +HDC ImplGetCachedDC( sal_uLong nID, HBITMAP hBmp ) +{ + SalData* pSalData = GetSalData(); + HDCCache* pC = &pSalData->mpHDCCache[ nID ]; + + if( !pC->mhDC ) + { + HDC hDC = GetDC( nullptr ); + + // create new DC with DefaultBitmap + pC->mhDC = CreateCompatibleDC( hDC ); + + if( pSalData->mhDitherPal ) + { + pC->mhDefPal = SelectPalette( pC->mhDC, pSalData->mhDitherPal, TRUE ); + RealizePalette( pC->mhDC ); + } + + pC->mhSelBmp = CreateCompatibleBitmap( hDC, CACHED_HDC_DEFEXT, CACHED_HDC_DEFEXT ); + pC->mhDefBmp = static_cast<HBITMAP>(SelectObject( pC->mhDC, pC->mhSelBmp )); + + ReleaseDC( nullptr, hDC ); + } + + if ( hBmp ) + SelectObject( pC->mhDC, pC->mhActBmp = hBmp ); + else + pC->mhActBmp = nullptr; + + return pC->mhDC; +} + +void ImplReleaseCachedDC( sal_uLong nID ) +{ + SalData* pSalData = GetSalData(); + HDCCache* pC = &pSalData->mpHDCCache[ nID ]; + + if ( pC->mhActBmp ) + SelectObject( pC->mhDC, pC->mhSelBmp ); +} + +void ImplClearHDCCache( SalData* pData ) +{ + for( sal_uLong i = 0; i < CACHESIZE_HDC; i++ ) + { + HDCCache* pC = &pData->mpHDCCache[ i ]; + + if( pC->mhDC ) + { + SelectObject( pC->mhDC, pC->mhDefBmp ); + + if( pC->mhDefPal ) + SelectPalette( pC->mhDC, pC->mhDefPal, TRUE ); + + DeleteDC( pC->mhDC ); + DeleteObject( pC->mhSelBmp ); + } + } +} + +std::unique_ptr< CompatibleDC > CompatibleDC::create(SalGraphics &rGraphics, int x, int y, int width, int height) +{ +#if HAVE_FEATURE_SKIA + if (SkiaHelper::isVCLSkiaEnabled()) + return std::make_unique< SkiaCompatibleDC >( rGraphics, x, y, width, height ); +#endif + if (OpenGLHelper::isVCLOpenGLEnabled()) + return std::make_unique< OpenGLCompatibleDC >( rGraphics, x, y, width, height ); + return std::unique_ptr< CompatibleDC >( new CompatibleDC( rGraphics, x, y, width, height )); +} + +CompatibleDC::CompatibleDC(SalGraphics &rGraphics, int x, int y, int width, int height, bool disable) + : mhBitmap(nullptr) + , mpData(nullptr) + , maRects(0, 0, width, height, x, y, width, height) + , mpImpl(nullptr) +{ + WinSalGraphics& rWinGraphics = static_cast<WinSalGraphics&>(rGraphics); + + if( disable ) + { + // we avoid the OpenGL drawing, instead we draw directly to the DC + mhCompatibleDC = rWinGraphics.getHDC(); + return; + } + + mpImpl = dynamic_cast<WinSalGraphicsImplBase*>(rWinGraphics.GetImpl()); + assert(mpImpl != nullptr); + mhCompatibleDC = CreateCompatibleDC(rWinGraphics.getHDC()); + + // move the origin so that we always paint at 0,0 - to keep the bitmap + // small + OffsetViewportOrgEx(mhCompatibleDC, -x, -y, nullptr); + + mhBitmap = WinSalVirtualDevice::ImplCreateVirDevBitmap(mhCompatibleDC, width, height, 32, reinterpret_cast<void **>(&mpData)); + + mhOrigBitmap = static_cast<HBITMAP>(SelectObject(mhCompatibleDC, mhBitmap)); +} + +CompatibleDC::~CompatibleDC() +{ + if (mpImpl) + { + SelectObject(mhCompatibleDC, mhOrigBitmap); + DeleteObject(mhBitmap); + DeleteDC(mhCompatibleDC); + } +} + +void CompatibleDC::fill(sal_uInt32 color) +{ + if (!mpData) + return; + + sal_uInt32 *p = mpData; + for (int i = maRects.mnSrcWidth * maRects.mnSrcHeight; i > 0; --i) + *p++ = color; +} + +WinSalGraphics::WinSalGraphics(WinSalGraphics::Type eType, bool bScreen, HWND hWnd, SalGeometryProvider *pProvider): + mhLocalDC(nullptr), + mbPrinter(eType == WinSalGraphics::PRINTER), + mbVirDev(eType == WinSalGraphics::VIRTUAL_DEVICE), + mbWindow(eType == WinSalGraphics::WINDOW), + mbScreen(bScreen), + mhWnd(hWnd), + mhRegion(nullptr), + mhDefPen(nullptr), + mhDefBrush(nullptr), + mhDefFont(nullptr), + mhDefPal(nullptr), + mpStdClipRgnData(nullptr), + mnPenWidth(GSL_PEN_WIDTH) +{ +#if HAVE_FEATURE_SKIA + if (SkiaHelper::isVCLSkiaEnabled() && !mbPrinter) + mpImpl.reset(new WinSkiaSalGraphicsImpl(*this, pProvider)); + else +#endif + if (OpenGLHelper::isVCLOpenGLEnabled() && !mbPrinter) + mpImpl.reset(new WinOpenGLSalGraphicsImpl(*this, pProvider)); + else + mpImpl.reset(new WinSalGraphicsImpl(*this)); +} + +WinSalGraphics::~WinSalGraphics() +{ + // free obsolete GDI objects + ReleaseFonts(); + + if ( mhRegion ) + { + DeleteRegion( mhRegion ); + mhRegion = nullptr; + } + + // delete cache data + delete [] reinterpret_cast<BYTE*>(mpStdClipRgnData); +} + +SalGraphicsImpl* WinSalGraphics::GetImpl() const +{ + return mpImpl.get(); +} + +bool WinSalGraphics::isPrinter() const +{ + return mbPrinter; +} + +bool WinSalGraphics::isVirtualDevice() const +{ + return mbVirDev; +} + +bool WinSalGraphics::isWindow() const +{ + return mbWindow; +} + +bool WinSalGraphics::isScreen() const +{ + return mbScreen; +} + +HWND WinSalGraphics::gethWnd() +{ + return mhWnd; +} + +void WinSalGraphics::setHWND(HWND hWnd) +{ + mhWnd = hWnd; +} + +HPALETTE WinSalGraphics::getDefPal() const +{ + return mhDefPal; +} + +void WinSalGraphics::setDefPal(HPALETTE hDefPal) +{ + mhDefPal = hDefPal; +} + +HRGN WinSalGraphics::getRegion() const +{ + return mhRegion; +} + +void WinSalGraphics::GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY ) +{ + rDPIX = GetDeviceCaps( getHDC(), LOGPIXELSX ); + rDPIY = GetDeviceCaps( getHDC(), LOGPIXELSY ); + + // #111139# this fixes the symptom of div by zero on startup + // however, printing will fail most likely as communication with + // the printer seems not to work in this case + if( !rDPIX || !rDPIY ) + rDPIX = rDPIY = 600; +} + +sal_uInt16 WinSalGraphics::GetBitCount() const +{ + return mpImpl->GetBitCount(); +} + +long WinSalGraphics::GetGraphicsWidth() const +{ + return mpImpl->GetGraphicsWidth(); +} + +void WinSalGraphics::ResetClipRegion() +{ + mpImpl->ResetClipRegion(); +} + +bool WinSalGraphics::setClipRegion( const vcl::Region& i_rClip ) +{ + return mpImpl->setClipRegion( i_rClip ); +} + +void WinSalGraphics::SetLineColor() +{ + mpImpl->SetLineColor(); +} + +void WinSalGraphics::SetLineColor( Color nColor ) +{ + mpImpl->SetLineColor( nColor ); +} + +void WinSalGraphics::SetFillColor() +{ + mpImpl->SetFillColor(); +} + +void WinSalGraphics::SetFillColor( Color nColor ) +{ + mpImpl->SetFillColor( nColor ); +} + +void WinSalGraphics::SetXORMode( bool bSet, bool bInvertOnly ) +{ + mpImpl->SetXORMode( bSet, bInvertOnly ); +} + +void WinSalGraphics::SetROPLineColor( SalROPColor nROPColor ) +{ + mpImpl->SetROPLineColor( nROPColor ); +} + +void WinSalGraphics::SetROPFillColor( SalROPColor nROPColor ) +{ + mpImpl->SetROPFillColor( nROPColor ); +} + +void WinSalGraphics::drawPixel( long nX, long nY ) +{ + mpImpl->drawPixel( nX, nY ); +} + +void WinSalGraphics::drawPixel( long nX, long nY, Color nColor ) +{ + mpImpl->drawPixel( nX, nY, nColor ); +} + +void WinSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 ) +{ + mpImpl->drawLine( nX1, nY1, nX2, nY2 ); +} + +void WinSalGraphics::drawRect( long nX, long nY, long nWidth, long nHeight ) +{ + mpImpl->drawRect( nX, nY, nWidth, nHeight ); +} + +void WinSalGraphics::drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) +{ + mpImpl->drawPolyLine( nPoints, pPtAry ); +} + +void WinSalGraphics::drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) +{ + mpImpl->drawPolygon( nPoints, pPtAry ); +} + +void WinSalGraphics::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, + PCONSTSALPOINT* pPtAry ) +{ + mpImpl->drawPolyPolygon( nPoly, pPoints, pPtAry ); +} + +bool WinSalGraphics::drawPolyLineBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry ) +{ + return mpImpl->drawPolyLineBezier( nPoints, pPtAry, pFlgAry ); +} + +bool WinSalGraphics::drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const PolyFlags* pFlgAry ) +{ + return mpImpl->drawPolygonBezier( nPoints, pPtAry, pFlgAry ); +} + +bool WinSalGraphics::drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, + const SalPoint* const* pPtAry, const PolyFlags* const* pFlgAry ) +{ + return mpImpl->drawPolyPolygonBezier( nPoly, pPoints, pPtAry, pFlgAry ); +} + +static BYTE* ImplSearchEntry( BYTE* pSource, BYTE const * pDest, sal_uLong nComp, sal_uLong nSize ) +{ + while ( nComp-- >= nSize ) + { + sal_uLong i; + for ( i = 0; i < nSize; i++ ) + { + if ( ( pSource[i]&~0x20 ) != ( pDest[i]&~0x20 ) ) + break; + } + if ( i == nSize ) + return pSource; + pSource++; + } + return nullptr; +} + +static bool ImplGetBoundingBox( double* nNumb, BYTE* pSource, sal_uLong nSize ) +{ + bool bRetValue = false; + BYTE* pDest = ImplSearchEntry( pSource, reinterpret_cast<BYTE const *>("%%BoundingBox:"), nSize, 14 ); + if ( pDest ) + { + nNumb[0] = nNumb[1] = nNumb[2] = nNumb[3] = 0; + pDest += 14; + + int nSizeLeft = nSize - ( pDest - pSource ); + if ( nSizeLeft > 100 ) + nSizeLeft = 100; // only 100 bytes following the bounding box will be checked + + int i; + for ( i = 0; ( i < 4 ) && nSizeLeft; i++ ) + { + int nDivision = 1; + bool bDivision = false; + bool bNegative = false; + bool bValid = true; + + while ( ( --nSizeLeft ) && ( ( *pDest == ' ' ) || ( *pDest == 0x9 ) ) ) pDest++; + BYTE nByte = *pDest; + while ( nSizeLeft && ( nByte != ' ' ) && ( nByte != 0x9 ) && ( nByte != 0xd ) && ( nByte != 0xa ) ) + { + switch ( nByte ) + { + case '.' : + if ( bDivision ) + bValid = false; + else + bDivision = true; + break; + case '-' : + bNegative = true; + break; + default : + if ( ( nByte < '0' ) || ( nByte > '9' ) ) + nSizeLeft = 1; // error parsing the bounding box values + else if ( bValid ) + { + if ( bDivision ) + nDivision*=10; + nNumb[i] *= 10; + nNumb[i] += nByte - '0'; + } + break; + } + nSizeLeft--; + nByte = *(++pDest); + } + if ( bNegative ) + nNumb[i] = -nNumb[i]; + if ( bDivision && ( nDivision != 1 ) ) + nNumb[i] /= nDivision; + } + if ( i == 4 ) + bRetValue = true; + } + return bRetValue; +} + +#define POSTSCRIPT_BUFSIZE 0x4000 // MAXIMUM BUFSIZE EQ 0xFFFF + +bool WinSalGraphics::drawEPS( long nX, long nY, long nWidth, long nHeight, void* pPtr, sal_uInt32 nSize ) +{ + bool bRetValue = false; + + if ( mbPrinter ) + { + int nEscape = POSTSCRIPT_PASSTHROUGH; + + if ( Escape( getHDC(), QUERYESCSUPPORT, sizeof( int ), reinterpret_cast<LPSTR>(&nEscape), nullptr ) ) + { + double nBoundingBox[4]; + + if ( ImplGetBoundingBox( nBoundingBox, static_cast<BYTE*>(pPtr), nSize ) ) + { + OStringBuffer aBuf( POSTSCRIPT_BUFSIZE ); + + // reserve place for a sal_uInt16 + aBuf.append( "aa" ); + + // #107797# Write out EPS encapsulation header + + // directly taken from the PLRM 3.0, p. 726. Note: + // this will definitely cause problems when + // recursively creating and embedding PostScript files + // in OOo, since we use statically-named variables + // here (namely, b4_Inc_state_salWin, dict_count_salWin and + // op_count_salWin). Currently, I have no idea on how to + // work around that, except from scanning and + // interpreting the EPS for unused identifiers. + + // append the real text + aBuf.append( "\n\n/b4_Inc_state_salWin save def\n" + "/dict_count_salWin countdictstack def\n" + "/op_count_salWin count 1 sub def\n" + "userdict begin\n" + "/showpage {} def\n" + "0 setgray 0 setlinecap\n" + "1 setlinewidth 0 setlinejoin\n" + "10 setmiterlimit [] 0 setdash newpath\n" + "/languagelevel where\n" + "{\n" + " pop languagelevel\n" + " 1 ne\n" + " {\n" + " false setstrokeadjust false setoverprint\n" + " } if\n" + "} if\n\n" ); + + // #i10737# Apply clipping manually + + // Windows seems to ignore any clipping at the HDC, + // when followed by a POSTSCRIPT_PASSTHROUGH + + // Check whether we've got a clipping, consisting of + // exactly one rect (other cases should be, but aren't + // handled currently) + + // TODO: Handle more than one rectangle here (take + // care, the buffer can handle only POSTSCRIPT_BUFSIZE + // characters!) + if ( mhRegion != nullptr && + mpStdClipRgnData != nullptr && + mpClipRgnData == mpStdClipRgnData && + mpClipRgnData->rdh.nCount == 1 ) + { + RECT* pRect = &(mpClipRgnData->rdh.rcBound); + + aBuf.append( "\nnewpath\n" ); + aBuf.append( pRect->left ); + aBuf.append( " " ); + aBuf.append( pRect->top ); + aBuf.append( " moveto\n" ); + aBuf.append( pRect->right ); + aBuf.append( " " ); + aBuf.append( pRect->top ); + aBuf.append( " lineto\n" ); + aBuf.append( pRect->right ); + aBuf.append( " " ); + aBuf.append( pRect->bottom ); + aBuf.append( " lineto\n" ); + aBuf.append( pRect->left ); + aBuf.append( " " ); + aBuf.append( pRect->bottom ); + aBuf.append( " lineto\n" + "closepath\n" + "clip\n" + "newpath\n" ); + } + + // #107797# Write out buffer + + *reinterpret_cast<sal_uInt16*>(const_cast<char *>(aBuf.getStr())) = static_cast<sal_uInt16>( aBuf.getLength() - 2 ); + Escape ( getHDC(), nEscape, aBuf.getLength(), aBuf.getStr(), nullptr ); + + // #107797# Write out EPS transformation code + + double dM11 = nWidth / ( nBoundingBox[2] - nBoundingBox[0] ); + double dM22 = nHeight / (nBoundingBox[1] - nBoundingBox[3] ); + // reserve a sal_uInt16 again + aBuf.setLength( 2 ); + aBuf.append( "\n\n[" ); + aBuf.append( dM11 ); + aBuf.append( " 0 0 " ); + aBuf.append( dM22 ); + aBuf.append( ' ' ); + aBuf.append( nX - ( dM11 * nBoundingBox[0] ) ); + aBuf.append( ' ' ); + aBuf.append( nY - ( dM22 * nBoundingBox[3] ) ); + aBuf.append( "] concat\n" + "%%BeginDocument:\n" ); + *reinterpret_cast<sal_uInt16*>(const_cast<char *>(aBuf.getStr())) = static_cast<sal_uInt16>( aBuf.getLength() - 2 ); + Escape ( getHDC(), nEscape, aBuf.getLength(), aBuf.getStr(), nullptr ); + + // #107797# Write out actual EPS content + + sal_uLong nToDo = nSize; + sal_uLong nDoNow; + while ( nToDo ) + { + nDoNow = nToDo; + if ( nToDo > POSTSCRIPT_BUFSIZE - 2 ) + nDoNow = POSTSCRIPT_BUFSIZE - 2; + // the following is based on the string buffer allocation + // of size POSTSCRIPT_BUFSIZE at construction time of aBuf + *reinterpret_cast<sal_uInt16*>(const_cast<char *>(aBuf.getStr())) = static_cast<sal_uInt16>(nDoNow); + memcpy( const_cast<char *>(aBuf.getStr() + 2), static_cast<BYTE*>(pPtr) + nSize - nToDo, nDoNow ); + sal_uLong nResult = Escape ( getHDC(), nEscape, nDoNow + 2, aBuf.getStr(), nullptr ); + if (!nResult ) + break; + nToDo -= nResult; + } + + // #107797# Write out EPS encapsulation footer + + // reserve a sal_uInt16 again + aBuf.setLength( 2 ); + aBuf.append( "%%EndDocument\n" + "count op_count_salWin sub {pop} repeat\n" + "countdictstack dict_count_salWin sub {end} repeat\n" + "b4_Inc_state_salWin restore\n\n" ); + *reinterpret_cast<sal_uInt16*>(const_cast<char *>(aBuf.getStr())) = static_cast<sal_uInt16>( aBuf.getLength() - 2 ); + Escape ( getHDC(), nEscape, aBuf.getLength(), aBuf.getStr(), nullptr ); + bRetValue = true; + } + } + } + + return bRetValue; +} + +SystemGraphicsData WinSalGraphics::GetGraphicsData() const +{ + SystemGraphicsData aRes; + aRes.nSize = sizeof(aRes); + aRes.hDC = getHDC(); + return aRes; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/salgdi2.cxx b/vcl/win/gdi/salgdi2.cxx new file mode 100644 index 000000000..fdac864d0 --- /dev/null +++ b/vcl/win/gdi/salgdi2.cxx @@ -0,0 +1,248 @@ +/* -*- 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 <string.h> +#include <stdlib.h> + +#include <svsys.h> + +#include <win/wincomp.hxx> +#include <win/salbmp.h> +#include <win/saldata.hxx> +#include <win/salids.hrc> +#include <win/salgdi.h> +#include <win/salframe.h> +#include <opengl/salbmp.hxx> + +#include <vcl/BitmapAccessMode.hxx> +#include <vcl/BitmapBuffer.hxx> +#include <vcl/BitmapPalette.hxx> +#include <vcl/Scanline.hxx> +#include <vcl/bitmapaccess.hxx> +#include <outdata.hxx> +#include <salgdiimpl.hxx> +#include <opengl/win/gdiimpl.hxx> + +#include <config_features.h> +#if HAVE_FEATURE_SKIA +#include <skia/win/gdiimpl.hxx> +#include <skia/salbmp.hxx> +#endif + + +bool WinSalGraphics::supportsOperation( OutDevSupportType eType ) const +{ + return mpImpl->supportsOperation(eType); +} + +void WinSalGraphics::copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) +{ + mpImpl->copyBits( rPosAry, pSrcGraphics ); +} + +void WinSalGraphics::copyArea( long nDestX, long nDestY, + long nSrcX, long nSrcY, + long nSrcWidth, long nSrcHeight, + bool bWindowInvalidate ) +{ + mpImpl->copyArea( nDestX, nDestY, nSrcX, nSrcY, + nSrcWidth, nSrcHeight, bWindowInvalidate ); +} + +namespace +{ + +class ColorScanlineConverter +{ +public: + ScanlineFormat meSourceFormat; + + int mnComponentSize; + int mnComponentExchangeIndex; + + long mnScanlineSize; + + ColorScanlineConverter(ScanlineFormat eSourceFormat, int nComponentSize, long nScanlineSize) + : meSourceFormat(eSourceFormat) + , mnComponentSize(nComponentSize) + , mnComponentExchangeIndex(0) + , mnScanlineSize(nScanlineSize) + { + if (meSourceFormat == ScanlineFormat::N32BitTcAbgr || + meSourceFormat == ScanlineFormat::N32BitTcArgb) + { + mnComponentExchangeIndex = 1; + } + } + + void convertScanline(sal_uInt8* pSource, sal_uInt8* pDestination) + { + for (long x = 0; x < mnScanlineSize; x += mnComponentSize) + { + for (int i = 0; i < mnComponentSize; ++i) + { + pDestination[x + i] = pSource[x + i]; + } + pDestination[x + mnComponentExchangeIndex + 0] = pSource[x + mnComponentExchangeIndex + 2]; + pDestination[x + mnComponentExchangeIndex + 2] = pSource[x + mnComponentExchangeIndex + 0]; + } + } +}; + +void convertToWinSalBitmap(SalBitmap& rSalBitmap, WinSalBitmap& rWinSalBitmap) +{ + BitmapPalette aBitmapPalette; + OpenGLSalBitmap* pGLSalBitmap = dynamic_cast<OpenGLSalBitmap*>(&rSalBitmap); + if (pGLSalBitmap != nullptr) + { + aBitmapPalette = pGLSalBitmap->GetBitmapPalette(); + } +#if HAVE_FEATURE_SKIA + if(SkiaSalBitmap* pSkiaSalBitmap = dynamic_cast<SkiaSalBitmap*>(&rSalBitmap)) + aBitmapPalette = pSkiaSalBitmap->Palette(); +#endif + + BitmapBuffer* pRead = rSalBitmap.AcquireBuffer(BitmapAccessMode::Read); + + rWinSalBitmap.Create(rSalBitmap.GetSize(), rSalBitmap.GetBitCount(), aBitmapPalette); + BitmapBuffer* pWrite = rWinSalBitmap.AcquireBuffer(BitmapAccessMode::Write); + + sal_uInt8* pSource(pRead->mpBits); + sal_uInt8* pDestination(pWrite->mpBits); + long readRowChange = pRead->mnScanlineSize; + if(pRead->mnFormat & ScanlineFormat::TopDown) + { + pSource += pRead->mnScanlineSize * (pRead->mnHeight - 1); + readRowChange = -readRowChange; + } + + std::unique_ptr<ColorScanlineConverter> pConverter; + + if (RemoveScanline(pRead->mnFormat) == ScanlineFormat::N24BitTcRgb) + pConverter.reset(new ColorScanlineConverter(ScanlineFormat::N24BitTcRgb, + 3, pRead->mnScanlineSize)); + else if (RemoveScanline(pRead->mnFormat) == ScanlineFormat::N32BitTcRgba) + pConverter.reset(new ColorScanlineConverter(ScanlineFormat::N32BitTcRgba, + 4, pRead->mnScanlineSize)); + if (pConverter) + { + for (long y = 0; y < pRead->mnHeight; y++) + { + pConverter->convertScanline(pSource, pDestination); + pSource += readRowChange; + pDestination += pWrite->mnScanlineSize; + } + } + else + { + for (long y = 0; y < pRead->mnHeight; y++) + { + memcpy(pDestination, pSource, pRead->mnScanlineSize); + pSource += readRowChange; + pDestination += pWrite->mnScanlineSize; + } + } + rWinSalBitmap.ReleaseBuffer(pWrite, BitmapAccessMode::Write); + + rSalBitmap.ReleaseBuffer(pRead, BitmapAccessMode::Read); +} + +} // end anonymous namespace + +void WinSalGraphics::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap) +{ + if (dynamic_cast<WinOpenGLSalGraphicsImpl*>(mpImpl.get()) == nullptr && +#if HAVE_FEATURE_SKIA + dynamic_cast<WinSkiaSalGraphicsImpl*>(mpImpl.get()) == nullptr && +#endif + dynamic_cast<const WinSalBitmap*>(&rSalBitmap) == nullptr) + { + std::unique_ptr<WinSalBitmap> pWinSalBitmap(new WinSalBitmap()); + SalBitmap& rConstBitmap = const_cast<SalBitmap&>(rSalBitmap); + convertToWinSalBitmap(rConstBitmap, *pWinSalBitmap); + mpImpl->drawBitmap(rPosAry, *pWinSalBitmap); + } + else + { + mpImpl->drawBitmap(rPosAry, rSalBitmap); + } +} + +void WinSalGraphics::drawBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rSSalBitmap, + const SalBitmap& rSTransparentBitmap ) +{ + if (dynamic_cast<WinOpenGLSalGraphicsImpl*>(mpImpl.get()) == nullptr && +#if HAVE_FEATURE_SKIA + dynamic_cast<WinSkiaSalGraphicsImpl*>(mpImpl.get()) == nullptr && +#endif + dynamic_cast<const WinSalBitmap*>(&rSSalBitmap) == nullptr) + { + std::unique_ptr<WinSalBitmap> pWinSalBitmap(new WinSalBitmap()); + SalBitmap& rConstBitmap = const_cast<SalBitmap&>(rSSalBitmap); + convertToWinSalBitmap(rConstBitmap, *pWinSalBitmap); + + + std::unique_ptr<WinSalBitmap> pWinTransparentSalBitmap(new WinSalBitmap()); + SalBitmap& rConstTransparentBitmap = const_cast<SalBitmap&>(rSTransparentBitmap); + convertToWinSalBitmap(rConstTransparentBitmap, *pWinTransparentSalBitmap); + + mpImpl->drawBitmap(rPosAry, *pWinSalBitmap, *pWinTransparentSalBitmap); + } + else + { + mpImpl->drawBitmap(rPosAry, rSSalBitmap, rSTransparentBitmap); + } +} + +bool WinSalGraphics::drawAlphaRect( long nX, long nY, long nWidth, + long nHeight, sal_uInt8 nTransparency ) +{ + return mpImpl->drawAlphaRect( nX, nY, nWidth, nHeight, nTransparency ); +} + +void WinSalGraphics::drawMask( const SalTwoRect& rPosAry, + const SalBitmap& rSSalBitmap, + Color nMaskColor ) +{ + mpImpl->drawMask( rPosAry, rSSalBitmap, nMaskColor ); +} + +std::shared_ptr<SalBitmap> WinSalGraphics::getBitmap( long nX, long nY, long nDX, long nDY ) +{ + return mpImpl->getBitmap( nX, nY, nDX, nDY ); +} + +Color WinSalGraphics::getPixel( long nX, long nY ) +{ + return mpImpl->getPixel( nX, nY ); +} + +void WinSalGraphics::invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags ) +{ + mpImpl->invert( nX, nY, nWidth, nHeight, nFlags ); +} + +void WinSalGraphics::invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nSalFlags ) +{ + mpImpl->invert( nPoints, pPtAry, nSalFlags ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/salgdi_gdiplus.cxx b/vcl/win/gdi/salgdi_gdiplus.cxx new file mode 100644 index 000000000..f56a22760 --- /dev/null +++ b/vcl/win/gdi/salgdi_gdiplus.cxx @@ -0,0 +1,98 @@ +/* -*- 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 <string.h> +#include <svsys.h> +#include <win/wincomp.hxx> +#include <win/saldata.hxx> +#include <win/salgdi.h> +#include <win/salbmp.h> + +#include "gdiimpl.hxx" + +bool WinSalGraphics::drawPolyPolygon( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fTransparency) +{ + return mpImpl->drawPolyPolygon( + rObjectToDevice, + rPolyPolygon, + fTransparency); +} + +bool WinSalGraphics::drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolygon, + double fTransparency, + double fLineWidth, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) +{ + return mpImpl->drawPolyLine( + rObjectToDevice, + rPolygon, + fTransparency, + fLineWidth, + pStroke, // MM01 + eLineJoin, + eLineCap, + fMiterMinimumAngle, + bPixelSnapHairline); +} + +bool WinSalGraphics::blendBitmap( + const SalTwoRect& rTR, + const SalBitmap& rBmp) +{ + return mpImpl->blendBitmap(rTR, rBmp); +} + +bool WinSalGraphics::blendAlphaBitmap( + const SalTwoRect& rTR, + const SalBitmap& rSrcBmp, + const SalBitmap& rMaskBmp, + const SalBitmap& rAlphaBmp) +{ + return mpImpl->blendAlphaBitmap(rTR, rSrcBmp, rMaskBmp, rAlphaBmp); +} + +bool WinSalGraphics::drawAlphaBitmap( + const SalTwoRect& rTR, + const SalBitmap& rSrcBitmap, + const SalBitmap& rAlphaBmp) +{ + return mpImpl->drawAlphaBitmap(rTR, rSrcBitmap, rAlphaBmp); +} + +bool WinSalGraphics::drawTransformedBitmap( + const basegfx::B2DPoint& rNull, + const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY, + const SalBitmap& rSourceBitmap, + const SalBitmap* pAlphaBitmap) +{ + return mpImpl->drawTransformedBitmap(rNull, rX, rY, + rSourceBitmap, pAlphaBitmap); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/salnativewidgets-luna.cxx b/vcl/win/gdi/salnativewidgets-luna.cxx new file mode 100644 index 000000000..68a18b020 --- /dev/null +++ b/vcl/win/gdi/salnativewidgets-luna.cxx @@ -0,0 +1,1559 @@ +/* -*- 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 . + */ + +// General info: +// http://msdn.microsoft.com/en-us/library/windows/desktop/hh270423%28v=vs.85%29.aspx +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb773178%28v=vs.85%29.aspx + +// Useful tool to explore the themes & their rendering: +// http://privat.rejbrand.se/UxExplore.exe +// (found at http://stackoverflow.com/questions/4009701/windows-visual-themes-gallery-of-parts-and-states/4009712#4009712) + +// Theme subclasses: +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb773218%28v=vs.85%29.aspx + +// Drawing in non-client area (general DWM-related info): +// http://msdn.microsoft.com/en-us/library/windows/desktop/bb688195%28v=vs.85%29.aspx + +#include <rtl/ustring.h> + +#include <osl/diagnose.h> +#include <osl/module.h> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <opengl/win/gdiimpl.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> + +#include <win/svsys.h> +#include <win/salgdi.h> +#include <win/saldata.hxx> +#include <win/scoped_gdi.hxx> + +#include <uxtheme.h> +#include <vssym32.h> + +#include <map> +#include <string> +#include <optional> +#include <ControlCacheKey.hxx> + +using namespace std; + +typedef map< wstring, HTHEME > ThemeMap; +static ThemeMap aThemeMap; + +/**************************************************** + wrap visual styles API to avoid linking against it + it is not available on all Windows platforms +*****************************************************/ + +namespace { + +class VisualStylesAPI +{ +private: + typedef HTHEME (WINAPI * OpenThemeData_Proc_T) ( HWND hwnd, LPCWSTR pszClassList ); + typedef HRESULT (WINAPI * CloseThemeData_Proc_T) ( HTHEME hTheme ); + typedef HRESULT (WINAPI * GetThemeBackgroundContentRect_Proc_T) ( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pBoundingRect, RECT *pContentRect ); + typedef HRESULT (WINAPI * DrawThemeBackground_Proc_T) ( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pRect, const RECT *pClipRect ); + typedef HRESULT (WINAPI * DrawThemeText_Proc_T) ( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, LPCWSTR pszText, int iCharCount, DWORD dwTextFlags, DWORD dwTextFlags2, const RECT *pRect ); + typedef HRESULT (WINAPI * GetThemePartSize_Proc_T) ( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, RECT *prc, THEMESIZE eSize, SIZE *psz ); + typedef BOOL (WINAPI * IsThemeActive_Proc_T) ( void ); + + OpenThemeData_Proc_T lpfnOpenThemeData; + CloseThemeData_Proc_T lpfnCloseThemeData; + GetThemeBackgroundContentRect_Proc_T lpfnGetThemeBackgroundContentRect; + DrawThemeBackground_Proc_T lpfnDrawThemeBackground; + DrawThemeText_Proc_T lpfnDrawThemeText; + GetThemePartSize_Proc_T lpfnGetThemePartSize; + IsThemeActive_Proc_T lpfnIsThemeActive; + + oslModule mhModule; + +public: + VisualStylesAPI(); + ~VisualStylesAPI(); + + HTHEME OpenThemeData( HWND hwnd, LPCWSTR pszClassList ); + HRESULT CloseThemeData( HTHEME hTheme ); + HRESULT GetThemeBackgroundContentRect( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pBoundingRect, RECT *pContentRect ); + HRESULT DrawThemeBackground( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pRect, const RECT *pClipRect ); + HRESULT DrawThemeText( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, LPCWSTR pszText, int iCharCount, DWORD dwTextFlags, DWORD dwTextFlags2, const RECT *pRect ); + HRESULT GetThemePartSize( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, RECT *prc, THEMESIZE eSize, SIZE *psz ); + bool IsThemeActive(); +}; + +} + +static VisualStylesAPI vsAPI; + +VisualStylesAPI::VisualStylesAPI() + : lpfnOpenThemeData( nullptr ), + lpfnCloseThemeData( nullptr ), + lpfnGetThemeBackgroundContentRect( nullptr ), + lpfnDrawThemeBackground( nullptr ), + lpfnDrawThemeText( nullptr ), + lpfnGetThemePartSize( nullptr ), + lpfnIsThemeActive( nullptr ) +{ + OUString aLibraryName( "uxtheme.dll" ); + mhModule = osl_loadModule( aLibraryName.pData, SAL_LOADMODULE_DEFAULT ); + + if ( mhModule ) + { + lpfnOpenThemeData = reinterpret_cast<OpenThemeData_Proc_T>(osl_getAsciiFunctionSymbol( mhModule, "OpenThemeData" )); + lpfnCloseThemeData = reinterpret_cast<CloseThemeData_Proc_T>(osl_getAsciiFunctionSymbol( mhModule, "CloseThemeData" )); + lpfnGetThemeBackgroundContentRect = reinterpret_cast<GetThemeBackgroundContentRect_Proc_T>(osl_getAsciiFunctionSymbol( mhModule, "GetThemeBackgroundContentRect" )); + lpfnDrawThemeBackground = reinterpret_cast<DrawThemeBackground_Proc_T>(osl_getAsciiFunctionSymbol( mhModule, "DrawThemeBackground" )); + lpfnDrawThemeText = reinterpret_cast<DrawThemeText_Proc_T>(osl_getAsciiFunctionSymbol( mhModule, "DrawThemeText" )); + lpfnGetThemePartSize = reinterpret_cast<GetThemePartSize_Proc_T>(osl_getAsciiFunctionSymbol( mhModule, "GetThemePartSize" )); + lpfnIsThemeActive = reinterpret_cast<IsThemeActive_Proc_T>(osl_getAsciiFunctionSymbol( mhModule, "IsThemeActive" )); + } +} + +VisualStylesAPI::~VisualStylesAPI() +{ + if( mhModule ) + osl_unloadModule( mhModule ); +} + +HTHEME VisualStylesAPI::OpenThemeData( HWND hwnd, LPCWSTR pszClassList ) +{ + if(lpfnOpenThemeData) + return (*lpfnOpenThemeData) (hwnd, pszClassList); + else + return nullptr; +} + +HRESULT VisualStylesAPI::CloseThemeData( HTHEME hTheme ) +{ + if(lpfnCloseThemeData) + return (*lpfnCloseThemeData) (hTheme); + else + return S_FALSE; +} + +HRESULT VisualStylesAPI::GetThemeBackgroundContentRect( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pBoundingRect, RECT *pContentRect ) +{ + if(lpfnGetThemeBackgroundContentRect) + return (*lpfnGetThemeBackgroundContentRect) ( hTheme, hdc, iPartId, iStateId, pBoundingRect, pContentRect ); + else + return S_FALSE; +} + +HRESULT VisualStylesAPI::DrawThemeBackground( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, const RECT *pRect, const RECT *pClipRect ) +{ + if(lpfnDrawThemeBackground) + return (*lpfnDrawThemeBackground) (hTheme, hdc, iPartId, iStateId, pRect, pClipRect); + else + return S_FALSE; +} + +HRESULT VisualStylesAPI::DrawThemeText( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, LPCWSTR pszText, int iCharCount, DWORD dwTextFlags, DWORD dwTextFlags2, const RECT *pRect ) +{ + if(lpfnDrawThemeText) + return (*lpfnDrawThemeText) (hTheme, hdc, iPartId, iStateId, pszText, iCharCount, dwTextFlags, dwTextFlags2, pRect); + else + return S_FALSE; +} + +HRESULT VisualStylesAPI::GetThemePartSize( HTHEME hTheme, HDC hdc, int iPartId, int iStateId, RECT *prc, THEMESIZE eSize, SIZE *psz ) +{ + if(lpfnGetThemePartSize) + return (*lpfnGetThemePartSize) (hTheme, hdc, iPartId, iStateId, prc, eSize, psz); + else + return S_FALSE; +} + +bool VisualStylesAPI::IsThemeActive() +{ + if(lpfnIsThemeActive) + return (*lpfnIsThemeActive) (); + else + return false; +} + +/********************************************************* + * Initialize XP theming and local stuff + *********************************************************/ +void SalData::initNWF() +{ + ImplSVData* pSVData = ImplGetSVData(); + + // the menu bar and the top docking area should have a common background (gradient) + pSVData->maNWFData.mbMenuBarDockingAreaCommonBG = true; +} + +// ********************************************************* +// * Release theming handles +// ******************************************************** +void SalData::deInitNWF() +{ + for( auto& rEntry : aThemeMap ) + vsAPI.CloseThemeData(rEntry.second); + aThemeMap.clear(); +} + +static HTHEME getThemeHandle( HWND hWnd, LPCWSTR name ) +{ + if( GetSalData()->mbThemeChanged ) + { + // throw away invalid theme handles + SalData::deInitNWF(); + GetSalData()->mbThemeChanged = false; + } + + ThemeMap::iterator iter; + if( (iter = aThemeMap.find( name )) != aThemeMap.end() ) + return iter->second; + // theme not found -> add it to map + HTHEME hTheme = vsAPI.OpenThemeData( hWnd, name ); + if( hTheme != nullptr ) + aThemeMap[name] = hTheme; + return hTheme; +} + +bool WinSalGraphics::isNativeControlSupported( ControlType nType, ControlPart nPart ) +{ + HTHEME hTheme = nullptr; + + switch( nType ) + { + case ControlType::Pushbutton: + case ControlType::Radiobutton: + case ControlType::Checkbox: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle( mhWnd, L"Button"); + break; + case ControlType::Scrollbar: + if( nPart == ControlPart::DrawBackgroundHorz || nPart == ControlPart::DrawBackgroundVert ) + return false; // no background painting needed + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle( mhWnd, L"Scrollbar"); + break; + case ControlType::Combobox: + if( nPart == ControlPart::HasBackgroundTexture ) + return false; // we do not paint the inner part (ie the selection background/focus indication) + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle( mhWnd, L"Edit"); + else if( nPart == ControlPart::ButtonDown ) + hTheme = getThemeHandle( mhWnd, L"Combobox"); + break; + case ControlType::Spinbox: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle( mhWnd, L"Edit"); + else if( nPart == ControlPart::AllButtons || + nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonDown || + nPart == ControlPart::ButtonLeft|| nPart == ControlPart::ButtonRight ) + hTheme = getThemeHandle( mhWnd, L"Spin"); + break; + case ControlType::SpinButtons: + if( nPart == ControlPart::Entire || nPart == ControlPart::AllButtons ) + hTheme = getThemeHandle( mhWnd, L"Spin"); + break; + case ControlType::Editbox: + case ControlType::MultilineEditbox: + if( nPart == ControlPart::HasBackgroundTexture ) + return false; // we do not paint the inner part (ie the selection background/focus indication) + //return TRUE; + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle( mhWnd, L"Edit"); + break; + case ControlType::Listbox: + if( nPart == ControlPart::HasBackgroundTexture ) + return false; // we do not paint the inner part (ie the selection background/focus indication) + if( nPart == ControlPart::Entire || nPart == ControlPart::ListboxWindow ) + hTheme = getThemeHandle( mhWnd, L"Listview"); + else if( nPart == ControlPart::ButtonDown ) + hTheme = getThemeHandle( mhWnd, L"Combobox"); + break; + case ControlType::TabPane: + case ControlType::TabBody: + case ControlType::TabItem: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle( mhWnd, L"Tab"); + break; + case ControlType::Toolbar: + if( nPart == ControlPart::Entire || nPart == ControlPart::Button ) + hTheme = getThemeHandle( mhWnd, L"Toolbar"); + else + // use rebar theme for grip and background + hTheme = getThemeHandle( mhWnd, L"Rebar"); + break; + case ControlType::Menubar: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle( mhWnd, L"Rebar"); + else if( GetSalData()->mbThemeMenuSupport ) + { + if( nPart == ControlPart::MenuItem ) + hTheme = getThemeHandle( mhWnd, L"Menu" ); + } + break; + case ControlType::MenuPopup: + if( GetSalData()->mbThemeMenuSupport ) + { + if( nPart == ControlPart::Entire || + nPart == ControlPart::MenuItem || + nPart == ControlPart::MenuItemCheckMark || + nPart == ControlPart::MenuItemRadioMark || + nPart == ControlPart::Separator ) + hTheme = getThemeHandle( mhWnd, L"Menu" ); + } + break; + case ControlType::Progress: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle( mhWnd, L"Progress"); + break; + case ControlType::Slider: + if( nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea ) + hTheme = getThemeHandle( mhWnd, L"Trackbar" ); + break; + case ControlType::ListNode: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle( mhWnd, L"TreeView" ); + break; + default: + hTheme = nullptr; + break; + } + + return (hTheme != nullptr); +} + +bool WinSalGraphics::hitTestNativeControl( ControlType, + ControlPart, + const tools::Rectangle&, + const Point&, + bool& ) +{ + return false; +} + +static bool ImplDrawTheme( HTHEME hTheme, HDC hDC, int iPart, int iState, RECT rc, const OUString& aStr) +{ + HRESULT hr = vsAPI.DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + + if( aStr.getLength() ) + { + RECT rcContent; + hr = vsAPI.GetThemeBackgroundContentRect( hTheme, hDC, iPart, iState, &rc, &rcContent); + hr = vsAPI.DrawThemeText( hTheme, hDC, iPart, iState, + o3tl::toW(aStr.getStr()), -1, + DT_CENTER | DT_VCENTER | DT_SINGLELINE, + 0, &rcContent); + } + return (hr == S_OK); +} + +static tools::Rectangle ImplGetThemeRect( HTHEME hTheme, HDC hDC, int iPart, int iState, const tools::Rectangle& /* aRect */, THEMESIZE eTS = TS_TRUE ) +{ + SIZE aSz; + HRESULT hr = vsAPI.GetThemePartSize( hTheme, hDC, iPart, iState, nullptr, eTS, &aSz ); // TS_TRUE returns optimal size + if( hr == S_OK ) + return tools::Rectangle( 0, 0, aSz.cx, aSz.cy ); + else + return tools::Rectangle(); +} + +// Helper functions + +static void ImplConvertSpinbuttonValues( ControlPart nControlPart, const ControlState& rState, const tools::Rectangle& rRect, + int* pLunaPart, int *pLunaState, RECT *pRect ) +{ + if( nControlPart == ControlPart::ButtonDown ) + { + *pLunaPart = SPNP_DOWN; + if( rState & ControlState::PRESSED ) + *pLunaState = DNS_PRESSED; + else if( !(rState & ControlState::ENABLED) ) + *pLunaState = DNS_DISABLED; + else if( rState & ControlState::ROLLOVER ) + *pLunaState = DNS_HOT; + else + *pLunaState = DNS_NORMAL; + } + if( nControlPart == ControlPart::ButtonUp ) + { + *pLunaPart = SPNP_UP; + if( rState & ControlState::PRESSED ) + *pLunaState = UPS_PRESSED; + else if( !(rState & ControlState::ENABLED) ) + *pLunaState = UPS_DISABLED; + else if( rState & ControlState::ROLLOVER ) + *pLunaState = UPS_HOT; + else + *pLunaState = UPS_NORMAL; + } + if( nControlPart == ControlPart::ButtonRight ) + { + *pLunaPart = SPNP_UPHORZ; + if( rState & ControlState::PRESSED ) + *pLunaState = DNHZS_PRESSED; + else if( !(rState & ControlState::ENABLED) ) + *pLunaState = DNHZS_DISABLED; + else if( rState & ControlState::ROLLOVER ) + *pLunaState = DNHZS_HOT; + else + *pLunaState = DNHZS_NORMAL; + } + if( nControlPart == ControlPart::ButtonLeft ) + { + *pLunaPart = SPNP_DOWNHORZ; + if( rState & ControlState::PRESSED ) + *pLunaState = UPHZS_PRESSED; + else if( !(rState & ControlState::ENABLED) ) + *pLunaState = UPHZS_DISABLED; + else if( rState & ControlState::ROLLOVER ) + *pLunaState = UPHZS_HOT; + else + *pLunaState = UPHZS_NORMAL; + } + + pRect->left = rRect.Left(); + pRect->right = rRect.Right()+1; + pRect->top = rRect.Top(); + pRect->bottom = rRect.Bottom()+1; +} + +/// Draw an own toolbar style on Windows Vista or later, looks better there +static void impl_drawAeroToolbar( HDC hDC, RECT rc, bool bHorizontal ) +{ + if ( rc.top == 0 && bHorizontal ) + { + const long GRADIENT_HEIGHT = 32; + + long gradient_break = rc.top; + long gradient_bottom = rc.bottom - 1; + GRADIENT_RECT g_rect[1] = { { 0, 1 } }; + + // very slow gradient at the top (if we have space for that) + if ( gradient_bottom - rc.top > GRADIENT_HEIGHT ) + { + gradient_break = gradient_bottom - GRADIENT_HEIGHT; + + TRIVERTEX vert[2] = { + { rc.left, rc.top, 0xff00, 0xff00, 0xff00, 0xff00 }, + { rc.right, gradient_break, 0xfa00, 0xfa00, 0xfa00, 0xff00 }, + }; + GdiGradientFill( hDC, vert, 2, g_rect, 1, GRADIENT_FILL_RECT_V ); + } + + // gradient at the bottom + TRIVERTEX vert[2] = { + { rc.left, gradient_break, 0xfa00, 0xfa00, 0xfa00, 0xff00 }, + { rc.right, gradient_bottom, 0xf000, 0xf000, 0xf000, 0xff00 } + }; + GdiGradientFill( hDC, vert, 2, g_rect, 1, GRADIENT_FILL_RECT_V ); + + // and a darker horizontal line under that + ScopedSelectedHPEN hPen(hDC, CreatePen(PS_SOLID, 1, RGB( 0xb0, 0xb0, 0xb0))); + + MoveToEx( hDC, rc.left, gradient_bottom, nullptr ); + LineTo( hDC, rc.right, gradient_bottom ); + } + else + { + ScopedHBRUSH hbrush(CreateSolidBrush(RGB(0xf0, 0xf0, 0xf0))); + FillRect(hDC, &rc, hbrush.get()); + + // darker line to distinguish the toolbar and viewshell + // it is drawn only for the horizontal toolbars; it did not look well + // when done for the vertical ones too + if ( bHorizontal ) + { + long from_x, from_y, to_x, to_y; + + from_x = rc.left; + to_x = rc.right; + from_y = to_y = rc.top; + + ScopedSelectedHPEN hPen(hDC, CreatePen(PS_SOLID, 1, RGB( 0xb0, 0xb0, 0xb0))); + + MoveToEx( hDC, from_x, from_y, nullptr ); + LineTo( hDC, to_x, to_y ); + } + } +} + +/** + * Gives the actual rectangle used for rendering by ControlType::MenuPopup's + * ControlPart::MenuItemCheckMark or ControlPart::MenuItemRadioMark. + */ +static tools::Rectangle GetMenuPopupMarkRegion(const ImplControlValue& rValue) +{ + tools::Rectangle aRet; + + auto pMVal = dynamic_cast<const MenupopupValue*>(&rValue); + if (!pMVal) + return aRet; + + aRet.SetTop(pMVal->maItemRect.Top()); + aRet.SetBottom(pMVal->maItemRect.Bottom() + 1); // see below in drawNativeControl + if (AllSettings::GetLayoutRTL()) + { + aRet.SetRight(pMVal->maItemRect.Right() + 1); + aRet.SetLeft(aRet.Right() - (pMVal->getNumericVal() - pMVal->maItemRect.Left())); + } + else + { + aRet.SetRight(pMVal->getNumericVal()); + aRet.SetLeft(pMVal->maItemRect.Left()); + } + + return aRet; +} + +static bool ImplDrawNativeControl( HDC hDC, HTHEME hTheme, RECT rc, + ControlType nType, + ControlPart nPart, + ControlState nState, + const ImplControlValue& aValue, + OUString const & aCaption ) +{ + // a listbox dropdown is actually a combobox dropdown + if( nType == ControlType::Listbox ) + if( nPart == ControlPart::ButtonDown ) + nType = ControlType::Combobox; + + // draw entire combobox as a large edit box + if( nType == ControlType::Combobox ) + if( nPart == ControlPart::Entire ) + nType = ControlType::Editbox; + + // draw entire spinbox as a large edit box + if( nType == ControlType::Spinbox ) + if( nPart == ControlPart::Entire ) + nType = ControlType::Editbox; + + int iPart(0), iState(0); + if( nType == ControlType::Scrollbar ) + { + HRESULT hr; + if( nPart == ControlPart::ButtonUp ) + { + iPart = SBP_ARROWBTN; + if( nState & ControlState::PRESSED ) + iState = ABS_UPPRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = ABS_UPDISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = ABS_UPHOT; + else + iState = ABS_UPNORMAL; + hr = vsAPI.DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + return (hr == S_OK); + } + if( nPart == ControlPart::ButtonDown ) + { + iPart = SBP_ARROWBTN; + if( nState & ControlState::PRESSED ) + iState = ABS_DOWNPRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = ABS_DOWNDISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = ABS_DOWNHOT; + else + iState = ABS_DOWNNORMAL; + hr = vsAPI.DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + return (hr == S_OK); + } + if( nPart == ControlPart::ButtonLeft ) + { + iPart = SBP_ARROWBTN; + if( nState & ControlState::PRESSED ) + iState = ABS_LEFTPRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = ABS_LEFTDISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = ABS_LEFTHOT; + else + iState = ABS_LEFTNORMAL; + hr = vsAPI.DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + return (hr == S_OK); + } + if( nPart == ControlPart::ButtonRight ) + { + iPart = SBP_ARROWBTN; + if( nState & ControlState::PRESSED ) + iState = ABS_RIGHTPRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = ABS_RIGHTDISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = ABS_RIGHTHOT; + else + iState = ABS_RIGHTNORMAL; + hr = vsAPI.DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + return (hr == S_OK); + } + if( nPart == ControlPart::ThumbHorz || nPart == ControlPart::ThumbVert ) + { + iPart = (nPart == ControlPart::ThumbHorz) ? SBP_THUMBBTNHORZ : SBP_THUMBBTNVERT; + if( nState & ControlState::PRESSED ) + iState = SCRBS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = SCRBS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = SCRBS_HOT; + else + iState = SCRBS_NORMAL; + + SIZE sz; + vsAPI.GetThemePartSize(hTheme, hDC, iPart, iState, nullptr, TS_MIN, &sz); + vsAPI.GetThemePartSize(hTheme, hDC, iPart, iState, nullptr, TS_TRUE, &sz); + vsAPI.GetThemePartSize(hTheme, hDC, iPart, iState, nullptr, TS_DRAW, &sz); + + hr = vsAPI.DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + // paint gripper on thumb if enough space + if( ( (nPart == ControlPart::ThumbVert) && (rc.bottom-rc.top > 12) ) || + ( (nPart == ControlPart::ThumbHorz) && (rc.right-rc.left > 12) ) ) + { + iPart = (nPart == ControlPart::ThumbHorz) ? SBP_GRIPPERHORZ : SBP_GRIPPERVERT; + iState = 0; + vsAPI.DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + } + return (hr == S_OK); + } + if( nPart == ControlPart::TrackHorzLeft || nPart == ControlPart::TrackHorzRight || nPart == ControlPart::TrackVertUpper || nPart == ControlPart::TrackVertLower ) + { + switch( nPart ) + { + case ControlPart::TrackHorzLeft: iPart = SBP_UPPERTRACKHORZ; break; + case ControlPart::TrackHorzRight: iPart = SBP_LOWERTRACKHORZ; break; + case ControlPart::TrackVertUpper: iPart = SBP_UPPERTRACKVERT; break; + case ControlPart::TrackVertLower: iPart = SBP_LOWERTRACKVERT; break; + default: break; + } + + if( nState & ControlState::PRESSED ) + iState = SCRBS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = SCRBS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = SCRBS_HOT; + else + iState = SCRBS_NORMAL; + hr = vsAPI.DrawThemeBackground( hTheme, hDC, iPart, iState, &rc, nullptr); + return (hr == S_OK); + } + } + if( nType == ControlType::SpinButtons && nPart == ControlPart::AllButtons ) + { + if( aValue.getType() == ControlType::SpinButtons ) + { + const SpinbuttonValue* pValue = (aValue.getType() == ControlType::SpinButtons) ? static_cast<const SpinbuttonValue*>(&aValue) : nullptr; + + RECT rect; + ImplConvertSpinbuttonValues( pValue->mnUpperPart, pValue->mnUpperState, pValue->maUpperRect, &iPart, &iState, &rect ); + bool bOk = ImplDrawTheme( hTheme, hDC, iPart, iState, rect, aCaption); + + if( bOk ) + { + ImplConvertSpinbuttonValues( pValue->mnLowerPart, pValue->mnLowerState, pValue->maLowerRect, &iPart, &iState, &rect ); + bOk = ImplDrawTheme( hTheme, hDC, iPart, iState, rect, aCaption); + } + + return bOk; + } + } + if( nType == ControlType::Spinbox ) + { + if( nPart == ControlPart::AllButtons ) + { + if( aValue.getType() == ControlType::SpinButtons ) + { + const SpinbuttonValue *pValue = static_cast<const SpinbuttonValue*>(&aValue); + + RECT rect; + ImplConvertSpinbuttonValues( pValue->mnUpperPart, pValue->mnUpperState, pValue->maUpperRect, &iPart, &iState, &rect ); + bool bOk = ImplDrawTheme( hTheme, hDC, iPart, iState, rect, aCaption); + + if( bOk ) + { + ImplConvertSpinbuttonValues( pValue->mnLowerPart, pValue->mnLowerState, pValue->maLowerRect, &iPart, &iState, &rect ); + bOk = ImplDrawTheme( hTheme, hDC, iPart, iState, rect, aCaption); + } + + return bOk; + } + } + + if( nPart == ControlPart::ButtonDown ) + { + iPart = SPNP_DOWN; + if( nState & ControlState::PRESSED ) + iState = DNS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = DNS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = DNS_HOT; + else + iState = DNS_NORMAL; + } + if( nPart == ControlPart::ButtonUp ) + { + iPart = SPNP_UP; + if( nState & ControlState::PRESSED ) + iState = UPS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = UPS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = UPS_HOT; + else + iState = UPS_NORMAL; + } + if( nPart == ControlPart::ButtonRight ) + { + iPart = SPNP_DOWNHORZ; + if( nState & ControlState::PRESSED ) + iState = DNHZS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = DNHZS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = DNHZS_HOT; + else + iState = DNHZS_NORMAL; + } + if( nPart == ControlPart::ButtonLeft ) + { + iPart = SPNP_UPHORZ; + if( nState & ControlState::PRESSED ) + iState = UPHZS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = UPHZS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = UPHZS_HOT; + else + iState = UPHZS_NORMAL; + } + if( nPart == ControlPart::ButtonLeft || nPart == ControlPart::ButtonRight || nPart == ControlPart::ButtonUp || nPart == ControlPart::ButtonDown ) + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + if( nType == ControlType::Combobox ) + { + if( nPart == ControlPart::ButtonDown ) + { + iPart = CP_DROPDOWNBUTTON; + if( nState & ControlState::PRESSED ) + iState = CBXS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = CBXS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = CBXS_HOT; + else + iState = CBXS_NORMAL; + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + } + if( nType == ControlType::Pushbutton ) + { + iPart = BP_PUSHBUTTON; + if( nState & ControlState::PRESSED ) + iState = PBS_PRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = PBS_DISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = PBS_HOT; + else if( nState & ControlState::DEFAULT ) + iState = PBS_DEFAULTED; + //else if( nState & ControlState::FOCUSED ) + // iState = PBS_DEFAULTED; // may need to draw focus rect + else + iState = PBS_NORMAL; + + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + + if( nType == ControlType::Radiobutton ) + { + iPart = BP_RADIOBUTTON; + bool bChecked = ( aValue.getTristateVal() == ButtonValue::On ); + + if( nState & ControlState::PRESSED ) + iState = bChecked ? RBS_CHECKEDPRESSED : RBS_UNCHECKEDPRESSED; + else if( !(nState & ControlState::ENABLED) ) + iState = bChecked ? RBS_CHECKEDDISABLED : RBS_UNCHECKEDDISABLED; + else if( nState & ControlState::ROLLOVER ) + iState = bChecked ? RBS_CHECKEDHOT : RBS_UNCHECKEDHOT; + else + iState = bChecked ? RBS_CHECKEDNORMAL : RBS_UNCHECKEDNORMAL; + + //if( nState & ControlState::FOCUSED ) + // iState |= PBS_DEFAULTED; // may need to draw focus rect + + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + + if( nType == ControlType::Checkbox ) + { + iPart = BP_CHECKBOX; + ButtonValue v = aValue.getTristateVal(); + + if( nState & ControlState::PRESSED ) + iState = (v == ButtonValue::On) ? CBS_CHECKEDPRESSED : + ( (v == ButtonValue::Off) ? CBS_UNCHECKEDPRESSED : CBS_MIXEDPRESSED ); + else if( !(nState & ControlState::ENABLED) ) + iState = (v == ButtonValue::On) ? CBS_CHECKEDDISABLED : + ( (v == ButtonValue::Off) ? CBS_UNCHECKEDDISABLED : CBS_MIXEDDISABLED ); + else if( nState & ControlState::ROLLOVER ) + iState = (v == ButtonValue::On) ? CBS_CHECKEDHOT : + ( (v == ButtonValue::Off) ? CBS_UNCHECKEDHOT : CBS_MIXEDHOT ); + else + iState = (v == ButtonValue::On) ? CBS_CHECKEDNORMAL : + ( (v == ButtonValue::Off) ? CBS_UNCHECKEDNORMAL : CBS_MIXEDNORMAL ); + + //if( nState & ControlState::FOCUSED ) + // iState |= PBS_DEFAULTED; // may need to draw focus rect + + //SIZE sz; + //THEMESIZE eSize = TS_DRAW; // TS_MIN, TS_TRUE, TS_DRAW + //vsAPI.GetThemePartSize( hTheme, hDC, iPart, iState, &rc, eSize, &sz); + + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + + if( ( nType == ControlType::Editbox ) || ( nType == ControlType::MultilineEditbox ) ) + { + iPart = EP_EDITTEXT; + if( !(nState & ControlState::ENABLED) ) + iState = ETS_DISABLED; + else if( nState & ControlState::FOCUSED ) + iState = ETS_FOCUSED; + else if( nState & ControlState::ROLLOVER ) + iState = ETS_HOT; + else + iState = ETS_NORMAL; + + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + + if( nType == ControlType::Listbox ) + { + if( nPart == ControlPart::Entire || nPart == ControlPart::ListboxWindow ) + { + iPart = LVP_EMPTYTEXT; // ??? no idea which part to choose here + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + } + + if( nType == ControlType::TabPane ) + { + iPart = TABP_PANE; + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + + if( nType == ControlType::TabBody ) + { + iPart = TABP_BODY; + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + + if( nType == ControlType::TabItem ) + { + iPart = TABP_TABITEMLEFTEDGE; + rc.bottom--; + + OSL_ASSERT( aValue.getType() == ControlType::TabItem ); + + const TabitemValue& rValue = static_cast<const TabitemValue&>(aValue); + if (rValue.isBothAligned()) + { + iPart = TABP_TABITEMLEFTEDGE; + rc.right--; + } + else if (rValue.isLeftAligned()) + iPart = TABP_TABITEMLEFTEDGE; + else if (rValue.isRightAligned()) + iPart = TABP_TABITEMRIGHTEDGE; + else iPart = TABP_TABITEM; + + if( !(nState & ControlState::ENABLED) ) + iState = TILES_DISABLED; + else if( nState & ControlState::SELECTED ) + { + iState = TILES_SELECTED; + // increase the selected tab + rc.left-=2; + if (rValue.isBothAligned()) + { + if (rValue.isLeftAligned() || rValue.isNotAligned()) + rc.right+=2; + if (rValue.isRightAligned()) + rc.right+=1; + } + rc.top-=2; + rc.bottom+=2; + } + else if( nState & ControlState::ROLLOVER ) + iState = TILES_HOT; + else if( nState & ControlState::FOCUSED ) + iState = TILES_FOCUSED; // may need to draw focus rect + else + iState = TILES_NORMAL; + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + + if( nType == ControlType::Toolbar ) + { + if( nPart == ControlPart::Button ) + { + iPart = TP_BUTTON; + bool bChecked = ( aValue.getTristateVal() == ButtonValue::On ); + if( !(nState & ControlState::ENABLED) ) + //iState = TS_DISABLED; + // disabled buttons are typically not painted at all but we need visual + // feedback when travelling by keyboard over disabled entries + iState = TS_HOT; + else if( nState & ControlState::PRESSED ) + iState = TS_PRESSED; + else if( nState & ControlState::ROLLOVER ) + iState = bChecked ? TS_HOTCHECKED : TS_HOT; + else + iState = bChecked ? TS_CHECKED : TS_NORMAL; + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + else if( nPart == ControlPart::ThumbHorz || nPart == ControlPart::ThumbVert ) + { + // the vertical gripper is not supported in most themes and it makes no + // sense to only support horizontal gripper + //iPart = (nPart == ControlPart::ThumbHorz) ? RP_GRIPPERVERT : RP_GRIPPER; + //return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + else if( nPart == ControlPart::DrawBackgroundHorz || nPart == ControlPart::DrawBackgroundVert ) + { + if( aValue.getType() == ControlType::Toolbar ) + { + const ToolbarValue *pValue = static_cast<const ToolbarValue*>(&aValue); + if( pValue->mbIsTopDockingArea ) + rc.top = 0; // extend potential gradient to cover menu bar as well + } + + // make it more compatible with Aero + if( ImplGetSVData()->maNWFData.mbDockingAreaAvoidTBFrames ) + { + impl_drawAeroToolbar( hDC, rc, nPart == ControlPart::DrawBackgroundHorz ); + return true; + } + + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + } + + if( nType == ControlType::Menubar ) + { + if( nPart == ControlPart::Entire ) + { + if( aValue.getType() == ControlType::Menubar ) + { + const MenubarValue *pValue = static_cast<const MenubarValue*>(&aValue); + rc.bottom += pValue->maTopDockingAreaHeight; // extend potential gradient to cover docking area as well + + // make it more compatible with Aero + if( ImplGetSVData()->maNWFData.mbDockingAreaAvoidTBFrames ) + { + impl_drawAeroToolbar( hDC, rc, true ); + return true; + } + } + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption); + } + else if( nPart == ControlPart::MenuItem ) + { + if( nState & ControlState::ENABLED ) + { + if( nState & ControlState::SELECTED ) + iState = MBI_PUSHED; + else if( nState & ControlState::ROLLOVER ) + iState = MBI_HOT; + else + iState = MBI_NORMAL; + } + else + { + if( nState & ControlState::SELECTED ) + iState = MBI_DISABLEDPUSHED; + else if( nState & ControlState::ROLLOVER ) + iState = MBI_DISABLEDHOT; + else + iState = MBI_DISABLED; + } + return ImplDrawTheme( hTheme, hDC, MENU_BARITEM, iState, rc, aCaption ); + } + } + + if( nType == ControlType::Progress ) + { + if( nPart != ControlPart::Entire ) + return false; + + if( ! ImplDrawTheme( hTheme, hDC, PP_BAR, iState, rc, aCaption) ) + return false; + RECT aProgressRect = rc; + if( vsAPI.GetThemeBackgroundContentRect( hTheme, hDC, PP_BAR, iState, &rc, &aProgressRect) != S_OK ) + return false; + + long nProgressWidth = aValue.getNumericVal(); + nProgressWidth *= (aProgressRect.right - aProgressRect.left); + nProgressWidth /= (rc.right - rc.left); + if( AllSettings::GetLayoutRTL() ) + aProgressRect.left = aProgressRect.right - nProgressWidth; + else + aProgressRect.right = aProgressRect.left + nProgressWidth; + + return ImplDrawTheme( hTheme, hDC, PP_CHUNK, iState, aProgressRect, aCaption ); + } + + if( nType == ControlType::Slider ) + { + iPart = (nPart == ControlPart::TrackHorzArea) ? TKP_TRACK : TKP_TRACKVERT; + iState = (nPart == ControlPart::TrackHorzArea) ? static_cast<int>(TRS_NORMAL) : static_cast<int>(TRVS_NORMAL); + + tools::Rectangle aTrackRect = ImplGetThemeRect( hTheme, hDC, iPart, iState, tools::Rectangle() ); + RECT aTRect = rc; + if( nPart == ControlPart::TrackHorzArea ) + { + long nH = aTrackRect.GetHeight(); + aTRect.top += (rc.bottom - rc.top - nH)/2; + aTRect.bottom = aTRect.top + nH; + } + else + { + long nW = aTrackRect.GetWidth(); + aTRect.left += (rc.right - rc.left - nW)/2; + aTRect.right = aTRect.left + nW; + } + ImplDrawTheme( hTheme, hDC, iPart, iState, aTRect, aCaption ); + + RECT aThumbRect; + OSL_ASSERT( aValue.getType() == ControlType::Slider ); + const SliderValue* pVal = static_cast<const SliderValue*>(&aValue); + aThumbRect.left = pVal->maThumbRect.Left(); + aThumbRect.top = pVal->maThumbRect.Top(); + aThumbRect.right = pVal->maThumbRect.Right(); + aThumbRect.bottom = pVal->maThumbRect.Bottom(); + iPart = (nPart == ControlPart::TrackHorzArea) ? TKP_THUMB : TKP_THUMBVERT; + iState = (nState & ControlState::ENABLED) ? TUS_NORMAL : TUS_DISABLED; + return ImplDrawTheme( hTheme, hDC, iPart, iState, aThumbRect, aCaption ); + } + + if( nType == ControlType::ListNode ) + { + if( nPart != ControlPart::Entire ) + return false; + + ButtonValue aButtonValue = aValue.getTristateVal(); + iPart = TVP_GLYPH; + switch( aButtonValue ) + { + case ButtonValue::On: + iState = GLPS_OPENED; + break; + case ButtonValue::Off: + iState = GLPS_CLOSED; + break; + default: + return false; + } + return ImplDrawTheme( hTheme, hDC, iPart, iState, rc, aCaption ); + } + + if( GetSalData()->mbThemeMenuSupport ) + { + if( nType == ControlType::MenuPopup ) + { + if( nPart == ControlPart::Entire ) + { + RECT aGutterRC = rc; + if( AllSettings::GetLayoutRTL() ) + { + aGutterRC.right -= aValue.getNumericVal()+1; + aGutterRC.left = aGutterRC.right-3; + } + else + { + aGutterRC.left += aValue.getNumericVal(); + aGutterRC.right = aGutterRC.left+3; + } + return + ImplDrawTheme( hTheme, hDC, MENU_POPUPBACKGROUND, 0, rc, aCaption ) && + ImplDrawTheme( hTheme, hDC, MENU_POPUPGUTTER, 0, aGutterRC, aCaption ) + ; + } + else if( nPart == ControlPart::MenuItem ) + { + if( nState & ControlState::ENABLED ) + iState = (nState & ControlState::SELECTED) ? MPI_HOT : MPI_NORMAL; + else + iState = (nState & ControlState::SELECTED) ? MPI_DISABLEDHOT : MPI_DISABLED; + return ImplDrawTheme( hTheme, hDC, MENU_POPUPITEM, iState, rc, aCaption ); + } + else if( nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark ) + { + if( nState & ControlState::PRESSED ) + { + RECT aBGRect = rc; + if( aValue.getType() == ControlType::MenuPopup ) + { + tools::Rectangle aRectangle = GetMenuPopupMarkRegion(aValue); + aBGRect.top = aRectangle.Top(); + aBGRect.left = aRectangle.Left(); + aBGRect.bottom = aRectangle.Bottom(); + aBGRect.right = aRectangle.Right(); + rc = aBGRect; + } + iState = (nState & ControlState::ENABLED) ? MCB_NORMAL : MCB_DISABLED; + ImplDrawTheme( hTheme, hDC, MENU_POPUPCHECKBACKGROUND, iState, aBGRect, aCaption ); + if( nPart == ControlPart::MenuItemCheckMark ) + iState = (nState & ControlState::ENABLED) ? MC_CHECKMARKNORMAL : MC_CHECKMARKDISABLED; + else + iState = (nState & ControlState::ENABLED) ? MC_BULLETNORMAL : MC_BULLETDISABLED; + return ImplDrawTheme( hTheme, hDC, MENU_POPUPCHECK, iState, rc, aCaption ); + } + else + return true; // unchecked: do nothing + } + else if( nPart == ControlPart::Separator ) + { + // adjust for gutter position + if( AllSettings::GetLayoutRTL() ) + rc.right -= aValue.getNumericVal()+1; + else + rc.left += aValue.getNumericVal()+1; + tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, + MENU_POPUPSEPARATOR, 0, tools::Rectangle( rc.left, rc.top, rc.right, rc.bottom ) ) ); + // center the separator inside the passed rectangle + long nDY = ((rc.bottom - rc.top + 1) - aRect.GetHeight()) / 2; + rc.top += nDY; + rc.bottom = rc.top+aRect.GetHeight()-1; + return ImplDrawTheme( hTheme, hDC, MENU_POPUPSEPARATOR, 0, rc, aCaption ); + } + } + } + + return false; +} + +bool WinSalGraphics::drawNativeControl( ControlType nType, + ControlPart nPart, + const tools::Rectangle& rControlRegion, + ControlState nState, + const ImplControlValue& aValue, + const OUString& aCaption, + const Color& /*rBackgroundColor*/ ) +{ + bool bOk = false; + HTHEME hTheme = nullptr; + + tools::Rectangle buttonRect = rControlRegion; + tools::Rectangle cacheRect = rControlRegion; + Size keySize = cacheRect.GetSize(); + + WinSalGraphicsImplBase* pImpl = dynamic_cast<WinSalGraphicsImplBase*>(mpImpl.get()); + if( !pImpl->UseRenderNativeControl()) + pImpl = nullptr; + + // tdf#95618 - A few controls render outside the region they're given. + if (pImpl && nType == ControlType::TabItem) + { + tools::Rectangle rNativeBoundingRegion; + tools::Rectangle rNativeContentRegion; + if (getNativeControlRegion(nType, nPart, rControlRegion, nState, aValue, aCaption, + rNativeBoundingRegion, rNativeContentRegion)) + { + cacheRect = rNativeBoundingRegion; + keySize = rNativeBoundingRegion.GetSize(); + } + } + + if (pImpl && nType == ControlType::MenuPopup && (nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark)) + { + tools::Rectangle aRectangle = GetMenuPopupMarkRegion(aValue); + if (!aRectangle.IsEmpty()) + { + cacheRect = GetMenuPopupMarkRegion(aValue); + buttonRect = cacheRect; + keySize = cacheRect.GetSize(); + } + } + + + ControlCacheKey aControlCacheKey(nType, nPart, nState, keySize); + if (pImpl != nullptr && pImpl->TryRenderCachedNativeControl(aControlCacheKey, buttonRect.Left(), buttonRect.Top())) + { + return true; + } + + switch( nType ) + { + case ControlType::Pushbutton: + case ControlType::Radiobutton: + case ControlType::Checkbox: + hTheme = getThemeHandle( mhWnd, L"Button"); + break; + case ControlType::Scrollbar: + hTheme = getThemeHandle( mhWnd, L"Scrollbar"); + break; + case ControlType::Combobox: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle( mhWnd, L"Edit"); + else if( nPart == ControlPart::ButtonDown ) + hTheme = getThemeHandle( mhWnd, L"Combobox"); + break; + case ControlType::Spinbox: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle( mhWnd, L"Edit"); + else + hTheme = getThemeHandle( mhWnd, L"Spin"); + break; + case ControlType::SpinButtons: + hTheme = getThemeHandle( mhWnd, L"Spin"); + break; + case ControlType::Editbox: + case ControlType::MultilineEditbox: + hTheme = getThemeHandle( mhWnd, L"Edit"); + break; + case ControlType::Listbox: + if( nPart == ControlPart::Entire || nPart == ControlPart::ListboxWindow ) + hTheme = getThemeHandle( mhWnd, L"Listview"); + else if( nPart == ControlPart::ButtonDown ) + hTheme = getThemeHandle( mhWnd, L"Combobox"); + break; + case ControlType::TabPane: + case ControlType::TabBody: + case ControlType::TabItem: + hTheme = getThemeHandle( mhWnd, L"Tab"); + break; + case ControlType::Toolbar: + if( nPart == ControlPart::Entire || nPart == ControlPart::Button ) + hTheme = getThemeHandle( mhWnd, L"Toolbar"); + else + // use rebar for grip and background + hTheme = getThemeHandle( mhWnd, L"Rebar"); + break; + case ControlType::Menubar: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle( mhWnd, L"Rebar"); + else if( GetSalData()->mbThemeMenuSupport ) + { + if( nPart == ControlPart::MenuItem ) + hTheme = getThemeHandle( mhWnd, L"Menu" ); + } + break; + case ControlType::Progress: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle( mhWnd, L"Progress"); + break; + case ControlType::ListNode: + if( nPart == ControlPart::Entire ) + hTheme = getThemeHandle( mhWnd, L"TreeView"); + break; + case ControlType::Slider: + if( nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea ) + hTheme = getThemeHandle( mhWnd, L"Trackbar" ); + break; + case ControlType::MenuPopup: + if( GetSalData()->mbThemeMenuSupport ) + { + if( nPart == ControlPart::Entire || nPart == ControlPart::MenuItem || + nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark || + nPart == ControlPart::Separator + ) + hTheme = getThemeHandle( mhWnd, L"Menu" ); + } + break; + default: + hTheme = nullptr; + break; + } + + if( !hTheme ) + return false; + + RECT rc; + rc.left = buttonRect.Left(); + rc.right = buttonRect.Right()+1; + rc.top = buttonRect.Top(); + rc.bottom = buttonRect.Bottom()+1; + + OUString aCaptionStr(aCaption.replace('~', '&')); // translate mnemonics + + if (pImpl == nullptr) + { + // set default text alignment + int ta = SetTextAlign(getHDC(), TA_LEFT|TA_TOP|TA_NOUPDATECP); + + bOk = ImplDrawNativeControl(getHDC(), hTheme, rc, nType, nPart, nState, aValue, aCaptionStr); + + // restore alignment + SetTextAlign(getHDC(), ta); + } + else + { + // We can do OpenGL/Skia + std::unique_ptr<CompatibleDC> aBlackDC(CompatibleDC::create(*this, cacheRect.Left(), cacheRect.Top(), cacheRect.GetWidth()+1, cacheRect.GetHeight()+1)); + SetTextAlign(aBlackDC->getCompatibleHDC(), TA_LEFT|TA_TOP|TA_NOUPDATECP); + aBlackDC->fill(RGB(0, 0, 0)); + + std::unique_ptr<CompatibleDC> aWhiteDC(CompatibleDC::create(*this, cacheRect.Left(), cacheRect.Top(), cacheRect.GetWidth()+1, cacheRect.GetHeight()+1)); + SetTextAlign(aWhiteDC->getCompatibleHDC(), TA_LEFT|TA_TOP|TA_NOUPDATECP); + aWhiteDC->fill(RGB(0xff, 0xff, 0xff)); + + if (ImplDrawNativeControl(aBlackDC->getCompatibleHDC(), hTheme, rc, nType, nPart, nState, aValue, aCaptionStr) && + ImplDrawNativeControl(aWhiteDC->getCompatibleHDC(), hTheme, rc, nType, nPart, nState, aValue, aCaptionStr)) + { + bOk = pImpl->RenderAndCacheNativeControl(*aWhiteDC, *aBlackDC, cacheRect.Left(), cacheRect.Top(), aControlCacheKey); + } + } + + return bOk; +} + +bool WinSalGraphics::getNativeControlRegion( ControlType nType, + ControlPart nPart, + const tools::Rectangle& rControlRegion, + ControlState nState, + const ImplControlValue& rControlValue, + const OUString&, + tools::Rectangle &rNativeBoundingRegion, + tools::Rectangle &rNativeContentRegion ) +{ + bool bRet = false; + + // FIXME: rNativeBoundingRegion has a different origin + // depending on which part is used; horrors. + + HDC hDC = GetDC( mhWnd ); + if( nType == ControlType::Toolbar ) + { + if( nPart == ControlPart::ThumbHorz || nPart == ControlPart::ThumbVert ) + { + /* + // the vertical gripper is not supported in most themes and it makes no + // sense to only support horizontal gripper + + HTHEME hTheme = getThemeHandle( mhWnd, L"Rebar"); + if( hTheme ) + { + tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, nPart == ControlPart::ThumbHorz ? RP_GRIPPERVERT : RP_GRIPPER, + 0, rControlRegion.GetBoundRect() ) ); + if( nPart == ControlPart::ThumbHorz && !aRect.IsEmpty() ) + { + tools::Rectangle aVertRect( 0, 0, aRect.getHeight(), aRect.getWidth() ); + rNativeContentRegion = aVertRect; + } + else + rNativeContentRegion = aRect; + rNativeBoundingRegion = rNativeContentRegion; + if( !rNativeContentRegion.IsEmpty() ) + bRet = TRUE; + } + */ + } + if( nPart == ControlPart::Button ) + { + HTHEME hTheme = getThemeHandle( mhWnd, L"Toolbar"); + if( hTheme ) + { + tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, TP_SPLITBUTTONDROPDOWN, + TS_HOT, rControlRegion ) ); + rNativeContentRegion = aRect; + rNativeBoundingRegion = rNativeContentRegion; + if( !rNativeContentRegion.IsEmpty() ) + bRet = true; + } + } + } + if( nType == ControlType::Progress && nPart == ControlPart::Entire ) + { + HTHEME hTheme = getThemeHandle( mhWnd, L"Progress"); + if( hTheme ) + { + tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, PP_BAR, + 0, rControlRegion ) ); + rNativeContentRegion = aRect; + rNativeBoundingRegion = rNativeContentRegion; + if( !rNativeContentRegion.IsEmpty() ) + bRet = true; + } + } + if( (nType == ControlType::Listbox || nType == ControlType::Combobox ) && nPart == ControlPart::Entire ) + { + HTHEME hTheme = getThemeHandle( mhWnd, L"Combobox"); + if( hTheme ) + { + tools::Rectangle aBoxRect( rControlRegion ); + tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, CP_DROPDOWNBUTTON, + CBXS_NORMAL, aBoxRect ) ); + if( aRect.GetHeight() > aBoxRect.GetHeight() ) + aBoxRect.SetBottom( aBoxRect.Top() + aRect.GetHeight() ); + if( aRect.GetWidth() > aBoxRect.GetWidth() ) + aBoxRect.SetRight( aBoxRect.Left() + aRect.GetWidth() ); + rNativeContentRegion = aBoxRect; + rNativeBoundingRegion = rNativeContentRegion; + if( !aRect.IsEmpty() ) + bRet = true; + } + } + + if( (nType == ControlType::Editbox || nType == ControlType::Spinbox) && nPart == ControlPart::Entire ) + { + HTHEME hTheme = getThemeHandle( mhWnd, L"Edit"); + if( hTheme ) + { + // get border size + tools::Rectangle aBoxRect( rControlRegion ); + tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, EP_BACKGROUNDWITHBORDER, + EBWBS_HOT, aBoxRect ) ); + // ad app font height + NONCLIENTMETRICSW aNonClientMetrics; + aNonClientMetrics.cbSize = sizeof( aNonClientMetrics ); + if ( SystemParametersInfoW( SPI_GETNONCLIENTMETRICS, sizeof( aNonClientMetrics ), &aNonClientMetrics, 0 ) ) + { + long nFontHeight = aNonClientMetrics.lfMessageFont.lfHeight; + if( nFontHeight < 0 ) + nFontHeight = -nFontHeight; + + if( aRect.GetHeight() && nFontHeight ) + { + aRect.AdjustBottom(aRect.GetHeight()); + aRect.AdjustBottom(nFontHeight); + if( aRect.GetHeight() > aBoxRect.GetHeight() ) + aBoxRect.SetBottom( aBoxRect.Top() + aRect.GetHeight() ); + if( aRect.GetWidth() > aBoxRect.GetWidth() ) + aBoxRect.SetRight( aBoxRect.Left() + aRect.GetWidth() ); + rNativeContentRegion = aBoxRect; + rNativeBoundingRegion = rNativeContentRegion; + bRet = true; + } + } + } + } + + if( GetSalData()->mbThemeMenuSupport ) + { + if( nType == ControlType::MenuPopup ) + { + if( nPart == ControlPart::MenuItemCheckMark || + nPart == ControlPart::MenuItemRadioMark ) + { + HTHEME hTheme = getThemeHandle( mhWnd, L"Menu"); + tools::Rectangle aBoxRect( rControlRegion ); + tools::Rectangle aRect( ImplGetThemeRect( hTheme, hDC, + MENU_POPUPCHECK, + MC_CHECKMARKNORMAL, + aBoxRect ) ); + if( aBoxRect.GetWidth() && aBoxRect.GetHeight() ) + { + rNativeContentRegion = aRect; + rNativeBoundingRegion = rNativeContentRegion; + bRet = true; + } + } + } + } + + if( nType == ControlType::Slider && ( (nPart == ControlPart::ThumbHorz) || (nPart == ControlPart::ThumbVert) ) ) + { + HTHEME hTheme = getThemeHandle( mhWnd, L"Trackbar"); + if( hTheme ) + { + int iPart = (nPart == ControlPart::ThumbHorz) ? TKP_THUMB : TKP_THUMBVERT; + int iState = (nPart == ControlPart::ThumbHorz) ? static_cast<int>(TUS_NORMAL) : static_cast<int>(TUVS_NORMAL); + tools::Rectangle aThumbRect = ImplGetThemeRect( hTheme, hDC, iPart, iState, tools::Rectangle() ); + if( nPart == ControlPart::ThumbHorz ) + { + long nW = aThumbRect.GetWidth(); + tools::Rectangle aRect( rControlRegion ); + aRect.SetRight( aRect.Left() + nW - 1 ); + rNativeContentRegion = aRect; + rNativeBoundingRegion = rNativeContentRegion; + } + else + { + long nH = aThumbRect.GetHeight(); + tools::Rectangle aRect( rControlRegion ); + aRect.SetBottom( aRect.Top() + nH - 1 ); + rNativeContentRegion = aRect; + rNativeBoundingRegion = rNativeContentRegion; + } + bRet = true; + } + } + + if ( ( nType == ControlType::TabItem ) && ( nPart == ControlPart::Entire ) ) + { + tools::Rectangle aControlRect( rControlRegion ); + rNativeContentRegion = aControlRect; + + aControlRect.AdjustBottom(-1); + + if( rControlValue.getType() == ControlType::TabItem ) + { + const TabitemValue& rValue = static_cast<const TabitemValue&>(rControlValue); + if (rValue.isBothAligned()) + aControlRect.AdjustRight(-1); + + if ( nState & ControlState::SELECTED ) + { + aControlRect.AdjustLeft(-2); + if (!rValue.isBothAligned()) + { + if (rValue.isLeftAligned() || rValue.isNotAligned()) + aControlRect.AdjustRight(2); + if (rValue.isRightAligned()) + aControlRect.AdjustRight(1); + } + aControlRect.AdjustTop(-2); + aControlRect.AdjustBottom(2); + } + } + rNativeBoundingRegion = aControlRect; + bRet = true; + } + + ReleaseDC( mhWnd, hDC ); + return bRet; +} + +void WinSalGraphics::updateSettingsNative( AllSettings& rSettings ) +{ + if ( !vsAPI.IsThemeActive() ) + return; + + StyleSettings aStyleSettings = rSettings.GetStyleSettings(); + ImplSVData* pSVData = ImplGetSVData(); + + // don't draw frame around each and every toolbar + pSVData->maNWFData.mbDockingAreaAvoidTBFrames = true; + + // FIXME get the color directly from the theme, not from the settings + Color aMenuBarTextColor = aStyleSettings.GetPersonaMenuBarTextColor().value_or( aStyleSettings.GetMenuTextColor() ); + // in aero menuitem highlight text is drawn in the same color as normal + aStyleSettings.SetMenuHighlightTextColor( aStyleSettings.GetMenuTextColor() ); + aStyleSettings.SetMenuBarRolloverTextColor( aMenuBarTextColor ); + aStyleSettings.SetMenuBarHighlightTextColor( aMenuBarTextColor ); + pSVData->maNWFData.mnMenuFormatBorderX = 2; + pSVData->maNWFData.mnMenuFormatBorderY = 2; + pSVData->maNWFData.maMenuBarHighlightTextColor = aMenuBarTextColor; + GetSalData()->mbThemeMenuSupport = true; + + rSettings.SetStyleSettings( aStyleSettings ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/salprn.cxx b/vcl/win/gdi/salprn.cxx new file mode 100644 index 000000000..98886c72a --- /dev/null +++ b/vcl/win/gdi/salprn.cxx @@ -0,0 +1,1629 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <memory> +#include <string.h> + +#include <svsys.h> + +#include <osl/module.h> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <tools/urlobj.hxx> + +#include <vcl/weld.hxx> + +#include <win/wincomp.hxx> +#include <win/saldata.hxx> +#include <win/salinst.h> +#include <win/salgdi.h> +#include <win/salframe.h> +#include <win/salprn.h> + +#include <salptype.hxx> +#include <print.h> +#include <jobset.h> + +#include <com/sun/star/ui/dialogs/TemplateDescription.hpp> +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> +#include <com/sun/star/ui/dialogs/FilePicker.hpp> +#include <com/sun/star/ui/dialogs/XFilterManager.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <comphelper/processfactory.hxx> + +#include <vcl/threadex.hxx> + +#include <malloc.h> + +#include <winspool.h> +#if defined GetDefaultPrinter +# undef GetDefaultPrinter +#endif +#if defined SetPrinterData +# undef SetPrinterData +#endif + +#define CATCH_DRIVER_EX_BEGIN \ + __try \ + { +#define CATCH_DRIVER_EX_END(mes, p) \ + } \ + __except(WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(GetExceptionCode(), GetExceptionInformation()))\ + { \ + OSL_FAIL( mes ); \ + p->markInvalid(); \ + } +#define CATCH_DRIVER_EX_END_2(mes) \ + } \ + __except(WinSalInstance::WorkaroundExceptionHandlingInUSER32Lib(GetExceptionCode(), GetExceptionInformation()))\ + { \ + OSL_FAIL( mes ); \ + } + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace com::sun::star::ui::dialogs; + +static const wchar_t aImplWindows[] = L"windows"; +static const wchar_t aImplDevice[] = L"device"; + +static DEVMODEW const * SAL_DEVMODE_W( const ImplJobSetup* pSetupData ) +{ + DEVMODEW const * pRet = nullptr; + SalDriverData const * pDrv = reinterpret_cast<SalDriverData const *>(pSetupData->GetDriverData()); + if( pSetupData->GetDriverDataLen() >= sizeof(DEVMODEW)+sizeof(SalDriverData)-1 ) + pRet = reinterpret_cast<DEVMODEW const *>((pSetupData->GetDriverData()) + (pDrv->mnDriverOffset)); + return pRet; +} + +static PrintQueueFlags ImplWinQueueStatusToSal( DWORD nWinStatus ) +{ + PrintQueueFlags nStatus = PrintQueueFlags::NONE; + if ( nWinStatus & PRINTER_STATUS_PAUSED ) + nStatus |= PrintQueueFlags::Paused; + if ( nWinStatus & PRINTER_STATUS_ERROR ) + nStatus |= PrintQueueFlags::Error; + if ( nWinStatus & PRINTER_STATUS_PENDING_DELETION ) + nStatus |= PrintQueueFlags::PendingDeletion; + if ( nWinStatus & PRINTER_STATUS_PAPER_JAM ) + nStatus |= PrintQueueFlags::PaperJam; + if ( nWinStatus & PRINTER_STATUS_PAPER_OUT ) + nStatus |= PrintQueueFlags::PaperOut; + if ( nWinStatus & PRINTER_STATUS_MANUAL_FEED ) + nStatus |= PrintQueueFlags::ManualFeed; + if ( nWinStatus & PRINTER_STATUS_PAPER_PROBLEM ) + nStatus |= PrintQueueFlags::PaperProblem; + if ( nWinStatus & PRINTER_STATUS_OFFLINE ) + nStatus |= PrintQueueFlags::Offline; + if ( nWinStatus & PRINTER_STATUS_IO_ACTIVE ) + nStatus |= PrintQueueFlags::IOActive; + if ( nWinStatus & PRINTER_STATUS_BUSY ) + nStatus |= PrintQueueFlags::Busy; + if ( nWinStatus & PRINTER_STATUS_PRINTING ) + nStatus |= PrintQueueFlags::Printing; + if ( nWinStatus & PRINTER_STATUS_OUTPUT_BIN_FULL ) + nStatus |= PrintQueueFlags::OutputBinFull; + if ( nWinStatus & PRINTER_STATUS_WAITING ) + nStatus |= PrintQueueFlags::Waiting; + if ( nWinStatus & PRINTER_STATUS_PROCESSING ) + nStatus |= PrintQueueFlags::Processing; + if ( nWinStatus & PRINTER_STATUS_INITIALIZING ) + nStatus |= PrintQueueFlags::Initializing; + if ( nWinStatus & PRINTER_STATUS_WARMING_UP ) + nStatus |= PrintQueueFlags::WarmingUp; + if ( nWinStatus & PRINTER_STATUS_TONER_LOW ) + nStatus |= PrintQueueFlags::TonerLow; + if ( nWinStatus & PRINTER_STATUS_NO_TONER ) + nStatus |= PrintQueueFlags::NoToner; + if ( nWinStatus & PRINTER_STATUS_PAGE_PUNT ) + nStatus |= PrintQueueFlags::PagePunt; + if ( nWinStatus & PRINTER_STATUS_USER_INTERVENTION ) + nStatus |= PrintQueueFlags::UserIntervention; + if ( nWinStatus & PRINTER_STATUS_OUT_OF_MEMORY ) + nStatus |= PrintQueueFlags::OutOfMemory; + if ( nWinStatus & PRINTER_STATUS_DOOR_OPEN ) + nStatus |= PrintQueueFlags::DoorOpen; + if ( nWinStatus & PRINTER_STATUS_SERVER_UNKNOWN ) + nStatus |= PrintQueueFlags::StatusUnknown; + if ( nWinStatus & PRINTER_STATUS_POWER_SAVE ) + nStatus |= PrintQueueFlags::PowerSave; + if ( nStatus == PrintQueueFlags::NONE && !(nWinStatus & PRINTER_STATUS_NOT_AVAILABLE) ) + nStatus |= PrintQueueFlags::Ready; + return nStatus; +} + + +void WinSalInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList ) +{ + DWORD i; + DWORD nBytes = 0; + DWORD nInfoPrn4 = 0; + EnumPrintersW( PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, nullptr, 4, nullptr, 0, &nBytes, &nInfoPrn4 ); + if ( nBytes ) + { + PRINTER_INFO_4W* pWinInfo4 = static_cast<PRINTER_INFO_4W*>(std::malloc( nBytes )); + if ( EnumPrintersW( PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS, nullptr, 4, reinterpret_cast<LPBYTE>(pWinInfo4), nBytes, &nBytes, &nInfoPrn4 ) ) + { + for ( i = 0; i < nInfoPrn4; i++ ) + { + std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo); + pInfo->maPrinterName = o3tl::toU(pWinInfo4[i].pPrinterName); + pInfo->mnStatus = PrintQueueFlags::NONE; + pInfo->mnJobs = 0; + pList->Add( std::move(pInfo) ); + } + } + std::free( pWinInfo4 ); + } +} + +void WinSalInstance::GetPrinterQueueState( SalPrinterQueueInfo* pInfo ) +{ + HANDLE hPrinter = nullptr; + LPWSTR pPrnName = const_cast<LPWSTR>(o3tl::toW(pInfo->maPrinterName.getStr())); + if( OpenPrinterW( pPrnName, &hPrinter, nullptr ) ) + { + DWORD nBytes = 0; + GetPrinterW( hPrinter, 2, nullptr, 0, &nBytes ); + if( nBytes ) + { + PRINTER_INFO_2W* pWinInfo2 = static_cast<PRINTER_INFO_2W*>(std::malloc(nBytes)); + if( GetPrinterW( hPrinter, 2, reinterpret_cast<LPBYTE>(pWinInfo2), nBytes, &nBytes ) ) + { + if( pWinInfo2->pDriverName ) + pInfo->maDriver = o3tl::toU(pWinInfo2->pDriverName); + OUString aPortName; + if ( pWinInfo2->pPortName ) + aPortName = o3tl::toU(pWinInfo2->pPortName); + // pLocation can be 0 (the Windows docu doesn't describe this) + if ( pWinInfo2->pLocation && *pWinInfo2->pLocation ) + pInfo->maLocation = o3tl::toU(pWinInfo2->pLocation); + else + pInfo->maLocation = aPortName; + // pComment can be 0 (the Windows docu doesn't describe this) + if ( pWinInfo2->pComment ) + pInfo->maComment = o3tl::toU(pWinInfo2->pComment); + pInfo->mnStatus = ImplWinQueueStatusToSal( pWinInfo2->Status ); + pInfo->mnJobs = pWinInfo2->cJobs; + if( ! pInfo->mpPortName ) + pInfo->mpPortName.reset(new OUString(aPortName)); + } + std::free(pWinInfo2); + } + ClosePrinter( hPrinter ); + } +} + +OUString WinSalInstance::GetDefaultPrinter() +{ + DWORD nChars = 0; + GetDefaultPrinterW( nullptr, &nChars ); + if( nChars ) + { + LPWSTR pStr = static_cast<LPWSTR>(std::malloc(nChars*sizeof(WCHAR))); + OUString aDefPrt; + if( GetDefaultPrinterW( pStr, &nChars ) ) + { + aDefPrt = o3tl::toU(pStr); + } + std::free( pStr ); + if( !aDefPrt.isEmpty() ) + return aDefPrt; + } + + // get default printer from win.ini + wchar_t szBuffer[256]; + GetProfileStringW( aImplWindows, aImplDevice, L"", szBuffer, SAL_N_ELEMENTS( szBuffer ) ); + if ( szBuffer[0] ) + { + // search for printer name + wchar_t* pBuf = szBuffer; + wchar_t* pTmp = pBuf; + while ( *pTmp && (*pTmp != ',') ) + pTmp++; + return OUString( o3tl::toU(pBuf), static_cast<sal_Int32>(pTmp-pBuf) ); + } + else + return OUString(); +} + +static DWORD ImplDeviceCaps( WinSalInfoPrinter const * pPrinter, WORD nCaps, + BYTE* pOutput, const ImplJobSetup* pSetupData ) +{ + DEVMODEW const * pDevMode; + if ( !pSetupData || !pSetupData->GetDriverData() ) + pDevMode = nullptr; + else + pDevMode = SAL_DEVMODE_W( pSetupData ); + + return DeviceCapabilitiesW( o3tl::toW(pPrinter->maDeviceName.getStr()), + o3tl::toW(pPrinter->maPortName.getStr()), + nCaps, reinterpret_cast<LPWSTR>(pOutput), pDevMode ); +} + +static bool ImplTestSalJobSetup( WinSalInfoPrinter const * pPrinter, + ImplJobSetup* pSetupData, bool bDelete ) +{ + if ( pSetupData && pSetupData->GetDriverData() ) + { + // signature and size must fit to avoid using + // JobSetups from a wrong system + + // initialize versions from jobsetup + // those will be overwritten with driver's version + DEVMODEW const * pDevModeW = nullptr; + LONG dmSpecVersion = -1; + LONG dmDriverVersion = -1; + SalDriverData const * pSalDriverData = reinterpret_cast<SalDriverData const *>(pSetupData->GetDriverData()); + BYTE const * pDriverData = reinterpret_cast<BYTE const *>(pSalDriverData) + pSalDriverData->mnDriverOffset; + pDevModeW = reinterpret_cast<DEVMODEW const *>(pDriverData); + + long nSysJobSize = -1; + if( pPrinter && pDevModeW ) + { + // just too many driver crashes in that area -> check the dmSpecVersion and dmDriverVersion fields always !!! + // this prevents using the jobsetup between different Windows versions (eg from XP to 9x) but we + // can avoid potential driver crashes as their jobsetups are often not compatible + // #110800#, #111151#, #112381#, #i16580#, #i14173# and perhaps #112375# + HANDLE hPrn; + LPWSTR pPrinterNameW = const_cast<LPWSTR>(o3tl::toW(pPrinter->maDeviceName.getStr())); + if ( !OpenPrinterW( pPrinterNameW, &hPrn, nullptr ) ) + return false; + + // #131642# hPrn==HGDI_ERROR even though OpenPrinter() succeeded! + if( hPrn == HGDI_ERROR ) + return false; + + nSysJobSize = DocumentPropertiesW( nullptr, hPrn, + pPrinterNameW, + nullptr, nullptr, 0 ); + + if( nSysJobSize < 0 ) + { + ClosePrinter( hPrn ); + return false; + } + DEVMODEW *pBuffer = static_cast<DEVMODEW*>(_alloca( nSysJobSize )); + LONG nRet = DocumentPropertiesW( nullptr, hPrn, + pPrinterNameW, + pBuffer, nullptr, DM_OUT_BUFFER ); + if( nRet < 0 ) + { + ClosePrinter( hPrn ); + return false; + } + + // the spec version differs between the windows platforms, ie 98,NT,2000/XP + // this allows us to throw away printer settings from other platforms that might crash a buggy driver + // we check the driver version as well + dmSpecVersion = pBuffer->dmSpecVersion; + dmDriverVersion = pBuffer->dmDriverVersion; + + ClosePrinter( hPrn ); + } + SalDriverData const * pSetupDriverData = reinterpret_cast<SalDriverData const *>(pSetupData->GetDriverData()); + if ( (pSetupData->GetSystem() == JOBSETUP_SYSTEM_WINDOWS) && + (pPrinter->maDriverName == pSetupData->GetDriver()) && + (pSetupData->GetDriverDataLen() > sizeof( SalDriverData )) && + static_cast<long>(pSetupData->GetDriverDataLen() - pSetupDriverData->mnDriverOffset) == nSysJobSize && + pSetupDriverData->mnSysSignature == SAL_DRIVERDATA_SYSSIGN ) + { + if( pDevModeW && + (dmSpecVersion == pDevModeW->dmSpecVersion) && + (dmDriverVersion == pDevModeW->dmDriverVersion) ) + return true; + } + if ( bDelete ) + { + std::free( const_cast<sal_uInt8*>(pSetupData->GetDriverData()) ); + pSetupData->SetDriverData( nullptr ); + pSetupData->SetDriverDataLen( 0 ); + } + } + + return false; +} + +static bool ImplUpdateSalJobSetup( WinSalInfoPrinter const * pPrinter, ImplJobSetup* pSetupData, + bool bIn, weld::Window* pVisibleDlgParent ) +{ + HANDLE hPrn; + LPWSTR pPrinterNameW = const_cast<LPWSTR>(o3tl::toW(pPrinter->maDeviceName.getStr())); + if ( !OpenPrinterW( pPrinterNameW, &hPrn, nullptr ) ) + return false; + // #131642# hPrn==HGDI_ERROR even though OpenPrinter() succeeded! + if( hPrn == HGDI_ERROR ) + return false; + + LONG nRet; + HWND hWnd = nullptr; + DWORD nMode = DM_OUT_BUFFER; + SalDriverData* pOutBuffer = nullptr; + BYTE const * pInBuffer = nullptr; + + LONG nSysJobSize = DocumentPropertiesW( hWnd, hPrn, + pPrinterNameW, + nullptr, nullptr, 0 ); + if ( nSysJobSize < 0 ) + { + ClosePrinter( hPrn ); + return false; + } + + // make Outputbuffer + const std::size_t nDriverDataLen = sizeof(SalDriverData) + nSysJobSize-1; + pOutBuffer = static_cast<SalDriverData*>(rtl_allocateZeroMemory( nDriverDataLen )); + pOutBuffer->mnSysSignature = SAL_DRIVERDATA_SYSSIGN; + // calculate driver data offset including structure padding + pOutBuffer->mnDriverOffset = sal::static_int_cast<sal_uInt16>( + reinterpret_cast<char*>(pOutBuffer->maDriverData) - + reinterpret_cast<char*>(pOutBuffer) ); + + // check if we have a suitable input buffer + if ( bIn && ImplTestSalJobSetup( pPrinter, pSetupData, false ) ) + { + pInBuffer = pSetupData->GetDriverData() + reinterpret_cast<SalDriverData const *>(pSetupData->GetDriverData())->mnDriverOffset; + nMode |= DM_IN_BUFFER; + } + + // check if the dialog should be shown + if ( pVisibleDlgParent ) + { + hWnd = pVisibleDlgParent->get_system_data().hWnd; + nMode |= DM_IN_PROMPT; + } + + // Release mutex, in the other case we don't get paints and so on + sal_uInt32 nMutexCount = 0; + WinSalInstance* pInst = GetSalData()->mpInstance; + if ( pInst && pVisibleDlgParent ) + nMutexCount = pInst->ReleaseYieldMutexAll(); + + BYTE* pOutDevMode = reinterpret_cast<BYTE*>(pOutBuffer) + pOutBuffer->mnDriverOffset; + nRet = DocumentPropertiesW( hWnd, hPrn, + pPrinterNameW, + reinterpret_cast<LPDEVMODEW>(pOutDevMode), reinterpret_cast<LPDEVMODEW>(const_cast<BYTE *>(pInBuffer)), nMode ); + if ( pInst && pVisibleDlgParent ) + pInst->AcquireYieldMutex( nMutexCount ); + ClosePrinter( hPrn ); + + if( (nRet < 0) || (pVisibleDlgParent && (nRet == IDCANCEL)) ) + { + std::free( pOutBuffer ); + return false; + } + + // fill up string buffers with 0 so they do not influence a JobSetup's memcmp + if( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmSize >= 64 ) + { + sal_Int32 nLen = rtl_ustr_getLength( o3tl::toU(reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmDeviceName) ); + if ( sal::static_int_cast<size_t>(nLen) < SAL_N_ELEMENTS( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmDeviceName ) ) + memset( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmDeviceName+nLen, 0, sizeof( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmDeviceName )-(nLen*sizeof(sal_Unicode)) ); + } + if( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmSize >= 166 ) + { + sal_Int32 nLen = rtl_ustr_getLength( o3tl::toU(reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmFormName) ); + if ( sal::static_int_cast<size_t>(nLen) < SAL_N_ELEMENTS( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmFormName ) ) + memset( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmFormName+nLen, 0, sizeof( reinterpret_cast<LPDEVMODEW>(pOutDevMode)->dmFormName )-(nLen*sizeof(sal_Unicode)) ); + } + + // update data + if ( pSetupData->GetDriverData() ) + std::free( const_cast<sal_uInt8*>(pSetupData->GetDriverData()) ); + pSetupData->SetDriverDataLen( nDriverDataLen ); + pSetupData->SetDriverData(reinterpret_cast<BYTE*>(pOutBuffer)); + pSetupData->SetSystem( JOBSETUP_SYSTEM_WINDOWS ); + + return true; +} + +static void ImplDevModeToJobSetup( WinSalInfoPrinter const * pPrinter, ImplJobSetup* pSetupData, JobSetFlags nFlags ) +{ + if ( !pSetupData || !pSetupData->GetDriverData() ) + return; + + DEVMODEW const * pDevModeW = SAL_DEVMODE_W(pSetupData); + if( pDevModeW == nullptr ) + return; + + // Orientation + if ( nFlags & JobSetFlags::ORIENTATION ) + { + if ( pDevModeW->dmOrientation == DMORIENT_PORTRAIT ) + pSetupData->SetOrientation( Orientation::Portrait ); + else if ( pDevModeW->dmOrientation == DMORIENT_LANDSCAPE ) + pSetupData->SetOrientation( Orientation::Landscape ); + } + + // PaperBin + if ( nFlags & JobSetFlags::PAPERBIN ) + { + const DWORD nCount = ImplDeviceCaps( pPrinter, DC_BINS, nullptr, pSetupData ); + + if ( nCount && (nCount != GDI_ERROR) ) + { + WORD* pBins = static_cast<WORD*>(rtl_allocateZeroMemory( nCount*sizeof(WORD) )); + ImplDeviceCaps( pPrinter, DC_BINS, reinterpret_cast<BYTE*>(pBins), pSetupData ); + pSetupData->SetPaperBin( 0 ); + + // search the right bin and assign index to mnPaperBin + for( DWORD i = 0; i < nCount; ++i ) + { + if( pDevModeW->dmDefaultSource == pBins[ i ] ) + { + pSetupData->SetPaperBin( static_cast<sal_uInt16>(i) ); + break; + } + } + + std::free( pBins ); + } + } + + // PaperSize + if ( nFlags & JobSetFlags::PAPERSIZE ) + { + if( (pDevModeW->dmFields & (DM_PAPERWIDTH|DM_PAPERLENGTH)) == (DM_PAPERWIDTH|DM_PAPERLENGTH) ) + { + pSetupData->SetPaperWidth( pDevModeW->dmPaperWidth*10 ); + pSetupData->SetPaperHeight( pDevModeW->dmPaperLength*10 ); + } + else + { + const DWORD nPaperCount = ImplDeviceCaps( pPrinter, DC_PAPERS, nullptr, pSetupData ); + WORD* pPapers = nullptr; + const DWORD nPaperSizeCount = ImplDeviceCaps( pPrinter, DC_PAPERSIZE, nullptr, pSetupData ); + POINT* pPaperSizes = nullptr; + if ( nPaperCount && (nPaperCount != GDI_ERROR) ) + { + pPapers = static_cast<WORD*>(rtl_allocateZeroMemory(nPaperCount*sizeof(WORD))); + ImplDeviceCaps( pPrinter, DC_PAPERS, reinterpret_cast<BYTE*>(pPapers), pSetupData ); + } + if ( nPaperSizeCount && (nPaperSizeCount != GDI_ERROR) ) + { + pPaperSizes = static_cast<POINT*>(rtl_allocateZeroMemory(nPaperSizeCount*sizeof(POINT))); + ImplDeviceCaps( pPrinter, DC_PAPERSIZE, reinterpret_cast<BYTE*>(pPaperSizes), pSetupData ); + } + if( nPaperSizeCount == nPaperCount && pPaperSizes && pPapers ) + { + for( DWORD i = 0; i < nPaperCount; ++i ) + { + if( pPapers[ i ] == pDevModeW->dmPaperSize ) + { + pSetupData->SetPaperWidth( pPaperSizes[ i ].x*10 ); + pSetupData->SetPaperHeight( pPaperSizes[ i ].y*10 ); + break; + } + } + } + if( pPapers ) + std::free( pPapers ); + if( pPaperSizes ) + std::free( pPaperSizes ); + } + switch( pDevModeW->dmPaperSize ) + { + case DMPAPER_LETTER: + pSetupData->SetPaperFormat( PAPER_LETTER ); + break; + case DMPAPER_TABLOID: + pSetupData->SetPaperFormat( PAPER_TABLOID ); + break; + case DMPAPER_LEDGER: + pSetupData->SetPaperFormat( PAPER_LEDGER ); + break; + case DMPAPER_LEGAL: + pSetupData->SetPaperFormat( PAPER_LEGAL ); + break; + case DMPAPER_STATEMENT: + pSetupData->SetPaperFormat( PAPER_STATEMENT ); + break; + case DMPAPER_EXECUTIVE: + pSetupData->SetPaperFormat( PAPER_EXECUTIVE ); + break; + case DMPAPER_A3: + pSetupData->SetPaperFormat( PAPER_A3 ); + break; + case DMPAPER_A4: + pSetupData->SetPaperFormat( PAPER_A4 ); + break; + case DMPAPER_A5: + pSetupData->SetPaperFormat( PAPER_A5 ); + break; + //See http://wiki.openoffice.org/wiki/DefaultPaperSize + //i.e. + //http://msdn.microsoft.com/en-us/library/dd319099(VS.85).aspx + //DMPAPER_B4 12 B4 (JIS) 257 x 364 mm + //http://partners.adobe.com/public/developer/en/ps/5003.PPD_Spec_v4.3.pdf + //also says that the MS DMPAPER_B4 is JIS, which makes most sense. And + //matches our Excel filter's belief about the matching XlPaperSize + //enumeration. + + //http://msdn.microsoft.com/en-us/library/ms776398(VS.85).aspx said + ////"DMPAPER_B4 12 B4 (JIS) 250 x 354" + //which is bogus as it's either JIS 257 x 364 or ISO 250 x 353 + //(cmc) + case DMPAPER_B4: + pSetupData->SetPaperFormat( PAPER_B4_JIS ); + break; + case DMPAPER_B5: + pSetupData->SetPaperFormat( PAPER_B5_JIS ); + break; + case DMPAPER_QUARTO: + pSetupData->SetPaperFormat( PAPER_QUARTO ); + break; + case DMPAPER_10X14: + pSetupData->SetPaperFormat( PAPER_10x14 ); + break; + case DMPAPER_NOTE: + pSetupData->SetPaperFormat( PAPER_LETTER ); + break; + case DMPAPER_ENV_9: + pSetupData->SetPaperFormat( PAPER_ENV_9 ); + break; + case DMPAPER_ENV_10: + pSetupData->SetPaperFormat( PAPER_ENV_10 ); + break; + case DMPAPER_ENV_11: + pSetupData->SetPaperFormat( PAPER_ENV_11 ); + break; + case DMPAPER_ENV_12: + pSetupData->SetPaperFormat( PAPER_ENV_12 ); + break; + case DMPAPER_ENV_14: + pSetupData->SetPaperFormat( PAPER_ENV_14 ); + break; + case DMPAPER_CSHEET: + pSetupData->SetPaperFormat( PAPER_C ); + break; + case DMPAPER_DSHEET: + pSetupData->SetPaperFormat( PAPER_D ); + break; + case DMPAPER_ESHEET: + pSetupData->SetPaperFormat( PAPER_E ); + break; + case DMPAPER_ENV_DL: + pSetupData->SetPaperFormat( PAPER_ENV_DL ); + break; + case DMPAPER_ENV_C5: + pSetupData->SetPaperFormat( PAPER_ENV_C5 ); + break; + case DMPAPER_ENV_C3: + pSetupData->SetPaperFormat( PAPER_ENV_C3 ); + break; + case DMPAPER_ENV_C4: + pSetupData->SetPaperFormat( PAPER_ENV_C4 ); + break; + case DMPAPER_ENV_C6: + pSetupData->SetPaperFormat( PAPER_ENV_C6 ); + break; + case DMPAPER_ENV_C65: + pSetupData->SetPaperFormat( PAPER_ENV_C65 ); + break; + case DMPAPER_ENV_ITALY: + pSetupData->SetPaperFormat( PAPER_ENV_ITALY ); + break; + case DMPAPER_ENV_MONARCH: + pSetupData->SetPaperFormat( PAPER_ENV_MONARCH ); + break; + case DMPAPER_ENV_PERSONAL: + pSetupData->SetPaperFormat( PAPER_ENV_PERSONAL ); + break; + case DMPAPER_FANFOLD_US: + pSetupData->SetPaperFormat( PAPER_FANFOLD_US ); + break; + case DMPAPER_FANFOLD_STD_GERMAN: + pSetupData->SetPaperFormat( PAPER_FANFOLD_DE ); + break; + case DMPAPER_FANFOLD_LGL_GERMAN: + pSetupData->SetPaperFormat( PAPER_FANFOLD_LEGAL_DE ); + break; + case DMPAPER_ISO_B4: + pSetupData->SetPaperFormat( PAPER_B4_ISO ); + break; + case DMPAPER_JAPANESE_POSTCARD: + pSetupData->SetPaperFormat( PAPER_POSTCARD_JP ); + break; + case DMPAPER_9X11: + pSetupData->SetPaperFormat( PAPER_9x11 ); + break; + case DMPAPER_10X11: + pSetupData->SetPaperFormat( PAPER_10x11 ); + break; + case DMPAPER_15X11: + pSetupData->SetPaperFormat( PAPER_15x11 ); + break; + case DMPAPER_ENV_INVITE: + pSetupData->SetPaperFormat( PAPER_ENV_INVITE ); + break; + case DMPAPER_A_PLUS: + pSetupData->SetPaperFormat( PAPER_A_PLUS ); + break; + case DMPAPER_B_PLUS: + pSetupData->SetPaperFormat( PAPER_B_PLUS ); + break; + case DMPAPER_LETTER_PLUS: + pSetupData->SetPaperFormat( PAPER_LETTER_PLUS ); + break; + case DMPAPER_A4_PLUS: + pSetupData->SetPaperFormat( PAPER_A4_PLUS ); + break; + case DMPAPER_A2: + pSetupData->SetPaperFormat( PAPER_A2 ); + break; + case DMPAPER_DBL_JAPANESE_POSTCARD: + pSetupData->SetPaperFormat( PAPER_DOUBLEPOSTCARD_JP ); + break; + case DMPAPER_A6: + pSetupData->SetPaperFormat( PAPER_A6 ); + break; + case DMPAPER_B6_JIS: + pSetupData->SetPaperFormat( PAPER_B6_JIS ); + break; + case DMPAPER_12X11: + pSetupData->SetPaperFormat( PAPER_12x11 ); + break; + default: + pSetupData->SetPaperFormat( PAPER_USER ); + break; + } + } + + if( nFlags & JobSetFlags::DUPLEXMODE ) + { + DuplexMode eDuplex = DuplexMode::Unknown; + if( pDevModeW->dmFields & DM_DUPLEX ) + { + if( pDevModeW->dmDuplex == DMDUP_SIMPLEX ) + eDuplex = DuplexMode::Off; + else if( pDevModeW->dmDuplex == DMDUP_VERTICAL ) + eDuplex = DuplexMode::LongEdge; + else if( pDevModeW->dmDuplex == DMDUP_HORIZONTAL ) + eDuplex = DuplexMode::ShortEdge; + } + pSetupData->SetDuplexMode( eDuplex ); + } +} + +static void ImplJobSetupToDevMode( WinSalInfoPrinter const * pPrinter, const ImplJobSetup* pSetupData, JobSetFlags nFlags ) +{ + if ( !pSetupData || !pSetupData->GetDriverData() ) + return; + + DEVMODEW* pDevModeW = const_cast<DEVMODEW *>(SAL_DEVMODE_W(pSetupData)); + if( pDevModeW == nullptr ) + return; + + // Orientation + if ( nFlags & JobSetFlags::ORIENTATION ) + { + pDevModeW->dmFields |= DM_ORIENTATION; + if ( pSetupData->GetOrientation() == Orientation::Portrait ) + pDevModeW->dmOrientation = DMORIENT_PORTRAIT; + else + pDevModeW->dmOrientation = DMORIENT_LANDSCAPE; + } + + // PaperBin + if ( nFlags & JobSetFlags::PAPERBIN ) + { + const DWORD nCount = ImplDeviceCaps( pPrinter, DC_BINS, nullptr, pSetupData ); + + if ( nCount && (nCount != GDI_ERROR) ) + { + WORD* pBins = static_cast<WORD*>(rtl_allocateZeroMemory(nCount*sizeof(WORD))); + ImplDeviceCaps( pPrinter, DC_BINS, reinterpret_cast<BYTE*>(pBins), pSetupData ); + pDevModeW->dmFields |= DM_DEFAULTSOURCE; + pDevModeW->dmDefaultSource = pBins[ pSetupData->GetPaperBin() ]; + std::free( pBins ); + } + } + + // PaperSize + if ( nFlags & JobSetFlags::PAPERSIZE ) + { + pDevModeW->dmFields |= DM_PAPERSIZE; + pDevModeW->dmPaperWidth = 0; + pDevModeW->dmPaperLength = 0; + + switch( pSetupData->GetPaperFormat() ) + { + case PAPER_A2: + pDevModeW->dmPaperSize = DMPAPER_A2; + break; + case PAPER_A3: + pDevModeW->dmPaperSize = DMPAPER_A3; + break; + case PAPER_A4: + pDevModeW->dmPaperSize = DMPAPER_A4; + break; + case PAPER_A5: + pDevModeW->dmPaperSize = DMPAPER_A5; + break; + case PAPER_B4_ISO: + pDevModeW->dmPaperSize = DMPAPER_ISO_B4; + break; + case PAPER_LETTER: + pDevModeW->dmPaperSize = DMPAPER_LETTER; + break; + case PAPER_LEGAL: + pDevModeW->dmPaperSize = DMPAPER_LEGAL; + break; + case PAPER_TABLOID: + pDevModeW->dmPaperSize = DMPAPER_TABLOID; + break; + + // http://msdn.microsoft.com/en-us/library/ms776398(VS.85).aspx + // DMPAPER_ENV_B6 is documented as: + // "DMPAPER_ENV_B6 35 Envelope B6 176 x 125 mm" + // which is the wrong way around, it is surely 125 x 176, i.e. + // compare DMPAPER_ENV_B4 and DMPAPER_ENV_B4 as + // DMPAPER_ENV_B4 33 Envelope B4 250 x 353 mm + // DMPAPER_ENV_B5 34 Envelope B5 176 x 250 mm + + case PAPER_ENV_C4: + pDevModeW->dmPaperSize = DMPAPER_ENV_C4; + break; + case PAPER_ENV_C5: + pDevModeW->dmPaperSize = DMPAPER_ENV_C5; + break; + case PAPER_ENV_C6: + pDevModeW->dmPaperSize = DMPAPER_ENV_C6; + break; + case PAPER_ENV_C65: + pDevModeW->dmPaperSize = DMPAPER_ENV_C65; + break; + case PAPER_ENV_DL: + pDevModeW->dmPaperSize = DMPAPER_ENV_DL; + break; + case PAPER_C: + pDevModeW->dmPaperSize = DMPAPER_CSHEET; + break; + case PAPER_D: + pDevModeW->dmPaperSize = DMPAPER_DSHEET; + break; + case PAPER_E: + pDevModeW->dmPaperSize = DMPAPER_ESHEET; + break; + case PAPER_EXECUTIVE: + pDevModeW->dmPaperSize = DMPAPER_EXECUTIVE; + break; + case PAPER_FANFOLD_LEGAL_DE: + pDevModeW->dmPaperSize = DMPAPER_FANFOLD_LGL_GERMAN; + break; + case PAPER_ENV_MONARCH: + pDevModeW->dmPaperSize = DMPAPER_ENV_MONARCH; + break; + case PAPER_ENV_PERSONAL: + pDevModeW->dmPaperSize = DMPAPER_ENV_PERSONAL; + break; + case PAPER_ENV_9: + pDevModeW->dmPaperSize = DMPAPER_ENV_9; + break; + case PAPER_ENV_10: + pDevModeW->dmPaperSize = DMPAPER_ENV_10; + break; + case PAPER_ENV_11: + pDevModeW->dmPaperSize = DMPAPER_ENV_11; + break; + case PAPER_ENV_12: + pDevModeW->dmPaperSize = DMPAPER_ENV_12; + break; + //See the comments on DMPAPER_B4 above + case PAPER_B4_JIS: + pDevModeW->dmPaperSize = DMPAPER_B4; + break; + case PAPER_B5_JIS: + pDevModeW->dmPaperSize = DMPAPER_B5; + break; + case PAPER_B6_JIS: + pDevModeW->dmPaperSize = DMPAPER_B6_JIS; + break; + case PAPER_LEDGER: + pDevModeW->dmPaperSize = DMPAPER_LEDGER; + break; + case PAPER_STATEMENT: + pDevModeW->dmPaperSize = DMPAPER_STATEMENT; + break; + case PAPER_10x14: + pDevModeW->dmPaperSize = DMPAPER_10X14; + break; + case PAPER_ENV_14: + pDevModeW->dmPaperSize = DMPAPER_ENV_14; + break; + case PAPER_ENV_C3: + pDevModeW->dmPaperSize = DMPAPER_ENV_C3; + break; + case PAPER_ENV_ITALY: + pDevModeW->dmPaperSize = DMPAPER_ENV_ITALY; + break; + case PAPER_FANFOLD_US: + pDevModeW->dmPaperSize = DMPAPER_FANFOLD_US; + break; + case PAPER_FANFOLD_DE: + pDevModeW->dmPaperSize = DMPAPER_FANFOLD_STD_GERMAN; + break; + case PAPER_POSTCARD_JP: + pDevModeW->dmPaperSize = DMPAPER_JAPANESE_POSTCARD; + break; + case PAPER_9x11: + pDevModeW->dmPaperSize = DMPAPER_9X11; + break; + case PAPER_10x11: + pDevModeW->dmPaperSize = DMPAPER_10X11; + break; + case PAPER_15x11: + pDevModeW->dmPaperSize = DMPAPER_15X11; + break; + case PAPER_ENV_INVITE: + pDevModeW->dmPaperSize = DMPAPER_ENV_INVITE; + break; + case PAPER_A_PLUS: + pDevModeW->dmPaperSize = DMPAPER_A_PLUS; + break; + case PAPER_B_PLUS: + pDevModeW->dmPaperSize = DMPAPER_B_PLUS; + break; + case PAPER_LETTER_PLUS: + pDevModeW->dmPaperSize = DMPAPER_LETTER_PLUS; + break; + case PAPER_A4_PLUS: + pDevModeW->dmPaperSize = DMPAPER_A4_PLUS; + break; + case PAPER_DOUBLEPOSTCARD_JP: + pDevModeW->dmPaperSize = DMPAPER_DBL_JAPANESE_POSTCARD; + break; + case PAPER_A6: + pDevModeW->dmPaperSize = DMPAPER_A6; + break; + case PAPER_12x11: + pDevModeW->dmPaperSize = DMPAPER_12X11; + break; + default: + { + short nPaper = 0; + const DWORD nPaperCount = ImplDeviceCaps( pPrinter, DC_PAPERS, nullptr, pSetupData ); + WORD* pPapers = nullptr; + const DWORD nPaperSizeCount = ImplDeviceCaps( pPrinter, DC_PAPERSIZE, nullptr, pSetupData ); + POINT* pPaperSizes = nullptr; + DWORD nLandscapeAngle = ImplDeviceCaps( pPrinter, DC_ORIENTATION, nullptr, pSetupData ); + if ( nPaperCount && (nPaperCount != GDI_ERROR) ) + { + pPapers = static_cast<WORD*>(rtl_allocateZeroMemory(nPaperCount*sizeof(WORD))); + ImplDeviceCaps( pPrinter, DC_PAPERS, reinterpret_cast<BYTE*>(pPapers), pSetupData ); + } + if ( nPaperSizeCount && (nPaperSizeCount != GDI_ERROR) ) + { + pPaperSizes = static_cast<POINT*>(rtl_allocateZeroMemory(nPaperSizeCount*sizeof(POINT))); + ImplDeviceCaps( pPrinter, DC_PAPERSIZE, reinterpret_cast<BYTE*>(pPaperSizes), pSetupData ); + } + if ( (nPaperSizeCount == nPaperCount) && pPapers && pPaperSizes ) + { + PaperInfo aInfo(pSetupData->GetPaperWidth(), pSetupData->GetPaperHeight()); + // compare paper formats and select a good match + for ( DWORD i = 0; i < nPaperCount; ++i ) + { + if ( aInfo.sloppyEqual(PaperInfo(pPaperSizes[i].x*10, pPaperSizes[i].y*10))) + { + nPaper = pPapers[i]; + break; + } + } + + // If the printer supports landscape orientation, check paper sizes again + // with landscape orientation. This is necessary as a printer driver provides + // all paper sizes with portrait orientation only!! + if ( !nPaper && nLandscapeAngle != 0 ) + { + PaperInfo aRotatedInfo(pSetupData->GetPaperHeight(), pSetupData->GetPaperWidth()); + for ( DWORD i = 0; i < nPaperCount; ++i ) + { + if ( aRotatedInfo.sloppyEqual(PaperInfo(pPaperSizes[i].x*10, pPaperSizes[i].y*10)) ) + { + nPaper = pPapers[i]; + break; + } + } + } + + if ( nPaper ) + pDevModeW->dmPaperSize = nPaper; + } + + if ( !nPaper ) + { + pDevModeW->dmFields |= DM_PAPERLENGTH | DM_PAPERWIDTH; + pDevModeW->dmPaperSize = DMPAPER_USER; + pDevModeW->dmPaperWidth = static_cast<short>(pSetupData->GetPaperWidth()/10); + pDevModeW->dmPaperLength = static_cast<short>(pSetupData->GetPaperHeight()/10); + } + + if ( pPapers ) + std::free(pPapers); + if ( pPaperSizes ) + std::free(pPaperSizes); + + break; + } + } + } + if( nFlags & JobSetFlags::DUPLEXMODE ) + { + switch( pSetupData->GetDuplexMode() ) + { + case DuplexMode::Off: + pDevModeW->dmFields |= DM_DUPLEX; + pDevModeW->dmDuplex = DMDUP_SIMPLEX; + break; + case DuplexMode::ShortEdge: + pDevModeW->dmFields |= DM_DUPLEX; + pDevModeW->dmDuplex = DMDUP_HORIZONTAL; + break; + case DuplexMode::LongEdge: + pDevModeW->dmFields |= DM_DUPLEX; + pDevModeW->dmDuplex = DMDUP_VERTICAL; + break; + case DuplexMode::Unknown: + break; + } + } +} + +static HDC ImplCreateICW_WithCatch( LPWSTR pDriver, + LPCWSTR pDevice, + DEVMODEW const * pDevMode ) +{ + HDC hDC = nullptr; + CATCH_DRIVER_EX_BEGIN; + hDC = CreateICW( pDriver, pDevice, nullptr, pDevMode ); + CATCH_DRIVER_EX_END_2( "exception in CreateICW" ); + return hDC; +} + +static HDC ImplCreateSalPrnIC( WinSalInfoPrinter const * pPrinter, const ImplJobSetup* pSetupData ) +{ + HDC hDC = nullptr; + DEVMODEW const * pDevMode; + if ( pSetupData && pSetupData->GetDriverData() ) + pDevMode = SAL_DEVMODE_W( pSetupData ); + else + pDevMode = nullptr; + // #95347 some buggy drivers (eg, OKI) write to those buffers in CreateIC, although declared const - so provide some space + // pl: does this hold true for Unicode functions ? + if( pPrinter->maDriverName.getLength() > 2048 || pPrinter->maDeviceName.getLength() > 2048 ) + return nullptr; + sal_Unicode pDriverName[ 4096 ]; + sal_Unicode pDeviceName[ 4096 ]; + memcpy( pDriverName, pPrinter->maDriverName.getStr(), pPrinter->maDriverName.getLength()*sizeof(sal_Unicode)); + memset( pDriverName+pPrinter->maDriverName.getLength(), 0, 32 ); + memcpy( pDeviceName, pPrinter->maDeviceName.getStr(), pPrinter->maDeviceName.getLength()*sizeof(sal_Unicode)); + memset( pDeviceName+pPrinter->maDeviceName.getLength(), 0, 32 ); + hDC = ImplCreateICW_WithCatch( o3tl::toW(pDriverName), + o3tl::toW(pDeviceName), + pDevMode ); + return hDC; +} + +static WinSalGraphics* ImplCreateSalPrnGraphics( HDC hDC ) +{ + WinSalGraphics* pGraphics = new WinSalGraphics(WinSalGraphics::PRINTER, false, nullptr, /* CHECKME */ nullptr); + pGraphics->SetLayout( SalLayoutFlags::NONE ); + pGraphics->setHDC(hDC); + pGraphics->InitGraphics(); + return pGraphics; +} + +static bool ImplUpdateSalPrnIC( WinSalInfoPrinter* pPrinter, const ImplJobSetup* pSetupData ) +{ + HDC hNewDC = ImplCreateSalPrnIC( pPrinter, pSetupData ); + if ( !hNewDC ) + return false; + + if ( pPrinter->mpGraphics ) + { + pPrinter->mpGraphics->DeInitGraphics(); + DeleteDC( pPrinter->mpGraphics->getHDC() ); + delete pPrinter->mpGraphics; + } + + pPrinter->mpGraphics = ImplCreateSalPrnGraphics( hNewDC ); + pPrinter->mhDC = hNewDC; + + return true; +} + + +SalInfoPrinter* WinSalInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo, + ImplJobSetup* pSetupData ) +{ + WinSalInfoPrinter* pPrinter = new WinSalInfoPrinter; + if( ! pQueueInfo->mpPortName ) + GetPrinterQueueState( pQueueInfo ); + pPrinter->maDriverName = pQueueInfo->maDriver; + pPrinter->maDeviceName = pQueueInfo->maPrinterName; + pPrinter->maPortName = pQueueInfo->mpPortName ? *pQueueInfo->mpPortName : OUString(); + + // check if the provided setup data match the actual printer + ImplTestSalJobSetup( pPrinter, pSetupData, true ); + + HDC hDC = ImplCreateSalPrnIC( pPrinter, pSetupData ); + if ( !hDC ) + { + delete pPrinter; + return nullptr; + } + + pPrinter->mpGraphics = ImplCreateSalPrnGraphics( hDC ); + pPrinter->mhDC = hDC; + if ( !pSetupData->GetDriverData() ) + ImplUpdateSalJobSetup( pPrinter, pSetupData, false, nullptr ); + ImplDevModeToJobSetup( pPrinter, pSetupData, JobSetFlags::ALL ); + pSetupData->SetSystem( JOBSETUP_SYSTEM_WINDOWS ); + + return pPrinter; +} + +void WinSalInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter ) +{ + delete pPrinter; +} + + +WinSalInfoPrinter::WinSalInfoPrinter() : + mpGraphics( nullptr ), + mhDC( nullptr ), + mbGraphics( false ) +{ + m_bPapersInit = false; +} + +WinSalInfoPrinter::~WinSalInfoPrinter() +{ + if ( mpGraphics ) + { + mpGraphics->DeInitGraphics(); + DeleteDC( mpGraphics->getHDC() ); + delete mpGraphics; + } +} + +void WinSalInfoPrinter::InitPaperFormats( const ImplJobSetup* pSetupData ) +{ + m_aPaperFormats.clear(); + + DWORD nCount = ImplDeviceCaps( this, DC_PAPERSIZE, nullptr, pSetupData ); + if( nCount == GDI_ERROR ) + nCount = 0; + + if( nCount ) + { + POINT* pPaperSizes = static_cast<POINT*>(rtl_allocateZeroMemory(nCount*sizeof(POINT))); + ImplDeviceCaps( this, DC_PAPERSIZE, reinterpret_cast<BYTE*>(pPaperSizes), pSetupData ); + + sal_Unicode* pNamesBuffer = static_cast<sal_Unicode*>(std::malloc(nCount*64*sizeof(sal_Unicode))); + ImplDeviceCaps( this, DC_PAPERNAMES, reinterpret_cast<BYTE*>(pNamesBuffer), pSetupData ); + for( DWORD i = 0; i < nCount; ++i ) + { + PaperInfo aInfo(pPaperSizes[i].x * 10, pPaperSizes[i].y * 10); + m_aPaperFormats.push_back( aInfo ); + } + std::free( pNamesBuffer ); + std::free( pPaperSizes ); + } + + m_bPapersInit = true; +} + +int WinSalInfoPrinter::GetLandscapeAngle( const ImplJobSetup* pSetupData ) +{ + const DWORD nRet = ImplDeviceCaps( this, DC_ORIENTATION, nullptr, pSetupData ); + + if( nRet != GDI_ERROR ) + return static_cast<int>(nRet) * 10; + return 900; // guess +} + +SalGraphics* WinSalInfoPrinter::AcquireGraphics() +{ + if ( mbGraphics ) + return nullptr; + + if ( mpGraphics ) + mbGraphics = true; + + return mpGraphics; +} + +void WinSalInfoPrinter::ReleaseGraphics( SalGraphics* ) +{ + mbGraphics = false; +} + +bool WinSalInfoPrinter::Setup(weld::Window* pFrame, ImplJobSetup* pSetupData) +{ + if ( ImplUpdateSalJobSetup(this, pSetupData, true, pFrame)) + { + ImplDevModeToJobSetup( this, pSetupData, JobSetFlags::ALL ); + return ImplUpdateSalPrnIC( this, pSetupData ); + } + + return false; +} + +bool WinSalInfoPrinter::SetPrinterData( ImplJobSetup* pSetupData ) +{ + if ( !ImplTestSalJobSetup( this, pSetupData, false ) ) + return false; + return ImplUpdateSalPrnIC( this, pSetupData ); +} + +bool WinSalInfoPrinter::SetData( JobSetFlags nFlags, ImplJobSetup* pSetupData ) +{ + ImplJobSetupToDevMode( this, pSetupData, nFlags ); + if ( ImplUpdateSalJobSetup( this, pSetupData, true, nullptr ) ) + { + ImplDevModeToJobSetup( this, pSetupData, nFlags ); + return ImplUpdateSalPrnIC( this, pSetupData ); + } + + return false; +} + +sal_uInt16 WinSalInfoPrinter::GetPaperBinCount( const ImplJobSetup* pSetupData ) +{ + DWORD nRet = ImplDeviceCaps( this, DC_BINS, nullptr, pSetupData ); + if ( nRet && (nRet != GDI_ERROR) ) + return nRet; + else + return 0; +} + +OUString WinSalInfoPrinter::GetPaperBinName( const ImplJobSetup* pSetupData, sal_uInt16 nPaperBin ) +{ + OUString aPaperBinName; + + DWORD nBins = ImplDeviceCaps( this, DC_BINNAMES, nullptr, pSetupData ); + if ( (nPaperBin < nBins) && (nBins != GDI_ERROR) ) + { + auto pBuffer = std::make_unique<sal_Unicode[]>(nBins*24); + DWORD nRet = ImplDeviceCaps( this, DC_BINNAMES, reinterpret_cast<BYTE*>(pBuffer.get()), pSetupData ); + if ( nRet && (nRet != GDI_ERROR) ) + aPaperBinName = OUString( pBuffer.get() + (nPaperBin*24) ); + } + + return aPaperBinName; +} + +sal_uInt32 WinSalInfoPrinter::GetCapabilities( const ImplJobSetup* pSetupData, PrinterCapType nType ) +{ + DWORD nRet; + + switch ( nType ) + { + case PrinterCapType::SupportDialog: + return TRUE; + case PrinterCapType::Copies: + nRet = ImplDeviceCaps( this, DC_COPIES, nullptr, pSetupData ); + if ( nRet && (nRet != GDI_ERROR) ) + return nRet; + return 0; + case PrinterCapType::CollateCopies: + nRet = ImplDeviceCaps( this, DC_COLLATE, nullptr, pSetupData ); + if ( nRet && (nRet != GDI_ERROR) ) + { + nRet = ImplDeviceCaps( this, DC_COPIES, nullptr, pSetupData ); + if ( nRet && (nRet != GDI_ERROR) ) + return nRet; + } + return 0; + + case PrinterCapType::SetOrientation: + nRet = ImplDeviceCaps( this, DC_ORIENTATION, nullptr, pSetupData ); + if ( nRet && (nRet != GDI_ERROR) ) + return TRUE; + return FALSE; + + case PrinterCapType::SetPaperSize: + case PrinterCapType::SetPaper: + nRet = ImplDeviceCaps( this, DC_PAPERS, nullptr, pSetupData ); + if ( nRet && (nRet != GDI_ERROR) ) + return TRUE; + return FALSE; + + default: + break; + } + + return 0; +} + +void WinSalInfoPrinter::GetPageInfo( const ImplJobSetup*, + long& rOutWidth, long& rOutHeight, + Point& rPageOffset, + Size& rPaperSize ) +{ + HDC hDC = mhDC; + + rOutWidth = GetDeviceCaps( hDC, HORZRES ); + rOutHeight = GetDeviceCaps( hDC, VERTRES ); + + rPageOffset.setX( GetDeviceCaps( hDC, PHYSICALOFFSETX ) ); + rPageOffset.setY( GetDeviceCaps( hDC, PHYSICALOFFSETY ) ); + rPaperSize.setWidth( GetDeviceCaps( hDC, PHYSICALWIDTH ) ); + rPaperSize.setHeight( GetDeviceCaps( hDC, PHYSICALHEIGHT ) ); +} + + +std::unique_ptr<SalPrinter> WinSalInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter ) +{ + WinSalPrinter* pPrinter = new WinSalPrinter; + pPrinter->mpInfoPrinter = static_cast<WinSalInfoPrinter*>(pInfoPrinter); + return std::unique_ptr<SalPrinter>(pPrinter); +} + +static BOOL CALLBACK SalPrintAbortProc( HDC hPrnDC, int /* nError */ ) +{ + SalData* pSalData = GetSalData(); + WinSalPrinter* pPrinter; + int i = 0; + bool bWhile = true; + + // Ensure we handle the mutex which will be released in WinSalInstance::DoYield + SolarMutexGuard aSolarMutexGuard; + do + { + // process messages + bWhile = Application::Reschedule( true ); + if (i > 15) + bWhile = false; + else + ++i; + + pPrinter = pSalData->mpFirstPrinter; + while ( pPrinter ) + { + if( pPrinter->mhDC == hPrnDC ) + break; + + pPrinter = pPrinter->mpNextPrinter; + } + + if ( !pPrinter || pPrinter->mbAbort ) + return FALSE; + } + while ( bWhile ); + + return TRUE; +} + +static DEVMODEW const * ImplSalSetCopies( DEVMODEW const * pDevMode, sal_uLong nCopies, bool bCollate ) +{ + if ( pDevMode && (nCopies > 1) ) + { + if ( nCopies > 32765 ) + nCopies = 32765; + sal_uLong nDevSize = pDevMode->dmSize+pDevMode->dmDriverExtra; + LPDEVMODEW pNewDevMode = static_cast<LPDEVMODEW>(std::malloc( nDevSize )); + assert(pNewDevMode); // Don't handle OOM conditions + memcpy( pNewDevMode, pDevMode, nDevSize ); + pNewDevMode->dmFields |= DM_COPIES; + pNewDevMode->dmCopies = static_cast<short>(static_cast<sal_uInt16>(nCopies)); + pNewDevMode->dmFields |= DM_COLLATE; + if ( bCollate ) + pNewDevMode->dmCollate = DMCOLLATE_TRUE; + else + pNewDevMode->dmCollate = DMCOLLATE_FALSE; + return pNewDevMode; + } + else + { + return pDevMode; + } +} + + +WinSalPrinter::WinSalPrinter() : + mpGraphics( nullptr ), + mpInfoPrinter( nullptr ), + mpNextPrinter( nullptr ), + mhDC( nullptr ), + mnError( SalPrinterError::NONE ), + mnCopies( 0 ), + mbCollate( false ), + mbAbort( false ), + mbValid( true ) +{ + SalData* pSalData = GetSalData(); + // insert printer in printerlist + mpNextPrinter = pSalData->mpFirstPrinter; + pSalData->mpFirstPrinter = this; +} + +WinSalPrinter::~WinSalPrinter() +{ + SalData* pSalData = GetSalData(); + + // release DC if there is one still around because of AbortJob + HDC hDC = mhDC; + if ( hDC ) + { + if ( mpGraphics ) + { + mpGraphics->DeInitGraphics(); + delete mpGraphics; + } + + DeleteDC( hDC ); + } + + // remove printer from printerlist + if ( this == pSalData->mpFirstPrinter ) + pSalData->mpFirstPrinter = mpNextPrinter; + else + { + WinSalPrinter* pTempPrinter = pSalData->mpFirstPrinter; + + while( pTempPrinter->mpNextPrinter != this ) + pTempPrinter = pTempPrinter->mpNextPrinter; + + pTempPrinter->mpNextPrinter = mpNextPrinter; + } + mbValid = false; +} + +void WinSalPrinter::markInvalid() +{ + mbValid = false; +} + +// need wrappers for StarTocW/A to use structured exception handling +// since SEH does not mix with standard exception handling's cleanup +static int lcl_StartDocW( HDC hDC, DOCINFOW const * pInfo, WinSalPrinter* pPrt ) +{ + int nRet = 0; + CATCH_DRIVER_EX_BEGIN; + nRet = ::StartDocW( hDC, pInfo ); + CATCH_DRIVER_EX_END( "exception in StartDocW", pPrt ); + return nRet; +} + +bool WinSalPrinter::StartJob( const OUString* pFileName, + const OUString& rJobName, + const OUString&, + sal_uInt32 nCopies, + bool bCollate, + bool /*bDirect*/, + ImplJobSetup* pSetupData ) +{ + mnError = SalPrinterError::NONE; + mbAbort = false; + mnCopies = nCopies; + mbCollate = bCollate; + + DEVMODEW const * pOrgDevModeW = nullptr; + DEVMODEW const * pDevModeW = nullptr; + HDC hDC = nullptr; + if ( pSetupData && pSetupData->GetDriverData() ) + { + pOrgDevModeW = SAL_DEVMODE_W( pSetupData ); + pDevModeW = ImplSalSetCopies( pOrgDevModeW, nCopies, bCollate ); + } + + // #95347 some buggy drivers (eg, OKI) write to those buffers in CreateDC, although declared const - so provide some space + sal_Unicode aDrvBuf[4096]; + sal_Unicode aDevBuf[4096]; + memcpy( aDrvBuf, mpInfoPrinter->maDriverName.getStr(), (mpInfoPrinter->maDriverName.getLength()+1)*sizeof(sal_Unicode)); + memcpy( aDevBuf, mpInfoPrinter->maDeviceName.getStr(), (mpInfoPrinter->maDeviceName.getLength()+1)*sizeof(sal_Unicode)); + hDC = CreateDCW( o3tl::toW(aDrvBuf), + o3tl::toW(aDevBuf), + nullptr, + pDevModeW ); + + if ( pDevModeW != pOrgDevModeW ) + std::free( const_cast<DEVMODEW *>(pDevModeW) ); + + if ( !hDC ) + { + mnError = SalPrinterError::General; + return false; + } + + // make sure mhDC is set before the printer driver may call our abortproc + mhDC = hDC; + if ( SetAbortProc( hDC, SalPrintAbortProc ) <= 0 ) + { + mnError = SalPrinterError::General; + return false; + } + + mnError = SalPrinterError::NONE; + mbAbort = false; + + // As the Telecom Balloon Fax driver tends to send messages repeatedly + // we try to process first all, and then insert a dummy message + for (int i = 0; Application::Reschedule( true ) && i <= 15; ++i); + bool const ret = PostMessageW(GetSalData()->mpInstance->mhComWnd, SAL_MSG_DUMMY, 0, 0); + SAL_WARN_IF(!ret, "vcl", "ERROR: PostMessage() failed!"); + + // bring up a file chooser if printing to file port but no file name given + OUString aOutFileName; + if( mpInfoPrinter->maPortName.equalsIgnoreAsciiCase( "FILE:" ) && !(pFileName && !pFileName->isEmpty()) ) + { + + uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + uno::Reference< XFilePicker3 > xFilePicker = FilePicker::createWithMode(xContext, TemplateDescription::FILESAVE_SIMPLE); + + if( xFilePicker->execute() == ExecutableDialogResults::OK ) + { + Sequence< OUString > aPathSeq( xFilePicker->getSelectedFiles() ); + INetURLObject aObj( aPathSeq[0] ); + aOutFileName = aObj.PathToFileName(); + } + else + { + mnError = SalPrinterError::Abort; + return false; + } + } + + DOCINFOW aInfo = {}; + aInfo.cbSize = sizeof( aInfo ); + aInfo.lpszDocName = o3tl::toW(rJobName.getStr()); + if ( pFileName || aOutFileName.getLength() ) + { + if ( (pFileName && !pFileName->isEmpty()) || aOutFileName.getLength() ) + { + aInfo.lpszOutput = o3tl::toW((pFileName && !pFileName->isEmpty()) ? pFileName->getStr() : aOutFileName.getStr()); + } + else + aInfo.lpszOutput = L"FILE:"; + } + else + aInfo.lpszOutput = nullptr; + + // start Job, in the main thread + int nRet = vcl::solarthread::syncExecute([hDC, this, &aInfo]() -> int { return lcl_StartDocW(hDC, &aInfo, this); }); + + if ( nRet <= 0 ) + { + long nError = GetLastError(); + if ( (nRet == SP_USERABORT) || (nRet == SP_APPABORT) || (nError == ERROR_PRINT_CANCELLED) || (nError == ERROR_CANCELLED) ) + mnError = SalPrinterError::Abort; + else + mnError = SalPrinterError::General; + return false; + } + + return true; +} + +void WinSalPrinter::DoEndDoc(HDC hDC) +{ + CATCH_DRIVER_EX_BEGIN; + if( ::EndDoc( hDC ) <= 0 ) + GetLastError(); + CATCH_DRIVER_EX_END( "exception in EndDoc", this ); +} + +bool WinSalPrinter::EndJob() +{ + HDC hDC = mhDC; + if ( isValid() && hDC ) + { + if ( mpGraphics ) + { + mpGraphics->DeInitGraphics(); + delete mpGraphics; + mpGraphics = nullptr; + } + + // #i54419# Windows fax printer brings up a dialog in EndDoc + // which text previously copied in soffice process can be + // pasted to -> deadlock due to mutex not released. + // it should be safe to release the yield mutex over the EndDoc + // call, however the real solution is supposed to be the threading + // framework yet to come. + { + SolarMutexReleaser aReleaser; + DoEndDoc( hDC ); + } + DeleteDC( hDC ); + mhDC = nullptr; + } + + return true; +} + +SalGraphics* WinSalPrinter::StartPage( ImplJobSetup* pSetupData, bool bNewJobData ) +{ + if( ! isValid() || mhDC == nullptr ) + return nullptr; + + HDC hDC = mhDC; + if ( pSetupData && pSetupData->GetDriverData() && bNewJobData ) + { + DEVMODEW const * pOrgDevModeW; + DEVMODEW const * pDevModeW; + pOrgDevModeW = SAL_DEVMODE_W( pSetupData ); + pDevModeW = ImplSalSetCopies( pOrgDevModeW, mnCopies, mbCollate ); + ResetDCW( hDC, pDevModeW ); + if ( pDevModeW != pOrgDevModeW ) + std::free( const_cast<DEVMODEW *>(pDevModeW) ); + } + volatile int nRet = 0; + CATCH_DRIVER_EX_BEGIN; + nRet = ::StartPage( hDC ); + CATCH_DRIVER_EX_END( "exception in StartPage", this ); + + if ( nRet <= 0 ) + { + GetLastError(); + mnError = SalPrinterError::General; + return nullptr; + } + + // Hack to work around old PostScript printer drivers optimizing away empty pages + // TODO: move into ImplCreateSalPrnGraphics()? + HPEN hTempPen = SelectPen( hDC, GetStockPen( NULL_PEN ) ); + HBRUSH hTempBrush = SelectBrush( hDC, GetStockBrush( NULL_BRUSH ) ); + Rectangle( hDC, -8000, -8000, -7999, -7999 ); + SelectPen( hDC, hTempPen ); + SelectBrush( hDC, hTempBrush ); + + mpGraphics = ImplCreateSalPrnGraphics( hDC ); + return mpGraphics; +} + +void WinSalPrinter::EndPage() +{ + HDC hDC = mhDC; + if ( hDC && mpGraphics ) + { + mpGraphics->DeInitGraphics(); + delete mpGraphics; + mpGraphics = nullptr; + } + + if( ! isValid() ) + return; + + volatile int nRet = 0; + CATCH_DRIVER_EX_BEGIN; + nRet = ::EndPage( hDC ); + CATCH_DRIVER_EX_END( "exception in EndPage", this ); + + if ( nRet <= 0 ) + { + GetLastError(); + mnError = SalPrinterError::General; + } +} + +SalPrinterError WinSalPrinter::GetErrorCode() +{ + return mnError; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/salvd.cxx b/vcl/win/gdi/salvd.cxx new file mode 100644 index 000000000..e1cacb8b0 --- /dev/null +++ b/vcl/win/gdi/salvd.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 <svsys.h> + +#include <comphelper/windowserrorstring.hxx> + +#include <vcl/sysdata.hxx> + +#include <win/wincomp.hxx> +#include <win/saldata.hxx> +#include <win/salinst.h> +#include <win/salgdi.h> +#include <win/salvd.h> +#include <opengl/win/gdiimpl.hxx> +#include <sal/log.hxx> +#include <o3tl/temporary.hxx> + +HBITMAP WinSalVirtualDevice::ImplCreateVirDevBitmap(HDC hDC, long nDX, long nDY, sal_uInt16 nBitCount, void **ppData) +{ + HBITMAP hBitmap; + + if ( nBitCount == 1 ) + { + hBitmap = CreateBitmap( static_cast<int>(nDX), static_cast<int>(nDY), 1, 1, nullptr ); + SAL_WARN_IF( !hBitmap, "vcl", "CreateBitmap failed: " << WindowsErrorString( GetLastError() ) ); + ppData = nullptr; + } + else + { + if (nBitCount == 0) + nBitCount = static_cast<WORD>(GetDeviceCaps(hDC, BITSPIXEL)); + + // #146839# Don't use CreateCompatibleBitmap() - there seem to + // be built-in limits for those HBITMAPs, at least this fails + // rather often on large displays/multi-monitor setups. + BITMAPINFO aBitmapInfo; + aBitmapInfo.bmiHeader.biSize = sizeof( BITMAPINFOHEADER ); + aBitmapInfo.bmiHeader.biWidth = nDX; + aBitmapInfo.bmiHeader.biHeight = nDY; + aBitmapInfo.bmiHeader.biPlanes = 1; + aBitmapInfo.bmiHeader.biBitCount = nBitCount; + aBitmapInfo.bmiHeader.biCompression = BI_RGB; + aBitmapInfo.bmiHeader.biSizeImage = 0; + aBitmapInfo.bmiHeader.biXPelsPerMeter = 0; + aBitmapInfo.bmiHeader.biYPelsPerMeter = 0; + aBitmapInfo.bmiHeader.biClrUsed = 0; + aBitmapInfo.bmiHeader.biClrImportant = 0; + + hBitmap = CreateDIBSection( hDC, &aBitmapInfo, + DIB_RGB_COLORS, ppData, nullptr, + 0 ); + SAL_WARN_IF( !hBitmap, "vcl", "CreateDIBSection failed: " << WindowsErrorString( GetLastError() ) ); + } + + return hBitmap; +} + +std::unique_ptr<SalVirtualDevice> WinSalInstance::CreateVirtualDevice( SalGraphics* pSGraphics, + long &nDX, long &nDY, + DeviceFormat eFormat, + const SystemGraphicsData* pData ) +{ + WinSalGraphics* pGraphics = static_cast<WinSalGraphics*>(pSGraphics); + HDC hDC = nullptr; + + if( pData ) + { + hDC = (pData->hDC) ? pData->hDC : GetDC(pData->hWnd); + if (hDC) + { + nDX = GetDeviceCaps( hDC, HORZRES ); + nDY = GetDeviceCaps( hDC, VERTRES ); + } + else + { + nDX = 0; + nDY = 0; + } + } + else + { + hDC = CreateCompatibleDC( pGraphics->getHDC() ); + SAL_WARN_IF( !hDC, "vcl", "CreateCompatibleDC failed: " << WindowsErrorString( GetLastError() ) ); + } + + if (!hDC) + return nullptr; + + sal_uInt16 nBitCount = (eFormat == DeviceFormat::BITMASK) ? 1 : 0; + + HBITMAP hBmp = nullptr; + if (!pData) + { + // #124826# continue even if hBmp could not be created + // if we would return a failure in this case, the process + // would terminate which is not required + hBmp = WinSalVirtualDevice::ImplCreateVirDevBitmap(pGraphics->getHDC(), + nDX, nDY, nBitCount, + &o3tl::temporary<void*>(nullptr)); + } + + const bool bForeignDC = pData != nullptr && pData->hDC != nullptr; + const SalData* pSalData = GetSalData(); + + WinSalVirtualDevice* pVDev = new WinSalVirtualDevice(hDC, hBmp, nBitCount, + bForeignDC, nDX, nDY); + + WinSalGraphics* pVirGraphics = new WinSalGraphics(WinSalGraphics::VIRTUAL_DEVICE, + pGraphics->isScreen(), nullptr, pVDev); + + // by default no! mirroring for VirtualDevices, can be enabled with EnableRTL() + pVirGraphics->SetLayout( SalLayoutFlags::NONE ); + pVirGraphics->setHDC(hDC); + + if ( pSalData->mhDitherPal && pVirGraphics->isScreen() ) + { + pVirGraphics->setDefPal(SelectPalette( hDC, pSalData->mhDitherPal, TRUE )); + RealizePalette( hDC ); + } + + pVirGraphics->InitGraphics(); + pVDev->setGraphics(pVirGraphics); + + return std::unique_ptr<SalVirtualDevice>(pVDev); +} + +WinSalVirtualDevice::WinSalVirtualDevice(HDC hDC, HBITMAP hBMP, sal_uInt16 nBitCount, bool bForeignDC, long nWidth, long nHeight) + : mhLocalDC(hDC), // HDC or 0 for Cache Device + mhBmp(hBMP), // Memory Bitmap + mnBitCount(nBitCount), // BitCount (0 or 1) + mbGraphics(false), // is Graphics used + mbForeignDC(bForeignDC), // uses a foreign DC instead of a bitmap + mnWidth(nWidth), + mnHeight(nHeight) +{ + // Default Bitmap + if (hBMP) + mhDefBmp = SelectBitmap(hDC, hBMP); + else + mhDefBmp = nullptr; + + // insert VirDev into list of virtual devices + SalData* pSalData = GetSalData(); + mpNext = pSalData->mpFirstVD; + pSalData->mpFirstVD = this; +} + +WinSalVirtualDevice::~WinSalVirtualDevice() +{ + // remove VirDev from list of virtual devices + SalData* pSalData = GetSalData(); + WinSalVirtualDevice** ppVirDev = &pSalData->mpFirstVD; + for(; (*ppVirDev != this) && *ppVirDev; ppVirDev = &(*ppVirDev)->mpNext ); + if( *ppVirDev ) + *ppVirDev = mpNext; + + // destroy saved DC + if( mpGraphics->getDefPal() ) + SelectPalette( mpGraphics->getHDC(), mpGraphics->getDefPal(), TRUE ); + mpGraphics->DeInitGraphics(); + if( mhDefBmp ) + SelectBitmap( mpGraphics->getHDC(), mhDefBmp ); + if( !mbForeignDC ) + DeleteDC( mpGraphics->getHDC() ); +} + +SalGraphics* WinSalVirtualDevice::AcquireGraphics() +{ + if ( mbGraphics ) + return nullptr; + + if ( mpGraphics ) + mbGraphics = true; + + return mpGraphics.get(); +} + +void WinSalVirtualDevice::ReleaseGraphics( SalGraphics* ) +{ + mbGraphics = false; +} + +bool WinSalVirtualDevice::SetSize( long nDX, long nDY ) +{ + if( mbForeignDC || !mhBmp ) + return true; // ??? + + HBITMAP hNewBmp = ImplCreateVirDevBitmap(getHDC(), nDX, nDY, mnBitCount, + &o3tl::temporary<void*>(nullptr)); + if (!hNewBmp) + { + mnWidth = 0; + mnHeight = 0; + return false; + } + + mnWidth = nDX; + mnHeight = nDY; + + SelectBitmap(getHDC(), hNewBmp); + mhBmp.reset(hNewBmp); + + if (mpGraphics) + mpGraphics->GetImpl()->Init(); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/win/gdi/winlayout.cxx b/vcl/win/gdi/winlayout.cxx new file mode 100644 index 000000000..056540921 --- /dev/null +++ b/vcl/win/gdi/winlayout.cxx @@ -0,0 +1,635 @@ + +/* -*- 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 <config_features.h> + +#include <memory> +#include <osl/module.h> +#include <osl/file.h> +#include <sal/log.hxx> + +#include <comphelper/windowserrorstring.hxx> +#include <comphelper/scopeguard.hxx> + +#include <opengl/win/gdiimpl.hxx> +#include <opengl/win/winlayout.hxx> + +#include <vcl/opengl/OpenGLHelper.hxx> +#include <win/salgdi.h> +#include <win/saldata.hxx> +#include <win/wingdiimpl.hxx> +#include <outdev.h> + +#include <win/DWriteTextRenderer.hxx> +#include <win/scoped_gdi.hxx> + +#include <sft.hxx> +#include <sallayout.hxx> + +#include <cstdio> +#include <cstdlib> + +#include <rtl/character.hxx> + +#include <boost/functional/hash.hpp> +#include <algorithm> + +#include <shlwapi.h> +#include <winver.h> + +GlobalWinGlyphCache * GlobalWinGlyphCache::get() +{ + SalData *data = GetSalData(); + if (!data->m_pGlobalWinGlyphCache) + { + if (OpenGLHelper::isVCLOpenGLEnabled()) + data->m_pGlobalWinGlyphCache.reset(new OpenGLGlobalWinGlyphCache); + } + return data->m_pGlobalWinGlyphCache.get(); +} + +bool WinFontInstance::CacheGlyphToAtlas(HDC hDC, HFONT hFont, int nGlyphIndex, + SalGraphics& rGraphics, const GenericSalLayout& rLayout) +{ + WinGlyphDrawElement aElement; + + ScopedHDC aHDC(CreateCompatibleDC(hDC)); + + if (!aHDC) + { + SAL_WARN("vcl.gdi", "CreateCompatibleDC failed: " << WindowsErrorString(GetLastError())); + return false; + } + + const HFONT hOrigFont = static_cast<HFONT>(SelectObject(aHDC.get(), hFont)); + if (hOrigFont == nullptr) + { + SAL_WARN("vcl.gdi", "SelectObject failed: " << WindowsErrorString(GetLastError())); + return false; + } + const ::comphelper::ScopeGuard aHFONTrestoreScopeGuard( + [&aHDC,hOrigFont]() { SelectFont(aHDC.get(), hOrigFont); }); + + // For now we assume DWrite is present and we won't bother with fallback paths. + D2DWriteTextOutRenderer * pTxt = dynamic_cast<D2DWriteTextOutRenderer *>(&TextOutRenderer::get(true)); + if (!pTxt) + return false; + + pTxt->changeTextAntiAliasMode(D2DTextAntiAliasMode::AntiAliased); + + if (!pTxt->BindFont(aHDC.get())) + { + SAL_WARN("vcl.gdi", "Binding of font failed. The font might not be supported by DirectWrite."); + return false; + } + const ::comphelper::ScopeGuard aFontReleaseScopeGuard([&pTxt]() { pTxt->ReleaseFont(); }); + + std::vector<WORD> aGlyphIndices(1); + aGlyphIndices[0] = nGlyphIndex; + // Fetch the ink boxes and calculate the size of the atlas. + tools::Rectangle bounds(0, 0, 0, 0); + auto aInkBoxes = pTxt->GetGlyphInkBoxes(aGlyphIndices.data(), aGlyphIndices.data() + 1); + if (aInkBoxes.empty()) + return false; + + for (auto &box : aInkBoxes) + bounds.Union(box + Point(bounds.Right(), 0)); + + // bounds.Top() is the offset from the baseline at (0,0) to the top of the + // inkbox. + aElement.mnBaselineOffset = -bounds.Top(); + aElement.mnHeight = bounds.getHeight(); + aElement.mbVertical = false; + + // Try hard to avoid overlap as we want to be able to use + // individual rectangles for each glyph. The ABC widths don't + // take anti-aliasing into consideration. Let's hope that leaving + // "extra" space between glyphs will help. + std::vector<float> aGlyphAdv(1); // offsets between glyphs + std::vector<DWRITE_GLYPH_OFFSET> aGlyphOffset(1, {0.0f, 0.0f}); + std::vector<int> aEnds(1); // end of each glyph box + float fHScale = getHScale(); + float totWidth = 0; + { + int overhang = aInkBoxes[0].Left(); + int blackWidth = aInkBoxes[0].getWidth() * fHScale; // width of non-AA pixels + aElement.maLeftOverhangs = overhang; + + aGlyphAdv[0] = blackWidth + aElement.getExtraSpace(); + aGlyphOffset[0].advanceOffset = -overhang; + + totWidth += aGlyphAdv[0]; + aEnds[0] = totWidth; + } + // Leave extra space also at top and bottom + int nBitmapWidth = totWidth; + int nBitmapHeight = bounds.getHeight() + aElement.getExtraSpace(); + + UINT nPos = 0; + + aElement.maLocation.SetLeft(nPos); + aElement.maLocation.SetRight(aEnds[0]); + aElement.maLocation.SetTop(0); + aElement.maLocation.SetBottom(bounds.getHeight() + aElement.getExtraSpace()); + nPos = aEnds[0]; + + std::unique_ptr<CompatibleDC> aDC(CompatibleDC::create(rGraphics, 0, 0, nBitmapWidth, nBitmapHeight)); + + SetTextColor(aDC->getCompatibleHDC(), RGB(0, 0, 0)); + SetBkColor(aDC->getCompatibleHDC(), RGB(255, 255, 255)); + + aDC->fill(RGB(0xff, 0xff, 0xff)); + + pTxt->BindDC(aDC->getCompatibleHDC(), tools::Rectangle(0, 0, nBitmapWidth, nBitmapHeight)); + auto pRT = pTxt->GetRenderTarget(); + + ID2D1SolidColorBrush* pBrush = nullptr; + if (!SUCCEEDED(pRT->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &pBrush))) + return false; + + D2D1_POINT_2F baseline = { + static_cast<FLOAT>(aElement.getExtraOffset()), + static_cast<FLOAT>(aElement.getExtraOffset() + aElement.mnBaselineOffset) + }; + + DWRITE_GLYPH_RUN glyphs = { + pTxt->GetFontFace(), + pTxt->GetEmHeight(), + 1, + aGlyphIndices.data(), + aGlyphAdv.data(), + aGlyphOffset.data(), + false, + 0 + }; + + WinFontTransformGuard aTransformGuard(pRT, fHScale, rLayout, baseline); + pRT->BeginDraw(); + pRT->DrawGlyphRun(baseline, &glyphs, pBrush); + HRESULT hResult = pRT->EndDraw(); + + pBrush->Release(); + + switch (hResult) + { + case S_OK: + break; + case D2DERR_RECREATE_TARGET: + pTxt->CreateRenderTarget(); + break; + default: + SAL_WARN("vcl.gdi", "DrawGlyphRun-EndDraw failed: " << WindowsErrorString(GetLastError())); + return false; + } + + if (!GlobalWinGlyphCache::get()->AllocateTexture(aElement, aDC.get())) + return false; + + maWinGlyphCache.PutDrawElementInCache(std::move(aElement), nGlyphIndex); + + return true; +} + +TextOutRenderer & TextOutRenderer::get(bool bUseDWrite) +{ + SalData *const pSalData = GetSalData(); + + if (!pSalData) + { // don't call this after DeInitVCL() + fprintf(stderr, "TextOutRenderer fatal error: no SalData"); + abort(); + } + + if (bUseDWrite) + { + if (!pSalData->m_pD2DWriteTextOutRenderer) + { + pSalData->m_pD2DWriteTextOutRenderer.reset(new D2DWriteTextOutRenderer()); + } + return *pSalData->m_pD2DWriteTextOutRenderer; + } + if (!pSalData->m_pExTextOutRenderer) + { + pSalData->m_pExTextOutRenderer.reset(new ExTextOutRenderer); + } + return *pSalData->m_pExTextOutRenderer; +} + + +bool ExTextOutRenderer::operator ()(GenericSalLayout const &rLayout, + SalGraphics & /*rGraphics*/, + HDC hDC) +{ + HFONT hFont = static_cast<HFONT>(GetCurrentObject( hDC, OBJ_FONT )); + ScopedHFONT hAltFont; + bool bUseAltFont = false; + bool bShift = false; + if (rLayout.GetFont().GetFontSelectPattern().mbVertical) + { + LOGFONTW aLogFont; + GetObjectW(hFont, sizeof(aLogFont), &aLogFont); + if (aLogFont.lfFaceName[0] == '@') + { + memmove(&aLogFont.lfFaceName[0], &aLogFont.lfFaceName[1], + sizeof(aLogFont.lfFaceName)-sizeof(aLogFont.lfFaceName[0])); + hAltFont.reset(CreateFontIndirectW(&aLogFont)); + } + else + { + bShift = true; + aLogFont.lfEscapement += 2700; + aLogFont.lfOrientation = aLogFont.lfEscapement; + hAltFont.reset(CreateFontIndirectW(&aLogFont)); + } + } + + UINT nTextAlign = GetTextAlign ( hDC ); + int nStart = 0; + Point aPos(0, 0); + const GlyphItem* pGlyph; + while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart)) + { + WORD glyphWStr[] = { pGlyph->glyphId() }; + if (hAltFont && pGlyph->IsVertical() == bUseAltFont) + { + bUseAltFont = !bUseAltFont; + SelectFont(hDC, bUseAltFont ? hAltFont.get() : hFont); + } + if (bShift && pGlyph->IsVertical()) + SetTextAlign(hDC, TA_TOP|TA_LEFT); + + ExtTextOutW(hDC, aPos.X(), aPos.Y(), ETO_GLYPH_INDEX, nullptr, LPCWSTR(&glyphWStr), 1, nullptr); + + if (bShift && pGlyph->IsVertical()) + SetTextAlign(hDC, nTextAlign); + } + if (hAltFont) + { + if (bUseAltFont) + SelectFont(hDC, hFont); + } + + return true; +} + +std::unique_ptr<GenericSalLayout> WinSalGraphics::GetTextLayout(int nFallbackLevel) +{ + assert(mpWinFontEntry[nFallbackLevel]); + if (!mpWinFontEntry[nFallbackLevel]) + return nullptr; + + assert(mpWinFontEntry[nFallbackLevel]->GetFontFace()); + + mpWinFontEntry[nFallbackLevel]->SetGraphics(this); + return std::make_unique<GenericSalLayout>(*mpWinFontEntry[nFallbackLevel]); +} + +WinFontInstance::WinFontInstance(const WinFontFace& rPFF, const FontSelectPattern& rFSP) + : LogicalFontInstance(rPFF, rFSP) + , m_pGraphics(nullptr) + , m_hFont(nullptr) + , m_fScale(1.0f) +{ +} + +WinFontInstance::~WinFontInstance() +{ + if (m_hFont) + ::DeleteFont(m_hFont); +} + +bool WinFontInstance::hasHScale() const +{ + const FontSelectPattern &rPattern = GetFontSelectPattern(); + int nHeight(rPattern.mnHeight); + int nWidth(rPattern.mnWidth ? rPattern.mnWidth * GetAverageWidthFactor() : nHeight); + return nWidth != nHeight; +} + +float WinFontInstance::getHScale() const +{ + const FontSelectPattern& rPattern = GetFontSelectPattern(); + int nHeight(rPattern.mnHeight); + if (!nHeight) + return 1.0; + float nWidth(rPattern.mnWidth ? rPattern.mnWidth * GetAverageWidthFactor() : nHeight); + return nWidth / nHeight; +} + +namespace { + +struct BlobReference +{ + hb_blob_t* mpBlob; + BlobReference(hb_blob_t* pBlob) : mpBlob(pBlob) + { + hb_blob_reference(mpBlob); + } + BlobReference(BlobReference const & other) + : mpBlob(other.mpBlob) + { + hb_blob_reference(mpBlob); + } + ~BlobReference() { hb_blob_destroy(mpBlob); } +}; + +} + +using BlobCacheKey = std::pair<rtl::Reference<PhysicalFontFace>, hb_tag_t>; + +namespace { + +struct BlobCacheKeyHash +{ + std::size_t operator()(BlobCacheKey const& rKey) const + { + std::size_t seed = 0; + boost::hash_combine(seed, rKey.first.get()); + boost::hash_combine(seed, rKey.second); + return seed; + } +}; + +} + +static hb_blob_t* getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData) +{ + static o3tl::lru_map<BlobCacheKey, BlobReference, BlobCacheKeyHash> gCache(50); + + WinFontInstance* pFont = static_cast<WinFontInstance*>(pUserData); + HDC hDC = pFont->GetGraphics()->getHDC(); + HFONT hFont = pFont->GetHFONT(); + assert(hDC); + assert(hFont); + + BlobCacheKey cacheKey { rtl::Reference<PhysicalFontFace>(pFont->GetFontFace()), nTableTag }; + auto it = gCache.find(cacheKey); + if (it != gCache.end()) + { + hb_blob_reference(it->second.mpBlob); + return it->second.mpBlob; + } + + sal_uLong nLength = 0; + unsigned char* pBuffer = nullptr; + + HGDIOBJ hOrigFont = SelectObject(hDC, hFont); + nLength = ::GetFontData(hDC, OSL_NETDWORD(nTableTag), 0, nullptr, 0); + if (nLength > 0 && nLength != GDI_ERROR) + { + pBuffer = new unsigned char[nLength]; + ::GetFontData(hDC, OSL_NETDWORD(nTableTag), 0, pBuffer, nLength); + } + SelectObject(hDC, hOrigFont); + + if (!pBuffer) + return nullptr; + + hb_blob_t* pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY, + pBuffer, [](void* data){ delete[] static_cast<unsigned char*>(data); }); + if (!pBlob) + return pBlob; + gCache.insert({cacheKey, BlobReference(pBlob)}); + return pBlob; +} + +hb_font_t* WinFontInstance::ImplInitHbFont() +{ + assert(m_pGraphics); + hb_font_t* pHbFont = InitHbFont(hb_face_create_for_tables(getFontTable, this, nullptr)); + + // Calculate the AverageWidthFactor, see LogicalFontInstance::GetScale(). + if (GetFontSelectPattern().mnWidth) + { + double nUPEM = hb_face_get_upem(hb_font_get_face(pHbFont)); + + LOGFONTW aLogFont; + GetObjectW(m_hFont, sizeof(LOGFONTW), &aLogFont); + + // Set the height (font size) to EM to minimize rounding errors. + aLogFont.lfHeight = -nUPEM; + // Set width to the default to get the original value in the metrics. + aLogFont.lfWidth = 0; + + TEXTMETRICW aFontMetric; + { + // Get the font metrics. + HDC hDC = m_pGraphics->getHDC(); + ScopedSelectedHFONT hFont(hDC, CreateFontIndirectW(&aLogFont)); + GetTextMetricsW(hDC, &aFontMetric); + } + + SetAverageWidthFactor(nUPEM / aFontMetric.tmAveCharWidth); + } + + return pHbFont; +} + +void WinFontInstance::SetGraphics(WinSalGraphics *pGraphics) +{ + m_pGraphics = pGraphics; + if (m_hFont) + return; + HFONT hOrigFont; + m_hFont = m_pGraphics->ImplDoSetFont(GetFontSelectPattern(), GetFontFace(), hOrigFont); + SelectObject(m_pGraphics->getHDC(), hOrigFont); +} + +bool WinSalGraphics::CacheGlyphs(const GenericSalLayout& rLayout) +{ + static bool bDoGlyphCaching = (std::getenv("SAL_DISABLE_GLYPH_CACHING") == nullptr); + if (!bDoGlyphCaching) + return false; + + if (rLayout.GetOrientation()) + // Our caching is incomplete, skip it for non-horizontal text. + return false; + + HDC hDC = getHDC(); + WinFontInstance& rFont = *static_cast<WinFontInstance*>(&rLayout.GetFont()); + HFONT hFONT = rFont.GetHFONT(); + + int nStart = 0; + Point aPos(0, 0); + const GlyphItem* pGlyph; + while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart)) + { + if (!rFont.GetWinGlyphCache().IsGlyphCached(pGlyph->glyphId())) + { + if (!rFont.CacheGlyphToAtlas(hDC, hFONT, pGlyph->glyphId(), *this, rLayout)) + return false; + } + } + + return true; +} + +bool WinSalGraphics::DrawCachedGlyphs(const GenericSalLayout& rLayout) +{ + HDC hDC = getHDC(); + + tools::Rectangle aRect; + rLayout.GetBoundRect(aRect); + + COLORREF color = GetTextColor(hDC); + Color salColor(GetRValue(color), GetGValue(color), GetBValue(color)); + + WinSalGraphicsImplBase *pImpl = dynamic_cast<WinSalGraphicsImplBase*>(mpImpl.get()); + if (!pImpl->UseTextDraw()) + return false; + + WinFontInstance& rFont = *static_cast<WinFontInstance*>(&rLayout.GetFont()); + + int nStart = 0; + Point aPos(0, 0); + const GlyphItem* pGlyph; + while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart)) + { + WinGlyphDrawElement& rElement(rFont.GetWinGlyphCache().GetDrawElement(pGlyph->glyphId())); + const CompatibleDC::Texture* texture = rElement.maTexture.get(); + + if (!texture || !texture->isValid()) + return false; + + SalTwoRect a2Rects(0, 0, + texture->GetWidth(), texture->GetHeight(), + aPos.X() - rElement.getExtraOffset() + rElement.maLeftOverhangs, + aPos.Y() - rElement.mnBaselineOffset - rElement.getExtraOffset(), + texture->GetWidth(), texture->GetHeight()); + + pImpl->DeferredTextDraw(texture, salColor, a2Rects); + } + + return true; +} + +static void PruneGlyphCache() +{ + GlobalWinGlyphCache::get()->Prune(); +} + +void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout, HDC hDC, bool bUseDWrite) +{ + TextOutRenderer &render = TextOutRenderer::get(bUseDWrite); + render(rLayout, *this, hDC); +} + +void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout) +{ + WinSalGraphicsImplBase* pImpl = dynamic_cast<WinSalGraphicsImplBase*>(mpImpl.get()); + if( !mbPrinter && pImpl->DrawTextLayout(rLayout)) + return; // handled by pImpl + + HDC hDC = getHDC(); + const WinFontInstance* pWinFont = static_cast<const WinFontInstance*>(&rLayout.GetFont()); + const HFONT hLayoutFont = pWinFont->GetHFONT(); + bool bUseClassic = !pImpl->UseTextDraw() || mbPrinter; + + // Our DirectWrite renderer is incomplete, skip it for vertical text where glyphs are not + // rotated. + bool bForceGDI = rLayout.GetFont().GetFontSelectPattern().mbVertical; + + if (bUseClassic) + { + // no OpenGL, just classic rendering + const HFONT hOrigFont = ::SelectFont(hDC, hLayoutFont); + DrawTextLayout(rLayout, hDC, false); + ::SelectFont(hDC, hOrigFont); + } + // if we can't draw the cached OpenGL glyphs, try to draw a full OpenGL layout + else if (!bForceGDI && CacheGlyphs(rLayout) && DrawCachedGlyphs(rLayout)) + { + PruneGlyphCache(); + } + else + { + PruneGlyphCache(); // prune the cache from the failed calls above + + // We have to render the text to a hidden texture, and draw it. + // + // Note that Windows GDI does not really support the alpha correctly + // when drawing - ie. it draws nothing to the alpha channel when + // rendering the text, even the antialiasing is done as 'real' pixels, + // not alpha... + // + // Luckily, this does not really limit us: + // + // To blend properly, we draw the texture, but then use it as an alpha + // channel for solid color (that will define the text color). This + // destroys the subpixel antialiasing - turns it into 'classic' + // antialiasing - but that is the best we can do, because the subpixel + // antialiasing needs to know what is in the background: When the + // background is white, or white-ish, it does the subpixel, but when + // there is a color, it just darkens the color (and does this even + // when part of the character is on a colored background, and part on + // white). It has to work this way, the results would look strange + // otherwise. + // + // For the GL rendering to work even with the subpixel antialiasing, + // we would need to get the current texture from the screen, let GDI + // draw the text to it (so that it can decide well where to use the + // subpixel and where not), and draw the result - but in that case we + // don't need alpha anyway. + // + // TODO: check the performance of this 2nd approach at some stage and + // switch to that if it performs well. + + tools::Rectangle aRect; + rLayout.GetBoundRect(aRect); + if( aRect.IsEmpty()) + return; + + pImpl->PreDrawText(); + + std::unique_ptr<CompatibleDC> aDC(CompatibleDC::create(*this, aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight())); + + // we are making changes to the DC, make sure we got a new one + assert(aDC->getCompatibleHDC() != hDC); + + RECT aWinRect = { aRect.Left(), aRect.Top(), aRect.Left() + aRect.GetWidth(), aRect.Top() + aRect.GetHeight() }; + ::FillRect(aDC->getCompatibleHDC(), &aWinRect, static_cast<HBRUSH>(::GetStockObject(WHITE_BRUSH))); + + // setup the hidden DC with black color and white background, we will + // use the result of the text drawing later as a mask only + const HFONT hOrigFont = ::SelectFont(aDC->getCompatibleHDC(), hLayoutFont); + + ::SetTextColor(aDC->getCompatibleHDC(), RGB(0, 0, 0)); + ::SetBkColor(aDC->getCompatibleHDC(), RGB(255, 255, 255)); + + UINT nTextAlign = ::GetTextAlign(hDC); + ::SetTextAlign(aDC->getCompatibleHDC(), nTextAlign); + + COLORREF color = ::GetTextColor(hDC); + Color salColor(GetRValue(color), GetGValue(color), GetBValue(color)); + + // the actual drawing + DrawTextLayout(rLayout, aDC->getCompatibleHDC(), !bForceGDI); + + std::unique_ptr<CompatibleDC::Texture> xTexture(aDC->getAsMaskTexture()); + if (xTexture) + pImpl->DrawTextMask(xTexture.get(), salColor, aDC->getTwoRect()); + + ::SelectFont(aDC->getCompatibleHDC(), hOrigFont); + + pImpl->PostDrawText(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |