1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "SFNTData.h"
8 
9 #include <algorithm>
10 #include <numeric>
11 
12 #include "BigEndianInts.h"
13 #include "Logging.h"
14 #include "mozilla/HashFunctions.h"
15 #include "mozilla/Span.h"
16 
17 namespace mozilla {
18 namespace gfx {
19 
20 #define TRUETYPE_TAG(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d))
21 
22 #pragma pack(push, 1)
23 
24 struct TTCHeader {
25   BigEndianUint32 ttcTag;   // Always 'ttcf'
26   BigEndianUint32 version;  // Fixed, 0x00010000
27   BigEndianUint32 numFonts;
28 };
29 
30 struct OffsetTable {
31   BigEndianUint32 sfntVersion;  // Fixed, 0x00010000 for version 1.0.
32   BigEndianUint16 numTables;
33   BigEndianUint16 searchRange;    // (Maximum power of 2 <= numTables) x 16.
34   BigEndianUint16 entrySelector;  // Log2(maximum power of 2 <= numTables).
35   BigEndianUint16 rangeShift;     // NumTables x 16-searchRange.
36 };
37 
38 struct TableDirEntry {
39   BigEndianUint32 tag;       // 4 -byte identifier.
40   BigEndianUint32 checkSum;  // CheckSum for this table.
41   BigEndianUint32 offset;    // Offset from beginning of TrueType font file.
42   BigEndianUint32 length;    // Length of this table.
43 
operator <(const TableDirEntry & lhs,const uint32_t aTag)44   friend bool operator<(const TableDirEntry& lhs, const uint32_t aTag) {
45     return lhs.tag < aTag;
46   }
47 };
48 
49 #pragma pack(pop)
50 
51 class SFNTData::Font {
52  public:
Font(const OffsetTable * aOffsetTable,const uint8_t * aFontData,uint32_t aDataLength)53   Font(const OffsetTable* aOffsetTable, const uint8_t* aFontData,
54        uint32_t aDataLength)
55       : mFontData(aFontData),
56         mFirstDirEntry(
57             reinterpret_cast<const TableDirEntry*>(aOffsetTable + 1)),
58         mEndOfDirEntries(mFirstDirEntry + aOffsetTable->numTables),
59         mDataLength(aDataLength) {}
60 
GetHeadTableBytes() const61   Span<const uint8_t> GetHeadTableBytes() const {
62     const TableDirEntry* dirEntry =
63         GetDirEntry(TRUETYPE_TAG('h', 'e', 'a', 'd'));
64     if (!dirEntry) {
65       gfxWarning() << "Head table entry not found.";
66       return nullptr;
67     }
68 
69     return MakeSpan(mFontData + dirEntry->offset, dirEntry->length);
70   }
71 
72  private:
GetDirEntry(const uint32_t aTag) const73   const TableDirEntry* GetDirEntry(const uint32_t aTag) const {
74     const TableDirEntry* foundDirEntry =
75         std::lower_bound(mFirstDirEntry, mEndOfDirEntries, aTag);
76 
77     if (foundDirEntry == mEndOfDirEntries || foundDirEntry->tag != aTag) {
78       gfxWarning() << "Font data does not contain tag.";
79       return nullptr;
80     }
81 
82     if (mDataLength < (foundDirEntry->offset + foundDirEntry->length)) {
83       gfxWarning() << "Font data too short to contain table.";
84       return nullptr;
85     }
86 
87     return foundDirEntry;
88   }
89 
90   const uint8_t* mFontData;
91   const TableDirEntry* mFirstDirEntry;
92   const TableDirEntry* mEndOfDirEntries;
93   uint32_t mDataLength;
94 };
95 
96 /* static */
Create(const uint8_t * aFontData,uint32_t aDataLength)97 UniquePtr<SFNTData> SFNTData::Create(const uint8_t* aFontData,
98                                      uint32_t aDataLength) {
99   MOZ_ASSERT(aFontData);
100 
101   // Check to see if this is a font collection.
102   if (aDataLength < sizeof(TTCHeader)) {
103     gfxWarning() << "Font data too short.";
104     return nullptr;
105   }
106 
107   const TTCHeader* ttcHeader = reinterpret_cast<const TTCHeader*>(aFontData);
108   if (ttcHeader->ttcTag == TRUETYPE_TAG('t', 't', 'c', 'f')) {
109     uint32_t numFonts = ttcHeader->numFonts;
110     if (aDataLength <
111         sizeof(TTCHeader) + (numFonts * sizeof(BigEndianUint32))) {
112       gfxWarning() << "Font data too short to contain full TTC Header.";
113       return nullptr;
114     }
115 
116     UniquePtr<SFNTData> sfntData(new SFNTData);
117     const BigEndianUint32* offset =
118         reinterpret_cast<const BigEndianUint32*>(aFontData + sizeof(TTCHeader));
119     const BigEndianUint32* endOfOffsets = offset + numFonts;
120     while (offset != endOfOffsets) {
121       if (!sfntData->AddFont(aFontData, aDataLength, *offset)) {
122         return nullptr;
123       }
124       ++offset;
125     }
126 
127     return sfntData;
128   }
129 
130   UniquePtr<SFNTData> sfntData(new SFNTData);
131   if (!sfntData->AddFont(aFontData, aDataLength, 0)) {
132     return nullptr;
133   }
134 
135   return sfntData;
136 }
137 
138 /* static */
GetUniqueKey(const uint8_t * aFontData,uint32_t aDataLength,uint32_t aVarDataSize,const void * aVarData)139 uint64_t SFNTData::GetUniqueKey(const uint8_t* aFontData, uint32_t aDataLength,
140                                 uint32_t aVarDataSize, const void* aVarData) {
141   uint64_t hash = 0;
142   UniquePtr<SFNTData> sfntData = SFNTData::Create(aFontData, aDataLength);
143   if (sfntData) {
144     hash = sfntData->HashHeadTables();
145   } else {
146     gfxWarning() << "Failed to create SFNTData from data, hashing whole font.";
147     hash = HashBytes(aFontData, aDataLength);
148   }
149 
150   if (aVarDataSize) {
151     hash = AddToHash(hash, HashBytes(aVarData, aVarDataSize));
152   }
153 
154   return hash << 32 | aDataLength;
155 }
156 
~SFNTData()157 SFNTData::~SFNTData() {
158   for (size_t i = 0; i < mFonts.length(); ++i) {
159     delete mFonts[i];
160   }
161 }
162 
AddFont(const uint8_t * aFontData,uint32_t aDataLength,uint32_t aOffset)163 bool SFNTData::AddFont(const uint8_t* aFontData, uint32_t aDataLength,
164                        uint32_t aOffset) {
165   uint32_t remainingLength = aDataLength - aOffset;
166   if (remainingLength < sizeof(OffsetTable)) {
167     gfxWarning() << "Font data too short to contain OffsetTable " << aOffset;
168     return false;
169   }
170 
171   const OffsetTable* offsetTable =
172       reinterpret_cast<const OffsetTable*>(aFontData + aOffset);
173   if (remainingLength <
174       sizeof(OffsetTable) + (offsetTable->numTables * sizeof(TableDirEntry))) {
175     gfxWarning() << "Font data too short to contain tables.";
176     return false;
177   }
178 
179   return mFonts.append(new Font(offsetTable, aFontData, aDataLength));
180 }
181 
HashHeadTables()182 uint32_t SFNTData::HashHeadTables() {
183   uint32_t headTableHash = std::accumulate(
184       mFonts.begin(), mFonts.end(), 0U, [](uint32_t hash, Font* font) {
185         Span<const uint8_t> headBytes = font->GetHeadTableBytes();
186         return AddToHash(hash, HashBytes(headBytes.data(), headBytes.size()));
187       });
188 
189   return headTableHash;
190 }
191 
192 }  // namespace gfx
193 }  // namespace mozilla
194