/* -*- 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 #include #include "BigEndianInts.h" #include "Logging.h" #include "mozilla/HashFunctions.h" #include "mozilla/Span.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(aOffsetTable + 1)), mEndOfDirEntries(mFirstDirEntry + aOffsetTable->numTables), mDataLength(aDataLength) {} Span GetHeadTableBytes() const { const TableDirEntry* dirEntry = GetDirEntry(TRUETYPE_TAG('h', 'e', 'a', 'd')); if (!dirEntry) { gfxWarning() << "Head table entry not found."; return {}; } return {mFontData + dirEntry->offset, dirEntry->length}; } Span GetCmapTableBytes() const { const TableDirEntry* dirEntry = GetDirEntry(TRUETYPE_TAG('c', 'm', 'a', 'p')); if (!dirEntry) { gfxWarning() << "Cmap table entry not found."; return {}; } return {mFontData + dirEntry->offset, dirEntry->length}; } private: const TableDirEntry* GetDirEntry(const uint32_t aTag) const { 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::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(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(new SFNTData); const BigEndianUint32* offset = reinterpret_cast(aFontData + sizeof(TTCHeader)); const BigEndianUint32* endOfOffsets = offset + numFonts; while (offset != endOfOffsets) { if (!sfntData->AddFont(aFontData, aDataLength, *offset)) { return nullptr; } ++offset; } return sfntData; } UniquePtr 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 = 0; UniquePtr sfntData = SFNTData::Create(aFontData, aDataLength); if (sfntData) { hash = sfntData->HashHeadAndCmapTables(); } else { gfxWarning() << "Failed to create SFNTData from data, hashing whole font."; hash = HashBytes(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::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(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)); } uint32_t SFNTData::HashHeadAndCmapTables() { uint32_t tablesHash = std::accumulate( mFonts.begin(), mFonts.end(), 0U, [](uint32_t hash, Font* font) { Span headBytes = font->GetHeadTableBytes(); hash = AddToHash(hash, HashBytes(headBytes.data(), headBytes.size())); Span cmapBytes = font->GetCmapTableBytes(); return AddToHash(hash, HashBytes(cmapBytes.data(), cmapBytes.size())); }); return tablesHash; } } // namespace gfx } // namespace mozilla