/************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright 2008 by Sun Microsystems, Inc. * * OpenOffice.org - a multi-platform office productivity suite * * $RCSfile: salgdi.cxx,v $ * $Revision: 1.74 $ * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * only, as published by the Free Software Foundation. * * OpenOffice.org is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License version 3 for more details * (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU Lesser General Public License * version 3 along with OpenOffice.org. If not, see * * for a copy of the LGPLv3 License. * ************************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_vcl.hxx" #include "salconst.h" #include "salgdi.h" #include "salbmp.h" #include "salframe.h" #include "salcolorutils.hxx" #include "vcl/impfont.hxx" #include "psprint/list.h" #include "psprint/sft.h" #include "osl/file.hxx" #include "vos/mutex.hxx" #include "osl/process.h" #include "rtl/bootstrap.h" #include "rtl/strbuf.hxx" #include "vcl/sallayout.hxx" #include "salatsuifontutils.hxx" #include "vcl/svapp.hxx" #include "basegfx/range/b2drectangle.hxx" //#include "basegfx/range/b2irange.hxx" #include "basegfx/polygon/b2dpolygon.hxx" #include "basegfx/polygon/b2dpolygontools.hxx" #include "basegfx/matrix/b2dhommatrix.hxx" /* #include "basebmp/color.hxx" #include #include using namespace std; */ typedef unsigned char Boolean; // copied from MacTypes.h, should be properly included typedef std::vector ByteVector; // ======================================================================= ImplMacFontData::ImplMacFontData( const ImplDevFontAttributes& rDFA, ATSUFontID nFontId ) : ImplFontData( rDFA, 0 ) , mnFontId( nFontId ) , mpCharMap( NULL ) , mbOs2Read( false ) , mbHasOs2Table( false ) , mbCmapEncodingRead( false ) , mbHasCJKSupport( false ) {} // ----------------------------------------------------------------------- ImplMacFontData::~ImplMacFontData() { if( mpCharMap ) mpCharMap->DeReference(); } // ----------------------------------------------------------------------- sal_IntPtr ImplMacFontData::GetFontId() const { return (sal_IntPtr)mnFontId; } // ----------------------------------------------------------------------- ImplFontData* ImplMacFontData::Clone() const { ImplMacFontData* pClone = new ImplMacFontData(*this); if( mpCharMap ) mpCharMap->AddReference(); return pClone; } // ----------------------------------------------------------------------- ImplFontEntry* ImplMacFontData::CreateFontInstance(ImplFontSelectData& rFSD) const { return new ImplFontEntry(rFSD); } // ----------------------------------------------------------------------- inline FourCharCode GetTag(const char aTagName[5]) { return (aTagName[0]<<24)+(aTagName[1]<<16)+(aTagName[2]<<8)+(aTagName[3]); } static unsigned GetUShort( const unsigned char* p ){return((p[0]<<8)+p[1]);} static unsigned GetUInt( const unsigned char* p ) { return((p[0]<<24)+(p[1]<<16)+(p[2]<<8)+p[3]);} ImplFontCharMap* ImplMacFontData::GetImplFontCharMap() const { if( mpCharMap ) { // return the cached charmap mpCharMap->AddReference(); return mpCharMap; } // set the default charmap mpCharMap = ImplFontCharMap::GetDefaultMap(); // get the CMAP byte size ATSFontRef rFont = FMGetATSFontRefFromFont( mnFontId ); ByteCount nBufSize = 0; OSStatus eStatus = ATSFontGetTable( rFont, GetTag("cmap"), 0, 0, NULL, &nBufSize ); DBG_ASSERT( (eStatus==noErr), "ImplMacFontData::GetImplFontCharMap : ATSFontGetTable1 failed!\n"); if( eStatus != noErr ) return mpCharMap; // allocate a buffer for the CMAP raw data ByteVector aBuffer( nBufSize ); // get the CMAP raw data ByteCount nRawLength = 0; eStatus = ATSFontGetTable( rFont, GetTag("cmap"), 0, nBufSize, (void*)&aBuffer[0], &nRawLength ); DBG_ASSERT( (eStatus==noErr), "ImplMacFontData::GetImplFontCharMap : ATSFontGetTable2 failed!\n"); if( eStatus != noErr ) return mpCharMap; DBG_ASSERT( (nBufSize==nRawLength), "ImplMacFontData::GetImplFontCharMap : ByteCount mismatch!\n"); // parse the CMAP CmapResult aCmapResult; if( !ParseCMAP( &aBuffer[0], nRawLength, aCmapResult ) ) return mpCharMap; mpCharMap = new ImplFontCharMap( aCmapResult.mnPairCount, aCmapResult.mpPairCodes, aCmapResult.mpStartGlyphs ); return mpCharMap; } // ----------------------------------------------------------------------- void ImplMacFontData::ReadOs2Table( void ) const { // read this only once per font if( mbOs2Read ) return; mbOs2Read = true; // prepare to get the OS/2 table raw data ATSFontRef rFont = FMGetATSFontRefFromFont( mnFontId ); ByteCount nBufSize = 0; OSStatus eStatus = ATSFontGetTable( rFont, GetTag("OS/2"), 0, 0, NULL, &nBufSize ); DBG_ASSERT( (eStatus==noErr), "ImplMacFontData::ReadOs2Table : ATSFontGetTable1 failed!\n"); if( eStatus != noErr ) return; // allocate a buffer for the OS/2 raw data ByteVector aBuffer; aBuffer.resize( nBufSize ); // get the OS/2 raw data ByteCount nRawLength = 0; eStatus = ATSFontGetTable( rFont, GetTag("OS/2"), 0, nBufSize, (void*)&aBuffer[0], &nRawLength ); DBG_ASSERT( (eStatus==noErr), "ImplMacFontData::ReadOs2Table : ATSFontGetTable2 failed!\n"); if( eStatus != noErr ) return; DBG_ASSERT( (nBufSize==nRawLength), "ImplMacFontData::ReadOs2Table : ByteCount mismatch!\n"); mbHasOs2Table = true; // parse the OS/2 raw data // TODO: also analyze panose info, etc. // check if the fonts needs the "CJK extra leading" heuristic const unsigned char* pOS2map = &aBuffer[0]; const sal_uInt32 nVersion = GetUShort( pOS2map ); if( nVersion >= 0x0001 ) { sal_uInt32 ulUnicodeRange2 = GetUInt( pOS2map + 46 ); if( ulUnicodeRange2 & 0x2DF00000 ) mbHasCJKSupport = true; } } void ImplMacFontData::ReadMacCmapEncoding( void ) const { // read this only once per font if( mbCmapEncodingRead ) return; mbCmapEncodingRead = true; ATSFontRef rFont = FMGetATSFontRefFromFont( mnFontId ); ByteCount nBufSize = 0; OSStatus eStatus = ATSFontGetTable( rFont, GetTag("cmap"), 0, 0, NULL, &nBufSize ); DBG_ASSERT( (eStatus==noErr), "ImplMacFontData::ReadMacCmapEncoding : ATSFontGetTable1 failed!\n"); if( eStatus != noErr ) return; ByteVector aBuffer; aBuffer.resize( nBufSize ); ByteCount nRawLength = 0; eStatus = ATSFontGetTable( rFont, GetTag("cmap"), 0, nBufSize, (void*)&aBuffer[0], &nRawLength ); DBG_ASSERT( (eStatus==noErr), "ImplMacFontData::ReadMacCmapEncoding : ATSFontGetTable2 failed!\n"); if( eStatus != noErr ) return; DBG_ASSERT( (nBufSize==nRawLength), "ImplMacFontData::ReadMacCmapEncoding : ByteCount mismatch!\n"); const unsigned char* pCmap = &aBuffer[0]; if (nRawLength < 24 ) return; if( GetUShort( pCmap ) != 0x0000 ) return; // check if the fonts needs the "CJK extra leading" heuristic int nSubTables = GetUShort( pCmap + 2 ); for( const unsigned char* p = pCmap + 4; --nSubTables >= 0; p += 8 ) { int nPlatform = GetUShort( p ); if( nPlatform == kFontMacintoshPlatform ) { int nEncoding = GetUShort (p + 2 ); if( nEncoding == kFontJapaneseScript || nEncoding == kFontTraditionalChineseScript || nEncoding == kFontKoreanScript || nEncoding == kFontSimpleChineseScript ) { mbHasCJKSupport = true; break; } } } } // ----------------------------------------------------------------------- bool ImplMacFontData::HasCJKSupport( void ) const { ReadOs2Table(); if( !mbHasOs2Table ) ReadMacCmapEncoding(); return mbHasCJKSupport; } // ======================================================================= AquaSalGraphics::AquaSalGraphics() : mpFrame( NULL ) , mxLayer( NULL ) , mrContext( NULL ) , mpXorEmulation( NULL ) , mnWidth( 0 ) , mnHeight( 0 ) , mnBitmapDepth( 0 ) , mnRealDPIX( 0 ) , mnRealDPIY( 0 ) , mfFakeDPIScale( 1.0 ) , mxClipRectsPath( NULL ) , mxClipPolysPath( NULL ) , maLineColor( COL_WHITE ) , maFillColor( COL_BLACK ) , mpMacFontData( NULL ) , mnATSUIRotation( 0 ) , mfFontScale( 1.0 ) , mfFontStretch( 1.0 ) , mbNonAntialiasedText( false ) , mbPrinter( false ) , mbVirDev( false ) , mbWindow( false ) { long nDummyX, nDummyY; GetResolution( nDummyX, nDummyY ); // create the style object for font attributes ATSUCreateStyle( &maATSUStyle ); } // ----------------------------------------------------------------------- AquaSalGraphics::~AquaSalGraphics() { /* if( mnUpdateGraphicsEvent ) { Application::RemoveUserEvent( mnUpdateGraphicsEvent ); } */ CGPathRelease( mxClipRectsPath ); CGPathRelease( mxClipPolysPath ); ATSUDisposeStyle( maATSUStyle ); if( mxLayer ) CGLayerRelease( mxLayer ); else if( mrContext && mbWindow ) { // destroy backbuffer bitmap context that we created ourself CGContextRelease( mrContext ); mrContext = NULL; // memory is freed automatically by maOwnContextMemory } if( mpXorEmulation ) delete mpXorEmulation; } bool AquaSalGraphics::supportsOperation( OutDevSupportType eType ) const { bool bRet = false; switch( eType ) { case OutDevSupport_TransparentRect: case OutDevSupport_B2DClip: case OutDevSupport_B2DDraw: bRet = true; break; default: break; } return bRet; } // ======================================================================= void AquaSalGraphics::updateResolution() { DBG_ASSERT( mbWindow, "updateResolution on inappropriate graphics" ); initResolution( (mbWindow && mpFrame) ? mpFrame->mpWindow : nil ); } void AquaSalGraphics::initResolution( NSWindow* pWin ) { NSScreen* pScreen = nil; if( pWin ) pScreen = [pWin screen]; if( pScreen == nil ) pScreen = [NSScreen mainScreen]; mnRealDPIX = mnRealDPIY = 96; if( pScreen ) { NSDictionary* pDev = [pScreen deviceDescription]; if( pDev ) { NSNumber* pVal = [pDev objectForKey: @"NSScreenNumber"]; if( pVal ) { // FIXME: casting a long to CGDirectDisplayID is evil, but // Apple suggest to do it this way const CGDirectDisplayID nDisplayID = (CGDirectDisplayID)[pVal longValue]; const CGSize aSize = CGDisplayScreenSize( nDisplayID ); // => result is in millimeters mnRealDPIX = static_cast((CGDisplayPixelsWide( nDisplayID ) * 25.4) / aSize.width); mnRealDPIY = static_cast((CGDisplayPixelsHigh( nDisplayID ) * 25.4) / aSize.height); } else { DBG_ERROR( "no resolution found in device description" ); } } else { DBG_ERROR( "no device description" ); } } else { DBG_ERROR( "no screen found" ); } // equalize x- and y-resolution if they are close enough to prevent unneeded font stretching if( (mnRealDPIX != mnRealDPIY) && (10*mnRealDPIX < 13*mnRealDPIY) && (13*mnRealDPIX > 10*mnRealDPIY) ) { mnRealDPIX = mnRealDPIY = (mnRealDPIX + mnRealDPIY + 1) / 2; } mfFakeDPIScale = 1.0; } void AquaSalGraphics::GetResolution( long& rDPIX, long& rDPIY ) { if( !mnRealDPIY ) initResolution( (mbWindow && mpFrame) ? mpFrame->mpWindow : nil ); rDPIX = static_cast(mfFakeDPIScale * mnRealDPIX); rDPIY = static_cast(mfFakeDPIScale * mnRealDPIY); } // ----------------------------------------------------------------------- void AquaSalGraphics::GetScreenFontResolution( long& rDPIX, long& rDPIY ) { GetResolution( rDPIX, rDPIY ); // the screen font resolution should equal the real resolution // but to satisfy the quite insane heuristics in Window::ImplUpdateGlobalSettings() // it needs to be tweaked // TODO: remove the tweaking below if it becomes possible if( (rDPIX < 72) || (rDPIY < 72) ) { const long nMinDPI = (rDPIX <= rDPIY) ? rDPIX : rDPIY; rDPIX = (72 * rDPIX + (nMinDPI/2)) / nMinDPI; rDPIY = (72 * rDPIY + (nMinDPI/2)) / nMinDPI; } } // ----------------------------------------------------------------------- USHORT AquaSalGraphics::GetBitCount() { USHORT nBits = mnBitmapDepth ? mnBitmapDepth : 32;//24; return nBits; } // ----------------------------------------------------------------------- static void AddPolygonToPath( CGMutablePathRef xPath, const ::basegfx::B2DPolygon& rPolygon, bool bPixelSnap ) { // short circuit if there is nothing to do const int nPointCount = rPolygon.count(); if( nPointCount <= 0 ) return; (void)bPixelSnap; // TODO const CGAffineTransform* pTransform = NULL; const bool bHasCurves = rPolygon.areControlPointsUsed(); bool bPendingCurve = false; for( int nPointIdx = 0, nPrevIdx = 0; nPointIdx <= nPointCount; nPrevIdx = nPointIdx++ ) { const int nClosedIdx = (nPointIdx < nPointCount) ? nPointIdx : 0; const ::basegfx::B2DPoint& rPoint = rPolygon.getB2DPoint( nClosedIdx ); if( !nPointIdx ) // first point { CGPathMoveToPoint( xPath, pTransform, rPoint.getX(), rPoint.getY() ); } else if( !bPendingCurve ) // line segment { CGPathAddLineToPoint( xPath, pTransform, rPoint.getX(), rPoint.getY() ); } else // cubic bezier segment { const ::basegfx::B2DPoint& rCP1 = rPolygon.getNextControlPoint( nPrevIdx ); const ::basegfx::B2DPoint& rCP2 = rPolygon.getPrevControlPoint( nClosedIdx ); CGPathAddCurveToPoint( xPath, pTransform, rCP1.getX(), rCP1.getY(), rCP2.getX(), rCP2.getY(), rPoint.getX(), rPoint.getY() ); } if( bHasCurves ) bPendingCurve = rPolygon.isNextControlPointUsed( nClosedIdx ); } CGPathCloseSubpath( xPath ); } static void AddPolyPolygonToPath( CGMutablePathRef xPath, const ::basegfx::B2DPolyPolygon& rPolyPoly, bool bPixelSnap ) { // short circuit if there is nothing to do const int nPolyCount = rPolyPoly.count(); if( nPolyCount <= 0 ) return; for( int nPolyIdx = 0; nPolyIdx < nPolyCount; ++nPolyIdx ) { const ::basegfx::B2DPolygon rPolygon = rPolyPoly.getB2DPolygon( nPolyIdx ); AddPolygonToPath( xPath, rPolygon, bPixelSnap ); } } // ----------------------------------------------------------------------- void AquaSalGraphics::ResetClipRegion() { // release old path and indicate no clipping CGPathRelease( mxClipRectsPath ); mxClipRectsPath = NULL; CGPathRelease( mxClipPolysPath ); mxClipPolysPath = NULL; if( CheckContext() ) SetState(); } // ----------------------------------------------------------------------- void AquaSalGraphics::BeginSetClipRegion( ULONG nRectCount ) { // release union-of-rectangles clip if( mxClipRectsPath ) { CGPathRelease( mxClipRectsPath ); mxClipRectsPath = NULL; } // release complex polygon clip if( !nRectCount ) { CGPathRelease( mxClipPolysPath ); mxClipPolysPath = NULL; } } // ----------------------------------------------------------------------- BOOL AquaSalGraphics::unionClipRegion( long nX, long nY, long nWidth, long nHeight ) { if( (nWidth <= 0) || (nHeight <= 0) ) return TRUE; if( !mxClipRectsPath ) mxClipRectsPath = CGPathCreateMutable(); const CGRect aClipRect = {{nX,nY},{nWidth,nHeight}}; CGPathAddRect( mxClipRectsPath, NULL, aClipRect ); return TRUE; } // ----------------------------------------------------------------------- bool AquaSalGraphics::unionClipRegion( const ::basegfx::B2DPolyPolygon& rPolyPolygon ) { if( rPolyPolygon.count() <= 0 ) return true; if( !mxClipPolysPath ) mxClipPolysPath = CGPathCreateMutable(); AddPolyPolygonToPath( mxClipPolysPath, rPolyPolygon, false ); return true; } // ----------------------------------------------------------------------- void AquaSalGraphics::EndSetClipRegion() { if( CheckContext() ) SetState(); } // ----------------------------------------------------------------------- void AquaSalGraphics::SetLineColor() { maLineColor.SetAlpha( 0.0 ); // transparent CGContextSetStrokeColor( mrContext, maLineColor.AsArray() ); } // ----------------------------------------------------------------------- void AquaSalGraphics::SetLineColor( SalColor nSalColor ) { maLineColor = RGBAColor( nSalColor ); CGContextSetStrokeColor( mrContext, maLineColor.AsArray() ); } // ----------------------------------------------------------------------- void AquaSalGraphics::SetFillColor() { maFillColor.SetAlpha( 0.0 ); // transparent CGContextSetFillColor( mrContext, maFillColor.AsArray() ); } // ----------------------------------------------------------------------- void AquaSalGraphics::SetFillColor( SalColor nSalColor ) { maFillColor = RGBAColor( nSalColor ); CGContextSetFillColor( mrContext, maFillColor.AsArray() ); } // ----------------------------------------------------------------------- static SalColor ImplGetROPSalColor( SalROPColor nROPColor ) { SalColor nSalColor; if ( nROPColor == SAL_ROP_0 ) nSalColor = MAKE_SALCOLOR( 0, 0, 0 ); else nSalColor = MAKE_SALCOLOR( 255, 255, 255 ); return nSalColor; } void AquaSalGraphics::SetROPLineColor( SalROPColor nROPColor ) { if( ! mbPrinter ) SetLineColor( ImplGetROPSalColor( nROPColor ) ); } // ----------------------------------------------------------------------- void AquaSalGraphics::SetROPFillColor( SalROPColor nROPColor ) { if( ! mbPrinter ) SetFillColor( ImplGetROPSalColor( nROPColor ) ); } // ----------------------------------------------------------------------- void AquaSalGraphics::ImplDrawPixel( long nX, long nY, const RGBAColor& rColor ) { if( !CheckContext() ) return; // overwrite the fill color CGContextSetFillColor( mrContext, rColor.AsArray() ); // draw 1x1 rect, there is no pixel drawing in Quartz CGRect aDstRect = {{nX,nY,},{1,1}}; CGContextFillRect( mrContext, aDstRect ); RefreshRect( aDstRect ); // reset the fill color CGContextSetFillColor( mrContext, maFillColor.AsArray() ); } void AquaSalGraphics::drawPixel( long nX, long nY ) { // draw pixel with current line color ImplDrawPixel( nX, nY, maLineColor ); } void AquaSalGraphics::drawPixel( long nX, long nY, SalColor nSalColor ) { const RGBAColor aPixelColor( nSalColor ); ImplDrawPixel( nX, nY, aPixelColor ); } // ----------------------------------------------------------------------- void AquaSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 ) { if( !CheckContext() ) return; CGContextBeginPath( mrContext ); CGContextMoveToPoint( mrContext, static_cast(nX1)+0.5, static_cast(nY1)+0.5 ); CGContextAddLineToPoint( mrContext, static_cast(nX2)+0.5, static_cast(nY2)+0.5 ); CGContextDrawPath( mrContext, kCGPathStroke ); Rectangle aRefreshRect( nX1, nY1, nX2, nY2 ); } // ----------------------------------------------------------------------- void AquaSalGraphics::drawRect( long nX, long nY, long nWidth, long nHeight ) { if( !CheckContext() ) return; CGRect aRect( CGRectMake(nX, nY, nWidth, nHeight) ); if( IsPenVisible() ) { aRect.origin.x += 0.5; aRect.origin.y += 0.5; aRect.size.width -= 1; aRect.size.height -= 1; } if( IsBrushVisible() ) CGContextFillRect( mrContext, aRect ); if( IsPenVisible() ) CGContextStrokeRect( mrContext, aRect ); RefreshRect( nX, nY, nWidth, nHeight ); } // ----------------------------------------------------------------------- static void getBoundRect( ULONG nPoints, const SalPoint *pPtAry, long &rX, long& rY, long& rWidth, long& rHeight ) { long nX1 = pPtAry->mnX; long nX2 = nX1; long nY1 = pPtAry->mnY; long nY2 = nY1; for( ULONG n = 1; n < nPoints; n++ ) { if( pPtAry[n].mnX < nX1 ) nX1 = pPtAry[n].mnX; else if( pPtAry[n].mnX > nX2 ) nX2 = pPtAry[n].mnX; if( pPtAry[n].mnY < nY1 ) nY1 = pPtAry[n].mnY; else if( pPtAry[n].mnY > nY2 ) nY2 = pPtAry[n].mnY; } rX = nX1; rY = nY1; rWidth = nX2 - nX1 + 1; rHeight = nY2 - nY1 + 1; } static inline void alignLinePoint( const SalPoint* i_pIn, float& o_fX, float& o_fY ) { o_fX = static_cast(i_pIn->mnX ) + 0.5; o_fY = static_cast(i_pIn->mnY ) + 0.5; } void AquaSalGraphics::drawPolyLine( ULONG nPoints, const SalPoint *pPtAry ) { if( nPoints < 1 ) return; if( !CheckContext() ) return; long nX = 0, nY = 0, nWidth = 0, nHeight = 0; getBoundRect( nPoints, pPtAry, nX, nY, nWidth, nHeight ); float fX, fY; CGContextBeginPath( mrContext ); alignLinePoint( pPtAry, fX, fY ); CGContextMoveToPoint( mrContext, fX, fY ); pPtAry++; for( ULONG nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ ) { alignLinePoint( pPtAry, fX, fY ); CGContextAddLineToPoint( mrContext, fX, fY ); } CGContextDrawPath( mrContext, kCGPathStroke ); RefreshRect( nX, nY, nWidth, nHeight ); } // ----------------------------------------------------------------------- void AquaSalGraphics::drawPolygon( ULONG nPoints, const SalPoint *pPtAry ) { if( nPoints <= 1 ) return; if( !CheckContext() ) return; long nX = 0, nY = 0, nWidth = 0, nHeight = 0; getBoundRect( nPoints, pPtAry, nX, nY, nWidth, nHeight ); CGPathDrawingMode eMode; if( IsBrushVisible() && IsPenVisible() ) eMode = kCGPathEOFillStroke; else if( IsPenVisible() ) eMode = kCGPathStroke; else if( IsBrushVisible() ) eMode = kCGPathEOFill; else return; CGContextBeginPath( mrContext ); if( IsPenVisible() ) { float fX, fY; alignLinePoint( pPtAry, fX, fY ); CGContextMoveToPoint( mrContext, fX, fY ); pPtAry++; for( ULONG nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ ) { alignLinePoint( pPtAry, fX, fY ); CGContextAddLineToPoint( mrContext, fX, fY ); } } else { CGContextMoveToPoint( mrContext, pPtAry->mnX, pPtAry->mnY ); pPtAry++; for( ULONG nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ ) CGContextAddLineToPoint( mrContext, pPtAry->mnX, pPtAry->mnY ); } CGContextDrawPath( mrContext, eMode ); RefreshRect( nX, nY, nWidth, nHeight ); } // ----------------------------------------------------------------------- void AquaSalGraphics::drawPolyPolygon( ULONG nPolyCount, const ULONG *pPoints, PCONSTSALPOINT *ppPtAry ) { if( nPolyCount <= 0 ) return; if( !CheckContext() ) return; // find bound rect long leftX = 0, topY = 0, maxWidth = 0, maxHeight = 0; getBoundRect( pPoints[0], ppPtAry[0], leftX, topY, maxWidth, maxHeight ); for( ULONG n = 1; n < nPolyCount; n++ ) { long nX = leftX, nY = topY, nW = maxWidth, nH = maxHeight; getBoundRect( pPoints[n], ppPtAry[n], nX, nY, nW, nH ); if( nX < leftX ) { maxWidth += leftX - nX; leftX = nX; } if( nY < topY ) { maxHeight += topY - nY; topY = nY; } if( nX + nW > leftX + maxWidth ) maxWidth = nX + nW - leftX; if( nY + nH > topY + maxHeight ) maxHeight = nY + nH - topY; } // prepare drawing mode CGPathDrawingMode eMode; if( IsBrushVisible() && IsPenVisible() ) eMode = kCGPathEOFillStroke; else if( IsPenVisible() ) eMode = kCGPathStroke; else if( IsBrushVisible() ) eMode = kCGPathEOFill; else return; // convert to CGPath CGContextBeginPath( mrContext ); if( IsPenVisible() ) { for( ULONG nPoly = 0; nPoly < nPolyCount; nPoly++ ) { const ULONG nPoints = pPoints[nPoly]; if( nPoints > 1 ) { const SalPoint *pPtAry = ppPtAry[nPoly]; float fX, fY; alignLinePoint( pPtAry, fX, fY ); CGContextMoveToPoint( mrContext, fX, fY ); pPtAry++; for( ULONG nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ ) { alignLinePoint( pPtAry, fX, fY ); CGContextAddLineToPoint( mrContext, fX, fY ); } CGContextClosePath(mrContext); } } } else { for( ULONG nPoly = 0; nPoly < nPolyCount; nPoly++ ) { const ULONG nPoints = pPoints[nPoly]; if( nPoints > 1 ) { const SalPoint *pPtAry = ppPtAry[nPoly]; CGContextMoveToPoint( mrContext, pPtAry->mnX, pPtAry->mnY ); pPtAry++; for( ULONG nPoint = 1; nPoint < nPoints; nPoint++, pPtAry++ ) CGContextAddLineToPoint( mrContext, pPtAry->mnX, pPtAry->mnY ); CGContextClosePath(mrContext); } } } CGContextDrawPath( mrContext, eMode ); RefreshRect( leftX, topY, maxWidth, maxHeight ); } // ----------------------------------------------------------------------- bool AquaSalGraphics::drawPolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly, double fTransparency ) { // short circuit if there is nothing to do const int nPolyCount = rPolyPoly.count(); if( nPolyCount <= 0 ) return true; // setup poly-polygon path CGMutablePathRef xPath = CGPathCreateMutable(); for( int nPolyIdx = 0; nPolyIdx < nPolyCount; ++nPolyIdx ) { const ::basegfx::B2DPolygon rPolygon = rPolyPoly.getB2DPolygon( nPolyIdx ); AddPolygonToPath( xPath, rPolygon, false ); } CGContextSaveGState( mrContext ); CGContextBeginPath( mrContext ); CGContextAddPath( mrContext, xPath ); const CGRect aRefreshRect = CGPathGetBoundingBox( xPath ); CGPathRelease( xPath ); // draw path with antialiased polygon CGContextSetShouldAntialias( mrContext, true ); CGContextSetAlpha( mrContext, 1.0 - fTransparency ); CGContextDrawPath( mrContext, kCGPathEOFillStroke ); CGContextRestoreGState( mrContext ); // mark modified rectangle as updated RefreshRect( aRefreshRect ); return true; } // ----------------------------------------------------------------------- bool AquaSalGraphics::drawPolyLine( const ::basegfx::B2DPolygon& rPolyLine, const ::basegfx::B2DVector& rLineWidths ) { // short circuit if there is nothing to do const int nPointCount = rPolyLine.count(); if( nPointCount <= 0 ) return true; // setup poly-polygon path CGMutablePathRef xPath = CGPathCreateMutable(); AddPolygonToPath( xPath, rPolyLine, false ); CGContextSaveGState( mrContext ); CGContextAddPath( mrContext, xPath ); const CGRect aRefreshRect = CGPathGetBoundingBox( xPath ); CGPathRelease( xPath ); // draw path with antialiased line // CGContextSetAllowsAntialiasing( mrContext, true ); CGContextSetShouldAntialias( mrContext, true ); CGContextSetLineWidth( mrContext, rLineWidths.getX() ); CGContextDrawPath( mrContext, kCGPathStroke ); CGContextRestoreGState( mrContext ); // mark modified rectangle as updated RefreshRect( aRefreshRect ); return true; } // ----------------------------------------------------------------------- sal_Bool AquaSalGraphics::drawPolyLineBezier( ULONG nPoints, const SalPoint* pPtAry, const BYTE* pFlgAry ) { return sal_False; } // ----------------------------------------------------------------------- sal_Bool AquaSalGraphics::drawPolygonBezier( ULONG nPoints, const SalPoint* pPtAry, const BYTE* pFlgAry ) { return sal_False; } // ----------------------------------------------------------------------- sal_Bool AquaSalGraphics::drawPolyPolygonBezier( ULONG nPoly, const ULONG* pPoints, const SalPoint* const* pPtAry, const BYTE* const* pFlgAry ) { return sal_False; } // ----------------------------------------------------------------------- void AquaSalGraphics::copyBits( const SalTwoRect *pPosAry, SalGraphics *pSrcGraphics ) { if( !pSrcGraphics ) pSrcGraphics = this; //from unix salgdi2.cxx //[FIXME] find a better way to prevent calc from crashing when width and height are negative if( pPosAry->mnSrcWidth <= 0 || pPosAry->mnSrcHeight <= 0 || pPosAry->mnDestWidth <= 0 || pPosAry->mnDestHeight <= 0 ) { return; } // accelerate trivial operations /*const*/ AquaSalGraphics* pSrc = static_cast(pSrcGraphics); const bool bSameGraphics = (this == pSrc) || (mpFrame && (mpFrame == pSrc->mpFrame)); if( bSameGraphics && (pPosAry->mnSrcWidth == pPosAry->mnDestWidth) && (pPosAry->mnSrcHeight == pPosAry->mnDestHeight)) { // short circuit if there is nothing to do if( (pPosAry->mnSrcX == pPosAry->mnDestX) && (pPosAry->mnSrcY == pPosAry->mnDestY)) return; // use copyArea() if source and destination context are identical copyArea( pPosAry->mnDestX, pPosAry->mnDestY, pPosAry->mnSrcX, pPosAry->mnSrcY, pPosAry->mnSrcWidth, pPosAry->mnSrcHeight, 0 ); return; } pSrc->ApplyXorContext(); #if 0 // TODO: make AquaSalBitmap as fast as the alternative implementation below SalBitmap* pBitmap = pSrc->getBitmap( pPosAry->mnSrcX, pPosAry->mnSrcY, pPosAry->mnSrcWidth, pPosAry->mnSrcHeight ); if( pBitmap ) { SalTwoRect aPosAry( *pPosAry ); aPosAry.mnSrcX = 0; aPosAry.mnSrcY = 0; drawBitmap( &aPosAry, *pBitmap ); delete pBitmap; } #else DBG_ASSERT( pSrc->mxLayer!=NULL, "AquaSalGraphics::copyBits() from non-layered graphics" ) CGContextSaveGState( mrContext ); const CGRect aDstRect = { {pPosAry->mnDestX, pPosAry->mnDestY}, {pPosAry->mnDestWidth, pPosAry->mnDestHeight} }; CGContextClipToRect( mrContext, aDstRect ); // draw at new destination // NOTE: flipped drawing gets disabled for this, else the subimage would be drawn upside down if( pSrc->IsFlipped() ) { CGContextTranslateCTM( mrContext, 0, +mnHeight ); CGContextScaleCTM( mrContext, +1, -1 ); } // TODO: pSrc->size() != this->size() const CGPoint aDstPoint = { +pPosAry->mnDestX - pPosAry->mnSrcX, pPosAry->mnDestY - pPosAry->mnSrcY }; if( !mnBitmapDepth || (aDstPoint.x + pSrc->mnWidth) <= mnWidth ) // workaround a Quartz crasher ::CGContextDrawLayerAtPoint( mrContext, aDstPoint, pSrc->mxLayer ); CGContextRestoreGState( mrContext ); // mark the destination rectangle as updated RefreshRect( aDstRect ); #endif } // ----------------------------------------------------------------------- void AquaSalGraphics::copyArea( long nDstX, long nDstY,long nSrcX, long nSrcY, long nSrcWidth, long nSrcHeight, USHORT nFlags ) { #if 0 // TODO: make AquaSalBitmap as fast as the alternative implementation below SalBitmap* pBitmap = getBitmap( nSrcX, nSrcY, nSrcWidth, nSrcHeight ); if( pBitmap ) { SalTwoRect aPosAry; aPosAry.mnSrcX = 0; aPosAry.mnSrcY = 0; aPosAry.mnSrcWidth = nSrcWidth; aPosAry.mnSrcHeight = nSrcHeight; aPosAry.mnDestX = nDstX; aPosAry.mnDestY = nDstY; aPosAry.mnDestWidth = nSrcWidth; aPosAry.mnDestHeight = nSrcHeight; drawBitmap( &aPosAry, *pBitmap ); delete pBitmap; } #else DBG_ASSERT( mxLayer!=NULL, "AquaSalGraphics::copyArea() for non-layered graphics" ) // drawing a layer onto its own context means trouble => copy it first // TODO: is it possible to get rid of this unneeded copy more often? // e.g. on OSX>=10.5 only this situation causes problems: // mnBitmapDepth && (aDstPoint.x + pSrc->mnWidth) > mnWidth CGLayerRef xSrcLayer = mxLayer; // TODO: if( mnBitmapDepth > 0 ) { const CGSize aSrcSize = { nSrcWidth, nSrcHeight }; xSrcLayer = CGLayerCreateWithContext( mrContext, aSrcSize, NULL ); const CGContextRef xSrcContext = CGLayerGetContext( xSrcLayer ); const CGPoint aSrcPoint = { -nSrcX, (nSrcY+nSrcHeight)-mnHeight }; ::CGContextDrawLayerAtPoint( xSrcContext, aSrcPoint, mxLayer ); } // draw at new destination // NOTE: flipped drawing gets disabled for this, else the subimage would be drawn upside down CGContextSaveGState( mrContext ); if( IsFlipped() ) { CGContextTranslateCTM( mrContext, 0, +mnHeight ); CGContextScaleCTM( mrContext, +1, -1 ); } const CGPoint aDstPoint = { +nDstX, mnHeight-(nDstY+nSrcHeight) }; ::CGContextDrawLayerAtPoint( mrContext, aDstPoint, xSrcLayer ); // cleanup if( xSrcLayer != mxLayer ) CGLayerRelease( xSrcLayer ); CGContextRestoreGState( mrContext ); // mark the destination rectangle as updated RefreshRect( nDstX, nDstY, nSrcWidth, nSrcHeight ); #endif } // ----------------------------------------------------------------------- void AquaSalGraphics::drawBitmap( const SalTwoRect* pPosAry, const SalBitmap& rSalBitmap ) { if( !CheckContext() ) return; const AquaSalBitmap& rBitmap = static_cast(rSalBitmap); CGImageRef xImage = rBitmap.CreateCroppedImage( (int)pPosAry->mnSrcX, (int)pPosAry->mnSrcY, (int)pPosAry->mnSrcWidth, (int)pPosAry->mnSrcHeight ); if( !xImage ) return; const CGRect aDstRect = {{pPosAry->mnDestX, pPosAry->mnDestY}, {pPosAry->mnDestWidth, pPosAry->mnDestHeight}}; CGContextDrawImage( mrContext, aDstRect, xImage ); CGImageRelease( xImage ); RefreshRect( aDstRect ); } // ----------------------------------------------------------------------- void AquaSalGraphics::drawBitmap( const SalTwoRect* pPosAry, const SalBitmap& rSalBitmap,SalColor nTransparentColor ) { DBG_ERROR("not implemented for color masking!"); drawBitmap( pPosAry, rSalBitmap ); } // ----------------------------------------------------------------------- void AquaSalGraphics::drawBitmap( const SalTwoRect* pPosAry, const SalBitmap& rSalBitmap, const SalBitmap& rTransparentBitmap ) { if( !CheckContext() ) return; const AquaSalBitmap& rBitmap = static_cast(rSalBitmap); const AquaSalBitmap& rMask = static_cast(rTransparentBitmap); CGImageRef xMaskedImage( rBitmap.CreateWithMask( rMask, pPosAry->mnSrcX, pPosAry->mnSrcY, pPosAry->mnSrcWidth, pPosAry->mnSrcHeight ) ); if( !xMaskedImage ) return; const CGRect aDstRect = {{pPosAry->mnDestX, pPosAry->mnDestY}, {pPosAry->mnDestWidth, pPosAry->mnDestHeight}}; CGContextDrawImage( mrContext, aDstRect, xMaskedImage ); CGImageRelease( xMaskedImage ); RefreshRect( aDstRect ); } // ----------------------------------------------------------------------- void AquaSalGraphics::drawMask( const SalTwoRect* pPosAry, const SalBitmap& rSalBitmap, SalColor nMaskColor ) { if( !CheckContext() ) return; const AquaSalBitmap& rBitmap = static_cast(rSalBitmap); CGImageRef xImage = rBitmap.CreateColorMask( pPosAry->mnSrcX, pPosAry->mnSrcY, pPosAry->mnSrcWidth, pPosAry->mnSrcHeight, nMaskColor ); if( !xImage ) return; const CGRect aDstRect = {{pPosAry->mnDestX, pPosAry->mnDestY}, {pPosAry->mnDestWidth, pPosAry->mnDestHeight}}; CGContextDrawImage( mrContext, aDstRect, xImage ); CGImageRelease( xImage ); RefreshRect( aDstRect ); } // ----------------------------------------------------------------------- SalBitmap* AquaSalGraphics::getBitmap( long nX, long nY, long nDX, long nDY ) { DBG_ASSERT( mxLayer, "AquaSalGraphics::getBitmap() with no layer" ) ApplyXorContext(); AquaSalBitmap* pBitmap = new AquaSalBitmap; if( !pBitmap->Create( mxLayer, mnBitmapDepth, nX, nY, nDX, nDY, !mbWindow ) ) { delete pBitmap; pBitmap = NULL; } return pBitmap; } // ----------------------------------------------------------------------- SalColor AquaSalGraphics::getPixel( long nX, long nY ) { SalColor nSalColor = 0; return nSalColor; } // ----------------------------------------------------------------------- void AquaSalGraphics::invert( long nX, long nY, long nWidth, long nHeight, SalInvert nFlags ) { if ( CheckContext() ) { CGRect aCGRect = CGRectMake( nX, nY, nWidth, nHeight); CGContextSaveGState(mrContext); if ( nFlags & SAL_INVERT_TRACKFRAME ) { const float dashLengths[2] = { 4.0, 4.0 }; // for drawing dashed line CGContextSetBlendMode( mrContext, kCGBlendModeDifference ); CGContextSetRGBStrokeColor ( mrContext, 1.0, 1.0, 1.0, 1.0 ); CGContextSetLineDash ( mrContext, 0, dashLengths, 2 ); CGContextSetLineWidth( mrContext, 2.0); CGContextStrokeRect ( mrContext, aCGRect ); } else if ( nFlags & SAL_INVERT_50 ) { //workaround // no xor in Core Graphics, so just invert CGContextSetBlendMode(mrContext, kCGBlendModeDifference); CGContextSetRGBFillColor( mrContext,1.0, 1.0, 1.0 , 1.0 ); CGContextFillRect ( mrContext, aCGRect ); } else // just invert { CGContextSetBlendMode(mrContext, kCGBlendModeDifference); CGContextSetRGBFillColor ( mrContext,1.0, 1.0, 1.0 , 1.0 ); CGContextFillRect ( mrContext, aCGRect ); } CGContextRestoreGState( mrContext); RefreshRect( aCGRect ); } } // ----------------------------------------------------------------------- void AquaSalGraphics::invert( ULONG nPoints, const SalPoint* pPtAry, SalInvert nSalFlags ) { CGPoint* CGpoints ; if ( CheckContext() ) { CGContextSaveGState(mrContext); CGpoints = makeCGptArray(nPoints,pPtAry); CGContextAddLines ( mrContext, CGpoints, nPoints ); if ( nSalFlags & SAL_INVERT_TRACKFRAME ) { const float dashLengths[2] = { 4.0, 4.0 }; // for drawing dashed line CGContextSetBlendMode( mrContext, kCGBlendModeDifference ); CGContextSetRGBStrokeColor ( mrContext, 1.0, 1.0, 1.0, 1.0 ); CGContextSetLineDash ( mrContext, 0, dashLengths, 2 ); CGContextSetLineWidth( mrContext, 2.0); CGContextStrokePath ( mrContext ); } else if ( nSalFlags & SAL_INVERT_50 ) { // workaround // no xor in Core Graphics, so just invert CGContextSetBlendMode(mrContext, kCGBlendModeDifference); CGContextSetRGBFillColor( mrContext,1.0, 1.0, 1.0 , 1.0 ); CGContextFillPath( mrContext ); } else // just invert { CGContextSetBlendMode( mrContext, kCGBlendModeDifference ); CGContextSetRGBFillColor( mrContext, 1.0, 1.0, 1.0, 1.0 ); CGContextFillPath( mrContext ); } const CGRect aRect = CGContextGetClipBoundingBox(mrContext); CGContextRestoreGState( mrContext); delete [] CGpoints; RefreshRect( aRect ); } } // ----------------------------------------------------------------------- BOOL AquaSalGraphics::drawEPS( long nX, long nY, long nWidth, long nHeight, void* pPtr, ULONG nSize ) { return FALSE; } // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= bool AquaSalGraphics::drawAlphaBitmap( const SalTwoRect& rTR, const SalBitmap& rSrcBitmap, const SalBitmap& rAlphaBmp ) { // An image mask can't have a depth > 8 bits (should be 1 to 8 bits) if( rAlphaBmp.GetBitCount() > 8 ) return false; // are these two tests really necessary? (see vcl/unx/source/gdi/salgdi2.cxx) // horizontal/vertical mirroring not implemented yet if( rTR.mnDestWidth < 0 || rTR.mnDestHeight < 0 ) return false; const AquaSalBitmap& rSrcSalBmp = static_cast(rSrcBitmap); const AquaSalBitmap& rMaskSalBmp = static_cast(rAlphaBmp); CGImageRef xMaskedImage = rSrcSalBmp.CreateWithMask( rMaskSalBmp, rTR.mnSrcX, rTR.mnSrcY, rTR.mnSrcWidth, rTR.mnSrcHeight ); if( !xMaskedImage ) return false; if ( CheckContext() ) { const CGRect aDstRect = {{rTR.mnDestX, rTR.mnDestY}, {rTR.mnDestWidth, rTR.mnDestHeight}}; CGContextDrawImage( mrContext, aDstRect, xMaskedImage ); RefreshRect( aDstRect ); } CGImageRelease(xMaskedImage); return true; } // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= bool AquaSalGraphics::drawAlphaRect( long nX, long nY, long nWidth, long nHeight, sal_uInt8 nTransparency ) { if( !CheckContext() ) return true; // save the current state CGContextSaveGState( mrContext ); CGContextSetAlpha( mrContext, (100-nTransparency) * (1.0/100) ); CGRect aRect = {{nX,nY},{nWidth-1,nHeight-1}}; if( IsPenVisible() ) { aRect.origin.x += 0.5; aRect.origin.y += 0.5; } CGContextBeginPath( mrContext ); CGContextAddRect( mrContext, aRect ); CGContextDrawPath( mrContext, kCGPathFill ); // restore state CGContextRestoreGState(mrContext); RefreshRect( aRect ); return true; } // ----------------------------------------------------------------------- void AquaSalGraphics::SetTextColor( SalColor nSalColor ) { RGBColor color; color.red = (unsigned short) ( SALCOLOR_RED(nSalColor) * 65535.0 / 255.0 ); color.green = (unsigned short) ( SALCOLOR_GREEN(nSalColor) * 65535.0 / 255.0 ); color.blue = (unsigned short) ( SALCOLOR_BLUE(nSalColor) * 65535.0 / 255.0 ); ATSUAttributeTag aTag = kATSUColorTag; ByteCount aValueSize = sizeof( color ); ATSUAttributeValuePtr aValue = &color; OSStatus err = ATSUSetAttributes( maATSUStyle, 1, &aTag, &aValueSize, &aValue ); DBG_ASSERT( (err==noErr), "AquaSalGraphics::SetTextColor() : Could not set font attributes!\n"); if( err != noErr ) return; } // ----------------------------------------------------------------------- void AquaSalGraphics::GetFontMetric( ImplFontMetricData* pMetric ) { // get the ATSU font metrics (in point units) // of the font that has eventually been size-limited ATSUFontID fontId; OSStatus err = ATSUGetAttribute( maATSUStyle, kATSUFontTag, sizeof(ATSUFontID), &fontId, 0 ); DBG_ASSERT( (err==noErr), "AquaSalGraphics::GetFontMetric() : could not get font id\n"); ATSFontMetrics aMetrics; ATSFontRef rFont = FMGetATSFontRefFromFont( fontId ); err = ATSFontGetHorizontalMetrics ( rFont, kATSOptionFlagsDefault, &aMetrics ); DBG_ASSERT( (err==noErr), "AquaSalGraphics::GetFontMetric() : could not get font metrics\n"); if( err != noErr ) return; // all ATS fonts are scalable fonts pMetric->mbScalableFont = true; // TODO: check if any kerning is possible pMetric->mbKernableFont = true; // convert into VCL font metrics (in unscaled pixel units) Fixed ptSize; err = ATSUGetAttribute( maATSUStyle, kATSUSizeTag, sizeof(Fixed), &ptSize, 0); DBG_ASSERT( (err==noErr), "AquaSalGraphics::GetFontMetric() : could not get font size\n"); const double fPointSize = Fix2X( ptSize ); // convert quartz units to pixel units // please see the comment in AquaSalGraphics::SetFont() for details const double fPixelSize = (mfFontScale * mfFakeDPIScale * fPointSize); pMetric->mnAscent = static_cast(+aMetrics.ascent * fPixelSize + 0.5); pMetric->mnDescent = static_cast((-aMetrics.descent + aMetrics.leading) * fPixelSize + 0.5); pMetric->mnIntLeading = 0; pMetric->mnExtLeading = 0; // ATSFontMetrics.avgAdvanceWidth is obsolete, so it is usually set to zero // since ImplFontMetricData::mnWidth is only used for stretching/squeezing fonts // setting this width to the pixel height of the fontsize is good enough // it also makes the calculation of the stretch factor simple pMetric->mnWidth = static_cast(mfFontStretch * fPixelSize + 0.5); // apply the "CJK needs extra leading" heuristic if needed #if 0 // #i85422# on MacOSX the CJK fonts have good metrics even without the heuristics below if( mpMacFontData->HasCJKSupport() ) { pMetric->mnIntLeading += pMetric->mnExtLeading; const long nHalfTmpExtLeading = pMetric->mnExtLeading / 2; const long nOtherHalfTmpExtLeading = pMetric->mnExtLeading - nHalfTmpExtLeading; long nCJKExtLeading = static_cast(0.30 * (pMetric->mnAscent + pMetric->mnDescent)); nCJKExtLeading -= pMetric->mnExtLeading; pMetric->mnExtLeading = (nCJKExtLeading > 0) ? nCJKExtLeading : 0; pMetric->mnAscent += nHalfTmpExtLeading; pMetric->mnDescent += nOtherHalfTmpExtLeading; } #endif } // ----------------------------------------------------------------------- ULONG AquaSalGraphics::GetKernPairs( ULONG nPairs, ImplKernPairData* pKernPairs ) { return 0; } // ----------------------------------------------------------------------- static bool AddTempFontDir( const char* pDir ) { FSRef aPathFSRef; Boolean bIsDirectory = true; OSStatus eStatus = FSPathMakeRef( reinterpret_cast(pDir), &aPathFSRef, &bIsDirectory ); if( eStatus != noErr ) return false; FSSpec aPathFSSpec; eStatus = ::FSGetCatalogInfo( &aPathFSRef, kFSCatInfoNone, NULL, NULL, &aPathFSSpec, NULL ); if( eStatus != noErr ) return false; // TODO: deactivate ATSFontContainerRef when closing app ATSFontContainerRef aATSFontContainer; const ATSFontContext eContext = kATSFontContextLocal; // TODO: *Global??? eStatus = ::ATSFontActivateFromFileSpecification( &aPathFSSpec, eContext, kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, &aATSFontContainer ); if( eStatus != noErr ) return false; return true; } static bool AddTempFontDir( void ) { static bool bFirst = true; if( !bFirst ) return false; bFirst = false; // add private font files found in brand and base layer rtl::OUString aBrandStr( RTL_CONSTASCII_USTRINGPARAM( "$BRAND_BASE_DIR" ) ); rtl_bootstrap_expandMacros( &aBrandStr.pData ); rtl::OUString aBrandSysPath; OSL_VERIFY( osl_getSystemPathFromFileURL( aBrandStr.pData, &aBrandSysPath.pData ) == osl_File_E_None ); rtl::OStringBuffer aBrandFontDir( aBrandSysPath.getLength()*2 ); aBrandFontDir.append( rtl::OUStringToOString( aBrandSysPath, RTL_TEXTENCODING_UTF8 ) ); aBrandFontDir.append( "/share/fonts/truetype/" ); bool bBrandSuccess = AddTempFontDir( aBrandFontDir.getStr() ); rtl::OUString aBaseStr( RTL_CONSTASCII_USTRINGPARAM( "$OOO_BASE_DIR" ) ); rtl_bootstrap_expandMacros( &aBaseStr.pData ); rtl::OUString aBaseSysPath; OSL_VERIFY( osl_getSystemPathFromFileURL( aBaseStr.pData, &aBaseSysPath.pData ) == osl_File_E_None ); rtl::OStringBuffer aBaseFontDir( aBaseSysPath.getLength()*2 ); aBaseFontDir.append( rtl::OUStringToOString( aBaseSysPath, RTL_TEXTENCODING_UTF8 ) ); aBaseFontDir.append( "/share/fonts/truetype/" ); bool bBaseSuccess = AddTempFontDir( aBaseFontDir.getStr() ); return bBrandSuccess && bBaseSuccess; } void AquaSalGraphics::GetDevFontList( ImplDevFontList* pFontList ) { DBG_ASSERT( pFontList, "AquaSalGraphics::GetDevFontList(NULL) !"); AddTempFontDir(); // The idea is to cache the list of system fonts once it has been generated. // SalData seems to be a good place for this caching. However we have to // carefully make the access to the font list thread-safe. If we register // a font-change event handler to update the font list in case fonts have // changed on the system we have to lock access to the list. The right // way to do that is the solar mutex since GetDevFontList is protected // through it as should be all event handlers SalData* pSalData = GetSalData(); if (pSalData->mpFontList == NULL) pSalData->mpFontList = new SystemFontList(); // Copy all ImplFontData objects contained in the SystemFontList pSalData->mpFontList->AnnounceFonts( *pFontList ); } // ----------------------------------------------------------------------- bool AquaSalGraphics::AddTempDevFont( ImplDevFontList* pFontList, const String& rFontFileURL, const String& rFontName ) { ::rtl::OUString aUSytemPath; OSL_VERIFY( !osl::FileBase::getSystemPathFromFileURL( rFontFileURL, aUSytemPath ) ); FSRef aNewRef; Boolean bIsDirectory = true; ::rtl::OString aCFileName = rtl::OUStringToOString( aUSytemPath, RTL_TEXTENCODING_UTF8 ); OSStatus eStatus = FSPathMakeRef( (UInt8*)aCFileName.getStr(), &aNewRef, &bIsDirectory ); if( eStatus != noErr ) return false; FSSpec aFontFSSpec; eStatus = ::FSGetCatalogInfo( &aNewRef, kFSCatInfoNone, NULL, NULL, &aFontFSSpec, NULL ); if( eStatus != noErr ) return false; ATSFontContainerRef oContainer; const ATSFontContext eContext = kATSFontContextLocal; // TODO: *Global??? eStatus = ::ATSFontActivateFromFileSpecification( &aFontFSSpec, eContext, kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, &oContainer ); if( eStatus != noErr ) return false; // TODO: ATSFontDeactivate( oContainer ) when fonts are no longer needed // TODO: register new ImplMacFontdata in pFontList return true; } // ----------------------------------------------------------------------- // callbacks from ATSUGlyphGetCubicPaths() fore GetGlyphOutline() struct GgoData { basegfx::B2DPolygon maPolygon; basegfx::B2DPolyPolygon* mpPolyPoly; }; static OSStatus GgoLineToProc( const Float32Point* pPoint, void* pData ) { basegfx::B2DPolygon& rPolygon = static_cast(pData)->maPolygon; const basegfx::B2DPoint aB2DPoint( pPoint->x, pPoint->y ); rPolygon.append( aB2DPoint ); return noErr; } static OSStatus GgoCurveToProc( const Float32Point* pCP1, const Float32Point* pCP2, const Float32Point* pPoint, void* pData ) { basegfx::B2DPolygon& rPolygon = static_cast(pData)->maPolygon; const sal_uInt32 nPointCount = rPolygon.count(); const basegfx::B2DPoint aB2DControlPoint1( pCP1->x, pCP1->y ); rPolygon.setNextControlPoint( nPointCount-1, aB2DControlPoint1 ); const basegfx::B2DPoint aB2DEndPoint( pPoint->x, pPoint->y ); rPolygon.append( aB2DEndPoint ); const basegfx::B2DPoint aB2DControlPoint2( pCP2->x, pCP2->y ); rPolygon.setPrevControlPoint( nPointCount, aB2DControlPoint2 ); return noErr; } static OSStatus GgoClosePathProc( void* pData ) { GgoData* pGgoData = static_cast(pData); basegfx::B2DPolygon& rPolygon = pGgoData->maPolygon; if( rPolygon.count() > 0 ) pGgoData->mpPolyPoly->append( rPolygon ); rPolygon.clear(); return noErr; } static OSStatus GgoMoveToProc( const Float32Point* pPoint, void* pData ) { GgoClosePathProc( pData ); OSStatus eStatus = GgoLineToProc( pPoint, pData ); return eStatus; } BOOL AquaSalGraphics::GetGlyphOutline( long nGlyphId, basegfx::B2DPolyPolygon& rPolyPoly ) { GgoData aGgoData; aGgoData.mpPolyPoly = &rPolyPoly; rPolyPoly.clear(); ATSUStyle rATSUStyle = maATSUStyle; // TODO: handle glyph fallback when CWS pdffix02 is integrated OSStatus eGgoStatus = noErr; OSStatus eStatus = ATSUGlyphGetCubicPaths( rATSUStyle, nGlyphId, GgoMoveToProc, GgoLineToProc, GgoCurveToProc, GgoClosePathProc, &aGgoData, &eGgoStatus ); if( (eStatus != noErr) ) // TODO: why is (eGgoStatus!=noErr) when curves are involved? return false; GgoClosePathProc( &aGgoData ); if( mfFontScale != 1.0 ) { basegfx::B2DHomMatrix aScale; aScale.scale( +mfFontScale, +mfFontScale ); rPolyPoly.transform( aScale ); } return true; } // ----------------------------------------------------------------------- long AquaSalGraphics::GetGraphicsWidth() const { if( mrContext && (mbWindow || mbVirDev) ) { return mnWidth; //CGBitmapContextGetWidth( mrContext ); } else return 0; } // ----------------------------------------------------------------------- BOOL AquaSalGraphics::GetGlyphBoundRect( long nIndex, Rectangle& ) { return sal_False; } // ----------------------------------------------------------------------- void AquaSalGraphics::GetDevFontSubstList( OutputDevice* ) { // nothing to do since there are no device-specific fonts on Aqua } // ----------------------------------------------------------------------- void AquaSalGraphics::DrawServerFontLayout( const ServerFontLayout& ) { } // ----------------------------------------------------------------------- USHORT AquaSalGraphics::SetFont( ImplFontSelectData* pReqFont, int nFallbackLevel ) { if( !pReqFont ) { ATSUClearStyle( maATSUStyle ); mpMacFontData = NULL; return 0; } // store the requested device font entry const ImplMacFontData* pMacFont = static_cast( pReqFont->mpFontData ); mpMacFontData = pMacFont; // convert pixel units (as seen by upper layers) to typographic point units double fScaledAtsHeight = pReqFont->mfExactHeight; // avoid Fixed16.16 overflows by limiting the ATS font size static const float fMaxAtsHeight = 144.0; if( fScaledAtsHeight <= fMaxAtsHeight ) mfFontScale = 1.0; else { mfFontScale = fScaledAtsHeight / fMaxAtsHeight; fScaledAtsHeight = fMaxAtsHeight; } Fixed fFixedSize = FloatToFixed( fScaledAtsHeight ); // enable bold-emulation if needed Boolean bFakeBold = FALSE; if( (pReqFont->GetWeight() >= WEIGHT_BOLD) && (pMacFont->GetWeight() < WEIGHT_SEMIBOLD) ) bFakeBold = TRUE; // enable italic-emulation if needed Boolean bFakeItalic = FALSE; if( ((pReqFont->GetSlant() == ITALIC_NORMAL) || (pReqFont->GetSlant() == ITALIC_OBLIQUE)) && !((pMacFont->GetSlant() == ITALIC_NORMAL) || (pMacFont->GetSlant() == ITALIC_OBLIQUE)) ) bFakeItalic = TRUE; // enable/disable antialiased text mbNonAntialiasedText = pReqFont->mbNonAntialiased; UInt32 nStyleRenderingOptions = kATSStyleNoOptions; if( pReqFont->mbNonAntialiased ) nStyleRenderingOptions |= kATSStyleNoAntiAliasing; // prepare ATS-fontid as type matching to the kATSUFontTag request ATSUFontID nFontID = static_cast(pMacFont->GetFontId()); // update ATSU style attributes with requested font parameters const ATSUAttributeTag aTag[] = { kATSUFontTag, kATSUSizeTag, kATSUQDBoldfaceTag, kATSUQDItalicTag, kATSUStyleRenderingOptionsTag }; const ByteCount aValueSize[] = { sizeof(ATSUFontID), sizeof(fFixedSize), sizeof(bFakeBold), sizeof(bFakeItalic), sizeof(nStyleRenderingOptions) }; const ATSUAttributeValuePtr aValue[] = { &nFontID, &fFixedSize, &bFakeBold, &bFakeItalic, &nStyleRenderingOptions }; OSStatus eStatus = ATSUSetAttributes( maATSUStyle, sizeof(aTag) / sizeof(ATSUAttributeTag), aTag, aValueSize, aValue ); DBG_ASSERT( (eStatus==noErr), "AquaSalGraphics::SetFont() : Could not set font attributes!\n"); // prepare font stretching const ATSUAttributeTag aMatrixTag = kATSUFontMatrixTag; if( (pReqFont->mnWidth == 0) || (pReqFont->mnWidth == pReqFont->mnHeight) ) { mfFontStretch = 1.0; ATSUClearAttributes( maATSUStyle, 1, &aMatrixTag ); } else { mfFontStretch = (float)pReqFont->mnWidth / pReqFont->mnHeight; CGAffineTransform aMatrix = CGAffineTransformMakeScale( mfFontStretch, 1.0F ); const ATSUAttributeValuePtr aAttr = &aMatrix; const ByteCount aMatrixBytes = sizeof(aMatrix); eStatus = ATSUSetAttributes( maATSUStyle, 1, &aMatrixTag, &aMatrixBytes, &aAttr ); DBG_ASSERT( (eStatus==noErr), "AquaSalGraphics::SetFont() : Could not set font matrix\n"); } // prepare font rotation mnATSUIRotation = FloatToFixed( pReqFont->mnOrientation / 10.0 ); #if OSL_DEBUG_LEVEL > 3 fprintf( stderr, "SetFont to (\"%s\", \"%s\", fontid=%d) for (\"%s\" \"%s\" weight=%d, slant=%d size=%dx%d orientation=%d)\n", ::rtl::OUStringToOString( pMacFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ).getStr(), ::rtl::OUStringToOString( pMacFont->GetStyleName(), RTL_TEXTENCODING_UTF8 ).getStr(), (int)nFontID, ::rtl::OUStringToOString( pReqFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ).getStr(), ::rtl::OUStringToOString( pReqFont->GetStyleName(), RTL_TEXTENCODING_UTF8 ).getStr(), pReqFont->GetWeight(), pReqFont->GetSlant(), pReqFont->mnHeight, pReqFont->mnWidth, pReqFont->mnOrientation); #endif return 0; } // ----------------------------------------------------------------------- ImplFontCharMap* AquaSalGraphics::GetImplFontCharMap() const { if( !mpMacFontData ) return ImplFontCharMap::GetDefaultMap(); return mpMacFontData->GetImplFontCharMap(); } // ----------------------------------------------------------------------- // fake a SFNT font directory entry for a font table // see http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6.html#Directory static void FakeDirEntry( FourCharCode eFCC, ByteCount nOfs, ByteCount nLen, const unsigned char* /*pData*/, unsigned char*& rpDest ) { // write entry tag rpDest[ 0] = (char)(eFCC >> 24); rpDest[ 1] = (char)(eFCC >> 16); rpDest[ 2] = (char)(eFCC >> 8); rpDest[ 3] = (char)(eFCC >> 0); // TODO: get entry checksum and write it // not too important since the subsetter doesn't care currently // for( pData+nOfs ... pData+nOfs+nLen ) // write entry offset rpDest[ 8] = (char)(nOfs >> 24); rpDest[ 9] = (char)(nOfs >> 16); rpDest[10] = (char)(nOfs >> 8); rpDest[11] = (char)(nOfs >> 0); // write entry length rpDest[12] = (char)(nLen >> 24); rpDest[13] = (char)(nLen >> 16); rpDest[14] = (char)(nLen >> 8); rpDest[15] = (char)(nLen >> 0); // advance to next entry rpDest += 16; } static bool GetRawFontData( const ImplFontData* pFontData, ByteVector& rBuffer ) { const ImplMacFontData* pMacFont = static_cast(pFontData); const ATSUFontID nFontId = static_cast(pMacFont->GetFontId()); ATSFontRef rFont = FMGetATSFontRefFromFont( nFontId ); // get font table availability and size in bytes ByteCount nHeadLen = 0; OSStatus eStatus = ATSFontGetTable( rFont, GetTag("head"), 0, 0, NULL, &nHeadLen); if( (eStatus != noErr) || (nHeadLen <= 0) ) return false; ByteCount nMaxpLen = 0; eStatus = ATSFontGetTable( rFont, GetTag("maxp"), 0, 0, NULL, &nMaxpLen); if( (eStatus != noErr) || (nMaxpLen <= 0) ) return false; ByteCount nCmapLen = 0; eStatus = ATSFontGetTable( rFont, GetTag("cmap"), 0, 0, NULL, &nCmapLen); if( (eStatus != noErr) || (nCmapLen <= 0) ) return false; ByteCount nLocaLen = 0; eStatus = ATSFontGetTable( rFont, GetTag("loca"), 0, 0, NULL, &nLocaLen); if( (eStatus != noErr) || (nLocaLen <= 0) ) return false; ByteCount nGlyfLen = 0; eStatus = ATSFontGetTable( rFont, GetTag("glyf"), 0, 0, NULL, &nGlyfLen); if( (eStatus != noErr) || (nGlyfLen <= 0) ) return false; ByteCount nNameLen = 0; eStatus = ATSFontGetTable( rFont, GetTag("name"), 0, 0, NULL, &nNameLen); if( (eStatus != noErr) || (nNameLen <= 0) ) return false; ByteCount nHheaLen = 0; eStatus = ATSFontGetTable( rFont, GetTag("hhea"), 0, 0, NULL, &nHheaLen); if( (eStatus != noErr) || (nHheaLen <= 0) ) return false; ByteCount nHmtxLen = 0; eStatus = ATSFontGetTable( rFont, GetTag("hmtx"), 0, 0, NULL, &nHmtxLen); if( (eStatus != noErr) || (nHmtxLen <= 0) ) return false; ByteCount nPrepLen=0, nCvtLen=0, nFpgmLen=0; if( 1 ) // TODO: reduce PDF size by making hint subsetting optional { eStatus = ATSFontGetTable( rFont, GetTag("prep"), 0, 0, NULL, &nPrepLen); eStatus = ATSFontGetTable( rFont, GetTag("cvt "), 0, 0, NULL, &nCvtLen); eStatus = ATSFontGetTable( rFont, GetTag("fpgm"), 0, 0, NULL, &nFpgmLen); } // prepare a byte buffer for a fake font int nTableCount = 8; nTableCount += (nPrepLen>0) + (nCvtLen>0) + (nFpgmLen>0); const ByteCount nFdirLen = 12 + 16*nTableCount; ByteCount nTotalLen = nFdirLen; nTotalLen += nHeadLen + nMaxpLen + nNameLen + nCmapLen + nLocaLen + nGlyfLen; nTotalLen += nHheaLen + nHmtxLen; nTotalLen += nPrepLen + nCvtLen + nFpgmLen; rBuffer.resize( nTotalLen ); // fake a SFNT font directory header if( nTableCount < 16 ) { int nLog2 = 0; while( (nTableCount >> nLog2) > 1 ) ++nLog2; rBuffer[ 1] = 1; // Win-TTF style scaler rBuffer[ 5] = nTableCount; // table count rBuffer[ 7] = nLog2*16; // searchRange rBuffer[ 9] = nLog2; // entrySelector rBuffer[11] = (nTableCount-nLog2)*16; // rangeShift } // get font table raw data and update the fake directory entries ByteCount nOfs = nFdirLen; unsigned char* pFakeEntry = &rBuffer[12]; eStatus = ATSFontGetTable( rFont, GetTag("cmap"), 0, nCmapLen, (void*)&rBuffer[nOfs], &nCmapLen); FakeDirEntry( GetTag("cmap"), nOfs, nCmapLen, &rBuffer[0], pFakeEntry ); nOfs += nCmapLen; if( nCvtLen ) { eStatus = ATSFontGetTable( rFont, GetTag("cvt "), 0, nCvtLen, (void*)&rBuffer[nOfs], &nCvtLen); FakeDirEntry( GetTag("cvt "), nOfs, nCvtLen, &rBuffer[0], pFakeEntry ); nOfs += nCvtLen; } if( nFpgmLen ) { eStatus = ATSFontGetTable( rFont, GetTag("fpgm"), 0, nFpgmLen, (void*)&rBuffer[nOfs], &nFpgmLen); FakeDirEntry( GetTag("fpgm"), nOfs, nFpgmLen, &rBuffer[0], pFakeEntry ); nOfs += nFpgmLen; } eStatus = ATSFontGetTable( rFont, GetTag("glyf"), 0, nGlyfLen, (void*)&rBuffer[nOfs], &nGlyfLen); FakeDirEntry( GetTag("glyf"), nOfs, nGlyfLen, &rBuffer[0], pFakeEntry ); nOfs += nGlyfLen; eStatus = ATSFontGetTable( rFont, GetTag("head"), 0, nHeadLen, (void*)&rBuffer[nOfs], &nHeadLen); FakeDirEntry( GetTag("head"), nOfs, nHeadLen, &rBuffer[0], pFakeEntry ); nOfs += nHeadLen; eStatus = ATSFontGetTable( rFont, GetTag("hhea"), 0, nHheaLen, (void*)&rBuffer[nOfs], &nHheaLen); FakeDirEntry( GetTag("hhea"), nOfs, nHheaLen, &rBuffer[0], pFakeEntry ); nOfs += nHheaLen; eStatus = ATSFontGetTable( rFont, GetTag("hmtx"), 0, nHmtxLen, (void*)&rBuffer[nOfs], &nHmtxLen); FakeDirEntry( GetTag("hmtx"), nOfs, nHmtxLen, &rBuffer[0], pFakeEntry ); nOfs += nHmtxLen; eStatus = ATSFontGetTable( rFont, GetTag("loca"), 0, nLocaLen, (void*)&rBuffer[nOfs], &nLocaLen); FakeDirEntry( GetTag("loca"), nOfs, nLocaLen, &rBuffer[0], pFakeEntry ); nOfs += nLocaLen; eStatus = ATSFontGetTable( rFont, GetTag("maxp"), 0, nMaxpLen, (void*)&rBuffer[nOfs], &nMaxpLen); FakeDirEntry( GetTag("maxp"), nOfs, nMaxpLen, &rBuffer[0], pFakeEntry ); nOfs += nMaxpLen; eStatus = ATSFontGetTable( rFont, GetTag("name"), 0, nNameLen, (void*)&rBuffer[nOfs], &nNameLen); FakeDirEntry( GetTag("name"), nOfs, nNameLen, &rBuffer[0], pFakeEntry ); nOfs += nNameLen; if( nPrepLen ) { eStatus = ATSFontGetTable( rFont, GetTag("prep"), 0, nPrepLen, (void*)&rBuffer[nOfs], &nPrepLen); FakeDirEntry( GetTag("prep"), nOfs, nPrepLen, &rBuffer[0], pFakeEntry ); nOfs += nPrepLen; } DBG_ASSERT( (nOfs==nTotalLen), "AquaSalGraphics::CreateFontSubset (nOfs!=nTotalLen)"); return true; } BOOL AquaSalGraphics::CreateFontSubset( const rtl::OUString& rToFile, const ImplFontData* pFontData, long* pGlyphIDs, sal_uInt8* pEncoding, sal_Int32* pGlyphWidths, int nGlyphCount, FontSubsetInfo& rInfo ) { ByteVector aBuffer; if( !GetRawFontData( pFontData, aBuffer ) ) return sal_False; // TODO: modernize psprint's horrible fontsubset C-API // this probably only makes sense after the switch to another SCM // that can preserve change history after file renames // prepare data for psprint's font subsetter TrueTypeFont* pSftFont = NULL; int nRC = ::OpenTTFontBuffer( (void*)&aBuffer[0], aBuffer.size(), 0, &pSftFont); if( nRC != SF_OK ) return sal_False; // get details about the subsetted font TTGlobalFontInfo aTTInfo; ::GetTTGlobalFontInfo( pSftFont, &aTTInfo ); rInfo.m_nFontType = SAL_FONTSUBSETINFO_TYPE_TRUETYPE; rInfo.m_aPSName = String( aTTInfo.psname, RTL_TEXTENCODING_UTF8 ); rInfo.m_aFontBBox = Rectangle( Point( aTTInfo.xMin, aTTInfo.yMin ), Point( aTTInfo.xMax, aTTInfo.yMax ) ); rInfo.m_nCapHeight = aTTInfo.yMax; // Well ... rInfo.m_nAscent = +aTTInfo.winAscent; rInfo.m_nDescent = -aTTInfo.winDescent; // mac fonts usually do not have an OS2-table // => get valid ascent/descent values from other tables if( !rInfo.m_nAscent ) rInfo.m_nAscent = +aTTInfo.typoAscender; if( !rInfo.m_nAscent ) rInfo.m_nAscent = +aTTInfo.ascender; if( !rInfo.m_nDescent ) rInfo.m_nDescent = +aTTInfo.typoDescender; if( !rInfo.m_nDescent ) rInfo.m_nDescent = -aTTInfo.descender; // subset glyphs and get their properties // take care that subset fonts require the NotDef glyph in pos 0 int nOrigCount = nGlyphCount; USHORT aShortIDs[ 256 ]; sal_uInt8 aTempEncs[ 256 ]; int nNotDef = -1; for( int i = 0; i < nGlyphCount; ++i ) { aTempEncs[i] = pEncoding[i]; sal_uInt32 nGlyphIdx = pGlyphIDs[i] & GF_IDXMASK; if( pGlyphIDs[i] & GF_ISCHAR ) { bool bVertical = (pGlyphIDs[i] & GF_ROTMASK) != 0; nGlyphIdx = ::MapChar( pSftFont, static_cast(nGlyphIdx), bVertical ); if( nGlyphIdx == 0 && pFontData->IsSymbolFont() ) { // #i12824# emulate symbol aliasing U+FXXX <-> U+0XXX nGlyphIdx = pGlyphIDs[i] & GF_IDXMASK; nGlyphIdx = (nGlyphIdx & 0xF000) ? (nGlyphIdx & 0x00FF) : (nGlyphIdx | 0xF000 ); nGlyphIdx = ::MapChar( pSftFont, static_cast(nGlyphIdx), bVertical ); } } aShortIDs[i] = static_cast( nGlyphIdx ); if( !nGlyphIdx ) if( nNotDef < 0 ) nNotDef = i; // first NotDef glyph found } if( nNotDef != 0 ) { // add fake NotDef glyph if needed if( nNotDef < 0 ) nNotDef = nGlyphCount++; // NotDef glyph must be in pos 0 => swap glyphids aShortIDs[ nNotDef ] = aShortIDs[0]; aTempEncs[ nNotDef ] = aTempEncs[0]; aShortIDs[0] = 0; aTempEncs[0] = 0; } DBG_ASSERT( nGlyphCount < 257, "too many glyphs for subsetting" ); // TODO: where to get bVertical? const bool bVertical = false; // fill the pGlyphWidths array // while making sure that the NotDef glyph is at index==0 TTSimpleGlyphMetrics* pGlyphMetrics = ::GetTTSimpleGlyphMetrics( pSftFont, aShortIDs, nGlyphCount, bVertical ); if( !pGlyphMetrics ) return FALSE; sal_uInt16 nNotDefAdv = pGlyphMetrics[0].adv; pGlyphMetrics[0].adv = pGlyphMetrics[nNotDef].adv; pGlyphMetrics[nNotDef].adv = nNotDefAdv; for( int i = 0; i < nOrigCount; ++i ) pGlyphWidths[i] = pGlyphMetrics[i].adv; free( pGlyphMetrics ); // write subset into destination file rtl::OUString aSysPath; if( osl_File_E_None != osl_getSystemPathFromFileURL( rToFile.pData, &aSysPath.pData ) ) return FALSE; rtl_TextEncoding aThreadEncoding = osl_getThreadTextEncoding(); ByteString aToFile( rtl::OUStringToOString( aSysPath, aThreadEncoding ) ); nRC = ::CreateTTFromTTGlyphs( pSftFont, aToFile.GetBuffer(), aShortIDs, aTempEncs, nGlyphCount, 0, NULL, 0 ); ::CloseTTFont(pSftFont); return (nRC == SF_OK); } // ----------------------------------------------------------------------- void AquaSalGraphics::GetGlyphWidths( const ImplFontData* pFontData, bool bVertical, Int32Vector& rGlyphWidths, Ucs2UIntMap& rUnicodeEnc ) { rGlyphWidths.clear(); rUnicodeEnc.clear(); if( pFontData->IsSubsettable() ) { ByteVector aBuffer; if( !GetRawFontData( pFontData, aBuffer ) ) return; // TODO: modernize psprint's horrible fontsubset C-API // this probably only makes sense after the switch to another SCM // that can preserve change history after file renames // use the font subsetter to get the widths TrueTypeFont* pSftFont = NULL; int nRC = ::OpenTTFontBuffer( (void*)&aBuffer[0], aBuffer.size(), 0, &pSftFont); if( nRC != SF_OK ) return; const int nGlyphCount = ::GetTTGlyphCount( pSftFont ); if( nGlyphCount > 0 ) { // get glyph metrics rGlyphWidths.resize(nGlyphCount); std::vector aGlyphIds(nGlyphCount); for( int i = 0; i < nGlyphCount; i++ ) aGlyphIds[i] = static_cast(i); const TTSimpleGlyphMetrics* pGlyphMetrics = ::GetTTSimpleGlyphMetrics( pSftFont, &aGlyphIds[0], nGlyphCount, bVertical ); if( pGlyphMetrics ) { for( int i = 0; i < nGlyphCount; ++i ) rGlyphWidths[i] = pGlyphMetrics[i].adv; free( (void*)pGlyphMetrics ); } const ImplFontCharMap* pMap = mpMacFontData->GetImplFontCharMap(); DBG_ASSERT( pMap && pMap->GetCharCount(), "no charmap" ); // get unicode<->glyph encoding int nCharCount = pMap->GetCharCount(); sal_uInt32 nChar = pMap->GetFirstChar(); for(; --nCharCount >= 0; nChar = pMap->GetNextChar( nChar ) ) { if( nChar > 0xFFFF ) // TODO: allow UTF-32 chars break; sal_Ucs nUcsChar = static_cast(nChar); sal_uInt32 nGlyph = ::MapChar( pSftFont, nUcsChar, bVertical ); if( nGlyph > 0 ) rUnicodeEnc[ nUcsChar ] = nGlyph; } } ::CloseTTFont( pSftFont ); } else if( pFontData->IsEmbeddable() ) { // get individual character widths #if 0 // FIXME rWidths.reserve( 224 ); for( sal_Unicode i = 32; i < 256; ++i ) { int nCharWidth = 0; if( ::GetCharWidth32W( mhDC, i, i, &nCharWidth ) ) { rUnicodeEnc[ i ] = rWidths.size(); rWidths.push_back( nCharWidth ); } } #else DBG_ERROR("not implemented for non-subsettable fonts!\n"); #endif } } // ----------------------------------------------------------------------- const Ucs2SIntMap* AquaSalGraphics::GetFontEncodingVector( const ImplFontData* pFontData, const Ucs2OStrMap** ppNonEncoded ) { return NULL; } // ----------------------------------------------------------------------- const void* AquaSalGraphics::GetEmbedFontData( const ImplFontData* pFontData, const sal_Ucs* pUnicodes, sal_Int32* pWidths, FontSubsetInfo& rInfo, long* pDataLen ) { // TODO: are the non-subsettable fonts on OSX that are embeddable? return NULL; } // ----------------------------------------------------------------------- void AquaSalGraphics::FreeEmbedFontData( const void* pData, long nDataLen ) { // TODO: implementing this only makes sense when the implementation of // AquaSalGraphics::GetEmbedFontData() returns non-NULL DBG_ASSERT( (pData!=NULL), "AquaSalGraphics::FreeEmbedFontData() is not implemented\n"); } // ----------------------------------------------------------------------- void AquaSalGraphics::SetXORMode( BOOL bSet ) { // return early if XOR mode doesn't/can't change if( mbPrinter ) return; if( (mpXorEmulation == NULL) && !bSet ) return; if( (mpXorEmulation != NULL) && (bSet == mpXorEmulation->IsEnabled()) ) return; if( !CheckContext() ) return; // prepare XOR emulation if( !mpXorEmulation ) mpXorEmulation = new XorEmulation( mnWidth, mnHeight, mnBitmapDepth ); // change the XOR mode if( bSet ) mrContext = mpXorEmulation->Enable( mrContext, mxLayer ); else { mpXorEmulation->UpdateTarget(); mrContext = mpXorEmulation->Disable(); } } // ----------------------------------------------------------------------- // apply the XOR mask to the target context if active and dirty void AquaSalGraphics::ApplyXorContext() { if( !mpXorEmulation ) return; if( mpXorEmulation->UpdateTarget() ) ; // TODO: RefreshRect } // ====================================================================== XorEmulation::XorEmulation( int nWidth, int nHeight, int nTargetDepth ) : mxTargetLayer( NULL ) , mxTargetContext( NULL ) , mxMaskContext( NULL ) , mxTempContext( NULL ) , mpMaskBuffer( NULL ) , mpTempBuffer( NULL ) , mnBufferSize( 0 ) { // prepare creation of matching CGBitmaps CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace; CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst; // TODO: use a 16-bit XorMask on 16-bit target graphics int nBitDepth = nTargetDepth; if( !nBitDepth ) nBitDepth = 32; int nBytesPerRow = (nBitDepth == 16) ? 2 : 4; const size_t nBitsPerComponent = (nBitDepth == 16) ? 5 : 8; if( nBitDepth <= 8 ) { aCGColorSpace = GetSalData()->mxGraySpace; aCGBmpInfo = kCGImageAlphaNone; nBytesPerRow = 1; } nBytesPerRow *= nWidth; mnBufferSize = (nHeight * nBytesPerRow + sizeof(long)-1) / sizeof(long); // create a XorMask context mpMaskBuffer = new long[ mnBufferSize ]; mxMaskContext = ::CGBitmapContextCreate( mpMaskBuffer, nWidth, nHeight, nBitsPerComponent, nBytesPerRow, aCGColorSpace, aCGBmpInfo ); // a bitmap context will be needed for manual XORing // create one unless the target context is a bitmap context if( !nTargetDepth ) { // create a bitmap context matching to the target context mpTempBuffer = new long[ mnBufferSize ]; mxTempContext = ::CGBitmapContextCreate( mpTempBuffer, nWidth, nHeight, nBitsPerComponent, nBytesPerRow, aCGColorSpace, aCGBmpInfo ); } // initialize XOR mask context for drawing CGContextSetFillColorSpace( mxMaskContext, aCGColorSpace ); CGContextSetStrokeColorSpace( mxMaskContext, aCGColorSpace ); CGContextSetShouldAntialias( mxMaskContext, false ); // improve the XorMask's XOR emulation a litte // NOTE: currently only enabled for monochrome contexts if( aCGColorSpace == GetSalData()->mxGraySpace ) CGContextSetBlendMode( mxMaskContext, kCGBlendModeDifference ); // initialize the default XorMask graphics state CGContextSaveGState( mxMaskContext ); } // ---------------------------------------------------------------------- XorEmulation::~XorEmulation() { Disable(); CGContextRelease( mxMaskContext ); if( mxTempContext != mxTargetContext ) CGContextRelease( mxTempContext ); delete[] mpMaskBuffer; delete[] mpTempBuffer; } // ---------------------------------------------------------------------- CGContextRef XorEmulation::Enable( CGContextRef xTargetContext, CGLayerRef xTargetLayer ) { #if 0 // intialize the transformation matrix to the drawing target const CGAffineTransform aCTM = CGContextGetCTM( xTargetContext ); CGContextConcatCTM( mxMaskContext, aCTM ); #endif // retarget drawing operations to the XOR mask mxTargetLayer = xTargetLayer; mxTargetContext = xTargetContext; // get the data pointer for bitmap targets if( !mxTempContext ) mpTempBuffer = (long*)CGBitmapContextGetData( mxTargetContext ); // reset the XOR mask to black memset( mpMaskBuffer, sizeof(long)* mnBufferSize, 0 ); return mxMaskContext; } // ---------------------------------------------------------------------- CGContextRef XorEmulation::Disable() { if( !mxTempContext ) mpTempBuffer = NULL; // retarget drawing operations to the XOR mask CGContextRef xOrigContext = mxTargetContext; mxTargetContext = NULL; mxTargetLayer = NULL; return xOrigContext; } // ---------------------------------------------------------------------- bool XorEmulation::UpdateTarget() { if( !IsEnabled() ) return false; // update the temp bitmap buffer if needed if( mxTempContext ) CGContextDrawLayerAtPoint( mxTempContext, CGPointZero, mxTargetLayer ); // do a manual XOR with the XorMask // this approach suffices for simple color manipulations // and also the complex-clipping-XOR-trick used in metafiles const long* pSrc = mpMaskBuffer; long* pDst = mpTempBuffer; for( int i = mnBufferSize; --i >= 0; ++pSrc, ++pDst ) *pDst ^= *pSrc; // write back the XOR results to the target context if( mxTempContext ) { CGImageRef xXorImage = CGBitmapContextCreateImage( mxTempContext ); const int nWidth = (int)CGImageGetWidth( xXorImage ); const int nHeight = (int)CGImageGetHeight( xXorImage ); const CGRect aFullRect = {{0,0},{nWidth,nHeight}}; CGContextDrawImage( mxTargetContext, aFullRect, xXorImage ); CGImageRelease( xXorImage ); } // reset the XorMask to black again // TODO: not needed for last update memset( mpMaskBuffer, mnBufferSize * sizeof(long), 0 ); // TODO: return FALSE if target was not changed return true; } // =======================================================================