mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 14:22:01 +00:00
d46766b464
When reading a U16 font name from the SFNTNameTable, a name entry with platformID == 1 (Macintosh) and platformSpecificID (aka encodingID) == 0 (Roman) is read as Mac Roman and converted to U16. This patch refactors the matchers created in CreateCanonicalU16Matchers to return name encoding type instead of a boolean. The encoding type can then be used to call the appropriate decoding function. CreateCanonicalU16Matchers is also changed so that it doesn't enqueue unnecessary matchers on OS X. On OS X, if the nametable record's platformID field is PLATFORM_ID, IsUTF16Encoding() will always return false so matchers requiring both of those conditions will never match. There are several other platformSpecificID's in Mac SFNTameTables such as Japanese, Traditional Chinese, and Korean. Fonts with names in those encodings won't have their names properly encoded, but that should be OK as SFNTData::GetUniqueKey falls back to another scheme for hashing fonts if the GetU16FullName call fails. Tests on El Capitan and Sierra revealed Mac's use Microsoft/Unicode SFNTNameTable names as well as Mac/Roman. MozReview-Commit-ID: F8fyDVDwHs7 --HG-- extra : transplant_source : %F6%3F%5B%E9y%FD%93%8C%26s%D1n%FC%AEYp%5C%3D%A6j
358 lines
9.9 KiB
C++
358 lines
9.9 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "SFNTNameTable.h"
|
|
|
|
#include "BigEndianInts.h"
|
|
#include "Logging.h"
|
|
#include "mozilla/Move.h"
|
|
|
|
#if defined(XP_MACOSX)
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#endif
|
|
|
|
namespace mozilla {
|
|
namespace gfx {
|
|
|
|
static const BigEndianUint16 FORMAT_0 = 0;
|
|
|
|
static const BigEndianUint16 NAME_ID_FAMILY = 1;
|
|
static const BigEndianUint16 NAME_ID_STYLE = 2;
|
|
static const BigEndianUint16 NAME_ID_FULL = 4;
|
|
|
|
static const BigEndianUint16 PLATFORM_ID_UNICODE = 0;
|
|
static const BigEndianUint16 PLATFORM_ID_MAC = 1;
|
|
static const BigEndianUint16 PLATFORM_ID_MICROSOFT = 3;
|
|
|
|
static const BigEndianUint16 ENCODING_ID_MICROSOFT_SYMBOL = 0;
|
|
static const BigEndianUint16 ENCODING_ID_MICROSOFT_UNICODEBMP = 1;
|
|
static const BigEndianUint16 ENCODING_ID_MICROSOFT_UNICODEFULL = 10;
|
|
|
|
static const BigEndianUint16 ENCODING_ID_MAC_ROMAN = 0;
|
|
|
|
static const BigEndianUint16 LANG_ID_MAC_ENGLISH = 0;
|
|
|
|
static const BigEndianUint16 LANG_ID_MICROSOFT_EN_US = 0x0409;
|
|
|
|
#pragma pack(push, 1)
|
|
|
|
// Name table has a header, followed by name records, followed by string data.
|
|
struct NameHeader
|
|
{
|
|
BigEndianUint16 format; // Format selector (=0).
|
|
BigEndianUint16 count; // Number of name records.
|
|
BigEndianUint16 stringOffset; // Offset to string storage from start of table.
|
|
};
|
|
|
|
struct NameRecord
|
|
{
|
|
BigEndianUint16 platformID;
|
|
BigEndianUint16 encodingID; // Platform-specific encoding ID
|
|
BigEndianUint16 languageID;
|
|
BigEndianUint16 nameID;
|
|
BigEndianUint16 length; // String length in bytes.
|
|
BigEndianUint16 offset; // String offset from start of storage in bytes.
|
|
};
|
|
|
|
#pragma pack(pop)
|
|
|
|
enum ENameDecoder : int
|
|
{
|
|
eNameDecoderUTF16,
|
|
#if defined(XP_MACOSX)
|
|
eNameDecoderMacRoman,
|
|
#endif
|
|
eNameDecoderNone
|
|
};
|
|
|
|
/* static */
|
|
UniquePtr<SFNTNameTable>
|
|
SFNTNameTable::Create(const uint8_t *aNameData, uint32_t aDataLength)
|
|
{
|
|
MOZ_ASSERT(aNameData);
|
|
|
|
if (aDataLength < sizeof(NameHeader)) {
|
|
gfxWarning() << "Name data too short to contain NameHeader.";
|
|
return nullptr;
|
|
}
|
|
|
|
const NameHeader *nameHeader = reinterpret_cast<const NameHeader*>(aNameData);
|
|
if (nameHeader->format != FORMAT_0) {
|
|
gfxWarning() << "Only Name Table Format 0 is supported.";
|
|
return nullptr;
|
|
}
|
|
|
|
uint16_t stringOffset = nameHeader->stringOffset;
|
|
|
|
if (stringOffset !=
|
|
sizeof(NameHeader) + (nameHeader->count * sizeof(NameRecord))) {
|
|
gfxWarning() << "Name table string offset is incorrect.";
|
|
return nullptr;
|
|
}
|
|
|
|
if (aDataLength < stringOffset) {
|
|
gfxWarning() << "Name data too short to contain name records.";
|
|
return nullptr;
|
|
}
|
|
|
|
return UniquePtr<SFNTNameTable>(
|
|
new SFNTNameTable(nameHeader, aNameData, aDataLength));
|
|
}
|
|
|
|
SFNTNameTable::SFNTNameTable(const NameHeader *aNameHeader,
|
|
const uint8_t *aNameData, uint32_t aDataLength)
|
|
: mFirstRecord(reinterpret_cast<const NameRecord*>(aNameData
|
|
+ sizeof(NameHeader)))
|
|
, mEndOfRecords(mFirstRecord + aNameHeader->count)
|
|
, mStringData(aNameData + aNameHeader->stringOffset)
|
|
, mStringDataLength(aDataLength - aNameHeader->stringOffset)
|
|
{
|
|
MOZ_ASSERT(reinterpret_cast<const uint8_t*>(aNameHeader) == aNameData);
|
|
}
|
|
|
|
static bool
|
|
IsUTF16Encoding(const NameRecord *aNameRecord)
|
|
{
|
|
if (aNameRecord->platformID == PLATFORM_ID_MICROSOFT &&
|
|
(aNameRecord->encodingID == ENCODING_ID_MICROSOFT_UNICODEBMP ||
|
|
aNameRecord->encodingID == ENCODING_ID_MICROSOFT_SYMBOL)) {
|
|
return true;
|
|
}
|
|
|
|
if (aNameRecord->platformID == PLATFORM_ID_UNICODE) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#if defined(XP_MACOSX)
|
|
static bool
|
|
IsMacRomanEncoding(const NameRecord *aNameRecord)
|
|
{
|
|
if (aNameRecord->platformID == PLATFORM_ID_MAC &&
|
|
aNameRecord->encodingID == ENCODING_ID_MAC_ROMAN) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
static NameRecordMatchers*
|
|
CreateCanonicalMatchers(const BigEndianUint16& aNameID)
|
|
{
|
|
// For Windows, we return only Microsoft platform name record
|
|
// matchers. On Mac, we return matchers for both Microsoft platform
|
|
// records and Mac platform records.
|
|
NameRecordMatchers *matchers = new NameRecordMatchers();
|
|
|
|
#if defined(XP_MACOSX)
|
|
// First, look for the English name.
|
|
if (!matchers->append(
|
|
[=](const NameRecord *aNameRecord) {
|
|
if (aNameRecord->nameID == aNameID &&
|
|
aNameRecord->languageID == LANG_ID_MAC_ENGLISH &&
|
|
aNameRecord->platformID == PLATFORM_ID_MAC &&
|
|
IsMacRomanEncoding(aNameRecord)) {
|
|
return eNameDecoderMacRoman;
|
|
} else {
|
|
return eNameDecoderNone;
|
|
}
|
|
})) {
|
|
MOZ_CRASH();
|
|
}
|
|
|
|
// Second, look for all languages.
|
|
if (!matchers->append(
|
|
[=](const NameRecord *aNameRecord) {
|
|
if (aNameRecord->nameID == aNameID &&
|
|
aNameRecord->platformID == PLATFORM_ID_MAC &&
|
|
IsMacRomanEncoding(aNameRecord)) {
|
|
return eNameDecoderMacRoman;
|
|
} else {
|
|
return eNameDecoderNone;
|
|
}
|
|
})) {
|
|
MOZ_CRASH();
|
|
}
|
|
#endif /* defined(XP_MACOSX) */
|
|
|
|
// First, look for the English name (this will normally succeed).
|
|
if (!matchers->append(
|
|
[=](const NameRecord *aNameRecord) {
|
|
if (aNameRecord->nameID == aNameID &&
|
|
aNameRecord->languageID == LANG_ID_MICROSOFT_EN_US &&
|
|
aNameRecord->platformID == PLATFORM_ID_MICROSOFT &&
|
|
IsUTF16Encoding(aNameRecord)) {
|
|
return eNameDecoderUTF16;
|
|
} else {
|
|
return eNameDecoderNone;
|
|
}
|
|
})) {
|
|
MOZ_CRASH();
|
|
}
|
|
|
|
// Second, look for all languages.
|
|
if (!matchers->append(
|
|
[=](const NameRecord *aNameRecord) {
|
|
if (aNameRecord->nameID == aNameID &&
|
|
aNameRecord->platformID == PLATFORM_ID_MICROSOFT &&
|
|
IsUTF16Encoding(aNameRecord)) {
|
|
return eNameDecoderUTF16;
|
|
} else {
|
|
return eNameDecoderNone;
|
|
}
|
|
})) {
|
|
MOZ_CRASH();
|
|
}
|
|
|
|
return matchers;
|
|
}
|
|
|
|
static const NameRecordMatchers&
|
|
FullNameMatchers()
|
|
{
|
|
static const NameRecordMatchers *sFullNameMatchers =
|
|
CreateCanonicalMatchers(NAME_ID_FULL);
|
|
return *sFullNameMatchers;
|
|
}
|
|
|
|
static const NameRecordMatchers&
|
|
FamilyMatchers()
|
|
{
|
|
static const NameRecordMatchers *sFamilyMatchers =
|
|
CreateCanonicalMatchers(NAME_ID_FAMILY);
|
|
return *sFamilyMatchers;
|
|
}
|
|
|
|
static const NameRecordMatchers&
|
|
StyleMatchers()
|
|
{
|
|
static const NameRecordMatchers *sStyleMatchers =
|
|
CreateCanonicalMatchers(NAME_ID_STYLE);
|
|
return *sStyleMatchers;
|
|
}
|
|
|
|
bool
|
|
SFNTNameTable::GetU16FullName(mozilla::u16string& aU16FullName)
|
|
{
|
|
if (ReadU16Name(FullNameMatchers(), aU16FullName)) {
|
|
return true;
|
|
}
|
|
|
|
// If the full name record doesn't exist create the name from the family space
|
|
// concatenated with the style.
|
|
mozilla::u16string familyName;
|
|
if (!ReadU16Name(FamilyMatchers(), familyName)) {
|
|
return false;
|
|
}
|
|
|
|
mozilla::u16string styleName;
|
|
if (!ReadU16Name(StyleMatchers(), styleName)) {
|
|
return false;
|
|
}
|
|
|
|
aU16FullName.assign(Move(familyName));
|
|
aU16FullName.append(u" ");
|
|
aU16FullName.append(styleName);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
SFNTNameTable::ReadU16Name(const NameRecordMatchers& aMatchers,
|
|
mozilla::u16string& aU16Name)
|
|
{
|
|
MOZ_ASSERT(!aMatchers.empty());
|
|
|
|
for (size_t i = 0; i < aMatchers.length(); ++i) {
|
|
const NameRecord* record = mFirstRecord;
|
|
while (record != mEndOfRecords) {
|
|
switch (aMatchers[i](record)) {
|
|
case eNameDecoderUTF16:
|
|
return ReadU16NameFromU16Record(record, aU16Name);
|
|
#if defined(XP_MACOSX)
|
|
case eNameDecoderMacRoman:
|
|
return ReadU16NameFromMacRomanRecord(record, aU16Name);
|
|
#endif
|
|
case eNameDecoderNone:
|
|
break;
|
|
default:
|
|
MOZ_CRASH("Invalid matcher encoding type");
|
|
break;
|
|
}
|
|
++record;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
SFNTNameTable::ReadU16NameFromU16Record(const NameRecord *aNameRecord,
|
|
mozilla::u16string& aU16Name)
|
|
{
|
|
uint32_t offset = aNameRecord->offset;
|
|
uint32_t length = aNameRecord->length;
|
|
if (mStringDataLength < offset + length) {
|
|
gfxWarning() << "Name data too short to contain name string.";
|
|
return false;
|
|
}
|
|
|
|
const uint8_t *startOfName = mStringData + offset;
|
|
size_t actualLength = length / sizeof(char16_t);
|
|
UniquePtr<char16_t[]> nameData(new char16_t[actualLength]);
|
|
NativeEndian::copyAndSwapFromBigEndian(nameData.get(), startOfName,
|
|
actualLength);
|
|
|
|
aU16Name.assign(nameData.get(), actualLength);
|
|
return true;
|
|
}
|
|
|
|
#if defined(XP_MACOSX)
|
|
bool
|
|
SFNTNameTable::ReadU16NameFromMacRomanRecord(const NameRecord *aNameRecord,
|
|
mozilla::u16string& aU16Name)
|
|
{
|
|
uint32_t offset = aNameRecord->offset;
|
|
uint32_t length = aNameRecord->length;
|
|
if (mStringDataLength < offset + length) {
|
|
gfxWarning() << "Name data too short to contain name string.";
|
|
return false;
|
|
}
|
|
if (length > INT_MAX) {
|
|
gfxWarning() << "Name record too long to decode.";
|
|
return false;
|
|
}
|
|
|
|
// pointer to the Mac Roman encoded string in the name record
|
|
const uint8_t *encodedStr = mStringData + offset;
|
|
|
|
CFStringRef cfString;
|
|
cfString = CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, encodedStr,
|
|
length, kCFStringEncodingMacRoman,
|
|
false, kCFAllocatorNull);
|
|
|
|
// length (in UTF-16 code pairs) of the decoded string
|
|
CFIndex decodedLength = CFStringGetLength(cfString);
|
|
|
|
// temporary buffer
|
|
UniquePtr<UniChar[]> u16Buffer = MakeUnique<UniChar[]>(decodedLength);
|
|
|
|
CFStringGetCharacters(cfString, CFRangeMake(0, decodedLength),
|
|
u16Buffer.get());
|
|
|
|
CFRelease(cfString);
|
|
|
|
aU16Name.assign(reinterpret_cast<char16_t*>(u16Buffer.get()), decodedLength);
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
} // gfx
|
|
} // mozilla
|