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