Files
Mozilla/mozilla/gfx/src/mac/nsMacUnicodeFontInfo.cpp
darin%meer.net 3d52b0860b fixes bug 219400 "remove callers of nsServiceManager:: methods" r=bsmedberg
git-svn-id: svn://10.0.0.236/trunk@165089 18797224-902f-48f8-a5cc-f745e15eee43
2004-11-07 23:59:35 +00:00

629 lines
18 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Frank Yung-Fong Tang <ftang@netscape.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsMacUnicodeFontInfo.h"
#include "nsCRT.h"
#include "prmem.h"
#include <Fonts.h>
//#define DEBUG_TRUE_TYPE
#include "nsICharRepresentable.h"
#include "nsCompressedCharMap.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIServiceManager.h"
#include "nsDependentString.h"
#include "nsLiteralString.h"
#include "nsDeviceContextMac.h"
#include "nsICharsetConverterManager.h"
#include "nsIPersistentProperties2.h"
#include "nsNetUtil.h"
#include "nsHashtable.h"
#include <ATSTypes.h>
#include <SFNTTypes.h>
#include <SFNTLayoutTypes.h>
//#define TRACK_INIT_PERFORMANCE
#ifdef TRACK_INIT_PERFORMANCE
#include <DriverServices.h>
#endif
class nsFontCleanupObserver : public nsIObserver {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
nsFontCleanupObserver() { }
virtual ~nsFontCleanupObserver() {}
};
NS_IMPL_ISUPPORTS1(nsFontCleanupObserver, nsIObserver)
NS_IMETHODIMP nsFontCleanupObserver::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData)
{
if (! nsCRT::strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID,aTopic))
{
nsMacUnicodeFontInfo::FreeGlobals();
}
return NS_OK;
}
static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CID);
static nsIPersistentProperties* gFontEncodingProperties = nsnull;
static nsICharsetConverterManager* gCharsetManager = nsnull;
static nsObjectHashtable* gFontMaps = nsnull;
static nsFontCleanupObserver *gFontCleanupObserver = nsnull;
static PRUint16* gCCMap = nsnull;
#ifdef IS_BIG_ENDIAN
# undef GET_SHORT
# define GET_SHORT(p) (*((PRUint16*)p))
# undef GET_LONG
# define GET_LONG(p) (*((PRUint32*)p))
#else
# ifdef IS_LITTLE_ENDIAN
# undef GET_SHORT
# define GET_SHORT(p) (((p)[0] << 8) | (p)[1])
# undef GET_LONG
# define GET_LONG(p) (((p)[0] << 24) | ((p)[1] << 16) | ((p)[2] << 8) | (p)[3])
# endif
#endif
// The following should be defined in ATSTypes.h, but they ar not
enum {
kFMOpenTypeFontTechnology = FOUR_CHAR_CODE('OTTO')
};
// The following should be defined in SNFTTypes.h, but they ar not
enum {
headFontTableTag = FOUR_CHAR_CODE('head'),
locaFontTableTag = FOUR_CHAR_CODE('loca')
};
#define ADD_GLYPH(a,b) SET_REPRESENTABLE(a,b)
#define FONT_HAS_GLYPH(a,b) IS_REPRESENTABLE(a,b)
#undef SET_SPACE
#define SET_SPACE(c) ADD_GLYPH(spaces, c)
#undef SHOULD_BE_SPACE
#define SHOULD_BE_SPACE(c) FONT_HAS_GLYPH(spaces, c)
static PRInt8
GetIndexToLocFormat(FMFont aFont)
{
PRUint16 indexToLocFormat;
ByteCount len = 0;
OSStatus err = ::FMGetFontTable(aFont, headFontTableTag, 50, 2, &indexToLocFormat, nsnull);
if (err != noErr)
return -1;
if (!indexToLocFormat)
return 0;
return 1;
}
static PRUint8*
GetSpaces(FMFont aFont, PRUint32* aMaxGlyph)
{
PRInt8 isLong = GetIndexToLocFormat(aFont);
if (isLong < 0)
return nsnull;
ByteCount len = 0;
OSStatus err = ::FMGetFontTable(aFont, locaFontTableTag, 0, 0, NULL, &len);
if ((err != noErr) || (!len))
return nsnull;
PRUint8* buf = (PRUint8*) nsMemory::Alloc(len);
NS_ASSERTION(buf, "cannot read 'loca' table because out of memory");
if (!buf)
return nsnull;
ByteCount newLen = 0;
err = ::FMGetFontTable(aFont, locaFontTableTag, 0, len, buf, &newLen);
NS_ASSERTION((newLen == len), "cannot read 'loca' table from the font");
if (newLen != len)
{
nsMemory::Free(buf);
return nsnull;
}
if (isLong)
{
PRUint32 longLen = ((len / 4) - 1);
*aMaxGlyph = longLen;
PRUint32* longBuf = (PRUint32*) buf;
for (PRUint32 i = 0; i < longLen; i++)
{
if (longBuf[i] == longBuf[i+1])
buf[i] = 1;
else
buf[i] = 0;
}
}
else
{
PRUint32 shortLen = ((len / 2) - 1);
*aMaxGlyph = shortLen;
PRUint16* shortBuf = (PRUint16*) buf;
for (PRUint16 i = 0; i < shortLen; i++)
{
if (shortBuf[i] == shortBuf[i+1])
buf[i] = 1;
else
buf[i] = 0;
}
}
return buf;
}
static int spacesInitialized = 0;
static PRUint32 spaces[2048];
static void InitSpace()
{
if (!spacesInitialized)
{
spacesInitialized = 1;
SET_SPACE(0x0020);
SET_SPACE(0x00A0);
for (PRUint16 c = 0x2000; c <= 0x200B; c++)
SET_SPACE(c);
SET_SPACE(0x3000);
}
}
static void HandleFormat4(PRUint16* aEntry, PRUint8* aEnd,
PRUint8* aIsSpace, PRUint32 aMaxGlyph,
PRUint32* aFontInfo)
{
// notice aIsSpace could be nsnull in case of OpenType font
PRUint8* end = aEnd;
PRUint16* s = aEntry;
PRUint16 segCount = s[3] / 2;
PRUint16* endCode = &s[7];
PRUint16* startCode = endCode + segCount + 1;
PRUint16* idDelta = startCode + segCount;
PRUint16* idRangeOffset = idDelta + segCount;
PRUint16* glyphIdArray = idRangeOffset + segCount;
PRUint16 i;
InitSpace();
for (i = 0; i < segCount; i++)
{
if (idRangeOffset[i])
{
PRUint16 startC = startCode[i];
PRUint16 endC = endCode[i];
for (PRUint32 c = startC; c <= endC; c++)
{
PRUint16* g = (idRangeOffset[i]/2 + (c - startC) + &idRangeOffset[i]);
if ((PRUint8*) g < end)
{
if (*g)
{
PRUint16 glyph = idDelta[i] + *g;
if (glyph < aMaxGlyph)
{
if (aIsSpace && aIsSpace[glyph])
{
if (SHOULD_BE_SPACE(c))
ADD_GLYPH(aFontInfo, c);
}
else
{
ADD_GLYPH(aFontInfo, c);
}
}
}
}
else
{
// XXX should we trust this font at all if it does this?
}
}
}
else
{
PRUint16 endC = endCode[i];
for (PRUint32 c = startCode[i]; c <= endC; c++)
{
PRUint16 glyph = idDelta[i] + c;
if (glyph < aMaxGlyph)
{
if (aIsSpace && aIsSpace[glyph])
{
if (SHOULD_BE_SPACE(c))
ADD_GLYPH(aFontInfo, c);
}
else
{
ADD_GLYPH(aFontInfo, c);
}
}
}
}
}
}
static PRBool FillFontInfoFromCMAP(FMFont aFont, PRUint32 *aFontInfo, FourCharCode aFontFormat)
{
ByteCount len;
OSErr err = ::FMGetFontTable(aFont, cmapFontTableTag, 0, 0, NULL, &len);
if((err!=noErr) || (!len))
return PR_FALSE;
PRUint8* buf = (PRUint8*) nsMemory::Alloc(len);
NS_ASSERTION(buf, "cannot read cmap because out of memory");
if (!buf)
return PR_FALSE;
ByteCount newLen;
err = ::FMGetFontTable(aFont, cmapFontTableTag, 0, len, buf, &newLen);
NS_ASSERTION(newLen == len, "cannot read cmap from the font");
if (newLen != len)
{
nsMemory::Free(buf);
return PR_FALSE;
}
PRUint8* p = buf + sizeof(PRUint16); // skip version, move to numberSubtables
PRUint16 n = GET_SHORT(p); // get numberSubtables
p += sizeof(PRUint16); // skip numberSubtables, move to the encoding subtables
PRUint16 i;
PRUint32 offset;
PRUint32 platformUnicodeOffset = 0;
// we look for platform =3 and encoding =1,
// if we cannot find it but there are a platform = 0 there, we
// remmeber that one and use it.
for (i = 0; i < n; i++)
{
PRUint16 platformID = GET_SHORT(p); // get platformID
p += sizeof(PRUint16); // move to platformSpecificID
PRUint16 encodingID = GET_SHORT(p); // get platformSpecificID
p += sizeof(PRUint16); // move to offset
offset = GET_LONG(p); // get offset
p += sizeof(PRUint32); // move to next entry
#ifdef DEBUG_TRUE_TYPE
printf("p=%d e=%d offset=%x\n", platformID, encodingID, offset);
#endif
if (platformID == kFontMicrosoftPlatform)
{
if (encodingID == kFontMicrosoftStandardScript)
{ // Unicode
// Some fonts claim to be unicode when they are actually
// 'pseudo-unicode' fonts that require a converter...
break; // break out from for(;;) loop
} //if (encodingID == kFontMicrosoftStandardScript)
#if 0
// if we have other encoding, we can still handle it, so... don't return this early
else if (encodingID == kFontMicrosoftSymbolScript)
{ // symbol
NS_ASSERTION(false, "cannot handle symbol font");
nsMemory::Free(buf);
return PR_FALSE;
}
#endif
} // if (platformID == kFontMicrosoftPlatform)
else {
if (platformID == kFontUnicodePlatform)
{ // Unicode
platformUnicodeOffset = offset;
}
}
} // for loop
NS_ASSERTION((i != n) || ( 0 != platformUnicodeOffset), "do not know the TrueType encoding");
if ((i == n) && ( 0 == platformUnicodeOffset))
{
nsMemory::Free(buf);
return PR_FALSE;
}
// usually, we come here for the entry platform = 3 encoding = 1
// or if we don't have it but we have a platform = 0 (platformUnicodeOffset != 0)
if(platformUnicodeOffset)
offset = platformUnicodeOffset;
p = buf + offset;
PRUint16 format = GET_SHORT(p);
NS_ASSERTION((kSFNTLookupSegmentArray == format), "hit some unknow format");
switch(format) {
case kSFNTLookupSegmentArray: // format 4
{
PRUint32 maxGlyph;
PRUint8* isSpace = GetSpaces(aFont, &maxGlyph);
// isSpace could be nsnull if the font do not have 'loca' table on Mac.
// Two reason for that:
// First, 'loca' table is not required in OpenType font.
//
// Second, on Mac, the sfnt-housed font may not have 'loca' table.
// exmaple are Beijing and Taipei font.
// see the WARNING section at the following link for details
// http://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html
HandleFormat4((PRUint16*) (buf + offset), buf+len, isSpace, maxGlyph, aFontInfo);
if (isSpace)
nsMemory::Free(isSpace);
nsMemory::Free(buf);
return PR_TRUE;
}
break;
default:
{
nsMemory::Free(buf);
return PR_FALSE;
}
break;
}
}
static PRUint16* InitGlobalCCMap()
{
PRUint32 info[2048];
memset(info, 0, sizeof(info));
#ifdef TRACK_INIT_PERFORMANCE
AbsoluteTime startTime;
AbsoluteTime endTime;
startTime = UpTime();
#endif
FMFontFamilyIterator aFontIterator;
OSStatus status = 0;
FMFont aFont;
FMFontFamily aFontFamily;
status = ::FMCreateFontFamilyIterator(NULL, NULL, kFMDefaultOptions,
&aFontIterator);
while (status == noErr)
{
FourCharCode aFormat;
status = ::FMGetNextFontFamily(&aFontIterator, &aFontFamily);
OSStatus status2;
FMFontStyle aStyle;
status2 = ::FMGetFontFromFontFamilyInstance(aFontFamily, 0, &aFont, &aStyle);
NS_ASSERTION(status2 == noErr, "cannot get font from family");
if (status2 == noErr)
{
status2 = ::FMGetFontFormat(aFont, &aFormat);
#ifdef DEBUG_TRUE_TYPE
OSStatus status3 = ::FMGetFontFormat(aFont, &aFormat);
const char *four = (const char*) &aFormat;
Str255 familyName;
status3 = ::FMGetFontFamilyName(aFontFamily, familyName);
familyName[familyName[0]+1] = '\0';
printf("%s format = %c%c%c%c\n", familyName+1, *four, *(four+1), *(four+2), *(four+3));
#endif
if ((status2 == noErr) &&
((kFMTrueTypeFontTechnology == aFormat) ||
(kFMOpenTypeFontTechnology == aFormat)))
{
PRBool ret = FillFontInfoFromCMAP(aFont, info, aFormat);
}
}
}
// Dispose of the contents of the font iterator.
status = ::FMDisposeFontFamilyIterator(&aFontIterator);
PRUint16* map = MapToCCMap(info);
NS_ASSERTION(map, "cannot create the compressed map");
//register an observer to take care of cleanup
gFontCleanupObserver = new nsFontCleanupObserver();
NS_ASSERTION(gFontCleanupObserver, "failed to create observer");
if (gFontCleanupObserver) {
// register for shutdown
nsresult rv;
nsCOMPtr<nsIObserverService> observerService(do_GetService("@mozilla.org/observer-service;1", &rv));
if (NS_SUCCEEDED(rv)) {
rv = observerService->AddObserver(gFontCleanupObserver, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE);
}
}
#ifdef TRACK_INIT_PERFORMANCE
endTime = UpTime();
Nanoseconds diff = ::AbsoluteToNanoseconds(SubAbsoluteFromAbsolute(endTime, startTime));
printf("nsMacUnicodeFontInfo::InitGolbal take %d %d nanosecond\n", diff.hi, diff.lo);
#endif
return map;
}
// Helper to determine if a font has a private encoding that we know something about
static nsresult
GetEncoding(const nsCString& aFontName, nsACString& aValue)
{
nsresult rv;
// see if we should init the property
if (! gFontEncodingProperties) {
// but bail out for common fonts used at startup...
if (aFontName.EqualsLiteral("Lucida Grande") ||
aFontName.EqualsLiteral("Charcoal") ||
aFontName.EqualsLiteral("Chicago") ||
aFontName.EqualsLiteral("Capitals") ||
aFontName.EqualsLiteral("Gadget") ||
aFontName.EqualsLiteral("Sand") ||
aFontName.EqualsLiteral("Techno") ||
aFontName.EqualsLiteral("Textile") ||
aFontName.EqualsLiteral("Geneva") )
return NS_ERROR_NOT_AVAILABLE; // error mean do not get a special encoding
// init the property now
rv = NS_LoadPersistentPropertiesFromURISpec(&gFontEncodingProperties,
NS_LITERAL_CSTRING("resource:/res/fonts/fontEncoding.properties"));
if NS_FAILED(rv)
return rv;
}
nsCAutoString name(NS_LITERAL_CSTRING("encoding.") +
aFontName +
NS_LITERAL_CSTRING(".ttf"));
name.StripWhitespace();
ToLowerCase(name);
nsAutoString value;
rv = gFontEncodingProperties->GetStringProperty(name, value);
if (NS_SUCCEEDED(rv))
CopyUCS2toASCII(value, aValue);
return rv;
}
// This function uses the charset converter manager (CCM) to get a pointer on
// the converter for the font whose name is given. The CCM caches the converter.
// The caller holds a reference and should take care of the release.
static nsresult
GetConverter(const nsCString& aFontName, nsIUnicodeEncoder** aConverter)
{
*aConverter = nsnull;
nsCAutoString value;
nsresult rv = GetEncoding(aFontName, value);
if (NS_FAILED(rv)) return rv;
if (!gCharsetManager)
{
rv = CallGetService(kCharsetConverterManagerCID, &gCharsetManager);
if(NS_FAILED(rv)) return rv;
}
rv = gCharsetManager->GetUnicodeEncoderRaw(value.get(), aConverter);
if (NS_FAILED(rv)) return rv;
nsIUnicodeEncoder* tmp = *aConverter;
return tmp->SetOutputErrorBehavior(tmp->kOnError_Replace, nsnull, '?');
}
// This function uses the charset converter manager to fill the map for the
// font whose name is given
static PRUint16*
GetCCMapThroughConverter(nsIUnicodeEncoder *converter)
{
// see if we know something about the converter of this font
nsCOMPtr<nsICharRepresentable> mapper(do_QueryInterface(converter));
return (mapper ? MapperToCCMap(mapper) : nsnull);
}
static PRBool PR_CALLBACK
HashtableFreeCCMap(nsHashKey *aKey, void *aData, void *closure)
{
PRUint16* ccmap = (PRUint16*)aData;
FreeCCMap(ccmap);
return PR_TRUE;
}
// If the font is symbolic, get its converter and its compressed character map
// (CCMap). The caller holds a reference to the converter (@see GetConverter()).
// The CCMap is cached in a hashtable and will be freed at shutdown.
nsresult
nsMacUnicodeFontInfo::GetConverterAndCCMap(const nsString& aFontName, nsIUnicodeEncoder** aConverter,
PRUint16** aCCMap)
{
if(NS_SUCCEEDED(GetConverter(NS_ConvertUCS2toUTF8(aFontName), aConverter)) && *aConverter)
{
// make sure we have the hashtable
if(!gFontMaps)
{
gFontMaps = new nsObjectHashtable(nsnull, nsnull, HashtableFreeCCMap, nsnull);
if(!gFontMaps)
{
*aConverter = nsnull;
return NS_ERROR_OUT_OF_MEMORY;
}
}
// first try to retrieve the ccmap for this font from the hashtable
nsStringKey hashKey(aFontName);
*aCCMap = (PRUint16*) gFontMaps->Get(&hashKey);
if(!*aCCMap)
{
// if it's not already in the hashtable, create it and add it to the hashtable
*aCCMap = GetCCMapThroughConverter(*aConverter);
if(!*aCCMap)
{
*aConverter = nsnull;
return NS_ERROR_FAILURE;
}
gFontMaps->Put(&hashKey, *aCCMap);
}
return NS_OK;
}
return NS_ERROR_FAILURE;
}
PRBool nsMacUnicodeFontInfo::HasGlyphFor(PRUnichar aChar)
{
if (0xfffd == aChar)
return PR_FALSE;
// MacOS 8.6 do not have FMxxx etc so we have to check
if (nsDeviceContextMac::HaveFontManager90()) {
if (!gCCMap)
gCCMap = InitGlobalCCMap();
NS_ASSERTION(gCCMap, "cannot init global ccmap");
if (gCCMap)
return CCMAP_HAS_CHAR(gCCMap, aChar);
}
return PR_FALSE;
}
void nsMacUnicodeFontInfo::FreeGlobals()
{
NS_IF_RELEASE(gFontEncodingProperties);
NS_IF_RELEASE(gCharsetManager);
delete gFontMaps;
if (gCCMap)
FreeCCMap(gCCMap);
}