summaryrefslogtreecommitdiffstats
path: root/vcl/headless
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 16:51:28 +0000
commit940b4d1848e8c70ab7642901a68594e8016caffc (patch)
treeeb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /vcl/headless
parentInitial commit. (diff)
downloadlibreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.tar.xz
libreoffice-940b4d1848e8c70ab7642901a68594e8016caffc.zip
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--vcl/headless/CustomWidgetDraw.cxx421
-rw-r--r--vcl/headless/headlessinst.cxx101
-rw-r--r--vcl/headless/svpbmp.cxx283
-rw-r--r--vcl/headless/svpcairotextrender.cxx40
-rw-r--r--vcl/headless/svpdata.cxx33
-rw-r--r--vcl/headless/svpdummies.cxx58
-rw-r--r--vcl/headless/svpframe.cxx502
-rw-r--r--vcl/headless/svpgdi.cxx2622
-rw-r--r--vcl/headless/svpinst.cxx627
-rw-r--r--vcl/headless/svpprn.cxx269
-rw-r--r--vcl/headless/svptext.cxx121
-rw-r--r--vcl/headless/svpvd.cxx148
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: */