gfx/2d/SFNTData.cpp
author Sylvestre Ledru <sledru@mozilla.com>
Fri, 30 Nov 2018 11:46:48 +0100
changeset 448947 6f3709b3878117466168c40affa7bca0b60cf75b
parent 421127 a0d11b55d5957a488b41420c4f6cc178df7cd2e7
permissions -rw-r--r--
Bug 1511181 - Reformat everything to the Google coding style r=ehsan a=clang-format # ignore-this-changeset

/* -*- 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 "SFNTData.h"

#include <algorithm>

#include "BigEndianInts.h"
#include "Logging.h"
#include "mozilla/HashFunctions.h"
#include "SFNTNameTable.h"

namespace mozilla {
namespace gfx {

#define TRUETYPE_TAG(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d))

#pragma pack(push, 1)

struct TTCHeader {
  BigEndianUint32 ttcTag;   // Always 'ttcf'
  BigEndianUint32 version;  // Fixed, 0x00010000
  BigEndianUint32 numFonts;
};

struct OffsetTable {
  BigEndianUint32 sfntVersion;  // Fixed, 0x00010000 for version 1.0.
  BigEndianUint16 numTables;
  BigEndianUint16 searchRange;    // (Maximum power of 2 <= numTables) x 16.
  BigEndianUint16 entrySelector;  // Log2(maximum power of 2 <= numTables).
  BigEndianUint16 rangeShift;     // NumTables x 16-searchRange.
};

struct TableDirEntry {
  BigEndianUint32 tag;       // 4 -byte identifier.
  BigEndianUint32 checkSum;  // CheckSum for this table.
  BigEndianUint32 offset;    // Offset from beginning of TrueType font file.
  BigEndianUint32 length;    // Length of this table.

  friend bool operator<(const TableDirEntry& lhs, const uint32_t aTag) {
    return lhs.tag < aTag;
  }
};

#pragma pack(pop)

class SFNTData::Font {
 public:
  Font(const OffsetTable* aOffsetTable, const uint8_t* aFontData,
       uint32_t aDataLength)
      : mFontData(aFontData),
        mFirstDirEntry(
            reinterpret_cast<const TableDirEntry*>(aOffsetTable + 1)),
        mEndOfDirEntries(mFirstDirEntry + aOffsetTable->numTables),
        mDataLength(aDataLength) {}

  bool GetU16FullName(mozilla::u16string& aU16FullName) {
    const TableDirEntry* dirEntry =
        GetDirEntry(TRUETYPE_TAG('n', 'a', 'm', 'e'));
    if (!dirEntry) {
      gfxWarning() << "Name table entry not found.";
      return false;
    }

    UniquePtr<SFNTNameTable> nameTable =
        SFNTNameTable::Create((mFontData + dirEntry->offset), dirEntry->length);
    if (!nameTable) {
      return false;
    }

    return nameTable->GetU16FullName(aU16FullName);
  }

 private:
  const TableDirEntry* GetDirEntry(const uint32_t aTag) {
    const TableDirEntry* foundDirEntry =
        std::lower_bound(mFirstDirEntry, mEndOfDirEntries, aTag);

    if (foundDirEntry == mEndOfDirEntries || foundDirEntry->tag != aTag) {
      gfxWarning() << "Font data does not contain tag.";
      return nullptr;
    }

    if (mDataLength < (foundDirEntry->offset + foundDirEntry->length)) {
      gfxWarning() << "Font data too short to contain table.";
      return nullptr;
    }

    return foundDirEntry;
  }

  const uint8_t* mFontData;
  const TableDirEntry* mFirstDirEntry;
  const TableDirEntry* mEndOfDirEntries;
  uint32_t mDataLength;
};

/* static */
UniquePtr<SFNTData> SFNTData::Create(const uint8_t* aFontData,
                                     uint32_t aDataLength) {
  MOZ_ASSERT(aFontData);

  // Check to see if this is a font collection.
  if (aDataLength < sizeof(TTCHeader)) {
    gfxWarning() << "Font data too short.";
    return nullptr;
  }

  const TTCHeader* ttcHeader = reinterpret_cast<const TTCHeader*>(aFontData);
  if (ttcHeader->ttcTag == TRUETYPE_TAG('t', 't', 'c', 'f')) {
    uint32_t numFonts = ttcHeader->numFonts;
    if (aDataLength <
        sizeof(TTCHeader) + (numFonts * sizeof(BigEndianUint32))) {
      gfxWarning() << "Font data too short to contain full TTC Header.";
      return nullptr;
    }

    UniquePtr<SFNTData> sfntData(new SFNTData);
    const BigEndianUint32* offset =
        reinterpret_cast<const BigEndianUint32*>(aFontData + sizeof(TTCHeader));
    const BigEndianUint32* endOfOffsets = offset + numFonts;
    while (offset != endOfOffsets) {
      if (!sfntData->AddFont(aFontData, aDataLength, *offset)) {
        return nullptr;
      }
      ++offset;
    }

    return sfntData;
  }

  UniquePtr<SFNTData> sfntData(new SFNTData);
  if (!sfntData->AddFont(aFontData, aDataLength, 0)) {
    return nullptr;
  }

  return sfntData;
}

