1 /*
2 * Copyright 2011 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7 #include "src/core/SkFontStream.h"
8
9 #include "include/core/SkFourByteTag.h"
10 #include "include/core/SkStream.h"
11 #include "include/core/SkTypes.h"
12 #include "include/private/base/SkMalloc.h"
13 #include "src/base/SkAutoMalloc.h"
14 #include "src/base/SkEndian.h"
15
16 #include <cstdint>
17
18 struct SkSFNTHeader {
19 uint32_t fVersion;
20 uint16_t fNumTables;
21 uint16_t fSearchRange;
22 uint16_t fEntrySelector;
23 uint16_t fRangeShift;
24 };
25
26 struct SkTTCFHeader {
27 uint32_t fTag;
28 uint32_t fVersion;
29 uint32_t fNumOffsets;
30 uint32_t fOffset0; // the first of N (fNumOffsets)
31 };
32
33 union SkSharedTTHeader {
34 SkSFNTHeader fSingle;
35 SkTTCFHeader fCollection;
36 };
37
38 struct SkSFNTDirEntry {
39 uint32_t fTag;
40 uint32_t fChecksum;
41 uint32_t fOffset;
42 uint32_t fLength;
43 };
44
read(SkStream * stream,void * buffer,size_t amount)45 static bool read(SkStream* stream, void* buffer, size_t amount) {
46 return stream->read(buffer, amount) == amount;
47 }
48
skip(SkStream * stream,size_t amount)49 static bool skip(SkStream* stream, size_t amount) {
50 return stream->skip(amount) == amount;
51 }
52
53 /** Return the number of tables, or if this is a TTC (collection), return the
54 number of tables in the first element of the collection. In either case,
55 if offsetToDir is not-null, set it to the offset to the beginning of the
56 table headers (SkSFNTDirEntry), relative to the start of the stream.
57
58 On an error, return 0 for number of tables, and ignore offsetToDir
59 */
count_tables(SkStream * stream,int ttcIndex,size_t * offsetToDir)60 static int count_tables(SkStream* stream, int ttcIndex, size_t* offsetToDir) {
61 SkASSERT(ttcIndex >= 0);
62
63 SkAutoSMalloc<1024> storage(sizeof(SkSharedTTHeader));
64 SkSharedTTHeader* header = (SkSharedTTHeader*)storage.get();
65
66 if (!read(stream, header, sizeof(SkSharedTTHeader))) {
67 return 0;
68 }
69
70 // by default, SkSFNTHeader is at the start of the stream
71 size_t offset = 0;
72
73 // if we're really a collection, the first 4-bytes will be 'ttcf'
74 uint32_t tag = SkEndian_SwapBE32(header->fCollection.fTag);
75 if (SkSetFourByteTag('t', 't', 'c', 'f') == tag) {
76 unsigned count = SkEndian_SwapBE32(header->fCollection.fNumOffsets);
77 if ((unsigned)ttcIndex >= count) {
78 return 0;
79 }
80
81 if (ttcIndex > 0) { // need to read more of the shared header
82 stream->rewind();
83 size_t amount = sizeof(SkSharedTTHeader) + ttcIndex * sizeof(uint32_t);
84 header = (SkSharedTTHeader*)storage.reset(amount);
85 if (!read(stream, header, amount)) {
86 return 0;
87 }
88 }
89 // this is the offset to the local SkSFNTHeader
90 offset = SkEndian_SwapBE32((&header->fCollection.fOffset0)[ttcIndex]);
91 stream->rewind();
92 if (!skip(stream, offset)) {
93 return 0;
94 }
95 if (!read(stream, header, sizeof(SkSFNTHeader))) {
96 return 0;
97 }
98 }
99
100 if (offsetToDir) {
101 // add the size of the header, so we will point to the DirEntries
102 *offsetToDir = offset + sizeof(SkSFNTHeader);
103 }
104 return SkEndian_SwapBE16(header->fSingle.fNumTables);
105 }
106
107 ///////////////////////////////////////////////////////////////////////////////
108
109 struct SfntHeader {
SfntHeaderSfntHeader110 SfntHeader() : fCount(0), fDir(nullptr) {}
~SfntHeaderSfntHeader111 ~SfntHeader() { sk_free(fDir); }
112
113 /** If it returns true, then fCount and fDir are properly initialized.
114 Note: fDir will point to the raw array of SkSFNTDirEntry values,
115 meaning they will still be in the file's native endianness (BE).
116
117 fDir will be automatically freed when this object is destroyed
118 */
initSfntHeader119 bool init(SkStream* stream, int ttcIndex) {
120 stream->rewind();
121
122 size_t offsetToDir;
123 fCount = count_tables(stream, ttcIndex, &offsetToDir);
124 if (0 == fCount) {
125 return false;
126 }
127
128 stream->rewind();
129 if (!skip(stream, offsetToDir)) {
130 return false;
131 }
132
133 size_t size = fCount * sizeof(SkSFNTDirEntry);
134 fDir = reinterpret_cast<SkSFNTDirEntry*>(sk_malloc_throw(size));
135 return read(stream, fDir, size);
136 }
137
138 int fCount;
139 SkSFNTDirEntry* fDir;
140 };
141
142 ///////////////////////////////////////////////////////////////////////////////
143
CountTTCEntries(SkStream * stream)144 int SkFontStream::CountTTCEntries(SkStream* stream) {
145 stream->rewind();
146
147 SkSharedTTHeader shared;
148 if (!read(stream, &shared, sizeof(shared))) {
149 return 0;
150 }
151
152 // if we're really a collection, the first 4-bytes will be 'ttcf'
153 uint32_t tag = SkEndian_SwapBE32(shared.fCollection.fTag);
154 if (SkSetFourByteTag('t', 't', 'c', 'f') == tag) {
155 return SkEndian_SwapBE32(shared.fCollection.fNumOffsets);
156 } else {
157 return 1; // normal 'sfnt' has 1 dir entry
158 }
159 }
160
GetTableTags(SkStream * stream,int ttcIndex,SkFontTableTag tags[])161 int SkFontStream::GetTableTags(SkStream* stream, int ttcIndex,
162 SkFontTableTag tags[]) {
163 SfntHeader header;
164 if (!header.init(stream, ttcIndex)) {
165 return 0;
166 }
167
168 if (tags) {
169 for (int i = 0; i < header.fCount; i++) {
170 tags[i] = SkEndian_SwapBE32(header.fDir[i].fTag);
171 }
172 }
173 return header.fCount;
174 }
175
GetTableData(SkStream * stream,int ttcIndex,SkFontTableTag tag,size_t offset,size_t length,void * data)176 size_t SkFontStream::GetTableData(SkStream* stream, int ttcIndex,
177 SkFontTableTag tag,
178 size_t offset, size_t length, void* data) {
179 SfntHeader header;
180 if (!header.init(stream, ttcIndex)) {
181 return 0;
182 }
183
184 for (int i = 0; i < header.fCount; i++) {
185 if (SkEndian_SwapBE32(header.fDir[i].fTag) == tag) {
186 size_t realOffset = SkEndian_SwapBE32(header.fDir[i].fOffset);
187 size_t realLength = SkEndian_SwapBE32(header.fDir[i].fLength);
188 if (offset >= realLength) {
189 // invalid
190 return 0;
191 }
192 // if the caller is trusting the length from the file, then a
193 // hostile file might choose a value which would overflow offset +
194 // length.
195 if (offset + length < offset) {
196 return 0;
197 }
198 if (length > realLength - offset) {
199 length = realLength - offset;
200 }
201 if (data) {
202 // skip the stream to the part of the table we want to copy from
203 stream->rewind();
204 size_t bytesToSkip = realOffset + offset;
205 if (!skip(stream, bytesToSkip)) {
206 return 0;
207 }
208 if (!read(stream, data, length)) {
209 return 0;
210 }
211 }
212 return length;
213 }
214 }
215 return 0;
216 }
217