michael@0: /* michael@0: * Copyright 2011 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: #include "SkEndian.h" michael@0: #include "SkFontStream.h" michael@0: #include "SkStream.h" michael@0: michael@0: struct SkSFNTHeader { michael@0: uint32_t fVersion; michael@0: uint16_t fNumTables; michael@0: uint16_t fSearchRange; michael@0: uint16_t fEntrySelector; michael@0: uint16_t fRangeShift; michael@0: }; michael@0: michael@0: struct SkTTCFHeader { michael@0: uint32_t fTag; michael@0: uint32_t fVersion; michael@0: uint32_t fNumOffsets; michael@0: uint32_t fOffset0; // the first of N (fNumOffsets) michael@0: }; michael@0: michael@0: union SkSharedTTHeader { michael@0: SkSFNTHeader fSingle; michael@0: SkTTCFHeader fCollection; michael@0: }; michael@0: michael@0: struct SkSFNTDirEntry { michael@0: uint32_t fTag; michael@0: uint32_t fChecksum; michael@0: uint32_t fOffset; michael@0: uint32_t fLength; michael@0: }; michael@0: michael@0: static bool read(SkStream* stream, void* buffer, size_t amount) { michael@0: return stream->read(buffer, amount) == amount; michael@0: } michael@0: michael@0: static bool skip(SkStream* stream, size_t amount) { michael@0: return stream->skip(amount) == amount; michael@0: } michael@0: michael@0: /** Return the number of tables, or if this is a TTC (collection), return the michael@0: number of tables in the first element of the collection. In either case, michael@0: if offsetToDir is not-null, set it to the offset to the beginning of the michael@0: table headers (SkSFNTDirEntry), relative to the start of the stream. michael@0: michael@0: On an error, return 0 for number of tables, and ignore offsetToDir michael@0: */ michael@0: static int count_tables(SkStream* stream, int ttcIndex, size_t* offsetToDir) { michael@0: SkASSERT(ttcIndex >= 0); michael@0: michael@0: SkAutoSMalloc<1024> storage(sizeof(SkSharedTTHeader)); michael@0: SkSharedTTHeader* header = (SkSharedTTHeader*)storage.get(); michael@0: michael@0: if (!read(stream, header, sizeof(SkSharedTTHeader))) { michael@0: return 0; michael@0: } michael@0: michael@0: // by default, SkSFNTHeader is at the start of the stream michael@0: size_t offset = 0; michael@0: michael@0: // if we're really a collection, the first 4-bytes will be 'ttcf' michael@0: uint32_t tag = SkEndian_SwapBE32(header->fCollection.fTag); michael@0: if (SkSetFourByteTag('t', 't', 'c', 'f') == tag) { michael@0: unsigned count = SkEndian_SwapBE32(header->fCollection.fNumOffsets); michael@0: if ((unsigned)ttcIndex >= count) { michael@0: return 0; michael@0: } michael@0: michael@0: if (ttcIndex > 0) { // need to read more of the shared header michael@0: stream->rewind(); michael@0: size_t amount = sizeof(SkSharedTTHeader) + ttcIndex * sizeof(uint32_t); michael@0: header = (SkSharedTTHeader*)storage.reset(amount); michael@0: if (!read(stream, header, amount)) { michael@0: return 0; michael@0: } michael@0: } michael@0: // this is the offset to the local SkSFNTHeader michael@0: offset = SkEndian_SwapBE32((&header->fCollection.fOffset0)[ttcIndex]); michael@0: stream->rewind(); michael@0: if (!skip(stream, offset)) { michael@0: return 0; michael@0: } michael@0: if (!read(stream, header, sizeof(SkSFNTHeader))) { michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: if (offsetToDir) { michael@0: // add the size of the header, so we will point to the DirEntries michael@0: *offsetToDir = offset + sizeof(SkSFNTHeader); michael@0: } michael@0: return SkEndian_SwapBE16(header->fSingle.fNumTables); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: struct SfntHeader { michael@0: SfntHeader() : fCount(0), fDir(NULL) {} michael@0: ~SfntHeader() { sk_free(fDir); } michael@0: michael@0: /** If it returns true, then fCount and fDir are properly initialized. michael@0: Note: fDir will point to the raw array of SkSFNTDirEntry values, michael@0: meaning they will still be in the file's native endianness (BE). michael@0: michael@0: fDir will be automatically freed when this object is destroyed michael@0: */ michael@0: bool init(SkStream* stream, int ttcIndex) { michael@0: stream->rewind(); michael@0: michael@0: size_t offsetToDir; michael@0: fCount = count_tables(stream, ttcIndex, &offsetToDir); michael@0: if (0 == fCount) { michael@0: return false; michael@0: } michael@0: michael@0: stream->rewind(); michael@0: if (!skip(stream, offsetToDir)) { michael@0: return false; michael@0: } michael@0: michael@0: size_t size = fCount * sizeof(SkSFNTDirEntry); michael@0: fDir = reinterpret_cast(sk_malloc_throw(size)); michael@0: return read(stream, fDir, size); michael@0: } michael@0: michael@0: int fCount; michael@0: SkSFNTDirEntry* fDir; michael@0: }; michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: int SkFontStream::CountTTCEntries(SkStream* stream) { michael@0: stream->rewind(); michael@0: michael@0: SkSharedTTHeader shared; michael@0: if (!read(stream, &shared, sizeof(shared))) { michael@0: return 0; michael@0: } michael@0: michael@0: // if we're really a collection, the first 4-bytes will be 'ttcf' michael@0: uint32_t tag = SkEndian_SwapBE32(shared.fCollection.fTag); michael@0: if (SkSetFourByteTag('t', 't', 'c', 'f') == tag) { michael@0: return SkEndian_SwapBE32(shared.fCollection.fNumOffsets); michael@0: } else { michael@0: return 1; // normal 'sfnt' has 1 dir entry michael@0: } michael@0: } michael@0: michael@0: int SkFontStream::GetTableTags(SkStream* stream, int ttcIndex, michael@0: SkFontTableTag tags[]) { michael@0: SfntHeader header; michael@0: if (!header.init(stream, ttcIndex)) { michael@0: return 0; michael@0: } michael@0: michael@0: if (tags) { michael@0: for (int i = 0; i < header.fCount; i++) { michael@0: tags[i] = SkEndian_SwapBE32(header.fDir[i].fTag); michael@0: } michael@0: } michael@0: return header.fCount; michael@0: } michael@0: michael@0: size_t SkFontStream::GetTableData(SkStream* stream, int ttcIndex, michael@0: SkFontTableTag tag, michael@0: size_t offset, size_t length, void* data) { michael@0: SfntHeader header; michael@0: if (!header.init(stream, ttcIndex)) { michael@0: return 0; michael@0: } michael@0: michael@0: for (int i = 0; i < header.fCount; i++) { michael@0: if (SkEndian_SwapBE32(header.fDir[i].fTag) == tag) { michael@0: size_t realOffset = SkEndian_SwapBE32(header.fDir[i].fOffset); michael@0: size_t realLength = SkEndian_SwapBE32(header.fDir[i].fLength); michael@0: // now sanity check the caller's offset/length michael@0: if (offset >= realLength) { michael@0: return 0; michael@0: } michael@0: // if the caller is trusting the length from the file, then a michael@0: // hostile file might choose a value which would overflow offset + michael@0: // length. michael@0: if (offset + length < offset) { michael@0: return 0; michael@0: } michael@0: if (length > realLength - offset) { michael@0: length = realLength - offset; michael@0: } michael@0: if (data) { michael@0: // skip the stream to the part of the table we want to copy from michael@0: stream->rewind(); michael@0: size_t bytesToSkip = realOffset + offset; michael@0: if (!skip(stream, bytesToSkip)) { michael@0: return 0; michael@0: } michael@0: if (!read(stream, data, length)) { michael@0: return 0; michael@0: } michael@0: } michael@0: return length; michael@0: } michael@0: } michael@0: return 0; michael@0: }