/* static */
uint64_t SFNTData::GetUniqueKey(const uint8_t* aFontData, uint32_t aDataLength,
                                uint32_t aVarDataSize, const void* aVarData) {
  uint64_t hash;
  UniquePtr<SFNTData> sfntData = SFNTData::Create(aFontData, aDataLength);
  mozilla::u16string firstName;
  if (sfntData && sfntData->GetU16FullName(0, firstName)) {
    hash = HashString(firstName.c_str(), firstName.length());
  } else {
    gfxWarning() << "Failed to get name from font data hashing whole font.";
    hash = HashString(aFontData, aDataLength);
  }

  if (aVarDataSize) {
    hash = AddToHash(hash, HashBytes(aVarData, aVarDataSize));
  }

  return hash << 32 | aDataLength;
  ;
}

SFNTData::~SFNTData() {
  for (size_t i = 0; i < mFonts.length(); ++i) {
    delete mFonts[i];
  }
}

bool SFNTData::GetU16FullName(uint32_t aIndex,
                              mozilla::u16string& aU16FullName) {
  if (aIndex >= mFonts.length()) {
    gfxWarning() << "aIndex to font data too high.";
    return false;
  }

  return mFonts[aIndex]->GetU16FullName(aU16FullName);
}

bool SFNTData::GetU16FullNames(Vector<mozilla::u16string>& aU16FullNames) {
  bool fontFound = false;
  for (size_t i = 0; i < mFonts.length(); ++i) {
    mozilla::u16string name;
    if (mFonts[i]->GetU16FullName(name)) {
      fontFound = true;
    }
    if (!aU16FullNames.append(std::move(name))) {
      return false;
    }
  }

  return fontFound;
}

bool SFNTData::GetIndexForU16Name(const mozilla::u16string& aU16FullName,
                                  uint32_t* aIndex, size_t aTruncatedLen) {
  for (size_t i = 0; i < mFonts.length(); ++i) {
    mozilla::u16string name;
    if (!mFonts[i]->GetU16FullName(name)) {
      continue;
    }

    if (aTruncatedLen) {
      MOZ_ASSERT(aU16FullName.length() <= aTruncatedLen);
      name = name.substr(0, aTruncatedLen);
    }

    if (name == aU16FullName) {
      *aIndex = i;
      return true;
    }
  }

  return false;
}

bool SFNTData::AddFont(const uint8_t* aFontData, uint32_t aDataLength,
                       uint32_t aOffset) {
  uint32_t remainingLength = aDataLength - aOffset;
  if (remainingLength < sizeof(OffsetTable)) {
    gfxWarning() << "Font data too short to contain OffsetTable " << aOffset;
    return false;
  }

  const OffsetTable* offsetTable =
      reinterpret_cast<const OffsetTable*>(aFontData + aOffset);
  if (remainingLength <
      sizeof(OffsetTable) + (offsetTable->numTables * sizeof(TableDirEntry))) {
    gfxWarning() << "Font data too short to contain tables.";
    return false;
  }

  return mFonts.append(new Font(offsetTable, aFontData, aDataLength));
}

}  // namespace gfx
}  // namespace mozilla