diff options
Diffstat (limited to 'vcl/headless')
-rw-r--r-- | vcl/headless/BitmapHelper.cxx | 226 | ||||
-rw-r--r-- | vcl/headless/CairoCommon.cxx | 2208 | ||||
-rw-r--r-- | vcl/headless/SvpGraphicsBackend.cxx | 287 | ||||
-rw-r--r-- | vcl/headless/headlessinst.cxx | 55 | ||||
-rw-r--r-- | vcl/headless/svpbmp.cxx | 272 | ||||
-rw-r--r-- | vcl/headless/svpdummies.cxx | 58 | ||||
-rw-r--r-- | vcl/headless/svpframe.cxx | 502 | ||||
-rw-r--r-- | vcl/headless/svpgdi.cxx | 98 | ||||
-rw-r--r-- | vcl/headless/svpinst.cxx | 553 | ||||
-rw-r--r-- | vcl/headless/svpprn.cxx | 268 | ||||
-rw-r--r-- | vcl/headless/svptext.cxx | 80 | ||||
-rw-r--r-- | vcl/headless/svpvd.cxx | 157 |
12 files changed, 4764 insertions, 0 deletions
diff --git a/vcl/headless/BitmapHelper.cxx b/vcl/headless/BitmapHelper.cxx new file mode 100644 index 0000000000..2cdf502fc9 --- /dev/null +++ b/vcl/headless/BitmapHelper.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 <headless/BitmapHelper.hxx> +#include <svdata.hxx> +#include <utility> + +BitmapHelper::BitmapHelper(const SalBitmap& rSourceBitmap, const bool bForceARGB32) +#ifdef HAVE_CAIRO_FORMAT_RGB24_888 + : m_bForceARGB32(bForceARGB32) +#endif +{ + const SvpSalBitmap& rSrcBmp = static_cast<const SvpSalBitmap&>(rSourceBitmap); +#ifdef HAVE_CAIRO_FORMAT_RGB24_888 + if ((rSrcBmp.GetBitCount() != 32 && rSrcBmp.GetBitCount() != 24) || bForceARGB32) +#else + (void)bForceARGB32; + if (rSrcBmp.GetBitCount() != 32) +#endif + { + //big stupid copy here + const BitmapBuffer* pSrc = rSrcBmp.GetBuffer(); + const SalTwoRect aTwoRect + = { 0, 0, pSrc->mnWidth, pSrc->mnHeight, 0, 0, pSrc->mnWidth, pSrc->mnHeight }; + std::optional<BitmapBuffer> pTmp + = (pSrc->mnFormat == SVP_24BIT_FORMAT + ? FastConvert24BitRgbTo32BitCairo(pSrc) + : StretchAndConvert(*pSrc, aTwoRect, SVP_CAIRO_FORMAT)); + aTmpBmp.Create(std::move(pTmp)); + + assert(aTmpBmp.GetBitCount() == 32); + implSetSurface(CairoCommon::createCairoSurface(aTmpBmp.GetBuffer())); + } + else + { + implSetSurface(CairoCommon::createCairoSurface(rSrcBmp.GetBuffer())); + } +} + +void BitmapHelper::mark_dirty() { cairo_surface_mark_dirty(implGetSurface()); } + +unsigned char* BitmapHelper::getBits(sal_Int32& rStride) +{ + cairo_surface_flush(implGetSurface()); + + unsigned char* mask_data = cairo_image_surface_get_data(implGetSurface()); + + const cairo_format_t nFormat = cairo_image_surface_get_format(implGetSurface()); +#ifdef HAVE_CAIRO_FORMAT_RGB24_888 + if (!m_bForceARGB32) + assert(nFormat == CAIRO_FORMAT_RGB24_888 && "Expected RGB24_888 image"); + else +#endif + { + assert(nFormat == CAIRO_FORMAT_ARGB32 + && "need to implement CAIRO_FORMAT_A1 after all here"); + } + + rStride + = cairo_format_stride_for_width(nFormat, cairo_image_surface_get_width(implGetSurface())); + + return mask_data; +} + +MaskHelper::MaskHelper(const SalBitmap& rAlphaBitmap) +{ + const SvpSalBitmap& rMask = static_cast<const SvpSalBitmap&>(rAlphaBitmap); + const BitmapBuffer* pMaskBuf = rMask.GetBuffer(); + assert(rAlphaBitmap.GetBitCount() == 8 && "we only support 8-bit masks now"); + + implSetSurface(cairo_image_surface_create_for_data(pMaskBuf->mpBits, CAIRO_FORMAT_A8, + pMaskBuf->mnWidth, pMaskBuf->mnHeight, + pMaskBuf->mnScanlineSize)); +} + +namespace +{ +// check for env var that decides for using downscale pattern +const char* pDisableDownScale(getenv("SAL_DISABLE_CAIRO_DOWNSCALE")); +bool bDisableDownScale(nullptr != pDisableDownScale); + +sal_Int64 estimateUsageInBytesForSurfaceHelper(const SurfaceHelper* pHelper) +{ + sal_Int64 nRetval(0); + + if (nullptr != pHelper) + { + cairo_surface_t* pSurface(pHelper->getSurface()); + + if (pSurface) + { + const tools::Long nStride(cairo_image_surface_get_stride(pSurface)); + const tools::Long nHeight(cairo_image_surface_get_height(pSurface)); + + nRetval = nStride * nHeight; + + // if we do downscale, size will grow by 1/4 + 1/16 + 1/32 + ..., + // rough estimation just multiplies by 1.25, should be good enough + // for estimation of buffer survival time + if (!bDisableDownScale) + { + nRetval = (nRetval * 5) / 4; + } + } + } + + return nRetval; +} + +} // end anonymous namespace + +SystemDependentData_BitmapHelper::SystemDependentData_BitmapHelper( + std::shared_ptr<BitmapHelper> xBitmapHelper) + : basegfx::SystemDependentData(Application::GetSystemDependentDataManager()) + , maBitmapHelper(std::move(xBitmapHelper)) +{ +} + +sal_Int64 SystemDependentData_BitmapHelper::estimateUsageInBytes() const +{ + return estimateUsageInBytesForSurfaceHelper(maBitmapHelper.get()); +} + +SystemDependentData_MaskHelper::SystemDependentData_MaskHelper( + std::shared_ptr<MaskHelper> xMaskHelper) + : basegfx::SystemDependentData(Application::GetSystemDependentDataManager()) + , maMaskHelper(std::move(xMaskHelper)) +{ +} + +sal_Int64 SystemDependentData_MaskHelper::estimateUsageInBytes() const +{ + return estimateUsageInBytesForSurfaceHelper(maMaskHelper.get()); +} + +namespace +{ +// MM02 decide to use buffers or not +const char* pDisableMM02Goodies(getenv("SAL_DISABLE_MM02_GOODIES")); +bool bUseBuffer(nullptr == pDisableMM02Goodies); +const tools::Long nMinimalSquareSizeToBuffer(64 * 64); +} + +void tryToUseSourceBuffer(const SalBitmap& rSourceBitmap, std::shared_ptr<BitmapHelper>& rSurface) +{ + // MM02 try to access buffered BitmapHelper + std::shared_ptr<SystemDependentData_BitmapHelper> pSystemDependentData_BitmapHelper; + const bool bBufferSource(bUseBuffer + && rSourceBitmap.GetSize().Width() * rSourceBitmap.GetSize().Height() + > nMinimalSquareSizeToBuffer); + + if (bBufferSource) + { + pSystemDependentData_BitmapHelper + = rSourceBitmap.getSystemDependentData<SystemDependentData_BitmapHelper>(); + + if (pSystemDependentData_BitmapHelper) + { + // reuse buffered data + rSurface = pSystemDependentData_BitmapHelper->getBitmapHelper(); + } + } + + if (rSurface) + return; + + // create data on-demand + rSurface = std::make_shared<BitmapHelper>(rSourceBitmap); + + if (bBufferSource) + { + // add to buffering mechanism to potentially reuse next time + rSourceBitmap.addOrReplaceSystemDependentData<SystemDependentData_BitmapHelper>(rSurface); + } +} + +void tryToUseMaskBuffer(const SalBitmap& rMaskBitmap, std::shared_ptr<MaskHelper>& rMask) +{ + // MM02 try to access buffered MaskHelper + std::shared_ptr<SystemDependentData_MaskHelper> pSystemDependentData_MaskHelper; + const bool bBufferMask(bUseBuffer + && rMaskBitmap.GetSize().Width() * rMaskBitmap.GetSize().Height() + > nMinimalSquareSizeToBuffer); + + if (bBufferMask) + { + pSystemDependentData_MaskHelper + = rMaskBitmap.getSystemDependentData<SystemDependentData_MaskHelper>(); + + if (pSystemDependentData_MaskHelper) + { + // reuse buffered data + rMask = pSystemDependentData_MaskHelper->getMaskHelper(); + } + } + + if (rMask) + return; + + // create data on-demand + rMask = std::make_shared<MaskHelper>(rMaskBitmap); + + if (bBufferMask) + { + // add to buffering mechanism to potentially reuse next time + rMaskBitmap.addOrReplaceSystemDependentData<SystemDependentData_MaskHelper>(rMask); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/CairoCommon.cxx b/vcl/headless/CairoCommon.cxx new file mode 100644 index 0000000000..2af8e0d892 --- /dev/null +++ b/vcl/headless/CairoCommon.cxx @@ -0,0 +1,2208 @@ +/* -*- 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 <headless/BitmapHelper.hxx> +#include <headless/CairoCommon.hxx> +#include <dlfcn.h> +#include <vcl/BitmapTools.hxx> +#include <SalGradient.hxx> +#include <svdata.hxx> +#include <tools/helpers.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/range/b2irange.hxx> +#include <unotools/configmgr.hxx> +#include <sal/log.hxx> +#include <osl/module.h> + +#if CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 12, 0) +#error "require at least cairo 1.12.0" +#endif + +void dl_cairo_surface_set_device_scale(cairo_surface_t* surface, double x_scale, double y_scale) +{ +#if !HAVE_DLAPI + cairo_surface_set_device_scale(surface, x_scale, y_scale); +#else + static auto func = reinterpret_cast<void (*)(cairo_surface_t*, double, double)>( + osl_getAsciiFunctionSymbol(nullptr, "cairo_surface_set_device_scale")); + if (func) + func(surface, x_scale, y_scale); +#endif +} + +void dl_cairo_surface_get_device_scale(cairo_surface_t* surface, double* x_scale, double* y_scale) +{ +#if !HAVE_DLAPI + cairo_surface_get_device_scale(surface, x_scale, y_scale); +#else + static auto func = reinterpret_cast<void (*)(cairo_surface_t*, double*, double*)>( + osl_getAsciiFunctionSymbol(nullptr, "cairo_surface_get_device_scale")); + if (func) + func(surface, x_scale, y_scale); + else + { + if (x_scale) + *x_scale = 1.0; + if (y_scale) + *y_scale = 1.0; + } +#endif +} + +basegfx::B2DRange getFillDamage(cairo_t* cr) +{ + double x1, y1, x2, y2; + + // this is faster than cairo_fill_extents, at the cost of some overdraw + cairo_path_extents(cr, &x1, &y1, &x2, &y2); + + // support B2DRange::isEmpty() + if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2) + { + return basegfx::B2DRange(x1, y1, x2, y2); + } + + return basegfx::B2DRange(); +} + +basegfx::B2DRange getClipBox(cairo_t* cr) +{ + double x1, y1, x2, y2; + + cairo_clip_extents(cr, &x1, &y1, &x2, &y2); + + // support B2DRange::isEmpty() + if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2) + { + return basegfx::B2DRange(x1, y1, x2, y2); + } + + return basegfx::B2DRange(); +} + +basegfx::B2DRange getClippedFillDamage(cairo_t* cr) +{ + basegfx::B2DRange aDamageRect(getFillDamage(cr)); + aDamageRect.intersect(getClipBox(cr)); + return aDamageRect; +} + +basegfx::B2DRange getStrokeDamage(cairo_t* cr) +{ + double x1, y1, x2, y2; + + // less accurate, but much faster + cairo_path_extents(cr, &x1, &y1, &x2, &y2); + + // support B2DRange::isEmpty() + if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2) + { + return basegfx::B2DRange(x1, y1, x2, y2); + } + + return basegfx::B2DRange(); +} + +basegfx::B2DRange getClippedStrokeDamage(cairo_t* cr) +{ + basegfx::B2DRange aDamageRect(getStrokeDamage(cr)); + aDamageRect.intersect(getClipBox(cr)); + return aDamageRect; +} + +// Remove bClosePath: Checked that the already used mechanism for Win using +// Gdiplus already relies on rPolygon.isClosed(), so should be safe to replace +// this. +// For PixelSnap we need the ObjectToDevice transformation here now. This is a +// special case relative to the also executed LineDraw-Offset of (0.5, 0.5) in +// DeviceCoordinates: The LineDraw-Offset is applied *after* the snap, so we +// need the ObjectToDevice transformation *without* that offset here to do the +// same. The LineDraw-Offset will be applied by the callers using a linear +// transformation for Cairo now +// For support of PixelSnapHairline we also need the ObjectToDevice transformation +// and a method (same as in gdiimpl.cxx for Win and Gdiplus). This is needed e.g. +// for Chart-content visualization. CAUTION: It's not the same as PixelSnap (!) +// tdf#129845 add reply value to allow counting a point/byte/size measurement to +// be included +size_t AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap, + bool bPixelSnapHairline) +{ + // short circuit if there is nothing to do + const sal_uInt32 nPointCount(rPolygon.count()); + size_t nSizeMeasure(0); + + if (0 == nPointCount) + { + return nSizeMeasure; + } + + const bool bHasCurves(rPolygon.areControlPointsUsed()); + const bool bClosePath(rPolygon.isClosed()); + const bool bObjectToDeviceUsed(!rObjectToDevice.isIdentity()); + basegfx::B2DHomMatrix aObjectToDeviceInv; + basegfx::B2DPoint aLast; + PixelSnapper aSnapper; + + for (sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++) + { + int nClosedIdx = nPointIdx; + if (nPointIdx >= nPointCount) + { + // prepare to close last curve segment if needed + if (bClosePath && (nPointIdx == nPointCount)) + { + nClosedIdx = 0; + } + else + { + break; + } + } + + basegfx::B2DPoint aPoint(rPolygon.getB2DPoint(nClosedIdx)); + + if (bPixelSnap) + { + // snap device coordinates to full pixels + if (bObjectToDeviceUsed) + { + // go to DeviceCoordinates + aPoint *= rObjectToDevice; + } + + // snap by rounding + aPoint.setX(basegfx::fround(aPoint.getX())); + aPoint.setY(basegfx::fround(aPoint.getY())); + + if (bObjectToDeviceUsed) + { + if (aObjectToDeviceInv.isIdentity()) + { + aObjectToDeviceInv = rObjectToDevice; + aObjectToDeviceInv.invert(); + } + + // go back to ObjectCoordinates + aPoint *= aObjectToDeviceInv; + } + } + + if (bPixelSnapHairline) + { + // snap horizontal and vertical lines (mainly used in Chart for + // 'nicer' AAing) + aPoint = aSnapper.snap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nClosedIdx); + } + + if (!nPointIdx) + { + // first point => just move there + cairo_move_to(cr, aPoint.getX(), aPoint.getY()); + aLast = aPoint; + continue; + } + + bool bPendingCurve(false); + + if (bHasCurves) + { + bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx); + bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx); + } + + if (!bPendingCurve) // line segment + { + cairo_line_to(cr, aPoint.getX(), aPoint.getY()); + nSizeMeasure++; + } + else // cubic bezier segment + { + basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx); + basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx); + + // tdf#99165 if the control points are 'empty', create the mathematical + // correct replacement ones to avoid problems with the graphical sub-system + // 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 (aCP1.equal(aLast)) + { + aCP1 = aLast + ((aCP2 - aLast) * 0.0005); + } + + if (aCP2.equal(aPoint)) + { + aCP2 = aPoint + ((aCP1 - aPoint) * 0.0005); + } + + cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aPoint.getX(), + aPoint.getY()); + // take some bigger measure for curve segments - too expensive to subdivide + // here and that precision not needed, but four (2 points, 2 control-points) + // would be a too low weight + nSizeMeasure += 10; + } + + aLast = aPoint; + } + + if (bClosePath) + { + cairo_close_path(cr); + } + + return nSizeMeasure; +} + +basegfx::B2DPoint PixelSnapper::snap(const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, + basegfx::B2DHomMatrix& rObjectToDeviceInv, sal_uInt32 nIndex) +{ + const sal_uInt32 nCount(rPolygon.count()); + + // get the data + if (nIndex == 0) + { + // if it's the first time, we need to calculate everything + maPrevPoint = rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount); + maCurrPoint = rObjectToDevice * rPolygon.getB2DPoint(nIndex); + maPrevTuple = basegfx::fround(maPrevPoint); + maCurrTuple = basegfx::fround(maCurrPoint); + } + else + { + // but for all other times, we can re-use the previous iteration computations + maPrevPoint = maCurrPoint; + maPrevTuple = maCurrTuple; + maCurrPoint = maNextPoint; + maCurrTuple = maNextTuple; + } + maNextPoint = rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount); + maNextTuple = basegfx::fround(maNextPoint); + + // get the states + const bool bPrevVertical(maPrevTuple.getX() == maCurrTuple.getX()); + const bool bNextVertical(maNextTuple.getX() == maCurrTuple.getX()); + const bool bPrevHorizontal(maPrevTuple.getY() == maCurrTuple.getY()); + const bool bNextHorizontal(maNextTuple.getY() == maCurrTuple.getY()); + const bool bSnapX(bPrevVertical || bNextVertical); + const bool bSnapY(bPrevHorizontal || bNextHorizontal); + + if (bSnapX || bSnapY) + { + basegfx::B2DPoint aSnappedPoint(bSnapX ? maCurrTuple.getX() : maCurrPoint.getX(), + bSnapY ? maCurrTuple.getY() : maCurrPoint.getY()); + + if (rObjectToDeviceInv.isIdentity()) + { + rObjectToDeviceInv = rObjectToDevice; + rObjectToDeviceInv.invert(); + } + + aSnappedPoint *= rObjectToDeviceInv; + + return aSnappedPoint; + } + + return rPolygon.getB2DPoint(nIndex); +} + +SystemDependentData_CairoPath::SystemDependentData_CairoPath(size_t nSizeMeasure, cairo_t* cr, + bool bNoJoin, bool bAntiAlias, + const std::vector<double>* pStroke) + : basegfx::SystemDependentData(Application::GetSystemDependentDataManager()) + , mpCairoPath(nullptr) + , mbNoJoin(bNoJoin) + , mbAntiAlias(bAntiAlias) +{ + static const bool bFuzzing = utl::ConfigManager::IsFuzzing(); + + // tdf#129845 only create a copy of the path when nSizeMeasure is + // bigger than some decent threshold + if (!bFuzzing && nSizeMeasure > 50) + { + mpCairoPath = cairo_copy_path(cr); + + if (nullptr != pStroke) + { + maStroke = *pStroke; + } + } +} + +SystemDependentData_CairoPath::~SystemDependentData_CairoPath() +{ + if (nullptr != mpCairoPath) + { + cairo_path_destroy(mpCairoPath); + mpCairoPath = nullptr; + } +} + +sal_Int64 SystemDependentData_CairoPath::estimateUsageInBytes() const +{ + // tdf#129845 by using the default return value of zero when no path + // was created, SystemDependentData::calculateCombinedHoldCyclesInSeconds + // will do the right thing and not buffer this entry at all + sal_Int64 nRetval(0); + + if (nullptr != mpCairoPath) + { + // per node + // - num_data incarnations of + // - sizeof(cairo_path_data_t) which is a union of defines and point data + // thus may 2 x sizeof(double) + nRetval = mpCairoPath->num_data * sizeof(cairo_path_data_t); + } + + return nRetval; +} + +void add_polygon_path(cairo_t* cr, const basegfx::B2DPolyPolygon& rPolyPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap) +{ + // try to access buffered data + std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath( + rPolyPolygon.getSystemDependentData<SystemDependentData_CairoPath>()); + + if (pSystemDependentData_CairoPath) + { + // re-use data + cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath()); + } + else + { + // create data + size_t nSizeMeasure(0); + + for (const auto& rPoly : rPolyPolygon) + { + // PixelOffset used: Was dependent of 'm_aLineColor != SALCOLOR_NONE' + // Adapt setupPolyPolygon-users to set a linear transformation to achieve PixelOffset + nSizeMeasure += AddPolygonToPath(cr, rPoly, rObjectToDevice, bPixelSnap, false); + } + + // copy and add to buffering mechanism + // for decisions how/what to buffer, see Note in WinSalGraphicsImpl::drawPolyPolygon + pSystemDependentData_CairoPath + = rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>( + nSizeMeasure, cr, false, false, nullptr); + } +} + +cairo_user_data_key_t* CairoCommon::getDamageKey() +{ + static cairo_user_data_key_t aDamageKey; + return &aDamageKey; +} + +sal_uInt16 CairoCommon::GetBitCount() const +{ + if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_ALPHA) + return 1; + return 32; +} + +cairo_t* CairoCommon::getCairoContext(bool bXorModeAllowed, bool bAntiAlias) const +{ + cairo_t* cr; + if (m_ePaintMode == PaintMode::Xor && bXorModeAllowed) + cr = createTmpCompatibleCairoContext(); + else + cr = cairo_create(m_pSurface); + cairo_set_line_width(cr, 1); + cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); + cairo_set_antialias(cr, bAntiAlias ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + + // ensure no linear transformation and no PathInfo in local cairo_path_t + cairo_identity_matrix(cr); + cairo_new_path(cr); + + return cr; +} + +void CairoCommon::releaseCairoContext(cairo_t* cr, bool bXorModeAllowed, + const basegfx::B2DRange& rExtents) const +{ + const bool bXoring = (m_ePaintMode == PaintMode::Xor && bXorModeAllowed); + + if (rExtents.isEmpty()) + { + //nothing changed, return early + if (bXoring) + { + cairo_surface_t* surface = cairo_get_target(cr); + cairo_surface_destroy(surface); + } + cairo_destroy(cr); + return; + } + + basegfx::B2IRange aIntExtents(basegfx::unotools::b2ISurroundingRangeFromB2DRange(rExtents)); + sal_Int32 nExtentsLeft(aIntExtents.getMinX()), nExtentsTop(aIntExtents.getMinY()); + sal_Int32 nExtentsRight(aIntExtents.getMaxX()), nExtentsBottom(aIntExtents.getMaxY()); + sal_Int32 nWidth = m_aFrameSize.getX(); + sal_Int32 nHeight = m_aFrameSize.getY(); + nExtentsLeft = std::max<sal_Int32>(nExtentsLeft, 0); + nExtentsTop = std::max<sal_Int32>(nExtentsTop, 0); + nExtentsRight = std::min<sal_Int32>(nExtentsRight, nWidth); + nExtentsBottom = std::min<sal_Int32>(nExtentsBottom, nHeight); + + cairo_surface_t* surface = cairo_get_target(cr); + cairo_surface_flush(surface); + + //For the most part we avoid the use of XOR these days, but there + //are some edge cases where legacy stuff still supports it, so + //emulate it (slowly) here. + if (bXoring) + doXorOnRelease(nExtentsLeft, nExtentsTop, nExtentsRight, nExtentsBottom, surface, nWidth); + + cairo_destroy(cr); // unref + + DamageHandler* pDamage + = static_cast<DamageHandler*>(cairo_surface_get_user_data(m_pSurface, getDamageKey())); + + if (pDamage) + { + pDamage->damaged(pDamage->handle, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft, + nExtentsBottom - nExtentsTop); + } +} + +void CairoCommon::doXorOnRelease(sal_Int32 nExtentsLeft, sal_Int32 nExtentsTop, + sal_Int32 nExtentsRight, sal_Int32 nExtentsBottom, + cairo_surface_t* const surface, sal_Int32 nWidth) const +{ + //For the most part we avoid the use of XOR these days, but there + //are some edge cases where legacy stuff still supports it, so + //emulate it (slowly) here. + cairo_surface_t* target_surface = m_pSurface; + if (cairo_surface_get_type(target_surface) != CAIRO_SURFACE_TYPE_IMAGE) + { + //in the unlikely case we can't use m_pSurface directly, copy contents + //to another temp image surface + if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_COLOR_ALPHA) + target_surface = cairo_surface_map_to_image(target_surface, nullptr); + else + { + // for gen, which is CAIRO_FORMAT_RGB24/CAIRO_CONTENT_COLOR I'm getting + // visual corruption in vcldemo with cairo_surface_map_to_image + cairo_t* copycr = createTmpCompatibleCairoContext(); + cairo_rectangle(copycr, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft, + nExtentsBottom - nExtentsTop); + cairo_set_source_surface(copycr, m_pSurface, 0, 0); + cairo_fill(copycr); + target_surface = cairo_get_target(copycr); + cairo_destroy(copycr); + } + } + + cairo_surface_flush(target_surface); + unsigned char* target_surface_data = cairo_image_surface_get_data(target_surface); + unsigned char* xor_surface_data = cairo_image_surface_get_data(surface); + + cairo_format_t nFormat = cairo_image_surface_get_format(target_surface); + assert(nFormat == CAIRO_FORMAT_ARGB32 && "need to implement CAIRO_FORMAT_A1 after all here"); + sal_Int32 nStride = cairo_format_stride_for_width(nFormat, nWidth * m_fScale); + sal_Int32 nUnscaledExtentsLeft = nExtentsLeft * m_fScale; + sal_Int32 nUnscaledExtentsRight = nExtentsRight * m_fScale; + sal_Int32 nUnscaledExtentsTop = nExtentsTop * m_fScale; + sal_Int32 nUnscaledExtentsBottom = nExtentsBottom * m_fScale; + + // Handle headless size forced to (1,1) by SvpSalFrame::GetSurfaceFrameSize(). + int target_surface_width = cairo_image_surface_get_width(target_surface); + if (nUnscaledExtentsLeft > target_surface_width) + nUnscaledExtentsLeft = target_surface_width; + if (nUnscaledExtentsRight > target_surface_width) + nUnscaledExtentsRight = target_surface_width; + int target_surface_height = cairo_image_surface_get_height(target_surface); + if (nUnscaledExtentsTop > target_surface_height) + nUnscaledExtentsTop = target_surface_height; + if (nUnscaledExtentsBottom > target_surface_height) + nUnscaledExtentsBottom = target_surface_height; + +#if !ENABLE_WASM_STRIP_PREMULTIPLY + vcl::bitmap::lookup_table const& unpremultiply_table = vcl::bitmap::get_unpremultiply_table(); + vcl::bitmap::lookup_table const& premultiply_table = vcl::bitmap::get_premultiply_table(); +#endif + for (sal_Int32 y = nUnscaledExtentsTop; y < nUnscaledExtentsBottom; ++y) + { + unsigned char* true_row = target_surface_data + (nStride * y); + unsigned char* xor_row = xor_surface_data + (nStride * y); + unsigned char* true_data = true_row + (nUnscaledExtentsLeft * 4); + unsigned char* xor_data = xor_row + (nUnscaledExtentsLeft * 4); + for (sal_Int32 x = nUnscaledExtentsLeft; x < nUnscaledExtentsRight; ++x) + { + sal_uInt8 a = true_data[SVP_CAIRO_ALPHA]; + sal_uInt8 xor_a = xor_data[SVP_CAIRO_ALPHA]; +#if ENABLE_WASM_STRIP_PREMULTIPLY + sal_uInt8 b = vcl::bitmap::unpremultiply(a, true_data[SVP_CAIRO_BLUE]) + ^ vcl::bitmap::unpremultiply(xor_a, xor_data[SVP_CAIRO_BLUE]); + sal_uInt8 g = vcl::bitmap::unpremultiply(a, true_data[SVP_CAIRO_GREEN]) + ^ vcl::bitmap::unpremultiply(xor_a, xor_data[SVP_CAIRO_GREEN]); + sal_uInt8 r = vcl::bitmap::unpremultiply(a, true_data[SVP_CAIRO_RED]) + ^ vcl::bitmap::unpremultiply(xor_a, xor_data[SVP_CAIRO_RED]); + true_data[SVP_CAIRO_BLUE] = vcl::bitmap::premultiply(a, b); + true_data[SVP_CAIRO_GREEN] = vcl::bitmap::premultiply(a, g); + true_data[SVP_CAIRO_RED] = vcl::bitmap::premultiply(a, r); +#else + sal_uInt8 b = unpremultiply_table[a][true_data[SVP_CAIRO_BLUE]] + ^ unpremultiply_table[xor_a][xor_data[SVP_CAIRO_BLUE]]; + sal_uInt8 g = unpremultiply_table[a][true_data[SVP_CAIRO_GREEN]] + ^ unpremultiply_table[xor_a][xor_data[SVP_CAIRO_GREEN]]; + sal_uInt8 r = unpremultiply_table[a][true_data[SVP_CAIRO_RED]] + ^ unpremultiply_table[xor_a][xor_data[SVP_CAIRO_RED]]; + true_data[SVP_CAIRO_BLUE] = premultiply_table[a][b]; + true_data[SVP_CAIRO_GREEN] = premultiply_table[a][g]; + true_data[SVP_CAIRO_RED] = premultiply_table[a][r]; +#endif + true_data += 4; + xor_data += 4; + } + } + cairo_surface_mark_dirty(target_surface); + + if (target_surface != m_pSurface) + { + if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_COLOR_ALPHA) + cairo_surface_unmap_image(m_pSurface, target_surface); + else + { + cairo_t* copycr = cairo_create(m_pSurface); + //copy contents back from image surface + cairo_rectangle(copycr, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft, + nExtentsBottom - nExtentsTop); + cairo_set_source_surface(copycr, target_surface, 0, 0); + cairo_fill(copycr); + cairo_destroy(copycr); + cairo_surface_destroy(target_surface); + } + } + + cairo_surface_destroy(surface); +} + +cairo_t* CairoCommon::createTmpCompatibleCairoContext() const +{ + cairo_surface_t* target = cairo_surface_create_similar_image(m_pSurface, CAIRO_FORMAT_ARGB32, + m_aFrameSize.getX() * m_fScale, + m_aFrameSize.getY() * m_fScale); + + dl_cairo_surface_set_device_scale(target, m_fScale, m_fScale); + + return cairo_create(target); +} + +void CairoCommon::applyColor(cairo_t* cr, Color aColor, double fTransparency) +{ + if (cairo_surface_get_content(cairo_get_target(cr)) != CAIRO_CONTENT_ALPHA) + { + cairo_set_source_rgba(cr, aColor.GetRed() / 255.0, aColor.GetGreen() / 255.0, + aColor.GetBlue() / 255.0, 1.0 - fTransparency); + } + else + { + double fSet = aColor == COL_BLACK ? 1.0 : 0.0; + cairo_set_source_rgba(cr, 1, 1, 1, fSet); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + } +} + +void CairoCommon::clipRegion(cairo_t* cr, const vcl::Region& rClipRegion) +{ + RectangleVector aRectangles; + if (!rClipRegion.IsEmpty()) + { + rClipRegion.GetRegionRectangles(aRectangles); + } + if (!aRectangles.empty()) + { + bool bEmpty = true; + for (auto const& rectangle : aRectangles) + { + if (rectangle.GetWidth() <= 0 || rectangle.GetHeight() <= 0) + { + SAL_WARN("vcl.gdi", "bad clip rect of: " << rectangle); + continue; + } + cairo_rectangle(cr, rectangle.Left(), rectangle.Top(), rectangle.GetWidth(), + rectangle.GetHeight()); + bEmpty = false; + } + if (!bEmpty) + cairo_clip(cr); + } +} + +void CairoCommon::clipRegion(cairo_t* cr) { CairoCommon::clipRegion(cr, m_aClipRegion); } + +void CairoCommon::SetXORMode(bool bSet, bool /*bInvertOnly*/) +{ + m_ePaintMode = bSet ? PaintMode::Xor : PaintMode::Over; +} + +void CairoCommon::SetROPLineColor(SalROPColor nROPColor) +{ + switch (nROPColor) + { + case SalROPColor::N0: + m_oLineColor = Color(0, 0, 0); + break; + case SalROPColor::N1: + m_oLineColor = Color(0xff, 0xff, 0xff); + break; + case SalROPColor::Invert: + m_oLineColor = Color(0xff, 0xff, 0xff); + break; + } +} + +void CairoCommon::SetROPFillColor(SalROPColor nROPColor) +{ + switch (nROPColor) + { + case SalROPColor::N0: + m_oFillColor = Color(0, 0, 0); + break; + case SalROPColor::N1: + m_oFillColor = Color(0xff, 0xff, 0xff); + break; + case SalROPColor::Invert: + m_oFillColor = Color(0xff, 0xff, 0xff); + break; + } +} + +void CairoCommon::drawPixel(const std::optional<Color>& rLineColor, tools::Long nX, tools::Long nY, + bool bAntiAlias) +{ + if (!rLineColor) + return; + + cairo_t* cr = getCairoContext(true, bAntiAlias); + clipRegion(cr); + + cairo_rectangle(cr, nX, nY, 1, 1); + CairoCommon::applyColor(cr, *rLineColor, 0.0); + cairo_fill(cr); + + basegfx::B2DRange extents = getClippedFillDamage(cr); + releaseCairoContext(cr, true, extents); +} + +Color CairoCommon::getPixel(cairo_surface_t* pSurface, tools::Long nX, tools::Long nY) +{ + cairo_surface_t* target + = cairo_surface_create_similar_image(pSurface, CAIRO_FORMAT_ARGB32, 1, 1); + + cairo_t* cr = cairo_create(target); + + cairo_rectangle(cr, 0, 0, 1, 1); + cairo_set_source_surface(cr, pSurface, -nX, -nY); + cairo_paint(cr); + cairo_destroy(cr); + + cairo_surface_flush(target); +#if !ENABLE_WASM_STRIP_PREMULTIPLY + vcl::bitmap::lookup_table const& unpremultiply_table = vcl::bitmap::get_unpremultiply_table(); +#endif + unsigned char* data = cairo_image_surface_get_data(target); + sal_uInt8 a = data[SVP_CAIRO_ALPHA]; +#if ENABLE_WASM_STRIP_PREMULTIPLY + sal_uInt8 b = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_BLUE]); + sal_uInt8 g = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_GREEN]); + sal_uInt8 r = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_RED]); +#else + sal_uInt8 b = unpremultiply_table[a][data[SVP_CAIRO_BLUE]]; + sal_uInt8 g = unpremultiply_table[a][data[SVP_CAIRO_GREEN]]; + sal_uInt8 r = unpremultiply_table[a][data[SVP_CAIRO_RED]]; +#endif + Color aColor(ColorAlpha, a, r, g, b); + cairo_surface_destroy(target); + + return aColor; +} + +void CairoCommon::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2, + bool bAntiAlias) +{ + cairo_t* cr = getCairoContext(false, bAntiAlias); + clipRegion(cr); + + basegfx::B2DPolygon aPoly; + + // PixelOffset used: To not mix with possible PixelSnap, cannot do + // directly on coordinates as tried before - despite being already 'snapped' + // due to being integer. If it would be directly added here, it would be + // 'snapped' again when !getAntiAlias(), losing the (0.5, 0.5) offset + aPoly.append(basegfx::B2DPoint(nX1, nY1)); + aPoly.append(basegfx::B2DPoint(nX2, nY2)); + + // PixelOffset used: Set PixelOffset as linear transformation + cairo_matrix_t aMatrix; + cairo_matrix_init_translate(&aMatrix, 0.5, 0.5); + cairo_set_matrix(cr, &aMatrix); + + AddPolygonToPath(cr, aPoly, basegfx::B2DHomMatrix(), !bAntiAlias, false); + + CairoCommon::applyColor(cr, *m_oLineColor); + + basegfx::B2DRange extents = getClippedStrokeDamage(cr); + extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5)); + + cairo_stroke(cr); + + releaseCairoContext(cr, false, extents); +} + +// true if we have a fill color and the line color is the same or non-existent +static bool onlyFillRect(const std::optional<Color>& rFillColor, + const std::optional<Color>& rLineColor) +{ + if (!rFillColor) + return false; + if (!rLineColor) + return true; + return *rFillColor == *rLineColor; +} + +void CairoCommon::drawRect(double nX, double nY, double nWidth, double nHeight, bool bAntiAlias) +{ + // fast path for the common case of simply creating a solid block of color + if (onlyFillRect(m_oFillColor, m_oLineColor)) + { + double fTransparency = 0; + // don't bother trying to draw stuff which is effectively invisible + if (nWidth < 0.1 || nHeight < 0.1) + return; + + cairo_t* cr = getCairoContext(true, bAntiAlias); + clipRegion(cr); + + bool bPixelSnap = !bAntiAlias; + if (bPixelSnap) + { + // snap by rounding + nX = basegfx::fround(nX); + nY = basegfx::fround(nY); + nWidth = basegfx::fround(nWidth); + nHeight = basegfx::fround(nHeight); + } + cairo_rectangle(cr, nX, nY, nWidth, nHeight); + + CairoCommon::applyColor(cr, *m_oFillColor, fTransparency); + // Get FillDamage + basegfx::B2DRange extents = getClippedFillDamage(cr); + + cairo_fill(cr); + + releaseCairoContext(cr, true, extents); + + return; + } + // because of the -1 hack we have to do fill and draw separately + std::optional<Color> aOrigFillColor = m_oFillColor; + std::optional<Color> aOrigLineColor = m_oLineColor; + m_oFillColor = std::nullopt; + m_oLineColor = std::nullopt; + + if (aOrigFillColor) + { + basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect( + basegfx::B2DRectangle(nX, nY, nX + nWidth, nY + nHeight)); + + m_oFillColor = aOrigFillColor; + drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aRect), 0.0, bAntiAlias); + m_oFillColor = std::nullopt; + } + + if (aOrigLineColor) + { + // need -1 hack to exclude the bottom and right edges to act like wingdi "Rectangle" + // function which is what was probably the ultimate origin of this behavior + basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect( + basegfx::B2DRectangle(nX, nY, nX + nWidth - 1, nY + nHeight - 1)); + + m_oLineColor = aOrigLineColor; + drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aRect), 0.0, bAntiAlias); + m_oLineColor = std::nullopt; + } + + m_oFillColor = aOrigFillColor; + m_oLineColor = aOrigLineColor; +} + +void CairoCommon::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry, bool bAntiAlias) +{ + basegfx::B2DPolygon aPoly; + aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints); + for (sal_uInt32 i = 1; i < nPoints; ++i) + aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY())); + + drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aPoly), 0.0, bAntiAlias); +} + +void CairoCommon::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPointCounts, + const Point** pPtAry, bool bAntiAlias) +{ + basegfx::B2DPolyPolygon aPolyPoly; + for (sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon) + { + sal_uInt32 nPoints = pPointCounts[nPolygon]; + if (nPoints) + { + const Point* pPoints = pPtAry[nPolygon]; + basegfx::B2DPolygon aPoly; + aPoly.append(basegfx::B2DPoint(pPoints->getX(), pPoints->getY()), nPoints); + for (sal_uInt32 i = 1; i < nPoints; ++i) + aPoly.setB2DPoint(i, basegfx::B2DPoint(pPoints[i].getX(), pPoints[i].getY())); + + aPolyPoly.append(aPoly); + } + } + + drawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPoly, 0.0, bAntiAlias); +} + +void CairoCommon::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& rPolyPolygon, double fTransparency, + bool bAntiAlias) +{ + const bool bHasFill(m_oFillColor.has_value()); + const bool bHasLine(m_oLineColor.has_value()); + + if (0 == rPolyPolygon.count() || !(bHasFill || bHasLine) || fTransparency < 0.0 + || fTransparency >= 1.0) + { + return; + } + + if (!bHasLine) + { + // don't bother trying to draw stuff which is effectively invisible, speeds up + // drawing some complex drawings. This optimisation is not valid when we do + // the pixel offset thing (i.e. bHasLine) + basegfx::B2DRange aPolygonRange = rPolyPolygon.getB2DRange(); + aPolygonRange.transform(rObjectToDevice); + if (aPolygonRange.getWidth() < 0.1 || aPolygonRange.getHeight() < 0.1) + return; + } + + cairo_t* cr = getCairoContext(true, bAntiAlias); + if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) + { + SAL_WARN("vcl.gdi", + "cannot render to surface: " << cairo_status_to_string(cairo_status(cr))); + releaseCairoContext(cr, true, basegfx::B2DRange()); + return; + } + clipRegion(cr); + + // Set full (Object-to-Device) transformation - if used + if (!rObjectToDevice.isIdentity()) + { + cairo_matrix_t aMatrix; + + cairo_matrix_init(&aMatrix, rObjectToDevice.get(0, 0), rObjectToDevice.get(1, 0), + rObjectToDevice.get(0, 1), rObjectToDevice.get(1, 1), + rObjectToDevice.get(0, 2), rObjectToDevice.get(1, 2)); + cairo_set_matrix(cr, &aMatrix); + } + + // To make releaseCairoContext work, use empty extents + basegfx::B2DRange extents; + + if (bHasFill) + { + add_polygon_path(cr, rPolyPolygon, rObjectToDevice, !bAntiAlias); + + CairoCommon::applyColor(cr, *m_oFillColor, fTransparency); + // Get FillDamage (will be extended for LineDamage below) + extents = getClippedFillDamage(cr); + + cairo_fill(cr); + } + + if (bHasLine) + { + // PixelOffset used: Set PixelOffset as linear transformation + cairo_matrix_t aMatrix; + cairo_matrix_init_translate(&aMatrix, 0.5, 0.5); + cairo_set_matrix(cr, &aMatrix); + + add_polygon_path(cr, rPolyPolygon, rObjectToDevice, !bAntiAlias); + + CairoCommon::applyColor(cr, *m_oLineColor, fTransparency); + + // expand with possible StrokeDamage + basegfx::B2DRange stroke_extents = getClippedStrokeDamage(cr); + stroke_extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5)); + extents.expand(stroke_extents); + + cairo_stroke(cr); + } + + // if transformation has been applied, transform also extents (ranges) + // of damage so they can be correctly redrawn + extents.transform(rObjectToDevice); + releaseCairoContext(cr, true, extents); +} + +void CairoCommon::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry, bool bAntiAlias) +{ + basegfx::B2DPolygon aPoly; + aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints); + for (sal_uInt32 i = 1; i < nPoints; ++i) + aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY())); + aPoly.setClosed(false); + + drawPolyLine(basegfx::B2DHomMatrix(), aPoly, 0.0, 1.0, nullptr, basegfx::B2DLineJoin::Miter, + css::drawing::LineCap_BUTT, basegfx::deg2rad(15.0) /*default*/, false, bAntiAlias); +} + +bool CairoCommon::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolyLine, double fTransparency, + double fLineWidth, const std::vector<double>* pStroke, + basegfx::B2DLineJoin eLineJoin, css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, bool bPixelSnapHairline, bool bAntiAlias) +{ + // short circuit if there is nothing to do + if (0 == rPolyLine.count() || fTransparency < 0.0 || fTransparency >= 1.0) + { + return true; + } + + static const bool bFuzzing = utl::ConfigManager::IsFuzzing(); + if (bFuzzing) + { + const basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyLine)); + if (aRange.getMaxX() - aRange.getMinX() > 0x10000000 + || aRange.getMaxY() - aRange.getMinY() > 0x10000000) + { + SAL_WARN("vcl.gdi", "drawPolyLine, skipping suspicious range of: " + << aRange << " for fuzzing performance"); + return true; + } + } + + cairo_t* cr = getCairoContext(false, bAntiAlias); + clipRegion(cr); + + // need to check/handle LineWidth when ObjectToDevice transformation is used + const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity()); + + // tdf#124848 calculate-back logical LineWidth for a hairline + // since this implementation hands over the transformation to + // the graphic sub-system + if (fLineWidth == 0) + { + fLineWidth = 1.0; + + if (!bObjectToDeviceIsIdentity) + { + basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice); + aObjectToDeviceInv.invert(); + fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength(); + } + } + + // PixelOffset used: Need to reflect in linear transformation + cairo_matrix_t aMatrix; + basegfx::B2DHomMatrix aDamageMatrix(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5)); + + if (bObjectToDeviceIsIdentity) + { + // Set PixelOffset as requested + cairo_matrix_init_translate(&aMatrix, 0.5, 0.5); + } + else + { + // Prepare ObjectToDevice transformation. Take PixelOffset for Lines into + // account: Multiply from left to act in DeviceCoordinates + aDamageMatrix = aDamageMatrix * rObjectToDevice; + cairo_matrix_init(&aMatrix, aDamageMatrix.get(0, 0), aDamageMatrix.get(1, 0), + aDamageMatrix.get(0, 1), aDamageMatrix.get(1, 1), aDamageMatrix.get(0, 2), + aDamageMatrix.get(1, 2)); + } + + // set linear transformation + cairo_set_matrix(cr, &aMatrix); + + // setup line attributes + cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER; + switch (eLineJoin) + { + case basegfx::B2DLineJoin::Bevel: + eCairoLineJoin = CAIRO_LINE_JOIN_BEVEL; + break; + case basegfx::B2DLineJoin::Round: + eCairoLineJoin = CAIRO_LINE_JOIN_ROUND; + break; + case basegfx::B2DLineJoin::NONE: + case basegfx::B2DLineJoin::Miter: + eCairoLineJoin = CAIRO_LINE_JOIN_MITER; + break; + } + + // convert miter minimum angle to miter limit + double fMiterLimit = 1.0 / sin(std::max(fMiterMinimumAngle, 0.01 * M_PI) / 2.0); + + // setup cap attribute + cairo_line_cap_t eCairoLineCap(CAIRO_LINE_CAP_BUTT); + + switch (eLineCap) + { + default: // css::drawing::LineCap_BUTT: + { + eCairoLineCap = CAIRO_LINE_CAP_BUTT; + break; + } + case css::drawing::LineCap_ROUND: + { + eCairoLineCap = CAIRO_LINE_CAP_ROUND; + break; + } + case css::drawing::LineCap_SQUARE: + { + eCairoLineCap = CAIRO_LINE_CAP_SQUARE; + break; + } + } + + cairo_set_source_rgba(cr, m_oLineColor->GetRed() / 255.0, m_oLineColor->GetGreen() / 255.0, + m_oLineColor->GetBlue() / 255.0, 1.0 - fTransparency); + + cairo_set_line_join(cr, eCairoLineJoin); + cairo_set_line_cap(cr, eCairoLineCap); + + constexpr int MaxNormalLineWidth = 64; + if (fLineWidth > MaxNormalLineWidth) + { + const double fLineWidthPixel + = bObjectToDeviceIsIdentity + ? fLineWidth + : (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength(); + if (fLineWidthPixel > MaxNormalLineWidth) + { + SAL_WARN("vcl.gdi", "drawPolyLine, suspicious input line width of: " + << fLineWidth << ", will be " << fLineWidthPixel + << " pixels thick"); + if (bFuzzing) + { + basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice); + aObjectToDeviceInv.invert(); + fLineWidth + = (aObjectToDeviceInv * basegfx::B2DVector(MaxNormalLineWidth, 0)).getLength(); + fLineWidth = std::min(fLineWidth, 2048.0); + } + } + } + cairo_set_line_width(cr, fLineWidth); + cairo_set_miter_limit(cr, fMiterLimit); + + // try to access buffered data + std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath( + rPolyLine.getSystemDependentData<SystemDependentData_CairoPath>()); + + // 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 const bool bDoDirectCairoStroke(true); + + // MM01 activate to stroke directly + if (bDoDirectCairoStroke && bStrokeUsed) + { + cairo_set_dash(cr, pStroke->data(), pStroke->size(), 0.0); + } + + if (!bDoDirectCairoStroke && pSystemDependentData_CairoPath) + { + // MM01 - check on stroke change. Used against not used, or if both used, + // equal or different? + const bool bStrokeWasUsed(!pSystemDependentData_CairoPath->getStroke().empty()); + + if (bStrokeWasUsed != bStrokeUsed + || (bStrokeUsed && *pStroke != pSystemDependentData_CairoPath->getStroke())) + { + // data invalid, forget + pSystemDependentData_CairoPath.reset(); + } + } + + // check for basegfx::B2DLineJoin::NONE to react accordingly + const bool bNoJoin( + (basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(fLineWidth, 0.0))); + + if (pSystemDependentData_CairoPath) + { + // check data validity + if (nullptr == pSystemDependentData_CairoPath->getCairoPath() + || pSystemDependentData_CairoPath->getNoJoin() != bNoJoin + || pSystemDependentData_CairoPath->getAntiAlias() != bAntiAlias + || bPixelSnapHairline /*tdf#124700*/) + { + // data invalid, forget + pSystemDependentData_CairoPath.reset(); + } + } + + if (pSystemDependentData_CairoPath) + { + // re-use data + cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath()); + } + else + { + // create data + size_t nSizeMeasure(0); + + // MM01 need to do line dashing as fallback stuff here now + basegfx::B2DPolyPolygon aPolyPolygonLine; + + if (!bDoDirectCairoStroke && bStrokeUsed) + { + // apply LineStyle + basegfx::utils::applyLineDashing(rPolyLine, // source + *pStroke, // pattern + &aPolyPolygonLine, // target for lines + nullptr, // target for gaps + fDotDashLength); // full length if available + } + else + { + // no line dashing or direct stroke, just copy + aPolyPolygonLine.append(rPolyLine); + } + + // MM01 checked/verified for Cairo + for (sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++) + { + const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a)); + + if (!bNoJoin) + { + // PixelOffset now reflected in linear transformation used + nSizeMeasure + += AddPolygonToPath(cr, aPolyLine, + rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset + !bAntiAlias, bPixelSnapHairline); + } + else + { + const sal_uInt32 nPointCount(aPolyLine.count()); + const sal_uInt32 nEdgeCount(aPolyLine.isClosed() ? nPointCount : nPointCount - 1); + basegfx::B2DPolygon aEdge; + + aEdge.append(aPolyLine.getB2DPoint(0)); + aEdge.append(basegfx::B2DPoint(0.0, 0.0)); + + for (sal_uInt32 i(0); i < nEdgeCount; i++) + { + const sal_uInt32 nNextIndex((i + 1) % nPointCount); + aEdge.setB2DPoint(1, aPolyLine.getB2DPoint(nNextIndex)); + aEdge.setNextControlPoint(0, aPolyLine.getNextControlPoint(i)); + aEdge.setPrevControlPoint(1, aPolyLine.getPrevControlPoint(nNextIndex)); + + // PixelOffset now reflected in linear transformation used + nSizeMeasure += AddPolygonToPath( + cr, aEdge, + rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset + !bAntiAlias, bPixelSnapHairline); + + // prepare next step + aEdge.setB2DPoint(0, aEdge.getB2DPoint(1)); + } + } + } + + // copy and add to buffering mechanism + if (!bPixelSnapHairline /*tdf#124700*/) + { + pSystemDependentData_CairoPath + = rPolyLine.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>( + nSizeMeasure, cr, bNoJoin, bAntiAlias, pStroke); + } + } + + // extract extents + basegfx::B2DRange extents = getClippedStrokeDamage(cr); + // transform also extents (ranges) of damage so they can be correctly redrawn + extents.transform(aDamageMatrix); + + // draw and consume + cairo_stroke(cr); + + releaseCairoContext(cr, false, extents); + + return true; +} + +bool CairoCommon::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth, + tools::Long nHeight, sal_uInt8 nTransparency, bool bAntiAlias) +{ + const bool bHasFill(m_oFillColor.has_value()); + const bool bHasLine(m_oLineColor.has_value()); + + if (!bHasFill && !bHasLine) + return true; + + cairo_t* cr = getCairoContext(false, bAntiAlias); + clipRegion(cr); + + const double fTransparency = nTransparency * (1.0 / 100); + + // To make releaseCairoContext work, use empty extents + basegfx::B2DRange extents; + + if (bHasFill) + { + cairo_rectangle(cr, nX, nY, nWidth, nHeight); + + applyColor(cr, *m_oFillColor, fTransparency); + + // set FillDamage + extents = getClippedFillDamage(cr); + + cairo_fill(cr); + } + + if (bHasLine) + { + // PixelOffset used: Set PixelOffset as linear transformation + // Note: Was missing here - probably not by purpose (?) + cairo_matrix_t aMatrix; + cairo_matrix_init_translate(&aMatrix, 0.5, 0.5); + cairo_set_matrix(cr, &aMatrix); + + cairo_rectangle(cr, nX, nY, nWidth, nHeight); + + applyColor(cr, *m_oLineColor, fTransparency); + + // expand with possible StrokeDamage + basegfx::B2DRange stroke_extents = getClippedStrokeDamage(cr); + stroke_extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5)); + extents.expand(stroke_extents); + + cairo_stroke(cr); + } + + releaseCairoContext(cr, false, extents); + + return true; +} + +bool CairoCommon::drawGradient(const tools::PolyPolygon& rPolyPolygon, const Gradient& rGradient, + bool bAntiAlias) +{ + if (rGradient.GetStyle() != css::awt::GradientStyle_LINEAR + && rGradient.GetStyle() != css::awt::GradientStyle_RADIAL) + return false; // unsupported + if (rGradient.GetSteps() != 0) + return false; // We can't tell cairo how many colors to use in the gradient. + + cairo_t* cr = getCairoContext(true, bAntiAlias); + clipRegion(cr); + + tools::Rectangle aInputRect(rPolyPolygon.GetBoundRect()); + if (rPolyPolygon.IsRect()) + { + // Rect->Polygon conversion loses the right and bottom edge, fix that. + aInputRect.AdjustRight(1); + aInputRect.AdjustBottom(1); + basegfx::B2DHomMatrix rObjectToDevice; + AddPolygonToPath(cr, tools::Polygon(aInputRect).getB2DPolygon(), rObjectToDevice, + !bAntiAlias, false); + } + else + { + basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPolygon.getB2DPolyPolygon()); + for (auto const& rPolygon : std::as_const(aB2DPolyPolygon)) + { + basegfx::B2DHomMatrix rObjectToDevice; + AddPolygonToPath(cr, rPolygon, rObjectToDevice, !bAntiAlias, false); + } + } + + Gradient aGradient(rGradient); + + tools::Rectangle aBoundRect; + Point aCenter; + + aGradient.SetAngle(aGradient.GetAngle() + 2700_deg10); + aGradient.GetBoundRect(aInputRect, aBoundRect, aCenter); + Color aStartColor = aGradient.GetStartColor(); + Color aEndColor = aGradient.GetEndColor(); + + cairo_pattern_t* pattern; + if (rGradient.GetStyle() == css::awt::GradientStyle_LINEAR) + { + tools::Polygon aPoly(aBoundRect); + aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10); + pattern + = cairo_pattern_create_linear(aPoly[0].X(), aPoly[0].Y(), aPoly[1].X(), aPoly[1].Y()); + } + else + { + double radius = std::max(aBoundRect.GetWidth() / 2.0, aBoundRect.GetHeight() / 2.0); + // Move the center a bit to the top-left (the default VCL algorithm is a bit off-center that way, + // cairo is the opposite way). + pattern = cairo_pattern_create_radial(aCenter.X() - 0.5, aCenter.Y() - 0.5, 0, + aCenter.X() - 0.5, aCenter.Y() - 0.5, radius); + std::swap(aStartColor, aEndColor); + } + + cairo_pattern_add_color_stop_rgba( + pattern, aGradient.GetBorder() / 100.0, + aStartColor.GetRed() * aGradient.GetStartIntensity() / 25500.0, + aStartColor.GetGreen() * aGradient.GetStartIntensity() / 25500.0, + aStartColor.GetBlue() * aGradient.GetStartIntensity() / 25500.0, 1.0); + + cairo_pattern_add_color_stop_rgba( + pattern, 1.0, aEndColor.GetRed() * aGradient.GetEndIntensity() / 25500.0, + aEndColor.GetGreen() * aGradient.GetEndIntensity() / 25500.0, + aEndColor.GetBlue() * aGradient.GetEndIntensity() / 25500.0, 1.0); + + cairo_set_source(cr, pattern); + cairo_pattern_destroy(pattern); + + basegfx::B2DRange extents = getClippedFillDamage(cr); + cairo_fill_preserve(cr); + + releaseCairoContext(cr, true, extents); + + return true; +} + +bool CairoCommon::implDrawGradient(basegfx::B2DPolyPolygon const& rPolyPolygon, + SalGradient const& rGradient, bool bAntiAlias) +{ + cairo_t* cr = getCairoContext(true, bAntiAlias); + + basegfx::B2DHomMatrix rObjectToDevice; + + for (auto const& rPolygon : rPolyPolygon) + AddPolygonToPath(cr, rPolygon, rObjectToDevice, !bAntiAlias, false); + + cairo_pattern_t* pattern + = cairo_pattern_create_linear(rGradient.maPoint1.getX(), rGradient.maPoint1.getY(), + rGradient.maPoint2.getX(), rGradient.maPoint2.getY()); + + for (SalGradientStop const& rStop : rGradient.maStops) + { + double r = rStop.maColor.GetRed() / 255.0; + double g = rStop.maColor.GetGreen() / 255.0; + double b = rStop.maColor.GetBlue() / 255.0; + double a = rStop.maColor.GetAlpha() / 255.0; + double offset = rStop.mfOffset; + + cairo_pattern_add_color_stop_rgba(pattern, offset, r, g, b, a); + } + cairo_set_source(cr, pattern); + cairo_pattern_destroy(pattern); + + basegfx::B2DRange extents = getClippedFillDamage(cr); + + cairo_fill_preserve(cr); + + releaseCairoContext(cr, true, extents); + + return true; +} + +namespace +{ +basegfx::B2DRange renderWithOperator(cairo_t* cr, const SalTwoRect& rTR, cairo_surface_t* source, + cairo_operator_t eOperator = CAIRO_OPERATOR_SOURCE) +{ + cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight); + + basegfx::B2DRange extents = getClippedFillDamage(cr); + + cairo_clip(cr); + + cairo_translate(cr, rTR.mnDestX, rTR.mnDestY); + if (rTR.mnSrcWidth != 0 && rTR.mnSrcHeight != 0) + { + double fXScale = static_cast<double>(rTR.mnDestWidth) / rTR.mnSrcWidth; + double fYScale = static_cast<double>(rTR.mnDestHeight) / rTR.mnSrcHeight; + cairo_scale(cr, fXScale, fYScale); + } + + cairo_save(cr); + cairo_set_source_surface(cr, source, -rTR.mnSrcX, -rTR.mnSrcY); + + if (cairo_status(cr) == CAIRO_STATUS_SUCCESS) + { + //tdf#133716 borders of upscaled images should not be blurred + cairo_pattern_t* sourcepattern = cairo_get_source(cr); + cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_PAD); + } + + cairo_set_operator(cr, eOperator); + cairo_paint(cr); + cairo_restore(cr); + + return extents; +} + +} // end anonymous ns + +basegfx::B2DRange CairoCommon::renderSource(cairo_t* cr, const SalTwoRect& rTR, + cairo_surface_t* source) +{ + return renderWithOperator(cr, rTR, source, CAIRO_OPERATOR_SOURCE); +} + +void CairoCommon::copyWithOperator(const SalTwoRect& rTR, cairo_surface_t* source, + cairo_operator_t eOp, bool bAntiAlias) +{ + cairo_t* cr = getCairoContext(false, bAntiAlias); + clipRegion(cr); + + basegfx::B2DRange extents = renderWithOperator(cr, rTR, source, eOp); + + releaseCairoContext(cr, false, extents); +} + +void CairoCommon::copySource(const SalTwoRect& rTR, cairo_surface_t* source, bool bAntiAlias) +{ + copyWithOperator(rTR, source, CAIRO_OPERATOR_SOURCE, bAntiAlias); +} + +void CairoCommon::copyBitsCairo(const SalTwoRect& rTR, cairo_surface_t* pSourceSurface, + bool bAntiAlias) +{ + SalTwoRect aTR(rTR); + + cairo_surface_t* pCopy = nullptr; + + if (pSourceSurface == getSurface()) + { + //self copy is a problem, so dup source in that case + pCopy + = cairo_surface_create_similar(pSourceSurface, cairo_surface_get_content(getSurface()), + aTR.mnSrcWidth * m_fScale, aTR.mnSrcHeight * m_fScale); + dl_cairo_surface_set_device_scale(pCopy, m_fScale, m_fScale); + cairo_t* cr = cairo_create(pCopy); + cairo_set_source_surface(cr, pSourceSurface, -aTR.mnSrcX, -aTR.mnSrcY); + cairo_rectangle(cr, 0, 0, aTR.mnSrcWidth, aTR.mnSrcHeight); + cairo_fill(cr); + cairo_destroy(cr); + + pSourceSurface = pCopy; + + aTR.mnSrcX = 0; + aTR.mnSrcY = 0; + } + + copySource(aTR, pSourceSurface, bAntiAlias); + + if (pCopy) + cairo_surface_destroy(pCopy); +} + +namespace +{ +cairo_pattern_t* create_stipple() +{ + static unsigned char data[16] = { 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF }; + cairo_surface_t* surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_A8, 4, 4, 4); + cairo_pattern_t* pattern = cairo_pattern_create_for_surface(surface); + cairo_surface_destroy(surface); + cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); + cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST); + return pattern; +} +} // end anonymous ns + +void CairoCommon::invert(const basegfx::B2DPolygon& rPoly, SalInvert nFlags, bool bAntiAlias) +{ + cairo_t* cr = getCairoContext(false, bAntiAlias); + clipRegion(cr); + + // To make releaseCairoContext work, use empty extents + basegfx::B2DRange extents; + + AddPolygonToPath(cr, rPoly, basegfx::B2DHomMatrix(), !bAntiAlias, false); + + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + + cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE); + + if (nFlags & SalInvert::TrackFrame) + { + cairo_set_line_width(cr, 2.0); + const double dashLengths[2] = { 4.0, 4.0 }; + cairo_set_dash(cr, dashLengths, 2, 0); + + extents = getClippedStrokeDamage(cr); + //see tdf#106577 under wayland, some pixel droppings seen, maybe we're + //out by one somewhere, or cairo_stroke_extents is confused by + //dashes/line width + if (!extents.isEmpty()) + { + extents.grow(1); + } + + cairo_stroke(cr); + } + else + { + extents = getClippedFillDamage(cr); + + cairo_clip(cr); + + if (nFlags & SalInvert::N50) + { + cairo_pattern_t* pattern = create_stipple(); + cairo_surface_t* surface = cairo_surface_create_similar( + m_pSurface, cairo_surface_get_content(m_pSurface), extents.getWidth() * m_fScale, + extents.getHeight() * m_fScale); + + dl_cairo_surface_set_device_scale(surface, m_fScale, m_fScale); + cairo_t* stipple_cr = cairo_create(surface); + cairo_set_source_rgb(stipple_cr, 1.0, 1.0, 1.0); + cairo_mask(stipple_cr, pattern); + cairo_pattern_destroy(pattern); + cairo_destroy(stipple_cr); + cairo_mask_surface(cr, surface, extents.getMinX(), extents.getMinY()); + cairo_surface_destroy(surface); + } + else + { + cairo_paint(cr); + } + } + + releaseCairoContext(cr, false, extents); +} + +void CairoCommon::invert(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, + SalInvert nFlags, bool bAntiAlias) +{ + basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect( + basegfx::B2DRectangle(nX, nY, nX + nWidth, nY + nHeight)); + + invert(aRect, nFlags, bAntiAlias); +} + +void CairoCommon::invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags, bool bAntiAlias) +{ + basegfx::B2DPolygon aPoly; + aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints); + for (sal_uInt32 i = 1; i < nPoints; ++i) + aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY())); + aPoly.setClosed(true); + + invert(aPoly, nFlags, bAntiAlias); +} + +void CairoCommon::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap, + bool bAntiAlias) +{ + // MM02 try to access buffered BitmapHelper + std::shared_ptr<BitmapHelper> aSurface; + tryToUseSourceBuffer(rSalBitmap, aSurface); + cairo_surface_t* source = aSurface->getSurface(rPosAry.mnDestWidth, rPosAry.mnDestHeight); + + if (!source) + { + SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case"); + return; + } + +#if 0 // LO code is not yet bitmap32-ready. + // if m_bSupportsBitmap32 becomes true for Svp revisit this + copyWithOperator(rPosAry, source, CAIRO_OPERATOR_OVER, bAntiAlias); +#else + copyWithOperator(rPosAry, source, CAIRO_OPERATOR_SOURCE, bAntiAlias); +#endif +} + +bool CairoCommon::drawAlphaBitmap(const SalTwoRect& rTR, const SalBitmap& rSourceBitmap, + const SalBitmap& rAlphaBitmap, bool bAntiAlias) +{ + if (rAlphaBitmap.GetBitCount() != 8 && rAlphaBitmap.GetBitCount() != 1) + { + SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap alpha depth case: " + << rAlphaBitmap.GetBitCount()); + return false; + } + + if (!rTR.mnSrcWidth || !rTR.mnSrcHeight) + { + SAL_WARN("vcl.gdi", "not possible to stretch nothing"); + return true; + } + + // MM02 try to access buffered BitmapHelper + std::shared_ptr<BitmapHelper> aSurface; + tryToUseSourceBuffer(rSourceBitmap, aSurface); + cairo_surface_t* source = aSurface->getSurface(rTR.mnDestWidth, rTR.mnDestHeight); + + if (!source) + { + SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case"); + return false; + } + + // MM02 try to access buffered MaskHelper + std::shared_ptr<MaskHelper> aMask; + tryToUseMaskBuffer(rAlphaBitmap, aMask); + cairo_surface_t* mask = aMask->getSurface(rTR.mnDestWidth, rTR.mnDestHeight); + + if (!mask) + { + SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case"); + return false; + } + + cairo_t* cr = getCairoContext(false, bAntiAlias); + if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) + { + SAL_WARN("vcl.gdi", + "cannot render to surface: " << cairo_status_to_string(cairo_status(cr))); + releaseCairoContext(cr, false, basegfx::B2DRange()); + return true; + } + + clipRegion(cr); + + cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight); + + basegfx::B2DRange extents = getClippedFillDamage(cr); + + cairo_clip(cr); + + cairo_pattern_t* maskpattern = cairo_pattern_create_for_surface(mask); + cairo_translate(cr, rTR.mnDestX, rTR.mnDestY); + double fXScale = static_cast<double>(rTR.mnDestWidth) / rTR.mnSrcWidth; + double fYScale = static_cast<double>(rTR.mnDestHeight) / rTR.mnSrcHeight; + cairo_scale(cr, fXScale, fYScale); + cairo_set_source_surface(cr, source, -rTR.mnSrcX, -rTR.mnSrcY); + + cairo_pattern_t* sourcepattern = cairo_get_source(cr); + + //tdf#133716 borders of upscaled images should not be blurred + //tdf#114117 when stretching a single or multi pixel width/height source to fit an area + //the image will be extended into that size. + cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_PAD); + cairo_pattern_set_extend(maskpattern, CAIRO_EXTEND_PAD); + + //this block is just "cairo_mask_surface", but we have to make it explicit + //because of the cairo_pattern_set_filter etc we may want applied + cairo_matrix_t matrix; + cairo_matrix_init_translate(&matrix, rTR.mnSrcX, rTR.mnSrcY); + cairo_pattern_set_matrix(maskpattern, &matrix); + cairo_mask(cr, maskpattern); + + cairo_pattern_destroy(maskpattern); + + releaseCairoContext(cr, false, extents); + + return true; +} + +bool CairoCommon::drawTransformedBitmap(const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY, const SalBitmap& rSourceBitmap, + const SalBitmap* pAlphaBitmap, double fAlpha, + bool bAntiAlias) +{ + if (pAlphaBitmap && pAlphaBitmap->GetBitCount() != 8 && pAlphaBitmap->GetBitCount() != 1) + { + SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap alpha depth case: " + << pAlphaBitmap->GetBitCount()); + return false; + } + + if (fAlpha != 1.0) + return false; + + // MM02 try to access buffered BitmapHelper + std::shared_ptr<BitmapHelper> aSurface; + tryToUseSourceBuffer(rSourceBitmap, aSurface); + const tools::Long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength())); + const tools::Long nDestHeight(basegfx::fround(basegfx::B2DVector(rY - rNull).getLength())); + cairo_surface_t* source(aSurface->getSurface(nDestWidth, nDestHeight)); + + if (!source) + { + SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case"); + return false; + } + + // MM02 try to access buffered MaskHelper + std::shared_ptr<MaskHelper> aMask; + if (nullptr != pAlphaBitmap) + { + tryToUseMaskBuffer(*pAlphaBitmap, aMask); + } + + // access cairo_surface_t from MaskHelper + cairo_surface_t* mask(nullptr); + if (aMask) + { + mask = aMask->getSurface(nDestWidth, nDestHeight); + } + + if (nullptr != pAlphaBitmap && nullptr == mask) + { + SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case"); + return false; + } + + const Size aSize = rSourceBitmap.GetSize(); + cairo_t* cr = getCairoContext(false, bAntiAlias); + clipRegion(cr); + + // setup the image transformation + // using the rNull,rX,rY points as destinations for the (0,0),(0,Width),(Height,0) source points + const basegfx::B2DVector aXRel = rX - rNull; + const basegfx::B2DVector aYRel = rY - rNull; + cairo_matrix_t matrix; + cairo_matrix_init(&matrix, aXRel.getX() / aSize.Width(), aXRel.getY() / aSize.Width(), + aYRel.getX() / aSize.Height(), aYRel.getY() / aSize.Height(), rNull.getX(), + rNull.getY()); + + cairo_transform(cr, &matrix); + + cairo_rectangle(cr, 0, 0, aSize.Width(), aSize.Height()); + basegfx::B2DRange extents = getClippedFillDamage(cr); + cairo_clip(cr); + + cairo_set_source_surface(cr, source, 0, 0); + if (mask) + cairo_mask_surface(cr, mask, 0, 0); + else + cairo_paint(cr); + + releaseCairoContext(cr, false, extents); + + return true; +} + +void CairoCommon::drawMask(const SalTwoRect& rTR, const SalBitmap& rSalBitmap, Color nMaskColor, + bool bAntiAlias) +{ + /** creates an image from the given rectangle, replacing all black pixels + * with nMaskColor and make all other full transparent */ + // MM02 here decided *against* using buffered BitmapHelper + // because the data gets somehow 'unmuliplied'. This may also be + // done just once, but I am not sure if this is safe to do. + // So for now dispense re-using data here. + BitmapHelper aSurface(rSalBitmap, true); // The mask is argb32 + if (!aSurface.getSurface()) + { + SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawMask case"); + return; + } + sal_Int32 nStride; + unsigned char* mask_data = aSurface.getBits(nStride); +#if !ENABLE_WASM_STRIP_PREMULTIPLY + vcl::bitmap::lookup_table const& unpremultiply_table = vcl::bitmap::get_unpremultiply_table(); +#endif + for (tools::Long y = rTR.mnSrcY; y < rTR.mnSrcY + rTR.mnSrcHeight; ++y) + { + unsigned char* row = mask_data + (nStride * y); + unsigned char* data = row + (rTR.mnSrcX * 4); + for (tools::Long x = rTR.mnSrcX; x < rTR.mnSrcX + rTR.mnSrcWidth; ++x) + { + sal_uInt8 a = data[SVP_CAIRO_ALPHA]; +#if ENABLE_WASM_STRIP_PREMULTIPLY + sal_uInt8 b = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_BLUE]); + sal_uInt8 g = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_GREEN]); + sal_uInt8 r = vcl::bitmap::unpremultiply(a, data[SVP_CAIRO_RED]); +#else + sal_uInt8 b = unpremultiply_table[a][data[SVP_CAIRO_BLUE]]; + sal_uInt8 g = unpremultiply_table[a][data[SVP_CAIRO_GREEN]]; + sal_uInt8 r = unpremultiply_table[a][data[SVP_CAIRO_RED]]; +#endif + if (r == 0 && g == 0 && b == 0) + { + data[0] = nMaskColor.GetBlue(); + data[1] = nMaskColor.GetGreen(); + data[2] = nMaskColor.GetRed(); + data[3] = 0xff; + } + else + { + data[0] = 0; + data[1] = 0; + data[2] = 0; + data[3] = 0; + } + data += 4; + } + } + aSurface.mark_dirty(); + + cairo_t* cr = getCairoContext(false, bAntiAlias); + clipRegion(cr); + + cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight); + + basegfx::B2DRange extents = getClippedFillDamage(cr); + + cairo_clip(cr); + + cairo_translate(cr, rTR.mnDestX, rTR.mnDestY); + double fXScale = static_cast<double>(rTR.mnDestWidth) / rTR.mnSrcWidth; + double fYScale = static_cast<double>(rTR.mnDestHeight) / rTR.mnSrcHeight; + cairo_scale(cr, fXScale, fYScale); + cairo_set_source_surface(cr, aSurface.getSurface(), -rTR.mnSrcX, -rTR.mnSrcY); + + if (cairo_status(cr) == CAIRO_STATUS_SUCCESS) + { + //tdf#133716 borders of upscaled images should not be blurred + cairo_pattern_t* sourcepattern = cairo_get_source(cr); + cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_PAD); + } + + cairo_paint(cr); + + releaseCairoContext(cr, false, extents); +} + +std::shared_ptr<SalBitmap> CairoCommon::getBitmap(tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight) +{ + std::shared_ptr<SvpSalBitmap> pBitmap = std::make_shared<SvpSalBitmap>(); + BitmapPalette aPal; + assert(GetBitCount() != 1 && "not supported anymore"); + vcl::PixelFormat ePixelFormat = vcl::PixelFormat::N32_BPP; + + if (!pBitmap->ImplCreate(Size(nWidth, nHeight), ePixelFormat, aPal, false)) + { + SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create bitmap"); + return nullptr; + } + + cairo_surface_t* target = CairoCommon::createCairoSurface(pBitmap->GetBuffer()); + if (!target) + { + SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create cairo surface"); + return nullptr; + } + cairo_t* cr = cairo_create(target); + + SalTwoRect aTR(nX, nY, nWidth, nHeight, 0, 0, nWidth, nHeight); + CairoCommon::renderSource(cr, aTR, m_pSurface); + + cairo_destroy(cr); + cairo_surface_destroy(target); + + return pBitmap; +} + +cairo_format_t getCairoFormat(const BitmapBuffer& rBuffer) +{ + cairo_format_t nFormat; +#ifdef HAVE_CAIRO_FORMAT_RGB24_888 + assert(rBuffer.mnBitCount == 32 || rBuffer.mnBitCount == 24 || rBuffer.mnBitCount == 1); +#else + assert(rBuffer.mnBitCount == 32 || rBuffer.mnBitCount == 1); +#endif + + if (rBuffer.mnBitCount == 32) + nFormat = CAIRO_FORMAT_ARGB32; +#ifdef HAVE_CAIRO_FORMAT_RGB24_888 + else if (rBuffer.mnBitCount == 24) + nFormat = CAIRO_FORMAT_RGB24_888; +#endif + else + nFormat = CAIRO_FORMAT_A1; + return nFormat; +} + +namespace +{ +bool isCairoCompatible(const BitmapBuffer* pBuffer) +{ + if (!pBuffer) + return false; + + // We use Cairo that supports 24-bit RGB. +#ifdef HAVE_CAIRO_FORMAT_RGB24_888 + if (pBuffer->mnBitCount != 32 && pBuffer->mnBitCount != 24 && pBuffer->mnBitCount != 1) +#else + if (pBuffer->mnBitCount != 32 && pBuffer->mnBitCount != 1) +#endif + return false; + + cairo_format_t nFormat = getCairoFormat(*pBuffer); + return (cairo_format_stride_for_width(nFormat, pBuffer->mnWidth) == pBuffer->mnScanlineSize); +} +} + +cairo_surface_t* CairoCommon::createCairoSurface(const BitmapBuffer* pBuffer) +{ + if (!isCairoCompatible(pBuffer)) + return nullptr; + + cairo_format_t nFormat = getCairoFormat(*pBuffer); + cairo_surface_t* target = cairo_image_surface_create_for_data( + pBuffer->mpBits, nFormat, pBuffer->mnWidth, pBuffer->mnHeight, pBuffer->mnScanlineSize); + if (cairo_surface_status(target) != CAIRO_STATUS_SUCCESS) + { + cairo_surface_destroy(target); + return nullptr; + } + return target; +} + +bool CairoCommon::hasFastDrawTransformedBitmap() { return false; } + +bool CairoCommon::supportsOperation(OutDevSupportType eType) +{ + switch (eType) + { + case OutDevSupportType::TransparentRect: + case OutDevSupportType::TransparentText: + return true; + } + return false; +} + +std::optional<BitmapBuffer> FastConvert24BitRgbTo32BitCairo(const BitmapBuffer* pSrc) +{ + if (pSrc == nullptr) + return std::nullopt; + + assert(pSrc->mnFormat == SVP_24BIT_FORMAT); + const tools::Long nWidth = pSrc->mnWidth; + const tools::Long nHeight = pSrc->mnHeight; + std::optional<BitmapBuffer> pDst(std::in_place); + pDst->mnFormat = (ScanlineFormat::N32BitTcArgb | ScanlineFormat::TopDown); + pDst->mnWidth = nWidth; + pDst->mnHeight = nHeight; + pDst->mnBitCount = 32; + pDst->maColorMask = pSrc->maColorMask; + pDst->maPalette = pSrc->maPalette; + + tools::Long nScanlineBase; + const bool bFail = o3tl::checked_multiply<tools::Long>(pDst->mnBitCount, nWidth, nScanlineBase); + if (bFail) + { + SAL_WARN("vcl.gdi", "checked multiply failed"); + pDst->mpBits = nullptr; + return std::nullopt; + } + + pDst->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase); + if (pDst->mnScanlineSize < nScanlineBase / 8) + { + SAL_WARN("vcl.gdi", "scanline calculation wraparound"); + pDst->mpBits = nullptr; + return std::nullopt; + } + + try + { + pDst->mpBits = new sal_uInt8[pDst->mnScanlineSize * nHeight]; + } + catch (const std::bad_alloc&) + { + // memory exception, clean up + pDst->mpBits = nullptr; + return std::nullopt; + } + + for (tools::Long y = 0; y < nHeight; ++y) + { + sal_uInt8* pS = pSrc->mpBits + y * pSrc->mnScanlineSize; + sal_uInt8* pD = pDst->mpBits + y * pDst->mnScanlineSize; + for (tools::Long x = 0; x < nWidth; ++x) + { +#if ENABLE_CAIRO_RGBA + static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown) + == ScanlineFormat::N32BitTcRgba, + "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra"); + static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown) + == ScanlineFormat::N24BitTcRgb, + "Expected SVP_24BIT_FORMAT set to N24BitTcRgb"); + pD[0] = pS[0]; + pD[1] = pS[1]; + pD[2] = pS[2]; + pD[3] = 0xff; // Alpha +#elif defined OSL_BIGENDIAN + static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown) + == ScanlineFormat::N32BitTcArgb, + "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra"); + static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown) + == ScanlineFormat::N24BitTcRgb, + "Expected SVP_24BIT_FORMAT set to N24BitTcRgb"); + pD[0] = 0xff; // Alpha + pD[1] = pS[0]; + pD[2] = pS[1]; + pD[3] = pS[2]; +#else + static_assert((SVP_CAIRO_FORMAT & ~ScanlineFormat::TopDown) + == ScanlineFormat::N32BitTcBgra, + "Expected SVP_CAIRO_FORMAT set to N32BitTcBgra"); + static_assert((SVP_24BIT_FORMAT & ~ScanlineFormat::TopDown) + == ScanlineFormat::N24BitTcBgr, + "Expected SVP_24BIT_FORMAT set to N24BitTcBgr"); + pD[0] = pS[0]; + pD[1] = pS[1]; + pD[2] = pS[2]; + pD[3] = 0xff; // Alpha +#endif + + pS += 3; + pD += 4; + } + } + + return pDst; +} + +namespace +{ +// check for env var that decides for using downscale pattern +const char* pDisableDownScale(getenv("SAL_DISABLE_CAIRO_DOWNSCALE")); +bool bDisableDownScale(nullptr != pDisableDownScale); +} + +cairo_surface_t* SurfaceHelper::implCreateOrReuseDownscale(unsigned long nTargetWidth, + unsigned long nTargetHeight) +{ + const unsigned long nSourceWidth(cairo_image_surface_get_width(pSurface)); + const unsigned long nSourceHeight(cairo_image_surface_get_height(pSurface)); + + // zoomed in, need to stretch at paint, no pre-scale useful + if (nTargetWidth >= nSourceWidth || nTargetHeight >= nSourceHeight) + { + return pSurface; + } + + // calculate downscale factor + unsigned long nWFactor(1); + unsigned long nW((nSourceWidth + 1) / 2); + unsigned long nHFactor(1); + unsigned long nH((nSourceHeight + 1) / 2); + + while (nW > nTargetWidth && nW > 1) + { + nW = (nW + 1) / 2; + nWFactor *= 2; + } + + while (nH > nTargetHeight && nH > 1) + { + nH = (nH + 1) / 2; + nHFactor *= 2; + } + + if (1 == nWFactor && 1 == nHFactor) + { + // original size *is* best binary size, use it + return pSurface; + } + + // go up one scale again - look for no change + nW = (1 == nWFactor) ? nTargetWidth : nW * 2; + nH = (1 == nHFactor) ? nTargetHeight : nH * 2; + + // check if we have a downscaled version of required size + // bail out if the multiplication for the key would overflow + if (nW >= SAL_MAX_UINT32 || nH >= SAL_MAX_UINT32) + return pSurface; + const sal_uInt64 key((nW * static_cast<sal_uInt64>(SAL_MAX_UINT32)) + nH); + auto isHit(maDownscaled.find(key)); + + if (isHit != maDownscaled.end()) + { + return isHit->second; + } + + // create new surface in the targeted size + cairo_surface_t* pSurfaceTarget + = cairo_surface_create_similar(pSurface, cairo_surface_get_content(pSurface), nW, nH); + + // made a version to scale self first that worked well, but would've + // been hard to support CAIRO_FORMAT_A1 including bit shifting, so + // I decided to go with cairo itself - use CAIRO_FILTER_FAST or + // CAIRO_FILTER_GOOD though. Please modify as needed for + // performance/quality + cairo_t* cr = cairo_create(pSurfaceTarget); + const double fScaleX(static_cast<double>(nW) / static_cast<double>(nSourceWidth)); + const double fScaleY(static_cast<double>(nH) / static_cast<double>(nSourceHeight)); + cairo_scale(cr, fScaleX, fScaleY); + cairo_set_source_surface(cr, pSurface, 0.0, 0.0); + cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_GOOD); + cairo_paint(cr); + cairo_destroy(cr); + + // need to set device_scale for downscale surfaces to get + // them handled correctly + cairo_surface_set_device_scale(pSurfaceTarget, fScaleX, fScaleY); + + // add entry to cached entries + maDownscaled[key] = pSurfaceTarget; + + return pSurfaceTarget; +} + +bool SurfaceHelper::isTrivial() const +{ + constexpr unsigned long nMinimalSquareSizeToBuffer(64 * 64); + const unsigned long nSourceWidth(cairo_image_surface_get_width(pSurface)); + const unsigned long nSourceHeight(cairo_image_surface_get_height(pSurface)); + + return nSourceWidth * nSourceHeight < nMinimalSquareSizeToBuffer; +} + +SurfaceHelper::SurfaceHelper() + : pSurface(nullptr) +{ +} + +SurfaceHelper::~SurfaceHelper() +{ + cairo_surface_destroy(pSurface); + for (auto& candidate : maDownscaled) + { + cairo_surface_destroy(candidate.second); + } +} + +cairo_surface_t* SurfaceHelper::getSurface(unsigned long nTargetWidth, + unsigned long nTargetHeight) const +{ + if (bDisableDownScale || 0 == nTargetWidth || 0 == nTargetHeight || !pSurface || isTrivial()) + { + // caller asks for original or disabled or trivial (smaller then a minimal square size) + // also excludes zero cases for width/height after this point if need to prescale + return pSurface; + } + + return const_cast<SurfaceHelper*>(this)->implCreateOrReuseDownscale(nTargetWidth, + nTargetHeight); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/SvpGraphicsBackend.cxx b/vcl/headless/SvpGraphicsBackend.cxx new file mode 100644 index 0000000000..e8d582b98f --- /dev/null +++ b/vcl/headless/SvpGraphicsBackend.cxx @@ -0,0 +1,287 @@ +/* -*- 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 <headless/SvpGraphicsBackend.hxx> + +#include <sal/log.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <vcl/BitmapTools.hxx> +#include <headless/BitmapHelper.hxx> + +SvpGraphicsBackend::SvpGraphicsBackend(CairoCommon& rCairoCommon) + : m_rCairoCommon(rCairoCommon) +{ +} + +void SvpGraphicsBackend::Init() {} + +void SvpGraphicsBackend::freeResources() {} + +void SvpGraphicsBackend::setClipRegion(const vcl::Region& i_rClip) +{ + m_rCairoCommon.m_aClipRegion = i_rClip; +} + +void SvpGraphicsBackend::ResetClipRegion() { m_rCairoCommon.m_aClipRegion.SetNull(); } + +sal_uInt16 SvpGraphicsBackend::GetBitCount() const { return m_rCairoCommon.GetBitCount(); } + +tools::Long SvpGraphicsBackend::GetGraphicsWidth() const +{ + return m_rCairoCommon.m_pSurface ? m_rCairoCommon.m_aFrameSize.getX() : 0; +} + +void SvpGraphicsBackend::SetLineColor() { m_rCairoCommon.m_oLineColor = std::nullopt; } + +void SvpGraphicsBackend::SetLineColor(Color nColor) { m_rCairoCommon.m_oLineColor = nColor; } + +void SvpGraphicsBackend::SetFillColor() { m_rCairoCommon.m_oFillColor = std::nullopt; } + +void SvpGraphicsBackend::SetFillColor(Color nColor) { m_rCairoCommon.m_oFillColor = nColor; } + +void SvpGraphicsBackend::SetXORMode(bool bSet, bool bInvertOnly) +{ + m_rCairoCommon.SetXORMode(bSet, bInvertOnly); +} + +void SvpGraphicsBackend::SetROPLineColor(SalROPColor nROPColor) +{ + m_rCairoCommon.SetROPLineColor(nROPColor); +} + +void SvpGraphicsBackend::SetROPFillColor(SalROPColor nROPColor) +{ + m_rCairoCommon.SetROPFillColor(nROPColor); +} + +void SvpGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY) +{ + m_rCairoCommon.drawPixel(m_rCairoCommon.m_oLineColor, nX, nY, getAntiAlias()); +} + +void SvpGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY, Color aColor) +{ + m_rCairoCommon.drawPixel(aColor, nX, nY, getAntiAlias()); +} + +void SvpGraphicsBackend::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2, + tools::Long nY2) +{ + m_rCairoCommon.drawLine(nX1, nY1, nX2, nY2, getAntiAlias()); +} + +void SvpGraphicsBackend::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth, + tools::Long nHeight) +{ + m_rCairoCommon.drawRect(nX, nY, nWidth, nHeight, getAntiAlias()); +} + +void SvpGraphicsBackend::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry) +{ + m_rCairoCommon.drawPolyLine(nPoints, pPtAry, getAntiAlias()); +} + +void SvpGraphicsBackend::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry) +{ + m_rCairoCommon.drawPolygon(nPoints, pPtAry, getAntiAlias()); +} + +void SvpGraphicsBackend::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPointCounts, + const Point** pPtAry) +{ + m_rCairoCommon.drawPolyPolygon(nPoly, pPointCounts, pPtAry, getAntiAlias()); +} + +void SvpGraphicsBackend::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fTransparency) +{ + m_rCairoCommon.drawPolyPolygon(rObjectToDevice, rPolyPolygon, fTransparency, getAntiAlias()); +} + +bool SvpGraphicsBackend::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolyLine, double fTransparency, + double fLineWidth, const std::vector<double>* pStroke, + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, double fMiterMinimumAngle, + bool bPixelSnapHairline) +{ + return m_rCairoCommon.drawPolyLine(rObjectToDevice, rPolyLine, fTransparency, fLineWidth, + pStroke, eLineJoin, eLineCap, fMiterMinimumAngle, + bPixelSnapHairline, getAntiAlias()); +} + +bool SvpGraphicsBackend::drawPolyLineBezier(sal_uInt32, const Point*, const PolyFlags*) +{ + SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyLineBezier case"); + return false; +} + +bool SvpGraphicsBackend::drawPolygonBezier(sal_uInt32, const Point*, const PolyFlags*) +{ + SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolygonBezier case"); + return false; +} + +bool SvpGraphicsBackend::drawPolyPolygonBezier(sal_uInt32, const sal_uInt32*, const Point* const*, + const PolyFlags* const*) +{ + SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyPolygonBezier case"); + return false; +} + +void SvpGraphicsBackend::copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX, + tools::Long nSrcY, tools::Long nSrcWidth, tools::Long nSrcHeight, + bool /*bWindowInvalidate*/) +{ + SalTwoRect aTR(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight); + + cairo_surface_t* source = m_rCairoCommon.m_pSurface; + m_rCairoCommon.copyBitsCairo(aTR, source, getAntiAlias()); +} + +void SvpGraphicsBackend::copyBits(const SalTwoRect& rTR, SalGraphics* pSrcGraphics) +{ + cairo_surface_t* source = nullptr; + + if (pSrcGraphics) + { + SvpGraphicsBackend* pSrc = static_cast<SvpGraphicsBackend*>(pSrcGraphics->GetImpl()); + source = pSrc->m_rCairoCommon.m_pSurface; + } + else + { + source = m_rCairoCommon.m_pSurface; + } + + m_rCairoCommon.copyBitsCairo(rTR, source, getAntiAlias()); +} + +void SvpGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap) +{ + m_rCairoCommon.drawBitmap(rPosAry, rSalBitmap, getAntiAlias()); +} + +void SvpGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap, + const SalBitmap& rTransparentBitmap) +{ + drawAlphaBitmap(rPosAry, rSalBitmap, rTransparentBitmap); +} + +void SvpGraphicsBackend::drawMask(const SalTwoRect& rTR, const SalBitmap& rSalBitmap, + Color nMaskColor) +{ + m_rCairoCommon.drawMask(rTR, rSalBitmap, nMaskColor, getAntiAlias()); +} + +std::shared_ptr<SalBitmap> SvpGraphicsBackend::getBitmap(tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight) +{ + return m_rCairoCommon.getBitmap(nX, nY, nWidth, nHeight); +} + +void SvpGraphicsBackend::drawBitmapBuffer(const SalTwoRect& rTR, const BitmapBuffer* pBuffer, + cairo_operator_t eOp) +{ + cairo_surface_t* source = CairoCommon::createCairoSurface(pBuffer); + m_rCairoCommon.copyWithOperator(rTR, source, eOp, getAntiAlias()); + cairo_surface_destroy(source); +} + +Color SvpGraphicsBackend::getPixel(tools::Long nX, tools::Long nY) +{ + return CairoCommon::getPixel(m_rCairoCommon.m_pSurface, nX, nY); +} + +void SvpGraphicsBackend::invert(tools::Long nX, tools::Long nY, tools::Long nWidth, + tools::Long nHeight, SalInvert nFlags) +{ + m_rCairoCommon.invert(nX, nY, nWidth, nHeight, nFlags, getAntiAlias()); +} + +void SvpGraphicsBackend::invert(sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags) +{ + m_rCairoCommon.invert(nPoints, pPtAry, nFlags, getAntiAlias()); +} + +bool SvpGraphicsBackend::drawEPS(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/, + tools::Long /*nHeight*/, void* /*pPtr*/, sal_uInt32 /*nSize*/) +{ + return false; +} + +bool SvpGraphicsBackend::blendBitmap(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rBitmap*/) +{ + return false; +} + +bool SvpGraphicsBackend::blendAlphaBitmap(const SalTwoRect& /*rPosAry*/, + const SalBitmap& /*rSrcBitmap*/, + const SalBitmap& /*rMaskBitmap*/, + const SalBitmap& /*rAlphaBitmap*/) +{ + return false; +} + +bool SvpGraphicsBackend::drawAlphaBitmap(const SalTwoRect& rTR, const SalBitmap& rSourceBitmap, + const SalBitmap& rAlphaBitmap) +{ + return m_rCairoCommon.drawAlphaBitmap(rTR, rSourceBitmap, rAlphaBitmap, getAntiAlias()); +} + +bool SvpGraphicsBackend::drawTransformedBitmap(const basegfx::B2DPoint& rNull, + const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY, + const SalBitmap& rSourceBitmap, + const SalBitmap* pAlphaBitmap, double fAlpha) +{ + return m_rCairoCommon.drawTransformedBitmap(rNull, rX, rY, rSourceBitmap, pAlphaBitmap, fAlpha, + getAntiAlias()); +} + +bool SvpGraphicsBackend::hasFastDrawTransformedBitmap() const +{ + return CairoCommon::hasFastDrawTransformedBitmap(); +} + +bool SvpGraphicsBackend::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth, + tools::Long nHeight, sal_uInt8 nTransparency) +{ + return m_rCairoCommon.drawAlphaRect(nX, nY, nWidth, nHeight, nTransparency, getAntiAlias()); +} + +bool SvpGraphicsBackend::drawGradient(const tools::PolyPolygon& rPolyPolygon, + const Gradient& rGradient) +{ + return m_rCairoCommon.drawGradient(rPolyPolygon, rGradient, getAntiAlias()); +} + +bool SvpGraphicsBackend::implDrawGradient(basegfx::B2DPolyPolygon const& rPolyPolygon, + SalGradient const& rGradient) +{ + return m_rCairoCommon.implDrawGradient(rPolyPolygon, rGradient, getAntiAlias()); +} + +bool SvpGraphicsBackend::supportsOperation(OutDevSupportType eType) const +{ + return CairoCommon::supportsOperation(eType); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/headlessinst.cxx b/vcl/headless/headlessinst.cxx new file mode 100644 index 0000000000..abe3e1cf92 --- /dev/null +++ b/vcl/headless/headlessinst.cxx @@ -0,0 +1,55 @@ +/* -*- 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/. + */ +#include <headless/svpinst.hxx> +#include <headless/svpdummies.hxx> +#include <headless/svpdata.hxx> +#include <unistd.h> + +class HeadlessSalInstance : public SvpSalInstance +{ +public: + explicit HeadlessSalInstance(std::unique_ptr<SalYieldMutex> pMutex); + + virtual SalSystem* CreateSalSystem() override; +}; + +HeadlessSalInstance::HeadlessSalInstance(std::unique_ptr<SalYieldMutex> pMutex) + : SvpSalInstance(std::move(pMutex)) +{ +} + +class HeadlessSalSystem : public SvpSalSystem { +public: + HeadlessSalSystem() : SvpSalSystem() {} + virtual int ShowNativeDialog( const OUString& rTitle, + const OUString& rMessage, + const std::vector< OUString >& rButtons ) override + { + (void)rButtons; + SAL_INFO("vcl.headless", + "LibreOffice - dialog '" + << rTitle << "': '" + << rMessage << "'"); + return 0; + } +}; + +SalSystem *HeadlessSalInstance::CreateSalSystem() +{ + return new HeadlessSalSystem(); +} + +extern "C" SalInstance *create_SalInstance() +{ + HeadlessSalInstance* pInstance = new HeadlessSalInstance(std::make_unique<SvpSalYieldMutex>()); + new SvpSalData(); + return pInstance; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/svpbmp.cxx b/vcl/headless/svpbmp.cxx new file mode 100644 index 0000000000..178ea129c6 --- /dev/null +++ b/vcl/headless/svpbmp.cxx @@ -0,0 +1,272 @@ +/* -*- 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 <cstring> + +#include <headless/svpbmp.hxx> +#include <headless/svpgdi.hxx> +#include <headless/svpinst.hxx> + +#include <basegfx/vector/b2ivector.hxx> +#include <basegfx/range/b2ibox.hxx> +#include <o3tl/safeint.hxx> +#include <tools/helpers.hxx> +#include <vcl/bitmap.hxx> + +using namespace basegfx; + +SvpSalBitmap::SvpSalBitmap() +{ +} + +SvpSalBitmap::~SvpSalBitmap() +{ + Destroy(); +} + +static std::optional<BitmapBuffer> ImplCreateDIB( + const Size& rSize, + vcl::PixelFormat ePixelFormat, + const BitmapPalette& rPal, + bool bClear) +{ + if (!rSize.Width() || !rSize.Height()) + return std::nullopt; + + std::optional<BitmapBuffer> pDIB(std::in_place); + + switch (ePixelFormat) + { + case vcl::PixelFormat::N8_BPP: + pDIB->mnFormat = ScanlineFormat::N8BitPal; + break; + case vcl::PixelFormat::N24_BPP: + pDIB->mnFormat = SVP_24BIT_FORMAT; + break; + case vcl::PixelFormat::N32_BPP: + pDIB->mnFormat = SVP_CAIRO_FORMAT; + break; + case vcl::PixelFormat::INVALID: + assert(false); + pDIB->mnFormat = SVP_CAIRO_FORMAT; + break; + } + + sal_uInt16 nColors = 0; + if (ePixelFormat <= vcl::PixelFormat::N8_BPP) + nColors = vcl::numberOfColors(ePixelFormat); + + pDIB->mnFormat |= ScanlineFormat::TopDown; + pDIB->mnWidth = rSize.Width(); + pDIB->mnHeight = rSize.Height(); + tools::Long nScanlineBase; + bool bFail = o3tl::checked_multiply<tools::Long>(pDIB->mnWidth, vcl::pixelFormatBitCount(ePixelFormat), nScanlineBase); + if (bFail) + { + SAL_WARN("vcl.gdi", "checked multiply failed"); + return std::nullopt; + } + pDIB->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase); + if (pDIB->mnScanlineSize < nScanlineBase/8) + { + SAL_WARN("vcl.gdi", "scanline calculation wraparound"); + return std::nullopt; + } + pDIB->mnBitCount = vcl::pixelFormatBitCount(ePixelFormat); + + if (nColors) + { + pDIB->maPalette = rPal; + pDIB->maPalette.SetEntryCount( nColors ); + } + + size_t size; + bFail = o3tl::checked_multiply<size_t>(pDIB->mnHeight, pDIB->mnScanlineSize, size); + SAL_WARN_IF(bFail, "vcl.gdi", "checked multiply failed"); + if (bFail || size > SAL_MAX_INT32/2) + { + return std::nullopt; + } + + try + { + pDIB->mpBits = new sal_uInt8[size]; +#ifdef __SANITIZE_ADDRESS__ + if (!pDIB->mpBits) + { // can only happen with ASAN allocator_may_return_null=1 + pDIB.reset(); + } + else +#endif + if (bClear) + { + std::memset(pDIB->mpBits, 0, size); + } + } + catch (const std::bad_alloc&) + { + pDIB.reset(); + } + + return pDIB; +} + +void SvpSalBitmap::Create(const std::optional<BitmapBuffer>& pBuf) +{ + Destroy(); + moDIB = pBuf; +} + +bool SvpSalBitmap::ImplCreate(const Size& rSize, vcl::PixelFormat ePixelFormat, + const BitmapPalette& rPal, bool bClear) +{ + Destroy(); + moDIB = ImplCreateDIB(rSize, ePixelFormat, rPal, bClear); + return moDIB.has_value(); +} + +bool SvpSalBitmap::Create(const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rPal) +{ + return ImplCreate(rSize, ePixelFormat, rPal, true); +} + +bool SvpSalBitmap::Create(const SalBitmap& rBmp) +{ + Destroy(); + + const SvpSalBitmap& rSalBmp = static_cast<const SvpSalBitmap&>(rBmp); + + if (rSalBmp.moDIB) + { + // TODO: reference counting... + moDIB.emplace( *rSalBmp.moDIB ); + + const size_t size = moDIB->mnScanlineSize * moDIB->mnHeight; + if (size > SAL_MAX_INT32/2) + { + moDIB.reset(); + return false; + } + + // TODO: get rid of this when BitmapBuffer gets copy constructor + try + { + moDIB->mpBits = new sal_uInt8[size]; + std::memcpy(moDIB->mpBits, rSalBmp.moDIB->mpBits, size); + } + catch (const std::bad_alloc&) + { + moDIB.reset(); + } + } + + return !rSalBmp.moDIB.has_value() || moDIB.has_value(); +} + +bool SvpSalBitmap::Create( const SalBitmap& /*rSalBmp*/, + SalGraphics* /*pGraphics*/ ) +{ + return false; +} + +bool SvpSalBitmap::Create(const SalBitmap& /*rSalBmp*/, + vcl::PixelFormat /*eNewPixelFormat*/) +{ + return false; +} + +bool SvpSalBitmap::Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& /*xBitmapCanvas*/, Size& /*rSize*/, bool /*bMask*/ ) +{ + return false; +} + +void SvpSalBitmap::Destroy() +{ + if (moDIB.has_value()) + { + delete[] moDIB->mpBits; + moDIB.reset(); + } +} + +Size SvpSalBitmap::GetSize() const +{ + Size aSize; + + if (moDIB.has_value()) + { + aSize.setWidth( moDIB->mnWidth ); + aSize.setHeight( moDIB->mnHeight ); + } + + return aSize; +} + +sal_uInt16 SvpSalBitmap::GetBitCount() const +{ + sal_uInt16 nBitCount; + + if (moDIB.has_value()) + nBitCount = moDIB->mnBitCount; + else + nBitCount = 0; + + return nBitCount; +} + +BitmapBuffer* SvpSalBitmap::AcquireBuffer(BitmapAccessMode) +{ + return moDIB ? &*moDIB : nullptr; +} + +void SvpSalBitmap::ReleaseBuffer(BitmapBuffer*, BitmapAccessMode nMode) +{ + if( nMode == BitmapAccessMode::Write ) + InvalidateChecksum(); +} + +bool SvpSalBitmap::GetSystemData( BitmapSystemData& ) +{ + return false; +} + +bool SvpSalBitmap::ScalingSupported() const +{ + return false; +} + +bool SvpSalBitmap::Scale( const double& /*rScaleX*/, const double& /*rScaleY*/, BmpScaleFlag /*nScaleFlag*/ ) +{ + return false; +} + +bool SvpSalBitmap::Replace( const ::Color& /*rSearchColor*/, const ::Color& /*rReplaceColor*/, sal_uInt8 /*nTol*/ ) +{ + return false; +} + +const basegfx::SystemDependentDataHolder* SvpSalBitmap::accessSystemDependentDataHolder() const +{ + return this; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/svpdummies.cxx b/vcl/headless/svpdummies.cxx new file mode 100644 index 0000000000..944d494976 --- /dev/null +++ b/vcl/headless/svpdummies.cxx @@ -0,0 +1,58 @@ +/* -*- 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 <rtl/ustrbuf.hxx> +#include <headless/svpdummies.hxx> +#include <headless/svpinst.hxx> + +SvpSalObject::~SvpSalObject() +{ +} + +void SvpSalObject::ResetClipRegion() {} +void SvpSalObject::BeginSetClipRegion( sal_uInt32 ) {} +void SvpSalObject::UnionClipRegion( tools::Long, tools::Long, tools::Long, tools::Long ) {} +void SvpSalObject::EndSetClipRegion() {} +void SvpSalObject::SetPosSize( tools::Long, tools::Long, tools::Long, tools::Long ) {} +void SvpSalObject::Show( bool ) {} +const SystemEnvData* SvpSalObject::GetSystemData() const { return &m_aSystemChildData; } + +// SalSystem +SvpSalSystem::~SvpSalSystem() {} + +unsigned int SvpSalSystem::GetDisplayScreenCount() +{ + return 1; +} + +AbsoluteScreenPixelRectangle SvpSalSystem::GetDisplayScreenPosSizePixel( unsigned int nScreen ) +{ + AbsoluteScreenPixelRectangle aRect; + if( nScreen == 0 ) + aRect = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint(0,0), AbsoluteScreenPixelSize(VIRTUAL_DESKTOP_WIDTH,VIRTUAL_DESKTOP_HEIGHT) ); + return aRect; +} + +int SvpSalSystem::ShowNativeDialog( const OUString&, const OUString&, + const std::vector< OUString >& ) +{ + return 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/svpframe.cxx b/vcl/headless/svpframe.cxx new file mode 100644 index 0000000000..e0971f85b9 --- /dev/null +++ b/vcl/headless/svpframe.cxx @@ -0,0 +1,502 @@ +/* -*- 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 <comphelper/lok.hxx> +#include <o3tl/safeint.hxx> +#include <vcl/syswin.hxx> +#include <sal/log.hxx> + +#include <headless/svpframe.hxx> +#include <headless/svpinst.hxx> +#ifndef IOS +#include <headless/svpgdi.hxx> +#endif +#include <salsys.hxx> + +#include <basegfx/vector/b2ivector.hxx> + +#ifndef IOS +#include <cairo.h> +#endif + +SvpSalFrame* SvpSalFrame::s_pFocusFrame = nullptr; + +#ifdef IOS +#define SvpSalGraphics AquaSalGraphics +#endif + +SvpSalFrame::SvpSalFrame( SvpSalInstance* pInstance, + SalFrame* pParent, + SalFrameStyleFlags nSalFrameStyle ) : + m_pInstance( pInstance ), + m_pParent( static_cast<SvpSalFrame*>(pParent) ), + m_nStyle( nSalFrameStyle ), + m_bVisible( false ), +#ifndef IOS + m_pSurface( nullptr ), +#endif + m_nMinWidth( 0 ), + m_nMinHeight( 0 ), + m_nMaxWidth( 0 ), + m_nMaxHeight( 0 ) +{ +#ifdef IOS + // Nothing +#elif defined ANDROID + // Nothing +#else + m_aSystemChildData.pSalFrame = this; +#endif + + if( m_pParent ) + m_pParent->m_aChildren.push_back( this ); + + if( m_pInstance ) + m_pInstance->registerFrame( this ); + + SetPosSize( 0, 0, 800, 600, SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT ); +} + +SvpSalFrame::~SvpSalFrame() +{ + if( m_pInstance ) + m_pInstance->deregisterFrame( this ); + + std::vector<SvpSalFrame*> Children = m_aChildren; + for( auto& rChild : Children ) + rChild->SetParent( m_pParent ); + if( m_pParent ) + std::erase(m_pParent->m_aChildren, this); + + if( s_pFocusFrame == this ) + { + s_pFocusFrame = nullptr; + // call directly here, else an event for a destroyed frame would be dispatched + CallCallback( SalEvent::LoseFocus, nullptr ); + // if the handler has not set a new focus frame + // pass focus to another frame, preferably a document style window + if( s_pFocusFrame == nullptr ) + { + for (auto pSalFrame : m_pInstance->getFrames() ) + { + SvpSalFrame* pFrame = static_cast<SvpSalFrame*>( pSalFrame ); + if( pFrame->m_bVisible && + pFrame->m_pParent == nullptr && + (pFrame->m_nStyle & (SalFrameStyleFlags::MOVEABLE | + SalFrameStyleFlags::SIZEABLE | + SalFrameStyleFlags::CLOSEABLE) ) + ) + { + pFrame->GetFocus(); + break; + } + } + } + } +#ifndef IOS + if (m_pSurface) + cairo_surface_destroy(m_pSurface); +#endif +} + +void SvpSalFrame::GetFocus() +{ + if (m_nStyle == SalFrameStyleFlags::NONE) + return; + if( s_pFocusFrame == this ) + return; + // FIXME: return if !m_bVisible + // That's IMHO why CppunitTest_sd_tiledrendering crashes non-headless + + if( (m_nStyle & (SalFrameStyleFlags::OWNERDRAWDECORATION | SalFrameStyleFlags::FLOAT)) == SalFrameStyleFlags::NONE ) + { + if( s_pFocusFrame ) + s_pFocusFrame->LoseFocus(); + s_pFocusFrame = this; + m_pInstance->PostEvent( this, nullptr, SalEvent::GetFocus ); + } +} + +void SvpSalFrame::LoseFocus() +{ + if( s_pFocusFrame == this ) + { + m_pInstance->PostEvent( this, nullptr, SalEvent::LoseFocus ); + s_pFocusFrame = nullptr; + } +} + +basegfx::B2IVector SvpSalFrame::GetSurfaceFrameSize() const +{ + basegfx::B2IVector aFrameSize( maGeometry.width(), maGeometry.height() ); + if( aFrameSize.getX() == 0 ) + aFrameSize.setX( 1 ); + if( aFrameSize.getY() == 0 ) + aFrameSize.setY( 1 ); + // Creating backing surfaces for invisible windows costs a big chunk of RAM. + if (Application::IsHeadlessModeEnabled()) + aFrameSize = basegfx::B2IVector( 1, 1 ); + return aFrameSize; +} + +SalGraphics* SvpSalFrame::AcquireGraphics() +{ + SvpSalGraphics* pGraphics = new SvpSalGraphics(); +#ifndef IOS + pGraphics->setSurface(m_pSurface, GetSurfaceFrameSize()); +#endif + m_aGraphics.push_back( pGraphics ); + return pGraphics; +} + +void SvpSalFrame::ReleaseGraphics( SalGraphics* pGraphics ) +{ + SvpSalGraphics* pSvpGraphics = dynamic_cast<SvpSalGraphics*>(pGraphics); + std::erase(m_aGraphics, pSvpGraphics); + delete pSvpGraphics; +} + +bool SvpSalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData) +{ + m_pInstance->PostEvent( this, pData.release(), SalEvent::UserEvent ); + return true; +} + +void SvpSalFrame::PostPaint() const +{ + if( m_bVisible ) + { + SalPaintEvent aPEvt(0, 0, maGeometry.width(), maGeometry.height()); + aPEvt.mbImmediateUpdate = false; + CallCallback( SalEvent::Paint, &aPEvt ); + } +} + +void SvpSalFrame::SetTitle(const OUString& sTitle) +{ + m_sTitle = sTitle; +} + +void SvpSalFrame::SetIcon( sal_uInt16 ) +{ +} + +void SvpSalFrame::SetMenu( SalMenu* ) +{ +} + +void SvpSalFrame::SetExtendedFrameStyle( SalExtStyle ) +{ +} + +void SvpSalFrame::Show( bool bVisible, bool bNoActivate ) +{ + if (m_nStyle == SalFrameStyleFlags::NONE) + return; + if (bVisible == m_bVisible) + { + if (m_bVisible && !bNoActivate) + GetFocus(); + return; + } + + if (bVisible) + { + m_bVisible = true; + m_pInstance->PostEvent( this, nullptr, SalEvent::Resize ); + if( ! bNoActivate ) + GetFocus(); + } + else + { + m_bVisible = false; + LoseFocus(); + } +} + +void SvpSalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight ) +{ + m_nMinWidth = nWidth; + m_nMinHeight = nHeight; +} + +void SvpSalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight ) +{ + m_nMaxWidth = nWidth; + m_nMaxHeight = nHeight; +} + +void SvpSalFrame::SetPosSize( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags ) +{ + if( (nFlags & SAL_FRAME_POSSIZE_X) != 0 ) + maGeometry.setX(nX); + if( (nFlags & SAL_FRAME_POSSIZE_Y) != 0 ) + maGeometry.setY(nY); + if( (nFlags & SAL_FRAME_POSSIZE_WIDTH) != 0 ) + { + maGeometry.setWidth(nWidth); + if (m_nMaxWidth > 0 && maGeometry.width() > m_nMaxWidth) + maGeometry.setWidth(m_nMaxWidth); + if (m_nMinWidth > 0 && maGeometry.width() < m_nMinWidth) + maGeometry.setWidth(m_nMinWidth); + } + if( (nFlags & SAL_FRAME_POSSIZE_HEIGHT) != 0 ) + { + maGeometry.setHeight(nHeight); + if (m_nMaxHeight > 0 && maGeometry.height() > m_nMaxHeight) + maGeometry.setHeight(m_nMaxHeight); + if (m_nMinHeight > 0 && maGeometry.height() < m_nMinHeight) + maGeometry.setHeight(m_nMinHeight); + } +#ifndef IOS + basegfx::B2IVector aFrameSize = GetSurfaceFrameSize(); + if (!m_pSurface || cairo_image_surface_get_width(m_pSurface) != aFrameSize.getX() || + cairo_image_surface_get_height(m_pSurface) != aFrameSize.getY() ) + { + if (m_pSurface) + cairo_surface_destroy(m_pSurface); + + m_pSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + aFrameSize.getX(), + aFrameSize.getY()); + + // update device in existing graphics + for (auto const& graphic : m_aGraphics) + { + graphic->setSurface(m_pSurface, aFrameSize); + } + } + if( m_bVisible ) + m_pInstance->PostEvent( this, nullptr, SalEvent::Resize ); +#endif +} + +void SvpSalFrame::GetClientSize( tools::Long& rWidth, tools::Long& rHeight ) +{ + rWidth = maGeometry.width(); + rHeight = maGeometry.height(); +} + +void SvpSalFrame::GetWorkArea( AbsoluteScreenPixelRectangle& rRect ) +{ + rRect = AbsoluteScreenPixelRectangle( AbsoluteScreenPixelPoint( 0, 0 ), + AbsoluteScreenPixelSize( VIRTUAL_DESKTOP_WIDTH, VIRTUAL_DESKTOP_HEIGHT ) ); +} + +SalFrame* SvpSalFrame::GetParent() const +{ + return m_pParent; +} + +void SvpSalFrame::SetWindowState(const vcl::WindowData *pState) +{ + if (pState == nullptr) + return; + + // Request for position or size change + if (!(pState->mask() & vcl::WindowDataMask::PosSize)) + return; + + tools::Long nX = maGeometry.x(); + tools::Long nY = maGeometry.y(); + tools::Long nWidth = maGeometry.width(); + tools::Long nHeight = maGeometry.height(); + + // change requested properties + if (pState->mask() & vcl::WindowDataMask::X) + nX = pState->x(); + if (pState->mask() & vcl::WindowDataMask::Y) + nY = pState->y(); + if (pState->mask() & vcl::WindowDataMask::Width) + nWidth = pState->width(); + if (pState->mask() & vcl::WindowDataMask::Height) + nHeight = pState->height(); + + SetPosSize( nX, nY, nWidth, nHeight, + SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y | + SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT ); +} + +bool SvpSalFrame::GetWindowState(vcl::WindowData* pState) +{ + pState->setPosSize(maGeometry.posSize()); + pState->setState(vcl::WindowState::Normal); + pState->setMask(vcl::WindowDataMask::PosSizeState); + return true; +} + +void SvpSalFrame::ShowFullScreen( bool, sal_Int32 ) +{ + SetPosSize( 0, 0, VIRTUAL_DESKTOP_WIDTH, VIRTUAL_DESKTOP_HEIGHT, + SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT ); +} + +void SvpSalFrame::StartPresentation( bool ) +{ +} + +void SvpSalFrame::SetAlwaysOnTop( bool ) +{ +} + +void SvpSalFrame::ToTop(SalFrameToTop nFlags) +{ + if (m_nStyle == SalFrameStyleFlags::NONE) + return; + if (nFlags & SalFrameToTop::RestoreWhenMin) + Show(true, false); + else + GetFocus(); +} + +void SvpSalFrame::SetPointer( PointerStyle ) +{ +} + +void SvpSalFrame::CaptureMouse( bool ) +{ +} + +void SvpSalFrame::SetPointerPos( tools::Long, tools::Long ) +{ +} + +void SvpSalFrame::Flush() +{ +} + +void SvpSalFrame::SetInputContext( SalInputContext* ) +{ +} + +void SvpSalFrame::EndExtTextInput( EndExtTextInputFlags ) +{ +} + +OUString SvpSalFrame::GetKeyName( sal_uInt16 ) +{ + return OUString(); +} + +bool SvpSalFrame::MapUnicodeToKeyCode( sal_Unicode, LanguageType, vcl::KeyCode& ) +{ + return false; +} + +LanguageType SvpSalFrame::GetInputLanguage() +{ + return LANGUAGE_DONTKNOW; +} + +void SvpSalFrame::UpdateSettings( AllSettings& rSettings ) +{ + StyleSettings aStyleSettings = rSettings.GetStyleSettings(); + + Color aBackgroundColor( 0xef, 0xef, 0xef ); + aStyleSettings.BatchSetBackgrounds( aBackgroundColor, false ); + aStyleSettings.SetMenuColor( aBackgroundColor ); + aStyleSettings.SetMenuBarColor( aBackgroundColor ); + + if (comphelper::LibreOfficeKit::isActive()) // TODO: remove this. + { + vcl::Font aStdFont( FAMILY_SWISS, Size( 0, 14 ) ); + aStdFont.SetCharSet( osl_getThreadTextEncoding() ); + aStdFont.SetWeight( WEIGHT_NORMAL ); + aStdFont.SetFamilyName( "Liberation Sans" ); + aStyleSettings.BatchSetFonts( aStdFont, aStdFont ); + + aStdFont.SetFontSize(Size(0, 12)); + aStyleSettings.SetMenuFont(aStdFont); + + SvpSalGraphics* pGraphics = m_aGraphics.empty() ? nullptr : m_aGraphics.back(); + bool bFreeGraphics = false; + if (!pGraphics) + { + pGraphics = dynamic_cast<SvpSalGraphics*>(AcquireGraphics()); + if (!pGraphics) + { + SAL_WARN("vcl.gtk3", "Could not get graphics - unable to update settings"); + return; + } + bFreeGraphics = true; + } + rSettings.SetStyleSettings(aStyleSettings); +#ifndef IOS // For now... + pGraphics->UpdateSettings(rSettings); +#endif + if (bFreeGraphics) + ReleaseGraphics(pGraphics); + } + else + rSettings.SetStyleSettings(aStyleSettings); +} + +void SvpSalFrame::Beep() +{ +} + +const SystemEnvData* SvpSalFrame::GetSystemData() const +{ + return &m_aSystemChildData; +} + +SalFrame::SalPointerState SvpSalFrame::GetPointerState() +{ + SalPointerState aState; + aState.mnState = 0; + return aState; +} + +KeyIndicatorState SvpSalFrame::GetIndicatorState() +{ + return KeyIndicatorState::NONE; +} + +void SvpSalFrame::SimulateKeyPress( sal_uInt16 /*nKeyCode*/ ) +{ +} + +void SvpSalFrame::SetParent( SalFrame* pNewParent ) +{ + if( m_pParent ) + std::erase(m_pParent->m_aChildren, this); + m_pParent = static_cast<SvpSalFrame*>(pNewParent); +} + +void SvpSalFrame::SetPluginParent( SystemParentData* ) +{ +} + +void SvpSalFrame::ResetClipRegion() +{ +} + +void SvpSalFrame::BeginSetClipRegion( sal_uInt32 ) +{ +} + +void SvpSalFrame::UnionClipRegion( tools::Long, tools::Long, tools::Long, tools::Long ) +{ +} + +void SvpSalFrame::EndSetClipRegion() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/svpgdi.cxx b/vcl/headless/svpgdi.cxx new file mode 100644 index 0000000000..c9edd7e9a1 --- /dev/null +++ b/vcl/headless/svpgdi.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 <config_features.h> + +#include <memory> +#include <numeric> + +#include <headless/svpgdi.hxx> +#include <comphelper/lok.hxx> + +SvpSalGraphics::SvpSalGraphics() + : m_aTextRenderImpl(m_aCairoCommon) + , m_pBackend(new SvpGraphicsBackend(m_aCairoCommon)) +{ + bool bLOKActive = comphelper::LibreOfficeKit::isActive(); + initWidgetDrawBackends(bLOKActive); +} + +SvpSalGraphics::~SvpSalGraphics() +{ + ReleaseFonts(); +} + +void SvpSalGraphics::setSurface(cairo_surface_t* pSurface, const basegfx::B2IVector& rSize) +{ + m_aCairoCommon.m_pSurface = pSurface; + m_aCairoCommon.m_aFrameSize = rSize; + dl_cairo_surface_get_device_scale(pSurface, &m_aCairoCommon.m_fScale, nullptr); + GetImpl()->ResetClipRegion(); +} + +void SvpSalGraphics::GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY ) +{ + rDPIX = rDPIY = 96; +} + +bool SvpSalGraphics::ShouldDownscaleIconsAtSurface(double* pScaleOut) const +{ + if (comphelper::LibreOfficeKit::isActive()) + return SalGraphics::ShouldDownscaleIconsAtSurface(pScaleOut); + if (pScaleOut) + *pScaleOut = m_aCairoCommon.m_fScale; + return true; +} + +#if ENABLE_CAIRO_CANVAS +bool SvpSalGraphics::SupportsCairo() const +{ + return false; +} + +cairo::SurfaceSharedPtr SvpSalGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& /*rSurface*/) const +{ + return cairo::SurfaceSharedPtr(); +} + +cairo::SurfaceSharedPtr SvpSalGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int /*x*/, int /*y*/, int /*width*/, int /*height*/) const +{ + return cairo::SurfaceSharedPtr(); +} + +cairo::SurfaceSharedPtr SvpSalGraphics::CreateBitmapSurface(const OutputDevice& /*rRefDevice*/, const BitmapSystemData& /*rData*/, const Size& /*rSize*/) const +{ + return cairo::SurfaceSharedPtr(); +} + +css::uno::Any SvpSalGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& /*rSurface*/, const basegfx::B2ISize& /*rSize*/) const +{ + return css::uno::Any(); +} + +#endif // ENABLE_CAIRO_CANVAS + +SystemGraphicsData SvpSalGraphics::GetGraphicsData() const +{ + SystemGraphicsData aGraphicsData; + aGraphicsData.pSurface = m_aCairoCommon.m_pSurface; + return aGraphicsData; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/svpinst.cxx b/vcl/headless/svpinst.cxx new file mode 100644 index 0000000000..19eef89976 --- /dev/null +++ b/vcl/headless/svpinst.cxx @@ -0,0 +1,553 @@ +/* -*- 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 <sal/config.h> + +#include <mutex> + +#include <pthread.h> +#include <sys/time.h> +#include <poll.h> + +#include <sal/types.h> +#include <sal/log.hxx> + +#include <vcl/virdev.hxx> +#include <vcl/inputtypes.hxx> +#include <vcl/lok.hxx> + +#include <headless/svpinst.hxx> +#include <headless/svpframe.hxx> +#include <headless/svpdummies.hxx> +#include <headless/svpvd.hxx> +#ifdef IOS +# include <quartz/salbmp.h> +# include <quartz/salgdi.h> +# include <quartz/salvd.h> +#else +# include <cairo.h> +# include <headless/svpgdi.hxx> +#endif +#include <headless/svpbmp.hxx> + +#include <salframe.hxx> +#include <svdata.hxx> +// FIXME: remove when we re-work the svp mainloop +#include <unx/salunxtime.h> +#include <comphelper/lok.hxx> +#include <tools/debug.hxx> + +SvpSalInstance* SvpSalInstance::s_pDefaultInstance = nullptr; + +#ifndef NDEBUG +static bool g_CheckedMutex = false; + +#define DBG_TESTSVPYIELDMUTEX() \ +do { \ + if (!g_CheckedMutex) \ + { \ + assert(dynamic_cast<SvpSalYieldMutex*>(GetYieldMutex()) != nullptr \ + && "This SvpSalInstance function requires use of SvpSalYieldMutex"); \ + g_CheckedMutex = true; \ + } \ +} while(false) + +#else // NDEBUG +#define DBG_TESTSVPYIELDMUTEX() ((void)0) +#endif + +#if !defined(ANDROID) && !defined(IOS) && !defined(EMSCRIPTEN) + +static void atfork_child() +{ + if (SvpSalInstance::s_pDefaultInstance != nullptr) + { + SvpSalInstance::s_pDefaultInstance->CloseWakeupPipe(); + } +} + +#endif + +SvpSalInstance::SvpSalInstance( std::unique_ptr<SalYieldMutex> pMutex ) + : SalGenericInstance( std::move(pMutex) ) +{ + m_aTimeout.tv_sec = 0; + m_aTimeout.tv_usec = 0; + m_nTimeoutMS = 0; + + m_MainThread = osl::Thread::getCurrentIdentifier(); + if( s_pDefaultInstance == nullptr ) + s_pDefaultInstance = this; +#if !defined(ANDROID) && !defined(IOS) && !defined(EMSCRIPTEN) + pthread_atfork(nullptr, nullptr, atfork_child); +#endif +} + +SvpSalInstance::~SvpSalInstance() +{ + if( s_pDefaultInstance == this ) + s_pDefaultInstance = nullptr; + CloseWakeupPipe(); +} + +void SvpSalInstance::CloseWakeupPipe() +{ + SvpSalYieldMutex *const pMutex(dynamic_cast<SvpSalYieldMutex*>(GetYieldMutex())); + if (!pMutex) + return; + while (!pMutex->m_FeedbackPipe.empty()) + pMutex->m_FeedbackPipe.pop(); +} + +void SvpSalInstance::TriggerUserEventProcessing() +{ + Wakeup(); +} + +void SvpSalInstance::Wakeup(SvpRequest const request) +{ + DBG_TESTSVPYIELDMUTEX(); + + ImplSVData* pSVData = ImplGetSVData(); + if (pSVData->mpWakeCallback && pSVData->mpPollClosure) + pSVData->mpWakeCallback(pSVData->mpPollClosure); + + SvpSalYieldMutex *const pMutex(static_cast<SvpSalYieldMutex*>(GetYieldMutex())); + std::scoped_lock<std::mutex> g(pMutex->m_WakeUpMainMutex); + if (request != SvpRequest::NONE) + pMutex->m_Request = request; + pMutex->m_wakeUpMain = true; + pMutex->m_WakeUpMainCond.notify_one(); +} + +bool SvpSalInstance::CheckTimeout( bool bExecuteTimers ) +{ + bool bRet = false; + if( m_aTimeout.tv_sec ) // timer is started + { + timeval aTimeOfDay; + gettimeofday( &aTimeOfDay, nullptr ); + if( aTimeOfDay >= m_aTimeout ) + { + bRet = true; + if( bExecuteTimers ) + { + // timed out, update timeout + m_aTimeout = aTimeOfDay; + m_aTimeout += m_nTimeoutMS; + + osl::Guard< comphelper::SolarMutex > aGuard( GetYieldMutex() ); + + // notify + ImplSVData* pSVData = ImplGetSVData(); + if( pSVData->maSchedCtx.mpSalTimer ) + pSVData->maSchedCtx.mpSalTimer->CallCallback(); + } + } + } + return bRet; +} + +SalFrame* SvpSalInstance::CreateChildFrame( SystemParentData* /*pParent*/, SalFrameStyleFlags nStyle ) +{ + return new SvpSalFrame( this, nullptr, nStyle ); +} + +SalFrame* SvpSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nStyle ) +{ + return new SvpSalFrame( this, pParent, nStyle ); +} + +void SvpSalInstance::DestroyFrame( SalFrame* pFrame ) +{ + delete pFrame; +} + +SalObject* SvpSalInstance::CreateObject( SalFrame*, SystemWindowData*, bool ) +{ + return new SvpSalObject; +} + +void SvpSalInstance::DestroyObject( SalObject* pObject ) +{ + delete pObject; +} + +#ifndef IOS + +std::unique_ptr<SalVirtualDevice> SvpSalInstance::CreateVirtualDevice(SalGraphics& rGraphics, + tools::Long &nDX, tools::Long &nDY, + DeviceFormat /*eFormat*/, + const SystemGraphicsData* pGd) +{ + SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(&rGraphics); + assert(pSvpSalGraphics); +#ifndef ANDROID + // tdf#127529 normally pPreExistingTarget is null and we are a true virtualdevice drawing to a backing buffer. + // Occasionally, for canvas/slideshow, pPreExistingTarget is pre-provided as a hack to use the vcl drawing + // apis to render onto a preexisting cairo surface. The necessity for that precedes the use of cairo in vcl proper + cairo_surface_t* pPreExistingTarget = pGd ? static_cast<cairo_surface_t*>(pGd->pSurface) : nullptr; +#else + //ANDROID case + (void)pGd; + cairo_surface_t* pPreExistingTarget = nullptr; +#endif + std::unique_ptr<SalVirtualDevice> xNew(new SvpSalVirtualDevice(pSvpSalGraphics->getSurface(), pPreExistingTarget)); + if (!xNew->SetSize(nDX, nDY)) + xNew.reset(); + return xNew; +} + +cairo_surface_t* get_underlying_cairo_surface(const VirtualDevice& rDevice) +{ + return static_cast<SvpSalVirtualDevice*>(rDevice.mpVirDev.get())->GetSurface(); +} + +const cairo_font_options_t* SvpSalInstance::GetCairoFontOptions() +{ + static cairo_font_options_t *gOptions = nullptr; + if (!gOptions) + { + gOptions = cairo_font_options_create(); + cairo_font_options_set_antialias(gOptions, CAIRO_ANTIALIAS_GRAY); + } + return gOptions; +} + +#else // IOS + +const cairo_font_options_t* SvpSalInstance::GetCairoFontOptions() +{ + return nullptr; +} + +#endif + +SalTimer* SvpSalInstance::CreateSalTimer() +{ + return new SvpSalTimer( this ); +} + +SalSystem* SvpSalInstance::CreateSalSystem() +{ + return new SvpSalSystem(); +} + +std::shared_ptr<SalBitmap> SvpSalInstance::CreateSalBitmap() +{ +#ifdef IOS + return std::make_shared<QuartzSalBitmap>(); +#else + return std::make_shared<SvpSalBitmap>(); +#endif +} + +void SvpSalInstance::ProcessEvent( SalUserEvent aEvent ) +{ + DBG_TESTSVPYIELDMUTEX(); + + aEvent.m_pFrame->CallCallback( aEvent.m_nEvent, aEvent.m_pData ); + if( aEvent.m_nEvent == SalEvent::Resize ) + { + // this would be a good time to post a paint + const SvpSalFrame* pSvpFrame = static_cast<const SvpSalFrame*>( aEvent.m_pFrame); + pSvpFrame->PostPaint(); + } + + SvpSalYieldMutex *const pMutex(static_cast<SvpSalYieldMutex*>(GetYieldMutex())); + pMutex->m_NonMainWaitingYieldCond.set(); +} + +SvpSalYieldMutex::SvpSalYieldMutex() +{ +} + +SvpSalYieldMutex::~SvpSalYieldMutex() +{ +} + +void SvpSalYieldMutex::doAcquire(sal_uInt32 const nLockCount) +{ + auto *const pInst = static_cast<SvpSalInstance*>(GetSalInstance()); + if (pInst && pInst->IsMainThread()) + { + if (m_bNoYieldLock) + return; + + do + { + SvpRequest request = SvpRequest::NONE; + { + std::unique_lock<std::mutex> g(m_WakeUpMainMutex); + if (m_aMutex.tryToAcquire()) { + // if there's a request, the other thread holds m_aMutex + assert(m_Request == SvpRequest::NONE); + m_wakeUpMain = false; + break; + } + m_WakeUpMainCond.wait(g, [this]() { return m_wakeUpMain; }); + m_wakeUpMain = false; + std::swap(m_Request, request); + } + if (request != SvpRequest::NONE) + { + // nested Yield on behalf of another thread + assert(!m_bNoYieldLock); + m_bNoYieldLock = true; + bool const bEvents = pInst->DoYield(false, request == SvpRequest::MainThreadDispatchAllEvents); + m_bNoYieldLock = false; + { + std::lock_guard lock(m_FeedbackMutex); + m_FeedbackPipe.push(bEvents); + } + m_FeedbackCV.notify_all(); + } + } + while (true); + } + else + { + m_aMutex.acquire(); + } + ++m_nCount; + SalYieldMutex::doAcquire(nLockCount - 1); +} + +sal_uInt32 SvpSalYieldMutex::doRelease(bool const bUnlockAll) +{ + auto *const pInst = static_cast<SvpSalInstance*>(GetSalInstance()); + if (pInst && pInst->IsMainThread()) + { + if (m_bNoYieldLock) + return 1; + else + return SalYieldMutex::doRelease(bUnlockAll); + } + sal_uInt32 nCount; + { + // read m_nCount before doRelease + bool const isReleased(bUnlockAll || m_nCount == 1); + nCount = comphelper::SolarMutex::doRelease( bUnlockAll ); + + if (isReleased) + { + if (vcl::lok::isUnipoll()) + { + if (pInst) + pInst->Wakeup(); + } + else + { + std::scoped_lock<std::mutex> g(m_WakeUpMainMutex); + m_wakeUpMain = true; + m_WakeUpMainCond.notify_one(); + } + } + } + return nCount; +} + +bool SvpSalYieldMutex::IsCurrentThread() const +{ + if (GetSalInstance()->IsMainThread() && m_bNoYieldLock) + return true; + else + return SalYieldMutex::IsCurrentThread(); +} + +bool SvpSalInstance::IsMainThread() const +{ + return osl::Thread::getCurrentIdentifier() == m_MainThread; +} + +void SvpSalInstance::updateMainThread() +{ + if (!IsMainThread()) + { + m_MainThread = osl::Thread::getCurrentIdentifier(); + ImplGetSVData()->mnMainThreadId = osl::Thread::getCurrentIdentifier(); + } +} + +bool SvpSalInstance::ImplYield(bool bWait, bool bHandleAllCurrentEvents) +{ + DBG_TESTSVPYIELDMUTEX(); + DBG_TESTSOLARMUTEX(); + assert(IsMainThread()); + + bool bWasEvent = DispatchUserEvents(bHandleAllCurrentEvents); + if (!bHandleAllCurrentEvents && bWasEvent) + return true; + + bWasEvent = CheckTimeout() || bWasEvent; + const bool bMustSleep = bWait && !bWasEvent; + + // This is wrong and must be removed! + // We always want to drop the SolarMutex on yield; that is the whole point of yield. + if (!bMustSleep) + return bWasEvent; + + sal_Int64 nTimeoutMicroS = 0; + if (bMustSleep) + { + if (m_aTimeout.tv_sec) // Timer is started. + { + timeval Timeout; + // determine remaining timeout. + gettimeofday (&Timeout, nullptr); + if (m_aTimeout > Timeout) + nTimeoutMicroS = ((m_aTimeout.tv_sec - Timeout.tv_sec) * 1000 * 1000 + + (m_aTimeout.tv_usec - Timeout.tv_usec)); + } + else + nTimeoutMicroS = -1; // wait until something happens + } + + SolarMutexReleaser aReleaser; + + if (vcl::lok::isUnipoll()) + { + ImplSVData* pSVData = ImplGetSVData(); + if (pSVData->mpPollClosure) + { + int nPollResult = pSVData->mpPollCallback(pSVData->mpPollClosure, nTimeoutMicroS); + if (nPollResult < 0) + pSVData->maAppData.mbAppQuit = true; + bWasEvent = bWasEvent || (nPollResult != 0); + } + } + else if (bMustSleep) + { + SvpSalYieldMutex *const pMutex(static_cast<SvpSalYieldMutex*>(GetYieldMutex())); + std::unique_lock<std::mutex> g(pMutex->m_WakeUpMainMutex); + // wait for doRelease() or Wakeup() to set the condition + if (nTimeoutMicroS == -1) + { + pMutex->m_WakeUpMainCond.wait(g, + [pMutex]() { return pMutex->m_wakeUpMain; }); + } + else + { + int nTimeoutMS = nTimeoutMicroS / 1000; + if (nTimeoutMicroS % 1000) + nTimeoutMS += 1; + pMutex->m_WakeUpMainCond.wait_for(g, + std::chrono::milliseconds(nTimeoutMS), + [pMutex]() { return pMutex->m_wakeUpMain; }); + } + // here no need to check m_Request because Acquire will do it + } + + return bWasEvent; +} + +bool SvpSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents) +{ + DBG_TESTSVPYIELDMUTEX(); + DBG_TESTSOLARMUTEX(); + + bool bWasEvent(false); + SvpSalYieldMutex *const pMutex(static_cast<SvpSalYieldMutex*>(GetYieldMutex())); + + if (IsMainThread()) + { + bWasEvent = ImplYield(bWait, bHandleAllCurrentEvents); + if (bWasEvent) + pMutex->m_NonMainWaitingYieldCond.set(); // wake up other threads + } + else + { + // TODO: use a SolarMutexReleaser here and drop the m_bNoYieldLock usage + Wakeup(bHandleAllCurrentEvents + ? SvpRequest::MainThreadDispatchAllEvents + : SvpRequest::MainThreadDispatchOneEvent); + + // blocking read (for synchronisation) + { + std::unique_lock lock(pMutex->m_FeedbackMutex); + pMutex->m_FeedbackCV.wait(lock, [pMutex] { return !pMutex->m_FeedbackPipe.empty(); }); + bWasEvent = pMutex->m_FeedbackPipe.front(); + pMutex->m_FeedbackPipe.pop(); + } + if (!bWasEvent && bWait) + { + // block & release YieldMutex until the main thread does something + pMutex->m_NonMainWaitingYieldCond.reset(); + SolarMutexReleaser aReleaser; + pMutex->m_NonMainWaitingYieldCond.wait(); + } + } + + return bWasEvent; +} + +bool SvpSalInstance::AnyInput( VclInputFlags nType ) +{ + if( nType & VclInputFlags::TIMER ) + return CheckTimeout( false ); + return false; +} + +OUString SvpSalInstance::GetConnectionIdentifier() +{ + return OUString(); +} + +void SvpSalInstance::StopTimer() +{ + m_aTimeout.tv_sec = 0; + m_aTimeout.tv_usec = 0; + m_nTimeoutMS = 0; +} + +void SvpSalInstance::StartTimer( sal_uInt64 nMS ) +{ + timeval aPrevTimeout (m_aTimeout); + gettimeofday (&m_aTimeout, nullptr); + + m_nTimeoutMS = nMS; + m_aTimeout += m_nTimeoutMS; + + if ((aPrevTimeout > m_aTimeout) || (aPrevTimeout.tv_sec == 0)) + { + // Wakeup from previous timeout (or stopped timer). + Wakeup(); + } +} + +void SvpSalInstance::AddToRecentDocumentList(const OUString&, const OUString&, const OUString&) +{ +} + +SvpSalTimer::~SvpSalTimer() +{ +} + +void SvpSalTimer::Stop() +{ + m_pInstance->StopTimer(); +} + +void SvpSalTimer::Start( sal_uInt64 nMS ) +{ + m_pInstance->StartTimer( nMS ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/svpprn.cxx b/vcl/headless/svpprn.cxx new file mode 100644 index 0000000000..6c40a0fd8d --- /dev/null +++ b/vcl/headless/svpprn.cxx @@ -0,0 +1,268 @@ +/* -*- 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 <string_view> + +#include <vcl/svapp.hxx> +#include <vcl/timer.hxx> +#include <vcl/QueueInfo.hxx> +#include <printerinfomanager.hxx> + +#include <jobset.h> +#include <print.h> +#include <salptype.hxx> +#include <svdata.hxx> + +#include <unx/genpspgraphics.h> + +#include <headless/svpprn.hxx> +#include <headless/svpinst.hxx> + +using namespace psp; + +/* + * static helpers + */ + +static OUString getPdfDir( const PrinterInfo& rInfo ) +{ + OUString aDir; + sal_Int32 nIndex = 0; + while( nIndex != -1 ) + { + OUString aToken( rInfo.m_aFeatures.getToken( 0, ',', nIndex ) ); + if( aToken.startsWith( "pdf=" ) ) + { + sal_Int32 nPos = 0; + aDir = aToken.getToken( 1, '=', nPos ); + if( aDir.isEmpty() ) + if (auto const env = getenv( "HOME" )) { + aDir = OStringToOUString( + std::string_view( env ), osl_getThreadTextEncoding() ); + } + break; + } + } + return aDir; +} + +static int PtTo10Mu( int nPoints ) { return static_cast<int>((static_cast<double>(nPoints)*35.27777778)+0.5); } + +static void copyJobDataToJobSetup( ImplJobSetup* pJobSetup, JobData& rData ) +{ + pJobSetup->SetOrientation( rData.m_eOrientation == orientation::Landscape ? Orientation::Landscape : Orientation::Portrait ); + + // copy page size + OUString aPaper; + int width, height; + + rData.m_aContext.getPageSize( aPaper, width, height ); + pJobSetup->SetPaperFormat( PaperInfo::fromPSName(OUStringToOString( aPaper, RTL_TEXTENCODING_ISO_8859_1 )) ); + pJobSetup->SetPaperWidth( 0 ); + pJobSetup->SetPaperHeight( 0 ); + if( pJobSetup->GetPaperFormat() == PAPER_USER ) + { + // transform to 100dth mm + width = PtTo10Mu( width ); + height = PtTo10Mu( height ); + + if( rData.m_eOrientation == psp::orientation::Portrait ) + { + pJobSetup->SetPaperWidth( width ); + pJobSetup->SetPaperHeight( height ); + } + else + { + pJobSetup->SetPaperWidth( height ); + pJobSetup->SetPaperHeight( width ); + } + } + + // copy input slot + const PPDKey* pKey = nullptr; + const PPDValue* pValue = nullptr; + + pJobSetup->SetPaperBin( 0xffff ); + if( rData.m_pParser ) + pKey = rData.m_pParser->getKey( "InputSlot" ); + if( pKey ) + pValue = rData.m_aContext.getValue( pKey ); + if( pKey && pValue ) + { + int nPaperBin; + for( nPaperBin = 0; + pValue != pKey->getValue( nPaperBin ) && + nPaperBin < pKey->countValues(); + nPaperBin++ ); + pJobSetup->SetPaperBin( + (nPaperBin == pKey->countValues() + || pValue == pKey->getDefaultValue()) + ? 0xffff : nPaperBin); + } + + // copy duplex + pKey = nullptr; + pValue = nullptr; + + pJobSetup->SetDuplexMode( DuplexMode::Unknown ); + if( rData.m_pParser ) + pKey = rData.m_pParser->getKey( "Duplex" ); + if( pKey ) + pValue = rData.m_aContext.getValue( pKey ); + if( pKey && pValue ) + { + if( pValue->m_aOption.equalsIgnoreAsciiCase( "None" ) || + pValue->m_aOption.startsWithIgnoreAsciiCase( "Simplex" ) + ) + { + pJobSetup->SetDuplexMode( DuplexMode::Off ); + } + else if( pValue->m_aOption.equalsIgnoreAsciiCase( "DuplexNoTumble" ) ) + { + pJobSetup->SetDuplexMode( DuplexMode::LongEdge ); + } + else if( pValue->m_aOption.equalsIgnoreAsciiCase( "DuplexTumble" ) ) + { + pJobSetup->SetDuplexMode( DuplexMode::ShortEdge ); + } + } + + // copy the whole context + + sal_uInt32 nBytes; + std::unique_ptr<sal_uInt8[]> pBuffer; + if( rData.getStreamBuffer( pBuffer, nBytes ) ) + pJobSetup->SetDriverData( std::move(pBuffer), nBytes ); + else + pJobSetup->SetDriverData( nullptr, 0 ); +} + +// SalInstance + +SalInfoPrinter* SvpSalInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo, + ImplJobSetup* pJobSetup ) +{ + // create and initialize SalInfoPrinter + SvpSalInfoPrinter* pPrinter = new SvpSalInfoPrinter; + + if( pJobSetup ) + { + PrinterInfoManager& rManager( PrinterInfoManager::get() ); + PrinterInfo aInfo( rManager.getPrinterInfo( pQueueInfo->maPrinterName ) ); + pPrinter->m_aJobData = aInfo; + + if( pJobSetup->GetDriverData() ) + JobData::constructFromStreamBuffer( pJobSetup->GetDriverData(), + pJobSetup->GetDriverDataLen(), aInfo ); + + pJobSetup->SetSystem( JOBSETUP_SYSTEM_UNIX ); + pJobSetup->SetPrinterName( pQueueInfo->maPrinterName ); + pJobSetup->SetDriver( aInfo.m_aDriverName ); + copyJobDataToJobSetup( pJobSetup, aInfo ); + } + + return pPrinter; +} + +void SvpSalInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter ) +{ + delete pPrinter; +} + +std::unique_ptr<SalPrinter> SvpSalInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter ) +{ + // create and initialize SalPrinter + SvpSalPrinter* pPrinter = new SvpSalPrinter( pInfoPrinter ); + pPrinter->m_aJobData = static_cast<SvpSalInfoPrinter*>(pInfoPrinter)->m_aJobData; + + return std::unique_ptr<SalPrinter>(pPrinter); +} + +void SvpSalInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList ) +{ + PrinterInfoManager& rManager( PrinterInfoManager::get() ); + static const char* pNoSyncDetection = getenv( "SAL_DISABLE_SYNCHRONOUS_PRINTER_DETECTION" ); + if( ! pNoSyncDetection || ! *pNoSyncDetection ) + { + // #i62663# synchronize possible asynchronouse printer detection now + rManager.checkPrintersChanged( true ); + } + ::std::vector< OUString > aPrinters; + rManager.listPrinters( aPrinters ); + + for (auto const& printer : aPrinters) + { + const PrinterInfo& rInfo( rManager.getPrinterInfo(printer) ); + // create new entry + std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo); + pInfo->maPrinterName = printer; + pInfo->maDriver = rInfo.m_aDriverName; + pInfo->maLocation = rInfo.m_aLocation; + pInfo->maComment = rInfo.m_aComment; + + sal_Int32 nIndex = 0; + while( nIndex != -1 ) + { + OUString aToken( rInfo.m_aFeatures.getToken( 0, ',', nIndex ) ); + if( aToken.startsWith( "pdf=" ) ) + { + pInfo->maLocation = getPdfDir( rInfo ); + break; + } + } + + pList->Add( std::move(pInfo) ); + } +} + +void SvpSalInstance::GetPrinterQueueState( SalPrinterQueueInfo* ) +{ +} + +OUString SvpSalInstance::GetDefaultPrinter() +{ + PrinterInfoManager& rManager( PrinterInfoManager::get() ); + return rManager.getDefaultPrinter(); +} + +void SvpSalInstance::PostPrintersChanged() +{ + SvpSalInstance *pInst = SvpSalInstance::s_pDefaultInstance; + for (auto pSalFrame : pInst->getFrames() ) + pInst->PostEvent( pSalFrame, nullptr, SalEvent::PrinterChanged ); +} + +std::unique_ptr<GenPspGraphics> SvpSalInstance::CreatePrintGraphics() +{ + return std::make_unique<GenPspGraphics>(); +} + +bool SvpSalInfoPrinter::Setup( weld::Window*, ImplJobSetup* ) +{ + return false; +} + +SvpSalPrinter::SvpSalPrinter( SalInfoPrinter* pInfoPrinter ) + : PspSalPrinter( pInfoPrinter ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/svptext.cxx b/vcl/headless/svptext.cxx new file mode 100644 index 0000000000..297523b8b3 --- /dev/null +++ b/vcl/headless/svptext.cxx @@ -0,0 +1,80 @@ +/* -*- 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/types.h> +#include <vcl/fontcharmap.hxx> +#include <basegfx/range/b2ibox.hxx> +#include <headless/svpgdi.hxx> +#include <font/FontMetricData.hxx> +#include <sallayout.hxx> + +void SvpSalGraphics::SetFont(LogicalFontInstance* pIFSD, int nFallbackLevel) +{ + m_aTextRenderImpl.SetFont(pIFSD, nFallbackLevel); +} + +void SvpSalGraphics::GetFontMetric( FontMetricDataRef& xFontMetric, int nFallbackLevel ) +{ + m_aTextRenderImpl.GetFontMetric(xFontMetric, nFallbackLevel); +} + +FontCharMapRef SvpSalGraphics::GetFontCharMap() const +{ + return m_aTextRenderImpl.GetFontCharMap(); +} + +bool SvpSalGraphics::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const +{ + return m_aTextRenderImpl.GetFontCapabilities(rFontCapabilities); +} + +void SvpSalGraphics::GetDevFontList( vcl::font::PhysicalFontCollection* pFontCollection ) +{ + m_aTextRenderImpl.GetDevFontList(pFontCollection); +} + +void SvpSalGraphics::ClearDevFontCache() +{ + m_aTextRenderImpl.ClearDevFontCache(); +} + +bool SvpSalGraphics::AddTempDevFont( vcl::font::PhysicalFontCollection* pFontCollection, + const OUString& rFileURL, const OUString& rFontName) +{ + return m_aTextRenderImpl.AddTempDevFont(pFontCollection, rFileURL, rFontName); +} + +std::unique_ptr<GenericSalLayout> SvpSalGraphics::GetTextLayout(int nFallbackLevel) +{ + return m_aTextRenderImpl.GetTextLayout(nFallbackLevel); +} + +void SvpSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout) +{ + m_aTextRenderImpl.DrawTextLayout(rLayout, *this); +} + +void SvpSalGraphics::SetTextColor( Color nColor ) +{ + m_aTextRenderImpl.SetTextColor(nColor); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/svpvd.cxx b/vcl/headless/svpvd.cxx new file mode 100644 index 0000000000..c65951e029 --- /dev/null +++ b/vcl/headless/svpvd.cxx @@ -0,0 +1,157 @@ +/* -*- 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 IOS + +#include <headless/svpbmp.hxx> +#include <headless/svpinst.hxx> +#include <headless/svpvd.hxx> +#include <headless/svpgdi.hxx> + +#include <basegfx/vector/b2ivector.hxx> +#include <comphelper/lok.hxx> + +#include <cairo.h> + +using namespace basegfx; + +SvpSalVirtualDevice::SvpSalVirtualDevice(cairo_surface_t* pRefSurface, cairo_surface_t* pPreExistingTarget) + : m_pRefSurface(pRefSurface) + , m_pSurface(pPreExistingTarget) + , m_bOwnsSurface(!pPreExistingTarget) +{ + cairo_surface_reference(m_pRefSurface); +} + +SvpSalVirtualDevice::~SvpSalVirtualDevice() +{ + if (m_bOwnsSurface) + cairo_surface_destroy(m_pSurface); + cairo_surface_destroy(m_pRefSurface); +} + +SvpSalGraphics* SvpSalVirtualDevice::AddGraphics(SvpSalGraphics* pGraphics) +{ + pGraphics->setSurface(m_pSurface, m_aFrameSize); + m_aGraphics.push_back(pGraphics); + return pGraphics; +} + +SalGraphics* SvpSalVirtualDevice::AcquireGraphics() +{ + return AddGraphics(new SvpSalGraphics()); +} + +void SvpSalVirtualDevice::ReleaseGraphics( SalGraphics* pGraphics ) +{ + std::erase(m_aGraphics, dynamic_cast<SvpSalGraphics*>(pGraphics)); + delete pGraphics; +} + +bool SvpSalVirtualDevice::SetSize( tools::Long nNewDX, tools::Long nNewDY ) +{ + return SetSizeUsingBuffer(nNewDX, nNewDY, nullptr); +} + +bool SvpSalVirtualDevice::CreateSurface(tools::Long nNewDX, tools::Long nNewDY, sal_uInt8 *const pBuffer) +{ + if (m_pSurface) + { + cairo_surface_destroy(m_pSurface); + } + + if (pBuffer) + { + // The buffer should only be set by VirtualDevice::SetOutputSizePixelScaleOffsetAndLOKBuffer() + // when used to draw a tile for LOK. It cannot be used for something else, because otherwise + // this would need a way to detect whether this is a tiled paint that needs LOK handling + // or whether it's that something else that just might happen to be called with LOK active. + assert(comphelper::LibreOfficeKit::isActive()); + // Force scaling of the painting + double fScale = comphelper::LibreOfficeKit::getDPIScale(); + + m_pSurface = cairo_image_surface_create_for_data(pBuffer, CAIRO_FORMAT_ARGB32, + nNewDX, nNewDY, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, nNewDX)); + dl_cairo_surface_set_device_scale(m_pSurface, fScale, fScale); + } + else if(nNewDX <= 32 && nNewDY <= 32) + { + double fXScale, fYScale; + dl_cairo_surface_get_device_scale(m_pRefSurface, &fXScale, &fYScale); + nNewDX *= fXScale; + nNewDY *= fYScale; + + // Force image-based surface if small. Small VirtualDevice instances are often used for small + // temporary bitmaps that will eventually have GetBitmap() called on them, which would cause + // X Server roundtrip with Xlib-based surface, which may be way more costly than doing the drawing + // in software (which should be fairly cheap for small surfaces anyway). + m_pSurface = cairo_surface_create_similar_image(m_pRefSurface, CAIRO_FORMAT_ARGB32, nNewDX, nNewDY); + dl_cairo_surface_set_device_scale(m_pSurface, fXScale, fYScale); + } + else + { + m_pSurface = cairo_surface_create_similar(m_pRefSurface, CAIRO_CONTENT_COLOR_ALPHA, nNewDX, nNewDY); + // Device scale is inherited in this case. + } + + SAL_WARN_IF(cairo_surface_status(m_pSurface) != CAIRO_STATUS_SUCCESS, "vcl", "surface of size " << nNewDX << " by " << nNewDY << " creation failed with status of: " << cairo_status_to_string(cairo_surface_status(m_pSurface))); + return cairo_surface_status(m_pSurface) == CAIRO_STATUS_SUCCESS; +} + +bool SvpSalVirtualDevice::SetSizeUsingBuffer( tools::Long nNewDX, tools::Long nNewDY, + sal_uInt8 *const pBuffer) +{ + bool bSuccess = true; + + if (nNewDX == 0) + nNewDX = 1; + if (nNewDY == 0) + nNewDY = 1; + + if (!m_pSurface || m_aFrameSize.getX() != nNewDX || + m_aFrameSize.getY() != nNewDY) + { + m_aFrameSize = basegfx::B2IVector(nNewDX, nNewDY); + + if (m_bOwnsSurface) + bSuccess = CreateSurface(nNewDX, nNewDY, pBuffer); + + assert(m_pSurface); + + // update device in existing graphics + for (auto const& graphic : m_aGraphics) + graphic->setSurface(m_pSurface, m_aFrameSize); + } + + return bSuccess; +} + +tools::Long SvpSalVirtualDevice::GetWidth() const +{ + return m_pSurface ? m_aFrameSize.getX() : 0; +} + +tools::Long SvpSalVirtualDevice::GetHeight() const +{ + return m_pSurface ? m_aFrameSize.getY() : 0; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |