calc: on editing invalidation of view with different zoom is wrong
[LibreOffice.git] / vcl / ios / salios.cxx
blob362fa258ea9cb929965b615dc63b56bab0b26700
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 // This file contains the iOS-specific versions of the functions which were touched in the commit to
21 // fix tdf#138122. The functions are here (for now) as they were before that commit. The
22 // macOS-specific versions of these functions are in vcl/osx/salmacos.cxx.
24 #include <sal/config.h>
25 #include <sal/log.hxx>
26 #include <osl/diagnose.h>
28 #include <vcl/bitmap.hxx>
30 #include <ios/iosinst.hxx>
31 #include <quartz/salbmp.h>
32 #include <quartz/salgdi.h>
33 #include <quartz/salvd.h>
34 #include <quartz/utils.h>
36 #include <svdata.hxx>
38 // From salbmp.cxx
40 bool QuartzSalBitmap::Create(CGLayerHolder const & rLayerHolder, int nBitmapBits, int nX, int nY, int nWidth, int nHeight, bool bFlipped)
42 SAL_WARN_IF(!rLayerHolder.isSet(), "vcl", "QuartzSalBitmap::Create() from non-layered context");
44 // sanitize input parameters
45 if( nX < 0 ) {
46 nWidth += nX;
47 nX = 0;
50 if( nY < 0 ) {
51 nHeight += nY;
52 nY = 0;
55 const CGSize aLayerSize = CGLayerGetSize(rLayerHolder.get());
57 if( nWidth >= static_cast<int>(aLayerSize.width) - nX )
58 nWidth = static_cast<int>(aLayerSize.width) - nX;
60 if( nHeight >= static_cast<int>(aLayerSize.height) - nY )
61 nHeight = static_cast<int>(aLayerSize.height) - nY;
63 if( (nWidth < 0) || (nHeight < 0) )
64 nWidth = nHeight = 0;
66 // initialize properties
67 mnWidth = nWidth;
68 mnHeight = nHeight;
69 mnBits = nBitmapBits ? nBitmapBits : 32;
71 // initialize drawing context
72 CreateContext();
74 // copy layer content into the bitmap buffer
75 const CGPoint aSrcPoint = { static_cast<CGFloat>(-nX), static_cast<CGFloat>(-nY) };
76 if (maGraphicContext.isSet()) // remove warning
78 if( bFlipped )
80 CGContextTranslateCTM( maGraphicContext.get(), 0, +mnHeight );
82 CGContextScaleCTM( maGraphicContext.get(), +1, -1 );
85 CGContextDrawLayerAtPoint(maGraphicContext.get(), aSrcPoint, rLayerHolder.get());
87 return true;
90 // From salgdicommon.cxx
92 void AquaGraphicsBackend::copyBits(const SalTwoRect& rPosAry, SalGraphics *pSrcGraphics)
94 //from unix salgdi2.cxx
95 //[FIXME] find a better way to prevent calc from crashing when width and height are negative
96 if( rPosAry.mnSrcWidth <= 0 ||
97 rPosAry.mnSrcHeight <= 0 ||
98 rPosAry.mnDestWidth <= 0 ||
99 rPosAry.mnDestHeight <= 0 )
101 return;
104 // If called from idle layout, maContextHolder.get() is NULL, no idea what to do
105 if (!mrShared.maContextHolder.isSet())
106 return;
108 AquaSharedAttributes* pSrcShared = nullptr;
110 if (pSrcGraphics)
112 AquaSalGraphics* pSrc = static_cast<AquaSalGraphics*>(pSrcGraphics);
113 pSrcShared = &pSrc->getAquaGraphicsBackend()->GetShared();
115 else
116 pSrcShared = &mrShared;
118 // accelerate trivial operations
119 const bool bSameGraphics = (pSrcShared == &mrShared);
121 if( bSameGraphics &&
122 (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) &&
123 (rPosAry.mnSrcHeight == rPosAry.mnDestHeight))
125 // short circuit if there is nothing to do
126 if( (rPosAry.mnSrcX == rPosAry.mnDestX) &&
127 (rPosAry.mnSrcY == rPosAry.mnDestY))
129 return;
131 // use copyArea() if source and destination context are identical
132 copyArea( rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnSrcX, rPosAry.mnSrcY,
133 rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, false/*bWindowInvalidate*/ );
134 return;
137 mrShared.applyXorContext();
138 if (!bSameGraphics)
139 pSrcShared->applyXorContext();
141 SAL_WARN_IF (!pSrcShared->maLayer.isSet(), "vcl.quartz",
142 "AquaSalGraphics::copyBits() from non-layered graphics this=" << this);
144 const CGPoint aDstPoint = CGPointMake(+rPosAry.mnDestX - rPosAry.mnSrcX, rPosAry.mnDestY - rPosAry.mnSrcY);
145 if ((rPosAry.mnSrcWidth == rPosAry.mnDestWidth &&
146 rPosAry.mnSrcHeight == rPosAry.mnDestHeight) &&
147 (!mrShared.mnBitmapDepth || (aDstPoint.x + pSrcShared->mnWidth) <= mrShared.mnWidth)
148 && pSrcShared->maLayer.isSet()) // workaround for a Quartz crash
150 // in XOR mode the drawing context is redirected to the XOR mask
151 // if source and target are identical then copyBits() paints onto the target context though
152 CGContextHolder aCopyContext = mrShared.maContextHolder;
153 if (mrShared.mpXorEmulation && mrShared.mpXorEmulation->IsEnabled())
155 if (bSameGraphics)
157 aCopyContext.set(mrShared.mpXorEmulation->GetTargetContext());
160 aCopyContext.saveState();
162 const CGRect aDstRect = CGRectMake(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnDestWidth, rPosAry.mnDestHeight);
163 CGContextClipToRect(aCopyContext.get(), aDstRect);
165 // draw at new destination
166 // NOTE: flipped drawing gets disabled for this, else the subimage would be drawn upside down
167 if (pSrcShared->isFlipped())
169 CGContextTranslateCTM(aCopyContext.get(), 0, +mrShared.mnHeight);
170 CGContextScaleCTM(aCopyContext.get(), +1, -1);
173 // TODO: pSrc->size() != this->size()
174 CGContextDrawLayerAtPoint(aCopyContext.get(), aDstPoint, pSrcShared->maLayer.get());
176 aCopyContext.restoreState();
177 // mark the destination rectangle as updated
178 refreshRect(aDstRect);
180 else
182 std::shared_ptr<SalBitmap> pBitmap;
183 if (pSrcGraphics)
184 pBitmap = pSrcGraphics->GetImpl()->getBitmap(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
185 else
186 pBitmap = getBitmap(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
188 if (pBitmap)
190 SalTwoRect aPosAry( rPosAry );
191 aPosAry.mnSrcX = 0;
192 aPosAry.mnSrcY = 0;
193 drawBitmap(aPosAry, *pBitmap);
198 void AquaGraphicsBackend::copyArea(tools::Long nDstX, tools::Long nDstY,tools::Long nSrcX, tools::Long nSrcY,
199 tools::Long nSrcWidth, tools::Long nSrcHeight, bool /*bWindowInvalidate*/)
201 SAL_WARN_IF (!mrShared.maLayer.isSet(), "vcl.quartz",
202 "AquaSalGraphics::copyArea() for non-layered graphics this=" << this);
204 if (!mrShared.maLayer.isSet())
205 return;
207 float fScale = mrShared.maLayer.getScale();
209 tools::Long nScaledSourceX = nSrcX * fScale;
210 tools::Long nScaledSourceY = nSrcY * fScale;
212 tools::Long nScaledTargetX = nDstX * fScale;
213 tools::Long nScaledTargetY = nDstY * fScale;
215 tools::Long nScaledSourceWidth = nSrcWidth * fScale;
216 tools::Long nScaledSourceHeight = nSrcHeight * fScale;
218 mrShared.applyXorContext();
220 mrShared.maContextHolder.saveState();
222 // in XOR mode the drawing context is redirected to the XOR mask
223 // copyArea() always works on the target context though
224 CGContextRef xCopyContext = mrShared.maContextHolder.get();
226 if (mrShared.mpXorEmulation && mrShared.mpXorEmulation->IsEnabled())
228 xCopyContext = mrShared.mpXorEmulation->GetTargetContext();
231 // If we have a scaled layer, we need to revert the scaling or else
232 // it will interfere with the coordinate calculation
233 CGContextScaleCTM(xCopyContext, 1.0 / fScale, 1.0 / fScale);
235 // drawing a layer onto its own context causes trouble on OSX => copy it first
236 // TODO: is it possible to get rid of this unneeded copy more often?
237 // e.g. on OSX>=10.5 only this situation causes problems:
238 // mnBitmapDepth && (aDstPoint.x + pSrc->mnWidth) > mnWidth
240 CGLayerHolder sSourceLayerHolder(mrShared.maLayer);
242 const CGSize aSrcSize = CGSizeMake(nScaledSourceWidth, nScaledSourceHeight);
243 sSourceLayerHolder.set(CGLayerCreateWithContext(xCopyContext, aSrcSize, nullptr));
245 const CGContextRef xSrcContext = CGLayerGetContext(sSourceLayerHolder.get());
247 CGPoint aSrcPoint = CGPointMake(-nScaledSourceX, -nScaledSourceY);
248 if (mrShared.isFlipped())
250 CGContextTranslateCTM(xSrcContext, 0, +nScaledSourceHeight);
251 CGContextScaleCTM(xSrcContext, +1, -1);
252 aSrcPoint.y = (nScaledSourceY + nScaledSourceHeight) - (mrShared.mnHeight * fScale);
254 CGContextSetBlendMode(xSrcContext, kCGBlendModeCopy);
256 CGContextDrawLayerAtPoint(xSrcContext, aSrcPoint, mrShared.maLayer.get());
259 // draw at new destination
260 const CGRect aTargetRect = CGRectMake(nScaledTargetX, nScaledTargetY, nScaledSourceWidth, nScaledSourceHeight);
261 CGContextSetBlendMode(xCopyContext, kCGBlendModeCopy);
262 CGContextDrawLayerInRect(xCopyContext, aTargetRect, sSourceLayerHolder.get());
264 mrShared.maContextHolder.restoreState();
266 // cleanup
267 if (sSourceLayerHolder.get() != mrShared.maLayer.get())
269 CGLayerRelease(sSourceLayerHolder.get());
272 // mark the destination rectangle as updated
273 mrShared.refreshRect(nDstX, nDstY, nSrcWidth, nSrcHeight);
276 void AquaSalGraphics::SetVirDevGraphics(SalVirtualDevice* pVirDev, CGLayerHolder const & rLayer, CGContextRef xContext,
277 int nBitmapDepth)
279 SAL_INFO( "vcl.quartz", "SetVirDevGraphics() this=" << this << " layer=" << rLayer.get() << " context=" << xContext );
281 maShared.mbPrinter = false;
282 maShared.mbVirDev = true;
284 // set graphics properties
285 maShared.maLayer = rLayer;
286 maShared.maContextHolder.set(xContext);
288 maShared.mnBitmapDepth = nBitmapDepth;
290 maShared.mbForeignContext = xContext != NULL;
292 mpBackend->UpdateGeometryProvider(pVirDev);
294 // return early if the virdev is being destroyed
295 if (!xContext)
296 return;
298 // get new graphics properties
299 if (!maShared.maLayer.isSet())
301 maShared.mnWidth = CGBitmapContextGetWidth(maShared.maContextHolder.get());
302 maShared.mnHeight = CGBitmapContextGetHeight(maShared.maContextHolder.get());
304 else
306 const CGSize aSize = CGLayerGetSize(maShared.maLayer.get());
307 maShared.mnWidth = static_cast<int>(aSize.width);
308 maShared.mnHeight = static_cast<int>(aSize.height);
311 // prepare graphics for drawing
312 const CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
313 CGContextSetFillColorSpace(maShared.maContextHolder.get(), aCGColorSpace);
314 CGContextSetStrokeColorSpace(maShared.maContextHolder.get(), aCGColorSpace);
316 // re-enable XorEmulation for the new context
317 if (maShared.mpXorEmulation)
319 maShared.mpXorEmulation->SetTarget(maShared.mnWidth, maShared.mnHeight, maShared.mnBitmapDepth, maShared.maContextHolder.get(), maShared.maLayer.get());
320 if (maShared.mpXorEmulation->IsEnabled())
322 maShared.maContextHolder.set(maShared.mpXorEmulation->GetMaskContext());
326 // initialize stack of CGContext states
327 maShared.maContextHolder.saveState();
328 maShared.setState();
331 void XorEmulation::SetTarget( int nWidth, int nHeight, int nTargetDepth,
332 CGContextRef xTargetContext, CGLayerRef xTargetLayer )
334 SAL_INFO( "vcl.quartz", "XorEmulation::SetTarget() this=" << this <<
335 " (" << nWidth << "x" << nHeight << ") depth=" << nTargetDepth <<
336 " context=" << xTargetContext << " layer=" << xTargetLayer );
338 // prepare to replace old mask+temp context
339 if( m_xMaskContext )
341 // cleanup the mask context
342 CGContextRelease( m_xMaskContext );
343 delete[] m_pMaskBuffer;
344 m_xMaskContext = nullptr;
345 m_pMaskBuffer = nullptr;
347 // cleanup the temp context if needed
348 if( m_xTempContext )
350 CGContextRelease( m_xTempContext );
351 delete[] m_pTempBuffer;
352 m_xTempContext = nullptr;
353 m_pTempBuffer = nullptr;
357 // return early if there is nothing more to do
358 if( !xTargetContext )
360 return;
362 // retarget drawing operations to the XOR mask
363 m_xTargetLayer = xTargetLayer;
364 m_xTargetContext = xTargetContext;
366 // prepare creation of matching CGBitmaps
367 CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
368 CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst;
369 int nBitDepth = nTargetDepth;
370 if( !nBitDepth )
372 nBitDepth = 32;
374 int nBytesPerRow = 4;
375 const size_t nBitsPerComponent = (nBitDepth == 16) ? 5 : 8;
376 if( nBitDepth <= 8 )
378 aCGColorSpace = GetSalData()->mxGraySpace;
379 aCGBmpInfo = kCGImageAlphaNone;
380 nBytesPerRow = 1;
382 nBytesPerRow *= nWidth;
383 m_nBufferLongs = (nHeight * nBytesPerRow + sizeof(sal_uLong)-1) / sizeof(sal_uLong);
385 // create a XorMask context
386 m_pMaskBuffer = new sal_uLong[ m_nBufferLongs ];
387 m_xMaskContext = CGBitmapContextCreate( m_pMaskBuffer,
388 nWidth, nHeight,
389 nBitsPerComponent, nBytesPerRow,
390 aCGColorSpace, aCGBmpInfo );
391 SAL_WARN_IF( !m_xMaskContext, "vcl.quartz", "mask context creation failed" );
393 // reset the XOR mask to black
394 memset( m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong) );
396 // a bitmap context will be needed for manual XORing
397 // create one unless the target context is a bitmap context
398 if( nTargetDepth )
400 m_pTempBuffer = static_cast<sal_uLong*>(CGBitmapContextGetData( m_xTargetContext ));
402 if( !m_pTempBuffer )
404 // create a bitmap context matching to the target context
405 m_pTempBuffer = new sal_uLong[ m_nBufferLongs ];
406 m_xTempContext = CGBitmapContextCreate( m_pTempBuffer,
407 nWidth, nHeight,
408 nBitsPerComponent, nBytesPerRow,
409 aCGColorSpace, aCGBmpInfo );
410 SAL_WARN_IF( !m_xTempContext, "vcl.quartz", "temp context creation failed" );
413 // initialize XOR mask context for drawing
414 CGContextSetFillColorSpace( m_xMaskContext, aCGColorSpace );
415 CGContextSetStrokeColorSpace( m_xMaskContext, aCGColorSpace );
416 CGContextSetShouldAntialias( m_xMaskContext, false );
418 // improve the XorMask's XOR emulation a little
419 // NOTE: currently only enabled for monochrome contexts
420 if( aCGColorSpace == GetSalData()->mxGraySpace )
422 CGContextSetBlendMode( m_xMaskContext, kCGBlendModeDifference );
424 // initialize the transformation matrix to the drawing target
425 const CGAffineTransform aCTM = CGContextGetCTM( xTargetContext );
426 CGContextConcatCTM( m_xMaskContext, aCTM );
427 if( m_xTempContext )
429 CGContextConcatCTM( m_xTempContext, aCTM );
431 // initialize the default XorMask graphics state
432 CGContextSaveGState( m_xMaskContext );
435 bool XorEmulation::UpdateTarget()
437 SAL_INFO( "vcl.quartz", "XorEmulation::UpdateTarget() this=" << this );
439 if( !IsEnabled() )
441 return false;
443 // update the temp bitmap buffer if needed
444 if( m_xTempContext )
446 SAL_WARN_IF( m_xTargetContext == nullptr, "vcl.quartz", "Target layer is NULL");
447 CGContextDrawLayerAtPoint( m_xTempContext, CGPointZero, m_xTargetLayer );
449 // do a manual XOR with the XorMask
450 // this approach suffices for simple color manipulations
451 // and also the complex-clipping-XOR-trick used in metafiles
452 const sal_uLong* pSrc = m_pMaskBuffer;
453 sal_uLong* pDst = m_pTempBuffer;
454 for( int i = m_nBufferLongs; --i >= 0;)
456 *(pDst++) ^= *(pSrc++);
458 // write back the XOR results to the target context
459 if( m_xTempContext )
461 CGImageRef xXorImage = CGBitmapContextCreateImage( m_xTempContext );
462 const int nWidth = static_cast<int>(CGImageGetWidth( xXorImage ));
463 const int nHeight = static_cast<int>(CGImageGetHeight( xXorImage ));
464 // TODO: update minimal changerect
465 const CGRect aFullRect = CGRectMake(0, 0, nWidth, nHeight);
466 CGContextDrawImage( m_xTargetContext, aFullRect, xXorImage );
467 CGImageRelease( xXorImage );
470 // reset the XorMask to black again
471 // TODO: not needed for last update
472 memset( m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong) );
474 // TODO: return FALSE if target was not changed
475 return true;
478 /// From salvd.cxx
480 void AquaSalVirtualDevice::Destroy()
482 SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::Destroy() this=" << this << " mbForeignContext=" << mbForeignContext );
484 if( mbForeignContext )
486 // Do not delete mxContext that we have received from outside VCL
487 maLayer.set(nullptr);
488 return;
491 if (maLayer.isSet())
493 if( mpGraphics )
495 mpGraphics->SetVirDevGraphics(this, nullptr, nullptr);
497 CGLayerRelease(maLayer.get());
498 maLayer.set(nullptr);
501 if (maBitmapContext.isSet())
503 void* pRawData = CGBitmapContextGetData(maBitmapContext.get());
504 std::free(pRawData);
505 CGContextRelease(maBitmapContext.get());
506 maBitmapContext.set(nullptr);
510 bool AquaSalVirtualDevice::SetSize( tools::Long nDX, tools::Long nDY )
512 SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
513 " (" << nDX << "x" << nDY << ") mbForeignContext=" << (mbForeignContext ? "YES" : "NO"));
515 if( mbForeignContext )
517 // Do not delete/resize mxContext that we have received from outside VCL
518 return true;
521 if (maLayer.isSet())
523 const CGSize aSize = CGLayerGetSize(maLayer.get());
524 if( (nDX == aSize.width) && (nDY == aSize.height) )
526 // Yay, we do not have to do anything :)
527 return true;
531 Destroy();
533 mnWidth = nDX;
534 mnHeight = nDY;
536 // create a CGLayer matching to the intended virdev usage
537 CGContextHolder xCGContextHolder;
538 if( mnBitmapDepth && (mnBitmapDepth < 16) )
540 mnBitmapDepth = 8; // TODO: are 1bit vdevs worth it?
541 const int nBytesPerRow = (mnBitmapDepth * nDX + 7) / 8;
543 void* pRawData = std::malloc( nBytesPerRow * nDY );
544 maBitmapContext.set(CGBitmapContextCreate( pRawData, nDX, nDY,
545 mnBitmapDepth, nBytesPerRow,
546 GetSalData()->mxGraySpace, kCGImageAlphaNone));
547 xCGContextHolder = maBitmapContext;
549 else
551 if (!xCGContextHolder.isSet())
553 // assert(Application::IsBitmapRendering());
554 mnBitmapDepth = 32;
556 const int nBytesPerRow = (mnBitmapDepth * nDX) / 8;
557 void* pRawData = std::malloc( nBytesPerRow * nDY );
558 const int nFlags = kCGImageAlphaNoneSkipFirst | kCGImageByteOrder32Little;
559 maBitmapContext.set(CGBitmapContextCreate(pRawData, nDX, nDY, 8, nBytesPerRow,
560 GetSalData()->mxRGBSpace, nFlags));
561 xCGContextHolder = maBitmapContext;
565 SAL_WARN_IF(!xCGContextHolder.isSet(), "vcl.quartz", "No context");
567 const CGSize aNewSize = { static_cast<CGFloat>(nDX), static_cast<CGFloat>(nDY) };
568 maLayer.set(CGLayerCreateWithContext(xCGContextHolder.get(), aNewSize, nullptr));
570 if (maLayer.isSet() && mpGraphics)
572 // get the matching Quartz context
573 CGContextRef xDrawContext = CGLayerGetContext( maLayer.get() );
575 // Here we pass the CGLayerRef that the CGLayerHolder maLayer holds as the first parameter
576 // to SetVirDevGraphics(). That parameter is of type CGLayerHolder, so what we actually pass
577 // is an implicitly constructed *separate* CGLayerHolder. Is that what we want? No idea.
578 // Possibly we could pass just maLayer as such? But doing that does not fix tdf#138122.
579 mpGraphics->SetVirDevGraphics(this, maLayer.get(), xDrawContext, mnBitmapDepth);
582 return maLayer.isSet();
585 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */