diff options
Diffstat (limited to '')
-rw-r--r-- | vcl/headless/CustomWidgetDraw.cxx | 421 | ||||
-rw-r--r-- | vcl/headless/headlessinst.cxx | 101 | ||||
-rw-r--r-- | vcl/headless/svpbmp.cxx | 283 | ||||
-rw-r--r-- | vcl/headless/svpcairotextrender.cxx | 40 | ||||
-rw-r--r-- | vcl/headless/svpdata.cxx | 33 | ||||
-rw-r--r-- | vcl/headless/svpdummies.cxx | 58 | ||||
-rw-r--r-- | vcl/headless/svpframe.cxx | 502 | ||||
-rw-r--r-- | vcl/headless/svpgdi.cxx | 2622 | ||||
-rw-r--r-- | vcl/headless/svpinst.cxx | 627 | ||||
-rw-r--r-- | vcl/headless/svpprn.cxx | 269 | ||||
-rw-r--r-- | vcl/headless/svptext.cxx | 121 | ||||
-rw-r--r-- | vcl/headless/svpvd.cxx | 148 |
12 files changed, 5225 insertions, 0 deletions
diff --git a/vcl/headless/CustomWidgetDraw.cxx b/vcl/headless/CustomWidgetDraw.cxx new file mode 100644 index 000000000..7c167ff05 --- /dev/null +++ b/vcl/headless/CustomWidgetDraw.cxx @@ -0,0 +1,421 @@ +/* -*- 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 <cairo.h> +#include <headless/CustomWidgetDraw.hxx> +#include <sal/config.h> +#include <rtl/bootstrap.hxx> +#include <tools/svlibrary.h> +#include <osl/module.hxx> +#include <svdata.hxx> + +namespace vcl +{ +WidgetThemeLibrary* CustomWidgetDraw::s_pWidgetImplementation = nullptr; + +CustomWidgetDraw::CustomWidgetDraw(SvpSalGraphics& rGraphics) + : m_rGraphics(rGraphics) +{ +#ifndef DISABLE_DYNLOADING + static bool s_bMissingLibrary = false; + if (!s_pWidgetImplementation && !s_bMissingLibrary) + { + OUString aUrl("${LO_LIB_DIR}/" SVLIBRARY("vcl_widget_theme")); + rtl::Bootstrap::expandMacros(aUrl); + osl::Module aLibrary; + aLibrary.load(aUrl, SAL_LOADMODULE_GLOBAL); + auto fCreateWidgetThemeLibraryFunction + = reinterpret_cast<vcl::WidgetThemeLibrary*(SAL_CALL*)()>( + aLibrary.getFunctionSymbol("CreateWidgetThemeLibrary")); + aLibrary.release(); + + if (fCreateWidgetThemeLibraryFunction) + s_pWidgetImplementation = (*fCreateWidgetThemeLibraryFunction)(); + + // Init + if (s_pWidgetImplementation) + { + ImplSVData* pSVData = ImplGetSVData(); + pSVData->maNWFData.mbNoFocusRects = true; + pSVData->maNWFData.mbNoFocusRectsForFlatButtons = true; + } + else + s_bMissingLibrary = true; + } +#endif +} + +CustomWidgetDraw::~CustomWidgetDraw() {} + +bool CustomWidgetDraw::isNativeControlSupported(ControlType eType, ControlPart ePart) +{ + return s_pWidgetImplementation + && s_pWidgetImplementation->isNativeControlSupported(eType, ePart); +} + +bool CustomWidgetDraw::hitTestNativeControl(ControlType /*eType*/, ControlPart /*ePart*/, + const tools::Rectangle& /*rBoundingControlRegion*/, + const Point& /*aPos*/, bool& /*rIsInside*/) +{ + return false; +} + +bool CustomWidgetDraw::drawNativeControl(ControlType eType, ControlPart ePart, + const tools::Rectangle& rControlRegion, + ControlState eState, const ImplControlValue& rValue, + const OUString& /*aCaptions*/, + const Color& /*rBackgroundColor*/) +{ + if (!s_pWidgetImplementation) + return false; + + bool bOldAA = m_rGraphics.getAntiAliasB2DDraw(); + m_rGraphics.setAntiAliasB2DDraw(true); + + cairo_t* pCairoContext = m_rGraphics.getCairoContext(false); + m_rGraphics.clipRegion(pCairoContext); + + cairo_translate(pCairoContext, rControlRegion.Left(), rControlRegion.Top()); + + long nWidth = rControlRegion.GetWidth(); + long nHeight = rControlRegion.GetHeight(); + + bool bOK = false; + + ControlDrawParameters aParameters{ pCairoContext, ePart, eState }; + + switch (eType) + { + case ControlType::Generic: + { + } + break; + case ControlType::Pushbutton: + { + const PushButtonValue* pPushButtonValue = static_cast<const PushButtonValue*>(&rValue); + if (pPushButtonValue) + aParameters.bIsAction = pPushButtonValue->mbIsAction; + bOK = s_pWidgetImplementation->drawPushButton(aParameters, nWidth, nHeight); + } + break; + case ControlType::Radiobutton: + { + aParameters.eButtonValue = rValue.getTristateVal(); + bOK = s_pWidgetImplementation->drawRadiobutton(aParameters, nWidth, nHeight); + } + break; + case ControlType::Checkbox: + { + aParameters.eButtonValue = rValue.getTristateVal(); + bOK = s_pWidgetImplementation->drawCheckbox(aParameters, nWidth, nHeight); + } + break; + case ControlType::Combobox: + { + bOK = s_pWidgetImplementation->drawCombobox(aParameters, nWidth, nHeight); + } + break; + case ControlType::Editbox: + { + bOK = s_pWidgetImplementation->drawEditbox(aParameters, nWidth, nHeight); + } + break; + case ControlType::EditboxNoBorder: + { + bOK = s_pWidgetImplementation->drawEditbox(aParameters, nWidth, nHeight); + } + break; + case ControlType::MultilineEditbox: + { + bOK = s_pWidgetImplementation->drawEditbox(aParameters, nWidth, nHeight); + } + break; + case ControlType::Listbox: + { + bOK = s_pWidgetImplementation->drawListbox(aParameters, nWidth, nHeight); + } + break; + case ControlType::Spinbox: + { + if (rValue.getType() == ControlType::SpinButtons) + { + const SpinbuttonValue* pSpinVal = static_cast<const SpinbuttonValue*>(&rValue); + + ControlPart upBtnPart = pSpinVal->mnUpperPart; + ControlState upBtnState = pSpinVal->mnUpperState; + + ControlPart downBtnPart = pSpinVal->mnLowerPart; + ControlState downBtnState = pSpinVal->mnLowerState; + { + ControlDrawParameters aParametersUp{ pCairoContext, upBtnPart, upBtnState }; + cairo_save(pCairoContext); + cairo_translate(pCairoContext, + pSpinVal->maUpperRect.Left() - rControlRegion.Left(), + pSpinVal->maUpperRect.Top() - rControlRegion.Top()); + bOK = s_pWidgetImplementation->drawSpinbox(aParametersUp, + pSpinVal->maUpperRect.GetWidth(), + pSpinVal->maUpperRect.GetHeight()); + cairo_restore(pCairoContext); + } + + if (bOK) + { + ControlDrawParameters aParametersDown{ pCairoContext, downBtnPart, + downBtnState }; + cairo_save(pCairoContext); + cairo_translate(pCairoContext, + pSpinVal->maLowerRect.Left() - rControlRegion.Left(), + pSpinVal->maLowerRect.Top() - rControlRegion.Top()); + bOK = s_pWidgetImplementation->drawSpinbox(aParametersDown, + pSpinVal->maLowerRect.GetWidth(), + pSpinVal->maLowerRect.GetHeight()); + cairo_restore(pCairoContext); + } + } + else + { + bOK = s_pWidgetImplementation->drawSpinbox(aParameters, nWidth, nHeight); + } + } + break; + case ControlType::SpinButtons: + { + bOK = s_pWidgetImplementation->drawSpinButtons(aParameters, nWidth, nHeight); + } + break; + case ControlType::TabItem: + { + bOK = s_pWidgetImplementation->drawTabItem(aParameters, nWidth, nHeight); + } + break; + case ControlType::TabPane: + { + bOK = s_pWidgetImplementation->drawTabPane(aParameters, nWidth, nHeight); + } + break; + case ControlType::TabHeader: + { + bOK = s_pWidgetImplementation->drawTabHeader(aParameters, nWidth, nHeight); + } + break; + case ControlType::TabBody: + { + bOK = s_pWidgetImplementation->drawTabBody(aParameters, nWidth, nHeight); + } + break; + case ControlType::Scrollbar: + { + bOK = s_pWidgetImplementation->drawScrollbar(aParameters, nWidth, nHeight); + } + break; + case ControlType::Slider: + { + cairo_save(pCairoContext); + bOK = s_pWidgetImplementation->drawSlider(aParameters, nWidth, nHeight); + cairo_restore(pCairoContext); + + if (bOK) + { + const SliderValue* pSliderValue = static_cast<const SliderValue*>(&rValue); + + ControlDrawParameters aParametersButton{ pCairoContext, ControlPart::Button, + eState | pSliderValue->mnThumbState }; + cairo_save(pCairoContext); + cairo_translate(pCairoContext, + pSliderValue->maThumbRect.Left() - rControlRegion.Left(), + pSliderValue->maThumbRect.Top() - rControlRegion.Top()); + bOK = s_pWidgetImplementation->drawSlider(aParametersButton, + pSliderValue->maThumbRect.GetWidth(), + pSliderValue->maThumbRect.GetHeight()); + cairo_restore(pCairoContext); + } + } + break; + case ControlType::Fixedline: + { + bOK = s_pWidgetImplementation->drawFixedline(aParameters, nWidth, nHeight); + } + break; + case ControlType::Toolbar: + { + bOK = s_pWidgetImplementation->drawToolbar(aParameters, nWidth, nHeight); + } + break; + case ControlType::Menubar: + break; + case ControlType::MenuPopup: + break; + case ControlType::Progress: + { + aParameters.nValue = rValue.getNumericVal(); + bOK = s_pWidgetImplementation->drawProgress(aParameters, nWidth, nHeight); + } + break; + case ControlType::IntroProgress: + break; + case ControlType::Tooltip: + break; + case ControlType::WindowBackground: + { + bOK = s_pWidgetImplementation->drawWindowsBackground(aParameters, nWidth, nHeight); + } + break; + case ControlType::Frame: + { + bOK = s_pWidgetImplementation->drawFrame(aParameters, nWidth, nHeight); + } + break; + case ControlType::ListNode: + { + aParameters.eButtonValue = rValue.getTristateVal(); + bOK = s_pWidgetImplementation->drawListNode(aParameters, nWidth, nHeight); + } + break; + case ControlType::ListNet: + { + bOK = s_pWidgetImplementation->drawListNet(aParameters, nWidth, nHeight); + } + break; + case ControlType::ListHeader: + { + bOK = s_pWidgetImplementation->drawListHeader(aParameters, nWidth, nHeight); + } + break; + } + + basegfx::B2DRange aExtents(rControlRegion.Left(), rControlRegion.Top(), rControlRegion.Right(), + rControlRegion.Bottom()); + + m_rGraphics.releaseCairoContext(pCairoContext, true, aExtents); + + m_rGraphics.setAntiAliasB2DDraw(bOldAA); + + return bOK; +} + +bool CustomWidgetDraw::getNativeControlRegion( + ControlType eType, ControlPart ePart, const tools::Rectangle& rBoundingControlRegion, + ControlState eState, const ImplControlValue& /*aValue*/, const OUString& /*aCaption*/, + tools::Rectangle& rNativeBoundingRegion, tools::Rectangle& rNativeContentRegion) +{ + // Translate to POD rectangle and back. + const rectangle_t aRegion + = { rBoundingControlRegion.getX(), rBoundingControlRegion.getY(), + rBoundingControlRegion.GetWidth(), rBoundingControlRegion.GetHeight() }; + if (s_pWidgetImplementation) + { + rectangle_t aNativeBoundingRegion; + rectangle_t aNativeContentRegion; + s_pWidgetImplementation->getRegion(eType, ePart, eState, aRegion, aNativeBoundingRegion, + aNativeContentRegion); + + rNativeBoundingRegion + = tools::Rectangle(aNativeBoundingRegion.x, aNativeBoundingRegion.y, + aNativeBoundingRegion.width, aNativeBoundingRegion.height); + rNativeContentRegion + = tools::Rectangle(aNativeBoundingRegion.x, aNativeBoundingRegion.y, + aNativeBoundingRegion.width, aNativeBoundingRegion.height); + } + + return false; +} + +bool CustomWidgetDraw::updateSettings(AllSettings& rSettings) +{ + if (!s_pWidgetImplementation) + return false; + + WidgetDrawStyle aStyle; + aStyle.nSize = sizeof(WidgetDrawStyle); + + if (s_pWidgetImplementation->updateSettings(aStyle)) + { + StyleSettings aStyleSet = rSettings.GetStyleSettings(); + + aStyleSet.SetFaceColor(aStyle.maFaceColor); + aStyleSet.SetCheckedColor(aStyle.maCheckedColor); + aStyleSet.SetLightColor(aStyle.maLightColor); + aStyleSet.SetLightBorderColor(aStyle.maLightBorderColor); + aStyleSet.SetShadowColor(aStyle.maShadowColor); + aStyleSet.SetDarkShadowColor(aStyle.maDarkShadowColor); + aStyleSet.SetDefaultButtonTextColor(aStyle.maDefaultButtonTextColor); + aStyleSet.SetButtonTextColor(aStyle.maButtonTextColor); + aStyleSet.SetDefaultActionButtonTextColor(aStyle.maDefaultActionButtonTextColor); + aStyleSet.SetActionButtonTextColor(aStyle.maActionButtonTextColor); + aStyleSet.SetFlatButtonTextColor(aStyle.maFlatButtonTextColor); + aStyleSet.SetDefaultButtonRolloverTextColor(aStyle.maDefaultButtonRolloverTextColor); + aStyleSet.SetButtonRolloverTextColor(aStyle.maButtonRolloverTextColor); + aStyleSet.SetDefaultActionButtonRolloverTextColor( + aStyle.maDefaultActionButtonRolloverTextColor); + aStyleSet.SetActionButtonRolloverTextColor(aStyle.maActionButtonRolloverTextColor); + aStyleSet.SetFlatButtonRolloverTextColor(aStyle.maFlatButtonRolloverTextColor); + aStyleSet.SetDefaultButtonPressedRolloverTextColor( + aStyle.maDefaultButtonPressedRolloverTextColor); + aStyleSet.SetButtonPressedRolloverTextColor(aStyle.maButtonPressedRolloverTextColor); + aStyleSet.SetDefaultActionButtonPressedRolloverTextColor( + aStyle.maDefaultActionButtonPressedRolloverTextColor); + aStyleSet.SetActionButtonPressedRolloverTextColor( + aStyle.maActionButtonPressedRolloverTextColor); + aStyleSet.SetFlatButtonPressedRolloverTextColor( + aStyle.maFlatButtonPressedRolloverTextColor); + aStyleSet.SetRadioCheckTextColor(aStyle.maRadioCheckTextColor); + aStyleSet.SetGroupTextColor(aStyle.maGroupTextColor); + aStyleSet.SetLabelTextColor(aStyle.maLabelTextColor); + aStyleSet.SetWindowColor(aStyle.maWindowColor); + aStyleSet.SetWindowTextColor(aStyle.maWindowTextColor); + aStyleSet.SetDialogColor(aStyle.maDialogColor); + aStyleSet.SetDialogTextColor(aStyle.maDialogTextColor); + aStyleSet.SetWorkspaceColor(aStyle.maWorkspaceColor); + aStyleSet.SetMonoColor(aStyle.maMonoColor); + aStyleSet.SetFieldColor(Color(aStyle.maFieldColor)); + aStyleSet.SetFieldTextColor(aStyle.maFieldTextColor); + aStyleSet.SetFieldRolloverTextColor(aStyle.maFieldRolloverTextColor); + aStyleSet.SetActiveColor(aStyle.maActiveColor); + aStyleSet.SetActiveTextColor(aStyle.maActiveTextColor); + aStyleSet.SetActiveBorderColor(aStyle.maActiveBorderColor); + aStyleSet.SetDeactiveColor(aStyle.maDeactiveColor); + aStyleSet.SetDeactiveTextColor(aStyle.maDeactiveTextColor); + aStyleSet.SetDeactiveBorderColor(aStyle.maDeactiveBorderColor); + aStyleSet.SetMenuColor(aStyle.maMenuColor); + aStyleSet.SetMenuBarColor(aStyle.maMenuBarColor); + aStyleSet.SetMenuBarRolloverColor(aStyle.maMenuBarRolloverColor); + aStyleSet.SetMenuBorderColor(aStyle.maMenuBorderColor); + aStyleSet.SetMenuTextColor(aStyle.maMenuTextColor); + aStyleSet.SetMenuBarTextColor(aStyle.maMenuBarTextColor); + aStyleSet.SetMenuBarRolloverTextColor(aStyle.maMenuBarRolloverTextColor); + aStyleSet.SetMenuBarHighlightTextColor(aStyle.maMenuBarHighlightTextColor); + aStyleSet.SetMenuHighlightColor(aStyle.maMenuHighlightColor); + aStyleSet.SetMenuHighlightTextColor(aStyle.maMenuHighlightTextColor); + aStyleSet.SetHighlightColor(aStyle.maHighlightColor); + aStyleSet.SetHighlightTextColor(aStyle.maHighlightTextColor); + aStyleSet.SetActiveTabColor(aStyle.maActiveTabColor); + aStyleSet.SetInactiveTabColor(aStyle.maInactiveTabColor); + aStyleSet.SetTabTextColor(aStyle.maTabTextColor); + aStyleSet.SetTabRolloverTextColor(aStyle.maTabRolloverTextColor); + aStyleSet.SetTabHighlightTextColor(aStyle.maTabHighlightTextColor); + aStyleSet.SetDisableColor(aStyle.maDisableColor); + aStyleSet.SetHelpColor(aStyle.maHelpColor); + aStyleSet.SetHelpTextColor(aStyle.maHelpTextColor); + aStyleSet.SetLinkColor(aStyle.maLinkColor); + aStyleSet.SetVisitedLinkColor(aStyle.maVisitedLinkColor); + aStyleSet.SetToolTextColor(aStyle.maToolTextColor); + aStyleSet.SetFontColor(aStyle.maFontColor); + + rSettings.SetStyleSettings(aStyleSet); + + return true; + } + + return false; +} + +} // end vcl namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/headlessinst.cxx b/vcl/headless/headlessinst.cxx new file mode 100644 index 000000000..e1694c429 --- /dev/null +++ b/vcl/headless/headlessinst.cxx @@ -0,0 +1,101 @@ +/* -*- 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 <unx/gendata.hxx> + +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(); +} + +class HeadlessSalData : public GenericUnixSalData +{ +public: + explicit HeadlessSalData( SalInstance *pInstance ) : GenericUnixSalData( SAL_DATA_HEADLESS, pInstance ) {} + virtual void ErrorTrapPush() override {} + virtual bool ErrorTrapPop( bool ) override { return false; } +}; + +void SalAbort( const OUString& rErrorText, bool bDumpCore ) +{ + OUString aError( rErrorText ); + if( aError.isEmpty() ) + aError = "Unknown application error"; + + SAL_WARN("vcl.headless", rErrorText); + SAL_INFO("vcl.headless", "SalAbort: '" << aError << "'."); + + if( bDumpCore ) + abort(); + else + _exit(1); +} + +const OUString& SalGetDesktopEnvironment() +{ + static OUString aEnv( "headless" ); + return aEnv; +} + +SalData::SalData() : + m_pInstance( nullptr ), + m_pPIManager( nullptr ) +{ +} + +SalData::~SalData() +{ +} + +// This is our main entry point: +SalInstance *CreateSalInstance() +{ + HeadlessSalInstance* pInstance = new HeadlessSalInstance(std::make_unique<SvpSalYieldMutex>()); + new HeadlessSalData( pInstance ); + pInstance->AcquireYieldMutex(); + return pInstance; +} + +void DestroySalInstance( SalInstance *pInst ) +{ + pInst->ReleaseYieldMutexAll(); + delete pInst; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/svpbmp.cxx b/vcl/headless/svpbmp.cxx new file mode 100644 index 000000000..4d881f025 --- /dev/null +++ b/vcl/headless/svpbmp.cxx @@ -0,0 +1,283 @@ +/* -*- 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() +: SalBitmap(), + basegfx::SystemDependentDataHolder(), // MM02 + mpDIB() +{ +} + +SvpSalBitmap::~SvpSalBitmap() +{ + Destroy(); +} + +static std::unique_ptr<BitmapBuffer> ImplCreateDIB( + const Size& rSize, + sal_uInt16 nBitCount, + const BitmapPalette& rPal) +{ + assert( + (nBitCount == 0 + || nBitCount == 1 + || nBitCount == 4 + || nBitCount == 8 + || nBitCount == 24 + || nBitCount == 32) + && "Unsupported BitCount!"); + + if (!rSize.Width() || !rSize.Height()) + return nullptr; + + std::unique_ptr<BitmapBuffer> pDIB; + + try + { + pDIB.reset(new BitmapBuffer); + } + catch (const std::bad_alloc&) + { + return nullptr; + } + + const sal_uInt16 nColors = ( nBitCount <= 8 ) ? ( 1 << nBitCount ) : 0; + + switch (nBitCount) + { + case 1: + pDIB->mnFormat = ScanlineFormat::N1BitLsbPal; + break; + case 4: + pDIB->mnFormat = ScanlineFormat::N4BitMsnPal; + break; + case 8: + pDIB->mnFormat = ScanlineFormat::N8BitPal; + break; + case 24: + pDIB->mnFormat = SVP_24BIT_FORMAT; + break; + default: + nBitCount = 32; + [[fallthrough]]; + case 32: + pDIB->mnFormat = SVP_CAIRO_FORMAT; + break; + } + + pDIB->mnFormat |= ScanlineFormat::TopDown; + pDIB->mnWidth = rSize.Width(); + pDIB->mnHeight = rSize.Height(); + long nScanlineBase; + bool bFail = o3tl::checked_multiply<long>(pDIB->mnWidth, nBitCount, nScanlineBase); + if (bFail) + { + SAL_WARN("vcl.gdi", "checked multiply failed"); + return nullptr; + } + pDIB->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase); + if (pDIB->mnScanlineSize < nScanlineBase/8) + { + SAL_WARN("vcl.gdi", "scanline calculation wraparound"); + return nullptr; + } + pDIB->mnBitCount = nBitCount; + + 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 nullptr; + } + + 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 + { + std::memset(pDIB->mpBits, 0, size); + } + } + catch (const std::bad_alloc&) + { + pDIB.reset(); + } + + return pDIB; +} + +void SvpSalBitmap::Create(std::unique_ptr<BitmapBuffer> pBuf) +{ + Destroy(); + mpDIB = std::move(pBuf); +} + +bool SvpSalBitmap::Create(const Size& rSize, sal_uInt16 nBitCount, const BitmapPalette& rPal) +{ + Destroy(); + mpDIB = ImplCreateDIB( rSize, nBitCount, rPal ); + return mpDIB != nullptr; +} + +bool SvpSalBitmap::Create(const SalBitmap& rBmp) +{ + Destroy(); + + const SvpSalBitmap& rSalBmp = static_cast<const SvpSalBitmap&>(rBmp); + + if (rSalBmp.mpDIB) + { + // TODO: reference counting... + mpDIB.reset(new BitmapBuffer( *rSalBmp.mpDIB )); + + const size_t size = mpDIB->mnScanlineSize * mpDIB->mnHeight; + if (size > SAL_MAX_INT32/2) + { + mpDIB.reset(); + return false; + } + + // TODO: get rid of this when BitmapBuffer gets copy constructor + try + { + mpDIB->mpBits = new sal_uInt8[size]; + std::memcpy(mpDIB->mpBits, rSalBmp.mpDIB->mpBits, size); + } + catch (const std::bad_alloc&) + { + mpDIB.reset(); + } + } + + return !rSalBmp.mpDIB || (mpDIB != nullptr); +} + +bool SvpSalBitmap::Create( const SalBitmap& /*rSalBmp*/, + SalGraphics* /*pGraphics*/ ) +{ + return false; +} + +bool SvpSalBitmap::Create( const SalBitmap& /*rSalBmp*/, + sal_uInt16 /*nNewBitCount*/ ) +{ + return false; +} + +bool SvpSalBitmap::Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& /*xBitmapCanvas*/, Size& /*rSize*/, bool /*bMask*/ ) +{ + return false; +} + +void SvpSalBitmap::Destroy() +{ + if (mpDIB) + { + delete[] mpDIB->mpBits; + mpDIB.reset(); + } +} + +Size SvpSalBitmap::GetSize() const +{ + Size aSize; + + if (mpDIB) + { + aSize.setWidth( mpDIB->mnWidth ); + aSize.setHeight( mpDIB->mnHeight ); + } + + return aSize; +} + +sal_uInt16 SvpSalBitmap::GetBitCount() const +{ + sal_uInt16 nBitCount; + + if (mpDIB) + nBitCount = mpDIB->mnBitCount; + else + nBitCount = 0; + + return nBitCount; +} + +BitmapBuffer* SvpSalBitmap::AcquireBuffer(BitmapAccessMode) +{ + return mpDIB.get(); +} + +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; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/svpcairotextrender.cxx b/vcl/headless/svpcairotextrender.cxx new file mode 100644 index 000000000..de62b9e50 --- /dev/null +++ b/vcl/headless/svpcairotextrender.cxx @@ -0,0 +1,40 @@ +/* -*- 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/svpcairotextrender.hxx> +#include <headless/svpgdi.hxx> +#include <cairo.h> + +SvpCairoTextRender::SvpCairoTextRender(SvpSalGraphics& rParent) + : mrParent(rParent) +{ +} + +cairo_t* SvpCairoTextRender::getCairoContext() +{ + return mrParent.getCairoContext(false); +} + +void SvpCairoTextRender::getSurfaceOffset(double& nDX, double& nDY) +{ + nDX = 0; + nDY = 0; +} + +void SvpCairoTextRender::clipRegion(cairo_t* cr) +{ + mrParent.clipRegion(cr); +} + +void SvpCairoTextRender::releaseCairoContext(cairo_t* cr) +{ + mrParent.releaseCairoContext(cr, false, basegfx::B2DRange()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/svpdata.cxx b/vcl/headless/svpdata.cxx new file mode 100644 index 000000000..4ac909c67 --- /dev/null +++ b/vcl/headless/svpdata.cxx @@ -0,0 +1,33 @@ +/* -*- 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 <unx/gendata.hxx> +#include <headless/svpinst.hxx> + +namespace { + +class SvpSalData : public GenericUnixSalData +{ +public: + explicit SvpSalData( SalInstance *pInstance ) : GenericUnixSalData( SAL_DATA_SVP, pInstance ) {} + virtual void ErrorTrapPush() override {} + virtual bool ErrorTrapPop( bool /*bIgnoreError*/ = true ) override { return false; } +}; + +} + +// plugin factory function +SalInstance* svp_create_SalInstance() +{ + SvpSalInstance* pInstance = new SvpSalInstance( std::make_unique<SvpSalYieldMutex>() ); + new SvpSalData( pInstance ); + return pInstance; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/svpdummies.cxx b/vcl/headless/svpdummies.cxx new file mode 100644 index 000000000..548868c05 --- /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( long, long, long, long ) {} +void SvpSalObject::EndSetClipRegion() {} +void SvpSalObject::SetPosSize( long, long, long, long ) {} +void SvpSalObject::Show( bool ) {} +const SystemEnvData* SvpSalObject::GetSystemData() const { return &m_aSystemChildData; } + +// SalSystem +SvpSalSystem::~SvpSalSystem() {} + +unsigned int SvpSalSystem::GetDisplayScreenCount() +{ + return 1; +} + +tools::Rectangle SvpSalSystem::GetDisplayScreenPosSizePixel( unsigned int nScreen ) +{ + tools::Rectangle aRect; + if( nScreen == 0 ) + aRect = tools::Rectangle( Point(0,0), Size(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 000000000..0f6da8d28 --- /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 <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 ) +{ + // SAL_DEBUG("SvpSalFrame::SvpSalFrame: " << this); +#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::list<SvpSalFrame*> Children = m_aChildren; + for( auto& rChild : Children ) + rChild->SetParent( m_pParent ); + if( m_pParent ) + m_pParent->m_aChildren.remove( this ); + + if( s_pFocusFrame == this ) + { + // SAL_DEBUG("SvpSalFrame::~SvpSalFrame: losing focus: " << 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( s_pFocusFrame == this ) + return; + + if( (m_nStyle & (SalFrameStyleFlags::OWNERDRAWDECORATION | SalFrameStyleFlags::FLOAT)) == SalFrameStyleFlags::NONE ) + { + if( s_pFocusFrame ) + s_pFocusFrame->LoseFocus(); + // SAL_DEBUG("SvpSalFrame::GetFocus(): " << this); + s_pFocusFrame = this; + m_pInstance->PostEvent( this, nullptr, SalEvent::GetFocus ); + } +} + +void SvpSalFrame::LoseFocus() +{ + if( s_pFocusFrame == this ) + { + // SAL_DEBUG("SvpSalFrame::LoseFocus: " << this); + m_pInstance->PostEvent( this, nullptr, SalEvent::LoseFocus ); + s_pFocusFrame = nullptr; + } +} + +SalGraphics* SvpSalFrame::AcquireGraphics() +{ + SvpSalGraphics* pGraphics = new SvpSalGraphics(); +#ifndef IOS + pGraphics->setSurface(m_pSurface, basegfx::B2IVector(maGeometry.nWidth, maGeometry.nHeight)); +#endif + m_aGraphics.push_back( pGraphics ); + return pGraphics; +} + +void SvpSalFrame::ReleaseGraphics( SalGraphics* pGraphics ) +{ + SvpSalGraphics* pSvpGraphics = dynamic_cast<SvpSalGraphics*>(pGraphics); + m_aGraphics.erase(std::remove(m_aGraphics.begin(), m_aGraphics.end(), pSvpGraphics), m_aGraphics.end()); + 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.nWidth, maGeometry.nHeight); + aPEvt.mbImmediateUpdate = false; + CallCallback( SalEvent::Paint, &aPEvt ); + } +} + +void SvpSalFrame::SetTitle( const OUString& ) +{ +} + +void SvpSalFrame::SetIcon( sal_uInt16 ) +{ +} + +void SvpSalFrame::SetMenu( SalMenu* ) +{ +} + +void SvpSalFrame::DrawMenuBar() +{ +} + +void SvpSalFrame::SetExtendedFrameStyle( SalExtStyle ) +{ +} + +void SvpSalFrame::Show( bool bVisible, bool bNoActivate ) +{ + if( bVisible && ! m_bVisible ) + { + // SAL_DEBUG("SvpSalFrame::Show: showing: " << this); + m_bVisible = true; + m_pInstance->PostEvent( this, nullptr, SalEvent::Resize ); + if( ! bNoActivate ) + GetFocus(); + } + else if( ! bVisible && m_bVisible ) + { + // SAL_DEBUG("SvpSalFrame::Show: hiding: " << this); + m_bVisible = false; + m_pInstance->PostEvent( this, nullptr, SalEvent::Resize ); + LoseFocus(); + } + else + { + // SAL_DEBUG("SvpSalFrame::Show: nothing: " << this); + } +} + +void SvpSalFrame::SetMinClientSize( long nWidth, long nHeight ) +{ + m_nMinWidth = nWidth; + m_nMinHeight = nHeight; +} + +void SvpSalFrame::SetMaxClientSize( long nWidth, long nHeight ) +{ + m_nMaxWidth = nWidth; + m_nMaxHeight = nHeight; +} + +void SvpSalFrame::SetPosSize( long nX, long nY, long nWidth, long nHeight, sal_uInt16 nFlags ) +{ + if( (nFlags & SAL_FRAME_POSSIZE_X) != 0 ) + maGeometry.nX = nX; + if( (nFlags & SAL_FRAME_POSSIZE_Y) != 0 ) + maGeometry.nY = nY; + if( (nFlags & SAL_FRAME_POSSIZE_WIDTH) != 0 ) + { + maGeometry.nWidth = nWidth; + if( m_nMaxWidth > 0 && maGeometry.nWidth > o3tl::make_unsigned(m_nMaxWidth) ) + maGeometry.nWidth = m_nMaxWidth; + if( m_nMinWidth > 0 && maGeometry.nWidth < o3tl::make_unsigned(m_nMinWidth) ) + maGeometry.nWidth = m_nMinWidth; + } + if( (nFlags & SAL_FRAME_POSSIZE_HEIGHT) != 0 ) + { + maGeometry.nHeight = nHeight; + if( m_nMaxHeight > 0 && maGeometry.nHeight > o3tl::make_unsigned(m_nMaxHeight) ) + maGeometry.nHeight = m_nMaxHeight; + if( m_nMinHeight > 0 && maGeometry.nHeight < o3tl::make_unsigned(m_nMinHeight) ) + maGeometry.nHeight = m_nMinHeight; + } +#ifndef IOS + basegfx::B2IVector aFrameSize( maGeometry.nWidth, maGeometry.nHeight ); + if (!m_pSurface || cairo_image_surface_get_width(m_pSurface) != aFrameSize.getX() || + cairo_image_surface_get_height(m_pSurface) != aFrameSize.getY() ) + { + if( aFrameSize.getX() == 0 ) + aFrameSize.setX( 1 ); + if( aFrameSize.getY() == 0 ) + aFrameSize.setY( 1 ); + + if (m_pSurface) + cairo_surface_destroy(m_pSurface); + + // Creating backing surfaces for invisible windows costs a big chunk of RAM. + if (Application::IsHeadlessModeEnabled()) + aFrameSize = basegfx::B2IVector( 1, 1 ); + + 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( long& rWidth, long& rHeight ) +{ + rWidth = maGeometry.nWidth; + rHeight = maGeometry.nHeight; +} + +void SvpSalFrame::GetWorkArea( tools::Rectangle& rRect ) +{ + rRect = tools::Rectangle( Point( 0, 0 ), + Size( VIRTUAL_DESKTOP_WIDTH, VIRTUAL_DESKTOP_HEIGHT ) ); +} + +SalFrame* SvpSalFrame::GetParent() const +{ + return m_pParent; +} + +static constexpr auto FRAMESTATE_MASK_GEOMETRY = + WindowStateMask::X | WindowStateMask::Y | + WindowStateMask::Width | WindowStateMask::Height; + +void SvpSalFrame::SetWindowState( const SalFrameState *pState ) +{ + if (pState == nullptr) + return; + + // Request for position or size change + if (pState->mnMask & FRAMESTATE_MASK_GEOMETRY) + { + long nX = maGeometry.nX; + long nY = maGeometry.nY; + long nWidth = maGeometry.nWidth; + long nHeight = maGeometry.nHeight; + + // change requested properties + if (pState->mnMask & WindowStateMask::X) + nX = pState->mnX; + if (pState->mnMask & WindowStateMask::Y) + nY = pState->mnY; + if (pState->mnMask & WindowStateMask::Width) + nWidth = pState->mnWidth; + if (pState->mnMask & WindowStateMask::Height) + nHeight = pState->mnHeight; + + SetPosSize( nX, nY, nWidth, nHeight, + SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y | + SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT ); + } +} + +bool SvpSalFrame::GetWindowState( SalFrameState* pState ) +{ + pState->mnState = WindowStateState::Normal; + pState->mnX = maGeometry.nX; + pState->mnY = maGeometry.nY; + pState->mnWidth = maGeometry.nWidth; + pState->mnHeight = maGeometry.nHeight; + pState->mnMask = FRAMESTATE_MASK_GEOMETRY | WindowStateMask::State; + + 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 ) +{ + GetFocus(); +} + +void SvpSalFrame::SetPointer( PointerStyle ) +{ +} + +void SvpSalFrame::CaptureMouse( bool ) +{ +} + +void SvpSalFrame::SetPointerPos( long, 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.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 ) + m_pParent->m_aChildren.remove( this ); + m_pParent = static_cast<SvpSalFrame*>(pNewParent); +} + +bool SvpSalFrame::SetPluginParent( SystemParentData* ) +{ + return true; +} + +void SvpSalFrame::ResetClipRegion() +{ +} + +void SvpSalFrame::BeginSetClipRegion( sal_uInt32 ) +{ +} + +void SvpSalFrame::UnionClipRegion( long, long, long, 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 000000000..fadf641fd --- /dev/null +++ b/vcl/headless/svpgdi.cxx @@ -0,0 +1,2622 @@ +/* -*- 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 <headless/svpbmp.hxx> +#include <headless/svpframe.hxx> +#include <headless/svpcairotextrender.hxx> +#include <headless/CustomWidgetDraw.hxx> +#include <saldatabasic.hxx> + +#include <sal/log.hxx> +#include <tools/helpers.hxx> +#include <o3tl/safeint.hxx> +#include <vcl/BitmapTools.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/gradient.hxx> +#include <config_cairo_canvas.h> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/range/b2ibox.hxx> +#include <basegfx/range/b2irange.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/utils/systemdependentdata.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <comphelper/lok.hxx> +#include <unx/gendata.hxx> +#include <dlfcn.h> + +#if ENABLE_CAIRO_CANVAS +# if defined CAIRO_VERSION && CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 10, 0) +# define CAIRO_OPERATOR_DIFFERENCE (static_cast<cairo_operator_t>(23)) +# endif +#endif + +namespace +{ + 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 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 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; + } +} + +bool SvpSalGraphics::blendBitmap( const SalTwoRect&, const SalBitmap& /*rBitmap*/ ) +{ + SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::blendBitmap case"); + return false; +} + +bool SvpSalGraphics::blendAlphaBitmap( const SalTwoRect&, const SalBitmap&, const SalBitmap&, const SalBitmap& ) +{ + SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::blendAlphaBitmap case"); + return false; +} + +namespace +{ + 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; + } + + void Toggle1BitTransparency(const BitmapBuffer& rBuf) + { + assert(rBuf.maPalette.GetBestIndex(BitmapColor(COL_BLACK)) == 0); + // TODO: make upper layers use standard alpha + if (getCairoFormat(rBuf) == CAIRO_FORMAT_A1) + { + const int nImageSize = rBuf.mnHeight * rBuf.mnScanlineSize; + unsigned char* pDst = rBuf.mpBits; + for (int i = nImageSize; --i >= 0; ++pDst) + *pDst = ~*pDst; + } + } + + std::unique_ptr<BitmapBuffer> FastConvert24BitRgbTo32BitCairo(const BitmapBuffer* pSrc) + { + if (pSrc == nullptr) + return nullptr; + + assert(pSrc->mnFormat == SVP_24BIT_FORMAT); + const long nWidth = pSrc->mnWidth; + const long nHeight = pSrc->mnHeight; + std::unique_ptr<BitmapBuffer> pDst(new BitmapBuffer); + pDst->mnFormat = (ScanlineFormat::N32BitTcArgb | ScanlineFormat::TopDown); + pDst->mnWidth = nWidth; + pDst->mnHeight = nHeight; + pDst->mnBitCount = 32; + pDst->maColorMask = pSrc->maColorMask; + pDst->maPalette = pSrc->maPalette; + + long nScanlineBase; + const bool bFail = o3tl::checked_multiply<long>(pDst->mnBitCount, nWidth, nScanlineBase); + if (bFail) + { + SAL_WARN("vcl.gdi", "checked multiply failed"); + pDst->mpBits = nullptr; + return nullptr; + } + + pDst->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase); + if (pDst->mnScanlineSize < nScanlineBase/8) + { + SAL_WARN("vcl.gdi", "scanline calculation wraparound"); + pDst->mpBits = nullptr; + return nullptr; + } + + try + { + pDst->mpBits = new sal_uInt8[ pDst->mnScanlineSize * nHeight ]; + } + catch (const std::bad_alloc&) + { + // memory exception, clean up + pDst->mpBits = nullptr; + return nullptr; + } + + for (long y = 0; y < nHeight; ++y) + { + sal_uInt8* pS = pSrc->mpBits + y * pSrc->mnScanlineSize; + sal_uInt8* pD = pDst->mpBits + y * pDst->mnScanlineSize; + for (long x = 0; x < nWidth; ++x) + { +#if defined(ANDROID) && !HAVE_FEATURE_ANDROID_LOK + 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; + } + + // check for env var that decides for using downscale pattern + static const char* pDisableDownScale(getenv("SAL_DISABLE_CAIRO_DOWNSCALE")); + static bool bDisableDownScale(nullptr != pDisableDownScale); + + class SurfaceHelper + { + private: + cairo_surface_t* pSurface; + std::unordered_map<unsigned long long, cairo_surface_t*> maDownscaled; + + SurfaceHelper(const SurfaceHelper&) = delete; + SurfaceHelper& operator=(const SurfaceHelper&) = delete; + + cairo_surface_t* 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 + const unsigned long long key((nW * LONG_MAX) + 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); + + // did a version to scale self first that worked well, but wouuld'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; + } + + protected: + cairo_surface_t* implGetSurface() const { return pSurface; } + void implSetSurface(cairo_surface_t* pNew) { pSurface = pNew; } + + bool 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; + } + + public: + explicit SurfaceHelper() + : pSurface(nullptr), + maDownscaled() + { + } + ~SurfaceHelper() + { + cairo_surface_destroy(pSurface); + for(auto& candidate : maDownscaled) + { + cairo_surface_destroy(candidate.second); + } + } + cairo_surface_t* getSurface( + unsigned long nTargetWidth = 0, + unsigned long nTargetHeight = 0) 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); + } + }; + + class BitmapHelper : public SurfaceHelper + { + private: +#ifdef HAVE_CAIRO_FORMAT_RGB24_888 + const bool m_bForceARGB32; +#endif + SvpSalBitmap aTmpBmp; + + public: + explicit BitmapHelper( + const SalBitmap& rSourceBitmap, + const bool bForceARGB32 = false) + : SurfaceHelper(), +#ifdef HAVE_CAIRO_FORMAT_RGB24_888 + m_bForceARGB32(bForceARGB32), +#endif + aTmpBmp() + { + 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::unique_ptr<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(SvpSalGraphics::createCairoSurface(aTmpBmp.GetBuffer())); + } + else + { + implSetSurface(SvpSalGraphics::createCairoSurface(rSrcBmp.GetBuffer())); + } + } + void mark_dirty() + { + cairo_surface_mark_dirty(implGetSurface()); + } + unsigned char* 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; + } + }; + + sal_Int64 estimateUsageInBytesForSurfaceHelper(const SurfaceHelper* pHelper) + { + sal_Int64 nRetval(0); + + if(nullptr != pHelper) + { + cairo_surface_t* pSurface(pHelper->getSurface()); + + if(pSurface) + { + const long nStride(cairo_image_surface_get_stride(pSurface)); + const 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; + } + + class SystemDependentData_BitmapHelper : public basegfx::SystemDependentData + { + private: + std::shared_ptr<BitmapHelper> maBitmapHelper; + + public: + SystemDependentData_BitmapHelper( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + const std::shared_ptr<BitmapHelper>& rBitmapHelper) + : basegfx::SystemDependentData(rSystemDependentDataManager), + maBitmapHelper(rBitmapHelper) + { + } + + const std::shared_ptr<BitmapHelper>& getBitmapHelper() const { return maBitmapHelper; }; + virtual sal_Int64 estimateUsageInBytes() const override; + }; + + sal_Int64 SystemDependentData_BitmapHelper::estimateUsageInBytes() const + { + return estimateUsageInBytesForSurfaceHelper(maBitmapHelper.get()); + } + + class MaskHelper : public SurfaceHelper + { + private: + std::unique_ptr<unsigned char[]> pAlphaBits; + + public: + explicit MaskHelper(const SalBitmap& rAlphaBitmap) + : SurfaceHelper(), + pAlphaBits() + { + const SvpSalBitmap& rMask = static_cast<const SvpSalBitmap&>(rAlphaBitmap); + const BitmapBuffer* pMaskBuf = rMask.GetBuffer(); + + if (rAlphaBitmap.GetBitCount() == 8) + { + // the alpha values need to be inverted for Cairo + // so big stupid copy and invert here + const int nImageSize = pMaskBuf->mnHeight * pMaskBuf->mnScanlineSize; + pAlphaBits.reset( new unsigned char[nImageSize] ); + memcpy(pAlphaBits.get(), pMaskBuf->mpBits, nImageSize); + + // TODO: make upper layers use standard alpha + sal_uInt32* pLDst = reinterpret_cast<sal_uInt32*>(pAlphaBits.get()); + for( int i = nImageSize/sizeof(sal_uInt32); --i >= 0; ++pLDst ) + *pLDst = ~*pLDst; + assert(reinterpret_cast<unsigned char*>(pLDst) == pAlphaBits.get()+nImageSize); + + implSetSurface( + cairo_image_surface_create_for_data( + pAlphaBits.get(), + CAIRO_FORMAT_A8, + pMaskBuf->mnWidth, + pMaskBuf->mnHeight, + pMaskBuf->mnScanlineSize)); + } + else + { + // the alpha values need to be inverted for Cairo + // so big stupid copy and invert here + const int nImageSize = pMaskBuf->mnHeight * pMaskBuf->mnScanlineSize; + pAlphaBits.reset( new unsigned char[nImageSize] ); + memcpy(pAlphaBits.get(), pMaskBuf->mpBits, nImageSize); + + const sal_Int32 nBlackIndex = pMaskBuf->maPalette.GetBestIndex(BitmapColor(COL_BLACK)); + if (nBlackIndex == 0) + { + // TODO: make upper layers use standard alpha + unsigned char* pDst = pAlphaBits.get(); + for (int i = nImageSize; --i >= 0; ++pDst) + *pDst = ~*pDst; + } + + implSetSurface( + cairo_image_surface_create_for_data( + pAlphaBits.get(), + CAIRO_FORMAT_A1, + pMaskBuf->mnWidth, + pMaskBuf->mnHeight, + pMaskBuf->mnScanlineSize)); + } + } + }; + + class SystemDependentData_MaskHelper : public basegfx::SystemDependentData + { + private: + std::shared_ptr<MaskHelper> maMaskHelper; + + public: + SystemDependentData_MaskHelper( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + const std::shared_ptr<MaskHelper>& rMaskHelper) + : basegfx::SystemDependentData(rSystemDependentDataManager), + maMaskHelper(rMaskHelper) + { + } + + const std::shared_ptr<MaskHelper>& getMaskHelper() const { return maMaskHelper; }; + virtual sal_Int64 estimateUsageInBytes() const override; + }; + + sal_Int64 SystemDependentData_MaskHelper::estimateUsageInBytes() const + { + return estimateUsageInBytesForSurfaceHelper(maMaskHelper.get()); + } + + // MM02 decide to use buffers or not + static const char* pDisableMM02Goodies(getenv("SAL_DISABLE_MM02_GOODIES")); + static bool bUseBuffer(nullptr == pDisableMM02Goodies); + static 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) + { + const SvpSalBitmap& rSrcBmp(static_cast<const SvpSalBitmap&>(rSourceBitmap)); + pSystemDependentData_BitmapHelper = rSrcBmp.getSystemDependentData<SystemDependentData_BitmapHelper>(); + + if(pSystemDependentData_BitmapHelper) + { + // reuse buffered data + rSurface = pSystemDependentData_BitmapHelper->getBitmapHelper(); + } + } + + if(!rSurface) + { + // create data on-demand + rSurface = std::make_shared<BitmapHelper>(rSourceBitmap); + + if(bBufferSource) + { + // add to buffering mechanism to potentially reuse next time + const SvpSalBitmap& rSrcBmp(static_cast<const SvpSalBitmap&>(rSourceBitmap)); + rSrcBmp.addOrReplaceSystemDependentData<SystemDependentData_BitmapHelper>( + ImplGetSystemDependentDataManager(), + 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) + { + const SvpSalBitmap& rSrcBmp(static_cast<const SvpSalBitmap&>(rMaskBitmap)); + pSystemDependentData_MaskHelper = rSrcBmp.getSystemDependentData<SystemDependentData_MaskHelper>(); + + if(pSystemDependentData_MaskHelper) + { + // reuse buffered data + rMask = pSystemDependentData_MaskHelper->getMaskHelper(); + } + } + + if(!rMask) + { + // create data on-demand + rMask = std::make_shared<MaskHelper>(rMaskBitmap); + + if(bBufferMask) + { + // add to buffering mechanism to potentially reuse next time + const SvpSalBitmap& rSrcBmp(static_cast<const SvpSalBitmap&>(rMaskBitmap)); + rSrcBmp.addOrReplaceSystemDependentData<SystemDependentData_MaskHelper>( + ImplGetSystemDependentDataManager(), + rMask); + } + } + } +} + +bool SvpSalGraphics::drawAlphaBitmap( const SalTwoRect& rTR, const SalBitmap& rSourceBitmap, const SalBitmap& rAlphaBitmap ) +{ + if (rAlphaBitmap.GetBitCount() != 8 && rAlphaBitmap.GetBitCount() != 1) + { + SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap alpha depth case: " << rAlphaBitmap.GetBitCount()); + return false; + } + + // 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); + 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); + + //tdf#114117 when stretching a single pixel width/height source to fit an area + //set extend and filter to stretch it with simplest expected interpolation + if ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1)) + { + cairo_pattern_t* sourcepattern = cairo_get_source(cr); + cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT); + cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST); + cairo_pattern_set_extend(maskpattern, CAIRO_EXTEND_REPEAT); + cairo_pattern_set_filter(maskpattern, CAIRO_FILTER_NEAREST); + } + + //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 SvpSalGraphics::drawTransformedBitmap( + const basegfx::B2DPoint& rNull, + const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY, + const SalBitmap& rSourceBitmap, + const SalBitmap* pAlphaBitmap) +{ + if (pAlphaBitmap && pAlphaBitmap->GetBitCount() != 8 && pAlphaBitmap->GetBitCount() != 1) + { + SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap alpha depth case: " << pAlphaBitmap->GetBitCount()); + return false; + } + + // MM02 try to access buffered BitmapHelper + std::shared_ptr<BitmapHelper> aSurface; + tryToUseSourceBuffer(rSourceBitmap, aSurface); + const long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength())); + const 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); + 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 SvpSalGraphics::clipRegion(cairo_t* cr, const vcl::Region& rClipRegion) +{ + RectangleVector aRectangles; + if (!rClipRegion.IsEmpty()) + { + rClipRegion.GetRegionRectangles(aRectangles); + } + if (!aRectangles.empty()) + { + for (auto const& rectangle : aRectangles) + { + cairo_rectangle(cr, rectangle.Left(), rectangle.Top(), rectangle.GetWidth(), rectangle.GetHeight()); + } + cairo_clip(cr); + } +} + +void SvpSalGraphics::clipRegion(cairo_t* cr) +{ + SvpSalGraphics::clipRegion(cr, m_aClipRegion); +} + +bool SvpSalGraphics::drawAlphaRect(long nX, long nY, long nWidth, long nHeight, sal_uInt8 nTransparency) +{ + const bool bHasFill(m_aFillColor != SALCOLOR_NONE); + const bool bHasLine(m_aLineColor != SALCOLOR_NONE); + + if(!(bHasFill || bHasLine)) + { + return true; + } + + cairo_t* cr = getCairoContext(false); + 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_aFillColor, 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_aLineColor, 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; +} + +SvpSalGraphics::SvpSalGraphics() + : m_pSurface(nullptr) + , m_fScale(1.0) + , m_aLineColor(Color(0x00, 0x00, 0x00)) + , m_aFillColor(Color(0xFF, 0xFF, 0XFF)) + , m_ePaintMode(PaintMode::Over) + , m_aTextRenderImpl(*this) +{ + bool bLOKActive = comphelper::LibreOfficeKit::isActive(); + if (!initWidgetDrawBackends(bLOKActive)) + { + if (bLOKActive) + m_pWidgetDraw.reset(new vcl::CustomWidgetDraw(*this)); + } +} + +SvpSalGraphics::~SvpSalGraphics() +{ + ReleaseFonts(); +} + +void SvpSalGraphics::setSurface(cairo_surface_t* pSurface, const basegfx::B2IVector& rSize) +{ + m_pSurface = pSurface; + m_aFrameSize = rSize; + dl_cairo_surface_get_device_scale(pSurface, &m_fScale, nullptr); + ResetClipRegion(); +} + +void SvpSalGraphics::GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY ) +{ + rDPIX = rDPIY = 96; +} + +sal_uInt16 SvpSalGraphics::GetBitCount() const +{ + if (cairo_surface_get_content(m_pSurface) != CAIRO_CONTENT_COLOR_ALPHA) + return 1; + return 32; +} + +long SvpSalGraphics::GetGraphicsWidth() const +{ + return m_pSurface ? m_aFrameSize.getX() : 0; +} + +void SvpSalGraphics::ResetClipRegion() +{ + m_aClipRegion.SetNull(); +} + +bool SvpSalGraphics::setClipRegion( const vcl::Region& i_rClip ) +{ + m_aClipRegion = i_rClip; + return true; +} + +void SvpSalGraphics::SetLineColor() +{ + m_aLineColor = SALCOLOR_NONE; +} + +void SvpSalGraphics::SetLineColor( Color nColor ) +{ + m_aLineColor = nColor; +} + +void SvpSalGraphics::SetFillColor() +{ + m_aFillColor = SALCOLOR_NONE; +} + +void SvpSalGraphics::SetFillColor( Color nColor ) +{ + m_aFillColor = nColor; +} + +void SvpSalGraphics::SetXORMode(bool bSet, bool ) +{ + m_ePaintMode = bSet ? PaintMode::Xor : PaintMode::Over; +} + +void SvpSalGraphics::SetROPLineColor( SalROPColor nROPColor ) +{ + switch( nROPColor ) + { + case SalROPColor::N0: + m_aLineColor = Color(0, 0, 0); + break; + case SalROPColor::N1: + m_aLineColor = Color(0xff, 0xff, 0xff); + break; + case SalROPColor::Invert: + m_aLineColor = Color(0xff, 0xff, 0xff); + break; + } +} + +void SvpSalGraphics::SetROPFillColor( SalROPColor nROPColor ) +{ + switch( nROPColor ) + { + case SalROPColor::N0: + m_aFillColor = Color(0, 0, 0); + break; + case SalROPColor::N1: + m_aFillColor = Color(0xff, 0xff, 0xff); + break; + case SalROPColor::Invert: + m_aFillColor = Color(0xff, 0xff, 0xff); + break; + } +} + +void SvpSalGraphics::drawPixel( long nX, long nY ) +{ + if (m_aLineColor != SALCOLOR_NONE) + { + drawPixel(nX, nY, m_aLineColor); + } +} + +void SvpSalGraphics::drawPixel( long nX, long nY, Color aColor ) +{ + cairo_t* cr = getCairoContext(true); + clipRegion(cr); + + cairo_rectangle(cr, nX, nY, 1, 1); + applyColor(cr, aColor, 0.0); + cairo_fill(cr); + + basegfx::B2DRange extents = getClippedFillDamage(cr); + releaseCairoContext(cr, true, extents); +} + +void SvpSalGraphics::drawRect( long nX, long nY, long nWidth, long nHeight ) +{ + // because of the -1 hack we have to do fill and draw separately + Color aOrigFillColor = m_aFillColor; + Color aOrigLineColor = m_aLineColor; + m_aFillColor = SALCOLOR_NONE; + m_aLineColor = SALCOLOR_NONE; + + if (aOrigFillColor != SALCOLOR_NONE) + { + basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle(nX, nY, nX+nWidth, nY+nHeight)); + m_aFillColor = aOrigFillColor; + + drawPolyPolygon( + basegfx::B2DHomMatrix(), + basegfx::B2DPolyPolygon(aRect), + 0.0); + + m_aFillColor = SALCOLOR_NONE; + } + + if (aOrigLineColor != SALCOLOR_NONE) + { + // need same -1 hack as X11SalGraphicsImpl::drawRect + basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle( nX, nY, nX+nWidth-1, nY+nHeight-1)); + m_aLineColor = aOrigLineColor; + + drawPolyPolygon( + basegfx::B2DHomMatrix(), + basegfx::B2DPolyPolygon(aRect), + 0.0); + + m_aLineColor = SALCOLOR_NONE; + } + + m_aFillColor = aOrigFillColor; + m_aLineColor = aOrigLineColor; +} + +void SvpSalGraphics::drawPolyLine(sal_uInt32 nPoints, const SalPoint* pPtAry) +{ + basegfx::B2DPolygon aPoly; + aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints); + for (sal_uInt32 i = 1; i < nPoints; ++i) + aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY)); + aPoly.setClosed(false); + + drawPolyLine( + basegfx::B2DHomMatrix(), + aPoly, + 0.0, + 1.0, + nullptr, // MM01 + basegfx::B2DLineJoin::Miter, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0) /*default*/, + false); +} + +void SvpSalGraphics::drawPolygon(sal_uInt32 nPoints, const SalPoint* pPtAry) +{ + basegfx::B2DPolygon aPoly; + aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints); + for (sal_uInt32 i = 1; i < nPoints; ++i) + aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY)); + + drawPolyPolygon( + basegfx::B2DHomMatrix(), + basegfx::B2DPolyPolygon(aPoly), + 0.0); +} + +void SvpSalGraphics::drawPolyPolygon(sal_uInt32 nPoly, + const sal_uInt32* pPointCounts, + PCONSTSALPOINT* pPtAry) +{ + basegfx::B2DPolyPolygon aPolyPoly; + for(sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon) + { + sal_uInt32 nPoints = pPointCounts[nPolygon]; + if (nPoints) + { + PCONSTSALPOINT pPoints = pPtAry[nPolygon]; + basegfx::B2DPolygon aPoly; + aPoly.append( basegfx::B2DPoint(pPoints->mnX, pPoints->mnY), nPoints); + for (sal_uInt32 i = 1; i < nPoints; ++i) + aPoly.setB2DPoint(i, basegfx::B2DPoint( pPoints[i].mnX, pPoints[i].mnY)); + + aPolyPoly.append(aPoly); + } + } + + drawPolyPolygon( + basegfx::B2DHomMatrix(), + aPolyPoly, + 0.0); +} + +static basegfx::B2DPoint impPixelSnap( + const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DHomMatrix& rObjectToDevice, + basegfx::B2DHomMatrix& rObjectToDeviceInv, + sal_uInt32 nIndex) +{ + const sal_uInt32 nCount(rPolygon.count()); + + // get the data + const basegfx::B2ITuple aPrevTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount))); + const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex)); + const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint)); + const basegfx::B2ITuple aNextTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount))); + + // get the states + const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); + const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); + const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); + const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); + const bool bSnapX(bPrevVertical || bNextVertical); + const bool bSnapY(bPrevHorizontal || bNextHorizontal); + + if(bSnapX || bSnapY) + { + basegfx::B2DPoint aSnappedPoint( + bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(), + bSnapY ? aCurrTuple.getY() : aCurrPoint.getY()); + + if(rObjectToDeviceInv.isIdentity()) + { + rObjectToDeviceInv = rObjectToDevice; + rObjectToDeviceInv.invert(); + } + + aSnappedPoint *= rObjectToDeviceInv; + + return aSnappedPoint; + } + + return rPolygon.getB2DPoint(nIndex); +} + +// 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 +static 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; + + 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 = impPixelSnap(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; +} + +void SvpSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 ) +{ + 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 !getAntiAliasB2DDraw(), losing the (0.5, 0.5) offset + aPoly.append(basegfx::B2DPoint(nX1, nY1)); + aPoly.append(basegfx::B2DPoint(nX2, nY2)); + + cairo_t* cr = getCairoContext(false); + clipRegion(cr); + + // 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(), + !getAntiAliasB2DDraw(), + false); + + applyColor(cr, m_aLineColor); + + basegfx::B2DRange extents = getClippedStrokeDamage(cr); + extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5)); + + cairo_stroke(cr); + + releaseCairoContext(cr, false, extents); +} + +namespace { + +class SystemDependentData_CairoPath : public basegfx::SystemDependentData +{ +private: + // the path data itself + cairo_path_t* mpCairoPath; + + // all other values the path data is based on and + // need to be compared with to check for data validity + bool mbNoJoin; + bool mbAntiAliasB2DDraw; + std::vector< double > maStroke; + +public: + SystemDependentData_CairoPath( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + size_t nSizeMeasure, + cairo_t* cr, + bool bNoJoin, + bool bAntiAliasB2DDraw, + const std::vector< double >* pStroke); // MM01 + virtual ~SystemDependentData_CairoPath() override; + + // read access + cairo_path_t* getCairoPath() { return mpCairoPath; } + bool getNoJoin() const { return mbNoJoin; } + bool getAntiAliasB2DDraw() const { return mbAntiAliasB2DDraw; } + const std::vector< double >& getStroke() const { return maStroke; } + + virtual sal_Int64 estimateUsageInBytes() const override; +}; + +} + +SystemDependentData_CairoPath::SystemDependentData_CairoPath( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + size_t nSizeMeasure, + cairo_t* cr, + bool bNoJoin, + bool bAntiAliasB2DDraw, + const std::vector< double >* pStroke) +: basegfx::SystemDependentData(rSystemDependentDataManager), + mpCairoPath(nullptr), + mbNoJoin(bNoJoin), + mbAntiAliasB2DDraw(bAntiAliasB2DDraw), + maStroke() +{ + // tdf#129845 only create a copy of the path when nSizeMeasure is + // bigger than some decent threshold + if(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; +} + +bool SvpSalGraphics::drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolyLine, + double fTransparency, + double fLineWidth, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) +{ + // short circuit if there is nothing to do + if(0 == rPolyLine.count() || fTransparency < 0.0 || fTransparency >= 1.0) + { + return true; + } + + // Wrap call to static version of ::drawPolyLine by + // preparing/getting some local data and parameters + // due to usage in vcl/unx/generic/gdi/salgdi.cxx. + // This is mainly about extended handling of extents + // and the way destruction of CairoContext is handled + // due to current XOR stuff + cairo_t* cr = getCairoContext(false); + basegfx::B2DRange aExtents; + clipRegion(cr); + + bool bRetval( + drawPolyLine( + cr, + &aExtents, + m_aLineColor, + getAntiAliasB2DDraw(), + rObjectToDevice, + rPolyLine, + fTransparency, + fLineWidth, + pStroke, // MM01 + eLineJoin, + eLineCap, + fMiterMinimumAngle, + bPixelSnapHairline)); + + releaseCairoContext(cr, false, aExtents); + + return bRetval; +} + +bool SvpSalGraphics::drawPolyLine( + cairo_t* cr, + basegfx::B2DRange* pExtents, + const Color& rLineColor, + bool bAntiAliasB2DDraw, + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolyLine, + double fTransparency, + double fLineWidth, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) +{ + // short circuit if there is nothing to do + if(0 == rPolyLine.count() || fTransparency < 0.0 || fTransparency >= 1.0) + { + return true; + } + + // 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( fMiterMinimumAngle / 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, + rLineColor.GetRed()/255.0, + rLineColor.GetGreen()/255.0, + rLineColor.GetBlue()/255.0, + 1.0-fTransparency); + + cairo_set_line_join(cr, eCairoLineJoin); + cairo_set_line_cap(cr, eCairoLineCap); + 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 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->getAntiAliasB2DDraw() != bAntiAliasB2DDraw + || 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 + !bAntiAliasB2DDraw, + 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 + !bAntiAliasB2DDraw, + 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>( + ImplGetSystemDependentDataManager(), + nSizeMeasure, + cr, + bNoJoin, + bAntiAliasB2DDraw, + pStroke); + } + } + + // extract extents + if (pExtents) + { + *pExtents = getClippedStrokeDamage(cr); + // transform also extents (ranges) of damage so they can be correctly redrawn + pExtents->transform(aDamageMatrix); + } + + // draw and consume + cairo_stroke(cr); + + return true; +} + +bool SvpSalGraphics::drawPolyLineBezier( sal_uInt32, + const SalPoint*, + const PolyFlags* ) +{ + SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyLineBezier case"); + return false; +} + +bool SvpSalGraphics::drawPolygonBezier( sal_uInt32, + const SalPoint*, + const PolyFlags* ) +{ + SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolygonBezier case"); + return false; +} + +bool SvpSalGraphics::drawPolyPolygonBezier( sal_uInt32, + const sal_uInt32*, + const SalPoint* const*, + const PolyFlags* const* ) +{ + SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyPolygonBezier case"); + return false; +} + +namespace +{ + 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>( + ImplGetSystemDependentDataManager(), + nSizeMeasure, + cr, + false, + false, + nullptr); + } + } +} + +bool SvpSalGraphics::drawPolyPolygon( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fTransparency) +{ + const bool bHasFill(m_aFillColor != SALCOLOR_NONE); + const bool bHasLine(m_aLineColor != SALCOLOR_NONE); + + if(0 == rPolyPolygon.count() || !(bHasFill || bHasLine) || fTransparency < 0.0 || fTransparency >= 1.0) + { + return true; + } + + cairo_t* cr = getCairoContext(true); + 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, !getAntiAliasB2DDraw()); + + applyColor(cr, m_aFillColor, 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, !getAntiAliasB2DDraw()); + + applyColor(cr, m_aLineColor, 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); + + return true; +} + +bool SvpSalGraphics::implDrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon, SalGradient const & rGradient) +{ + cairo_t* cr = getCairoContext(true); + clipRegion(cr); + + basegfx::B2DHomMatrix rObjectToDevice; + + for (auto const & rPolygon : rPolyPolygon) + AddPolygonToPath(cr, rPolygon, rObjectToDevice, !getAntiAliasB2DDraw(), false); + + cairo_pattern_t* pattern; + 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 = (0xFF - rStop.maColor.GetTransparency()) / 255.0; + double offset = rStop.mfOffset; + + cairo_pattern_add_color_stop_rgba(pattern, offset, r, g, b, a); + } + cairo_set_source(cr, pattern); + + basegfx::B2DRange extents = getClippedFillDamage(cr); + cairo_fill_preserve(cr); + + releaseCairoContext(cr, true, extents); + + return true; +} + +void SvpSalGraphics::applyColor(cairo_t *cr, Color aColor, double fTransparency) +{ + if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_COLOR_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 SvpSalGraphics::copyArea( long nDestX, + long nDestY, + long nSrcX, + long nSrcY, + long nSrcWidth, + long nSrcHeight, + bool /*bWindowInvalidate*/ ) +{ + SalTwoRect aTR(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight); + copyBits(aTR, this); +} + +static 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); + double fXScale = 1.0f; + double fYScale = 1.0f; + if (rTR.mnSrcWidth != 0 && rTR.mnSrcHeight != 0) { + fXScale = static_cast<double>(rTR.mnDestWidth)/rTR.mnSrcWidth; + 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 ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1)) + { + cairo_pattern_t* sourcepattern = cairo_get_source(cr); + cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT); + cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST); + } + cairo_set_operator(cr, eOperator); + cairo_paint(cr); + cairo_restore(cr); + + return extents; +} + +static basegfx::B2DRange renderSource(cairo_t* cr, const SalTwoRect& rTR, + cairo_surface_t* source) +{ + return renderWithOperator(cr, rTR, source, CAIRO_OPERATOR_SOURCE); +} + +void SvpSalGraphics::copyWithOperator( const SalTwoRect& rTR, cairo_surface_t* source, + cairo_operator_t eOp ) +{ + cairo_t* cr = getCairoContext(false); + clipRegion(cr); + + basegfx::B2DRange extents = renderWithOperator(cr, rTR, source, eOp); + + releaseCairoContext(cr, false, extents); +} + +void SvpSalGraphics::copySource( const SalTwoRect& rTR, cairo_surface_t* source ) +{ + copyWithOperator(rTR, source, CAIRO_OPERATOR_SOURCE); +} + +void SvpSalGraphics::copyBits( const SalTwoRect& rTR, + SalGraphics* pSrcGraphics ) +{ + SalTwoRect aTR(rTR); + + SvpSalGraphics* pSrc = pSrcGraphics ? + static_cast<SvpSalGraphics*>(pSrcGraphics) : this; + + cairo_surface_t* source = pSrc->m_pSurface; + + cairo_surface_t *pCopy = nullptr; + if (pSrc == this) + { + //self copy is a problem, so dup source in that case + pCopy = cairo_surface_create_similar(source, + cairo_surface_get_content(m_pSurface), + 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, source, -aTR.mnSrcX, -aTR.mnSrcY); + cairo_rectangle(cr, 0, 0, aTR.mnSrcWidth, aTR.mnSrcHeight); + cairo_fill(cr); + cairo_destroy(cr); + + source = pCopy; + + aTR.mnSrcX = 0; + aTR.mnSrcY = 0; + } + + copySource(aTR, source); + + if (pCopy) + cairo_surface_destroy(pCopy); +} + +void SvpSalGraphics::drawBitmap(const SalTwoRect& rTR, const SalBitmap& rSourceBitmap) +{ + // 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; + } + + copyWithOperator(rTR, source, CAIRO_OPERATOR_OVER); +} + +void SvpSalGraphics::drawBitmap(const SalTwoRect& rTR, const BitmapBuffer* pBuffer, cairo_operator_t eOp) +{ + cairo_surface_t* source = createCairoSurface( pBuffer ); + copyWithOperator(rTR, source, eOp); + cairo_surface_destroy(source); +} + +void SvpSalGraphics::drawBitmap( const SalTwoRect& rTR, + const SalBitmap& rSourceBitmap, + const SalBitmap& rTransparentBitmap ) +{ + drawAlphaBitmap(rTR, rSourceBitmap, rTransparentBitmap); +} + +void SvpSalGraphics::drawMask( const SalTwoRect& rTR, + const SalBitmap& rSalBitmap, + Color nMaskColor ) +{ + /** 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); + vcl::bitmap::lookup_table unpremultiply_table = vcl::bitmap::get_unpremultiply_table(); + for (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 (long x = rTR.mnSrcX; x < rTR.mnSrcX + rTR.mnSrcWidth; ++x) + { + sal_uInt8 a = data[SVP_CAIRO_ALPHA]; + 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]]; + 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); + 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 ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1)) + { + cairo_pattern_t* sourcepattern = cairo_get_source(cr); + cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT); + cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST); + } + cairo_paint(cr); + + releaseCairoContext(cr, false, extents); +} + +std::shared_ptr<SalBitmap> SvpSalGraphics::getBitmap( long nX, long nY, long nWidth, long nHeight ) +{ + std::shared_ptr<SvpSalBitmap> pBitmap = std::make_shared<SvpSalBitmap>(); + BitmapPalette aPal; + if (GetBitCount() == 1) + { + aPal.SetEntryCount(2); + aPal[0] = COL_BLACK; + aPal[1] = COL_WHITE; + } + + if (!pBitmap->Create(Size(nWidth, nHeight), GetBitCount(), aPal)) + { + SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create bitmap"); + return nullptr; + } + + cairo_surface_t* target = SvpSalGraphics::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); + renderSource(cr, aTR, m_pSurface); + + cairo_destroy(cr); + cairo_surface_destroy(target); + + Toggle1BitTransparency(*pBitmap->GetBuffer()); + + return pBitmap; +} + +Color SvpSalGraphics::getPixel( long nX, long nY ) +{ +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) + cairo_surface_t *target = cairo_surface_create_similar_image(m_pSurface, CAIRO_FORMAT_ARGB32, 1, 1); +#else + cairo_surface_t *target = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); +#endif + + cairo_t* cr = cairo_create(target); + + cairo_rectangle(cr, 0, 0, 1, 1); + cairo_set_source_surface(cr, m_pSurface, -nX, -nY); + cairo_paint(cr); + cairo_destroy(cr); + + cairo_surface_flush(target); + vcl::bitmap::lookup_table unpremultiply_table = vcl::bitmap::get_unpremultiply_table(); + unsigned char *data = cairo_image_surface_get_data(target); + sal_uInt8 a = data[SVP_CAIRO_ALPHA]; + 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]]; + Color aColor(0xFF - a, r, g, b); + cairo_surface_destroy(target); + + return aColor; +} + +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; + } +} + +void SvpSalGraphics::invert(const basegfx::B2DPolygon &rPoly, SalInvert nFlags) +{ + cairo_t* cr = getCairoContext(false); + clipRegion(cr); + + // To make releaseCairoContext work, use empty extents + basegfx::B2DRange extents; + + AddPolygonToPath( + cr, + rPoly, + basegfx::B2DHomMatrix(), + !getAntiAliasB2DDraw(), + false); + + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + + if (cairo_version() >= CAIRO_VERSION_ENCODE(1, 10, 0)) + { + cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE); + } + else + { + SAL_WARN("vcl.gdi", "SvpSalGraphics::invert, archaic cairo"); + } + + 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 SvpSalGraphics::invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags ) +{ + basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle(nX, nY, nX+nWidth, nY+nHeight)); + + invert(aRect, nFlags); +} + +void SvpSalGraphics::invert(sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags) +{ + basegfx::B2DPolygon aPoly; + aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints); + for (sal_uInt32 i = 1; i < nPoints; ++i) + aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY)); + aPoly.setClosed(true); + + invert(aPoly, nFlags); +} + +bool SvpSalGraphics::drawEPS( long, long, long, long, void*, sal_uInt32 ) +{ + return false; +} + +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* SvpSalGraphics::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; +} + +cairo_t* SvpSalGraphics::createTmpCompatibleCairoContext() const +{ +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) + cairo_surface_t *target = cairo_surface_create_similar_image(m_pSurface, +#else + cairo_surface_t *target = cairo_image_surface_create( +#endif + 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); +} + +cairo_t* SvpSalGraphics::getCairoContext(bool bXorModeAllowed) 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, getAntiAliasB2DDraw() ? 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; +} + +cairo_user_data_key_t* SvpSalGraphics::getDamageKey() +{ + static cairo_user_data_key_t aDamageKey; + return &aDamageKey; +} + +void SvpSalGraphics::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) + { + 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 + cairo_t* copycr = createTmpCompatibleCairoContext(); + cairo_rectangle(copycr, nExtentsLeft, nExtentsTop, + nExtentsRight - nExtentsLeft, + nExtentsBottom - nExtentsTop); + cairo_set_source_surface(copycr, m_pSurface, 0, 0); + cairo_paint(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; + vcl::bitmap::lookup_table unpremultiply_table = vcl::bitmap::get_unpremultiply_table(); + vcl::bitmap::lookup_table premultiply_table = vcl::bitmap::get_premultiply_table(); + 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]; + 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]; + true_data+=4; + xor_data+=4; + } + } + cairo_surface_mark_dirty(target_surface); + + if (target_surface != m_pSurface) + { + cairo_t* copycr = cairo_create(m_pSurface); + //unlikely case we couldn't use m_pSurface directly, 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_paint(copycr); + cairo_destroy(copycr); + cairo_surface_destroy(target_surface); + } + + cairo_surface_destroy(surface); + } + + 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); + } +} + +#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 +{ + return SystemGraphicsData(); +} + +bool SvpSalGraphics::supportsOperation(OutDevSupportType eType) const +{ + switch (eType) + { + case OutDevSupportType::TransparentRect: + case OutDevSupportType::B2DDraw: + return true; + } + return false; +} + +void dl_cairo_surface_set_device_scale(cairo_surface_t *surface, double x_scale, double y_scale) +{ +#ifdef ANDROID + cairo_surface_set_device_scale(surface, x_scale, y_scale); +#else + static auto func = reinterpret_cast<void(*)(cairo_surface_t*, double, double)>( + dlsym(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) +{ +#ifdef ANDROID + cairo_surface_get_device_scale(surface, x_scale, y_scale); +#else + static auto func = reinterpret_cast<void(*)(cairo_surface_t*, double*, double*)>( + dlsym(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 +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/svpinst.cxx b/vcl/headless/svpinst.cxx new file mode 100644 index 000000000..daaa4d170 --- /dev/null +++ b/vcl/headless/svpinst.cxx @@ -0,0 +1,627 @@ +/* -*- 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 <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <sys/time.h> +#include <sys/poll.h> + +#include <sal/types.h> +#include <sal/log.hxx> + +#include <vcl/virdev.hxx> +#include <vcl/inputtypes.hxx> +#include <vcl/lok.hxx> +#if HAVE_FEATURE_UI +# include <vcl/opengl/OpenGLContext.hxx> +#endif + +#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 <headless/svpgdi.hxx> +#endif +#include <headless/svpbmp.hxx> + +#include <salframe.hxx> +#include <svdata.hxx> +#include <unx/gendata.hxx> +// FIXME: remove when we re-work the svp mainloop +#include <unx/salunxtime.h> +#include <comphelper/lok.hxx> + +SvpSalInstance* SvpSalInstance::s_pDefaultInstance = nullptr; + +#if !defined(ANDROID) && !defined(IOS) + +static void atfork_child() +{ + if (SvpSalInstance::s_pDefaultInstance != nullptr) + { + SvpSalInstance::s_pDefaultInstance->CloseWakeupPipe(false); + SvpSalInstance::s_pDefaultInstance->CreateWakeupPipe(false); + } +} + +#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(); + CreateWakeupPipe(true); + if( s_pDefaultInstance == nullptr ) + s_pDefaultInstance = this; +#if !defined(ANDROID) && !defined(IOS) + pthread_atfork(nullptr, nullptr, atfork_child); +#endif +} + +SvpSalInstance::~SvpSalInstance() +{ + if( s_pDefaultInstance == this ) + s_pDefaultInstance = nullptr; + CloseWakeupPipe(true); +} + +void SvpSalInstance::CloseWakeupPipe(bool log) +{ + SvpSalYieldMutex *const pMutex(dynamic_cast<SvpSalYieldMutex*>(GetYieldMutex())); + if (!pMutex) + return; + if (pMutex->m_FeedbackFDs[0] != -1) + { + if (log) + { + SAL_INFO("vcl.headless", "CloseWakeupPipe: Closing inherited feedback pipe: [" << pMutex->m_FeedbackFDs[0] << "," << pMutex->m_FeedbackFDs[1] << "]"); + } + close (pMutex->m_FeedbackFDs[0]); + close (pMutex->m_FeedbackFDs[1]); + pMutex->m_FeedbackFDs[0] = pMutex->m_FeedbackFDs[1] = -1; + } +} + +void SvpSalInstance::CreateWakeupPipe(bool log) +{ + SvpSalYieldMutex *const pMutex(dynamic_cast<SvpSalYieldMutex*>(GetYieldMutex())); + if (!pMutex) + return; + if (pipe (pMutex->m_FeedbackFDs) == -1) + { + if (log) + { + SAL_WARN("vcl.headless", "Could not create feedback pipe: " << strerror(errno)); + std::abort(); + } + } + else + { + if (log) + { + SAL_INFO("vcl.headless", "CreateWakeupPipe: Created feedback pipe: [" << pMutex->m_FeedbackFDs[0] << "," << pMutex->m_FeedbackFDs[1] << "]"); + } + + int flags; + + // set close-on-exec descriptor flag. + if ((flags = fcntl (pMutex->m_FeedbackFDs[0], F_GETFD)) != -1) + { + flags |= FD_CLOEXEC; + (void) fcntl(pMutex->m_FeedbackFDs[0], F_SETFD, flags); + } + if ((flags = fcntl (pMutex->m_FeedbackFDs[1], F_GETFD)) != -1) + { + flags |= FD_CLOEXEC; + (void) fcntl(pMutex->m_FeedbackFDs[1], F_SETFD, flags); + } + + // retain the default blocking I/O for feedback pipe + } +} + +void SvpSalInstance::TriggerUserEventProcessing() +{ + Wakeup(); +} + +#ifndef NDEBUG +static bool g_CheckedMutex = false; +#endif + +void SvpSalInstance::Wakeup(SvpRequest const request) +{ +#ifndef NDEBUG + if (!g_CheckedMutex) + { + assert(dynamic_cast<SvpSalYieldMutex*>(GetYieldMutex()) != nullptr + && "This SvpSalInstance function requires use of SvpSalYieldMutex"); + g_CheckedMutex = true; + } +#endif + + 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* pGraphics, + long &nDX, long &nDY, + DeviceFormat eFormat, + const SystemGraphicsData* pGd) +{ + SvpSalGraphics *pSvpSalGraphics = dynamic_cast<SvpSalGraphics*>(pGraphics); + 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> pNew(new SvpSalVirtualDevice(eFormat, pSvpSalGraphics->getSurface(), pPreExistingTarget)); + pNew->SetSize( nDX, nDY ); + return pNew; +} + +cairo_surface_t* get_underlying_cairo_surface(const VirtualDevice& rDevice) +{ + return static_cast<SvpSalVirtualDevice*>(rDevice.mpVirDev.get())->GetSurface(); +} + +#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 ) +{ + 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(); + } +#ifndef NDEBUG + if (!g_CheckedMutex) + { + assert(dynamic_cast<SvpSalYieldMutex*>(GetYieldMutex()) != nullptr + && "This SvpSalInstance function requires use of SvpSalYieldMutex"); + g_CheckedMutex = true; + } +#endif + SvpSalYieldMutex *const pMutex(static_cast<SvpSalYieldMutex*>(GetYieldMutex())); + pMutex->m_NonMainWaitingYieldCond.set(); +} + +SvpSalYieldMutex::SvpSalYieldMutex() +{ +#ifndef IOS + m_FeedbackFDs[0] = m_FeedbackFDs[1] = -1; +#endif +} + +SvpSalYieldMutex::~SvpSalYieldMutex() +{ +} + +void SvpSalYieldMutex::doAcquire(sal_uInt32 const nLockCount) +{ + SvpSalInstance *const pInst = static_cast<SvpSalInstance *>(GetSalData()->m_pInstance); + 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; + write(m_FeedbackFDs[1], &bEvents, sizeof(bool)); + } + } + while (true); + } + else + { + m_aMutex.acquire(); + } + ++m_nCount; + SalYieldMutex::doAcquire(nLockCount - 1); +} + +sal_uInt32 SvpSalYieldMutex::doRelease(bool const bUnlockAll) +{ + SvpSalInstance *const pInst = static_cast<SvpSalInstance *>(GetSalData()->m_pInstance); + 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(SvpRequest::NONE); + } + else + { + std::scoped_lock<std::mutex> g(m_WakeUpMainMutex); + m_wakeUpMain = true; + m_WakeUpMainCond.notify_one(); + } + } + } + return nCount; +} + +bool SvpSalYieldMutex::IsCurrentThread() const +{ + if (GetSalData()->m_pInstance->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::DoYield(bool bWait, bool bHandleAllCurrentEvents) +{ +#ifndef NDEBUG + if (!g_CheckedMutex) + { + assert(dynamic_cast<SvpSalYieldMutex*>(GetYieldMutex()) != nullptr + && "This SvpSalInstance function requires use of SvpSalYieldMutex"); + g_CheckedMutex = true; + } +#endif + + // first, process current user events + bool bEvent = DispatchUserEvents(bHandleAllCurrentEvents); + if (!bHandleAllCurrentEvents && bEvent) + return true; + + ImplSVData* pSVData = ImplGetSVData(); + + bool bTimeout = CheckTimeout(); + bool bSkipPoll = bEvent; + if (pSVData->mpPollCallback == nullptr) + bSkipPoll = bEvent || bTimeout; + // else - give the poll-callback visibility into waiting timeouts too. + + SvpSalYieldMutex *const pMutex(static_cast<SvpSalYieldMutex*>(GetYieldMutex())); + + if (IsMainThread()) + { + // in kit case + if (bWait && !bSkipPoll) + { + sal_Int64 nTimeoutMicroS = 0; + 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 + + sal_uInt32 nAcquireCount = ReleaseYieldMutexAll(); + + if (pSVData->mpPollCallback) + { + // Poll for events from the LOK client. + if (nTimeoutMicroS < 0) + nTimeoutMicroS = 5000 * 1000; + + // External poll. + if (pSVData->mpPollClosure != nullptr && + pSVData->mpPollCallback(pSVData->mpPollClosure, nTimeoutMicroS) < 0) + pSVData->maAppData.mbAppQuit = true; + } + else + { + 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 + } + AcquireYieldMutex( nAcquireCount ); + } + else if (bSkipPoll) + { + pMutex->m_NonMainWaitingYieldCond.set(); // wake up other threads + } + } + else // !IsMainThread() + { + Wakeup(bHandleAllCurrentEvents + ? SvpRequest::MainThreadDispatchAllEvents + : SvpRequest::MainThreadDispatchOneEvent); + + bool bDidWork(false); + // blocking read (for synchronisation) + auto const nRet = read(pMutex->m_FeedbackFDs[0], &bDidWork, sizeof(bool)); + assert(nRet == 1); (void) nRet; + if (!bDidWork && bWait) + { + // block & release YieldMutex until the main thread does something + pMutex->m_NonMainWaitingYieldCond.reset(); + sal_uInt32 nAcquireCount = ReleaseYieldMutexAll(); + pMutex->m_NonMainWaitingYieldCond.wait(); + AcquireYieldMutex( nAcquireCount ); + } + } + + return bSkipPoll; +} + +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&) +{ +} + +std::shared_ptr<vcl::BackendCapabilities> SvpSalInstance::GetBackendCapabilities() +{ + auto pBackendCapabilities = SalInstance::GetBackendCapabilities(); + pBackendCapabilities->mbSupportsBitmap32 = true; + return pBackendCapabilities; +} + +//obviously doesn't actually do anything, it's just a nonfunctional stub + +#if HAVE_FEATURE_UI + +namespace { + +class SvpOpenGLContext : public OpenGLContext +{ + GLWindow m_aGLWin; +private: + virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; } + virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; } +}; + +} + +OpenGLContext* SvpSalInstance::CreateOpenGLContext() +{ + return new SvpOpenGLContext; +} + +#else + +class SvpOpenGLContext +{ +}; + +OpenGLContext* SvpSalInstance::CreateOpenGLContext() +{ + return nullptr; +} + + +#endif + +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 000000000..aa8cd59de --- /dev/null +++ b/vcl/headless/svpprn.cxx @@ -0,0 +1,269 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <vcl/svapp.hxx> +#include <vcl/timer.hxx> +#include <printerinfomanager.hxx> + +#include <jobset.h> +#include <print.h> +#include <salptype.hxx> +#include <saldatabasic.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() ) + aDir = OStringToOUString( OString( getenv( "HOME" ) ), 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 + if( pJobSetup->GetDriverData() ) + std::free( const_cast<sal_uInt8*>(pJobSetup->GetDriverData()) ); + + sal_uInt32 nBytes; + void* pBuffer = nullptr; + if( rData.getStreamBuffer( pBuffer, nBytes ) ) + { + pJobSetup->SetDriverDataLen( nBytes ); + pJobSetup->SetDriverData( static_cast<sal_uInt8*>(pBuffer) ); + } + else + { + pJobSetup->SetDriverDataLen( 0 ); + pJobSetup->SetDriverData( nullptr ); + } +} + +// 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; + pPrinter->m_aPrinterGfx.Init( pPrinter->m_aJobData ); + + 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 000000000..e4b625b36 --- /dev/null +++ b/vcl/headless/svptext.cxx @@ -0,0 +1,121 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/types.h> +#include <unotools/configmgr.hxx> +#include <vcl/fontcharmap.hxx> +#include <basegfx/range/b2ibox.hxx> +#include <headless/svpgdi.hxx> +#include <config_cairo_canvas.h> +#include <impfontmetricdata.hxx> +#include <sallayout.hxx> + +void SvpSalGraphics::SetFont(LogicalFontInstance* pIFSD, int nFallbackLevel) +{ + m_aTextRenderImpl.SetFont(pIFSD, nFallbackLevel); +} + +void SvpSalGraphics::GetFontMetric( ImplFontMetricDataRef& 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( PhysicalFontCollection* pFontCollection ) +{ + m_aTextRenderImpl.GetDevFontList(pFontCollection); +} + +void SvpSalGraphics::ClearDevFontCache() +{ + m_aTextRenderImpl.ClearDevFontCache(); +} + +bool SvpSalGraphics::AddTempDevFont( PhysicalFontCollection* pFontCollection, + const OUString& rFileURL, const OUString& rFontName) +{ + return m_aTextRenderImpl.AddTempDevFont(pFontCollection, rFileURL, rFontName); +} + +bool SvpSalGraphics::CreateFontSubset( + const OUString& rToFile, + const PhysicalFontFace* pFont, + const sal_GlyphId* pGlyphIds, + const sal_uInt8* pEncoding, + sal_Int32* pWidths, + int nGlyphCount, + FontSubsetInfo& rInfo) +{ + return m_aTextRenderImpl.CreateFontSubset(rToFile, pFont, pGlyphIds, pEncoding, pWidths, nGlyphCount, rInfo); +} + +const void* SvpSalGraphics::GetEmbedFontData(const PhysicalFontFace* pFont, long* pDataLen) +{ + return m_aTextRenderImpl.GetEmbedFontData(pFont, pDataLen); +} + +void SvpSalGraphics::FreeEmbedFontData( const void* pData, long nLen ) +{ + m_aTextRenderImpl.FreeEmbedFontData(pData, nLen); +} + +void SvpSalGraphics::GetGlyphWidths( const PhysicalFontFace* pFont, + bool bVertical, + std::vector< sal_Int32 >& rWidths, + Ucs2UIntMap& rUnicodeEnc ) +{ + m_aTextRenderImpl.GetGlyphWidths(pFont, bVertical, rWidths, rUnicodeEnc); +} + +std::unique_ptr<GenericSalLayout> SvpSalGraphics::GetTextLayout(int nFallbackLevel) +{ + if (utl::ConfigManager::IsFuzzing()) + return nullptr; + 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); +} + +#if ENABLE_CAIRO_CANVAS + +SystemFontData SvpSalGraphics::GetSysFontData( int nFallbacklevel ) const +{ + return m_aTextRenderImpl.GetSysFontData(nFallbacklevel); +} + +#endif // ENABLE_CAIRO_CANVAS + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/headless/svpvd.cxx b/vcl/headless/svpvd.cxx new file mode 100644 index 000000000..70ac5785e --- /dev/null +++ b/vcl/headless/svpvd.cxx @@ -0,0 +1,148 @@ +/* -*- 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(DeviceFormat eFormat, cairo_surface_t* pRefSurface, cairo_surface_t* pPreExistingTarget) + : m_eFormat(eFormat) + , 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 ) +{ + m_aGraphics.erase(std::remove(m_aGraphics.begin(), m_aGraphics.end(), dynamic_cast<SvpSalGraphics*>(pGraphics)), m_aGraphics.end()); + delete pGraphics; +} + +bool SvpSalVirtualDevice::SetSize( long nNewDX, long nNewDY ) +{ + return SetSizeUsingBuffer(nNewDX, nNewDY, nullptr); +} + +void SvpSalVirtualDevice::CreateSurface(long nNewDX, long nNewDY, sal_uInt8 *const pBuffer) +{ + if (m_pSurface) + { + cairo_surface_destroy(m_pSurface); + } + + if (m_eFormat == DeviceFormat::BITMASK) + { + m_pSurface = cairo_surface_create_similar(m_pRefSurface, CAIRO_CONTENT_ALPHA, + nNewDX, nNewDY); + } + else if (pBuffer) + { + double fXScale, fYScale; + if (comphelper::LibreOfficeKit::isActive()) + { + // Force scaling of the painting + fXScale = fYScale = comphelper::LibreOfficeKit::getDPIScale(); + } + else + { + dl_cairo_surface_get_device_scale(m_pRefSurface, &fXScale, &fYScale); + nNewDX *= fXScale; + nNewDY *= fYScale; + } + + 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, fXScale, fYScale); + } + else + { + m_pSurface = cairo_surface_create_similar(m_pRefSurface, CAIRO_CONTENT_COLOR_ALPHA, nNewDX, nNewDY); + } +} + +bool SvpSalVirtualDevice::SetSizeUsingBuffer( long nNewDX, long nNewDY, + sal_uInt8 *const pBuffer) +{ + 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) + 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 true; +} + +long SvpSalVirtualDevice::GetWidth() const +{ + return m_pSurface ? m_aFrameSize.getX() : 0; +} + +long SvpSalVirtualDevice::GetHeight() const +{ + return m_pSurface ? m_aFrameSize.getY() : 0; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |