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