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