1
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 */
8#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
39/** 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.
43
44    On an error, return 0 for number of tables, and ignore offsetToDir
45 */
46static 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
52    // by default, SkSFNTHeader is at the start of the stream
53    size_t offset = 0;
54
55    // if we're really a collection, the first 4-bytes will be 'ttcf'
56    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        }
61        // this is the offset to the first local SkSFNTHeader
62        offset = SkEndian_SwapBE32(shared.fCollection.fOffset0);
63        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        }
70    }
71
72    if (offsetToDir) {
73        // add the size of the header, so we will point to the DirEntries
74        *offsetToDir = offset + sizeof(SkSFNTHeader);
75    }
76    return SkEndian_SwapBE16(shared.fSingle.fNumTables);
77}
78
79///////////////////////////////////////////////////////////////////////////////
80
81struct SfntHeader {
82    SfntHeader() : fCount(0), fDir(NULL) {}
83    ~SfntHeader() { sk_free(fDir); }
84
85    /** 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).
88
89        fDir will be automatically freed when this object is destroyed
90     */
91    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();
99        if (stream->skip(offsetToDir) != offsetToDir) {
100            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    }
107
108    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    }
129
130    SkAutoUnref au(stream);
131    SfntHeader  header;
132    if (!header.init(stream)) {
133        return 0;
134    }
135
136    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    }
147
148    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    }
168
169    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            }
183            // 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            }
189            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