gfx/2d/SFNTNameTable.cpp
author Brian Hackett <bhackett1024@gmail.com>
Thu, 27 Dec 2018 13:24:55 -1000
changeset 453697 e2af5f75beaf4ec851514352d62ff6f4b8a1d037
parent 448947 6f3709b3878117466168c40affa7bca0b60cf75b
child 454520 5f4630838d46dd81dadb13220a4af0da9e23a619
permissions -rw-r--r--
Bug 1516578 Part 1 - Merge HitCheckpoint and HitBreakpoint messages, r=mccr8.

/* -*- 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(std::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

}  // namespace gfx
}  // namespace mozilla