/************************************************************** * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_vcl.hxx" #include #include #include #include #include "salgdi.h" #include "atsfonts.hxx" #include "vcl/svapp.hxx" #include "vcl/impfont.hxx" #include "basegfx/polygon/b2dpolygon.hxx" #include "basegfx/matrix/b2dhommatrix.hxx" typedef GlyphID ATSGlyphID; // ======================================================================= // mac specific physically available font face class AtsFontData : public ImplMacFontData { public: explicit AtsFontData( const ImplDevFontAttributes&, ATSUFontID ); virtual ~AtsFontData( void ); virtual ImplFontData* Clone( void ) const; virtual ImplMacTextStyle* CreateMacTextStyle( const ImplFontSelectData& ) const; virtual ImplFontEntry* CreateFontInstance( /*const*/ ImplFontSelectData& ) const; virtual int GetFontTable( const char pTagName[5], unsigned char* ) const; }; // ======================================================================= class AtsFontList : public SystemFontList { public: explicit AtsFontList( void ); virtual ~AtsFontList( void ); virtual void AnnounceFonts( ImplDevFontList& ) const; virtual ImplMacFontData* GetFontDataFromId( sal_IntPtr nFontId ) const; private: typedef std::hash_map AtsFontContainer; AtsFontContainer maFontContainer; void InitGlyphFallbacks( void ); ATSUFontFallbacks maFontFallbacks; }; // ======================================================================= AtsFontData::AtsFontData( const ImplDevFontAttributes& rDFA, ATSUFontID nFontId ) : ImplMacFontData( rDFA, (sal_IntPtr)nFontId ) {} // ----------------------------------------------------------------------- AtsFontData::~AtsFontData( void ) {} // ----------------------------------------------------------------------- ImplFontData* AtsFontData::Clone( void ) const { AtsFontData* pClone = new AtsFontData(*this); return pClone; } // ----------------------------------------------------------------------- ImplMacTextStyle* AtsFontData::CreateMacTextStyle( const ImplFontSelectData& rFSD ) const { return new AtsTextStyle( rFSD ); } // ----------------------------------------------------------------------- ImplFontEntry* AtsFontData::CreateFontInstance( /*const*/ ImplFontSelectData& rFSD ) const { return new ImplFontEntry( rFSD ); } // ----------------------------------------------------------------------- int AtsFontData::GetFontTable( const char pTagName[5], unsigned char* pResultBuf ) const { DBG_ASSERT( aTagName[4]=='\0', "AtsFontData::GetFontTable with invalid tagname!\n" ); const FourCharCode pTagCode = (pTagName[0]<<24) + (pTagName[1]<<16) + (pTagName[2]<<8) + (pTagName[3]<<0); // get the byte size of the raw table ATSFontRef rATSFont = FMGetATSFontRefFromFont( (ATSUFontID)mnFontId ); ByteCount nBufSize = 0; OSStatus eStatus = ATSFontGetTable( rATSFont, pTagCode, 0, 0, NULL, &nBufSize ); if( eStatus != noErr ) return 0; // get the raw table data if requested if( pResultBuf && (nBufSize > 0)) { ByteCount nRawLength = 0; eStatus = ATSFontGetTable( rATSFont, pTagCode, 0, nBufSize, (void*)pResultBuf, &nRawLength ); if( eStatus != noErr ) return 0; DBG_ASSERT( (nBufSize==nRawLength), "AtsFontData::GetFontTable ByteCount mismatch!\n"); } return nBufSize; } // ======================================================================= AtsTextStyle::AtsTextStyle( const ImplFontSelectData& rFSD ) : ImplMacTextStyle( rFSD ) { // create the style object for ATSUI font attributes ATSUCreateStyle( &maATSUStyle ); const ImplFontSelectData* const pReqFont = &rFSD; mpFontData = (AtsFontData*)rFSD.mpFontData; // limit the ATS font size to avoid Fixed16.16 overflows double fScaledFontHeight = pReqFont->mfExactHeight; static const float fMaxFontHeight = 144.0; if( fScaledFontHeight > fMaxFontHeight ) { mfFontScale = fScaledFontHeight / fMaxFontHeight; fScaledFontHeight = fMaxFontHeight; } // convert font rotation to radian mfFontRotation = pReqFont->mnOrientation * (M_PI / 1800.0); // determine if font stretching is needed if( (pReqFont->mnWidth != 0) && (pReqFont->mnWidth != pReqFont->mnHeight) ) { mfFontStretch = (float)pReqFont->mnWidth / pReqFont->mnHeight; // set text style to stretching matrix CGAffineTransform aMatrix = CGAffineTransformMakeScale( mfFontStretch, 1.0F ); const ATSUAttributeTag aMatrixTag = kATSUFontMatrixTag; const ATSUAttributeValuePtr aAttr = &aMatrix; const ByteCount aMatrixBytes = sizeof(aMatrix); /*OSStatus eStatus =*/ ATSUSetAttributes( maATSUStyle, 1, &aMatrixTag, &aMatrixBytes, &aAttr ); } } // ----------------------------------------------------------------------- AtsTextStyle::~AtsTextStyle( void ) { ATSUDisposeStyle( maATSUStyle ); } // ----------------------------------------------------------------------- void AtsTextStyle::GetFontMetric( float fDPIY, ImplFontMetricData& rMetric ) const { // get the font metrics (in point units) // of the font that has eventually been size-limited // get the matching ATSU font handle 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 rMetric.mbScalableFont = true; // TODO: check if any kerning is possible rMetric.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 * fDPIY * fPointSize); rMetric.mnAscent = static_cast(+aMetrics.ascent * fPixelSize + 0.5); rMetric.mnDescent = static_cast(-aMetrics.descent * fPixelSize + 0.5); const long nExtDescent = static_cast((-aMetrics.descent + aMetrics.leading) * fPixelSize + 0.5); rMetric.mnExtLeading = nExtDescent - rMetric.mnDescent; rMetric.mnIntLeading = 0; // 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 rMetric.mnWidth = static_cast(mfFontStretch * fPixelSize + 0.5); } // ----------------------------------------------------------------------- void AtsTextStyle::SetTextColor( const RGBAColor& rColor ) { RGBColor aAtsColor; aAtsColor.red = (unsigned short)( rColor.GetRed() * 65535.0 ); aAtsColor.green = (unsigned short)( rColor.GetGreen() * 65535.0 ); aAtsColor.blue = (unsigned short)( rColor.GetColor() * 65535.0 ); ATSUAttributeTag aTag = kATSUColorTag; ByteCount aValueSize = sizeof( aAtsColor ); ATSUAttributeValuePtr aValue = &aAtsColor; /*OSStatus err =*/ ATSUSetAttributes( maATSUStyle, 1, &aTag, &aValueSize, &aValue ); } // ----------------------------------------------------------------------- bool AtsTextStyle::GetGlyphBoundRect( sal_GlyphId aGlyphId, Rectangle& rRect ) const { ATSUStyle rATSUStyle = maATSUStyle; // TODO: handle glyph fallback ATSGlyphID aGlyphId = aGlyphId; ATSGlyphScreenMetrics aGlyphMetrics; const bool bNonAntialiasedText = false; OSStatus eStatus = ATSUGlyphGetScreenMetrics( rATSUStyle, 1, &aGlyphId, 0, FALSE, !bNonAntialiasedText, &aGlyphMetrics ); if( eStatus != noErr ) return false; const long nMinX = (long)(+aGlyphMetrics.topLeft.x * mfFontScale - 0.5); const long nMaxX = (long)(aGlyphMetrics.width * mfFontScale + 0.5) + nMinX; const long nMinY = (long)(-aGlyphMetrics.topLeft.y * mfFontScale - 0.5); const long nMaxY = (long)(aGlyphMetrics.height * mfFontScale + 0.5) + nMinY; rRect = Rectangle( nMinX, nMinY, nMaxX, nMaxY ); 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 AtsTextStyle::GetGlyphOutline( sal_GlyphId aGlyphId, basegfx::B2DPolyPolygon& rResult ) const { GgoData aGgoData; aGgoData.mpPolyPoly = &rResult; rResult.clear(); OSStatus eGgoStatus = noErr; OSStatus eStatus = ATSUGlyphGetCubicPaths( maATSUStyle, aGlyphId, GgoMoveToProc, GgoLineToProc, GgoCurveToProc, GgoClosePathProc, &aGgoData, &eGgoStatus ); if( (eStatus != noErr) ) // TODO: why is (eGgoStatus!=noErr) when curves are involved? return false; GgoClosePathProc( &aGgoData ); // apply the font scale if( mfFontScale != 1.0 ) { basegfx::B2DHomMatrix aScale; aScale.scale( +mfFontScale, +mfFontScale ); rResult.transform( aScale ); } return true; } // ======================================================================= static bool GetDevFontAttributes( ATSUFontID nFontID, ImplDevFontAttributes& rDFA ) { // all ATSU fonts are device fonts that can be directly rotated rDFA.mbOrientation = true; rDFA.mbDevice = true; rDFA.mnQuality = 0; // reset the attributes rDFA.meFamily = FAMILY_DONTKNOW; rDFA.mePitch = PITCH_VARIABLE; rDFA.meWidthType = WIDTH_NORMAL; rDFA.meWeight = WEIGHT_NORMAL; rDFA.meItalic = ITALIC_NONE; rDFA.mbSymbolFlag = false; // ignore bitmap fonts ATSFontRef rATSFontRef = FMGetATSFontRefFromFont( nFontID ); ByteCount nHeadLen = 0; OSStatus rc = ATSFontGetTable( rATSFontRef, 0x68656164/*head*/, 0, 0, NULL, &nHeadLen ); if( (rc != noErr) || (nHeadLen <= 0) ) return false; // all scalable fonts on this platform are subsettable rDFA.mbSubsettable = true; rDFA.mbEmbeddable = false; // TODO: these members are needed only for our X11 platform targets rDFA.meAntiAlias = ANTIALIAS_DONTKNOW; rDFA.meEmbeddedBitmap = EMBEDDEDBITMAP_DONTKNOW; // prepare iterating over all name strings of the font ItemCount nFontNameCount = 0; rc = ATSUCountFontNames( nFontID, &nFontNameCount ); if( rc != noErr ) return false; int nBestNameValue = 0; int nBestStyleValue = 0; FontLanguageCode eBestLangCode = 0; const FontLanguageCode eUILangCode = Application::GetSettings().GetUILanguage(); typedef std::vector NameBuffer; NameBuffer aNameBuffer( 256 ); // iterate over all available name strings of the font for( ItemCount nNameIndex = 0; nNameIndex < nFontNameCount; ++nNameIndex ) { ByteCount nNameLength = 0; FontNameCode eFontNameCode; FontPlatformCode eFontNamePlatform; FontScriptCode eFontNameScript; FontLanguageCode eFontNameLanguage; rc = ATSUGetIndFontName( nFontID, nNameIndex, 0, NULL, &nNameLength, &eFontNameCode, &eFontNamePlatform, &eFontNameScript, &eFontNameLanguage ); if( rc != noErr ) continue; // ignore non-interesting name entries if( (eFontNameCode != kFontFamilyName) && (eFontNameCode != kFontStyleName) && (eFontNameCode != kFontPostscriptName) ) continue; // heuristic to find the most common font name // prefering default language names or even better the names matching to the UI language int nNameValue = (eFontNameLanguage==eUILangCode) ? 0 : ((eFontNameLanguage==0) ? -10 : -20); rtl_TextEncoding eEncoding = RTL_TEXTENCODING_UNICODE; const int nPlatformEncoding = ((int)eFontNamePlatform << 8) + (int)eFontNameScript; switch( nPlatformEncoding ) { case 0x000: nNameValue += 23; break; // Unicode 1.0 case 0x001: nNameValue += 24; break; // Unicode 1.1 case 0x002: nNameValue += 25; break; // iso10646_1993 case 0x003: nNameValue += 26; break; // UCS-2 case 0x301: nNameValue += 27; break; // Win UCS-2 case 0x004: // UCS-4 case 0x30A: nNameValue += 0; // Win-UCS-4 eEncoding = RTL_TEXTENCODING_UCS4; break; case 0x100: nNameValue += 21; // Mac Roman eEncoding = RTL_TEXTENCODING_APPLE_ROMAN; break; case 0x300: nNameValue = 0; // Win Symbol encoded name! rDFA.mbSymbolFlag = true; // (often seen for symbol fonts) break; default: nNameValue = 0; // ignore other encodings break; } // ignore name entries with no useful encoding if( nNameValue <= 0 ) continue; if( nNameLength >= aNameBuffer.size() ) continue; // get the encoded name aNameBuffer.reserve( nNameLength+1 ); // extra byte helps for debugging rc = ATSUGetIndFontName( nFontID, nNameIndex, nNameLength, &aNameBuffer[0], &nNameLength, &eFontNameCode, &eFontNamePlatform, &eFontNameScript, &eFontNameLanguage ); if( rc != noErr ) continue; // convert to unicode name UniString aUtf16Name; if( eEncoding == RTL_TEXTENCODING_UNICODE ) // we are just interested in UTF16 encoded names aUtf16Name = UniString( (const sal_Unicode*)&aNameBuffer[0], nNameLength/2 ); else if( eEncoding == RTL_TEXTENCODING_UCS4 ) aUtf16Name = UniString(); // TODO else // assume the non-unicode encoded names are byte encoded aUtf16Name = UniString( &aNameBuffer[0], nNameLength, eEncoding ); // ignore empty strings if( aUtf16Name.Len() <= 0 ) continue; // handle the name depending on its namecode switch( eFontNameCode ) { case kFontFamilyName: // ignore font names starting with '.' if( aUtf16Name.GetChar(0) == '.' ) nNameValue = 0; else if( rDFA.maName.Len() ) { // even if a family name is not the one we are looking for // it is still useful as a font name alternative if( rDFA.maMapNames.Len() ) rDFA.maMapNames += ';'; rDFA.maMapNames += (nBestNameValue < nNameValue) ? rDFA.maName : aUtf16Name; } if( nBestNameValue < nNameValue ) { // get the best family name nBestNameValue = nNameValue; eBestLangCode = eFontNameLanguage; rDFA.maName = aUtf16Name; } break; case kFontStyleName: // get a style name matching to the family name if( nBestStyleValue < nNameValue ) { nBestStyleValue = nNameValue; rDFA.maStyleName = aUtf16Name; } break; case kFontPostscriptName: // use the postscript name to get some useful info UpdateAttributesFromPSName( aUtf16Name, rDFA ); break; default: // TODO: use other name entries too? break; } } bool bRet = (rDFA.maName.Len() > 0); return bRet; } // ======================================================================= SystemFontList* GetAtsFontList( void ) { return new AtsFontList(); } // ======================================================================= AtsFontList::AtsFontList() { // count available system fonts ItemCount nATSUICompatibleFontsAvailable = 0; if( ATSUFontCount(&nATSUICompatibleFontsAvailable) != noErr ) return; if( nATSUICompatibleFontsAvailable <= 0 ) return; // enumerate available system fonts typedef std::vector AtsFontIDVector; AtsFontIDVector aFontIDVector( nATSUICompatibleFontsAvailable ); ItemCount nFontItemsCount = 0; if( ATSUGetFontIDs( &aFontIDVector[0], aFontIDVector.capacity(), &nFontItemsCount ) != noErr ) return; BOOST_ASSERT(nATSUICompatibleFontsAvailable == nFontItemsCount && "Strange I would expect them to be equal"); // prepare use of the available fonts AtsFontIDVector::const_iterator it = aFontIDVector.begin(); for(; it != aFontIDVector.end(); ++it ) { const ATSUFontID nFontID = *it; ImplDevFontAttributes aDevFontAttr; if( !GetDevFontAttributes( nFontID, aDevFontAttr ) ) continue; AtsFontData* pFontData = new AtsFontData( aDevFontAttr, nFontID ); maFontContainer[ nFontID ] = pFontData; } InitGlyphFallbacks(); } // ----------------------------------------------------------------------- AtsFontList::~AtsFontList() { AtsFontContainer::const_iterator it = maFontContainer.begin(); for(; it != maFontContainer.end(); ++it ) delete (*it).second; maFontContainer.clear(); ATSUDisposeFontFallbacks( maFontFallbacks ); } // ----------------------------------------------------------------------- void AtsFontList::AnnounceFonts( ImplDevFontList& rFontList ) const { AtsFontContainer::const_iterator it = maFontContainer.begin(); for(; it != maFontContainer.end(); ++it ) rFontList.Add( (*it).second->Clone() ); } // ----------------------------------------------------------------------- ImplMacFontData* AtsFontList::GetFontDataFromId( sal_IntPtr nFontId ) const { AtsFontContainer::const_iterator it = maFontContainer.find( nFontId ); if( it == maFontContainer.end() ) return NULL; return (*it).second; } // ----------------------------------------------------------------------- // not all fonts are suitable for glyph fallback => sort them struct GfbCompare{ bool operator()(const ImplMacFontData*, const ImplMacFontData*); }; inline bool GfbCompare::operator()( const ImplMacFontData* pA, const ImplMacFontData* pB ) { // use symbol fonts only as last resort bool bPreferA = !pA->IsSymbolFont(); bool bPreferB = !pB->IsSymbolFont(); if( bPreferA != bPreferB ) return bPreferA; // prefer scalable fonts bPreferA = pA->IsScalable(); bPreferB = pB->IsScalable(); if( bPreferA != bPreferB ) return bPreferA; // prefer non-slanted fonts bPreferA = (pA->GetSlant() == ITALIC_NONE); bPreferB = (pB->GetSlant() == ITALIC_NONE); if( bPreferA != bPreferB ) return bPreferA; // prefer normal weight fonts bPreferA = (pA->GetWeight() == WEIGHT_NORMAL); bPreferB = (pB->GetWeight() == WEIGHT_NORMAL); if( bPreferA != bPreferB ) return bPreferA; // prefer normal width fonts bPreferA = (pA->GetWidthType() == WIDTH_NORMAL); bPreferB = (pB->GetWidthType() == WIDTH_NORMAL); if( bPreferA != bPreferB ) return bPreferA; return false; } // ----------------------------------------------------------------------- void AtsFontList::InitGlyphFallbacks() { // sort fonts for "glyph fallback" typedef std::multiset FallbackSet; FallbackSet aFallbackSet; AtsFontContainer::const_iterator it = maFontContainer.begin(); for(; it != maFontContainer.end(); ++it ) { const ImplMacFontData* pIFD = (*it).second; // TODO: subsettable/embeddable glyph fallback only for PDF export? if( pIFD->IsSubsettable() || pIFD->IsEmbeddable() ) aFallbackSet.insert( pIFD ); } // tell ATSU about font preferences for "glyph fallback" typedef std::vector AtsFontIDVector; AtsFontIDVector aFallbackVector; aFallbackVector.reserve( maFontContainer.size() ); FallbackSet::const_iterator itFData = aFallbackSet.begin(); for(; itFData != aFallbackSet.end(); ++itFData ) { const ImplMacFontData* pFontData = (*itFData); ATSUFontID nFontID = (ATSUFontID)pFontData->GetFontId(); aFallbackVector.push_back( nFontID ); } ATSUCreateFontFallbacks( &maFontFallbacks ); ATSUSetObjFontFallbacks( maFontFallbacks, aFallbackVector.size(), &aFallbackVector[0], kATSUSequentialFallbacksPreferred ); } // =======================================================================