summaryrefslogtreecommitdiffstats
path: root/vcl/osx/salmacos.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/osx/salmacos.cxx
parentInitial commit. (diff)
downloadlibreoffice-upstream.tar.xz
libreoffice-upstream.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/osx/salmacos.cxx')
-rw-r--r--vcl/osx/salmacos.cxx529
1 files changed, 529 insertions, 0 deletions
diff --git a/vcl/osx/salmacos.cxx b/vcl/osx/salmacos.cxx
new file mode 100644
index 000000000..700b252cf
--- /dev/null
+++ b/vcl/osx/salmacos.cxx
@@ -0,0 +1,529 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * 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 .
+ */
+
+// This file contains the macOS-specific versions of the functions which were touched in the commit
+// to fix tdf#138122. The iOS-specific versions of these functions are kept (for now, when this
+// comment is written) as they were before that commit in vcl/ios/salios.cxx.
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <vcl/bitmap.hxx>
+
+#include <quartz/salbmp.h>
+#include <quartz/salgdi.h>
+#include <quartz/salvd.h>
+#include <quartz/utils.h>
+
+#include <osx/saldata.hxx>
+
+// From salbmp.cxx
+
+bool QuartzSalBitmap::Create(CGLayerHolder const & rLayerHolder, int nBitmapBits, int nX, int nY, int nWidth, int nHeight, bool bFlipped)
+{
+
+ // TODO: Bitmaps from scaled layers are reverted to single precision. This is a workaround only unless bitmaps with precision of
+ // source layer are implemented.
+
+ SAL_WARN_IF(!rLayerHolder.isSet(), "vcl", "QuartzSalBitmap::Create() from non-layered context");
+
+ // sanitize input parameters
+ if( nX < 0 ) {
+ nWidth += nX;
+ nX = 0;
+ }
+
+ if( nY < 0 ) {
+ nHeight += nY;
+ nY = 0;
+ }
+
+ CGSize aLayerSize = CGLayerGetSize(rLayerHolder.get());
+ const float fScale = rLayerHolder.getScale();
+ aLayerSize.width /= fScale;
+ aLayerSize.height /= fScale;
+
+ if( nWidth >= static_cast<int>(aLayerSize.width) - nX )
+ nWidth = static_cast<int>(aLayerSize.width) - nX;
+
+ if( nHeight >= static_cast<int>(aLayerSize.height) - nY )
+ nHeight = static_cast<int>(aLayerSize.height) - nY;
+
+ if( (nWidth < 0) || (nHeight < 0) )
+ nWidth = nHeight = 0;
+
+ // initialize properties
+ mnWidth = nWidth;
+ mnHeight = nHeight;
+ mnBits = nBitmapBits ? nBitmapBits : 32;
+
+ // initialize drawing context
+ CreateContext();
+
+ // copy layer content into the bitmap buffer
+ const CGPoint aSrcPoint = { static_cast<CGFloat>(-nX * fScale), static_cast<CGFloat>(-nY * fScale) };
+ if (maGraphicContext.isSet())
+ {
+ if( bFlipped )
+ {
+ CGContextTranslateCTM(maGraphicContext.get(), 0, +mnHeight);
+ CGContextScaleCTM(maGraphicContext.get(), +1, -1);
+ }
+ maGraphicContext.saveState();
+ CGContextScaleCTM(maGraphicContext.get(), 1 / fScale, 1 / fScale);
+ CGContextDrawLayerAtPoint(maGraphicContext.get(), aSrcPoint, rLayerHolder.get());
+ maGraphicContext.restoreState();
+ }
+ return true;
+}
+
+// From salgdicommon.cxx
+
+void AquaGraphicsBackend::copyBits(const SalTwoRect &rPosAry, SalGraphics *pSrcGraphics)
+{
+ AquaSharedAttributes* pSrcShared = nullptr;
+
+ if (pSrcGraphics)
+ {
+ AquaSalGraphics* pSrc = static_cast<AquaSalGraphics*>(pSrcGraphics);
+ pSrcShared = &pSrc->getAquaGraphicsBackend()->GetShared();
+ }
+ else
+ pSrcShared = &mrShared;
+
+ if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0 || rPosAry.mnDestHeight <= 0)
+ return;
+ if (!mrShared.maContextHolder.isSet())
+ return;
+
+ SAL_WARN_IF (!pSrcShared->maLayer.isSet(), "vcl.quartz", "AquaSalGraphics::copyBits() from non-layered graphics this=" << this);
+
+ // Layered graphics are copied by AquaSalGraphics::copyScaledArea() which is able to consider the layer's scaling.
+
+ if (pSrcShared->maLayer.isSet())
+ copyScaledArea(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, pSrcShared);
+ else
+ {
+ mrShared.applyXorContext();
+ pSrcShared->applyXorContext();
+ std::shared_ptr<SalBitmap> pBitmap = pSrcGraphics->GetImpl()->getBitmap(rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ if (pBitmap)
+ {
+ SalTwoRect aPosAry(rPosAry);
+ aPosAry.mnSrcX = 0;
+ aPosAry.mnSrcY = 0;
+ drawBitmap(aPosAry, *pBitmap);
+ }
+ }
+}
+
+void AquaGraphicsBackend::copyArea(tools::Long nDstX, tools::Long nDstY,tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight, bool)
+{
+ if (!mrShared.maContextHolder.isSet())
+ return;
+
+ // Functionality is implemented in protected member function AquaSalGraphics::copyScaledArea() which requires an additional
+ // parameter of type SalGraphics to be used in AquaSalGraphics::copyBits() too.
+
+ copyScaledArea(nDstX, nDstY, nSrcX, nSrcY, nSrcWidth, nSrcHeight, &mrShared);
+}
+
+void AquaGraphicsBackend::copyScaledArea(tools::Long nDstX, tools::Long nDstY,tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight, AquaSharedAttributes* pSrcShared)
+{
+ SAL_WARN_IF(!mrShared.maLayer.isSet(), "vcl.quartz",
+ "AquaSalGraphics::copyScaledArea() without graphics context or for non-layered graphics this=" << this);
+
+ if (!mrShared.maContextHolder.isSet() || !mrShared.maLayer.isSet())
+ return;
+
+ // Determine scaled geometry of source and target area assuming source and target area have the same scale
+
+ float fScale = mrShared.maLayer.getScale();
+ CGFloat nScaledSourceX = nSrcX * fScale;
+ CGFloat nScaledSourceY = nSrcY * fScale;
+ CGFloat nScaledTargetX = nDstX * fScale;
+ CGFloat nScaledTargetY = nDstY * fScale;
+ CGFloat nScaledSourceWidth = nSrcWidth * fScale;
+ CGFloat nScaledSourceHeight = nSrcHeight * fScale;
+
+ // Apply XOR context and get copy context from current graphics context or XOR context
+
+ mrShared.applyXorContext();
+ mrShared.maContextHolder.saveState();
+ CGContextRef xCopyContext = mrShared.maContextHolder.get();
+ if (mrShared.mpXorEmulation && mrShared.mpXorEmulation->IsEnabled())
+ xCopyContext = mrShared.mpXorEmulation->GetTargetContext();
+
+ // Set scale matrix of copy context to consider layer scaling
+
+ CGContextScaleCTM(xCopyContext, 1 / fScale, 1 / fScale);
+
+ // Creating an additional layer is required for drawing with the required scale and extent at the drawing destination
+ // thereafter.
+
+ CGLayerHolder aSourceLayerHolder(pSrcShared->maLayer);
+ const CGSize aSourceSize = CGSizeMake(nScaledSourceWidth, nScaledSourceHeight);
+ aSourceLayerHolder.set(CGLayerCreateWithContext(xCopyContext, aSourceSize, nullptr));
+ const CGContextRef xSourceContext = CGLayerGetContext(aSourceLayerHolder.get());
+ CGPoint aSrcPoint = CGPointMake(-nScaledSourceX, -nScaledSourceY);
+ if (pSrcShared->isFlipped())
+ {
+ CGContextTranslateCTM(xSourceContext, 0, nScaledSourceHeight);
+ CGContextScaleCTM(xSourceContext, 1, -1);
+ aSrcPoint.y = nScaledSourceY + nScaledSourceHeight - mrShared.mnHeight * fScale;
+ }
+ CGContextSetBlendMode(xSourceContext, kCGBlendModeCopy);
+ CGContextDrawLayerAtPoint(xSourceContext, aSrcPoint, pSrcShared->maLayer.get());
+
+ // Copy source area from additional layer to target area
+
+ const CGRect aTargetRect = CGRectMake(nScaledTargetX, nScaledTargetY, nScaledSourceWidth, nScaledSourceHeight);
+ CGContextSetBlendMode(xCopyContext, kCGBlendModeCopy);
+ CGContextDrawLayerInRect(xCopyContext, aTargetRect, aSourceLayerHolder.get());
+
+ // Housekeeping on exit
+
+ mrShared.maContextHolder.restoreState();
+ if (aSourceLayerHolder.get() != mrShared.maLayer.get())
+ CGLayerRelease(aSourceLayerHolder.get());
+
+ mrShared.refreshRect(nDstX, nDstY, nSrcWidth, nSrcHeight);
+}
+
+void AquaSalGraphics::SetVirDevGraphics(SalVirtualDevice* pVirDev, CGLayerHolder const &rLayer, CGContextRef xContext, int nBitmapDepth)
+{
+ SAL_INFO("vcl.quartz", "SetVirDevGraphics() this=" << this << " layer=" << rLayer.get() << " context=" << xContext);
+
+ // Set member variables
+
+ InvalidateContext();
+ maShared.mbWindow = false;
+ maShared.mbPrinter = false;
+ maShared.mbVirDev = true;
+ maShared.maLayer = rLayer;
+ maShared.mnBitmapDepth = nBitmapDepth;
+
+ mpBackend->UpdateGeometryProvider(pVirDev);
+
+ // Get size and scale from layer if set else from bitmap and sal::aqua::getWindowScaling(), which is used to determine
+ // scaling for direct graphics output too
+
+ CGSize aSize;
+ float fScale;
+ if (maShared.maLayer.isSet())
+ {
+ maShared.maContextHolder.set(CGLayerGetContext(maShared.maLayer.get()));
+ aSize = CGLayerGetSize(maShared.maLayer.get());
+ fScale = maShared.maLayer.getScale();
+ }
+ else
+ {
+ maShared.maContextHolder.set(xContext);
+ if (!xContext)
+ return;
+ aSize.width = CGBitmapContextGetWidth(xContext);
+ aSize.height = CGBitmapContextGetHeight(xContext);
+ fScale = sal::aqua::getWindowScaling();
+ }
+ maShared.mnWidth = aSize.width / fScale;
+ maShared.mnHeight = aSize.height / fScale;
+
+ // Set color space for fill and stroke
+
+ CGColorSpaceRef aColorSpace = GetSalData()->mxRGBSpace;
+ CGContextSetFillColorSpace(maShared.maContextHolder.get(), aColorSpace);
+ CGContextSetStrokeColorSpace(maShared.maContextHolder.get(), aColorSpace);
+
+ // Apply scale matrix to virtual device graphics
+
+ CGContextScaleCTM(maShared.maContextHolder.get(), fScale, fScale);
+
+ // Apply XOR emulation if required
+
+ if (maShared.mpXorEmulation)
+ {
+ maShared.mpXorEmulation->SetTarget(maShared.mnWidth, maShared.mnHeight, maShared.mnBitmapDepth, maShared.maContextHolder.get(), maShared.maLayer.get());
+ if (maShared.mpXorEmulation->IsEnabled())
+ maShared.maContextHolder.set(maShared.mpXorEmulation->GetMaskContext());
+ }
+
+ // Housekeeping on exit
+
+ maShared.maContextHolder.saveState();
+ maShared.setState();
+
+ SAL_INFO("vcl.quartz", "SetVirDevGraphics() this=" << this <<
+ " (" << maShared.mnWidth << "x" << maShared.mnHeight << ") fScale=" << fScale << " mnBitmapDepth=" << maShared.mnBitmapDepth);
+}
+
+void XorEmulation::SetTarget(int nWidth, int nHeight, int nTargetDepth, CGContextRef xTargetContext, CGLayerRef xTargetLayer)
+{
+ SAL_INFO("vcl.quartz", "XorEmulation::SetTarget() this=" << this <<
+ " (" << nWidth << "x" << nHeight << ") depth=" << nTargetDepth <<
+ " context=" << xTargetContext << " layer=" << xTargetLayer);
+
+ // Prepare to replace old mask and temporary context
+
+ if (m_xMaskContext)
+ {
+ CGContextRelease(m_xMaskContext);
+ delete[] m_pMaskBuffer;
+ m_xMaskContext = nullptr;
+ m_pMaskBuffer = nullptr;
+ if (m_xTempContext)
+ {
+ CGContextRelease(m_xTempContext);
+ delete[] m_pTempBuffer;
+ m_xTempContext = nullptr;
+ m_pTempBuffer = nullptr;
+ }
+ }
+
+ // Return early if there is nothing more to do
+
+ if (!xTargetContext)
+ return;
+
+ // Retarget drawing operations to the XOR mask
+
+ m_xTargetLayer = xTargetLayer;
+ m_xTargetContext = xTargetContext;
+
+ // Prepare creation of matching bitmaps
+
+ CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
+ CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst;
+ int nBitDepth = nTargetDepth;
+ if (!nBitDepth)
+ nBitDepth = 32;
+ int nBytesPerRow = 4;
+ const size_t nBitsPerComponent = (nBitDepth == 16) ? 5 : 8;
+ if (nBitDepth <= 8)
+ {
+ aCGColorSpace = GetSalData()->mxGraySpace;
+ aCGBmpInfo = kCGImageAlphaNone;
+ nBytesPerRow = 1;
+ }
+ float fScale = sal::aqua::getWindowScaling();
+ size_t nScaledWidth = nWidth * fScale;
+ size_t nScaledHeight = nHeight * fScale;
+ nBytesPerRow *= nScaledWidth;
+ m_nBufferLongs = (nScaledHeight * nBytesPerRow + sizeof(sal_uLong) - 1) / sizeof(sal_uLong);
+
+ // Create XOR mask context
+
+ m_pMaskBuffer = new sal_uLong[m_nBufferLongs];
+ m_xMaskContext = CGBitmapContextCreate(m_pMaskBuffer, nScaledWidth, nScaledHeight,
+ nBitsPerComponent, nBytesPerRow, aCGColorSpace, aCGBmpInfo);
+ SAL_WARN_IF(!m_xMaskContext, "vcl.quartz", "mask context creation failed");
+
+ // Reset XOR mask to black
+
+ memset(m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong));
+
+ // Create bitmap context for manual XOR unless target context is a bitmap context
+
+ if (nTargetDepth)
+ m_pTempBuffer = static_cast<sal_uLong*>(CGBitmapContextGetData(m_xTargetContext));
+ if (!m_pTempBuffer)
+ {
+ m_pTempBuffer = new sal_uLong[m_nBufferLongs];
+ m_xTempContext = CGBitmapContextCreate(m_pTempBuffer, nScaledWidth, nScaledHeight,
+ nBitsPerComponent, nBytesPerRow, aCGColorSpace, aCGBmpInfo);
+ SAL_WARN_IF(!m_xTempContext, "vcl.quartz", "temp context creation failed");
+ }
+
+ // Initialize XOR mask context for drawing
+
+ CGContextSetFillColorSpace(m_xMaskContext, aCGColorSpace);
+ CGContextSetStrokeColorSpace(m_xMaskContext, aCGColorSpace);
+ CGContextSetShouldAntialias(m_xMaskContext, false);
+
+ // Improve XOR emulation for monochrome contexts
+
+ if (aCGColorSpace == GetSalData()->mxGraySpace)
+ CGContextSetBlendMode(m_xMaskContext, kCGBlendModeDifference);
+
+ // Initialize XOR mask transformation matrix and apply scale matrix to consider layer scaling
+
+ const CGAffineTransform aCTM = CGContextGetCTM(xTargetContext);
+ CGContextConcatCTM(m_xMaskContext, aCTM);
+ if (m_xTempContext)
+ {
+ CGContextConcatCTM( m_xTempContext, aCTM );
+ CGContextScaleCTM(m_xTempContext, 1 / fScale, 1 / fScale);
+ }
+ CGContextSaveGState(m_xMaskContext);
+}
+
+bool XorEmulation::UpdateTarget()
+{
+ SAL_INFO("vcl.quartz", "XorEmulation::UpdateTarget() this=" << this);
+
+ if (!IsEnabled())
+ return false;
+
+ // Update temporary bitmap buffer
+
+ if (m_xTempContext)
+ {
+ SAL_WARN_IF(m_xTargetContext == nullptr, "vcl.quartz", "Target layer is NULL");
+ CGContextDrawLayerAtPoint(m_xTempContext, CGPointZero, m_xTargetLayer);
+ }
+
+ // XOR using XOR mask (sufficient for simple color manipulations as well as for complex XOR clipping used in metafiles)
+
+ const sal_uLong *pSrc = m_pMaskBuffer;
+ sal_uLong *pDst = m_pTempBuffer;
+ for (int i = m_nBufferLongs; --i >= 0;)
+ *(pDst++) ^= *(pSrc++);
+
+ // Write back XOR results to target context
+
+ if (m_xTempContext)
+ {
+ CGImageRef xXorImage = CGBitmapContextCreateImage(m_xTempContext);
+ size_t nWidth = CGImageGetWidth(xXorImage);
+ size_t nHeight = CGImageGetHeight(xXorImage);
+
+ // Set scale matrix of target context to consider layer scaling and update target context
+ // TODO: Update minimal change rectangle
+
+ const CGRect aFullRect = CGRectMake(0, 0, nWidth, nHeight);
+ CGContextSaveGState(m_xTargetContext);
+ float fScale = sal::aqua::getWindowScaling();
+ CGContextScaleCTM(m_xTargetContext, 1 / fScale, 1 / fScale);
+ CGContextDrawImage(m_xTargetContext, aFullRect, xXorImage);
+ CGContextRestoreGState(m_xTargetContext);
+ CGImageRelease(xXorImage);
+ }
+
+ // Reset XOR mask to black again
+ // TODO: Not needed for last update
+
+ memset(m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong));
+
+ // TODO: Return FALSE if target was not changed
+
+ return true;
+}
+
+// From salvd.cxx
+
+void AquaSalVirtualDevice::Destroy()
+{
+ SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::Destroy() this=" << this << " mbForeignContext=" << mbForeignContext );
+
+ if (mbForeignContext)
+ {
+ // Do not delete mxContext that we have received from outside VCL
+ maLayer.set(nullptr);
+ return;
+ }
+
+ if (maLayer.isSet())
+ {
+ if( mpGraphics )
+ {
+ mpGraphics->SetVirDevGraphics(this, nullptr, nullptr);
+ }
+ CGLayerRelease(maLayer.get());
+ maLayer.set(nullptr);
+ }
+
+ if (maBitmapContext.isSet())
+ {
+ CGContextRelease(maBitmapContext.get());
+ maBitmapContext.set(nullptr);
+ }
+}
+
+bool AquaSalVirtualDevice::SetSize(tools::Long nDX, tools::Long nDY)
+{
+ SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
+ " (" << nDX << "x" << nDY << ") mbForeignContext=" << (mbForeignContext ? "YES" : "NO"));
+
+ // Do not delete/resize graphics context if it has been received from outside VCL
+
+ if (mbForeignContext)
+ return true;
+
+ // Do not delete/resize graphics context if no change of geometry has been requested
+
+ float fScale;
+ if (maLayer.isSet())
+ {
+ fScale = maLayer.getScale();
+ const CGSize aSize = CGLayerGetSize(maLayer.get());
+ if ((nDX == aSize.width / fScale) && (nDY == aSize.height / fScale))
+ return true;
+ }
+
+ // Destroy graphics context if change of geometry has been requested
+
+ Destroy();
+
+ // Prepare new graphics context for initialization, use scaling independent of prior graphics context calculated by
+ // sal::aqua::getWindowScaling(), which is used to determine scaling for direct graphics output too
+
+ mnWidth = nDX;
+ mnHeight = nDY;
+ fScale = sal::aqua::getWindowScaling();
+ CGColorSpaceRef aColorSpace;
+ uint32_t nFlags;
+ if (mnBitmapDepth && (mnBitmapDepth < 16))
+ {
+ mnBitmapDepth = 8;
+ aColorSpace = GetSalData()->mxGraySpace;
+ nFlags = kCGImageAlphaNone;
+ }
+ else
+ {
+ mnBitmapDepth = 32;
+ aColorSpace = GetSalData()->mxRGBSpace;
+
+ nFlags = uint32_t(kCGImageAlphaNoneSkipFirst) | uint32_t(kCGBitmapByteOrder32Host);
+ }
+
+ // Allocate buffer for virtual device graphics as bitmap context to store graphics with highest required (scaled) resolution
+
+ size_t nScaledWidth = mnWidth * fScale;
+ size_t nScaledHeight = mnHeight * fScale;
+ size_t nBytesPerRow = mnBitmapDepth * nScaledWidth / 8;
+ maBitmapContext.set(CGBitmapContextCreate(nullptr, nScaledWidth, nScaledHeight, 8, nBytesPerRow, aColorSpace, nFlags));
+
+ SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
+ " fScale=" << fScale << " mnBitmapDepth=" << mnBitmapDepth);
+
+ CGSize aLayerSize = { static_cast<CGFloat>(nScaledWidth), static_cast<CGFloat>(nScaledHeight) };
+ maLayer.set(CGLayerCreateWithContext(maBitmapContext.get(), aLayerSize, nullptr));
+ maLayer.setScale(fScale);
+ mpGraphics->SetVirDevGraphics(this, maLayer, CGLayerGetContext(maLayer.get()), mnBitmapDepth);
+
+ SAL_WARN_IF(!maBitmapContext.isSet(), "vcl.quartz", "No context");
+
+ return maLayer.isSet();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */