1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 // MARKER(update_precomp.py): autogen include statement, do not remove 25 #include "precompiled_vcl.hxx" 26 27 #include "osl/file.hxx" 28 #include "osl/process.h" 29 30 #include "vos/mutex.hxx" 31 32 #include "rtl/bootstrap.h" 33 #include "rtl/strbuf.hxx" 34 35 #include "basegfx/range/b2drectangle.hxx" 36 #include "basegfx/polygon/b2dpolygon.hxx" 37 #include "basegfx/polygon/b2dpolygontools.hxx" 38 #include "basegfx/matrix/b2dhommatrix.hxx" 39 #include "basegfx/matrix/b2dhommatrixtools.hxx" 40 41 #include "vcl/sysdata.hxx" 42 #include "vcl/svapp.hxx" 43 44 #include "aqua/salconst.h" 45 #include "aqua/salgdi.h" 46 #include "aqua/salbmp.h" 47 #include "aqua/salframe.h" 48 #include "aqua/salcolorutils.hxx" 49 #include "aqua/salatsuifontutils.hxx" 50 51 #include "fontsubset.hxx" 52 #include "impfont.hxx" 53 #include "sallayout.hxx" 54 #include "sft.hxx" 55 56 57 using namespace vcl; 58 59 //typedef unsigned char Boolean; // copied from MacTypes.h, should be properly included 60 typedef std::vector<unsigned char> ByteVector; 61 62 63 // ======================================================================= 64 65 ImplMacFontData::ImplMacFontData( const ImplDevFontAttributes& rDFA, ATSUFontID nFontId ) 66 : ImplFontData( rDFA, 0 ) 67 , mnFontId( nFontId ) 68 , mpCharMap( NULL ) 69 , mbOs2Read( false ) 70 , mbHasOs2Table( false ) 71 , mbCmapEncodingRead( false ) 72 , mbHasCJKSupport( false ) 73 {} 74 75 // ----------------------------------------------------------------------- 76 77 ImplMacFontData::~ImplMacFontData() 78 { 79 if( mpCharMap ) 80 mpCharMap->DeReference(); 81 } 82 83 // ----------------------------------------------------------------------- 84 85 sal_IntPtr ImplMacFontData::GetFontId() const 86 { 87 return (sal_IntPtr)mnFontId; 88 } 89 90 // ----------------------------------------------------------------------- 91 92 ImplFontData* ImplMacFontData::Clone() const 93 { 94 ImplMacFontData* pClone = new ImplMacFontData(*this); 95 if( mpCharMap ) 96 mpCharMap->AddReference(); 97 return pClone; 98 } 99 100 // ----------------------------------------------------------------------- 101 102 ImplFontEntry* ImplMacFontData::CreateFontInstance(ImplFontSelectData& rFSD) const 103 { 104 return new ImplFontEntry(rFSD); 105 } 106 107 // ----------------------------------------------------------------------- 108 109 inline FourCharCode GetTag(const char aTagName[5]) 110 { 111 return (aTagName[0]<<24)+(aTagName[1]<<16)+(aTagName[2]<<8)+(aTagName[3]); 112 } 113 114 static unsigned GetUShort( const unsigned char* p ){return((p[0]<<8)+p[1]);} 115 static unsigned GetUInt( const unsigned char* p ) { return((p[0]<<24)+(p[1]<<16)+(p[2]<<8)+p[3]);} 116 117 const ImplFontCharMap* ImplMacFontData::GetImplFontCharMap() const 118 { 119 // return the cached charmap 120 if( mpCharMap ) 121 return mpCharMap; 122 123 // set the default charmap 124 mpCharMap = ImplFontCharMap::GetDefaultMap(); 125 mpCharMap->AddReference(); 126 127 // get the CMAP byte size 128 ATSFontRef rFont = FMGetATSFontRefFromFont( mnFontId ); 129 ByteCount nBufSize = 0; 130 OSStatus eStatus = ATSFontGetTable( rFont, GetTag("cmap"), 0, 0, NULL, &nBufSize ); 131 DBG_ASSERT( (eStatus==noErr), "ImplMacFontData::GetImplFontCharMap : ATSFontGetTable1 failed!\n"); 132 if( eStatus != noErr ) 133 return mpCharMap; 134 135 // allocate a buffer for the CMAP raw data 136 ByteVector aBuffer( nBufSize ); 137 138 // get the CMAP raw data 139 ByteCount nRawLength = 0; 140 eStatus = ATSFontGetTable( rFont, GetTag("cmap"), 0, nBufSize, (void*)&aBuffer[0], &nRawLength ); 141 DBG_ASSERT( (eStatus==noErr), "ImplMacFontData::GetImplFontCharMap : ATSFontGetTable2 failed!\n"); 142 if( eStatus != noErr ) 143 return mpCharMap; 144 DBG_ASSERT( (nBufSize==nRawLength), "ImplMacFontData::GetImplFontCharMap : ByteCount mismatch!\n"); 145 146 // parse the CMAP 147 CmapResult aCmapResult; 148 if( ParseCMAP( &aBuffer[0], nRawLength, aCmapResult ) ) 149 { 150 // create the matching charmap 151 mpCharMap->DeReference(); 152 mpCharMap = new ImplFontCharMap( aCmapResult ); 153 mpCharMap->AddReference(); 154 } 155 156 return mpCharMap; 157 } 158 159 // ----------------------------------------------------------------------- 160 161 void ImplMacFontData::ReadOs2Table( void ) const 162 { 163 // read this only once per font 164 if( mbOs2Read ) 165 return; 166 mbOs2Read = true; 167 168 // prepare to get the OS/2 table raw data 169 ATSFontRef rFont = FMGetATSFontRefFromFont( mnFontId ); 170 ByteCount nBufSize = 0; 171 OSStatus eStatus = ATSFontGetTable( rFont, GetTag("OS/2"), 0, 0, NULL, &nBufSize ); 172 DBG_ASSERT( (eStatus==noErr), "ImplMacFontData::ReadOs2Table : ATSFontGetTable1 failed!\n"); 173 if( eStatus != noErr ) 174 return; 175 176 // allocate a buffer for the OS/2 raw data 177 ByteVector aBuffer( nBufSize ); 178 179 // get the OS/2 raw data 180 ByteCount nRawLength = 0; 181 eStatus = ATSFontGetTable( rFont, GetTag("OS/2"), 0, nBufSize, (void*)&aBuffer[0], &nRawLength ); 182 DBG_ASSERT( (eStatus==noErr), "ImplMacFontData::ReadOs2Table : ATSFontGetTable2 failed!\n"); 183 if( eStatus != noErr ) 184 return; 185 DBG_ASSERT( (nBufSize==nRawLength), "ImplMacFontData::ReadOs2Table : ByteCount mismatch!\n"); 186 mbHasOs2Table = true; 187 188 // parse the OS/2 raw data 189 // TODO: also analyze panose info, etc. 190 191 // check if the fonts needs the "CJK extra leading" heuristic 192 const unsigned char* pOS2map = &aBuffer[0]; 193 const sal_uInt32 nVersion = GetUShort( pOS2map ); 194 if( nVersion >= 0x0001 ) 195 { 196 sal_uInt32 ulUnicodeRange2 = GetUInt( pOS2map + 46 ); 197 if( ulUnicodeRange2 & 0x2DF00000 ) 198 mbHasCJKSupport = true; 199 } 200 } 201 202 void ImplMacFontData::ReadMacCmapEncoding( void ) const 203 { 204 // read this only once per font 205 if( mbCmapEncodingRead ) 206 return; 207 mbCmapEncodingRead = true; 208 209 ATSFontRef rFont = FMGetATSFontRefFromFont( mnFontId ); 210 ByteCount nBufSize = 0; 211 OSStatus eStatus = ATSFontGetTable( rFont, GetTag("cmap"), 0, 0, NULL, &nBufSize ); 212 DBG_ASSERT( (eStatus==noErr), "ImplMacFontData::ReadMacCmapEncoding : ATSFontGetTable1 failed!\n"); 213 if( eStatus != noErr ) 214 return; 215 216 ByteVector aBuffer( nBufSize ); 217 218 ByteCount nRawLength = 0; 219 eStatus = ATSFontGetTable( rFont, GetTag("cmap"), 0, nBufSize, (void*)&aBuffer[0], &nRawLength ); 220 DBG_ASSERT( (eStatus==noErr), "ImplMacFontData::ReadMacCmapEncoding : ATSFontGetTable2 failed!\n"); 221 if( eStatus != noErr ) 222 return; 223 DBG_ASSERT( (nBufSize==nRawLength), "ImplMacFontData::ReadMacCmapEncoding : ByteCount mismatch!\n"); 224 225 const unsigned char* pCmap = &aBuffer[0]; 226 227 if (nRawLength < 24 ) 228 return; 229 if( GetUShort( pCmap ) != 0x0000 ) 230 return; 231 232 // check if the fonts needs the "CJK extra leading" heuristic 233 int nSubTables = GetUShort( pCmap + 2 ); 234 235 for( const unsigned char* p = pCmap + 4; --nSubTables >= 0; p += 8 ) 236 { 237 int nPlatform = GetUShort( p ); 238 if( nPlatform == kFontMacintoshPlatform ) { 239 int nEncoding = GetUShort (p + 2 ); 240 if( nEncoding == kFontJapaneseScript || 241 nEncoding == kFontTraditionalChineseScript || 242 nEncoding == kFontKoreanScript || 243 nEncoding == kFontSimpleChineseScript ) 244 { 245 mbHasCJKSupport = true; 246 break; 247 } 248 } 249 } 250 } 251 252 // ----------------------------------------------------------------------- 253 254 bool ImplMacFontData::HasCJKSupport( void ) const 255 { 256 ReadOs2Table(); 257 if( !mbHasOs2Table ) 258 ReadMacCmapEncoding(); 259 260 return mbHasCJKSupport; 261 } 262 263 // ======================================================================= 264 265 AquaSalGraphics::AquaSalGraphics() 266 : mpFrame( NULL ) 267 , mxLayer( NULL ) 268 , mrContext( NULL ) 269 , mpXorEmulation( NULL ) 270 , mnXorMode( 0 ) 271 , mnWidth( 0 ) 272 , mnHeight( 0 ) 273 , mnBitmapDepth( 0 ) 274 , mnRealDPIX( 0 ) 275 , mnRealDPIY( 0 ) 276 , mfFakeDPIScale( 1.0 ) 277 , mxClipPath( NULL ) 278 , maLineColor( COL_WHITE ) 279 , maFillColor( COL_BLACK ) 280 , mpMacFontData( NULL ) 281 , mnATSUIRotation( 0 ) 282 , mfFontScale( 1.0 ) 283 , mfFontStretch( 1.0 ) 284 , mbNonAntialiasedText( false ) 285 , mbPrinter( false ) 286 , mbVirDev( false ) 287 , mbWindow( false ) 288 { 289 // create the style object for font attributes 290 ATSUCreateStyle( &maATSUStyle ); 291 } 292 293 // ----------------------------------------------------------------------- 294 295 AquaSalGraphics::~AquaSalGraphics() 296 { 297 /* 298 if( mnUpdateGraphicsEvent ) 299 { 300 Application::RemoveUserEvent( mnUpdateGraphicsEvent ); 301 } 302 */ 303 CGPathRelease( mxClipPath ); 304 ATSUDisposeStyle( maATSUStyle ); 305 306 if( mpXorEmulation ) 307 delete mpXorEmulation; 308 309 if( mxLayer ) 310 CGLayerRelease( mxLayer ); 311 else if( mrContext && mbWindow ) 312 { 313 // destroy backbuffer bitmap context that we created ourself 314 CGContextRelease( mrContext ); 315 mrContext = NULL; 316 // memory is freed automatically by maOwnContextMemory 317 } 318 } 319 320 bool AquaSalGraphics::supportsOperation( OutDevSupportType eType ) const 321 { 322 bool bRet = false; 323 switch( eType ) 324 { 325 case OutDevSupport_TransparentRect: 326 case OutDevSupport_B2DClip: 327 case OutDevSupport_B2DDraw: 328 bRet = true; 329 break; 330 default: break; 331 } 332 return bRet; 333 } 334 335 // ======================================================================= 336 337 void AquaSalGraphics::updateResolution() 338 { 339 DBG_ASSERT( mbWindow, "updateResolution on inappropriate graphics" ); 340 341 initResolution( (mbWindow && mpFrame) ? mpFrame->mpWindow : nil ); 342 } 343 344 void AquaSalGraphics::initResolution( NSWindow* ) 345 { 346 // #i100617# read DPI only once; there is some kind of weird caching going on 347 // if the main screen changes 348 // FIXME: this is really unfortunate and needs to be investigated 349 350 SalData* pSalData = GetSalData(); 351 if( pSalData->mnDPIX == 0 || pSalData->mnDPIY == 0 ) 352 { 353 NSScreen* pScreen = nil; 354 355 /* #i91301# 356 many woes went into the try to have different resolutions 357 on different screens. The result of these trials is that OOo is not ready 358 for that yet, vcl and applications would need to be adapted. 359 360 Unfortunately this is not possible in the 3.0 timeframe. 361 So let's stay with one resolution for all Windows and VirtualDevices 362 which is the resolution of the main screen 363 364 This of course also means that measurements are exact only on the main screen. 365 For activating different resolutions again just comment out the two lines below. 366 367 if( pWin ) 368 pScreen = [pWin screen]; 369 */ 370 if( pScreen == nil ) 371 { 372 NSArray* pScreens = [NSScreen screens]; 373 if( pScreens ) 374 pScreen = [pScreens objectAtIndex: 0]; 375 } 376 377 mnRealDPIX = mnRealDPIY = 96; 378 if( pScreen ) 379 { 380 NSDictionary* pDev = [pScreen deviceDescription]; 381 if( pDev ) 382 { 383 NSNumber* pVal = [pDev objectForKey: @"NSScreenNumber"]; 384 if( pVal ) 385 { 386 // FIXME: casting a long to CGDirectDisplayID is evil, but 387 // Apple suggest to do it this way 388 const CGDirectDisplayID nDisplayID = (CGDirectDisplayID)[pVal longValue]; 389 const CGSize aSize = CGDisplayScreenSize( nDisplayID ); // => result is in millimeters 390 mnRealDPIX = static_cast<long>((CGDisplayPixelsWide( nDisplayID ) * 25.4) / aSize.width); 391 mnRealDPIY = static_cast<long>((CGDisplayPixelsHigh( nDisplayID ) * 25.4) / aSize.height); 392 } 393 else 394 { 395 DBG_ERROR( "no resolution found in device description" ); 396 } 397 } 398 else 399 { 400 DBG_ERROR( "no device description" ); 401 } 402 } 403 else 404 { 405 DBG_ERROR( "no screen found" ); 406 } 407 408 // #i107076# maintaining size-WYSIWYG-ness causes many problems for 409 // low-DPI, high-DPI or for mis-reporting devices 410 // => it is better to limit the calculation result then 411 static const int nMinDPI = 72; 412 if( (mnRealDPIX < nMinDPI) || (mnRealDPIY < nMinDPI) ) 413 mnRealDPIX = mnRealDPIY = nMinDPI; 414 static const int nMaxDPI = 200; 415 if( (mnRealDPIX > nMaxDPI) || (mnRealDPIY > nMaxDPI) ) 416 mnRealDPIX = mnRealDPIY = nMaxDPI; 417 418 // for OSX any anisotropy reported for the display resolution is best ignored (e.g. TripleHead2Go) 419 mnRealDPIX = mnRealDPIY = (mnRealDPIX + mnRealDPIY + 1) / 2; 420 421 pSalData->mnDPIX = mnRealDPIX; 422 pSalData->mnDPIY = mnRealDPIY; 423 } 424 else 425 { 426 mnRealDPIX = pSalData->mnDPIX; 427 mnRealDPIY = pSalData->mnDPIY; 428 } 429 430 mfFakeDPIScale = 1.0; 431 } 432 433 void AquaSalGraphics::GetResolution( long& rDPIX, long& rDPIY ) 434 { 435 if( !mnRealDPIY ) 436 initResolution( (mbWindow && mpFrame) ? mpFrame->mpWindow : nil ); 437 438 rDPIX = static_cast<long>(mfFakeDPIScale * mnRealDPIX); 439 rDPIY = static_cast<long>(mfFakeDPIScale * mnRealDPIY); 440 } 441 442 void AquaSalGraphics::copyResolution( AquaSalGraphics& rGraphics ) 443 { 444 if( !rGraphics.mnRealDPIY && rGraphics.mbWindow && rGraphics.mpFrame ) 445 rGraphics.initResolution( rGraphics.mpFrame->mpWindow ); 446 447 mnRealDPIX = rGraphics.mnRealDPIX; 448 mnRealDPIY = rGraphics.mnRealDPIY; 449 mfFakeDPIScale = rGraphics.mfFakeDPIScale; 450 } 451 452 // ----------------------------------------------------------------------- 453 454 sal_uInt16 AquaSalGraphics::GetBitCount() 455 { 456 sal_uInt16 nBits = mnBitmapDepth ? mnBitmapDepth : 32;//24; 457 return nBits; 458 } 459 460 // ----------------------------------------------------------------------- 461 462 static const basegfx::B2DPoint aHalfPointOfs ( 0.5, 0.5 ); 463 464 static void AddPolygonToPath( CGMutablePathRef xPath, 465 const ::basegfx::B2DPolygon& rPolygon, bool bClosePath, bool bPixelSnap, bool bLineDraw ) 466 { 467 // short circuit if there is nothing to do 468 const int nPointCount = rPolygon.count(); 469 if( nPointCount <= 0 ) 470 return; 471 472 (void)bPixelSnap; // TODO 473 const CGAffineTransform* pTransform = NULL; 474 475 const bool bHasCurves = rPolygon.areControlPointsUsed(); 476 for( int nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++ ) 477 { 478 int nClosedIdx = nPointIdx; 479 if( nPointIdx >= nPointCount ) 480 { 481 // prepare to close last curve segment if needed 482 if( bClosePath && (nPointIdx == nPointCount) ) 483 nClosedIdx = 0; 484 else 485 break; 486 } 487 488 ::basegfx::B2DPoint aPoint = rPolygon.getB2DPoint( nClosedIdx ); 489 490 if( bPixelSnap) 491 { 492 // snap device coordinates to full pixels 493 aPoint.setX( basegfx::fround( aPoint.getX() ) ); 494 aPoint.setY( basegfx::fround( aPoint.getY() ) ); 495 } 496 497 if( bLineDraw ) 498 aPoint += aHalfPointOfs; 499 500 if( !nPointIdx ) { // first point => just move there 501 CGPathMoveToPoint( xPath, pTransform, aPoint.getX(), aPoint.getY() ); 502 continue; 503 } 504 505 bool bPendingCurve = false; 506 if( bHasCurves ) 507 { 508 bPendingCurve = rPolygon.isNextControlPointUsed( nPrevIdx ); 509 bPendingCurve |= rPolygon.isPrevControlPointUsed( nClosedIdx ); 510 } 511 512 if( !bPendingCurve ) // line segment 513 CGPathAddLineToPoint( xPath, pTransform, aPoint.getX(), aPoint.getY() ); 514 else // cubic bezier segment 515 { 516 basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint( nPrevIdx ); 517 basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint( nClosedIdx ); 518 if( bLineDraw ) 519 { 520 aCP1 += aHalfPointOfs; 521 aCP2 += aHalfPointOfs; 522 } 523 CGPathAddCurveToPoint( xPath, pTransform, aCP1.getX(), aCP1.getY(), 524 aCP2.getX(), aCP2.getY(), aPoint.getX(), aPoint.getY() ); 525 } 526 } 527 528 if( bClosePath ) 529 CGPathCloseSubpath( xPath ); 530 } 531 532 static void AddPolyPolygonToPath( CGMutablePathRef xPath, 533 const ::basegfx::B2DPolyPolygon& rPolyPoly, bool bPixelSnap, bool bLineDraw ) 534 { 535 // short circuit if there is nothing to do 536 const int nPolyCount = rPolyPoly.count(); 537 if( nPolyCount <= 0 ) 538 return; 539 540 for( int nPolyIdx = 0; nPolyIdx < nPolyCount; ++nPolyIdx ) 541 { 542 const ::basegfx::B2DPolygon rPolygon = rPolyPoly.getB2DPolygon( nPolyIdx ); 543 AddPolygonToPath( xPath, rPolygon, true, bPixelSnap, bLineDraw ); 544 } 545 } 546 547 // ----------------------------------------------------------------------- 548 549 void AquaSalGraphics::ResetClipRegion() 550 { 551 // release old path and indicate no clipping 552 if( mxClipPath ) 553 { 554 CGPathRelease( mxClipPath ); 555 mxClipPath = NULL; 556 } 557 if( CheckContext() ) 558 SetState(); 559 } 560 561 // ----------------------------------------------------------------------- 562 563 bool AquaSalGraphics::setClipRegion( const Region& i_rClip ) 564 { 565 // release old clip path 566 if( mxClipPath ) 567 { 568 CGPathRelease( mxClipPath ); 569 mxClipPath = NULL; 570 } 571 mxClipPath = CGPathCreateMutable(); 572 573 // set current path, either as polypolgon or sequence of rectangles 574 if(i_rClip.HasPolyPolygonOrB2DPolyPolygon()) 575 { 576 const basegfx::B2DPolyPolygon aClip(i_rClip.GetAsB2DPolyPolygon()); 577 578 AddPolyPolygonToPath( mxClipPath, aClip, !getAntiAliasB2DDraw(), false ); 579 } 580 else 581 { 582 RectangleVector aRectangles; 583 i_rClip.GetRegionRectangles(aRectangles); 584 585 for(RectangleVector::const_iterator aRectIter(aRectangles.begin()); aRectIter != aRectangles.end(); aRectIter++) 586 { 587 const long nW(aRectIter->Right() - aRectIter->Left() + 1); // uses +1 logic in original 588 589 if(nW) 590 { 591 const long nH(aRectIter->Bottom() - aRectIter->Top() + 1); // uses +1 logic in original 592 593 if(nH) 594 { 595 CGRect aRect = {{ aRectIter->Left(), aRectIter->Top() }, { nW, nH }}; 596 CGPathAddRect( mxClipPath, NULL, aRect ); 597 } 598 } 599 } 600 } 601 // set the current path as clip region 602 if( CheckContext() ) 603 SetState(); 604 return true; 605 } 606 607 // ----------------------------------------------------------------------- 608 609 void AquaSalGraphics::SetLineColor() 610 { 611 maLineColor.SetAlpha( 0.0 ); // transparent 612 if( CheckContext() ) 613 CGContextSetStrokeColor( mrContext, maLineColor.AsArray() ); 614 } 615 616 // ----------------------------------------------------------------------- 617 618 void AquaSalGraphics::SetLineColor( SalColor nSalColor ) 619 { 620 maLineColor = RGBAColor( nSalColor ); 621 if( CheckContext() ) 622 CGContextSetStrokeColor( mrContext, maLineColor.AsArray() ); 623 } 624 625 // ----------------------------------------------------------------------- 626 627 void AquaSalGraphics::SetFillColor() 628 { 629 maFillColor.SetAlpha( 0.0 ); // transparent 630 if( CheckContext() ) 631 CGContextSetFillColor( mrContext, maFillColor.AsArray() ); 632 } 633 634 // ----------------------------------------------------------------------- 635 636 void AquaSalGraphics::SetFillColor( SalColor nSalColor ) 637 { 638 maFillColor = RGBAColor( nSalColor ); 639 if( CheckContext() ) 640 CGContextSetFillColor( mrContext, maFillColor.AsArray() ); 641 } 642 643 // ----------------------------------------------------------------------- 644 645 static SalColor ImplGetROPSalColor( SalROPColor nROPColor ) 646 { 647 SalColor nSalColor; 648 if ( nROPColor == SAL_ROP_0 ) 649 nSalColor = MAKE_SALCOLOR( 0, 0, 0 ); 650 else 651 nSalColor = MAKE_SALCOLOR( 255, 255, 255 ); 652 return nSalColor; 653 } 654 655 void AquaSalGraphics::SetROPLineColor( SalROPColor nROPColor ) 656 { 657 if( ! mbPrinter ) 658 SetLineColor( ImplGetROPSalColor( nROPColor ) ); 659 } 660 661 // ----------------------------------------------------------------------- 662 663 void AquaSalGraphics::SetROPFillColor( SalROPColor nROPColor ) 664 { 665 if( ! mbPrinter ) 666 SetFillColor( ImplGetROPSalColor( nROPColor ) ); 667 } 668 669 // ----------------------------------------------------------------------- 670 671 void AquaSalGraphics::ImplDrawPixel( long nX, long nY, const RGBAColor& rColor ) 672 { 673 if( !CheckContext() ) 674 return; 675 676 // overwrite the fill color 677 CGContextSetFillColor( mrContext, rColor.AsArray() ); 678 // draw 1x1 rect, there is no pixel drawing in Quartz 679 CGRect aDstRect = {{nX,nY,},{1,1}}; 680 CGContextFillRect( mrContext, aDstRect ); 681 RefreshRect( aDstRect ); 682 // reset the fill color 683 CGContextSetFillColor( mrContext, maFillColor.AsArray() ); 684 } 685 686 void AquaSalGraphics::drawPixel( long nX, long nY ) 687 { 688 // draw pixel with current line color 689 ImplDrawPixel( nX, nY, maLineColor ); 690 } 691 692 void AquaSalGraphics::drawPixel( long nX, long nY, SalColor nSalColor ) 693 { 694 const RGBAColor aPixelColor( nSalColor ); 695 ImplDrawPixel( nX, nY, aPixelColor ); 696 } 697 698 // ----------------------------------------------------------------------- 699 700 void AquaSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 ) 701 { 702 if( nX1 == nX2 && nY1 == nY2 ) 703 { 704 // #i109453# platform independent code expects at least one pixel to be drawn 705 drawPixel( nX1, nY1 ); 706 return; 707 } 708 709 if( !CheckContext() ) 710 return; 711 712 CGContextBeginPath( mrContext ); 713 CGContextMoveToPoint( mrContext, static_cast<float>(nX1)+0.5, static_cast<float>(nY1)+0.5 ); 714 CGContextAddLineToPoint( mrContext, static_cast<float>(nX2)+0.5, static_cast<float>(nY2)+0.5 ); 715 CGContextDrawPath( mrContext, kCGPathStroke ); 716 717 Rectangle aRefreshRect( nX1, nY1, nX2, nY2 ); 718 } 719 720 // ----------------------------------------------------------------------- 721 722 void AquaSalGraphics::drawRect( long nX, long nY, long nWidth, long nHeight ) 723 { 724 if( !CheckContext() ) 725 return; 726 727 CGRect aRect( CGRectMake(nX, nY, nWidth, nHeight) ); 728 if( IsPenVisible() ) 729 { 730 aRect.origin.x += 0.5; 731 aRect.origin.y += 0.5; 732 aRect.size.width -= 1; 733 aRect.size.height -= 1; 734 } 735 736 if( IsBrushVisible() ) 737 CGContextFillRect( mrContext, aRect ); 738 739 if( IsPenVisible() ) 740 CGContextStrokeRect( mrContext, aRect ); 741 742 RefreshRect( nX, nY, nWidth, nHeight ); 743 } 744 745 // ----------------------------------------------------------------------- 746 747 static void getBoundRect( sal_uLong nPoints, const SalPoint *pPtAry, long &rX, long& rY, long& rWidth, long& rHeight ) 748 { 749 long nX1 = pPtAry->mnX; 750 long nX2 = nX1; 751 long nY1 = pPtAry->mnY; 752 long nY2 = nY1; 753 for( sal_uLong n = 1; n < nPoints; n++ ) 754 { 755 if( pPtAry[n].mnX < nX1 ) 756 nX1 = pPtAry[n].mnX; 757 else if( pPtAry[n].mnX > nX2 ) 758 nX2 = pPtAry[n].mnX; 759 760 if( pPtAry[n].mnY < nY1 ) 761 nY1 = pPtAry[n].mnY; 762 else if( pPtAry[n].mnY > nY2 ) 763 nY2 = pPtAry[n].mnY; 764 } 765 rX = nX1; 766 rY = nY1; 767 rWidth = nX2 - nX1 + 1; 768 rHeight = nY2 - nY1 + 1; 769 } 770 771 static inline void alignLinePoint( const SalPoint* i_pIn, float& o_fX, float& o_fY ) 772 { 773 o_fX = static_cast<float>(i_pIn->mnX ) + 0.5; 774 o_fY = static_cast<float>(i_pIn->mnY ) + 0.5; 775 } 776 777 void AquaSalGraphics::drawPolyLine( sal_uLong nPoints, const SalPoint *pPtAry ) 778 { 779 if( nPoints < 1 ) 780 return; 781 if( !CheckContext() ) 782 return; 783 784 long nX = 0, nY = 0, nWidth = 0, nHeight = 0; 785 getBoundRect( nPoints, pPtAry, nX, nY, nWidth, nHeight ); 786 787 float fX, fY; 788 789 CGContextBeginPath( mrContext ); 790 alignLinePoint( pPtAry, fX, fY ); 791 CGContextMoveToPoint( mrContext, fX, fY ); 792 pPtAry++; 793 for( sal_uLong nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ ) 794 { 795 alignLinePoint( pPtAry, fX, fY ); 796 CGContextAddLineToPoint( mrContext, fX, fY ); 797 } 798 CGContextDrawPath( mrContext, kCGPathStroke ); 799 800 RefreshRect( nX, nY, nWidth, nHeight ); 801 } 802 803 // ----------------------------------------------------------------------- 804 805 void AquaSalGraphics::drawPolygon( sal_uLong nPoints, const SalPoint *pPtAry ) 806 { 807 if( nPoints <= 1 ) 808 return; 809 if( !CheckContext() ) 810 return; 811 812 long nX = 0, nY = 0, nWidth = 0, nHeight = 0; 813 getBoundRect( nPoints, pPtAry, nX, nY, nWidth, nHeight ); 814 815 CGPathDrawingMode eMode; 816 if( IsBrushVisible() && IsPenVisible() ) 817 eMode = kCGPathEOFillStroke; 818 else if( IsPenVisible() ) 819 eMode = kCGPathStroke; 820 else if( IsBrushVisible() ) 821 eMode = kCGPathEOFill; 822 else 823 return; 824 825 CGContextBeginPath( mrContext ); 826 827 if( IsPenVisible() ) 828 { 829 float fX, fY; 830 alignLinePoint( pPtAry, fX, fY ); 831 CGContextMoveToPoint( mrContext, fX, fY ); 832 pPtAry++; 833 for( sal_uLong nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ ) 834 { 835 alignLinePoint( pPtAry, fX, fY ); 836 CGContextAddLineToPoint( mrContext, fX, fY ); 837 } 838 } 839 else 840 { 841 CGContextMoveToPoint( mrContext, pPtAry->mnX, pPtAry->mnY ); 842 pPtAry++; 843 for( sal_uLong nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ ) 844 CGContextAddLineToPoint( mrContext, pPtAry->mnX, pPtAry->mnY ); 845 } 846 847 CGContextDrawPath( mrContext, eMode ); 848 RefreshRect( nX, nY, nWidth, nHeight ); 849 } 850 851 // ----------------------------------------------------------------------- 852 853 void AquaSalGraphics::drawPolyPolygon( sal_uLong nPolyCount, const sal_uLong *pPoints, PCONSTSALPOINT *ppPtAry ) 854 { 855 if( nPolyCount <= 0 ) 856 return; 857 if( !CheckContext() ) 858 return; 859 860 // find bound rect 861 long leftX = 0, topY = 0, maxWidth = 0, maxHeight = 0; 862 getBoundRect( pPoints[0], ppPtAry[0], leftX, topY, maxWidth, maxHeight ); 863 for( sal_uLong n = 1; n < nPolyCount; n++ ) 864 { 865 long nX = leftX, nY = topY, nW = maxWidth, nH = maxHeight; 866 getBoundRect( pPoints[n], ppPtAry[n], nX, nY, nW, nH ); 867 if( nX < leftX ) 868 { 869 maxWidth += leftX - nX; 870 leftX = nX; 871 } 872 if( nY < topY ) 873 { 874 maxHeight += topY - nY; 875 topY = nY; 876 } 877 if( nX + nW > leftX + maxWidth ) 878 maxWidth = nX + nW - leftX; 879 if( nY + nH > topY + maxHeight ) 880 maxHeight = nY + nH - topY; 881 } 882 883 // prepare drawing mode 884 CGPathDrawingMode eMode; 885 if( IsBrushVisible() && IsPenVisible() ) 886 eMode = kCGPathEOFillStroke; 887 else if( IsPenVisible() ) 888 eMode = kCGPathStroke; 889 else if( IsBrushVisible() ) 890 eMode = kCGPathEOFill; 891 else 892 return; 893 894 // convert to CGPath 895 CGContextBeginPath( mrContext ); 896 if( IsPenVisible() ) 897 { 898 for( sal_uLong nPoly = 0; nPoly < nPolyCount; nPoly++ ) 899 { 900 const sal_uLong nPoints = pPoints[nPoly]; 901 if( nPoints > 1 ) 902 { 903 const SalPoint *pPtAry = ppPtAry[nPoly]; 904 float fX, fY; 905 alignLinePoint( pPtAry, fX, fY ); 906 CGContextMoveToPoint( mrContext, fX, fY ); 907 pPtAry++; 908 for( sal_uLong nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ ) 909 { 910 alignLinePoint( pPtAry, fX, fY ); 911 CGContextAddLineToPoint( mrContext, fX, fY ); 912 } 913 CGContextClosePath(mrContext); 914 } 915 } 916 } 917 else 918 { 919 for( sal_uLong nPoly = 0; nPoly < nPolyCount; nPoly++ ) 920 { 921 const sal_uLong nPoints = pPoints[nPoly]; 922 if( nPoints > 1 ) 923 { 924 const SalPoint *pPtAry = ppPtAry[nPoly]; 925 CGContextMoveToPoint( mrContext, pPtAry->mnX, pPtAry->mnY ); 926 pPtAry++; 927 for( sal_uLong nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ ) 928 CGContextAddLineToPoint( mrContext, pPtAry->mnX, pPtAry->mnY ); 929 CGContextClosePath(mrContext); 930 } 931 } 932 } 933 934 CGContextDrawPath( mrContext, eMode ); 935 936 RefreshRect( leftX, topY, maxWidth, maxHeight ); 937 } 938 939 // ----------------------------------------------------------------------- 940 941 bool AquaSalGraphics::drawPolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly, 942 double fTransparency ) 943 { 944 // short circuit if there is nothing to do 945 const int nPolyCount = rPolyPoly.count(); 946 if( nPolyCount <= 0 ) 947 return true; 948 949 // ignore invisible polygons 950 if( (fTransparency >= 1.0) || (fTransparency < 0) ) 951 return true; 952 953 // setup poly-polygon path 954 CGMutablePathRef xPath = CGPathCreateMutable(); 955 for( int nPolyIdx = 0; nPolyIdx < nPolyCount; ++nPolyIdx ) 956 { 957 const ::basegfx::B2DPolygon rPolygon = rPolyPoly.getB2DPolygon( nPolyIdx ); 958 AddPolygonToPath( xPath, rPolygon, true, !getAntiAliasB2DDraw(), IsPenVisible() ); 959 } 960 961 const CGRect aRefreshRect = CGPathGetBoundingBox( xPath ); 962 #ifndef NO_I97317_WORKAROUND 963 // #i97317# workaround for Quartz having problems with drawing small polygons 964 if( ! ((aRefreshRect.size.width <= 0.125) && (aRefreshRect.size.height <= 0.125)) ) 965 #endif 966 { 967 // use the path to prepare the graphics context 968 CGContextSaveGState( mrContext ); 969 CGContextBeginPath( mrContext ); 970 CGContextAddPath( mrContext, xPath ); 971 972 // draw path with antialiased polygon 973 CGContextSetShouldAntialias( mrContext, true ); 974 CGContextSetAlpha( mrContext, 1.0 - fTransparency ); 975 CGContextDrawPath( mrContext, kCGPathEOFillStroke ); 976 CGContextRestoreGState( mrContext ); 977 978 // mark modified rectangle as updated 979 RefreshRect( aRefreshRect ); 980 } 981 982 CGPathRelease( xPath ); 983 984 return true; 985 } 986 987 // ----------------------------------------------------------------------- 988 989 bool AquaSalGraphics::drawPolyLine( 990 const ::basegfx::B2DPolygon& rPolyLine, 991 double fTransparency, 992 const ::basegfx::B2DVector& rLineWidths, 993 basegfx::B2DLineJoin eLineJoin, 994 com::sun::star::drawing::LineCap eLineCap) 995 { 996 // short circuit if there is nothing to do 997 const int nPointCount = rPolyLine.count(); 998 if( nPointCount <= 0 ) 999 return true; 1000 1001 // reject requests that cannot be handled yet 1002 if( rLineWidths.getX() != rLineWidths.getY() ) 1003 return false; 1004 1005 // #i101491# Aqua does not support B2DLINEJOIN_NONE; return false to use 1006 // the fallback (own geometry preparation) 1007 // #i104886# linejoin-mode and thus the above only applies to "fat" lines 1008 if( (basegfx::B2DLINEJOIN_NONE == eLineJoin) 1009 && (rLineWidths.getX() > 1.3) ) 1010 return false; 1011 1012 // setup line attributes 1013 CGLineJoin aCGLineJoin = kCGLineJoinMiter; 1014 switch( eLineJoin ) { 1015 case ::basegfx::B2DLINEJOIN_NONE: aCGLineJoin = /*TODO?*/kCGLineJoinMiter; break; 1016 case ::basegfx::B2DLINEJOIN_MIDDLE: aCGLineJoin = /*TODO?*/kCGLineJoinMiter; break; 1017 case ::basegfx::B2DLINEJOIN_BEVEL: aCGLineJoin = kCGLineJoinBevel; break; 1018 case ::basegfx::B2DLINEJOIN_MITER: aCGLineJoin = kCGLineJoinMiter; break; 1019 case ::basegfx::B2DLINEJOIN_ROUND: aCGLineJoin = kCGLineJoinRound; break; 1020 } 1021 1022 // setup cap attribute 1023 CGLineCap aCGLineCap(kCGLineCapButt); 1024 1025 switch(eLineCap) 1026 { 1027 default: // com::sun::star::drawing::LineCap_BUTT: 1028 { 1029 aCGLineCap = kCGLineCapButt; 1030 break; 1031 } 1032 case com::sun::star::drawing::LineCap_ROUND: 1033 { 1034 aCGLineCap = kCGLineCapRound; 1035 break; 1036 } 1037 case com::sun::star::drawing::LineCap_SQUARE: 1038 { 1039 aCGLineCap = kCGLineCapSquare; 1040 break; 1041 } 1042 } 1043 1044 // setup poly-polygon path 1045 CGMutablePathRef xPath = CGPathCreateMutable(); 1046 AddPolygonToPath( xPath, rPolyLine, rPolyLine.isClosed(), !getAntiAliasB2DDraw(), true ); 1047 1048 const CGRect aRefreshRect = CGPathGetBoundingBox( xPath ); 1049 #ifndef NO_I97317_WORKAROUND 1050 // #i97317# workaround for Quartz having problems with drawing small polygons 1051 if( ! ((aRefreshRect.size.width <= 0.125) && (aRefreshRect.size.height <= 0.125)) ) 1052 #endif 1053 { 1054 // use the path to prepare the graphics context 1055 CGContextSaveGState( mrContext ); 1056 CGContextAddPath( mrContext, xPath ); 1057 // draw path with antialiased line 1058 CGContextSetShouldAntialias( mrContext, true ); 1059 CGContextSetAlpha( mrContext, 1.0 - fTransparency ); 1060 CGContextSetLineJoin( mrContext, aCGLineJoin ); 1061 CGContextSetLineCap( mrContext, aCGLineCap ); 1062 CGContextSetLineWidth( mrContext, rLineWidths.getX() ); 1063 CGContextDrawPath( mrContext, kCGPathStroke ); 1064 CGContextRestoreGState( mrContext ); 1065 1066 // mark modified rectangle as updated 1067 RefreshRect( aRefreshRect ); 1068 } 1069 1070 CGPathRelease( xPath ); 1071 1072 return true; 1073 } 1074 1075 // ----------------------------------------------------------------------- 1076 1077 sal_Bool AquaSalGraphics::drawPolyLineBezier( sal_uLong, const SalPoint*, const sal_uInt8* ) 1078 { 1079 return sal_False; 1080 } 1081 1082 // ----------------------------------------------------------------------- 1083 1084 sal_Bool AquaSalGraphics::drawPolygonBezier( sal_uLong, const SalPoint*, const sal_uInt8* ) 1085 { 1086 return sal_False; 1087 } 1088 1089 // ----------------------------------------------------------------------- 1090 1091 sal_Bool AquaSalGraphics::drawPolyPolygonBezier( sal_uLong, const sal_uLong*, 1092 const SalPoint* const*, const sal_uInt8* const* ) 1093 { 1094 return sal_False; 1095 } 1096 1097 // ----------------------------------------------------------------------- 1098 1099 void AquaSalGraphics::copyBits( const SalTwoRect *pPosAry, SalGraphics *pSrcGraphics ) 1100 { 1101 if( !pSrcGraphics ) 1102 pSrcGraphics = this; 1103 1104 //from unix salgdi2.cxx 1105 //[FIXME] find a better way to prevent calc from crashing when width and height are negative 1106 if( pPosAry->mnSrcWidth <= 0 1107 || pPosAry->mnSrcHeight <= 0 1108 || pPosAry->mnDestWidth <= 0 1109 || pPosAry->mnDestHeight <= 0 ) 1110 { 1111 return; 1112 } 1113 1114 // accelerate trivial operations 1115 /*const*/ AquaSalGraphics* pSrc = static_cast<AquaSalGraphics*>(pSrcGraphics); 1116 const bool bSameGraphics = (this == pSrc) || (mbWindow && mpFrame && pSrc->mbWindow && (mpFrame == pSrc->mpFrame)); 1117 if( bSameGraphics 1118 && (pPosAry->mnSrcWidth == pPosAry->mnDestWidth) 1119 && (pPosAry->mnSrcHeight == pPosAry->mnDestHeight)) 1120 { 1121 // short circuit if there is nothing to do 1122 if( (pPosAry->mnSrcX == pPosAry->mnDestX) 1123 && (pPosAry->mnSrcY == pPosAry->mnDestY)) 1124 return; 1125 // use copyArea() if source and destination context are identical 1126 copyArea( pPosAry->mnDestX, pPosAry->mnDestY, pPosAry->mnSrcX, pPosAry->mnSrcY, 1127 pPosAry->mnSrcWidth, pPosAry->mnSrcHeight, 0 ); 1128 return; 1129 } 1130 1131 ApplyXorContext(); 1132 pSrc->ApplyXorContext(); 1133 1134 DBG_ASSERT( pSrc->mxLayer!=NULL, "AquaSalGraphics::copyBits() from non-layered graphics" ); 1135 1136 const CGPoint aDstPoint = { +pPosAry->mnDestX - pPosAry->mnSrcX, pPosAry->mnDestY - pPosAry->mnSrcY }; 1137 if( (pPosAry->mnSrcWidth == pPosAry->mnDestWidth && pPosAry->mnSrcHeight == pPosAry->mnDestHeight) && 1138 (!mnBitmapDepth || (aDstPoint.x + pSrc->mnWidth) <= mnWidth) ) // workaround a Quartz crasher 1139 { 1140 // in XOR mode the drawing context is redirected to the XOR mask 1141 // if source and target are identical then copyBits() paints onto the target context though 1142 CGContextRef xCopyContext = mrContext; 1143 if( mpXorEmulation && mpXorEmulation->IsEnabled() ) 1144 if( pSrcGraphics == this ) 1145 xCopyContext = mpXorEmulation->GetTargetContext(); 1146 1147 CGContextSaveGState( xCopyContext ); 1148 const CGRect aDstRect = { {pPosAry->mnDestX, pPosAry->mnDestY}, {pPosAry->mnDestWidth, pPosAry->mnDestHeight} }; 1149 CGContextClipToRect( xCopyContext, aDstRect ); 1150 1151 // draw at new destination 1152 // NOTE: flipped drawing gets disabled for this, else the subimage would be drawn upside down 1153 if( pSrc->IsFlipped() ) 1154 { CGContextTranslateCTM( xCopyContext, 0, +mnHeight ); CGContextScaleCTM( xCopyContext, +1, -1 ); } 1155 // TODO: pSrc->size() != this->size() 1156 ::CGContextDrawLayerAtPoint( xCopyContext, aDstPoint, pSrc->mxLayer ); 1157 CGContextRestoreGState( xCopyContext ); 1158 // mark the destination rectangle as updated 1159 RefreshRect( aDstRect ); 1160 } 1161 else 1162 { 1163 SalBitmap* pBitmap = pSrc->getBitmap( pPosAry->mnSrcX, pPosAry->mnSrcY, pPosAry->mnSrcWidth, pPosAry->mnSrcHeight ); 1164 1165 if( pBitmap ) 1166 { 1167 SalTwoRect aPosAry( *pPosAry ); 1168 aPosAry.mnSrcX = 0; 1169 aPosAry.mnSrcY = 0; 1170 drawBitmap( &aPosAry, *pBitmap ); 1171 delete pBitmap; 1172 } 1173 } 1174 } 1175 1176 // ----------------------------------------------------------------------- 1177 1178 void AquaSalGraphics::copyArea( long nDstX, long nDstY,long nSrcX, long nSrcY, long nSrcWidth, long nSrcHeight, sal_uInt16 /*nFlags*/ ) 1179 { 1180 ApplyXorContext(); 1181 1182 #if 0 // TODO: make AquaSalBitmap as fast as the alternative implementation below 1183 SalBitmap* pBitmap = getBitmap( nSrcX, nSrcY, nSrcWidth, nSrcHeight ); 1184 if( pBitmap ) 1185 { 1186 SalTwoRect aPosAry; 1187 aPosAry.mnSrcX = 0; 1188 aPosAry.mnSrcY = 0; 1189 aPosAry.mnSrcWidth = nSrcWidth; 1190 aPosAry.mnSrcHeight = nSrcHeight; 1191 aPosAry.mnDestX = nDstX; 1192 aPosAry.mnDestY = nDstY; 1193 aPosAry.mnDestWidth = nSrcWidth; 1194 aPosAry.mnDestHeight = nSrcHeight; 1195 drawBitmap( &aPosAry, *pBitmap ); 1196 delete pBitmap; 1197 } 1198 #else 1199 DBG_ASSERT( mxLayer!=NULL, "AquaSalGraphics::copyArea() for non-layered graphics" ); 1200 1201 // in XOR mode the drawing context is redirected to the XOR mask 1202 // copyArea() always works on the target context though 1203 CGContextRef xCopyContext = mrContext; 1204 if( mpXorEmulation && mpXorEmulation->IsEnabled() ) 1205 xCopyContext = mpXorEmulation->GetTargetContext(); 1206 1207 // drawing a layer onto its own context causes trouble on OSX => copy it first 1208 // TODO: is it possible to get rid of this unneeded copy more often? 1209 // e.g. on OSX>=10.5 only this situation causes problems: 1210 // mnBitmapDepth && (aDstPoint.x + pSrc->mnWidth) > mnWidth 1211 CGLayerRef xSrcLayer = mxLayer; 1212 // TODO: if( mnBitmapDepth > 0 ) 1213 { 1214 const CGSize aSrcSize = { nSrcWidth, nSrcHeight }; 1215 xSrcLayer = ::CGLayerCreateWithContext( xCopyContext, aSrcSize, NULL ); 1216 const CGContextRef xSrcContext = CGLayerGetContext( xSrcLayer ); 1217 CGPoint aSrcPoint = { -nSrcX, -nSrcY }; 1218 if( IsFlipped() ) 1219 { 1220 ::CGContextTranslateCTM( xSrcContext, 0, +nSrcHeight ); 1221 ::CGContextScaleCTM( xSrcContext, +1, -1 ); 1222 aSrcPoint.y = (nSrcY + nSrcHeight) - mnHeight; 1223 } 1224 ::CGContextDrawLayerAtPoint( xSrcContext, aSrcPoint, mxLayer ); 1225 } 1226 1227 // draw at new destination 1228 const CGPoint aDstPoint = { +nDstX, +nDstY }; 1229 ::CGContextDrawLayerAtPoint( xCopyContext, aDstPoint, xSrcLayer ); 1230 1231 // cleanup 1232 if( xSrcLayer != mxLayer ) 1233 CGLayerRelease( xSrcLayer ); 1234 1235 // mark the destination rectangle as updated 1236 RefreshRect( nDstX, nDstY, nSrcWidth, nSrcHeight ); 1237 #endif 1238 } 1239 1240 // ----------------------------------------------------------------------- 1241 1242 void AquaSalGraphics::drawBitmap( const SalTwoRect* pPosAry, const SalBitmap& rSalBitmap ) 1243 { 1244 if( !CheckContext() ) 1245 return; 1246 1247 const AquaSalBitmap& rBitmap = static_cast<const AquaSalBitmap&>(rSalBitmap); 1248 CGImageRef xImage = rBitmap.CreateCroppedImage( (int)pPosAry->mnSrcX, (int)pPosAry->mnSrcY, (int)pPosAry->mnSrcWidth, (int)pPosAry->mnSrcHeight ); 1249 if( !xImage ) 1250 return; 1251 1252 const CGRect aDstRect = {{pPosAry->mnDestX, pPosAry->mnDestY}, {pPosAry->mnDestWidth, pPosAry->mnDestHeight}}; 1253 CGContextDrawImage( mrContext, aDstRect, xImage ); 1254 CGImageRelease( xImage ); 1255 RefreshRect( aDstRect ); 1256 } 1257 1258 // ----------------------------------------------------------------------- 1259 1260 void AquaSalGraphics::drawBitmap( const SalTwoRect* pPosAry, const SalBitmap& rSalBitmap,SalColor ) 1261 { 1262 DBG_ERROR("not implemented for color masking!"); 1263 drawBitmap( pPosAry, rSalBitmap ); 1264 } 1265 1266 // ----------------------------------------------------------------------- 1267 1268 void AquaSalGraphics::drawBitmap( const SalTwoRect* pPosAry, const SalBitmap& rSalBitmap, const SalBitmap& rTransparentBitmap ) 1269 { 1270 if( !CheckContext() ) 1271 return; 1272 1273 const AquaSalBitmap& rBitmap = static_cast<const AquaSalBitmap&>(rSalBitmap); 1274 const AquaSalBitmap& rMask = static_cast<const AquaSalBitmap&>(rTransparentBitmap); 1275 CGImageRef xMaskedImage( rBitmap.CreateWithMask( rMask, pPosAry->mnSrcX, pPosAry->mnSrcY, pPosAry->mnSrcWidth, pPosAry->mnSrcHeight ) ); 1276 if( !xMaskedImage ) 1277 return; 1278 1279 const CGRect aDstRect = {{pPosAry->mnDestX, pPosAry->mnDestY}, {pPosAry->mnDestWidth, pPosAry->mnDestHeight}}; 1280 CGContextDrawImage( mrContext, aDstRect, xMaskedImage ); 1281 CGImageRelease( xMaskedImage ); 1282 RefreshRect( aDstRect ); 1283 } 1284 1285 // ----------------------------------------------------------------------- 1286 1287 void AquaSalGraphics::drawMask( const SalTwoRect* pPosAry, const SalBitmap& rSalBitmap, SalColor nMaskColor ) 1288 { 1289 if( !CheckContext() ) 1290 return; 1291 1292 const AquaSalBitmap& rBitmap = static_cast<const AquaSalBitmap&>(rSalBitmap); 1293 CGImageRef xImage = rBitmap.CreateColorMask( pPosAry->mnSrcX, pPosAry->mnSrcY, pPosAry->mnSrcWidth, pPosAry->mnSrcHeight, nMaskColor ); 1294 if( !xImage ) 1295 return; 1296 1297 const CGRect aDstRect = {{pPosAry->mnDestX, pPosAry->mnDestY}, {pPosAry->mnDestWidth, pPosAry->mnDestHeight}}; 1298 CGContextDrawImage( mrContext, aDstRect, xImage ); 1299 CGImageRelease( xImage ); 1300 RefreshRect( aDstRect ); 1301 } 1302 1303 // ----------------------------------------------------------------------- 1304 1305 SalBitmap* AquaSalGraphics::getBitmap( long nX, long nY, long nDX, long nDY ) 1306 { 1307 DBG_ASSERT( mxLayer, "AquaSalGraphics::getBitmap() with no layer" ); 1308 1309 ApplyXorContext(); 1310 1311 AquaSalBitmap* pBitmap = new AquaSalBitmap; 1312 if( !pBitmap->Create( mxLayer, mnBitmapDepth, nX, nY, nDX, nDY, !mbWindow ) ) 1313 { 1314 delete pBitmap; 1315 pBitmap = NULL; 1316 } 1317 1318 return pBitmap; 1319 } 1320 1321 // ----------------------------------------------------------------------- 1322 1323 SalColor AquaSalGraphics::getPixel( long nX, long nY ) 1324 { 1325 // return default value on printers or when out of bounds 1326 if( !mxLayer 1327 || (nX < 0) || (nX >= mnWidth) 1328 || (nY < 0) || (nY >= mnHeight)) 1329 return COL_BLACK; 1330 1331 // prepare creation of matching a CGBitmapContext 1332 CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace; 1333 CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Big; 1334 #if __BIG_ENDIAN__ 1335 struct{ unsigned char b, g, r, a; } aPixel; 1336 #else 1337 struct{ unsigned char a, r, g, b; } aPixel; 1338 #endif 1339 1340 // create a one-pixel bitmap context 1341 // TODO: is it worth to cache it? 1342 CGContextRef xOnePixelContext = ::CGBitmapContextCreate( &aPixel, 1343 1, 1, 8, sizeof(aPixel), aCGColorSpace, aCGBmpInfo ); 1344 1345 // update this graphics layer 1346 ApplyXorContext(); 1347 1348 // copy the requested pixel into the bitmap context 1349 if( IsFlipped() ) 1350 nY = mnHeight - nY; 1351 const CGPoint aCGPoint = {-nX, -nY}; 1352 CGContextDrawLayerAtPoint( xOnePixelContext, aCGPoint, mxLayer ); 1353 CGContextRelease( xOnePixelContext ); 1354 1355 SalColor nSalColor = MAKE_SALCOLOR( aPixel.r, aPixel.g, aPixel.b ); 1356 return nSalColor; 1357 } 1358 1359 // ----------------------------------------------------------------------- 1360 1361 1362 static void DrawPattern50( void*, CGContextRef rContext ) 1363 { 1364 static const CGRect aRects[2] = { { {0,0}, { 2, 2 } }, { { 2, 2 }, { 2, 2 } } }; 1365 CGContextAddRects( rContext, aRects, 2 ); 1366 CGContextFillPath( rContext ); 1367 } 1368 1369 void AquaSalGraphics::Pattern50Fill() 1370 { 1371 static const float aFillCol[4] = { 1,1,1,1 }; 1372 static const CGPatternCallbacks aCallback = { 0, &DrawPattern50, NULL }; 1373 if( ! GetSalData()->mxP50Space ) 1374 GetSalData()->mxP50Space = CGColorSpaceCreatePattern( GetSalData()->mxRGBSpace ); 1375 if( ! GetSalData()->mxP50Pattern ) 1376 GetSalData()->mxP50Pattern = CGPatternCreate( NULL, CGRectMake( 0, 0, 4, 4 ), 1377 CGAffineTransformIdentity, 4, 4, 1378 kCGPatternTilingConstantSpacing, 1379 false, &aCallback ); 1380 1381 CGContextSetFillColorSpace( mrContext, GetSalData()->mxP50Space ); 1382 CGContextSetFillPattern( mrContext, GetSalData()->mxP50Pattern, aFillCol ); 1383 CGContextFillPath( mrContext ); 1384 } 1385 1386 void AquaSalGraphics::invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags ) 1387 { 1388 if ( CheckContext() ) 1389 { 1390 CGRect aCGRect = CGRectMake( nX, nY, nWidth, nHeight); 1391 CGContextSaveGState(mrContext); 1392 1393 if ( nFlags & SAL_INVERT_TRACKFRAME ) 1394 { 1395 const float dashLengths[2] = { 4.0, 4.0 }; // for drawing dashed line 1396 CGContextSetBlendMode( mrContext, kCGBlendModeDifference ); 1397 CGContextSetRGBStrokeColor ( mrContext, 1.0, 1.0, 1.0, 1.0 ); 1398 CGContextSetLineDash ( mrContext, 0, dashLengths, 2 ); 1399 CGContextSetLineWidth( mrContext, 2.0); 1400 CGContextStrokeRect ( mrContext, aCGRect ); 1401 } 1402 else if ( nFlags & SAL_INVERT_50 ) 1403 { 1404 //CGContextSetAllowsAntialiasing( mrContext, false ); 1405 CGContextSetBlendMode(mrContext, kCGBlendModeDifference); 1406 CGContextAddRect( mrContext, aCGRect ); 1407 Pattern50Fill(); 1408 } 1409 else // just invert 1410 { 1411 CGContextSetBlendMode(mrContext, kCGBlendModeDifference); 1412 CGContextSetRGBFillColor ( mrContext,1.0, 1.0, 1.0 , 1.0 ); 1413 CGContextFillRect ( mrContext, aCGRect ); 1414 } 1415 CGContextRestoreGState( mrContext); 1416 RefreshRect( aCGRect ); 1417 } 1418 } 1419 1420 // ----------------------------------------------------------------------- 1421 1422 void AquaSalGraphics::invert( sal_uLong nPoints, const SalPoint* pPtAry, SalInvert nSalFlags ) 1423 { 1424 CGPoint* CGpoints ; 1425 if ( CheckContext() ) 1426 { 1427 CGContextSaveGState(mrContext); 1428 CGpoints = makeCGptArray(nPoints,pPtAry); 1429 CGContextAddLines ( mrContext, CGpoints, nPoints ); 1430 if ( nSalFlags & SAL_INVERT_TRACKFRAME ) 1431 { 1432 const float dashLengths[2] = { 4.0, 4.0 }; // for drawing dashed line 1433 CGContextSetBlendMode( mrContext, kCGBlendModeDifference ); 1434 CGContextSetRGBStrokeColor ( mrContext, 1.0, 1.0, 1.0, 1.0 ); 1435 CGContextSetLineDash ( mrContext, 0, dashLengths, 2 ); 1436 CGContextSetLineWidth( mrContext, 2.0); 1437 CGContextStrokePath ( mrContext ); 1438 } 1439 else if ( nSalFlags & SAL_INVERT_50 ) 1440 { 1441 CGContextSetBlendMode(mrContext, kCGBlendModeDifference); 1442 Pattern50Fill(); 1443 } 1444 else // just invert 1445 { 1446 CGContextSetBlendMode( mrContext, kCGBlendModeDifference ); 1447 CGContextSetRGBFillColor( mrContext, 1.0, 1.0, 1.0, 1.0 ); 1448 CGContextFillPath( mrContext ); 1449 } 1450 const CGRect aRefreshRect = CGContextGetClipBoundingBox(mrContext); 1451 CGContextRestoreGState( mrContext); 1452 delete [] CGpoints; 1453 RefreshRect( aRefreshRect ); 1454 } 1455 } 1456 1457 // ----------------------------------------------------------------------- 1458 1459 sal_Bool AquaSalGraphics::drawEPS( long nX, long nY, long nWidth, long nHeight, 1460 void* pEpsData, sal_uLong nByteCount ) 1461 { 1462 // convert the raw data to an NSImageRef 1463 NSData* xNSData = [NSData dataWithBytes:(void*)pEpsData length:(int)nByteCount]; 1464 NSImageRep* xEpsImage = [NSEPSImageRep imageRepWithData: xNSData]; 1465 if( !xEpsImage ) 1466 return false; 1467 1468 // get the target context 1469 if( !CheckContext() ) 1470 return false; 1471 1472 // NOTE: flip drawing, else the nsimage would be drawn upside down 1473 CGContextSaveGState( mrContext ); 1474 // CGContextTranslateCTM( mrContext, 0, +mnHeight ); 1475 CGContextScaleCTM( mrContext, +1, -1 ); 1476 nY = /*mnHeight*/ - (nY + nHeight); 1477 1478 // prepare the target context 1479 NSGraphicsContext* pOrigNSCtx = [NSGraphicsContext currentContext]; 1480 [pOrigNSCtx retain]; 1481 1482 // create new context 1483 NSGraphicsContext* pDrawNSCtx = [NSGraphicsContext graphicsContextWithGraphicsPort: mrContext flipped: IsFlipped()]; 1484 // set it, setCurrentContext also releases the prviously set one 1485 [NSGraphicsContext setCurrentContext: pDrawNSCtx]; 1486 1487 // draw the EPS 1488 const NSRect aDstRect = {{nX,nY},{nWidth,nHeight}}; 1489 const BOOL bOK = [xEpsImage drawInRect: aDstRect]; 1490 1491 // restore the NSGraphicsContext 1492 [NSGraphicsContext setCurrentContext: pOrigNSCtx]; 1493 [pOrigNSCtx release]; // restore the original retain count 1494 1495 CGContextRestoreGState( mrContext ); 1496 // mark the destination rectangle as updated 1497 RefreshRect( aDstRect ); 1498 1499 return bOK; 1500 } 1501 1502 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 1503 bool AquaSalGraphics::drawAlphaBitmap( const SalTwoRect& rTR, 1504 const SalBitmap& rSrcBitmap, const SalBitmap& rAlphaBmp ) 1505 { 1506 // An image mask can't have a depth > 8 bits (should be 1 to 8 bits) 1507 if( rAlphaBmp.GetBitCount() > 8 ) 1508 return false; 1509 1510 // are these two tests really necessary? (see vcl/unx/source/gdi/salgdi2.cxx) 1511 // horizontal/vertical mirroring not implemented yet 1512 if( rTR.mnDestWidth < 0 || rTR.mnDestHeight < 0 ) 1513 return false; 1514 1515 const AquaSalBitmap& rSrcSalBmp = static_cast<const AquaSalBitmap&>(rSrcBitmap); 1516 const AquaSalBitmap& rMaskSalBmp = static_cast<const AquaSalBitmap&>(rAlphaBmp); 1517 1518 CGImageRef xMaskedImage = rSrcSalBmp.CreateWithMask( rMaskSalBmp, rTR.mnSrcX, rTR.mnSrcY, rTR.mnSrcWidth, rTR.mnSrcHeight ); 1519 if( !xMaskedImage ) 1520 return false; 1521 1522 if ( CheckContext() ) 1523 { 1524 const CGRect aDstRect = {{rTR.mnDestX, rTR.mnDestY}, {rTR.mnDestWidth, rTR.mnDestHeight}}; 1525 CGContextDrawImage( mrContext, aDstRect, xMaskedImage ); 1526 RefreshRect( aDstRect ); 1527 } 1528 1529 CGImageRelease(xMaskedImage); 1530 return true; 1531 } 1532 1533 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 1534 bool AquaSalGraphics::drawAlphaRect( long nX, long nY, long nWidth, 1535 long nHeight, sal_uInt8 nTransparency ) 1536 { 1537 if( !CheckContext() ) 1538 return true; 1539 1540 // save the current state 1541 CGContextSaveGState( mrContext ); 1542 CGContextSetAlpha( mrContext, (100-nTransparency) * (1.0/100) ); 1543 1544 CGRect aRect = {{nX,nY},{nWidth-1,nHeight-1}}; 1545 if( IsPenVisible() ) 1546 { 1547 aRect.origin.x += 0.5; 1548 aRect.origin.y += 0.5; 1549 } 1550 1551 CGContextBeginPath( mrContext ); 1552 CGContextAddRect( mrContext, aRect ); 1553 CGContextDrawPath( mrContext, kCGPathFill ); 1554 1555 // restore state 1556 CGContextRestoreGState(mrContext); 1557 RefreshRect( aRect ); 1558 return true; 1559 } 1560 1561 // ----------------------------------------------------------------------- 1562 1563 void AquaSalGraphics::SetTextColor( SalColor nSalColor ) 1564 { 1565 RGBColor color; 1566 color.red = (unsigned short) ( SALCOLOR_RED(nSalColor) * 65535.0 / 255.0 ); 1567 color.green = (unsigned short) ( SALCOLOR_GREEN(nSalColor) * 65535.0 / 255.0 ); 1568 color.blue = (unsigned short) ( SALCOLOR_BLUE(nSalColor) * 65535.0 / 255.0 ); 1569 1570 ATSUAttributeTag aTag = kATSUColorTag; 1571 ByteCount aValueSize = sizeof( color ); 1572 ATSUAttributeValuePtr aValue = &color; 1573 1574 OSStatus err = ATSUSetAttributes( maATSUStyle, 1, &aTag, &aValueSize, &aValue ); 1575 DBG_ASSERT( (err==noErr), "AquaSalGraphics::SetTextColor() : Could not set font attributes!\n"); 1576 if( err != noErr ) 1577 return; 1578 } 1579 1580 // ----------------------------------------------------------------------- 1581 1582 void AquaSalGraphics::GetFontMetric( ImplFontMetricData* pMetric, int nFallbackLevel ) 1583 { 1584 (void)nFallbackLevel; // glyph-fallback on ATSU is done differently -> no fallback level 1585 1586 // get the ATSU font metrics (in point units) 1587 // of the font that has eventually been size-limited 1588 1589 ATSUFontID fontId; 1590 OSStatus err = ATSUGetAttribute( maATSUStyle, kATSUFontTag, sizeof(ATSUFontID), &fontId, 0 ); 1591 DBG_ASSERT( (err==noErr), "AquaSalGraphics::GetFontMetric() : could not get font id\n"); 1592 1593 ATSFontMetrics aMetrics; 1594 ATSFontRef rFont = FMGetATSFontRefFromFont( fontId ); 1595 err = ATSFontGetHorizontalMetrics ( rFont, kATSOptionFlagsDefault, &aMetrics ); 1596 DBG_ASSERT( (err==noErr), "AquaSalGraphics::GetFontMetric() : could not get font metrics\n"); 1597 if( err != noErr ) 1598 return; 1599 1600 // all ATS fonts are scalable fonts 1601 pMetric->mbScalableFont = true; 1602 // TODO: check if any kerning is possible 1603 pMetric->mbKernableFont = true; 1604 1605 // convert into VCL font metrics (in unscaled pixel units) 1606 1607 Fixed ptSize; 1608 err = ATSUGetAttribute( maATSUStyle, kATSUSizeTag, sizeof(Fixed), &ptSize, 0); 1609 DBG_ASSERT( (err==noErr), "AquaSalGraphics::GetFontMetric() : could not get font size\n"); 1610 const double fPointSize = Fix2X( ptSize ); 1611 1612 // convert quartz units to pixel units 1613 // please see the comment in AquaSalGraphics::SetFont() for details 1614 const double fPixelSize = (mfFontScale * mfFakeDPIScale * fPointSize); 1615 pMetric->mnAscent = static_cast<long>(+aMetrics.ascent * fPixelSize + 0.5); 1616 pMetric->mnDescent = static_cast<long>(-aMetrics.descent * fPixelSize + 0.5); 1617 const long nExtDescent = static_cast<long>((-aMetrics.descent + aMetrics.leading) * fPixelSize + 0.5); 1618 pMetric->mnExtLeading = nExtDescent - pMetric->mnDescent; 1619 pMetric->mnIntLeading = 0; 1620 // ATSFontMetrics.avgAdvanceWidth is obsolete, so it is usually set to zero 1621 // since ImplFontMetricData::mnWidth is only used for stretching/squeezing fonts 1622 // setting this width to the pixel height of the fontsize is good enough 1623 // it also makes the calculation of the stretch factor simple 1624 pMetric->mnWidth = static_cast<long>(mfFontStretch * fPixelSize + 0.5); 1625 } 1626 1627 // ----------------------------------------------------------------------- 1628 1629 sal_uLong AquaSalGraphics::GetKernPairs( sal_uLong, ImplKernPairData* ) 1630 { 1631 return 0; 1632 } 1633 1634 // ----------------------------------------------------------------------- 1635 1636 static bool AddTempFontDir( const char* pDir ) 1637 { 1638 FSRef aPathFSRef; 1639 Boolean bIsDirectory = true; 1640 OSStatus eStatus = FSPathMakeRef( reinterpret_cast<const UInt8*>(pDir), &aPathFSRef, &bIsDirectory ); 1641 DBG_ASSERTWARNING( (eStatus==noErr) && bIsDirectory, "vcl AddTempFontDir() with invalid directory name!" ); 1642 if( eStatus != noErr ) 1643 return false; 1644 1645 // TODO: deactivate ATSFontContainerRef when closing app 1646 ATSFontContainerRef aATSFontContainer; 1647 1648 const ATSFontContext eContext = kATSFontContextLocal; // TODO: *Global??? 1649 #if defined(MAC_OS_X_VERSION_10_5) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) 1650 eStatus = ::ATSFontActivateFromFileReference( &aPathFSRef, 1651 eContext, kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, 1652 &aATSFontContainer ); 1653 #else 1654 FSSpec aPathFSSpec; 1655 eStatus = ::FSGetCatalogInfo( &aPathFSRef, kFSCatInfoNone, 1656 NULL, NULL, &aPathFSSpec, NULL ); 1657 if( eStatus != noErr ) 1658 return false; 1659 1660 eStatus = ::ATSFontActivateFromFileSpecification( &aPathFSSpec, 1661 eContext, kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, 1662 &aATSFontContainer ); 1663 #endif 1664 if( eStatus != noErr ) 1665 return false; 1666 1667 return true; 1668 } 1669 1670 static bool AddLocalTempFontDirs( void ) 1671 { 1672 static bool bFirst = true; 1673 if( !bFirst ) 1674 return false; 1675 bFirst = false; 1676 1677 // add private font files found in brand and base layer 1678 1679 rtl::OUString aBrandStr( RTL_CONSTASCII_USTRINGPARAM( "$BRAND_BASE_DIR" ) ); 1680 rtl_bootstrap_expandMacros( &aBrandStr.pData ); 1681 rtl::OUString aBrandSysPath; 1682 OSL_VERIFY( osl_getSystemPathFromFileURL( aBrandStr.pData, &aBrandSysPath.pData ) == osl_File_E_None ); 1683 1684 rtl::OStringBuffer aBrandFontDir( aBrandSysPath.getLength()*2 ); 1685 aBrandFontDir.append( rtl::OUStringToOString( aBrandSysPath, RTL_TEXTENCODING_UTF8 ) ); 1686 aBrandFontDir.append( "/share/fonts/truetype/" ); 1687 bool bBrandSuccess = AddTempFontDir( aBrandFontDir.getStr() ); 1688 1689 rtl::OUString aBaseStr( RTL_CONSTASCII_USTRINGPARAM( "$OOO_BASE_DIR" ) ); 1690 rtl_bootstrap_expandMacros( &aBaseStr.pData ); 1691 rtl::OUString aBaseSysPath; 1692 OSL_VERIFY( osl_getSystemPathFromFileURL( aBaseStr.pData, &aBaseSysPath.pData ) == osl_File_E_None ); 1693 1694 rtl::OStringBuffer aBaseFontDir( aBaseSysPath.getLength()*2 ); 1695 aBaseFontDir.append( rtl::OUStringToOString( aBaseSysPath, RTL_TEXTENCODING_UTF8 ) ); 1696 aBaseFontDir.append( "/share/fonts/truetype/" ); 1697 bool bBaseSuccess = AddTempFontDir( aBaseFontDir.getStr() ); 1698 1699 return bBrandSuccess && bBaseSuccess; 1700 } 1701 1702 void AquaSalGraphics::GetDevFontList( ImplDevFontList* pFontList ) 1703 { 1704 DBG_ASSERT( pFontList, "AquaSalGraphics::GetDevFontList(NULL) !"); 1705 1706 AddLocalTempFontDirs(); 1707 1708 // The idea is to cache the list of system fonts once it has been generated. 1709 // SalData seems to be a good place for this caching. However we have to 1710 // carefully make the access to the font list thread-safe. If we register 1711 // a font-change event handler to update the font list in case fonts have 1712 // changed on the system we have to lock access to the list. The right 1713 // way to do that is the solar mutex since GetDevFontList is protected 1714 // through it as should be all event handlers 1715 1716 SalData* pSalData = GetSalData(); 1717 if (pSalData->mpFontList == NULL) 1718 pSalData->mpFontList = new SystemFontList(); 1719 1720 // Copy all ImplFontData objects contained in the SystemFontList 1721 pSalData->mpFontList->AnnounceFonts( *pFontList ); 1722 } 1723 1724 // ----------------------------------------------------------------------- 1725 1726 bool AquaSalGraphics::AddTempDevFont( ImplDevFontList*, 1727 const String& rFontFileURL, const String& /*rFontName*/ ) 1728 { 1729 ::rtl::OUString aUSytemPath; 1730 OSL_VERIFY( !osl::FileBase::getSystemPathFromFileURL( rFontFileURL, aUSytemPath ) ); 1731 1732 FSRef aNewRef; 1733 Boolean bIsDirectory = true; 1734 ::rtl::OString aCFileName = rtl::OUStringToOString( aUSytemPath, RTL_TEXTENCODING_UTF8 ); 1735 OSStatus eStatus = FSPathMakeRef( (UInt8*)aCFileName.getStr(), &aNewRef, &bIsDirectory ); 1736 DBG_ASSERT( (eStatus==noErr) && !bIsDirectory, "vcl AddTempDevFont() with invalid fontfile name!" ); 1737 if( eStatus != noErr ) 1738 return false; 1739 1740 ATSFontContainerRef oContainer; 1741 1742 const ATSFontContext eContext = kATSFontContextLocal; // TODO: *Global??? 1743 #if defined(MAC_OS_X_VERSION_10_5) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) 1744 eStatus = ::ATSFontActivateFromFileReference( &aNewRef, 1745 eContext, kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, 1746 &oContainer ); 1747 #else 1748 FSSpec aFontFSSpec; 1749 eStatus = ::FSGetCatalogInfo( &aNewRef, kFSCatInfoNone, 1750 NULL, NULL, &aFontFSSpec, NULL ); 1751 if( eStatus != noErr ) 1752 return false; 1753 1754 eStatus = ::ATSFontActivateFromFileSpecification( &aFontFSSpec, 1755 eContext, kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, 1756 &oContainer ); 1757 #endif 1758 if( eStatus != noErr ) 1759 return false; 1760 1761 // TODO: ATSFontDeactivate( oContainer ) when fonts are no longer needed 1762 // TODO: register new ImplMacFontdata in pFontList 1763 return true; 1764 } 1765 1766 // ----------------------------------------------------------------------- 1767 1768 // callbacks from ATSUGlyphGetCubicPaths() fore GetGlyphOutline() 1769 struct GgoData { basegfx::B2DPolygon maPolygon; basegfx::B2DPolyPolygon* mpPolyPoly; }; 1770 1771 static OSStatus GgoLineToProc( const Float32Point* pPoint, void* pData ) 1772 { 1773 basegfx::B2DPolygon& rPolygon = static_cast<GgoData*>(pData)->maPolygon; 1774 const basegfx::B2DPoint aB2DPoint( pPoint->x, pPoint->y ); 1775 rPolygon.append( aB2DPoint ); 1776 return noErr; 1777 } 1778 1779 static OSStatus GgoCurveToProc( const Float32Point* pCP1, const Float32Point* pCP2, 1780 const Float32Point* pPoint, void* pData ) 1781 { 1782 basegfx::B2DPolygon& rPolygon = static_cast<GgoData*>(pData)->maPolygon; 1783 const sal_uInt32 nPointCount = rPolygon.count(); 1784 const basegfx::B2DPoint aB2DControlPoint1( pCP1->x, pCP1->y ); 1785 rPolygon.setNextControlPoint( nPointCount-1, aB2DControlPoint1 ); 1786 const basegfx::B2DPoint aB2DEndPoint( pPoint->x, pPoint->y ); 1787 rPolygon.append( aB2DEndPoint ); 1788 const basegfx::B2DPoint aB2DControlPoint2( pCP2->x, pCP2->y ); 1789 rPolygon.setPrevControlPoint( nPointCount, aB2DControlPoint2 ); 1790 return noErr; 1791 } 1792 1793 static OSStatus GgoClosePathProc( void* pData ) 1794 { 1795 GgoData* pGgoData = static_cast<GgoData*>(pData); 1796 basegfx::B2DPolygon& rPolygon = pGgoData->maPolygon; 1797 if( rPolygon.count() > 0 ) 1798 pGgoData->mpPolyPoly->append( rPolygon ); 1799 rPolygon.clear(); 1800 return noErr; 1801 } 1802 1803 static OSStatus GgoMoveToProc( const Float32Point* pPoint, void* pData ) 1804 { 1805 GgoClosePathProc( pData ); 1806 OSStatus eStatus = GgoLineToProc( pPoint, pData ); 1807 return eStatus; 1808 } 1809 1810 sal_Bool AquaSalGraphics::GetGlyphOutline( long nGlyphId, basegfx::B2DPolyPolygon& rPolyPoly ) 1811 { 1812 GgoData aGgoData; 1813 aGgoData.mpPolyPoly = &rPolyPoly; 1814 rPolyPoly.clear(); 1815 1816 ATSUStyle rATSUStyle = maATSUStyle; // TODO: handle glyph fallback when CWS pdffix02 is integrated 1817 OSStatus eGgoStatus = noErr; 1818 OSStatus eStatus = ATSUGlyphGetCubicPaths( rATSUStyle, nGlyphId, 1819 GgoMoveToProc, GgoLineToProc, GgoCurveToProc, GgoClosePathProc, 1820 &aGgoData, &eGgoStatus ); 1821 if( (eStatus != noErr) ) // TODO: why is (eGgoStatus!=noErr) when curves are involved? 1822 return false; 1823 1824 GgoClosePathProc( &aGgoData ); 1825 if( mfFontScale != 1.0 ) { 1826 rPolyPoly.transform(basegfx::tools::createScaleB2DHomMatrix(+mfFontScale, +mfFontScale)); 1827 } 1828 return true; 1829 } 1830 1831 // ----------------------------------------------------------------------- 1832 1833 long AquaSalGraphics::GetGraphicsWidth() const 1834 { 1835 long w = 0; 1836 if( mrContext && (mbWindow || mbVirDev) ) 1837 { 1838 w = mnWidth; 1839 } 1840 1841 if( w == 0 ) 1842 { 1843 if( mbWindow && mpFrame ) 1844 w = mpFrame->maGeometry.nWidth; 1845 } 1846 1847 return w; 1848 } 1849 1850 // ----------------------------------------------------------------------- 1851 1852 sal_Bool AquaSalGraphics::GetGlyphBoundRect( long nGlyphId, Rectangle& rRect ) 1853 { 1854 ATSUStyle rATSUStyle = maATSUStyle; // TODO: handle glyph fallback 1855 GlyphID aGlyphId = nGlyphId; 1856 ATSGlyphScreenMetrics aGlyphMetrics; 1857 OSStatus eStatus = ATSUGlyphGetScreenMetrics( rATSUStyle, 1858 1, &aGlyphId, 0, FALSE, !mbNonAntialiasedText, &aGlyphMetrics ); 1859 if( eStatus != noErr ) 1860 return false; 1861 1862 const long nMinX = (long)(+aGlyphMetrics.topLeft.x * mfFontScale - 0.5); 1863 const long nMaxX = (long)(aGlyphMetrics.width * mfFontScale + 0.5) + nMinX; 1864 const long nMinY = (long)(-aGlyphMetrics.topLeft.y * mfFontScale - 0.5); 1865 const long nMaxY = (long)(aGlyphMetrics.height * mfFontScale + 0.5) + nMinY; 1866 rRect = Rectangle( nMinX, nMinY, nMaxX, nMaxY ); 1867 return true; 1868 } 1869 1870 // ----------------------------------------------------------------------- 1871 1872 void AquaSalGraphics::GetDevFontSubstList( OutputDevice* ) 1873 { 1874 // nothing to do since there are no device-specific fonts on Aqua 1875 } 1876 1877 // ----------------------------------------------------------------------- 1878 1879 void AquaSalGraphics::DrawServerFontLayout( const ServerFontLayout& ) 1880 { 1881 } 1882 1883 // ----------------------------------------------------------------------- 1884 1885 sal_uInt16 AquaSalGraphics::SetFont( ImplFontSelectData* pReqFont, int /*nFallbackLevel*/ ) 1886 { 1887 if( !pReqFont ) 1888 { 1889 ATSUClearStyle( maATSUStyle ); 1890 mpMacFontData = NULL; 1891 return 0; 1892 } 1893 1894 // store the requested device font entry 1895 const ImplMacFontData* pMacFont = static_cast<const ImplMacFontData*>( pReqFont->mpFontData ); 1896 mpMacFontData = pMacFont; 1897 1898 // convert pixel units (as seen by upper layers) to typographic point units 1899 double fScaledAtsHeight = pReqFont->mfExactHeight; 1900 // avoid Fixed16.16 overflows by limiting the ATS font size 1901 static const float fMaxAtsHeight = 144.0; 1902 if( fScaledAtsHeight <= fMaxAtsHeight ) 1903 mfFontScale = 1.0; 1904 else 1905 { 1906 mfFontScale = fScaledAtsHeight / fMaxAtsHeight; 1907 fScaledAtsHeight = fMaxAtsHeight; 1908 } 1909 Fixed fFixedSize = FloatToFixed( fScaledAtsHeight ); 1910 // enable bold-emulation if needed 1911 Boolean bFakeBold = FALSE; 1912 if( (pReqFont->GetWeight() >= WEIGHT_BOLD) 1913 && (pMacFont->GetWeight() < WEIGHT_SEMIBOLD) ) 1914 bFakeBold = TRUE; 1915 // enable italic-emulation if needed 1916 Boolean bFakeItalic = FALSE; 1917 if( ((pReqFont->GetSlant() == ITALIC_NORMAL) || (pReqFont->GetSlant() == ITALIC_OBLIQUE)) 1918 && !((pMacFont->GetSlant() == ITALIC_NORMAL) || (pMacFont->GetSlant() == ITALIC_OBLIQUE)) ) 1919 bFakeItalic = TRUE; 1920 1921 // enable/disable antialiased text 1922 mbNonAntialiasedText = pReqFont->mbNonAntialiased; 1923 UInt32 nStyleRenderingOptions = kATSStyleNoOptions; 1924 if( pReqFont->mbNonAntialiased ) 1925 nStyleRenderingOptions |= kATSStyleNoAntiAliasing; 1926 1927 // set horizontal/vertical mode 1928 ATSUVerticalCharacterType aVerticalCharacterType = kATSUStronglyHorizontal; 1929 if( pReqFont->mbVertical ) 1930 aVerticalCharacterType = kATSUStronglyVertical; 1931 1932 // prepare ATS-fontid as type matching to the kATSUFontTag request 1933 ATSUFontID nFontID = static_cast<ATSUFontID>(pMacFont->GetFontId()); 1934 1935 // update ATSU style attributes with requested font parameters 1936 // TODO: no need to set styles which are already defaulted 1937 1938 const ATSUAttributeTag aTag[] = 1939 { 1940 kATSUFontTag, 1941 kATSUSizeTag, 1942 kATSUQDBoldfaceTag, 1943 kATSUQDItalicTag, 1944 kATSUStyleRenderingOptionsTag, 1945 kATSUVerticalCharacterTag 1946 }; 1947 1948 const ByteCount aValueSize[] = 1949 { 1950 sizeof(ATSUFontID), 1951 sizeof(fFixedSize), 1952 sizeof(bFakeBold), 1953 sizeof(bFakeItalic), 1954 sizeof(nStyleRenderingOptions), 1955 sizeof(aVerticalCharacterType) 1956 }; 1957 1958 const ATSUAttributeValuePtr aValue[] = 1959 { 1960 &nFontID, 1961 &fFixedSize, 1962 &bFakeBold, 1963 &bFakeItalic, 1964 &nStyleRenderingOptions, 1965 &aVerticalCharacterType 1966 }; 1967 1968 static const int nTagCount = sizeof(aTag) / sizeof(*aTag); 1969 OSStatus eStatus = ATSUSetAttributes( maATSUStyle, nTagCount, 1970 aTag, aValueSize, aValue ); 1971 // reset ATSUstyle if there was an error 1972 if( eStatus != noErr ) 1973 { 1974 DBG_WARNING( "AquaSalGraphics::SetFont() : Could not set font attributes!\n"); 1975 ATSUClearStyle( maATSUStyle ); 1976 mpMacFontData = NULL; 1977 return 0; 1978 } 1979 1980 // prepare font stretching 1981 const ATSUAttributeTag aMatrixTag = kATSUFontMatrixTag; 1982 if( (pReqFont->mnWidth == 0) || (pReqFont->mnWidth == pReqFont->mnHeight) ) 1983 { 1984 mfFontStretch = 1.0; 1985 ATSUClearAttributes( maATSUStyle, 1, &aMatrixTag ); 1986 } 1987 else 1988 { 1989 mfFontStretch = (float)pReqFont->mnWidth / pReqFont->mnHeight; 1990 CGAffineTransform aMatrix = CGAffineTransformMakeScale( mfFontStretch, 1.0F ); 1991 const ATSUAttributeValuePtr aAttr = &aMatrix; 1992 const ByteCount aMatrixBytes = sizeof(aMatrix); 1993 eStatus = ATSUSetAttributes( maATSUStyle, 1, &aMatrixTag, &aMatrixBytes, &aAttr ); 1994 DBG_ASSERT( (eStatus==noErr), "AquaSalGraphics::SetFont() : Could not set font matrix\n"); 1995 } 1996 1997 // prepare font rotation 1998 mnATSUIRotation = FloatToFixed( pReqFont->mnOrientation / 10.0 ); 1999 2000 #if OSL_DEBUG_LEVEL > 3 2001 fprintf( stderr, "SetFont to (\"%s\", \"%s\", fontid=%d) for (\"%s\" \"%s\" weight=%d, slant=%d size=%dx%d orientation=%d)\n", 2002 ::rtl::OUStringToOString( pMacFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ).getStr(), 2003 ::rtl::OUStringToOString( pMacFont->GetStyleName(), RTL_TEXTENCODING_UTF8 ).getStr(), 2004 (int)nFontID, 2005 ::rtl::OUStringToOString( pReqFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ).getStr(), 2006 ::rtl::OUStringToOString( pReqFont->GetStyleName(), RTL_TEXTENCODING_UTF8 ).getStr(), 2007 pReqFont->GetWeight(), 2008 pReqFont->GetSlant(), 2009 pReqFont->mnHeight, 2010 pReqFont->mnWidth, 2011 pReqFont->mnOrientation); 2012 #endif 2013 2014 return 0; 2015 } 2016 2017 // ----------------------------------------------------------------------- 2018 2019 const ImplFontCharMap* AquaSalGraphics::GetImplFontCharMap() const 2020 { 2021 if( !mpMacFontData ) 2022 return ImplFontCharMap::GetDefaultMap(); 2023 2024 return mpMacFontData->GetImplFontCharMap(); 2025 } 2026 2027 // ----------------------------------------------------------------------- 2028 2029 // fake a SFNT font directory entry for a font table 2030 // see http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6.html#Directory 2031 static void FakeDirEntry( FourCharCode eFCC, ByteCount nOfs, ByteCount nLen, 2032 const unsigned char* /*pData*/, unsigned char*& rpDest ) 2033 { 2034 // write entry tag 2035 rpDest[ 0] = (char)(eFCC >> 24); 2036 rpDest[ 1] = (char)(eFCC >> 16); 2037 rpDest[ 2] = (char)(eFCC >> 8); 2038 rpDest[ 3] = (char)(eFCC >> 0); 2039 // TODO: get entry checksum and write it 2040 // not too important since the subsetter doesn't care currently 2041 // for( pData+nOfs ... pData+nOfs+nLen ) 2042 // write entry offset 2043 rpDest[ 8] = (char)(nOfs >> 24); 2044 rpDest[ 9] = (char)(nOfs >> 16); 2045 rpDest[10] = (char)(nOfs >> 8); 2046 rpDest[11] = (char)(nOfs >> 0); 2047 // write entry length 2048 rpDest[12] = (char)(nLen >> 24); 2049 rpDest[13] = (char)(nLen >> 16); 2050 rpDest[14] = (char)(nLen >> 8); 2051 rpDest[15] = (char)(nLen >> 0); 2052 // advance to next entry 2053 rpDest += 16; 2054 } 2055 2056 static bool GetRawFontData( const ImplFontData* pFontData, 2057 ByteVector& rBuffer, bool* pJustCFF ) 2058 { 2059 const ImplMacFontData* pMacFont = static_cast<const ImplMacFontData*>(pFontData); 2060 const ATSUFontID nFontId = static_cast<ATSUFontID>(pMacFont->GetFontId()); 2061 ATSFontRef rFont = FMGetATSFontRefFromFont( nFontId ); 2062 2063 ByteCount nCffLen = 0; 2064 OSStatus eStatus = ATSFontGetTable( rFont, GetTag("CFF "), 0, 0, NULL, &nCffLen); 2065 if( pJustCFF != NULL ) 2066 { 2067 *pJustCFF = (eStatus == noErr) && (nCffLen > 0); 2068 if( *pJustCFF ) 2069 { 2070 rBuffer.resize( nCffLen ); 2071 eStatus = ATSFontGetTable( rFont, GetTag("CFF "), 0, nCffLen, (void*)&rBuffer[0], &nCffLen); 2072 if( (eStatus != noErr) || (nCffLen <= 0) ) 2073 return false; 2074 return true; 2075 } 2076 } 2077 2078 // get font table availability and size in bytes 2079 ByteCount nHeadLen = 0; 2080 eStatus = ATSFontGetTable( rFont, GetTag("head"), 0, 0, NULL, &nHeadLen); 2081 if( (eStatus != noErr) || (nHeadLen <= 0) ) 2082 return false; 2083 ByteCount nMaxpLen = 0; 2084 eStatus = ATSFontGetTable( rFont, GetTag("maxp"), 0, 0, NULL, &nMaxpLen); 2085 if( (eStatus != noErr) || (nMaxpLen <= 0) ) 2086 return false; 2087 ByteCount nCmapLen = 0; 2088 eStatus = ATSFontGetTable( rFont, GetTag("cmap"), 0, 0, NULL, &nCmapLen); 2089 if( (eStatus != noErr) || (nCmapLen <= 0) ) 2090 return false; 2091 ByteCount nNameLen = 0; 2092 eStatus = ATSFontGetTable( rFont, GetTag("name"), 0, 0, NULL, &nNameLen); 2093 if( (eStatus != noErr) || (nNameLen <= 0) ) 2094 return false; 2095 ByteCount nHheaLen = 0; 2096 eStatus = ATSFontGetTable( rFont, GetTag("hhea"), 0, 0, NULL, &nHheaLen); 2097 if( (eStatus != noErr) || (nHheaLen <= 0) ) 2098 return false; 2099 ByteCount nHmtxLen = 0; 2100 eStatus = ATSFontGetTable( rFont, GetTag("hmtx"), 0, 0, NULL, &nHmtxLen); 2101 if( (eStatus != noErr) || (nHmtxLen <= 0) ) 2102 return false; 2103 2104 // get the glyph outline tables 2105 ByteCount nLocaLen = 0; 2106 ByteCount nGlyfLen = 0; 2107 if( (eStatus != noErr) || (nCffLen <= 0) ) 2108 { 2109 eStatus = ATSFontGetTable( rFont, GetTag("loca"), 0, 0, NULL, &nLocaLen); 2110 if( (eStatus != noErr) || (nLocaLen <= 0) ) 2111 return false; 2112 eStatus = ATSFontGetTable( rFont, GetTag("glyf"), 0, 0, NULL, &nGlyfLen); 2113 if( (eStatus != noErr) || (nGlyfLen <= 0) ) 2114 return false; 2115 } 2116 2117 ByteCount nPrepLen=0, nCvtLen=0, nFpgmLen=0; 2118 if( nGlyfLen ) // TODO: reduce PDF size by making hint subsetting optional 2119 { 2120 eStatus = ATSFontGetTable( rFont, GetTag("prep"), 0, 0, NULL, &nPrepLen); 2121 eStatus = ATSFontGetTable( rFont, GetTag("cvt "), 0, 0, NULL, &nCvtLen); 2122 eStatus = ATSFontGetTable( rFont, GetTag("fpgm"), 0, 0, NULL, &nFpgmLen); 2123 } 2124 2125 // prepare a byte buffer for a fake font 2126 int nTableCount = 7; 2127 nTableCount += (nPrepLen>0) + (nCvtLen>0) + (nFpgmLen>0) + (nGlyfLen>0); 2128 const ByteCount nFdirLen = 12 + 16*nTableCount; 2129 ByteCount nTotalLen = nFdirLen; 2130 nTotalLen += nHeadLen + nMaxpLen + nNameLen + nCmapLen; 2131 if( nGlyfLen ) 2132 nTotalLen += nLocaLen + nGlyfLen; 2133 else 2134 nTotalLen += nCffLen; 2135 nTotalLen += nHheaLen + nHmtxLen; 2136 nTotalLen += nPrepLen + nCvtLen + nFpgmLen; 2137 rBuffer.resize( nTotalLen ); 2138 2139 // fake a SFNT font directory header 2140 if( nTableCount < 16 ) 2141 { 2142 int nLog2 = 0; 2143 while( (nTableCount >> nLog2) > 1 ) ++nLog2; 2144 rBuffer[ 1] = 1; // Win-TTF style scaler 2145 rBuffer[ 5] = nTableCount; // table count 2146 rBuffer[ 7] = nLog2*16; // searchRange 2147 rBuffer[ 9] = nLog2; // entrySelector 2148 rBuffer[11] = (nTableCount-nLog2)*16; // rangeShift 2149 } 2150 2151 // get font table raw data and update the fake directory entries 2152 ByteCount nOfs = nFdirLen; 2153 unsigned char* pFakeEntry = &rBuffer[12]; 2154 eStatus = ATSFontGetTable( rFont, GetTag("cmap"), 0, nCmapLen, (void*)&rBuffer[nOfs], &nCmapLen); 2155 FakeDirEntry( GetTag("cmap"), nOfs, nCmapLen, &rBuffer[0], pFakeEntry ); 2156 nOfs += nCmapLen; 2157 if( nCvtLen ) { 2158 eStatus = ATSFontGetTable( rFont, GetTag("cvt "), 0, nCvtLen, (void*)&rBuffer[nOfs], &nCvtLen); 2159 FakeDirEntry( GetTag("cvt "), nOfs, nCvtLen, &rBuffer[0], pFakeEntry ); 2160 nOfs += nCvtLen; 2161 } 2162 if( nFpgmLen ) { 2163 eStatus = ATSFontGetTable( rFont, GetTag("fpgm"), 0, nFpgmLen, (void*)&rBuffer[nOfs], &nFpgmLen); 2164 FakeDirEntry( GetTag("fpgm"), nOfs, nFpgmLen, &rBuffer[0], pFakeEntry ); 2165 nOfs += nFpgmLen; 2166 } 2167 if( nCffLen ) { 2168 eStatus = ATSFontGetTable( rFont, GetTag("CFF "), 0, nCffLen, (void*)&rBuffer[nOfs], &nCffLen); 2169 FakeDirEntry( GetTag("CFF "), nOfs, nCffLen, &rBuffer[0], pFakeEntry ); 2170 nOfs += nGlyfLen; 2171 } else { 2172 eStatus = ATSFontGetTable( rFont, GetTag("glyf"), 0, nGlyfLen, (void*)&rBuffer[nOfs], &nGlyfLen); 2173 FakeDirEntry( GetTag("glyf"), nOfs, nGlyfLen, &rBuffer[0], pFakeEntry ); 2174 nOfs += nGlyfLen; 2175 eStatus = ATSFontGetTable( rFont, GetTag("loca"), 0, nLocaLen, (void*)&rBuffer[nOfs], &nLocaLen); 2176 FakeDirEntry( GetTag("loca"), nOfs, nLocaLen, &rBuffer[0], pFakeEntry ); 2177 nOfs += nLocaLen; 2178 } 2179 eStatus = ATSFontGetTable( rFont, GetTag("head"), 0, nHeadLen, (void*)&rBuffer[nOfs], &nHeadLen); 2180 FakeDirEntry( GetTag("head"), nOfs, nHeadLen, &rBuffer[0], pFakeEntry ); 2181 nOfs += nHeadLen; 2182 eStatus = ATSFontGetTable( rFont, GetTag("hhea"), 0, nHheaLen, (void*)&rBuffer[nOfs], &nHheaLen); 2183 FakeDirEntry( GetTag("hhea"), nOfs, nHheaLen, &rBuffer[0], pFakeEntry ); 2184 nOfs += nHheaLen; 2185 eStatus = ATSFontGetTable( rFont, GetTag("hmtx"), 0, nHmtxLen, (void*)&rBuffer[nOfs], &nHmtxLen); 2186 FakeDirEntry( GetTag("hmtx"), nOfs, nHmtxLen, &rBuffer[0], pFakeEntry ); 2187 nOfs += nHmtxLen; 2188 eStatus = ATSFontGetTable( rFont, GetTag("maxp"), 0, nMaxpLen, (void*)&rBuffer[nOfs], &nMaxpLen); 2189 FakeDirEntry( GetTag("maxp"), nOfs, nMaxpLen, &rBuffer[0], pFakeEntry ); 2190 nOfs += nMaxpLen; 2191 eStatus = ATSFontGetTable( rFont, GetTag("name"), 0, nNameLen, (void*)&rBuffer[nOfs], &nNameLen); 2192 FakeDirEntry( GetTag("name"), nOfs, nNameLen, &rBuffer[0], pFakeEntry ); 2193 nOfs += nNameLen; 2194 if( nPrepLen ) { 2195 eStatus = ATSFontGetTable( rFont, GetTag("prep"), 0, nPrepLen, (void*)&rBuffer[nOfs], &nPrepLen); 2196 FakeDirEntry( GetTag("prep"), nOfs, nPrepLen, &rBuffer[0], pFakeEntry ); 2197 nOfs += nPrepLen; 2198 } 2199 2200 DBG_ASSERT( (nOfs==nTotalLen), "AquaSalGraphics::CreateFontSubset (nOfs!=nTotalLen)"); 2201 2202 return sal_True; 2203 } 2204 2205 sal_Bool AquaSalGraphics::CreateFontSubset( const rtl::OUString& rToFile, 2206 const ImplFontData* pFontData, long* pGlyphIDs, sal_uInt8* pEncoding, 2207 sal_Int32* pGlyphWidths, int nGlyphCount, FontSubsetInfo& rInfo ) 2208 { 2209 // TODO: move more of the functionality here into the generic subsetter code 2210 2211 // prepare the requested file name for writing the font-subset file 2212 rtl::OUString aSysPath; 2213 if( osl_File_E_None != osl_getSystemPathFromFileURL( rToFile.pData, &aSysPath.pData ) ) 2214 return sal_False; 2215 const rtl_TextEncoding aThreadEncoding = osl_getThreadTextEncoding(); 2216 const ByteString aToFile( rtl::OUStringToOString( aSysPath, aThreadEncoding ) ); 2217 2218 // get the raw-bytes from the font to be subset 2219 ByteVector aBuffer; 2220 bool bCffOnly = false; 2221 if( !GetRawFontData( pFontData, aBuffer, &bCffOnly ) ) 2222 return sal_False; 2223 2224 // handle CFF-subsetting 2225 if( bCffOnly ) 2226 { 2227 // provide the raw-CFF data to the subsetter 2228 ByteCount nCffLen = aBuffer.size(); 2229 rInfo.LoadFont( FontSubsetInfo::CFF_FONT, &aBuffer[0], nCffLen ); 2230 2231 // NOTE: assuming that all glyphids requested on Aqua are fully translated 2232 2233 // make the subsetter provide the requested subset 2234 FILE* pOutFile = fopen( aToFile.GetBuffer(), "wb" ); 2235 bool bRC = rInfo.CreateFontSubset( FontSubsetInfo::TYPE1_PFB, pOutFile, NULL, 2236 pGlyphIDs, pEncoding, nGlyphCount, pGlyphWidths ); 2237 fclose( pOutFile ); 2238 return bRC; 2239 } 2240 2241 // TODO: modernize psprint's horrible fontsubset C-API 2242 // this probably only makes sense after the switch to another SCM 2243 // that can preserve change history after file renames 2244 2245 // prepare data for psprint's font subsetter 2246 TrueTypeFont* pSftFont = NULL; 2247 int nRC = ::OpenTTFontBuffer( (void*)&aBuffer[0], aBuffer.size(), 0, &pSftFont); 2248 if( nRC != SF_OK ) 2249 return sal_False; 2250 2251 // get details about the subsetted font 2252 TTGlobalFontInfo aTTInfo; 2253 ::GetTTGlobalFontInfo( pSftFont, &aTTInfo ); 2254 rInfo.m_nFontType = FontSubsetInfo::SFNT_TTF; 2255 rInfo.m_aPSName = String( aTTInfo.psname, RTL_TEXTENCODING_UTF8 ); 2256 rInfo.m_aFontBBox = Rectangle( Point( aTTInfo.xMin, aTTInfo.yMin ), 2257 Point( aTTInfo.xMax, aTTInfo.yMax ) ); 2258 rInfo.m_nCapHeight = aTTInfo.yMax; // Well ... 2259 rInfo.m_nAscent = aTTInfo.winAscent; 2260 rInfo.m_nDescent = aTTInfo.winDescent; 2261 // mac fonts usually do not have an OS2-table 2262 // => get valid ascent/descent values from other tables 2263 if( !rInfo.m_nAscent ) 2264 rInfo.m_nAscent = +aTTInfo.typoAscender; 2265 if( !rInfo.m_nAscent ) 2266 rInfo.m_nAscent = +aTTInfo.ascender; 2267 if( !rInfo.m_nDescent ) 2268 rInfo.m_nDescent = +aTTInfo.typoDescender; 2269 if( !rInfo.m_nDescent ) 2270 rInfo.m_nDescent = -aTTInfo.descender; 2271 2272 // subset glyphs and get their properties 2273 // take care that subset fonts require the NotDef glyph in pos 0 2274 int nOrigCount = nGlyphCount; 2275 sal_uInt16 aShortIDs[ 256 ]; 2276 sal_uInt8 aTempEncs[ 256 ]; 2277 2278 int nNotDef = -1; 2279 for( int i = 0; i < nGlyphCount; ++i ) 2280 { 2281 aTempEncs[i] = pEncoding[i]; 2282 sal_uInt32 nGlyphIdx = pGlyphIDs[i] & GF_IDXMASK; 2283 if( pGlyphIDs[i] & GF_ISCHAR ) 2284 { 2285 bool bVertical = (pGlyphIDs[i] & GF_ROTMASK) != 0; 2286 nGlyphIdx = ::MapChar( pSftFont, static_cast<sal_uInt16>(nGlyphIdx), bVertical ); 2287 if( nGlyphIdx == 0 && pFontData->IsSymbolFont() ) 2288 { 2289 // #i12824# emulate symbol aliasing U+FXXX <-> U+0XXX 2290 nGlyphIdx = pGlyphIDs[i] & GF_IDXMASK; 2291 nGlyphIdx = (nGlyphIdx & 0xF000) ? (nGlyphIdx & 0x00FF) : (nGlyphIdx | 0xF000 ); 2292 nGlyphIdx = ::MapChar( pSftFont, static_cast<sal_uInt16>(nGlyphIdx), bVertical ); 2293 } 2294 } 2295 aShortIDs[i] = static_cast<sal_uInt16>( nGlyphIdx ); 2296 if( !nGlyphIdx ) 2297 if( nNotDef < 0 ) 2298 nNotDef = i; // first NotDef glyph found 2299 } 2300 2301 if( nNotDef != 0 ) 2302 { 2303 // add fake NotDef glyph if needed 2304 if( nNotDef < 0 ) 2305 nNotDef = nGlyphCount++; 2306 2307 // NotDef glyph must be in pos 0 => swap glyphids 2308 aShortIDs[ nNotDef ] = aShortIDs[0]; 2309 aTempEncs[ nNotDef ] = aTempEncs[0]; 2310 aShortIDs[0] = 0; 2311 aTempEncs[0] = 0; 2312 } 2313 DBG_ASSERT( nGlyphCount < 257, "too many glyphs for subsetting" ); 2314 2315 // TODO: where to get bVertical? 2316 const bool bVertical = false; 2317 2318 // fill the pGlyphWidths array 2319 // while making sure that the NotDef glyph is at index==0 2320 TTSimpleGlyphMetrics* pGlyphMetrics = 2321 ::GetTTSimpleGlyphMetrics( pSftFont, aShortIDs, nGlyphCount, bVertical ); 2322 if( !pGlyphMetrics ) 2323 return sal_False; 2324 sal_uInt16 nNotDefAdv = pGlyphMetrics[0].adv; 2325 pGlyphMetrics[0].adv = pGlyphMetrics[nNotDef].adv; 2326 pGlyphMetrics[nNotDef].adv = nNotDefAdv; 2327 for( int i = 0; i < nOrigCount; ++i ) 2328 pGlyphWidths[i] = pGlyphMetrics[i].adv; 2329 free( pGlyphMetrics ); 2330 2331 // write subset into destination file 2332 nRC = ::CreateTTFromTTGlyphs( pSftFont, aToFile.GetBuffer(), aShortIDs, 2333 aTempEncs, nGlyphCount, 0, NULL, 0 ); 2334 ::CloseTTFont(pSftFont); 2335 return (nRC == SF_OK); 2336 } 2337 2338 // ----------------------------------------------------------------------- 2339 2340 void AquaSalGraphics::GetGlyphWidths( const ImplFontData* pFontData, bool bVertical, 2341 Int32Vector& rGlyphWidths, Ucs2UIntMap& rUnicodeEnc ) 2342 { 2343 rGlyphWidths.clear(); 2344 rUnicodeEnc.clear(); 2345 2346 if( pFontData->IsSubsettable() ) 2347 { 2348 ByteVector aBuffer; 2349 if( !GetRawFontData( pFontData, aBuffer, NULL ) ) 2350 return; 2351 2352 // TODO: modernize psprint's horrible fontsubset C-API 2353 // this probably only makes sense after the switch to another SCM 2354 // that can preserve change history after file renames 2355 2356 // use the font subsetter to get the widths 2357 TrueTypeFont* pSftFont = NULL; 2358 int nRC = ::OpenTTFontBuffer( (void*)&aBuffer[0], aBuffer.size(), 0, &pSftFont); 2359 if( nRC != SF_OK ) 2360 return; 2361 2362 const int nGlyphCount = ::GetTTGlyphCount( pSftFont ); 2363 if( nGlyphCount > 0 ) 2364 { 2365 // get glyph metrics 2366 rGlyphWidths.resize(nGlyphCount); 2367 std::vector<sal_uInt16> aGlyphIds(nGlyphCount); 2368 for( int i = 0; i < nGlyphCount; i++ ) 2369 aGlyphIds[i] = static_cast<sal_uInt16>(i); 2370 const TTSimpleGlyphMetrics* pGlyphMetrics = ::GetTTSimpleGlyphMetrics( 2371 pSftFont, &aGlyphIds[0], nGlyphCount, bVertical ); 2372 if( pGlyphMetrics ) 2373 { 2374 for( int i = 0; i < nGlyphCount; ++i ) 2375 rGlyphWidths[i] = pGlyphMetrics[i].adv; 2376 free( (void*)pGlyphMetrics ); 2377 } 2378 2379 const ImplFontCharMap* pMap = mpMacFontData->GetImplFontCharMap(); 2380 DBG_ASSERT( pMap && pMap->GetCharCount(), "no charmap" ); 2381 pMap->AddReference(); // TODO: add and use RAII object instead 2382 2383 // get unicode<->glyph encoding 2384 // TODO? avoid sft mapping by using the pMap itself 2385 int nCharCount = pMap->GetCharCount(); 2386 sal_uInt32 nChar = pMap->GetFirstChar(); 2387 for(; --nCharCount >= 0; nChar = pMap->GetNextChar( nChar ) ) 2388 { 2389 if( nChar > 0xFFFF ) // TODO: allow UTF-32 chars 2390 break; 2391 sal_Ucs nUcsChar = static_cast<sal_Ucs>(nChar); 2392 sal_uInt32 nGlyph = ::MapChar( pSftFont, nUcsChar, bVertical ); 2393 if( nGlyph > 0 ) 2394 rUnicodeEnc[ nUcsChar ] = nGlyph; 2395 } 2396 2397 pMap->DeReference(); // TODO: add and use RAII object instead 2398 } 2399 2400 ::CloseTTFont( pSftFont ); 2401 } 2402 else if( pFontData->IsEmbeddable() ) 2403 { 2404 // get individual character widths 2405 #if 0 // FIXME 2406 rWidths.reserve( 224 ); 2407 for( sal_Unicode i = 32; i < 256; ++i ) 2408 { 2409 int nCharWidth = 0; 2410 if( ::GetCharWidth32W( mhDC, i, i, &nCharWidth ) ) 2411 { 2412 rUnicodeEnc[ i ] = rWidths.size(); 2413 rWidths.push_back( nCharWidth ); 2414 } 2415 } 2416 #else 2417 DBG_ERROR("not implemented for non-subsettable fonts!\n"); 2418 #endif 2419 } 2420 } 2421 2422 // ----------------------------------------------------------------------- 2423 2424 const Ucs2SIntMap* AquaSalGraphics::GetFontEncodingVector( 2425 const ImplFontData*, const Ucs2OStrMap** /*ppNonEncoded*/ ) 2426 { 2427 return NULL; 2428 } 2429 2430 // ----------------------------------------------------------------------- 2431 2432 const void* AquaSalGraphics::GetEmbedFontData( const ImplFontData*, 2433 const sal_Ucs* /*pUnicodes*/, 2434 sal_Int32* /*pWidths*/, 2435 FontSubsetInfo&, 2436 long* /*pDataLen*/ ) 2437 { 2438 return NULL; 2439 } 2440 2441 // ----------------------------------------------------------------------- 2442 2443 void AquaSalGraphics::FreeEmbedFontData( const void* pData, long /*nDataLen*/ ) 2444 { 2445 // TODO: implementing this only makes sense when the implementation of 2446 // AquaSalGraphics::GetEmbedFontData() returns non-NULL 2447 (void)pData; 2448 DBG_ASSERT( (pData!=NULL), "AquaSalGraphics::FreeEmbedFontData() is not implemented\n"); 2449 } 2450 2451 // ----------------------------------------------------------------------- 2452 2453 SystemFontData AquaSalGraphics::GetSysFontData( int /* nFallbacklevel */ ) const 2454 { 2455 SystemFontData aSysFontData; 2456 OSStatus err; 2457 aSysFontData.nSize = sizeof( SystemFontData ); 2458 2459 // NOTE: Native ATSU font fallbacks are used, not the VCL fallbacks. 2460 ATSUFontID fontId; 2461 err = ATSUGetAttribute( maATSUStyle, kATSUFontTag, sizeof(fontId), &fontId, 0 ); 2462 if (err) fontId = 0; 2463 aSysFontData.aATSUFontID = (void *) fontId; 2464 2465 Boolean bFbold; 2466 err = ATSUGetAttribute( maATSUStyle, kATSUQDBoldfaceTag, sizeof(bFbold), &bFbold, 0 ); 2467 if (err) bFbold = FALSE; 2468 aSysFontData.bFakeBold = (bool) bFbold; 2469 2470 Boolean bFItalic; 2471 err = ATSUGetAttribute( maATSUStyle, kATSUQDItalicTag, sizeof(bFItalic), &bFItalic, 0 ); 2472 if (err) bFItalic = FALSE; 2473 aSysFontData.bFakeItalic = (bool) bFItalic; 2474 2475 ATSUVerticalCharacterType aVerticalCharacterType; 2476 err = ATSUGetAttribute( maATSUStyle, kATSUVerticalCharacterTag, sizeof(aVerticalCharacterType), &aVerticalCharacterType, 0 ); 2477 if (!err && aVerticalCharacterType == kATSUStronglyVertical) { 2478 aSysFontData.bVerticalCharacterType = true; 2479 } else { 2480 aSysFontData.bVerticalCharacterType = false; 2481 } 2482 2483 aSysFontData.bAntialias = !mbNonAntialiasedText; 2484 2485 return aSysFontData; 2486 } 2487 2488 // ----------------------------------------------------------------------- 2489 2490 SystemGraphicsData AquaSalGraphics::GetGraphicsData() const 2491 { 2492 SystemGraphicsData aRes; 2493 aRes.nSize = sizeof(aRes); 2494 aRes.rCGContext = mrContext; 2495 return aRes; 2496 } 2497 2498 // ----------------------------------------------------------------------- 2499 2500 void AquaSalGraphics::SetXORMode( bool bSet, bool bInvertOnly ) 2501 { 2502 // return early if XOR mode remains unchanged 2503 if( mbPrinter ) 2504 return; 2505 2506 if( ! bSet && mnXorMode == 2 ) 2507 { 2508 CGContextSetBlendMode( mrContext, kCGBlendModeNormal ); 2509 mnXorMode = 0; 2510 return; 2511 } 2512 else if( bSet && bInvertOnly && mnXorMode == 0) 2513 { 2514 CGContextSetBlendMode( mrContext, kCGBlendModeDifference ); 2515 mnXorMode = 2; 2516 return; 2517 } 2518 2519 if( (mpXorEmulation == NULL) && !bSet ) 2520 return; 2521 if( (mpXorEmulation != NULL) && (bSet == mpXorEmulation->IsEnabled()) ) 2522 return; 2523 if( !CheckContext() ) 2524 return; 2525 2526 // prepare XOR emulation 2527 if( !mpXorEmulation ) 2528 { 2529 mpXorEmulation = new XorEmulation(); 2530 mpXorEmulation->SetTarget( mnWidth, mnHeight, mnBitmapDepth, mrContext, mxLayer ); 2531 } 2532 2533 // change the XOR mode 2534 if( bSet ) 2535 { 2536 mpXorEmulation->Enable(); 2537 mrContext = mpXorEmulation->GetMaskContext(); 2538 mnXorMode = 1; 2539 } 2540 else 2541 { 2542 mpXorEmulation->UpdateTarget(); 2543 mpXorEmulation->Disable(); 2544 mrContext = mpXorEmulation->GetTargetContext(); 2545 mnXorMode = 0; 2546 } 2547 } 2548 2549 // ----------------------------------------------------------------------- 2550 2551 // apply the XOR mask to the target context if active and dirty 2552 void AquaSalGraphics::ApplyXorContext() 2553 { 2554 if( !mpXorEmulation ) 2555 return; 2556 if( mpXorEmulation->UpdateTarget() ) 2557 RefreshRect( 0, 0, mnWidth, mnHeight ); // TODO: refresh minimal changerect 2558 } 2559 2560 // ====================================================================== 2561 2562 XorEmulation::XorEmulation() 2563 : mxTargetLayer( NULL ) 2564 , mxTargetContext( NULL ) 2565 , mxMaskContext( NULL ) 2566 , mxTempContext( NULL ) 2567 , mpMaskBuffer( NULL ) 2568 , mpTempBuffer( NULL ) 2569 , mnBufferLongs( 0 ) 2570 , mbIsEnabled( false ) 2571 {} 2572 2573 // ---------------------------------------------------------------------- 2574 2575 XorEmulation::~XorEmulation() 2576 { 2577 Disable(); 2578 SetTarget( 0, 0, 0, NULL, NULL ); 2579 } 2580 2581 // ----------------------------------------------------------------------- 2582 2583 void XorEmulation::SetTarget( int nWidth, int nHeight, int nTargetDepth, 2584 CGContextRef xTargetContext, CGLayerRef xTargetLayer ) 2585 { 2586 // prepare to replace old mask+temp context 2587 if( mxMaskContext ) 2588 { 2589 // cleanup the mask context 2590 CGContextRelease( mxMaskContext ); 2591 delete[] mpMaskBuffer; 2592 mxMaskContext = NULL; 2593 mpMaskBuffer = NULL; 2594 2595 // cleanup the temp context if needed 2596 if( mxTempContext ) 2597 { 2598 CGContextRelease( mxTempContext ); 2599 delete[] mpTempBuffer; 2600 mxTempContext = NULL; 2601 mpTempBuffer = NULL; 2602 } 2603 } 2604 2605 // return early if there is nothing more to do 2606 if( !xTargetContext ) 2607 return; 2608 2609 // retarget drawing operations to the XOR mask 2610 mxTargetLayer = xTargetLayer; 2611 mxTargetContext = xTargetContext; 2612 2613 // prepare creation of matching CGBitmaps 2614 CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace; 2615 CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst; 2616 int nBitDepth = nTargetDepth; 2617 if( !nBitDepth ) 2618 nBitDepth = 32; 2619 int nBytesPerRow = (nBitDepth == 16) ? 2 : 4; 2620 const size_t nBitsPerComponent = (nBitDepth == 16) ? 5 : 8; 2621 if( nBitDepth <= 8 ) 2622 { 2623 aCGColorSpace = GetSalData()->mxGraySpace; 2624 aCGBmpInfo = kCGImageAlphaNone; 2625 nBytesPerRow = 1; 2626 } 2627 nBytesPerRow *= nWidth; 2628 mnBufferLongs = (nHeight * nBytesPerRow + sizeof(sal_uLong)-1) / sizeof(sal_uLong); 2629 2630 // create a XorMask context 2631 mpMaskBuffer = new sal_uLong[ mnBufferLongs ]; 2632 mxMaskContext = ::CGBitmapContextCreate( mpMaskBuffer, 2633 nWidth, nHeight, nBitsPerComponent, nBytesPerRow, 2634 aCGColorSpace, aCGBmpInfo ); 2635 // reset the XOR mask to black 2636 memset( mpMaskBuffer, 0, mnBufferLongs * sizeof(sal_uLong) ); 2637 2638 // a bitmap context will be needed for manual XORing 2639 // create one unless the target context is a bitmap context 2640 if( nTargetDepth ) 2641 mpTempBuffer = (sal_uLong*)CGBitmapContextGetData( mxTargetContext ); 2642 if( !mpTempBuffer ) 2643 { 2644 // create a bitmap context matching to the target context 2645 mpTempBuffer = new sal_uLong[ mnBufferLongs ]; 2646 mxTempContext = ::CGBitmapContextCreate( mpTempBuffer, 2647 nWidth, nHeight, nBitsPerComponent, nBytesPerRow, 2648 aCGColorSpace, aCGBmpInfo ); 2649 } 2650 2651 // initialize XOR mask context for drawing 2652 CGContextSetFillColorSpace( mxMaskContext, aCGColorSpace ); 2653 CGContextSetStrokeColorSpace( mxMaskContext, aCGColorSpace ); 2654 CGContextSetShouldAntialias( mxMaskContext, false ); 2655 2656 // improve the XorMask's XOR emulation a litte 2657 // NOTE: currently only enabled for monochrome contexts 2658 if( aCGColorSpace == GetSalData()->mxGraySpace ) 2659 CGContextSetBlendMode( mxMaskContext, kCGBlendModeDifference ); 2660 2661 // intialize the transformation matrix to the drawing target 2662 const CGAffineTransform aCTM = CGContextGetCTM( xTargetContext ); 2663 CGContextConcatCTM( mxMaskContext, aCTM ); 2664 if( mxTempContext ) 2665 CGContextConcatCTM( mxTempContext, aCTM ); 2666 2667 // initialize the default XorMask graphics state 2668 CGContextSaveGState( mxMaskContext ); 2669 } 2670 2671 // ---------------------------------------------------------------------- 2672 2673 bool XorEmulation::UpdateTarget() 2674 { 2675 if( !IsEnabled() ) 2676 return false; 2677 2678 // update the temp bitmap buffer if needed 2679 if( mxTempContext ) 2680 CGContextDrawLayerAtPoint( mxTempContext, CGPointZero, mxTargetLayer ); 2681 2682 // do a manual XOR with the XorMask 2683 // this approach suffices for simple color manipulations 2684 // and also the complex-clipping-XOR-trick used in metafiles 2685 const sal_uLong* pSrc = mpMaskBuffer; 2686 sal_uLong* pDst = mpTempBuffer; 2687 for( int i = mnBufferLongs; --i >= 0;) 2688 *(pDst++) ^= *(pSrc++); 2689 2690 // write back the XOR results to the target context 2691 if( mxTempContext ) 2692 { 2693 CGImageRef xXorImage = CGBitmapContextCreateImage( mxTempContext ); 2694 const int nWidth = (int)CGImageGetWidth( xXorImage ); 2695 const int nHeight = (int)CGImageGetHeight( xXorImage ); 2696 // TODO: update minimal changerect 2697 const CGRect aFullRect = {{0,0},{nWidth,nHeight}}; 2698 CGContextDrawImage( mxTargetContext, aFullRect, xXorImage ); 2699 CGImageRelease( xXorImage ); 2700 } 2701 2702 // reset the XorMask to black again 2703 // TODO: not needed for last update 2704 memset( mpMaskBuffer, 0, mnBufferLongs * sizeof(sal_uLong) ); 2705 2706 // TODO: return FALSE if target was not changed 2707 return true; 2708 } 2709 2710 // ======================================================================= 2711 2712