/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 #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::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(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( new SFNTNameTable(nameHeader, aNameData, aDataLength)); } SFNTNameTable::SFNTNameTable(const NameHeader *aNameHeader, const uint8_t *aNameData, uint32_t aDataLength) : mFirstRecord(reinterpret_cast(aNameData + sizeof(NameHeader))) , mEndOfRecords(mFirstRecord + aNameHeader->count) , mStringData(aNameData + aNameHeader->stringOffset) , mStringDataLength(aDataLength - aNameHeader->stringOffset) { MOZ_ASSERT(reinterpret_cast(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 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 u16Buffer = MakeUnique(decodedLength); CFStringGetCharacters(cfString, CFRangeMake(0, decodedLength), u16Buffer.get()); CFRelease(cfString); aU16Name.assign(reinterpret_cast(u16Buffer.get()), decodedLength); return true; } #endif } // gfx } // mozilla