blob: 9af04230e89f189c615a376eb1e0d786be4566dc [file] [log] [blame]
/**************************************************************
*
* 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 <boost/assert.hpp>
#include <vector>
#include <hash_map>
#include <set>
#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<sal_IntPtr,AtsFontData*> 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<long>(+aMetrics.ascent * fPixelSize + 0.5);
rMetric.mnDescent = static_cast<long>(-aMetrics.descent * fPixelSize + 0.5);
const long nExtDescent = static_cast<long>((-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<long>(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<GgoData*>(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<GgoData*>(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<GgoData*>(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<char> 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<ATSUFontID> 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<const ImplMacFontData*,GfbCompare> 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<ATSUFontID> 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 );
}
// =======================================================================