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