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