blob: 9878119a6410b582b501a42bd48661792a0f12c9 [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2011 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
reed@android.comd6638e62009-04-08 05:03:52 +00008#include "SkEndian.h"
9#include "SkFontHost.h"
10#include "SkStream.h"
11
12struct SkSFNTHeader {
13 uint32_t fVersion;
14 uint16_t fNumTables;
15 uint16_t fSearchRange;
16 uint16_t fEntrySelector;
17 uint16_t fRangeShift;
18};
19
20struct SkTTCFHeader {
21 uint32_t fTag;
22 uint32_t fVersion;
23 uint32_t fNumOffsets;
24 uint32_t fOffset0; // the first of N (fNumOffsets)
25};
26
27union SkSharedTTHeader {
28 SkSFNTHeader fSingle;
29 SkTTCFHeader fCollection;
30};
31
32struct SkSFNTDirEntry {
33 uint32_t fTag;
34 uint32_t fChecksum;
35 uint32_t fOffset;
36 uint32_t fLength;
37};
38
reed@android.com9781ca52009-04-14 14:28:22 +000039/** Return the number of tables, or if this is a TTC (collection), return the
40 number of tables in the first element of the collection. In either case,
41 if offsetToDir is not-null, set it to the offset to the beginning of the
42 table headers (SkSFNTDirEntry), relative to the start of the stream.
reed@android.com304b59e2009-04-14 15:10:55 +000043
reed@android.com9781ca52009-04-14 14:28:22 +000044 On an error, return 0 for number of tables, and ignore offsetToDir
45 */
reed@android.comd6638e62009-04-08 05:03:52 +000046static int count_tables(SkStream* stream, size_t* offsetToDir = NULL) {
47 SkSharedTTHeader shared;
48 if (stream->read(&shared, sizeof(shared)) != sizeof(shared)) {
49 return 0;
50 }
51
reed@android.com9781ca52009-04-14 14:28:22 +000052 // by default, SkSFNTHeader is at the start of the stream
53 size_t offset = 0;
reed@android.com304b59e2009-04-14 15:10:55 +000054
reed@android.com9781ca52009-04-14 14:28:22 +000055 // if we're really a collection, the first 4-bytes will be 'ttcf'
reed@android.comd6638e62009-04-08 05:03:52 +000056 uint32_t tag = SkEndian_SwapBE32(shared.fCollection.fTag);
57 if (SkSetFourByteTag('t', 't', 'c', 'f') == tag) {
58 if (shared.fCollection.fNumOffsets == 0) {
59 return 0;
60 }
reed@android.com9781ca52009-04-14 14:28:22 +000061 // this is the offset to the first local SkSFNTHeader
62 offset = SkEndian_SwapBE32(shared.fCollection.fOffset0);
reed@android.comd6638e62009-04-08 05:03:52 +000063 stream->rewind();
64 if (stream->skip(offset) != offset) {
65 return 0;
66 }
67 if (stream->read(&shared, sizeof(shared)) != sizeof(shared)) {
68 return 0;
69 }
reed@android.comd6638e62009-04-08 05:03:52 +000070 }
reed@android.com304b59e2009-04-14 15:10:55 +000071
reed@android.com9781ca52009-04-14 14:28:22 +000072 if (offsetToDir) {
73 // add the size of the header, so we will point to the DirEntries
agl@chromium.orgb9890c12009-05-07 00:48:12 +000074 *offsetToDir = offset + sizeof(SkSFNTHeader);
reed@android.com9781ca52009-04-14 14:28:22 +000075 }
reed@android.comd6638e62009-04-08 05:03:52 +000076 return SkEndian_SwapBE16(shared.fSingle.fNumTables);
77}
78
79///////////////////////////////////////////////////////////////////////////////
80
81struct SfntHeader {
82 SfntHeader() : fCount(0), fDir(NULL) {}
83 ~SfntHeader() { sk_free(fDir); }
agl@chromium.orgfc3ac322009-04-13 21:43:40 +000084
reed@android.com9781ca52009-04-14 14:28:22 +000085 /** If it returns true, then fCount and fDir are properly initialized.
86 Note: fDir will point to the raw array of SkSFNTDirEntry values,
87 meaning they will still be in the file's native endianness (BE).
reed@android.com304b59e2009-04-14 15:10:55 +000088
reed@android.com9781ca52009-04-14 14:28:22 +000089 fDir will be automatically freed when this object is destroyed
90 */
reed@android.comd6638e62009-04-08 05:03:52 +000091 bool init(SkStream* stream) {
92 size_t offsetToDir;
93 fCount = count_tables(stream, &offsetToDir);
94 if (0 == fCount) {
95 return false;
96 }
97
98 stream->rewind();
reed@android.com9781ca52009-04-14 14:28:22 +000099 if (stream->skip(offsetToDir) != offsetToDir) {
reed@android.comd6638e62009-04-08 05:03:52 +0000100 return false;
101 }
102
103 size_t size = fCount * sizeof(SkSFNTDirEntry);
104 fDir = reinterpret_cast<SkSFNTDirEntry*>(sk_malloc_throw(size));
105 return stream->read(fDir, size) == size;
106 }
agl@chromium.orgfc3ac322009-04-13 21:43:40 +0000107
reed@android.comd6638e62009-04-08 05:03:52 +0000108 int fCount;
109 SkSFNTDirEntry* fDir;
110};
111
112///////////////////////////////////////////////////////////////////////////////
113
114int SkFontHost::CountTables(SkFontID fontID) {
115 SkStream* stream = SkFontHost::OpenStream(fontID);
116 if (NULL == stream) {
117 return 0;
118 }
119
120 SkAutoUnref au(stream);
121 return count_tables(stream);
122}
123
124int SkFontHost::GetTableTags(SkFontID fontID, SkFontTableTag tags[]) {
125 SkStream* stream = SkFontHost::OpenStream(fontID);
126 if (NULL == stream) {
127 return 0;
128 }
agl@chromium.orgfc3ac322009-04-13 21:43:40 +0000129
reed@android.comd6638e62009-04-08 05:03:52 +0000130 SkAutoUnref au(stream);
131 SfntHeader header;
132 if (!header.init(stream)) {
133 return 0;
134 }
agl@chromium.orgfc3ac322009-04-13 21:43:40 +0000135
reed@android.comd6638e62009-04-08 05:03:52 +0000136 for (int i = 0; i < header.fCount; i++) {
137 tags[i] = SkEndian_SwapBE32(header.fDir[i].fTag);
138 }
139 return header.fCount;
140}
141
142size_t SkFontHost::GetTableSize(SkFontID fontID, SkFontTableTag tag) {
143 SkStream* stream = SkFontHost::OpenStream(fontID);
144 if (NULL == stream) {
145 return 0;
146 }
agl@chromium.orgfc3ac322009-04-13 21:43:40 +0000147
reed@android.comd6638e62009-04-08 05:03:52 +0000148 SkAutoUnref au(stream);
149 SfntHeader header;
150 if (!header.init(stream)) {
151 return 0;
152 }
153
154 for (int i = 0; i < header.fCount; i++) {
155 if (SkEndian_SwapBE32(header.fDir[i].fTag) == tag) {
156 return SkEndian_SwapBE32(header.fDir[i].fLength);
157 }
158 }
159 return 0;
160}
161
162size_t SkFontHost::GetTableData(SkFontID fontID, SkFontTableTag tag,
163 size_t offset, size_t length, void* data) {
164 SkStream* stream = SkFontHost::OpenStream(fontID);
165 if (NULL == stream) {
166 return 0;
167 }
agl@chromium.orgfc3ac322009-04-13 21:43:40 +0000168
reed@android.comd6638e62009-04-08 05:03:52 +0000169 SkAutoUnref au(stream);
170 SfntHeader header;
171 if (!header.init(stream)) {
172 return 0;
173 }
174
175 for (int i = 0; i < header.fCount; i++) {
176 if (SkEndian_SwapBE32(header.fDir[i].fTag) == tag) {
177 size_t realOffset = SkEndian_SwapBE32(header.fDir[i].fOffset);
178 size_t realLength = SkEndian_SwapBE32(header.fDir[i].fLength);
179 // now sanity check the caller's offset/length
180 if (offset >= realLength) {
181 return 0;
182 }
agl@chromium.orgfc3ac322009-04-13 21:43:40 +0000183 // if the caller is trusting the length from the file, then a
184 // hostile file might choose a value which would overflow offset +
185 // length.
186 if (offset + length < offset) {
187 return 0;
188 }
reed@android.comd6638e62009-04-08 05:03:52 +0000189 if (offset + length > realLength) {
190 length = realLength - offset;
191 }
192 // skip the stream to the part of the table we want to copy from
193 stream->rewind();
194 size_t bytesToSkip = realOffset + offset;
195 if (stream->skip(bytesToSkip) != bytesToSkip) {
196 return 0;
197 }
198 if (stream->read(data, length) != length) {
199 return 0;
200 }
201 return length;
202 }
203 }
204 return 0;
205}
206