diff options
-rw-r--r-- | INSTALL | 5 | ||||
-rw-r--r-- | LICENSE | 27 | ||||
-rw-r--r-- | README | 20 | ||||
-rw-r--r-- | include/opentype-sanitiser.h | 180 | ||||
-rw-r--r-- | include/ots-memory-stream.h | 50 | ||||
-rw-r--r-- | src/cff.cc | 895 | ||||
-rw-r--r-- | src/cff.h | 19 | ||||
-rw-r--r-- | src/cmap.cc | 757 | ||||
-rw-r--r-- | src/cmap.h | 39 | ||||
-rw-r--r-- | src/cvt.cc | 56 | ||||
-rw-r--r-- | src/cvt.h | 19 | ||||
-rw-r--r-- | src/fpgm.cc | 50 | ||||
-rw-r--r-- | src/fpgm.h | 19 | ||||
-rw-r--r-- | src/gasp.cc | 106 | ||||
-rw-r--r-- | src/gasp.h | 22 | ||||
-rw-r--r-- | src/glyf.cc | 265 | ||||
-rw-r--r-- | src/glyf.h | 21 | ||||
-rw-r--r-- | src/hdmx.cc | 132 | ||||
-rw-r--r-- | src/hdmx.h | 29 | ||||
-rw-r--r-- | src/head.cc | 146 | ||||
-rw-r--r-- | src/head.h | 29 | ||||
-rw-r--r-- | src/hhea.cc | 120 | ||||
-rw-r--r-- | src/hhea.h | 28 | ||||
-rw-r--r-- | src/hmtx.cc | 107 | ||||
-rw-r--r-- | src/hmtx.h | 22 | ||||
-rw-r--r-- | src/loca.cc | 98 | ||||
-rw-r--r-- | src/loca.h | 20 | ||||
-rw-r--r-- | src/ltsh.cc | 82 | ||||
-rw-r--r-- | src/ltsh.h | 21 | ||||
-rw-r--r-- | src/maxp.cc | 128 | ||||
-rw-r--r-- | src/maxp.h | 35 | ||||
-rw-r--r-- | src/name.cc | 86 | ||||
-rw-r--r-- | src/os2.cc | 290 | ||||
-rw-r--r-- | src/os2.h | 54 | ||||
-rw-r--r-- | src/ots.cc | 505 | ||||
-rw-r--r-- | src/ots.h | 192 | ||||
-rw-r--r-- | src/post.cc | 181 | ||||
-rw-r--r-- | src/post.h | 29 | ||||
-rw-r--r-- | src/prep.cc | 50 | ||||
-rw-r--r-- | src/prep.h | 19 | ||||
-rw-r--r-- | src/vdmx.cc | 176 | ||||
-rw-r--r-- | src/vdmx.h | 45 | ||||
-rw-r--r-- | src/vorg.cc | 97 | ||||
-rw-r--r-- | src/vorg.h | 28 | ||||
-rw-r--r-- | test/README | 243 | ||||
-rw-r--r-- | test/SConstruct | 43 | ||||
-rw-r--r-- | test/file-stream.h | 39 | ||||
-rw-r--r-- | test/idempotent.cc | 94 | ||||
-rw-r--r-- | test/ot-sanitise.cc | 54 | ||||
-rw-r--r-- | test/perf.cc | 79 | ||||
-rw-r--r-- | test/side-by-side.cc | 281 | ||||
-rw-r--r-- | test/validator-checker.cc | 117 | ||||
-rwxr-xr-x | tools/ttf-checksum.py | 44 |
53 files changed, 6293 insertions, 0 deletions
@@ -0,0 +1,5 @@ +How to build (on Linux): + + $ cd ots/test/ + $ sudo apt-get install scons g++ libfreetype6-dev + $ scons @@ -0,0 +1,27 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. @@ -0,0 +1,20 @@ +Sanitiser for OpenType +---------------------- + +(Idea from Alex Russell) + +The CSS font-face property[1] is great for web typography. Having to use images +in order to get the correct typeface is a great sadness; one should be able to +use vectors. + +However, the TrueType renderers on many platforms have never been part of the +attack surface before and putting them on the front line is a scary proposition. +Esp on platforms like Windows where it's a closed-source blob running with high +privilege. + +Thus, the OpenType Sanitiser (OTS) is designed to parse and serialise OpenType +files, validating them and sanitising them as it goes. + +This is just an initial release. Do not use it in anger yet. + +[1] http://www.w3.org/TR/CSS2/fonts.html#font-descriptions diff --git a/include/opentype-sanitiser.h b/include/opentype-sanitiser.h new file mode 100644 index 0000000..dc593ba --- /dev/null +++ b/include/opentype-sanitiser.h @@ -0,0 +1,180 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OPENTYPE_SANITISER_H_ +#define OPENTYPE_SANITISER_H_ + +#if defined(_MSC_VER) +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +#include <Winsock2.h> // for htons/ntohs +#else +#include <arpa/inet.h> +#include <stdint.h> +#endif + +#include <algorithm> // for std::min +#include <cassert> +#include <cstring> + +namespace ots { + +// ----------------------------------------------------------------------------- +// This is an interface for an abstract stream class which is used for writing +// the serialised results out. +// ----------------------------------------------------------------------------- +class OTSStream { + public: + OTSStream() { + ResetChecksum(); + } + + virtual ~OTSStream() {} + + // This should be implemented to perform the actual write. + virtual bool WriteRaw(const void *data, size_t length) = 0; + + bool Write(const void *data, size_t length) { + if (!length) return false; + + const size_t orig_length = length; + size_t offset = 0; + if (chksum_buffer_offset_) { + const size_t l = + std::min(length, static_cast<size_t>(4) - chksum_buffer_offset_); + std::memcpy(chksum_buffer_ + chksum_buffer_offset_, data, l); + chksum_buffer_offset_ += l; + offset += l; + length -= l; + } + + if (chksum_buffer_offset_ == 4) { + chksum_ += ntohl(*reinterpret_cast<const uint32_t*>(chksum_buffer_)); + chksum_buffer_offset_ = 0; + } + + while (length >= 4) { + chksum_ += ntohl(*reinterpret_cast<const uint32_t*>( + reinterpret_cast<const uint8_t*>(data) + offset)); + length -= 4; + offset += 4; + } + + if (length) { + if (chksum_buffer_offset_ != 0) return false; // not reached + if (length > 4) return false; // not reached + std::memcpy(chksum_buffer_, + reinterpret_cast<const uint8_t*>(data) + offset, length); + chksum_buffer_offset_ = length; + } + + return WriteRaw(data, orig_length); + } + + virtual bool Seek(off_t position) = 0; + virtual off_t Tell() const = 0; + + virtual bool Pad(size_t bytes) { + static const uint32_t kZero = 0; + while (bytes >= 4) { + if (!WriteTag(kZero)) return false; + bytes -= 4; + } + while (bytes) { + static const uint8_t kZerob = 0; + if (!Write(&kZerob, 1)) return false; + bytes--; + } + return true; + } + + bool WriteU16(uint16_t v) { + v = htons(v); + return Write(&v, sizeof(v)); + } + + bool WriteS16(int16_t v) { + v = htons(v); + return Write(&v, sizeof(v)); + } + + bool WriteU32(uint32_t v) { + v = htonl(v); + return Write(&v, sizeof(v)); + } + + bool WriteS32(int32_t v) { + v = htonl(v); + return Write(&v, sizeof(v)); + } + + bool WriteR64(uint64_t v) { + return Write(&v, sizeof(v)); + } + + bool WriteTag(uint32_t v) { + return Write(&v, sizeof(v)); + } + + void ResetChecksum() { + chksum_ = 0; + chksum_buffer_offset_ = 0; + } + + uint32_t chksum() const { + assert(chksum_buffer_offset_ == 0); + return chksum_; + } + + struct ChecksumState { + uint32_t chksum; + uint8_t chksum_buffer[4]; + unsigned chksum_buffer_offset; + }; + + ChecksumState SaveChecksumState() const { + ChecksumState s; + s.chksum = chksum_; + s.chksum_buffer_offset = chksum_buffer_offset_; + std::memcpy(s.chksum_buffer, chksum_buffer_, 4); + + return s; + } + + void RestoreChecksum(const ChecksumState &s) { + assert(chksum_buffer_offset_ == 0); + chksum_ += s.chksum; + chksum_buffer_offset_ = s.chksum_buffer_offset; + std::memcpy(chksum_buffer_, s.chksum_buffer, 4); + } + + protected: + uint32_t chksum_; + uint8_t chksum_buffer_[4]; + unsigned chksum_buffer_offset_; +}; + +// ----------------------------------------------------------------------------- +// Process a given OpenType file and write out a sanitised version +// output: a pointer to an object implementing the OTSStream interface. The +// sanitisied output will be written to this. In the even of a failure, +// partial output may have been written. +// input: the OpenType file +// length: the size, in bytes, of |input| +// ----------------------------------------------------------------------------- +bool Process(OTSStream *output, const uint8_t *input, size_t length); + +// Force to disable debug output even when the library is compiled with +// -DOTS_DEBUG. +void DisableDebugOutput(); + +} // namespace ots + +#endif // OPENTYPE_SANITISER_H_ diff --git a/include/ots-memory-stream.h b/include/ots-memory-stream.h new file mode 100644 index 0000000..42b21f1 --- /dev/null +++ b/include/ots-memory-stream.h @@ -0,0 +1,50 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_MEMORY_STREAM_H_ +#define OTS_MEMORY_STREAM_H_ + +#include <cstring> +#include <limits> + +#include "opentype-sanitiser.h" + +namespace ots { + +class MemoryStream : public OTSStream { + public: + MemoryStream(void *ptr, size_t length) + : ptr_(ptr), length_(length), off_(0) { + } + + bool WriteRaw(const void *data, size_t length) { + if ((off_ + length > length_) || + (length > std::numeric_limits<size_t>::max() - off_)) { + return false; + } + std::memcpy(static_cast<char*>(ptr_) + off_, data, length); + off_ += length; + return true; + } + + bool Seek(off_t position) { + if (position < 0) return false; + if (static_cast<size_t>(position) > length_) return false; + off_ = position; + return true; + } + + off_t Tell() const { + return off_; + } + + private: + void * const ptr_; + size_t length_; + off_t off_; +}; + +} // namespace ots + +#endif // OTS_MEMORY_STREAM_H_ diff --git a/src/cff.cc b/src/cff.cc new file mode 100644 index 0000000..28676b5 --- /dev/null +++ b/src/cff.cc @@ -0,0 +1,895 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cff.h" + +#include <cstring> +#include <utility> // std::pair +#include <vector> + +// CFF - PostScript font program (Compact Font Format) table +// http://www.microsoft.com/opentype/otspec/cff.htm +// http://www.microsoft.com/opentype/otspec/5176.CFF.pdf + +namespace { + +struct CFFIndex { + CFFIndex() + : count(0), off_size(0) {} + uint16_t count; + uint8_t off_size; + std::vector<uint32_t> offsets; +}; + +enum DICT_OPERAND_TYPE { + DICT_OPERAND_INTEGER, + DICT_OPERAND_REAL, + DICT_OPERATOR, +}; + +// see Appendix. A +const size_t kNStdString = 390; + +bool ReadOffset(ots::Buffer *table, uint8_t off_size, uint32_t *offset) { + if (off_size > 4) { + return OTS_FAILURE(); + } + + uint32_t tmp32 = 0; + for (unsigned i = 0; i < off_size; ++i) { + uint8_t tmp8 = 0; + if (!table->ReadU8(&tmp8)) { + return OTS_FAILURE(); + } + tmp32 <<= 8; + tmp32 += tmp8; + } + *offset = tmp32; + return true; +} + +bool ParseIndex(ots::Buffer *table, CFFIndex *index) { + index->off_size = 0; + index->offsets.clear(); + + if (!table->ReadU16(&(index->count))) { + return OTS_FAILURE(); + } + if (index->count == 0) { + return true; + } + + if (!table->ReadU8(&(index->off_size))) { + return OTS_FAILURE(); + } + if ((index->off_size == 0) || + (index->off_size > 4)) { + return OTS_FAILURE(); + } + + const size_t array_size = (index->count + 1) * index->off_size; + // less than ((64k + 1) * 4), thus does not overflow. + const size_t object_data_offset = table->offset() + array_size; + // does not overflow too, since offset() <= 1GB. + + if (object_data_offset >= table->length()) { + return OTS_FAILURE(); + } + + for (unsigned i = 0; i <= index->count; ++i) { // '<=' is not a typo. + uint32_t rel_offset; + if (!ReadOffset(table, index->off_size, &rel_offset)) { + return OTS_FAILURE(); + } + if (rel_offset < 1) { + return OTS_FAILURE(); + } + if (i == 0 && rel_offset != 1) { + return OTS_FAILURE(); + } + + if (rel_offset > table->length()) { + return OTS_FAILURE(); + } + + // does not underflow. + if (object_data_offset > table->length() - (rel_offset - 1)) { + return OTS_FAILURE(); + } + + index->offsets.push_back( + object_data_offset + (rel_offset - 1)); // less than length(), 1GB. + } + + for (unsigned i = 1; i < index->offsets.size(); ++i) { + if (index->offsets[i] <= index->offsets[i - 1]) { + return OTS_FAILURE(); + } + } + + return true; +} + +bool ParseNameData(ots::Buffer *table, const CFFIndex &index) { + uint8_t name[256]; + for (unsigned i = 1; i < index.offsets.size(); ++i) { + size_t length = index.offsets[i] - index.offsets[i - 1]; + // font names should be no longer than 127 characters. + if (length > 127) { + return OTS_FAILURE(); + } + + table->set_offset(index.offsets[i - 1]); + if (!table->Read(name, length)) { + return OTS_FAILURE(); + } + + for (size_t j = 0; j < length; ++j) { + // setting the first byte to NUL is allowed. + if (j == 0 && name[j] == 0) continue; + // non-ASCII characters are not recommended (except the first character). + if (name[j] < 33 || name[j] > 126) { + return OTS_FAILURE(); + } + // [, ], ... are not allowed. + if (std::strchr("[](){}<>/% ", name[j])) { + return OTS_FAILURE(); + } + } + } + + return true; +} + +bool CheckOffset(const std::pair<uint32_t, DICT_OPERAND_TYPE>& operand, + size_t table_length) { + if (operand.second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + if (operand.first >= table_length) { + return OTS_FAILURE(); + } + return true; +} + +bool CheckSid(const std::pair<uint32_t, DICT_OPERAND_TYPE>& operand, + size_t sid_max) { + if (operand.second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + if (operand.first > sid_max) { + return OTS_FAILURE(); + } + return true; +} + + +bool ParseDictDataBcd( + ots::Buffer *table, + std::vector<std::pair<uint32_t, DICT_OPERAND_TYPE> > *operands) { + bool read_decimal_point = false; + bool read_e = false; + + uint8_t nibble = 0; + size_t count = 0; + while (true) { + if (!table->ReadU8(&nibble)) { + return OTS_FAILURE(); + } + if ((nibble & 0xf0) == 0xf0) { + if ((nibble & 0xf) == 0xf) { + // TODO(yusukes): would be better to store actual double value, + // rather than the dummy integer. + operands->push_back(std::make_pair(0, DICT_OPERAND_REAL)); + return true; + } + return OTS_FAILURE(); + } + if ((nibble & 0x0f) == 0x0f) { + operands->push_back(std::make_pair(0, DICT_OPERAND_REAL)); + return true; + } + + // check number format + uint8_t nibbles[2]; + nibbles[0] = (nibble & 0xf0) >> 8; + nibbles[1] = (nibble & 0x0f); + for (unsigned i = 0; i < 2; ++i) { + if (nibbles[i] == 0xd) { // reserved number + return OTS_FAILURE(); + } + if ((nibbles[i] == 0xe) && // minus + ((count > 0) || (i > 0))) { + return OTS_FAILURE(); // minus sign should be the first character. + } + if (nibbles[i] == 0xa) { // decimal point + if (!read_decimal_point) { + read_decimal_point = true; + } else { + return OTS_FAILURE(); // two or more points. + } + } + if ((nibbles[i] == 0xb) || // E+ + (nibbles[i] == 0xc)) { // E- + if (!read_e) { + read_e = true; + } else { + return OTS_FAILURE(); // two or more E's. + } + } + } + ++count; + } +} + +bool ParseDictDataEscapedOperator( + ots::Buffer *table, + std::vector<std::pair<uint32_t, DICT_OPERAND_TYPE> > *operands) { + uint8_t op = 0; + if (!table->ReadU8(&op)) { + return OTS_FAILURE(); + } + + if ((op <= 14) || + (op >= 17 && op <= 23) || + (op >= 30 && op <= 38)) { + operands->push_back(std::make_pair((12U << 8) + op, DICT_OPERATOR)); + return true; + } + + // reserved area. + return OTS_FAILURE(); +} + +bool ParseDictDataNumber( + ots::Buffer *table, uint8_t b0, + std::vector<std::pair<uint32_t, DICT_OPERAND_TYPE> > *operands) { + uint8_t b1 = 0; + uint8_t b2 = 0; + uint8_t b3 = 0; + uint8_t b4 = 0; + + switch (b0) { + case 28: // shortint + if (!table->ReadU8(&b1) || + !table->ReadU8(&b2)) { + return OTS_FAILURE(); + } + operands->push_back(std::make_pair((b1 << 8) + b2, DICT_OPERAND_INTEGER)); + return true; + + case 29: // longint + if (!table->ReadU8(&b1) || + !table->ReadU8(&b2) || + !table->ReadU8(&b3) || + !table->ReadU8(&b4)) { + return OTS_FAILURE(); + } + operands->push_back(std::make_pair( + (b1 << 24) + (b2 << 16) + (b3 << 8) + b4, DICT_OPERAND_INTEGER)); + return true; + + case 30: // binary coded decimal + return ParseDictDataBcd(table, operands); + + default: + break; + } + + uint32_t result; + if (b0 >=32 && b0 <=246) { + result = b0 - 139; + } else if (b0 >=247 && b0 <= 250) { + if (!table->ReadU8(&b1)) { + return OTS_FAILURE(); + } + result = (b0 - 247) * 256 + b1 + 108; + } else if (b0 >= 251 && b0 <= 254) { + if (!table->ReadU8(&b1)) { + return OTS_FAILURE(); + } + result = -(b0 - 251) * 256 + b1 - 108; + } else { + return OTS_FAILURE(); + } + + operands->push_back(std::make_pair(result, DICT_OPERAND_INTEGER)); + return true; +} + +bool ParseDictDataReadNext( + ots::Buffer *table, + std::vector<std::pair<uint32_t, DICT_OPERAND_TYPE> > *operands) { + uint8_t op = 0; + if (!table->ReadU8(&op)) { + return OTS_FAILURE(); + } + if (op <= 21) { + if (op == 12) { + return ParseDictDataEscapedOperator(table, operands); + } + operands->push_back(std::make_pair(op, DICT_OPERATOR)); + return true; + } else if (op <= 27 || op == 31 || op == 255) { + // reserved area. + return OTS_FAILURE(); + } + + return ParseDictDataNumber(table, op, operands); +} + +bool ParsePrivateDictData( + const uint8_t *data, + size_t table_length, size_t offset, size_t dict_length) { + ots::Buffer table(data + offset, dict_length); + + std::vector<std::pair<uint32_t, DICT_OPERAND_TYPE> > operands; + while (table.offset() < dict_length) { + if (!ParseDictDataReadNext(&table, &operands)) { + return OTS_FAILURE(); + } + if (operands.empty()) { + return OTS_FAILURE(); + } + if (operands.size() > 48) { + // An operator may be preceded by up to a maximum of 48 operands. + return OTS_FAILURE(); + } + if (operands.back().second != DICT_OPERATOR) { + continue; + } + + // got operator + const uint32_t op = operands.back().first; + operands.pop_back(); + + switch (op) { + // array + case 6: // BlueValues + case 7: // OtherBlues + case 8: // FamilyBlues + case 9: // FamilyOtherBlues + case (12U << 8) + 12: // StemSnapH (delta) + case (12U << 8) + 13: // StemSnapV (delta) + if (operands.empty()) { + return OTS_FAILURE(); + } + break; + + // number + case 10: // StdHW + case 11: // StdVW + case 20: // defaultWidthX + case 21: // nominalWidthX + case (12U << 8) + 9: // BlueScale + case (12U << 8) + 10: // BlueShift + case (12U << 8) + 11: // BlueFuzz + case (12U << 8) + 17: // LanguageGroup + case (12U << 8) + 18: // ExpansionFactor + case (12U << 8) + 19: // initialRandomSeed + if (operands.size() != 1) { + return OTS_FAILURE(); + } + break; + + // Local Subrs INDEX, offset(self) + case 19: { + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (operands.back().second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + if (operands.back().first >= 1024 * 1024 * 1024) { + return OTS_FAILURE(); + } + if (operands.back().first + offset >= table_length) { + return OTS_FAILURE(); + } + // parse "16. Local Subrs INDEX" + ots::Buffer table(data, table_length); + table.set_offset(operands.back().first + offset); + CFFIndex local_subrs_index; + if (!ParseIndex(&table, &local_subrs_index)) { + return OTS_FAILURE(); + } + break; + } + + // boolean + case (12U << 8) + 14: // ForceBold + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (operands.back().second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + if (operands.back().first >= 2) { + return OTS_FAILURE(); + } + break; + + default: + return OTS_FAILURE(); + } + operands.clear(); + } + + return true; +} + +bool ParseDictData(const uint8_t *data, size_t table_length, + const CFFIndex &index, size_t sid_max, bool toplevel) { + for (unsigned i = 1; i < index.offsets.size(); ++i) { + size_t dict_length = index.offsets[i] - index.offsets[i - 1]; + ots::Buffer table(data + index.offsets[i - 1], dict_length); + + std::vector<std::pair<uint32_t, DICT_OPERAND_TYPE> > operands; + + bool have_ros = false; + size_t glyphs = 0; + size_t charset_offset = 0; + + while (table.offset() < dict_length) { + if (!ParseDictDataReadNext(&table, &operands)) { + return OTS_FAILURE(); + } + if (operands.empty()) { + return OTS_FAILURE(); + } + if (operands.size() > 48) { + // An operator may be preceded by up to a maximum of 48 operands. + return OTS_FAILURE(); + } + if (operands.back().second != DICT_OPERATOR) continue; + + // got operator + const uint32_t op = operands.back().first; + operands.pop_back(); + + switch (op) { + // SID + case 0: // version + case 1: // Notice + case 2: // Copyright + case 3: // FullName + case 4: // FamilyName + case (12U << 8) + 0: // Copyright + case (12U << 8) + 21: // PostScript + case (12U << 8) + 22: // BaseFontName + case (12U << 8) + 38: // FontName + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (!CheckSid(operands.back(), sid_max)) { + return OTS_FAILURE(); + } + break; + + // array + case 5: // FontBBox + case 14: // XUID + case (12U << 8) + 7: // FontMatrix + case (12U << 8) + 23: // BaseFontBlend (delta) + if (operands.empty()) { + return OTS_FAILURE(); + } + break; + + // number + case 13: // UniqueID + case (12U << 8) + 2: // ItalicAngle + case (12U << 8) + 3: // UnderlinePosition + case (12U << 8) + 4: // UnderlineThickness + case (12U << 8) + 5: // PaintType + case (12U << 8) + 6: // CharstringType + case (12U << 8) + 8: // StrokeWidth + case (12U << 8) + 20: // SyntheticBase + case (12U << 8) + 31: // CIDFontVersion + case (12U << 8) + 32: // CIDFontRevision + case (12U << 8) + 33: // CIDFontType + case (12U << 8) + 34: // CIDCount + case (12U << 8) + 35: // UIDBase + if (operands.size() != 1) { + return OTS_FAILURE(); + } + break; + + // boolean + case (12U << 8) + 1: // isFixedPitch + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (operands.back().second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + if (operands.back().first >= 2) { + return OTS_FAILURE(); + } + break; + + // offset(0) + case 15: // charset + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (operands.back().first <= 2) { + // predefined charset, ISOAdobe, Expert or ExpertSubset, is used. + break; + } + if (!CheckOffset(operands.back(), table_length)) { + return OTS_FAILURE(); + } + if (charset_offset) { + return OTS_FAILURE(); // multiple charset tables? + } + charset_offset = operands.back().first; + break; + + case 16: { // Encoding + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (operands.back().first <= 1) { + break; // predefined encoding, "Standard" or "Expert", is used. + } + if (!CheckOffset(operands.back(), table_length)) { + return OTS_FAILURE(); + } + + // parse sub dictionary INDEX. + ots::Buffer table(data, table_length); + table.set_offset(operands.back().first); + uint8_t format = 0; + if (!table.ReadU8(&format)) { + return OTS_FAILURE(); + } + if (format & 0x80) { + // supplemental encoding is not supported at the moment. + return OTS_FAILURE(); + } + // TODO(yusukes): support & parse supplemental encoding tables. + break; + } + + case 17: { // CharStrings + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (!CheckOffset(operands.back(), table_length)) { + return OTS_FAILURE(); + } + // parse "14. CharStrings INDEX" + ots::Buffer table(data, table_length); + table.set_offset(operands.back().first); + CFFIndex charstring_index; + if (!ParseIndex(&table, &charstring_index)) { + return OTS_FAILURE(); + } + if (charstring_index.count < 2) { + return OTS_FAILURE(); + } + if (glyphs) { + return OTS_FAILURE(); // multiple charstring tables? + } + glyphs = charstring_index.count; + break; + } + + case (12U << 8) + 36: { // FDArray + if (!toplevel) { + return OTS_FAILURE(); + } + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (!CheckOffset(operands.back(), table_length)) { + return OTS_FAILURE(); + } + + // parse sub dictionary INDEX. + ots::Buffer table(data, table_length); + table.set_offset(operands.back().first); + CFFIndex sub_dict_index; + if (!ParseIndex(&table, &sub_dict_index)) { + return OTS_FAILURE(); + } + if (!ParseDictData(data, table_length, + sub_dict_index, sid_max, false /* toplevel */)) { + return OTS_FAILURE(); + } + break; + } + + case (12U << 8) + 37: { // FDSelect + if (!toplevel) { + return OTS_FAILURE(); + } + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (!CheckOffset(operands.back(), table_length)) { + return OTS_FAILURE(); + } + + // parse FDSelect data structure + ots::Buffer table(data, table_length); + table.set_offset(operands.back().first); + uint8_t format = 0; + if (!table.ReadU8(&format)) { + return OTS_FAILURE(); + } + if (format == 0) { + if (!table.Skip(glyphs)) { + return OTS_FAILURE(); + } + // TODO(yusukes): check fd value? + } else if (format == 3) { + uint16_t n_ranges; + if (!table.ReadU16(&n_ranges)) { + return OTS_FAILURE(); + } + if (n_ranges == 0) { + return OTS_FAILURE(); + } + + uint16_t last_gid = 0; + for (unsigned j = 0; j < n_ranges; ++j) { + uint16_t first; // GID + if (!table.ReadU16(&first)) { + return OTS_FAILURE(); + } + uint8_t fd = 0; + if (!table.ReadU8(&fd)) { + return OTS_FAILURE(); + } + if ((j == 0) && (first != 0)) { + return OTS_FAILURE(); + } + if ((j != 0) && (last_gid >= first)) { + return OTS_FAILURE(); // not increasing order. + } + last_gid = first; + // TODO(yusukes): check fd & GID values? + } + uint16_t sentinel; + if (!table.ReadU16(&sentinel)) { + return OTS_FAILURE(); + } + if (last_gid >= sentinel) { + return OTS_FAILURE(); + } + } else { + // unknown format + return OTS_FAILURE(); + } + break; + } + + // Private DICT (2 * number) + case 18: { + if (operands.size() != 2) { + return OTS_FAILURE(); + } + if (operands.back().second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + const uint32_t private_offset = operands.back().first; + operands.pop_back(); + if (operands.back().second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + const uint32_t private_length = operands.back().first; + if (private_offset >= table_length) { + return OTS_FAILURE(); + } + if (private_length >= table_length) { + return OTS_FAILURE(); + } + if (private_length + private_offset > table_length) { + // does not overflow since table_length < 1GB + return OTS_FAILURE(); + } + // parse "15. Private DICT Data" + if (!ParsePrivateDictData(data, table_length, + private_offset, private_length)) { + return OTS_FAILURE(); + } + break; + } + + // ROS + case (12U << 8) + 30: + if (!toplevel) { + return OTS_FAILURE(); + } + if (operands.size() != 3) { + return OTS_FAILURE(); + } + // check SIDs + operands.pop_back(); // ignore the first number. + if (!CheckSid(operands.back(), sid_max)) { + return OTS_FAILURE(); + } + operands.pop_back(); + if (!CheckSid(operands.back(), sid_max)) { + return OTS_FAILURE(); + } + if (have_ros) { + return OTS_FAILURE(); // multiple ROS tables? + } + have_ros = true; + break; + + default: + return OTS_FAILURE(); + } + operands.clear(); + } + + // parse "13. Charsets" + if (charset_offset) { + ots::Buffer table(data, table_length); + table.set_offset(charset_offset); + uint8_t format = 0; + if (!table.ReadU8(&format)) { + return OTS_FAILURE(); + } + switch (format) { + case 0: + for (unsigned j = 1 /* .notdef is omitted */; j < glyphs; ++j) { + uint16_t sid; + if (!table.ReadU16(&sid)) { + return OTS_FAILURE(); + } + if (!have_ros && (sid > sid_max)) { + return OTS_FAILURE(); + } + // TODO(yusukes): check CIDs when have_ros is true. + } + break; + + case 1: + case 2: { + uint32_t total = 1; // .notdef is omitted. + while (total < glyphs) { + uint16_t sid; + if (!table.ReadU16(&sid)) { + return OTS_FAILURE(); + } + if (!have_ros && (sid > sid_max)) { + return OTS_FAILURE(); + } + // TODO(yusukes): check CIDs when have_ros is true. + + if (format == 1) { + uint8_t left = 0; + if (!table.ReadU8(&left)) { + return OTS_FAILURE(); + } + total += (left + 1); + } else { + uint16_t left; + if (!table.ReadU16(&left)) { + return OTS_FAILURE(); + } + total += (left + 1); + } + } + break; + } + + default: + return OTS_FAILURE(); + } + } + } + return true; +} + +} // namespace + +namespace ots { + +bool ots_cff_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + + file->cff = new OpenTypeCFF; + file->cff->data = data; + file->cff->length = length; + + // parse "6. Header" in the Adobe Compact Font Format Specification + uint8_t major = 0; + uint8_t minor = 0; + uint8_t hdr_size = 0; + uint8_t off_size = 0; + if (!table.ReadU8(&major)) { + return OTS_FAILURE(); + } + if (!table.ReadU8(&minor)) { + return OTS_FAILURE(); + } + if (!table.ReadU8(&hdr_size)) { + return OTS_FAILURE(); + } + if (!table.ReadU8(&off_size)) { + return OTS_FAILURE(); + } + + if ((major != 1) || + (minor != 0) || + (hdr_size != 4)) { + return OTS_FAILURE(); + } + if (hdr_size >= length) { + return OTS_FAILURE(); + } + + // parse "7. Name INDEX" + table.set_offset(hdr_size); + CFFIndex name_index; + if (!ParseIndex(&table, &name_index)) { + return OTS_FAILURE(); + } + if (!ParseNameData(&table, name_index)) { + return OTS_FAILURE(); + } + + // parse "8. Top DICT INDEX" + table.set_offset(name_index.offsets[name_index.count]); + CFFIndex top_dict_index; + if (!ParseIndex(&table, &top_dict_index)) { + return OTS_FAILURE(); + } + if (name_index.count != top_dict_index.count) { + return OTS_FAILURE(); + } + + // parse "10. String INDEX" + table.set_offset(top_dict_index.offsets[top_dict_index.count]); + CFFIndex string_index; + if (!ParseIndex(&table, &string_index)) { + return OTS_FAILURE(); + } + if (string_index.count >= 65000 - kNStdString) { + return OTS_FAILURE(); + } + + const size_t sid_max = string_index.count + kNStdString; + // string_index.count == 0 is allowed. + + // parse "9. Top DICT Data" + if (!ParseDictData(data, length, top_dict_index, + sid_max, true /* toplevel */)) { + return OTS_FAILURE(); + } + + // parse "16. Global Subrs INDEX" + table.set_offset(string_index.offsets[string_index.count]); + CFFIndex global_subrs_index; + if (!ParseIndex(&table, &global_subrs_index)) { + return OTS_FAILURE(); + } + + return true; +} + +bool ots_cff_should_serialise(OpenTypeFile *file) { + return file->cff; +} + +bool ots_cff_serialise(OTSStream *out, OpenTypeFile *file) { + // TODO(yusukes): would be better to transcode the data, + // rather than simple memcpy. + if (!out->Write(file->cff->data, file->cff->length)) { + return OTS_FAILURE(); + } + return true; +} + +void ots_cff_free(OpenTypeFile *file) { + delete file->cff; +} + +} // namespace ots diff --git a/src/cff.h b/src/cff.h new file mode 100644 index 0000000..6d16ae8 --- /dev/null +++ b/src/cff.h @@ -0,0 +1,19 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_CFF_H_ +#define OTS_CFF_H_ + +#include "ots.h" + +namespace ots { + +struct OpenTypeCFF { + const uint8_t *data; + size_t length; +}; + +} // namespace ots + +#endif // OTS_CFF_H_ diff --git a/src/cmap.cc b/src/cmap.cc new file mode 100644 index 0000000..a65c551 --- /dev/null +++ b/src/cmap.cc @@ -0,0 +1,757 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cmap.h" + +#include "maxp.h" + +// cmap - Character To Glyph Index Mapping Table +// http://www.microsoft.com/opentype/otspec/cmap.htm + +namespace { + +struct CMAPSubtableHeader { + uint16_t platform; + uint16_t encoding; + uint32_t offset; + uint16_t format; + uint32_t length; +}; + +struct Subtable314Range { + uint16_t start_range; + uint16_t end_range; + int16_t id_delta; + uint16_t id_range_offset; + uint32_t id_range_offset_offset; +}; + +// The maximum number of groups in format 12 or 13 subtables. Set so that we'll +// allocate, at most, 8MB of memory when parsing these. +const unsigned kMaxCMAPGroups = 699050; + +// Glyph array size for the Mac Roman (format 0) table. +const size_t kFormat0ArraySize = 256; + +bool Parse3x4(ots::OpenTypeFile *file, int encoding, + const uint8_t *data, size_t length, uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // 3.0.4 or 3.1.4 subtables are complex and, rather than expanding the + // whole thing and recompacting it, we valid it and include it verbatim + // in the ouput. + + if (!subtable.Skip(4)) { + return OTS_FAILURE(); + } + uint16_t language; + if (!subtable.ReadU16(&language)) { + return OTS_FAILURE(); + } + if (language) { + return OTS_FAILURE(); + } + + uint16_t segcountx2, search_range, entry_selector, range_shift; + if (!subtable.ReadU16(&segcountx2) || + !subtable.ReadU16(&search_range) || + !subtable.ReadU16(&entry_selector) || + !subtable.ReadU16(&range_shift)) { + return OTS_FAILURE(); + } + + if (segcountx2 & 1 || search_range & 1) { + return OTS_FAILURE(); + } + const uint16_t segcount = segcountx2 >> 1; + // There must be at least one segment according the spec. + if (segcount < 1) { + return OTS_FAILURE(); + } + + // log2segcount is the maximal x s.t. 2^x < segcount + unsigned log2segcount = 0; + while (1u << (log2segcount + 1) <= segcount) { + log2segcount++; + } + + const uint16_t expected_search_range = 2 * 1u << log2segcount; + if (expected_search_range != search_range) { + return OTS_FAILURE(); + } + + if (entry_selector != log2segcount) { + return OTS_FAILURE(); + } + + const uint16_t expected_range_shift = segcountx2 - search_range; + if (range_shift != expected_range_shift) { + return OTS_FAILURE(); + } + + std::vector<Subtable314Range> ranges(segcount); + + for (unsigned i = 0; i < segcount; ++i) { + if (!subtable.ReadU16(&ranges[i].end_range)) { + return OTS_FAILURE(); + } + } + + uint16_t padding; + if (!subtable.ReadU16(&padding)) { + return OTS_FAILURE(); + } + if (padding) { + return OTS_FAILURE(); + } + + for (unsigned i = 0; i < segcount; ++i) { + if (!subtable.ReadU16(&ranges[i].start_range)) { + return OTS_FAILURE(); + } + } + for (unsigned i = 0; i < segcount; ++i) { + if (!subtable.ReadS16(&ranges[i].id_delta)) { + return OTS_FAILURE(); + } + } + for (unsigned i = 0; i < segcount; ++i) { + ranges[i].id_range_offset_offset = subtable.offset(); + if (!subtable.ReadU16(&ranges[i].id_range_offset)) { + return OTS_FAILURE(); + } + + if (ranges[i].id_range_offset & 1) { + // Some font generators seem to put 65535 on id_range_offset + // for 0xFFFF-0xFFFF range. + // (e.g., many fonts in http://www.princexml.com/fonts/) + if (i == segcount - 1u) { + OTS_WARNING("bad id_range_offset"); + ranges[i].id_range_offset = 0; + // The id_range_offset value in the transcoded font will not change + // since this table is not actually "transcoded" yet. + } else { + return OTS_FAILURE(); + } + } + } + + // ranges must be ascending order, based on the end_code. Ranges may not + // overlap. + for (unsigned i = 1; i < segcount; ++i) { + if ((i == segcount - 1u) && + (ranges[i - 1].start_range == 0xffff) && + (ranges[i - 1].end_range == 0xffff) && + (ranges[i].start_range == 0xffff) && + (ranges[i].end_range == 0xffff)) { + // Some fonts (e.g., Germania.ttf) have multiple 0xffff terminators. + // We'll accept them as an exception. + OTS_WARNING("multiple 0xffff terminators found"); + continue; + } + + // Note: some Linux fonts (e.g., LucidaSansOblique.ttf, bsmi00lp.ttf) have + // unsorted table... + if (ranges[i].end_range <= ranges[i - 1].end_range) { + return OTS_FAILURE(); + } + if (ranges[i].start_range <= ranges[i - 1].end_range) { + return OTS_FAILURE(); + } + } + + // The last range must end at 0xffff + if (ranges[segcount - 1].end_range != 0xffff) { + return OTS_FAILURE(); + } + + // A format 4 CMAP subtable is complex. To be safe we simulate a lookup of + // each code-point defined in the table and make sure that they are all valid + // glyphs and that we don't access anything out-of-bounds. + for (unsigned i = 1; i < segcount; ++i) { + for (unsigned cp = ranges[i].start_range; cp <= ranges[i].end_range; ++cp) { + const uint16_t code_point = cp; + if (ranges[i].id_range_offset == 0) { + // this is explictly allowed to overflow in the spec + const uint16_t glyph = code_point + ranges[i].id_delta; + if (glyph >= num_glyphs) { + return OTS_FAILURE(); + } + } else { + const uint16_t range_delta = code_point - ranges[i].start_range; + // this might seem odd, but it's true. The offset is relative to the + // location of the offset value itself. + const uint32_t glyph_id_offset = ranges[i].id_range_offset_offset + + ranges[i].id_range_offset + + range_delta * 2; + // We need to be able to access a 16-bit value from this offset + if (glyph_id_offset + 1 >= length) { + return OTS_FAILURE(); + } + uint16_t glyph; + memcpy(&glyph, data + glyph_id_offset, 2); + glyph = ntohs(glyph); + if (glyph >= num_glyphs) { + return OTS_FAILURE(); + } + } + } + } + + // We accept the table. + // TODO(yusukes): transcode the subtable. + if (encoding == 0) { + file->cmap->subtable_304_data = data; + file->cmap->subtable_304_length = length; + } else if (encoding == 1) { + file->cmap->subtable_314_data = data; + file->cmap->subtable_314_length = length; + } else { + return OTS_FAILURE(); + } + + return true; +} + +bool Parse31012(ots::OpenTypeFile *file, + const uint8_t *data, size_t length, uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // Format 12 tables are simple. We parse these and fully serialise them + // later. + + if (!subtable.Skip(8)) { + return OTS_FAILURE(); + } + uint32_t language; + if (!subtable.ReadU32(&language)) { + return OTS_FAILURE(); + } + if (language) { + return OTS_FAILURE(); + } + + uint32_t num_groups; + if (!subtable.ReadU32(&num_groups)) { + return OTS_FAILURE(); + } + // There are 12 bytes of data per group. In order to keep some sanity, we'll + // only allow ourselves to allocate 8MB of memory here. That means that + // we'll allow, at most, 8 * 1024 * 1024 / 12 groups. Note that this is + // still far in excess of the number of Unicode code-points currently + // allocated. + if (num_groups == 0 || num_groups > kMaxCMAPGroups) { + return OTS_FAILURE(); + } + + std::vector<ots::OpenTypeCMAPSubtableRange> &groups + = file->cmap->subtable_31012; + groups.resize(num_groups); + + for (unsigned i = 0; i < num_groups; ++i) { + if (!subtable.ReadU32(&groups[i].start_range) || + !subtable.ReadU32(&groups[i].end_range) || + !subtable.ReadU32(&groups[i].start_glyph_id)) { + return OTS_FAILURE(); + } + + if (groups[i].start_range > 0x10FFFF || + groups[i].end_range > 0x10FFFF || + groups[i].start_glyph_id > 0xFFFF) { + return OTS_FAILURE(); + } + + // We assert that the glyph value is within range. Because the range + // limits, above, we don't need to worry about overflow. + if (groups[i].end_range < groups[i].start_range) { + return OTS_FAILURE(); + } + if ((groups[i].end_range - groups[i].start_range) + + groups[i].start_glyph_id > num_glyphs) { + return OTS_FAILURE(); + } + } + + // the groups must be sorted by start code and may not overlap + for (unsigned i = 1; i < num_groups; ++i) { + if (groups[i].start_range <= groups[i - 1].start_range) { + return OTS_FAILURE(); + } + if (groups[i].start_range <= groups[i - 1].end_range) { + return OTS_FAILURE(); + } + } + + return true; +} + +bool Parse31013(ots::OpenTypeFile *file, + const uint8_t *data, size_t length, uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // Format 13 tables are simple. We parse these and fully serialise them + // later. + + if (!subtable.Skip(8)) { + return OTS_FAILURE(); + } + uint16_t language; + if (!subtable.ReadU16(&language)) { + return OTS_FAILURE(); + } + if (language) { + return OTS_FAILURE(); + } + + uint32_t num_groups; + if (!subtable.ReadU32(&num_groups)) { + return OTS_FAILURE(); + } + + // We limit the number of groups in the same way as in 3.10.12 tables. See + // the comment there in + if (num_groups == 0 || num_groups > kMaxCMAPGroups) { + return OTS_FAILURE(); + } + + std::vector<ots::OpenTypeCMAPSubtableRange> &groups + = file->cmap->subtable_31013; + groups.resize(num_groups); + + for (unsigned i = 0; i < num_groups; ++i) { + if (!subtable.ReadU32(&groups[i].start_range) || + !subtable.ReadU32(&groups[i].end_range) || + !subtable.ReadU32(&groups[i].start_glyph_id)) { + return OTS_FAILURE(); + } + + // We conservatively limit all of the values to 2^30 which is vastly larger + // than the number of Unicode code-points defined and might protect some + // parsers from overflows + if (groups[i].start_range > 0x40000000 || + groups[i].end_range > 0x40000000 || + groups[i].start_glyph_id > 0x40000000) { + return OTS_FAILURE(); + } + + if (groups[i].start_glyph_id >= num_glyphs) { + return OTS_FAILURE(); + } + } + + // the groups must be sorted by start code and may not overlap + for (unsigned i = 1; i < num_groups; ++i) { + if (groups[i].start_range <= groups[i - 1].start_range) { + return OTS_FAILURE(); + } + if (groups[i].start_range <= groups[i - 1].end_range) { + return OTS_FAILURE(); + } + } + + return true; +} + +bool Parse100(ots::OpenTypeFile *file, const uint8_t *data, size_t length) { + // Mac Roman table + ots::Buffer subtable(data, length); + + if (!subtable.Skip(4)) { + return OTS_FAILURE(); + } + uint16_t language; + if (!subtable.ReadU16(&language)) { + return OTS_FAILURE(); + } + if (language) { + // simsun.ttf has non-zero language id. + OTS_WARNING("language id should be zero: %u", language); + } + + file->cmap->subtable_100.reserve(kFormat0ArraySize); + for (size_t i = 0; i < kFormat0ArraySize; ++i) { + uint8_t glyph_id = 0; + if (!subtable.ReadU8(&glyph_id)) { + return OTS_FAILURE(); + } + file->cmap->subtable_100.push_back(glyph_id); + } + + return true; +} + +} // namespace + +namespace ots { + +bool ots_cmap_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + file->cmap = new OpenTypeCMAP; + + uint16_t version, num_tables; + if (!table.ReadU16(&version) || + !table.ReadU16(&num_tables)) { + return OTS_FAILURE(); + } + + if (version != 0) { + return OTS_FAILURE(); + } + if (!num_tables) { + return OTS_FAILURE(); + } + + std::vector<CMAPSubtableHeader> subtable_headers; + + // read the subtable headers + subtable_headers.reserve(num_tables); + for (unsigned i = 0; i < num_tables; ++i) { + CMAPSubtableHeader subt; + + if (!table.ReadU16(&subt.platform) || + !table.ReadU16(&subt.encoding) || + !table.ReadU32(&subt.offset)) { + return OTS_FAILURE(); + } + + subtable_headers.push_back(subt); + } + + const size_t data_offset = table.offset(); + + // make sure that all the offsets are valid. + uint32_t last_id = 0; + for (unsigned i = 0; i < num_tables; ++i) { + if (subtable_headers[i].offset > 1024 * 1024 * 1024) { + return OTS_FAILURE(); + } + if (subtable_headers[i].offset < data_offset || + subtable_headers[i].offset >= length) { + return OTS_FAILURE(); + } + + // check if the table is sorted first by platform ID, then by encoding ID. + uint32_t current_id + = (subtable_headers[i].platform << 16) + subtable_headers[i].encoding; + if ((i != 0) && (last_id >= current_id)) { + return OTS_FAILURE(); + } + last_id = current_id; + } + + // the format of the table is the first couple of bytes in the table. The + // length of the table is in a format specific format afterwards. + for (unsigned i = 0; i < num_tables; ++i) { + table.set_offset(subtable_headers[i].offset); + if (!table.ReadU16(&subtable_headers[i].format)) { + return OTS_FAILURE(); + } + + if ((subtable_headers[i].format == 0) || + (subtable_headers[i].format == 4)) { + uint16_t len; + if (!table.ReadU16(&len)) { + return OTS_FAILURE(); + } + subtable_headers[i].length = len; + } else if (subtable_headers[i].format == 12 || + subtable_headers[i].format == 13) { + if (!table.Skip(2)) { + return OTS_FAILURE(); + } + if (!table.ReadU32(&subtable_headers[i].length)) { + return OTS_FAILURE(); + } + } else { + subtable_headers[i].length = 0; + } + } + + // Now, verify that all the lengths are sane + for (unsigned i = 0; i < num_tables; ++i) { + if (!subtable_headers[i].length) continue; + if (subtable_headers[i].length > 1024 * 1024 * 1024) { + return OTS_FAILURE(); + } + // We know that both the offset and length are < 1GB, so the following + // addition doesn't overflow + const uint32_t end_byte + = subtable_headers[i].offset + subtable_headers[i].length; + if (end_byte > length) { + return OTS_FAILURE(); + } + } + + // we grab the number of glyphs in the file from the maxp table to make sure + // that the character map isn't referencing anything beyound this range. + if (!file->maxp) { + return OTS_FAILURE(); + } + const uint16_t num_glyphs = file->maxp->num_glyphs; + + // We only support a subset of the possible character map tables. Microsoft + // 'strongly recommends' that everyone supports the Unicode BMP table with + // the UCS-4 table for non-BMP glyphs. We'll pass the following subtables: + // Platform ID Encoding ID Format + // 0 0 4 (Unicode Default) + // 0 3 4 (Unicode BMP) + // 0 3 12 (Unicode UCS-4) + // 1 0 0 (Mac Roman) + // 3 0 4 (MS Symbol) + // 3 1 4 (MS Unicode BMP) + // 3 10 12 (MS Unicode UCS-4) + // 3 10 13 (MS UCS-4 Fallback mapping) + + for (unsigned i = 0; i < num_tables; ++i) { + if (subtable_headers[i].platform == 0) { + // Unicode platform + + if ((subtable_headers[i].encoding == 0) && + (subtable_headers[i].format == 4)) { + // parse and output the 0-0-4 table as 3-1-4 table. Sometimes the 0-0-4 + // table actually points to MS symbol data and thus should be parsed as + // 3-0-4 table (e.g., marqueem.ttf and quixotic.ttf). This error will be + // recovered in ots_cmap_serialise(). + if (!Parse3x4(file, 1, data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE(); + } + } else if ((subtable_headers[i].encoding == 3) && + (subtable_headers[i].format == 4)) { + // parse and output the 0-3-4 table as 3-1-4 table. + file->cmap->subtable_314_data = 0; + file->cmap->subtable_314_length = 0; + if (!Parse3x4(file, 1, data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE(); + } + } else if ((subtable_headers[i].encoding == 3) && + (subtable_headers[i].format == 12)) { + // parse and output the 0-3-12 table as 3-10-12 table. + if (!Parse31012(file, data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE(); + } + } + } else if (subtable_headers[i].platform == 1) { + // Mac platform + + if ((subtable_headers[i].encoding == 0) && + (subtable_headers[i].format == 0)) { + // parse and output the 1-0-0 table. + if (!Parse100(file, data + subtable_headers[i].offset, + subtable_headers[i].length)) { + return OTS_FAILURE(); + } + } + } else if (subtable_headers[i].platform == 3) { + // MS platform + + if (subtable_headers[i].encoding == 0) { + if (subtable_headers[i].format == 4) { + if (!Parse3x4(file, 0, data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE(); + } + } + } else if (subtable_headers[i].encoding == 1) { + if (subtable_headers[i].format == 4) { + // clear 0-0-4 or 0-3-4 table. + file->cmap->subtable_314_data = 0; + file->cmap->subtable_314_length = 0; + if (!Parse3x4(file, 1, data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE(); + } + } + } else if (subtable_headers[i].encoding == 10) { + if (subtable_headers[i].format == 12) { + // clear 0-3-12 table. + file->cmap->subtable_31012.clear(); + if (!Parse31012(file, data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE(); + } + } else if (subtable_headers[i].format == 13) { + if (!Parse31013(file, data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE(); + } + } + } + } + } + + return true; +} + +bool ots_cmap_should_serialise(OpenTypeFile *file) { + return file->cmap; +} + +bool ots_cmap_serialise(OTSStream *out, OpenTypeFile *file) { + const bool have_100 = file->cmap->subtable_100.size(); + const bool have_304 = file->cmap->subtable_304_data; + // MS Symbol and MS Unicode tables should not co-exist. + // See the comment above in 0-0-4 parser. + const bool have_314 = (!have_304) && file->cmap->subtable_314_data; + const bool have_31012 = file->cmap->subtable_31012.size(); + const bool have_31013 = file->cmap->subtable_31013.size(); + const unsigned num_subtables = static_cast<unsigned>(have_100) + + static_cast<unsigned>(have_304) + + static_cast<unsigned>(have_314) + + static_cast<unsigned>(have_31012) + + static_cast<unsigned>(have_31013); + const off_t table_start = out->Tell(); + + // Some fonts don't have 3-0-4 MS Symbol nor 3-1-4 Unicode BMP tables + // (e.g., old fonts for Mac). We don't support them. + if (!have_304 && !have_314) { + return OTS_FAILURE(); + } + + if (!out->WriteU16(0) || + !out->WriteU16(num_subtables)) { + return OTS_FAILURE(); + } + + const off_t record_offset = out->Tell(); + if (!out->Pad(num_subtables * 8)) { + return OTS_FAILURE(); + } + + const off_t offset_100 = out->Tell(); + if (have_100) { + if (!out->WriteU16(0) || // format + !out->WriteU16(6 + kFormat0ArraySize) || // length + !out->WriteU16(0)) { // language + return OTS_FAILURE(); + } + if (!out->Write(&(file->cmap->subtable_100[0]), kFormat0ArraySize)) { + return OTS_FAILURE(); + } + } + + const off_t offset_304 = out->Tell(); + if (have_304) { + if (!out->Write(file->cmap->subtable_304_data, + file->cmap->subtable_304_length)) { + return OTS_FAILURE(); + } + } + + const off_t offset_314 = out->Tell(); + if (have_314) { + if (!out->Write(file->cmap->subtable_314_data, + file->cmap->subtable_314_length)) { + return OTS_FAILURE(); + } + } + + const off_t offset_31012 = out->Tell(); + if (have_31012) { + std::vector<OpenTypeCMAPSubtableRange> &groups = file->cmap->subtable_31012; + const unsigned num_groups = groups.size(); + if (!out->WriteU16(12) || + !out->WriteU16(0) || + !out->WriteU32(num_groups * 12 + 16) || + !out->WriteU32(0) || + !out->WriteU32(num_groups)) { + return OTS_FAILURE(); + } + + for (unsigned i = 0; i < num_groups; ++i) { + if (!out->WriteU32(groups[i].start_range) || + !out->WriteU32(groups[i].end_range) || + !out->WriteU32(groups[i].start_glyph_id)) { + return OTS_FAILURE(); + } + } + } + + const off_t offset_31013 = out->Tell(); + if (have_31013) { + std::vector<OpenTypeCMAPSubtableRange> &groups = file->cmap->subtable_31013; + const unsigned num_groups = groups.size(); + if (!out->WriteU16(13) || + !out->WriteU16(0) || + !out->WriteU32(num_groups * 12 + 14) || + !out->WriteU32(0) || + !out->WriteU32(num_groups)) { + return OTS_FAILURE(); + } + + for (unsigned i = 0; i < num_groups; ++i) { + if (!out->WriteU32(groups[i].start_range) || + !out->WriteU32(groups[i].end_range) || + !out->WriteU32(groups[i].start_glyph_id)) { + return OTS_FAILURE(); + } + } + } + + const off_t table_end = out->Tell(); + // We might have hanging bytes from the above's checksum which the OTSStream + // then merges into the table of offsets. + OTSStream::ChecksumState saved_checksum = out->SaveChecksumState(); + out->ResetChecksum(); + + // Now seek back and write the table of offsets + if (!out->Seek(record_offset)) { + return OTS_FAILURE(); + } + + if (have_100) { + if (!out->WriteU16(1) || + !out->WriteU16(0) || + !out->WriteU32(offset_100 - table_start)) { + return OTS_FAILURE(); + } + } + + if (have_304) { + if (!out->WriteU16(3) || + !out->WriteU16(0) || + !out->WriteU32(offset_304 - table_start)) { + return OTS_FAILURE(); + } + } + + if (have_314) { + if (!out->WriteU16(3) || + !out->WriteU16(1) || + !out->WriteU32(offset_314 - table_start)) { + return OTS_FAILURE(); + } + } + + if (have_31012) { + if (!out->WriteU16(3) || + !out->WriteU16(10) || + !out->WriteU32(offset_31012 - table_start)) { + return OTS_FAILURE(); + } + } + + if (have_31013) { + if (!out->WriteU16(3) || + !out->WriteU16(10) || + !out->WriteU32(offset_31013 - table_start)) { + return OTS_FAILURE(); + } + } + + if (!out->Seek(table_end)) { + return OTS_FAILURE(); + } + out->RestoreChecksum(saved_checksum); + + return true; +} + +void ots_cmap_free(OpenTypeFile *file) { + delete file->cmap; +} + +} // namespace ots diff --git a/src/cmap.h b/src/cmap.h new file mode 100644 index 0000000..bf98c3d --- /dev/null +++ b/src/cmap.h @@ -0,0 +1,39 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_CMAP_H_ +#define OTS_CMAP_H_ + +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeCMAPSubtableRange { + uint32_t start_range; + uint32_t end_range; + uint32_t start_glyph_id; +}; + +struct OpenTypeCMAP { + OpenTypeCMAP() + : subtable_304_data(NULL), + subtable_304_length(0), + subtable_314_data(NULL), + subtable_314_length(0) { + } + + const uint8_t *subtable_304_data; + size_t subtable_304_length; + const uint8_t *subtable_314_data; + size_t subtable_314_length; + std::vector<OpenTypeCMAPSubtableRange> subtable_31012; + std::vector<OpenTypeCMAPSubtableRange> subtable_31013; + std::vector<uint8_t> subtable_100; +}; + +} // namespace ots + +#endif diff --git a/src/cvt.cc b/src/cvt.cc new file mode 100644 index 0000000..3403a87 --- /dev/null +++ b/src/cvt.cc @@ -0,0 +1,56 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cvt.h" + +// cvt - Control Value Table +// http://www.microsoft.com/opentype/otspec/cvt.htm + +namespace ots { + +bool ots_cvt_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypeCVT *cvt = new OpenTypeCVT; + file->cvt = cvt; + + if (length >= 128 * 1024u) { + return OTS_FAILURE(); // almost all cvt tables are less than 4k bytes. + } + + if (length % 2 != 0) { + return OTS_FAILURE(); + } + + if (!table.Skip(length)) { + return OTS_FAILURE(); + } + + cvt->data = data; + cvt->length = length; + return true; +} + +bool ots_cvt_should_serialise(OpenTypeFile *file) { + if (!file->glyf) { + return false; // this table is not for CFF fonts. + } + return g_transcode_hints && file->cvt; +} + +bool ots_cvt_serialise(OTSStream *out, OpenTypeFile *file) { + const OpenTypeCVT *cvt = file->cvt; + + if (!out->Write(cvt->data, cvt->length)) { + return OTS_FAILURE(); + } + + return true; +} + +void ots_cvt_free(OpenTypeFile *file) { + delete file->cvt; +} + +} // namespace ots diff --git a/src/cvt.h b/src/cvt.h new file mode 100644 index 0000000..3c25f06 --- /dev/null +++ b/src/cvt.h @@ -0,0 +1,19 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_CVT_H_ +#define OTS_CVT_H_ + +#include "ots.h" + +namespace ots { + +struct OpenTypeCVT { + const uint8_t *data; + uint32_t length; +}; + +} // namespace ots + +#endif // OTS_CVT_H_ diff --git a/src/fpgm.cc b/src/fpgm.cc new file mode 100644 index 0000000..cf0e53c --- /dev/null +++ b/src/fpgm.cc @@ -0,0 +1,50 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "fpgm.h" + +// fpgm - Font Program +// http://www.microsoft.com/opentype/otspec/fpgm.htm + +namespace ots { + +bool ots_fpgm_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypeFPGM *fpgm = new OpenTypeFPGM; + file->fpgm = fpgm; + + if (length >= 128 * 1024u) { + return OTS_FAILURE(); // almost all fpgm tables are less than 5k bytes. + } + + if (!table.Skip(length)) { + return OTS_FAILURE(); + } + + fpgm->data = data; + fpgm->length = length; + return true; +} + +bool ots_fpgm_should_serialise(OpenTypeFile *file) { + if (!file->glyf) return false; // this table is not for CFF fonts. + return g_transcode_hints && file->fpgm; +} + +bool ots_fpgm_serialise(OTSStream *out, OpenTypeFile *file) { + const OpenTypeFPGM *fpgm = file->fpgm; + + if (!out->Write(fpgm->data, fpgm->length)) { + return OTS_FAILURE(); + } + + return true; +} + +void ots_fpgm_free(OpenTypeFile *file) { + delete file->fpgm; +} + +} // namespace ots diff --git a/src/fpgm.h b/src/fpgm.h new file mode 100644 index 0000000..8fabac3 --- /dev/null +++ b/src/fpgm.h @@ -0,0 +1,19 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_FPGM_H_ +#define OTS_FPGM_H_ + +#include "ots.h" + +namespace ots { + +struct OpenTypeFPGM { + const uint8_t *data; + uint32_t length; +}; + +} // namespace ots + +#endif // OTS_FPGM_H_ diff --git a/src/gasp.cc b/src/gasp.cc new file mode 100644 index 0000000..2adcc94 --- /dev/null +++ b/src/gasp.cc @@ -0,0 +1,106 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "gasp.h" + +// gasp - Grid-fitting And Scan-conversion Procedure +// http://www.microsoft.com/opentype/otspec/gasp.htm + +#define DROP_THIS_TABLE \ + do { delete file->gasp; file->gasp = 0; } while (0) + +namespace ots { + +bool ots_gasp_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypeGASP *gasp = new OpenTypeGASP; + file->gasp = gasp; + + uint16_t num_ranges; + if (!table.ReadU16(&gasp->version) || + !table.ReadU16(&num_ranges)) { + return OTS_FAILURE(); + } + + if (gasp->version > 1) { + // Lots of Linux fonts have bad version numbers... + OTS_WARNING("bad version: %u", gasp->version); + DROP_THIS_TABLE; + return true; + } + + if (num_ranges == 0) { + OTS_WARNING("num_ranges is zero"); + DROP_THIS_TABLE; + return true; + } + + gasp->gasp_ranges.reserve(num_ranges); + for (unsigned i = 0; i < num_ranges; ++i) { + uint16_t max_ppem; + uint16_t behavior; + if (!table.ReadU16(&max_ppem) || + !table.ReadU16(&behavior)) { + return OTS_FAILURE(); + } + if ((i > 0) && (gasp->gasp_ranges[i - 1].first >= max_ppem)) { + // The records in the gaspRange[] array must be sorted in order of + // increasing rangeMaxPPEM value. + OTS_WARNING("ranges are not sorted"); + DROP_THIS_TABLE; + return true; + } + if ((i == num_ranges - 1u) && // never underflow. + (max_ppem != 0xffffu)) { + OTS_WARNING("The last record should be 0xFFFF as a sentinel value " + "for rangeMaxPPEM"); + DROP_THIS_TABLE; + return true; + } + + if (behavior >> 8) { + OTS_WARNING("undefined bits are used: %x", behavior); + // mask undefined bits. + behavior &= 0x000fu; + } + + if (gasp->version == 0 && (behavior >> 2) != 0) { + OTS_WARNING("changed the version number to 1"); + gasp->version = 1; + } + + gasp->gasp_ranges.push_back(std::make_pair(max_ppem, behavior)); + } + + return true; +} + +bool ots_gasp_should_serialise(OpenTypeFile *file) { + return file->gasp; +} + +bool ots_gasp_serialise(OTSStream *out, OpenTypeFile *file) { + const OpenTypeGASP *gasp = file->gasp; + + if (!out->WriteU16(gasp->version) || + !out->WriteU16(gasp->gasp_ranges.size())) { + return OTS_FAILURE(); + } + + for (unsigned i = 0; i < gasp->gasp_ranges.size(); ++i) { + if (!out->WriteU16(gasp->gasp_ranges[i].first) || + !out->WriteU16(gasp->gasp_ranges[i].second)) { + return OTS_FAILURE(); + } + } + + return true; +} + +void ots_gasp_free(OpenTypeFile *file) { + delete file->gasp; +} + +} // namespace ots diff --git a/src/gasp.h b/src/gasp.h new file mode 100644 index 0000000..9e7efba --- /dev/null +++ b/src/gasp.h @@ -0,0 +1,22 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_GASP_H_ +#define OTS_GASP_H_ + +#include <utility> // std::pair +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeGASP { + uint16_t version; + std::vector<std::pair<uint16_t, uint16_t> > gasp_ranges; // max PPEM, etc. +}; + +} // namespace ots + +#endif // OTS_GASP_H_ diff --git a/src/glyf.cc b/src/glyf.cc new file mode 100644 index 0000000..bae147c --- /dev/null +++ b/src/glyf.cc @@ -0,0 +1,265 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "glyf.h" + +#include <algorithm> +#include <limits> + +#include "head.h" +#include "loca.h" +#include "maxp.h" + +// glyf - Glyph Data +// http://www.microsoft.com/opentype/otspec/glyf.htm + +namespace ots { + +bool ots_glyf_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + + if (!file->maxp || !file->loca || !file->head) { + return OTS_FAILURE(); + } + + OpenTypeGLYF *glyf = new OpenTypeGLYF; + file->glyf = glyf; + + const unsigned num_glyphs = file->maxp->num_glyphs; + std::vector<uint32_t> &offsets = file->loca->offsets; + + if (offsets.size() != num_glyphs + 1) { + return OTS_FAILURE(); + } + + std::vector<uint32_t> resulting_offsets(num_glyphs + 1); + uint32_t current_offset = 0; + + for (unsigned i = 0; i < num_glyphs; ++i) { + const unsigned gly_offset = offsets[i]; + // The LOCA parser checks that these values are monotonic + const unsigned gly_length = offsets[i + 1] - offsets[i]; + if (!gly_length) { + // this glyph has no outline (e.g. the space charactor) + resulting_offsets[i] = current_offset; + continue; + } + + if (gly_offset >= length) { + return OTS_FAILURE(); + } + // Since these are unsigned types, the compiler is not allowed to assume + // that they never overflow. + if (gly_offset + gly_length < gly_offset) { + return OTS_FAILURE(); + } + if (gly_offset + gly_length > length) { + return OTS_FAILURE(); + } + + table.set_offset(gly_offset); + int16_t num_contours, xmin, ymin, xmax, ymax; + if (!table.ReadS16(&num_contours) || + !table.ReadS16(&xmin) || + !table.ReadS16(&ymin) || + !table.ReadS16(&xmax) || + !table.ReadS16(&ymax)) { + return OTS_FAILURE(); + } + + if (num_contours < -1) { + // -2, -3, -4, ... are reserved for future use. + return OTS_FAILURE(); + } + + // workaround for fonts in http://www.princexml.com/fonts/ + if ((xmin == 32767) && + (xmax == -32767) && + (ymin == 32767) && + (ymax == -32767)) { + OTS_WARNING("bad xmin/xmax/ymin/ymax values"); + xmin = xmax = ymin = ymax = 0; + } + + if (xmin > xmax || ymin > ymax) { + return OTS_FAILURE(); + } + + unsigned new_size = 0; + if (num_contours >= 0) { + // this is a simple glyph and might contain bytecode + + // read the end-points array + uint16_t num_flags = 0; + for (int j = 0; j < num_contours; ++j) { + uint16_t tmp_index; + if (!table.ReadU16(&tmp_index)) { + return OTS_FAILURE(); + } + if (tmp_index == 0xffffu) { + return OTS_FAILURE(); + } + // check if the indices are monotonically increasing + if (j && (tmp_index + 1 <= num_flags)) { + return OTS_FAILURE(); + } + num_flags = tmp_index + 1; + } + + uint16_t bytecode_length; + if (!table.ReadU16(&bytecode_length)) { + return OTS_FAILURE(); + } + if ((file->maxp->version_1) && + (file->maxp->max_glyf_insns < bytecode_length)) { + return OTS_FAILURE(); + } + + const unsigned gly_header_length = 10 + num_contours * 2 + 2; + if (gly_length < (gly_header_length + bytecode_length)) { + return OTS_FAILURE(); + } + + if (g_transcode_hints) { + glyf->iov.push_back(std::make_pair( + data + gly_offset, gly_header_length + bytecode_length)); + } else { + // enqueue two vectors: the glyph data up to the bytecode length, then + // a pointer to a static uint16_t 0 to overwrite the length. + glyf->iov.push_back(std::make_pair( + data + gly_offset, gly_header_length - 2)); + glyf->iov.push_back(std::make_pair((const uint8_t*) "\x00\x00", 2)); + } + + if (!table.Skip(bytecode_length)) { + return OTS_FAILURE(); + } + + uint32_t flags_count_physical = 0; // on memory + uint32_t xy_coordinates_length = 0; + for (uint32_t flags_count_logical = 0; + flags_count_logical < num_flags; + ++flags_count_logical, ++flags_count_physical) { + uint8_t flag = 0; + if (!table.ReadU8(&flag)) { + return OTS_FAILURE(); + } + + uint32_t delta = 0; + if (flag & (1u << 1)) { // x-Short + ++delta; + } else if (!(flag & (1u << 4))) { + delta += 2; + } + + if (flag & (1u << 2)) { // y-Short + ++delta; + } else if (!(flag & (1u << 5))) { + delta += 2; + } + + if (flag & (1u << 3)) { // repeat + if (flags_count_logical + 1 >= num_flags) { + return OTS_FAILURE(); + } + uint8_t repeat = 0; + if (!table.ReadU8(&repeat)) { + return OTS_FAILURE(); + } + if (repeat == 0) { + return OTS_FAILURE(); + } + delta += (delta * repeat); + + flags_count_logical += repeat; + if (flags_count_logical >= num_flags) { + return OTS_FAILURE(); + } + ++flags_count_physical; + } + + if ((flag & (1u << 6)) || (flag & (1u << 7))) { // reserved flags + return OTS_FAILURE(); + } + + xy_coordinates_length += delta; + if (gly_length < xy_coordinates_length) { + return OTS_FAILURE(); + } + } + + if (gly_length < (gly_header_length + bytecode_length + + flags_count_physical + xy_coordinates_length)) { + return OTS_FAILURE(); + } + + if (gly_length - (gly_header_length + bytecode_length + + flags_count_physical + xy_coordinates_length) > 3) { + // We allow 0-3 bytes difference since gly_length is 4-bytes aligned, + // zero-padded length. + return OTS_FAILURE(); + } + + glyf->iov.push_back(std::make_pair( + data + gly_offset + gly_header_length + bytecode_length, + flags_count_physical + xy_coordinates_length)); + + new_size + = gly_header_length + flags_count_physical + xy_coordinates_length; + if (g_transcode_hints) { + new_size += bytecode_length; + } + } else { + // it's a composite glyph without any bytecode. Enqueue the whole thing + glyf->iov.push_back(std::make_pair(data + gly_offset, gly_length)); + new_size = gly_length; + } + + resulting_offsets[i] = current_offset; + // glyphs must be four byte aligned + // TODO(yusukes): investigate whether this padding is really necessary. + // Which part of the spec requires this? + const unsigned padding = (4 - (new_size & 3)) % 4; + if (padding) { + glyf->iov.push_back(std::make_pair( + reinterpret_cast<const uint8_t*>("\x00\x00\x00\x00"), padding)); + new_size += padding; + } + current_offset += new_size; + } + resulting_offsets[num_glyphs] = current_offset; + + const uint16_t max16 = std::numeric_limits<uint16_t>::max(); + if ((*std::max_element(resulting_offsets.begin(), + resulting_offsets.end()) >= (max16 * 2u)) && + (file->head->index_to_loc_format != 1)) { + OTS_WARNING("2-bytes indexing is not possible (due to the padding above)"); + file->head->index_to_loc_format = 1; + } + + file->loca->offsets = resulting_offsets; + return true; +} + +bool ots_glyf_should_serialise(OpenTypeFile *file) { + return file->glyf; +} + +bool ots_glyf_serialise(OTSStream *out, OpenTypeFile *file) { + const OpenTypeGLYF *glyf = file->glyf; + + for (unsigned i = 0; i < glyf->iov.size(); ++i) { + if (!out->Write(glyf->iov[i].first, glyf->iov[i].second)) { + return OTS_FAILURE(); + } + } + + return true; +} + +void ots_glyf_free(OpenTypeFile *file) { + delete file->glyf; +} + +} // namespace ots diff --git a/src/glyf.h b/src/glyf.h new file mode 100644 index 0000000..d2eda77 --- /dev/null +++ b/src/glyf.h @@ -0,0 +1,21 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_GLYF_H_ +#define OTS_GLYF_H_ + +#include <utility> // std::pair +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeGLYF { + std::vector<std::pair<const uint8_t*, size_t> > iov; +}; + +} // namespace ots + +#endif // OTS_GLYF_H_ diff --git a/src/hdmx.cc b/src/hdmx.cc new file mode 100644 index 0000000..690bcce --- /dev/null +++ b/src/hdmx.cc @@ -0,0 +1,132 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "hdmx.h" +#include "head.h" +#include "maxp.h" + +// hdmx - Horizontal Device Metrics +// http://www.microsoft.com/opentype/otspec/hdmx.htm + +#define DROP_THIS_TABLE \ + do { delete file->hdmx; file->hdmx = 0; } while (0) + +namespace ots { + +bool ots_hdmx_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + file->hdmx = new OpenTypeHDMX; + OpenTypeHDMX * const hdmx = file->hdmx; + + if (!file->head || !file->maxp) { + return OTS_FAILURE(); + } + + if ((file->head->flags & 0x14) == 0) { + OTS_WARNING("the table should not be present when bit 2 and 4 of the " + "head->flags are not set"); + DROP_THIS_TABLE; + return true; + } + + int16_t num_recs; + if (!table.ReadU16(&hdmx->version) || + !table.ReadS16(&num_recs) || + !table.ReadS32(&hdmx->size_device_record)) { + return OTS_FAILURE(); + } + if (hdmx->version != 0) { + OTS_WARNING("bad version: %u", hdmx->version); + DROP_THIS_TABLE; + return true; + } + if (num_recs <= 0) { + OTS_WARNING("bad num_recs: %d", num_recs); + DROP_THIS_TABLE; + return true; + } + if (hdmx->size_device_record < (file->maxp->num_glyphs + 2)) { + OTS_WARNING("bad hdmx->size_device_record: %d", hdmx->size_device_record); + DROP_THIS_TABLE; + return true; + } + + hdmx->pad_len = hdmx->size_device_record - (file->maxp->num_glyphs + 2); + if (hdmx->pad_len > 3) { + return OTS_FAILURE(); + } + + uint8_t last_pixel_size = 0; + hdmx->records.reserve(num_recs); + for (int i = 0; i < num_recs; ++i) { + OpenTypeHDMXDeviceRecord rec; + + if (!table.ReadU8(&rec.pixel_size) || + !table.ReadU8(&rec.max_width)) { + return OTS_FAILURE(); + } + if ((i != 0) && + (rec.pixel_size <= last_pixel_size)) { + OTS_WARNING("records are not sorted"); + DROP_THIS_TABLE; + return true; + } + last_pixel_size = rec.pixel_size; + + rec.widths.reserve(file->maxp->num_glyphs); + for (unsigned j = 0; j < file->maxp->num_glyphs; ++j) { + uint8_t width; + if (!table.ReadU8(&width)) { + return OTS_FAILURE(); + } + rec.widths.push_back(width); + } + + if ((hdmx->pad_len > 0) && + !table.Skip(hdmx->pad_len)) { + return OTS_FAILURE(); + } + + hdmx->records.push_back(rec); + } + + return true; +} + +bool ots_hdmx_should_serialise(OpenTypeFile *file) { + if (!file->hdmx) return false; + if (!file->glyf) return false; // this table is not for CFF fonts. + return true; +} + +bool ots_hdmx_serialise(OTSStream *out, OpenTypeFile *file) { + OpenTypeHDMX * const hdmx = file->hdmx; + + if (!out->WriteU16(hdmx->version) || + !out->WriteS16(hdmx->records.size()) || + !out->WriteS32(hdmx->size_device_record)) { + return OTS_FAILURE(); + } + + for (unsigned i = 0; i < hdmx->records.size(); ++i) { + const OpenTypeHDMXDeviceRecord& rec = hdmx->records[i]; + if (!out->Write(&rec.pixel_size, 1) || + !out->Write(&rec.max_width, 1) || + !out->Write(&rec.widths[0], rec.widths.size())) { + return OTS_FAILURE(); + } + if ((hdmx->pad_len > 0) && + !out->Write((const uint8_t *)"\x00\x00\x00", hdmx->pad_len)) { + return OTS_FAILURE(); + } + } + + return true; +} + +void ots_hdmx_free(OpenTypeFile *file) { + delete file->hdmx; +} + +} // namespace ots diff --git a/src/hdmx.h b/src/hdmx.h new file mode 100644 index 0000000..9ec2124 --- /dev/null +++ b/src/hdmx.h @@ -0,0 +1,29 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_HDMX_H_ +#define OTS_HDMX_H_ + +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeHDMXDeviceRecord { + uint8_t pixel_size; + uint8_t max_width; + std::vector<uint8_t> widths; +}; + +struct OpenTypeHDMX { + uint16_t version; + int32_t size_device_record; + int32_t pad_len; + std::vector<OpenTypeHDMXDeviceRecord> records; +}; + +} // namespace ots + +#endif diff --git a/src/head.cc b/src/head.cc new file mode 100644 index 0000000..f9b456d --- /dev/null +++ b/src/head.cc @@ -0,0 +1,146 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "head.h" + +#include <cstring> + +// head - Font Header +// http://www.microsoft.com/opentype/otspec/head.htm + +namespace ots { + +bool ots_head_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + file->head = new OpenTypeHEAD; + + uint32_t version; + if (!table.ReadU32(&version) || + !table.ReadU32(&file->head->revision)) { + return OTS_FAILURE(); + } + + if (version >> 16 != 1) { + return OTS_FAILURE(); + } + + // Skip the checksum adjustment + if (!table.Skip(4)) { + return OTS_FAILURE(); + } + + uint32_t magic; + if (!table.ReadTag(&magic) || + std::memcmp(&magic, "\x5F\x0F\x3C\xF5", 4)) { + return OTS_FAILURE(); + } + + if (!table.ReadU16(&file->head->flags)) { + return OTS_FAILURE(); + } + + // We allow bits 0..4, 11..13 + file->head->flags &= 0x381f; + + if (!table.ReadU16(&file->head->ppem)) { + return OTS_FAILURE(); + } + + // ppem must be in range + if (file->head->ppem < 16 || + file->head->ppem > 16384) { + return OTS_FAILURE(); + } + + // ppem must be a power of two + if ((file->head->ppem - 1) & file->head->ppem) { + // We don't call ots_failure() for now since lots of TrueType fonts are + // not following this rule. Putting OTS_WARNING here is too noisy. + } + + if (!table.ReadR64(&file->head->created) || + !table.ReadR64(&file->head->modified)) { + return OTS_FAILURE(); + } + + if (!table.ReadS16(&file->head->xmin) || + !table.ReadS16(&file->head->ymin) || + !table.ReadS16(&file->head->xmax) || + !table.ReadS16(&file->head->ymax)) { + return OTS_FAILURE(); + } + + if (file->head->xmin > file->head->xmax) { + return OTS_FAILURE(); + } + if (file->head->ymin > file->head->ymax) { + return OTS_FAILURE(); + } + + if (!table.ReadU16(&file->head->mac_style)) { + return OTS_FAILURE(); + } + + // We allow bits 0..6 + file->head->mac_style &= 0x7f; + + if (!table.ReadU16(&file->head->min_ppem)) { + return OTS_FAILURE(); + } + + // We don't care about the font direction hint + if (!table.Skip(2)) { + return OTS_FAILURE(); + } + + if (!table.ReadS16(&file->head->index_to_loc_format)) { + return OTS_FAILURE(); + } + if (file->head->index_to_loc_format < 0 || + file->head->index_to_loc_format > 1) { + return OTS_FAILURE(); + } + + int16_t glyph_data_format; + if (!table.ReadS16(&glyph_data_format) || + glyph_data_format) { + return OTS_FAILURE(); + } + + return true; +} + +bool ots_head_should_serialise(OpenTypeFile *file) { + return file->head; +} + +bool ots_head_serialise(OTSStream *out, OpenTypeFile *file) { + if (!out->WriteU32(0x00010000) || + !out->WriteU32(file->head->revision) || + !out->WriteU32(0) || // check sum not filled in yet + !out->WriteU32(0x5F0F3CF5) || + !out->WriteU16(file->head->flags) || + !out->WriteU16(file->head->ppem) || + !out->WriteR64(file->head->created) || + !out->WriteR64(file->head->modified) || + !out->WriteS16(file->head->xmin) || + !out->WriteS16(file->head->ymin) || + !out->WriteS16(file->head->xmax) || + !out->WriteS16(file->head->ymax) || + !out->WriteU16(file->head->mac_style) || + !out->WriteU16(file->head->min_ppem) || + !out->WriteS16(2) || + !out->WriteS16(file->head->index_to_loc_format) || + !out->WriteS16(0)) { + return OTS_FAILURE(); + } + + return true; +} + +void ots_head_free(OpenTypeFile *file) { + delete file->head; +} + +} // namespace diff --git a/src/head.h b/src/head.h new file mode 100644 index 0000000..5967c4b --- /dev/null +++ b/src/head.h @@ -0,0 +1,29 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_HEAD_H_ +#define OTS_HEAD_H_ + +#include "ots.h" + +namespace ots { + +struct OpenTypeHEAD { + uint32_t revision; + uint16_t flags; + uint16_t ppem; + uint64_t created; + uint64_t modified; + + int16_t xmin, xmax; + int16_t ymin, ymax; + + uint16_t mac_style; + uint16_t min_ppem; + int16_t index_to_loc_format; +}; + +} // namespace ots + +#endif // OTS_HEAD_H_ diff --git a/src/hhea.cc b/src/hhea.cc new file mode 100644 index 0000000..68704ef --- /dev/null +++ b/src/hhea.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "hhea.h" + +#include "head.h" +#include "maxp.h" + +// hhea - Horizontal Header +// http://www.microsoft.com/opentype/otspec/hhea.htm + +namespace ots { + +bool ots_hhea_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + OpenTypeHHEA *hhea = new OpenTypeHHEA; + file->hhea = hhea; + + uint32_t version; + if (!table.ReadU32(&version)) { + return OTS_FAILURE(); + } + if (version >> 16 != 1) { + return OTS_FAILURE(); + } + + if (!table.ReadS16(&hhea->ascent) || + !table.ReadS16(&hhea->descent) || + !table.ReadS16(&hhea->linegap) || + !table.ReadU16(&hhea->adv_width_max) || + !table.ReadS16(&hhea->min_lsb) || + !table.ReadS16(&hhea->min_rsb) || + !table.ReadS16(&hhea->x_max_extent) || + !table.ReadS16(&hhea->caret_slope_rise) || + !table.ReadS16(&hhea->caret_slope_run) || + !table.ReadS16(&hhea->caret_offset)) { + return OTS_FAILURE(); + } + + if (hhea->ascent < 0) { + OTS_WARNING("bad ascent: %d", hhea->ascent); + hhea->ascent = 0; + } + if (hhea->linegap < 0) { + OTS_WARNING("bad linegap: %d", hhea->linegap); + hhea->linegap = 0; + } + + if (!file->head) { + return OTS_FAILURE(); + } + + // if the font is non-slanted, caret_offset should be zero. + if (!(file->head->mac_style & 2) && + (hhea->caret_offset != 0)) { + OTS_WARNING("bad caret offset: %d", hhea->caret_offset); + hhea->caret_offset = 0; + } + + // skip the reserved bytes + if (!table.Skip(8)) { + return OTS_FAILURE(); + } + + int16_t data_format; + if (!table.ReadS16(&data_format)) { + return OTS_FAILURE(); + } + if (data_format) { + return OTS_FAILURE(); + } + + if (!table.ReadU16(&hhea->num_hmetrics)) { + return OTS_FAILURE(); + } + + if (!file->maxp) { + return OTS_FAILURE(); + } + + if (hhea->num_hmetrics > file->maxp->num_glyphs) { + return OTS_FAILURE(); + } + + return true; +} + +bool ots_hhea_should_serialise(OpenTypeFile *file) { + return file->hhea; +} + +bool ots_hhea_serialise(OTSStream *out, OpenTypeFile *file) { + const OpenTypeHHEA *hhea = file->hhea; + + if (!out->WriteU32(0x00010000) || + !out->WriteS16(hhea->ascent) || + !out->WriteS16(hhea->descent) || + !out->WriteS16(hhea->linegap) || + !out->WriteU16(hhea->adv_width_max) || + !out->WriteS16(hhea->min_lsb) || + !out->WriteS16(hhea->min_rsb) || + !out->WriteS16(hhea->x_max_extent) || + !out->WriteS16(hhea->caret_slope_rise) || + !out->WriteS16(hhea->caret_slope_run) || + !out->WriteS16(hhea->caret_offset) || + !out->WriteR64(0) || // reserved + !out->WriteS16(0) || // metric data format + !out->WriteU16(hhea->num_hmetrics)) { + return OTS_FAILURE(); + } + + return true; +} + +void ots_hhea_free(OpenTypeFile *file) { + delete file->hhea; +} + +} // namespace ots diff --git a/src/hhea.h b/src/hhea.h new file mode 100644 index 0000000..99aafde --- /dev/null +++ b/src/hhea.h @@ -0,0 +1,28 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_HHEA_H_ +#define OTS_HHEA_H_ + +#include "ots.h" + +namespace ots { + +struct OpenTypeHHEA { + int16_t ascent; + int16_t descent; + int16_t linegap; + uint16_t adv_width_max; + int16_t min_lsb; + int16_t min_rsb; + int16_t x_max_extent; + int16_t caret_slope_rise; + int16_t caret_slope_run; + int16_t caret_offset; + uint16_t num_hmetrics; +}; + +} // namespace ots + +#endif // OTS_HHEA_H_ diff --git a/src/hmtx.cc b/src/hmtx.cc new file mode 100644 index 0000000..3b6ece0 --- /dev/null +++ b/src/hmtx.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "hmtx.h" + +#include "hhea.h" +#include "maxp.h" + +// hmtx - Horizontal Metrics +// http://www.microsoft.com/opentype/otspec/hmtx.htm + +namespace ots { + +bool ots_hmtx_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + OpenTypeHMTX *hmtx = new OpenTypeHMTX; + file->hmtx = hmtx; + + if (!file->hhea || !file->maxp) { + return OTS_FAILURE(); + } + + // |num_hmetrics| is a uint16_t, so it's bounded < 65536. This limits that + // amount of memory that we'll allocate for this to a sane amount. + const unsigned num_hmetrics = file->hhea->num_hmetrics; + + if (num_hmetrics > file->maxp->num_glyphs) { + return OTS_FAILURE(); + } + if (!num_hmetrics) { + return OTS_FAILURE(); + } + const unsigned num_lsbs = file->maxp->num_glyphs - num_hmetrics; + + hmtx->metrics.reserve(num_hmetrics); + for (unsigned i = 0; i < num_hmetrics; ++i) { + uint16_t adv; + int16_t lsb; + if (!table.ReadU16(&adv) || !table.ReadS16(&lsb)) { + return OTS_FAILURE(); + } + + // Since so many fonts don't have proper value on |adv| and |lsb|, + // we should not call ots_failure() here. For example, about 20% of fonts + // in http://www.princexml.com/fonts/ (200+ fonts) fails these tests. + if (adv > file->hhea->adv_width_max) { + OTS_WARNING("bad adv: %u > %u", adv, file->hhea->adv_width_max); + adv = file->hhea->adv_width_max; + } + if (lsb < file->hhea->min_lsb) { + OTS_WARNING("bad lsb: %d < %d", lsb, file->hhea->min_lsb); + lsb = file->hhea->min_lsb; + } + + hmtx->metrics.push_back(std::make_pair(adv, lsb)); + } + + hmtx->lsbs.reserve(num_lsbs); + for (unsigned i = 0; i < num_lsbs; ++i) { + int16_t lsb; + if (!table.ReadS16(&lsb)) { + // Some Japanese font (e.g., mona.ttf) have bad hmtx table. + return OTS_FAILURE(); + } + + if (lsb < file->hhea->min_lsb) { + // The same as above. Three fonts in http://www.fontsquirrel.com/fontface + // (e.g., Notice2Std.otf) have weird lsb values. + OTS_WARNING("bad lsb: %d < %d", lsb, file->hhea->min_lsb); + lsb = file->hhea->min_lsb; + } + + hmtx->lsbs.push_back(lsb); + } + + return true; +} + +bool ots_hmtx_should_serialise(OpenTypeFile *file) { + return file->hmtx; +} + +bool ots_hmtx_serialise(OTSStream *out, OpenTypeFile *file) { + const OpenTypeHMTX *hmtx = file->hmtx; + + for (unsigned i = 0; i < hmtx->metrics.size(); ++i) { + if (!out->WriteU16(hmtx->metrics[i].first) || + !out->WriteS16(hmtx->metrics[i].second)) { + return OTS_FAILURE(); + } + } + + for (unsigned i = 0; i < hmtx->lsbs.size(); ++i) { + if (!out->WriteS16(hmtx->lsbs[i])) { + return OTS_FAILURE(); + } + } + + return true; +} + +void ots_hmtx_free(OpenTypeFile *file) { + delete file->hmtx; +} + +} // namespace ots diff --git a/src/hmtx.h b/src/hmtx.h new file mode 100644 index 0000000..79a9cb6 --- /dev/null +++ b/src/hmtx.h @@ -0,0 +1,22 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_HMTX_H_ +#define OTS_HMTX_H_ + +#include <utility> // std::pair +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeHMTX { + std::vector<std::pair<uint16_t, int16_t> > metrics; + std::vector<int16_t> lsbs; +}; + +} // namespace ots + +#endif // OTS_HMTX_H_ diff --git a/src/loca.cc b/src/loca.cc new file mode 100644 index 0000000..49588e3 --- /dev/null +++ b/src/loca.cc @@ -0,0 +1,98 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "loca.h" + +#include "head.h" +#include "maxp.h" + +// loca - Index to Location +// http://www.microsoft.com/opentype/otspec/loca.htm + +namespace ots { + +bool ots_loca_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + + // We can't do anything useful in validating this data except to ensure that + // the values are monotonically increasing. + + OpenTypeLOCA *loca = new OpenTypeLOCA; + file->loca = loca; + + if (!file->maxp || !file->head) { + return OTS_FAILURE(); + } + + const unsigned num_glyphs = file->maxp->num_glyphs; + unsigned last_offset = 0; + loca->offsets.resize(num_glyphs + 1); + // maxp->num_glyphs is uint16_t, thus the addition never overflows. + + if (file->head->index_to_loc_format == 0) { + // Note that the <= here (and below) is correct. There is one more offset + // than the number of glyphs in order to give the length of the final + // glyph. + for (unsigned i = 0; i <= num_glyphs; ++i) { + uint16_t offset; + if (!table.ReadU16(&offset)) { + return OTS_FAILURE(); + } + if (offset < last_offset) { + return OTS_FAILURE(); + } + last_offset = offset; + loca->offsets[i] = offset * 2; + } + } else { + for (unsigned i = 0; i <= num_glyphs; ++i) { + uint32_t offset; + if (!table.ReadU32(&offset)) { + return OTS_FAILURE(); + } + if (offset < last_offset) { + return OTS_FAILURE(); + } + last_offset = offset; + loca->offsets[i] = offset; + } + } + + return true; +} + +bool ots_loca_should_serialise(OpenTypeFile *file) { + return file->loca; +} + +bool ots_loca_serialise(OTSStream *out, OpenTypeFile *file) { + const OpenTypeLOCA *loca = file->loca; + const OpenTypeHEAD *head = file->head; + + if (!head) { + return OTS_FAILURE(); + } + + if (head->index_to_loc_format == 0) { + for (unsigned i = 0; i < loca->offsets.size(); ++i) { + if (!out->WriteU16(loca->offsets[i] >> 1)) { + return OTS_FAILURE(); + } + } + } else { + for (unsigned i = 0; i < loca->offsets.size(); ++i) { + if (!out->WriteU32(loca->offsets[i])) { + return OTS_FAILURE(); + } + } + } + + return true; +} + +void ots_loca_free(OpenTypeFile *file) { + delete file->loca; +} + +} // namespace ots diff --git a/src/loca.h b/src/loca.h new file mode 100644 index 0000000..255ef06 --- /dev/null +++ b/src/loca.h @@ -0,0 +1,20 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_LOCA_H_ +#define OTS_LOCA_H_ + +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeLOCA { + std::vector<uint32_t> offsets; +}; + +} // namespace ots + +#endif // OTS_LOCA_H_ diff --git a/src/ltsh.cc b/src/ltsh.cc new file mode 100644 index 0000000..57118c1 --- /dev/null +++ b/src/ltsh.cc @@ -0,0 +1,82 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ltsh.h" + +#include "maxp.h" + +// LTSH - Linear Threshold +// http://www.microsoft.com/typography/otspec/ltsh.htm + +#define DROP_THIS_TABLE \ + do { delete file->ltsh; file->ltsh = 0; } while (0) + +namespace ots { + +bool ots_ltsh_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + + if (!file->maxp) { + return OTS_FAILURE(); + } + + OpenTypeLTSH *ltsh = new OpenTypeLTSH; + file->ltsh = ltsh; + + uint16_t num_glyphs; + if (!table.ReadU16(<sh->version) || + !table.ReadU16(&num_glyphs)) { + return OTS_FAILURE(); + } + + if (ltsh->version != 0) { + OTS_WARNING("bad version: %u", ltsh->version); + DROP_THIS_TABLE; + return true; + } + + if (num_glyphs != file->maxp->num_glyphs) { + OTS_WARNING("bad num_glyphs: %u", num_glyphs); + DROP_THIS_TABLE; + return true; + } + + ltsh->ypels.reserve(num_glyphs); + for (unsigned i = 0; i < num_glyphs; ++i) { + uint8_t pel = 0; + if (!table.ReadU8(&pel)) { + return OTS_FAILURE(); + } + ltsh->ypels.push_back(pel); + } + + return true; +} + +bool ots_ltsh_should_serialise(OpenTypeFile *file) { + if (!file->glyf) return false; // this table is not for CFF fonts. + return file->ltsh; +} + +bool ots_ltsh_serialise(OTSStream *out, OpenTypeFile *file) { + const OpenTypeLTSH *ltsh = file->ltsh; + + if (!out->WriteU16(ltsh->version) || + !out->WriteU16(ltsh->ypels.size())) { + return OTS_FAILURE(); + } + for (unsigned i = 0; i < ltsh->ypels.size(); ++i) { + if (!out->Write(&(ltsh->ypels[i]), 1)) { + return OTS_FAILURE(); + } + } + + return true; +} + +void ots_ltsh_free(OpenTypeFile *file) { + delete file->ltsh; +} + +} // namespace ots diff --git a/src/ltsh.h b/src/ltsh.h new file mode 100644 index 0000000..23d97d7 --- /dev/null +++ b/src/ltsh.h @@ -0,0 +1,21 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_LTSH_H_ +#define OTS_LTSH_H_ + +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeLTSH { + uint16_t version; + std::vector<uint8_t> ypels; +}; + +} // namespace ots + +#endif // OTS_LTSH_H_ diff --git a/src/maxp.cc b/src/maxp.cc new file mode 100644 index 0000000..b984fe9 --- /dev/null +++ b/src/maxp.cc @@ -0,0 +1,128 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "maxp.h" + +// maxp - Maximum Profile +// http://www.microsoft.com/opentype/otspec/maxp.htm + +namespace ots { + +bool ots_maxp_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypeMAXP *maxp = new OpenTypeMAXP; + file->maxp = maxp; + + uint32_t version; + if (!table.ReadU32(&version)) { + return OTS_FAILURE(); + } + + if (version >> 16 > 1) { + return OTS_FAILURE(); + } + + if (!table.ReadU16(&maxp->num_glyphs)) { + return OTS_FAILURE(); + } + + if (!maxp->num_glyphs) { + return OTS_FAILURE(); + } + + if (version >> 16 == 1) { + maxp->version_1 = true; + if (!table.ReadU16(&maxp->max_points) || + !table.ReadU16(&maxp->max_contours) || + !table.ReadU16(&maxp->max_c_points) || + !table.ReadU16(&maxp->max_c_contours) || + !table.ReadU16(&maxp->max_zones) || + !table.ReadU16(&maxp->max_t_points) || + !table.ReadU16(&maxp->max_storage) || + !table.ReadU16(&maxp->max_fdefs) || + !table.ReadU16(&maxp->max_idefs) || + !table.ReadU16(&maxp->max_stack) || + !table.ReadU16(&maxp->max_glyf_insns) || + !table.ReadU16(&maxp->max_c_components) || + !table.ReadU16(&maxp->max_c_recursion)) { + return OTS_FAILURE(); + } + + if (maxp->max_zones == 0) { + // workaround for ipa*.ttf Japanese fonts. + OTS_WARNING("bad max_zones: %u", maxp->max_zones); + maxp->max_zones = 1; + } else if (maxp->max_zones == 3) { + // workaround for Ecolier-*.ttf fonts. + OTS_WARNING("bad max_zones: %u", maxp->max_zones); + maxp->max_zones = 2; + } + + if ((maxp->max_zones != 1) && (maxp->max_zones != 2)) { + return OTS_FAILURE(); + } + } else { + maxp->version_1 = false; + } + + return true; +} + +bool ots_maxp_should_serialise(OpenTypeFile *file) { + return file->maxp; +} + +bool ots_maxp_serialise(OTSStream *out, OpenTypeFile *file) { + const OpenTypeMAXP *maxp = file->maxp; + + if (!out->WriteU32(maxp->version_1 ? 0x00010000 : 0x00005000) || + !out->WriteU16(maxp->num_glyphs)) { + return OTS_FAILURE(); + } + + if (!maxp->version_1) return true; + + if (!out->WriteU16(maxp->max_points) || + !out->WriteU16(maxp->max_contours) || + !out->WriteU16(maxp->max_c_points) || + !out->WriteU16(maxp->max_c_contours)) { + return OTS_FAILURE(); + } + + if (g_transcode_hints) { + if (!out->WriteU16(maxp->max_zones) || + !out->WriteU16(maxp->max_t_points) || + !out->WriteU16(maxp->max_storage) || + !out->WriteU16(maxp->max_fdefs) || + !out->WriteU16(maxp->max_idefs) || + !out->WriteU16(maxp->max_stack) || + !out->WriteU16(maxp->max_glyf_insns)) { + return OTS_FAILURE(); + } + } else { + if (!out->WriteU16(1) || // max zones + !out->WriteU16(0) || // max twilight points + !out->WriteU16(0) || // max storage + !out->WriteU16(0) || // max function defs + !out->WriteU16(0) || // max instruction defs + !out->WriteU16(0) || // max stack elements + !out->WriteU16(0)) { // max instruction byte count + return OTS_FAILURE(); + } + } + + if (!out->WriteU16(maxp->max_c_components) || + !out->WriteU16(maxp->max_c_recursion)) { + return OTS_FAILURE(); + } + + return true; +} + +void ots_maxp_free(OpenTypeFile *file) { + delete file->maxp; +} + +} // namespace ots diff --git a/src/maxp.h b/src/maxp.h new file mode 100644 index 0000000..5809f75 --- /dev/null +++ b/src/maxp.h @@ -0,0 +1,35 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_MAXP_H_ +#define OTS_MAXP_H_ + +#include "ots.h" + +namespace ots { + +struct OpenTypeMAXP { + uint16_t num_glyphs; + bool version_1; + + uint16_t max_points; + uint16_t max_contours; + uint16_t max_c_points; + uint16_t max_c_contours; + + uint16_t max_zones; + uint16_t max_t_points; + uint16_t max_storage; + uint16_t max_fdefs; + uint16_t max_idefs; + uint16_t max_stack; + uint16_t max_glyf_insns; + + uint16_t max_c_components; + uint16_t max_c_recursion; +}; + +} // namespace ots + +#endif // OTS_MAXP_H_ diff --git a/src/name.cc b/src/name.cc new file mode 100644 index 0000000..2ab7ebc --- /dev/null +++ b/src/name.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <cstring> + +#include "ots.h" + +// name - Naming Table +// http://www.microsoft.com/opentype/otspec/name.htm + +namespace ots { + +bool ots_name_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + return true; +} + +bool ots_name_should_serialise(OpenTypeFile *) { + return true; +} + +bool ots_name_serialise(OTSStream *out, OpenTypeFile *) { + // NAME is a required table, but we don't want anything to do with it. Thus, + // we don't bother parsing it and we just serialise an empty name table. + + static const char * const kStrings[] = { + "Derived font data", // 0: copyright + "OTS derived font", // 1: the name the user sees + "Unspecified", // 2: face weight + "UniqueID", // 3: unique id + "OTS derivied font", // 4: human readable name + "Version 0.0", // 5: version + "False", // 6: postscript name + NULL, // 7: trademark data + "OTS", // 8: foundary + "OTS", // 9: designer + }; + static const size_t kStringsLen = sizeof(kStrings) / sizeof(kStrings[0]); + + unsigned num_strings = 0; + for (unsigned i = 0; i < kStringsLen; ++i) { + if (kStrings[i]) num_strings++; + } + + if (!out->WriteU16(0) || // version + !out->WriteU16(num_strings) || // count + !out->WriteU16(6 + num_strings * 12)) { // string data offset + return OTS_FAILURE(); + } + + unsigned current_offset = 0; + for (unsigned i = 0; i < kStringsLen; ++i) { + if (!kStrings[i]) continue; + + const size_t len = std::strlen(kStrings[i]) * 2; + if (!out->WriteU16(3) || // Windows + !out->WriteU16(1) || // Roman + !out->WriteU16(0x0409) || // US English + !out->WriteU16(i) || + !out->WriteU16(len) || + !out->WriteU16(current_offset)) { + return OTS_FAILURE(); + } + + current_offset += len; + } + + for (unsigned i = 0; i < kStringsLen; ++i) { + if (!kStrings[i]) continue; + + const size_t len = std::strlen(kStrings[i]); + for (size_t j = 0; j < len; ++j) { + uint16_t v = kStrings[i][j]; + if (!out->WriteU16(v)) { + return OTS_FAILURE(); + } + } + } + + return true; +} + +void ots_name_free(OpenTypeFile *) { +} + +} // namespace diff --git a/src/os2.cc b/src/os2.cc new file mode 100644 index 0000000..983482e --- /dev/null +++ b/src/os2.cc @@ -0,0 +1,290 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "os2.h" + +#include "head.h" + +// OS/2 - OS/2 and Windows Metrics +// http://www.microsoft.com/opentype/otspec/os2.htm + +namespace ots { + +bool ots_os2_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypeOS2 *os2 = new OpenTypeOS2; + file->os2 = os2; + + if (!table.ReadU16(&os2->version) || + !table.ReadS16(&os2->avg_char_width) || + !table.ReadU16(&os2->weight_class) || + !table.ReadU16(&os2->width_class) || + !table.ReadU16(&os2->type) || + !table.ReadS16(&os2->subscript_x_size) || + !table.ReadS16(&os2->subscript_y_size) || + !table.ReadS16(&os2->subscript_x_offset) || + !table.ReadS16(&os2->subscript_y_offset) || + !table.ReadS16(&os2->superscript_x_size) || + !table.ReadS16(&os2->superscript_y_size) || + !table.ReadS16(&os2->superscript_x_offset) || + !table.ReadS16(&os2->superscript_y_offset) || + !table.ReadS16(&os2->strikeout_size) || + !table.ReadS16(&os2->strikeout_position) || + !table.ReadS16(&os2->family_class)) { + return OTS_FAILURE(); + } + + if (os2->version > 4) { + return OTS_FAILURE(); + } + + // Some linux fonts (e.g., Kedage-t.ttf and LucidaSansDemiOblique.ttf) have + // weird weight/width classes. Overwrite them with FW_NORMAL/1/9. + if (os2->weight_class < 100 || + os2->weight_class > 900 || + os2->weight_class % 100) { + OTS_WARNING("bad weight: %u", os2->weight_class); + os2->weight_class = 400; // FW_NORMAL + } + if (os2->width_class < 1) { + OTS_WARNING("bad width: %u", os2->width_class); + os2->width_class = 1; + } else if (os2->width_class > 9) { + OTS_WARNING("bad width: %u", os2->width_class); + os2->width_class = 9; + } + + // lowest 3 bits of fsType are exclusive. + if (os2->type & 0x2) { + // mask bits 2 & 3. + os2->type &= 0xfff3u; + } else if (os2->type & 0x4) { + // mask bits 1 & 3. + os2->type &= 0xfff4u; + } else if (os2->type & 0x8) { + // mask bits 1 & 2. + os2->type &= 0xfff9u; + } + + // mask reserved bits. use only 0..3, 8, 9 bits. + os2->type &= 0x30f; + + if (os2->subscript_x_size < 0) { + OTS_WARNING("bad subscript_x_size: %d", os2->subscript_x_size); + os2->subscript_x_size = 0; + } + if (os2->subscript_y_size < 0) { + OTS_WARNING("bad subscript_y_size: %d", os2->subscript_y_size); + os2->subscript_y_size = 0; + } + if (os2->superscript_x_size < 0) { + OTS_WARNING("bad superscript_x_size: %d", os2->superscript_x_size); + os2->superscript_x_size = 0; + } + if (os2->superscript_y_size < 0) { + OTS_WARNING("bad superscript_y_size: %d", os2->superscript_y_size); + os2->superscript_y_size = 0; + } + if (os2->strikeout_size < 0) { + OTS_WARNING("bad strikeout_size: %d", os2->strikeout_size); + os2->strikeout_size = 0; + } + + for (unsigned i = 0; i < 10; ++i) { + if (!table.ReadU8(&os2->panose[i])) { + return OTS_FAILURE(); + } + } + + if (!table.ReadU32(&os2->unicode_range_1) || + !table.ReadU32(&os2->unicode_range_2) || + !table.ReadU32(&os2->unicode_range_3) || + !table.ReadU32(&os2->unicode_range_4) || + !table.ReadU32(&os2->vendor_id) || + !table.ReadU16(&os2->selection) || + !table.ReadU16(&os2->first_char_index) || + !table.ReadU16(&os2->last_char_index) || + !table.ReadS16(&os2->typo_ascender) || + !table.ReadS16(&os2->typo_descender) || + !table.ReadS16(&os2->typo_linegap) || + !table.ReadU16(&os2->win_ascent) || + !table.ReadU16(&os2->win_descent)) { + return OTS_FAILURE(); + } + + // If bit 6 is set, then bits 0 and 5 must be clear. + if (os2->selection & 0x40) { + os2->selection &= 0xffdeu; + } + + // the settings of bits 0 and 1 must be reflected in the macStyle bits + // in the 'head' table. + if (!file->head) { + return OTS_FAILURE(); + } + if ((os2->selection & 0x1) && + !(file->head->mac_style & 0x2)) { + OTS_WARNING("adjusting Mac style (italic)"); + file->head->mac_style |= 0x2; + } + if ((os2->selection & 0x2) && + !(file->head->mac_style & 0x4)) { + OTS_WARNING("adjusting Mac style (underscore)"); + file->head->mac_style |= 0x4; + } + + // While bit 6 on implies that bits 0 and 1 of macStyle are clear, + // the reverse is not true. + if ((os2->selection & 0x40) && + (file->head->mac_style & 0x3)) { + OTS_WARNING("adjusting Mac style (regular)"); + file->head->mac_style &= 0xfffcu; + } + + if ((os2->version < 4) && + (os2->selection & 0x300)) { + // bit 8 and 9 must be unset in OS/2 table versions less than 4. + return OTS_FAILURE(); + } + + // mask reserved bits. use only 0..9 bits. + os2->selection &= 0x3ff; + + if (os2->first_char_index > os2->last_char_index) { + return OTS_FAILURE(); + } + if (os2->typo_linegap < 0) { + OTS_WARNING("bad linegap: %d", os2->typo_linegap); + os2->typo_linegap = 0; + } + + if (os2->version < 1) { + // http://www.microsoft.com/typography/otspec/os2ver0.htm + return true; + } + + if (length < offsetof(OpenTypeOS2, code_page_range_2)) { + OTS_WARNING("bad version number: %u", os2->version); + // Some fonts (e.g., kredit1.ttf and quinquef.ttf) have weird version + // numbers. Fix them. + os2->version = 0; + return true; + } + + if (!table.ReadU32(&os2->code_page_range_1) || + !table.ReadU32(&os2->code_page_range_2)) { + return OTS_FAILURE(); + } + + if (os2->version < 2) { + // http://www.microsoft.com/typography/otspec/os2ver1.htm + return true; + } + + if (length < offsetof(OpenTypeOS2, max_context)) { + OTS_WARNING("bad version number: %u", os2->version); + // some Japanese fonts (e.g., mona.ttf) have weird version number. + // fix them. + os2->version = 1; + return true; + } + + if (!table.ReadS16(&os2->x_height) || + !table.ReadS16(&os2->cap_height) || + !table.ReadU16(&os2->default_char) || + !table.ReadU16(&os2->break_char) || + !table.ReadU16(&os2->max_context)) { + return OTS_FAILURE(); + } + + if (os2->x_height < 0) { + OTS_WARNING("bad x_height: %d", os2->x_height); + os2->x_height = 0; + } + if (os2->cap_height < 0) { + OTS_WARNING("bad cap_height: %d", os2->cap_height); + os2->cap_height = 0; + } + + return true; +} + +bool ots_os2_should_serialise(OpenTypeFile *file) { + return file->os2; +} + +bool ots_os2_serialise(OTSStream *out, OpenTypeFile *file) { + const OpenTypeOS2 *os2 = file->os2; + + if (!out->WriteU16(os2->version) || + !out->WriteS16(os2->avg_char_width) || + !out->WriteU16(os2->weight_class) || + !out->WriteU16(os2->width_class) || + !out->WriteU16(os2->type) || + !out->WriteS16(os2->subscript_x_size) || + !out->WriteS16(os2->subscript_y_size) || + !out->WriteS16(os2->subscript_x_offset) || + !out->WriteS16(os2->subscript_y_offset) || + !out->WriteS16(os2->superscript_x_size) || + !out->WriteS16(os2->superscript_y_size) || + !out->WriteS16(os2->superscript_x_offset) || + !out->WriteS16(os2->superscript_y_offset) || + !out->WriteS16(os2->strikeout_size) || + !out->WriteS16(os2->strikeout_position) || + !out->WriteS16(os2->family_class)) { + return OTS_FAILURE(); + } + + for (unsigned i = 0; i < 10; ++i) { + if (!out->Write(&os2->panose[i], 1)) { + return OTS_FAILURE(); + } + } + + if (!out->WriteU32(os2->unicode_range_1) || + !out->WriteU32(os2->unicode_range_2) || + !out->WriteU32(os2->unicode_range_3) || + !out->WriteU32(os2->unicode_range_4) || + !out->WriteU32(os2->vendor_id) || + !out->WriteU16(os2->selection) || + !out->WriteU16(os2->first_char_index) || + !out->WriteU16(os2->last_char_index) || + !out->WriteS16(os2->typo_ascender) || + !out->WriteS16(os2->typo_descender) || + !out->WriteS16(os2->typo_linegap) || + !out->WriteU16(os2->win_ascent) || + !out->WriteU16(os2->win_descent)) { + return OTS_FAILURE(); + } + + if (os2->version < 1) { + return true; + } + + if (!out->WriteU32(os2->code_page_range_1) || + !out->WriteU32(os2->code_page_range_2)) { + return OTS_FAILURE(); + } + + if (os2->version < 2) { + return true; + } + + if (!out->WriteS16(os2->x_height) || + !out->WriteS16(os2->cap_height) || + !out->WriteU16(os2->default_char) || + !out->WriteU16(os2->break_char) || + !out->WriteU16(os2->max_context)) { + return OTS_FAILURE(); + } + + return true; +} + +void ots_os2_free(OpenTypeFile *file) { + delete file->os2; +} + +} // namespace ots diff --git a/src/os2.h b/src/os2.h new file mode 100644 index 0000000..9e0fc34 --- /dev/null +++ b/src/os2.h @@ -0,0 +1,54 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_OS2_H_ +#define OTS_OS2_H_ + +#include "ots.h" + +namespace ots { + +struct OpenTypeOS2 { + uint16_t version; + int16_t avg_char_width; + uint16_t weight_class; + uint16_t width_class; + uint16_t type; + int16_t subscript_x_size; + int16_t subscript_y_size; + int16_t subscript_x_offset; + int16_t subscript_y_offset; + int16_t superscript_x_size; + int16_t superscript_y_size; + int16_t superscript_x_offset; + int16_t superscript_y_offset; + int16_t strikeout_size; + int16_t strikeout_position; + int16_t family_class; + uint8_t panose[10]; + uint32_t unicode_range_1; + uint32_t unicode_range_2; + uint32_t unicode_range_3; + uint32_t unicode_range_4; + uint32_t vendor_id; + uint16_t selection; + uint16_t first_char_index; + uint16_t last_char_index; + int16_t typo_ascender; + int16_t typo_descender; + int16_t typo_linegap; + uint16_t win_ascent; + uint16_t win_descent; + uint32_t code_page_range_1; + uint32_t code_page_range_2; + int16_t x_height; + int16_t cap_height; + uint16_t default_char; + uint16_t break_char; + uint16_t max_context; +}; + +} // namespace ots + +#endif // OTS_OS2_H_ diff --git a/src/ots.cc b/src/ots.cc new file mode 100644 index 0000000..024af63 --- /dev/null +++ b/src/ots.cc @@ -0,0 +1,505 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ots.h" + +#include <sys/types.h> + +#include <algorithm> +#include <cstdlib> +#include <cstring> +#include <map> +#include <vector> + +// The OpenType Font File +// http://www.microsoft.com/opentype/otspec/otff.htm + +#define F(name, capname) \ + namespace ots { \ + bool ots_##name##_parse(OpenTypeFile *f, const uint8_t *d, size_t l); \ + bool ots_##name##_should_serialise(OpenTypeFile *f); \ + bool ots_##name##_serialise(OTSStream *s, OpenTypeFile *f); \ + void ots_##name##_free(OpenTypeFile *f); \ + } + // TODO(yusukes): change these function names to follow the coding rule. +FOR_EACH_TABLE_TYPE +#undef F + +namespace { + +bool g_debug_output = true; + +struct OpenTypeTable { + uint32_t tag; + uint32_t chksum; + uint32_t offset; + uint32_t length; +}; + +// Round a value up to the nearest multiple of 4. Note that this can overflow +// and return zero. +template<typename T> T Round4(T value) { + return (value + 3) & ~3; +} + +uint32_t Tag(const char *tag_str) { + uint32_t ret; + std::memcpy(&ret, tag_str, 4); + return ret; +} + +bool CheckTag(uint32_t tag_value) { + for (unsigned i = 0; i < 4; ++i) { + const uint32_t check = tag_value & 0xff; + if (check < 32 || check > 126) { + return false; // non-ASCII character found. + } + tag_value >>= 8; + } + return true; +} + +struct OutputTable { + uint32_t tag; + size_t offset; + size_t length; + uint32_t chksum; + + static bool SortByTag(const OutputTable& a, const OutputTable& b) { + const uint32_t atag = ntohl(a.tag); + const uint32_t btag = ntohl(b.tag); + return atag < btag; + } +}; + +struct BypassTable { + uint32_t tag; + size_t offset; // offset into input data + size_t length; +}; + +const struct { + uint32_t tag; + bool (*parse)(ots::OpenTypeFile *otf, const uint8_t *data, size_t length); + bool (*serialise)(ots::OTSStream *out, ots::OpenTypeFile *file); + bool (*should_serialise)(ots::OpenTypeFile *file); + void (*free)(ots::OpenTypeFile *file); + bool required; + bool bypass; +} table_parsers[] = { + { Tag("maxp"), ots::ots_maxp_parse, ots::ots_maxp_serialise, + ots::ots_maxp_should_serialise, ots::ots_maxp_free, 1, 0 }, + { Tag("cmap"), ots::ots_cmap_parse, ots::ots_cmap_serialise, + ots::ots_cmap_should_serialise, ots::ots_cmap_free, 1, 0 }, + { Tag("head"), ots::ots_head_parse, ots::ots_head_serialise, + ots::ots_head_should_serialise, ots::ots_head_free, 1, 0 }, + { Tag("hhea"), ots::ots_hhea_parse, ots::ots_hhea_serialise, + ots::ots_hhea_should_serialise, ots::ots_hhea_free, 1, 0 }, + { Tag("hmtx"), ots::ots_hmtx_parse, ots::ots_hmtx_serialise, + ots::ots_hmtx_should_serialise, ots::ots_hmtx_free, 1, 0 }, + { Tag("name"), ots::ots_name_parse, ots::ots_name_serialise, + ots::ots_name_should_serialise, ots::ots_name_free, 1, 0 }, + { Tag("OS/2"), ots::ots_os2_parse, ots::ots_os2_serialise, + ots::ots_os2_should_serialise, ots::ots_os2_free, 1, 0 }, + { Tag("post"), ots::ots_post_parse, ots::ots_post_serialise, + ots::ots_post_should_serialise, ots::ots_post_free, 1, 0 }, + { Tag("loca"), ots::ots_loca_parse, ots::ots_loca_serialise, + ots::ots_loca_should_serialise, ots::ots_loca_free, 0, 0 }, + { Tag("glyf"), ots::ots_glyf_parse, ots::ots_glyf_serialise, + ots::ots_glyf_should_serialise, ots::ots_glyf_free, 0, 0 }, + { Tag("CFF "), ots::ots_cff_parse, ots::ots_cff_serialise, + ots::ots_cff_should_serialise, ots::ots_cff_free, 0, 0 }, + { Tag("VDMX"), ots::ots_vdmx_parse, ots::ots_vdmx_serialise, + ots::ots_vdmx_should_serialise, ots::ots_vdmx_free, 0, 0 }, + { Tag("hdmx"), ots::ots_hdmx_parse, ots::ots_hdmx_serialise, + ots::ots_hdmx_should_serialise, ots::ots_hdmx_free, 0, 0 }, + { Tag("gasp"), ots::ots_gasp_parse, ots::ots_gasp_serialise, + ots::ots_gasp_should_serialise, ots::ots_gasp_free, 0, 0 }, + { Tag("cvt "), ots::ots_cvt_parse, ots::ots_cvt_serialise, + ots::ots_cvt_should_serialise, ots::ots_cvt_free, 0, 0 }, + { Tag("fpgm"), ots::ots_fpgm_parse, ots::ots_fpgm_serialise, + ots::ots_fpgm_should_serialise, ots::ots_fpgm_free, 0, 0 }, + { Tag("prep"), ots::ots_prep_parse, ots::ots_prep_serialise, + ots::ots_prep_should_serialise, ots::ots_prep_free, 0, 0 }, + { Tag("LTSH"), ots::ots_ltsh_parse, ots::ots_ltsh_serialise, + ots::ots_ltsh_should_serialise, ots::ots_ltsh_free, 0, 0 }, + { Tag("VORG"), ots::ots_vorg_parse, ots::ots_vorg_serialise, + ots::ots_vorg_should_serialise, ots::ots_vorg_free, 0, 0 }, + { 0, NULL, NULL, NULL, 0, 0, 0 }, +}; + +bool DoProcess(ots::OpenTypeFile *header, + ots::OTSStream *output, const uint8_t *data, size_t length) { + ots::Buffer file(data, length); + + // we disallow all files > 1GB in size for sanity. + if (length > 1024 * 1024 * 1024) { + return OTS_FAILURE(); + } + + if (!file.ReadTag(&header->version)) { + return OTS_FAILURE(); + } + if ((header->version != Tag("\x00\x01\x00\x00")) && + // OpenType fonts with CFF data have 'OTTO' tag. + (header->version != Tag("OTTO")) && + // Older Mac fonts might have 'true' or 'typ1' tag. + (header->version != Tag("true")) && (header->version != Tag("typ1"))) { + return OTS_FAILURE(); + } + + if (!file.ReadU16(&header->num_tables) || + !file.ReadU16(&header->search_range) || + !file.ReadU16(&header->entry_selector) || + !file.ReadU16(&header->range_shift)) { + return OTS_FAILURE(); + } + + // search_range is (Maximum power of 2 <= numTables) x 16. Thus, to avoid + // overflow num_tables is, at most, 2^16 / 16 = 2^12 + if (header->num_tables >= 4096 || header->num_tables < 1) { + return OTS_FAILURE(); + } + + unsigned max_pow2 = 0; + while (1u << (max_pow2 + 1) <= header->num_tables) { + max_pow2++; + } + const uint16_t expected_search_range = (1u << max_pow2) << 4; + + // Don't call ots_failure() here since ~25% of fonts (250+ fonts) in + // http://www.princexml.com/fonts/ have bad search_range value. + if (header->search_range != expected_search_range) { + OTS_WARNING("bad search range"); + header->search_range = expected_search_range; // Fix the value. + } + + // entry_selector is Log2(maximum power of 2 <= numTables) + if (header->entry_selector != max_pow2) { + return OTS_FAILURE(); + } + + // range_shift is NumTables x 16-searchRange. We know that 16*num_tables + // doesn't over flow because we range checked it above. Also, we know that + // it's > header->search_range by construction of search_range. + const uint32_t expected_range_shift + = 16 * header->num_tables - header->search_range; + if (header->range_shift != expected_range_shift) { + OTS_WARNING("bad range shift"); + header->range_shift = expected_range_shift; // the same as above. + } + + // Next up is the list of tables. + std::vector<OpenTypeTable> tables; + + for (unsigned i = 0; i < header->num_tables; ++i) { + OpenTypeTable table; + if (!file.ReadTag(&table.tag) || + !file.ReadU32(&table.chksum) || + !file.ReadU32(&table.offset) || + !file.ReadU32(&table.length)) { + return OTS_FAILURE(); + } + + tables.push_back(table); + } + + const size_t data_offset = file.offset(); + + for (unsigned i = 0; i < header->num_tables; ++i) { + // the tables must be sorted by tag (when taken as big-endian numbers). + // This also remove the possibility of duplicate tables. + if (i) { + const uint32_t this_tag = ntohl(tables[i].tag); + const uint32_t prev_tag = ntohl(tables[i - 1].tag); + if (this_tag <= prev_tag) { + return OTS_FAILURE(); + } + } + + // all tag names must be built from printing characters + if (!CheckTag(tables[i].tag)) { + return OTS_FAILURE(); + } + + // tables must be 4-byte aligned + if (tables[i].offset & 3) { + return OTS_FAILURE(); + } + + // and must be within the file + if (tables[i].offset < data_offset || tables[i].offset >= length) { + return OTS_FAILURE(); + } + // disallow all tables with a zero length + if (tables[i].length < 1) { + // Note: malayalam.ttf has zero length CVT table... + return OTS_FAILURE(); + } + // disallow all tables with a length > 1GB + if (tables[i].length > 1024 * 1024 * 1024) { + return OTS_FAILURE(); + } + // since we required that the file be < 1GB in length, and that the table + // length is < 1GB, the following addtion doesn't overflow + const uint32_t end_byte = Round4(tables[i].offset + tables[i].length); + if (!end_byte || end_byte > length) { + return OTS_FAILURE(); + } + } + + std::map<uint32_t, OpenTypeTable> table_map; + for (unsigned i = 0; i < header->num_tables; ++i) { + table_map[tables[i].tag] = tables[i]; + } + + // check that the tables are not overlapping. + std::vector<std::pair<uint32_t, uint8_t> > overlap_checker; + for (unsigned i = 0; i < header->num_tables; ++i) { + overlap_checker.push_back( + std::make_pair(tables[i].offset, 1 /* start */)); + overlap_checker.push_back( + std::make_pair(tables[i].offset + tables[i].length, 0 /* end */)); + } + std::sort(overlap_checker.begin(), overlap_checker.end()); + int overlap_count = 0; + for (unsigned i = 0; i < overlap_checker.size(); ++i) { + overlap_count += (overlap_checker[i].second ? 1 : -1); + if (overlap_count > 1) { + return OTS_FAILURE(); + } + } + + std::vector<BypassTable> bypass_tables; + + for (unsigned i = 0; ; ++i) { + if (table_parsers[i].parse == NULL) break; + + const std::map<uint32_t, OpenTypeTable>::const_iterator it + = table_map.find(table_parsers[i].tag); + + if (it == table_map.end()) { + if (table_parsers[i].required) { + return OTS_FAILURE(); + } + continue; + } + + if (table_parsers[i].bypass) { + BypassTable bypass; + bypass.offset = it->second.offset; + bypass.length = it->second.length; + bypass.tag = table_parsers[i].tag; + bypass_tables.push_back(bypass); + } + + if (!table_parsers[i].parse( + header, data + it->second.offset, it->second.length)) { + return OTS_FAILURE(); + } + } + + if (header->cff) { + // font with PostScript glyph + if (header->version != Tag("OTTO")) { + return OTS_FAILURE(); + } + if (header->glyf || header->loca) { + // mixing outline formats are not recommended + return OTS_FAILURE(); + } + } else { + if (!header->glyf || !header->loca) { + // No TrueType glyph found. + // Note: bitmap-only fonts are not supported. + return OTS_FAILURE(); + } + } + + unsigned num_output_tables = 0; + for (unsigned i = 0; ; ++i) { + if (table_parsers[i].parse == NULL) { + break; + } + + if (table_parsers[i].bypass) { + continue; + } + + if (table_parsers[i].should_serialise(header)) { + num_output_tables++; + } + } + + num_output_tables += bypass_tables.size(); + + max_pow2 = 0; + while (1u << (max_pow2 + 1) <= num_output_tables) { + max_pow2++; + } + const uint16_t output_search_range = (1u << max_pow2) << 4; + + output->ResetChecksum(); + if (!output->WriteTag(header->version) || + !output->WriteU16(num_output_tables) || + !output->WriteU16(output_search_range) || + !output->WriteU16(max_pow2) || + !output->WriteU16((num_output_tables << 4) - output_search_range)) { + return OTS_FAILURE(); + } + const uint32_t offset_table_chksum = output->chksum(); + + const size_t table_record_offset = output->Tell(); + if (!output->Pad(16 * num_output_tables)) { + return OTS_FAILURE(); + } + + std::vector<OutputTable> out_tables; + + size_t head_table_offset = 0; + for (unsigned i = 0; i < bypass_tables.size(); ++i) { + const BypassTable &bypass = bypass_tables[i]; + + OutputTable out; + out.tag = bypass.tag; + out.offset = output->Tell(); + + output->ResetChecksum(); + if (bypass.tag == Tag("head")) { + head_table_offset = out.offset; + } + if (!output->Write(data + bypass.offset, bypass.length)) { + return OTS_FAILURE(); + } + const size_t end_offset = output->Tell(); + out.length = end_offset - out.offset; + + // align tables to four bytes + if (!output->Pad((4 - (end_offset & 3)) % 4)) { + return OTS_FAILURE(); + } + out.chksum = output->chksum(); + out_tables.push_back(out); + } + + for (unsigned i = 0; ; ++i) { + if (table_parsers[i].parse == NULL) { + break; + } + + if (table_parsers[i].bypass) { + continue; + } + + if (!table_parsers[i].should_serialise(header)) { + continue; + } + + OutputTable out; + out.tag = table_parsers[i].tag; + out.offset = output->Tell(); + + output->ResetChecksum(); + if (table_parsers[i].tag == Tag("head")) { + head_table_offset = out.offset; + } + if (!table_parsers[i].serialise(output, header)) { + return OTS_FAILURE(); + } + + const size_t end_offset = output->Tell(); + out.length = end_offset - out.offset; + + // align tables to four bytes + if (!output->Pad((4 - (end_offset & 3)) % 4)) { + return OTS_FAILURE(); + } + out.chksum = output->chksum(); + out_tables.push_back(out); + } + + const size_t end_of_file = output->Tell(); + + // Need to sort the output tables for inclusion in the file + std::sort(out_tables.begin(), out_tables.end(), OutputTable::SortByTag); + if (!output->Seek(table_record_offset)) { + return OTS_FAILURE(); + } + + output->ResetChecksum(); + uint32_t tables_chksum = 0; + for (unsigned i = 0; i < out_tables.size(); ++i) { + if (!output->WriteTag(out_tables[i].tag) || + !output->WriteU32(out_tables[i].chksum) || + !output->WriteU32(out_tables[i].offset) || + !output->WriteU32(out_tables[i].length)) { + return OTS_FAILURE(); + } + tables_chksum += out_tables[i].chksum; + } + const uint32_t table_record_chksum = output->chksum(); + + // http://www.microsoft.com/typography/otspec/otff.htm + const uint32_t file_chksum + = offset_table_chksum + tables_chksum + table_record_chksum; + const uint32_t chksum_magic = static_cast<uint32_t>(0xb1b0afba) - file_chksum; + + // seek into the 'head' table and write in the checksum magic value + assert(head_table_offset != 0); + if (!output->Seek(head_table_offset + 8)) { + return OTS_FAILURE(); + } + if (!output->WriteU32(chksum_magic)) { + return OTS_FAILURE(); + } + + if (!output->Seek(end_of_file)) { + return OTS_FAILURE(); + } + + return true; +} + +} // namespace + +namespace ots { + +void DisableDebugOutput() { + g_debug_output = false; +} + +bool Process(OTSStream *output, const uint8_t *data, size_t length) { + OpenTypeFile header; + const bool result = DoProcess(&header, output, data, length); + + for (unsigned i = 0; ; ++i) { + if (table_parsers[i].parse == NULL) break; + table_parsers[i].free(&header); + } + return result; +} + +#if !defined(_MSC_VER) && defined(OTS_DEBUG) +bool Failure(const char *f, int l, const char *fn) { + if (g_debug_output) { + std::fprintf(stderr, "ERROR at %s:%d (%s)\n", f, l, fn); + std::fflush(stderr); + } + return false; +} + +void Warning(const char *f, int l, const char *format, ...) { + if (g_debug_output) { + std::fprintf(stderr, "WARNING at %s:%d: ", f, l); + std::va_list va; + va_start(va, format); + std::vfprintf(stderr, format, va); + va_end(va); + std::fprintf(stderr, "\n"); + std::fflush(stderr); + } +} +#endif + +} // namespace ots diff --git a/src/ots.h b/src/ots.h new file mode 100644 index 0000000..d026d34 --- /dev/null +++ b/src/ots.h @@ -0,0 +1,192 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_H_ +#define OTS_H_ + +#include <cstdarg> +#include <cstdio> +#include <cstdlib> +#include <cstring> + +#include "opentype-sanitiser.h" + +namespace ots { + +#if defined(_MSC_VER) || !defined(OTS_DEBUG) +#define OTS_FAILURE() false +#else +#define OTS_FAILURE() ots::Failure(__FILE__, __LINE__, __PRETTY_FUNCTION__) +bool Failure(const char *f, int l, const char *fn); +#endif + +#if defined(_MSC_VER) +// MSVC supports C99 style variadic macros. +#define OTS_WARNING(format, ...) +#else +// GCC +#if defined(OTS_DEBUG) +#define OTS_WARNING(format, args...) \ + ots::Warning(__FILE__, __LINE__, format, ##args) +void Warning(const char *f, int l, const char *format, ...) + __attribute__((format(printf, 3, 4))); +#else +#define OTS_WARNING(format, args...) +#endif +#endif + +// Define OTS_NO_TRANSCODE_HINTS (i.e., g++ -DOTS_NO_TRANSCODE_HINTS) if you +// want to omit TrueType hinting instructions and variables in glyf, fpgm, prep, +// and cvt tables. +#if defined(OTS_NO_TRANSCODE_HINTS) +const bool g_transcode_hints = false; +#else +const bool g_transcode_hints = true; +#endif + +// ----------------------------------------------------------------------------- +// Buffer helper class +// +// This class perform some trival buffer operations while checking for +// out-of-bounds errors. As a family they return false if anything is amiss, +// updating the current offset otherwise. +// ----------------------------------------------------------------------------- +class Buffer { + public: + Buffer(const uint8_t *buffer, size_t len) + : buffer_(buffer), + length_(len), + offset_(0) { } + + bool Skip(size_t n_bytes) { + return Read(NULL, n_bytes); + } + + bool Read(uint8_t *buffer, size_t n_bytes) { + if (n_bytes > 1024 * 1024 * 1024) { + return OTS_FAILURE(); + } + if ((offset_ + n_bytes > length_) || + (offset_ > length_ - n_bytes)) { + return OTS_FAILURE(); + } + if (buffer) { + std::memcpy(buffer, buffer_ + offset_, n_bytes); + } + offset_ += n_bytes; + return true; + } + + inline bool ReadU8(uint8_t *value) { + if (offset_ + 1 > length_) { + return OTS_FAILURE(); + } + *value = buffer_[offset_]; + ++offset_; + return true; + } + + bool ReadU16(uint16_t *value) { + if (offset_ + 2 > length_) { + return OTS_FAILURE(); + } + std::memcpy(value, buffer_ + offset_, sizeof(uint16_t)); + *value = ntohs(*value); + offset_ += 2; + return true; + } + + bool ReadS16(int16_t *value) { + return ReadU16(reinterpret_cast<uint16_t*>(value)); + } + + bool ReadU32(uint32_t *value) { + if (offset_ + 4 > length_) { + return OTS_FAILURE(); + } + std::memcpy(value, buffer_ + offset_, sizeof(uint32_t)); + *value = ntohl(*value); + offset_ += 4; + return true; + } + + bool ReadS32(int32_t *value) { + return ReadU32(reinterpret_cast<uint32_t*>(value)); + } + + bool ReadTag(uint32_t *value) { + if (offset_ + 4 > length_) { + return OTS_FAILURE(); + } + std::memcpy(value, buffer_ + offset_, sizeof(uint32_t)); + offset_ += 4; + return true; + } + + bool ReadR64(uint64_t *value) { + if (offset_ + 8 > length_) { + return OTS_FAILURE(); + } + std::memcpy(value, buffer_ + offset_, sizeof(uint64_t)); + offset_ += 8; + return true; + } + + size_t offset() const { return offset_; } + size_t length() const { return length_; } + + void set_offset(size_t newoffset) { offset_ = newoffset; } + + private: + const uint8_t * const buffer_; + const size_t length_; + size_t offset_; +}; + +#define FOR_EACH_TABLE_TYPE \ + F(cff, CFF) \ + F(cmap, CMAP) \ + F(cvt, CVT) \ + F(fpgm, FPGM) \ + F(gasp, GASP) \ + F(glyf, GLYF) \ + F(hdmx, HDMX) \ + F(head, HEAD) \ + F(hhea, HHEA) \ + F(hmtx, HMTX) \ + F(loca, LOCA) \ + F(ltsh, LTSH) \ + F(maxp, MAXP) \ + F(name, NAME) \ + F(os2, OS2) \ + F(post, POST) \ + F(prep, PREP) \ + F(vdmx, VDMX) \ + F(vorg, VORG) + +#define F(name, capname) struct OpenType##capname; +FOR_EACH_TABLE_TYPE +#undef F + +struct OpenTypeFile { + OpenTypeFile() { +#define F(name, capname) name = NULL; + FOR_EACH_TABLE_TYPE +#undef F + } + + uint32_t version; + uint16_t num_tables; + uint16_t search_range; + uint16_t entry_selector; + uint16_t range_shift; + +#define F(name, capname) OpenType##capname *name; +FOR_EACH_TABLE_TYPE +#undef F +}; + +} // namespace ots + +#endif // OTS_H_ diff --git a/src/post.cc b/src/post.cc new file mode 100644 index 0000000..cb42417 --- /dev/null +++ b/src/post.cc @@ -0,0 +1,181 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "post.h" + +#include "maxp.h" + +// post - PostScript +// http://www.microsoft.com/opentype/otspec/post.htm + +namespace ots { + +bool ots_post_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypePOST *post = new OpenTypePOST; + file->post = post; + + if (!table.ReadU32(&post->version) || + !table.ReadU32(&post->italic_angle) || + !table.ReadS16(&post->underline) || + !table.ReadS16(&post->underline_thickness) || + !table.ReadU32(&post->is_fixed_pitch)) { + return OTS_FAILURE(); + } + + if (post->underline_thickness < 0) { + post->underline_thickness = 1; + } + + if (post->version == 0x00010000) { + return true; + } else if (post->version == 0x00030000) { + return true; + } else if (post->version != 0x00020000) { + // 0x00025000 is deprecated. We don't accept it. + return OTS_FAILURE(); + } + + // We have a version 2 table with a list of Pascal strings at the end + + // We don't care about the memory usage fields. We'll set all these to zero + // when serialising + if (!table.Skip(16)) { + return OTS_FAILURE(); + } + + uint16_t num_glyphs; + if (!table.ReadU16(&num_glyphs)) { + return OTS_FAILURE(); + } + + if (!file->maxp) { + return OTS_FAILURE(); + } + + if (num_glyphs == 0) { + if (file->maxp->num_glyphs > 258) { + return OTS_FAILURE(); + } + OTS_WARNING("table version is 1, but no glyf names are found"); + // workaround for fonts in http://www.fontsquirrel.com/fontface + // (e.g., yataghan.ttf). + post->version = 0x00010000; + return true; + } + + if (num_glyphs != file->maxp->num_glyphs) { + // Note: Fixedsys500c.ttf seems to have inconsistent num_glyphs values. + return OTS_FAILURE(); + } + + post->glyph_name_index.resize(num_glyphs); + for (unsigned i = 0; i < num_glyphs; ++i) { + if (!table.ReadU16(&post->glyph_name_index[i])) { + return OTS_FAILURE(); + } + if (post->glyph_name_index[i] >= 32768) { + // Note: droid_arialuni.ttf fails this test. + return OTS_FAILURE(); // reserved area. + } + } + + // Now we have an array of Pascal strings. We have to check that they are all + // valid and read them in. + const size_t strings_offset = table.offset(); + const uint8_t *strings = data + strings_offset; + const uint8_t *strings_end = data + length; + + for (;;) { + if (strings == strings_end) break; + const unsigned string_length = *strings; + if (strings + 1 + string_length > strings_end) { + return OTS_FAILURE(); + } + if (std::memchr(strings + 1, '\0', string_length)) { + return OTS_FAILURE(); + } + post->names.push_back( + std::string(reinterpret_cast<const char*>(strings + 1), string_length)); + strings += 1 + string_length; + } + const unsigned num_strings = post->names.size(); + + // check that all the references are within bounds + for (unsigned i = 0; i < num_glyphs; ++i) { + unsigned offset = post->glyph_name_index[i]; + if (offset < 258) { + continue; + } + + offset -= 258; + if (offset >= num_strings) { + return OTS_FAILURE(); + } + } + + return true; +} + +bool ots_post_should_serialise(OpenTypeFile *file) { + return file->post; +} + +bool ots_post_serialise(OTSStream *out, OpenTypeFile *file) { + const OpenTypePOST *post = file->post; + + // OpenType with CFF glyphs must have v3 post table. + if (file->post && file->cff && file->post->version != 0x00030000) { + return OTS_FAILURE(); + } + + if (!out->WriteU32(post->version) || + !out->WriteU32(post->italic_angle) || + !out->WriteS16(post->underline) || + !out->WriteS16(post->underline_thickness) || + !out->WriteU32(post->is_fixed_pitch) || + !out->WriteU32(0) || + !out->WriteU32(0) || + !out->WriteU32(0) || + !out->WriteU32(0)) { + return OTS_FAILURE(); + } + + if (post->version != 0x00020000) { + return true; // v1.0 and v3.0 does not have glyph names. + } + + if (!out->WriteU16(post->glyph_name_index.size())) { + return OTS_FAILURE(); + } + + for (unsigned i = 0; i < post->glyph_name_index.size(); ++i) { + if (!out->WriteU16(post->glyph_name_index[i])) { + return OTS_FAILURE(); + } + } + + // Now we just have to write out the strings in the correct order + for (unsigned i = 0; i < post->names.size(); ++i) { + const std::string& s = post->names[i]; + const uint8_t string_length = s.size(); + if (!out->Write(&string_length, 1)) { + return OTS_FAILURE(); + } + // Some ttf fonts (e.g., frank.ttf on Windows Vista) have zero-length name. + // We allow them. + if (string_length > 0 && !out->Write(s.data(), string_length)) { + return OTS_FAILURE(); + } + } + + return true; +} + +void ots_post_free(OpenTypeFile *file) { + delete file->post; +} + +} // namespace ots diff --git a/src/post.h b/src/post.h new file mode 100644 index 0000000..f220d4f --- /dev/null +++ b/src/post.h @@ -0,0 +1,29 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_POST_H_ +#define OTS_POST_H_ + +#include "ots.h" + +#include <map> +#include <string> +#include <vector> + +namespace ots { + +struct OpenTypePOST { + uint32_t version; + uint32_t italic_angle; + int16_t underline; + int16_t underline_thickness; + uint32_t is_fixed_pitch; + + std::vector<uint16_t> glyph_name_index; + std::vector<std::string> names; +}; + +} // namespace ots + +#endif // OTS_POST_H_ diff --git a/src/prep.cc b/src/prep.cc new file mode 100644 index 0000000..6567a6f --- /dev/null +++ b/src/prep.cc @@ -0,0 +1,50 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "prep.h" + +// prep - Control Value Program +// http://www.microsoft.com/opentype/otspec/prep.htm + +namespace ots { + +bool ots_prep_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypePREP *prep = new OpenTypePREP; + file->prep = prep; + + if (length >= 128 * 1024u) { + return OTS_FAILURE(); // almost all prep tables are less than 9k bytes. + } + + if (!table.Skip(length)) { + return OTS_FAILURE(); + } + + prep->data = data; + prep->length = length; + return true; +} + +bool ots_prep_should_serialise(OpenTypeFile *file) { + if (!file->glyf) return false; // this table is not for CFF fonts. + return g_transcode_hints && file->prep; +} + +bool ots_prep_serialise(OTSStream *out, OpenTypeFile *file) { + const OpenTypePREP *prep = file->prep; + + if (!out->Write(prep->data, prep->length)) { + return OTS_FAILURE(); + } + + return true; +} + +void ots_prep_free(OpenTypeFile *file) { + delete file->prep; +} + +} // namespace ots diff --git a/src/prep.h b/src/prep.h new file mode 100644 index 0000000..935ca11 --- /dev/null +++ b/src/prep.h @@ -0,0 +1,19 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_PREP_H_ +#define OTS_PREP_H_ + +#include "ots.h" + +namespace ots { + +struct OpenTypePREP { + const uint8_t *data; + uint32_t length; +}; + +} // namespace ots + +#endif // OTS_PREP_H_ diff --git a/src/vdmx.cc b/src/vdmx.cc new file mode 100644 index 0000000..73236c1 --- /dev/null +++ b/src/vdmx.cc @@ -0,0 +1,176 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "vdmx.h" + +// VDMX - Vertical Device Metrics +// http://www.microsoft.com/opentype/otspec/vdmx.htm + +#define DROP_THIS_TABLE \ + do { delete file->vdmx; file->vdmx = 0; } while (0) + +namespace ots { + +bool ots_vdmx_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + file->vdmx = new OpenTypeVDMX; + OpenTypeVDMX * const vdmx = file->vdmx; + + if (!table.ReadU16(&vdmx->version) || + !table.ReadU16(&vdmx->num_recs) || + !table.ReadU16(&vdmx->num_ratios)) { + return OTS_FAILURE(); + } + + if (vdmx->version > 1) { + OTS_WARNING("bad version: %u", vdmx->version); + DROP_THIS_TABLE; + return true; // continue transcoding + } + + vdmx->rat_ranges.reserve(vdmx->num_ratios); + for (unsigned i = 0; i < vdmx->num_ratios; ++i) { + OpenTypeVDMXRatioRecord rec; + + if (!table.ReadU8(&rec.charset) || + !table.ReadU8(&rec.x_ratio) || + !table.ReadU8(&rec.y_start_ratio) || + !table.ReadU8(&rec.y_end_ratio)) { + return OTS_FAILURE(); + } + + if (rec.charset > 1) { + OTS_WARNING("bad charset: %u", rec.charset); + DROP_THIS_TABLE; + return true; + } + if (rec.y_start_ratio < rec.y_end_ratio) { + OTS_WARNING("bad y ratio"); + DROP_THIS_TABLE; + return true; + } + + // All values set to zero signal the default grouping to use; + // if present, this must be the last Ratio group in the table. + if ((i < vdmx->num_ratios - 1u) && + (rec.x_ratio == 0) && + (rec.y_start_ratio == 0) && + (rec.y_end_ratio == 0)) { + // workaround for fonts which have 2 or more {0, 0, 0} terminators. + OTS_WARNING("superfluous terminator found"); + DROP_THIS_TABLE; + return true; // continue transcoding + } + + vdmx->rat_ranges.push_back(rec); + } + + vdmx->offsets.reserve(vdmx->num_ratios); + const size_t current_offset = table.offset(); + // current_offset is less than (2 bytes * 3) + (4 bytes * USHRT_MAX) = 256k. + for (unsigned i = 0; i < vdmx->num_ratios; ++i) { + uint16_t offset; + if (!table.ReadU16(&offset)) { + return OTS_FAILURE(); + } + if (current_offset + offset >= length) { // thus doesn't overflow. + return OTS_FAILURE(); + } + + vdmx->offsets.push_back(offset); + } + + vdmx->groups.reserve(vdmx->num_recs); + for (unsigned i = 0; i < vdmx->num_recs; ++i) { + OpenTypeVDMXGroup group; + if (!table.ReadU16(&group.recs) || + !table.ReadU8(&group.startsz) || + !table.ReadU8(&group.endsz)) { + return OTS_FAILURE(); + } + group.entries.reserve(group.recs); + for (unsigned j = 0; j < group.recs; ++j) { + OpenTypeVDMXVTable vt; + if (!table.ReadU16(&vt.y_pel_height) || + !table.ReadS16(&vt.y_max) || + !table.ReadS16(&vt.y_min)) { + return OTS_FAILURE(); + } + if (vt.y_max < vt.y_min) { + OTS_WARNING("bad y min/max"); + DROP_THIS_TABLE; + return true; + } + + // This table must appear in sorted order (sorted by yPelHeight), + // but need not be continuous. + if ((j != 0) && (group.entries[j - 1].y_pel_height >= vt.y_pel_height)) { + OTS_WARNING("the table is not sorted"); + DROP_THIS_TABLE; + return true; + } + + group.entries.push_back(vt); + } + vdmx->groups.push_back(group); + } + + return true; +} + +bool ots_vdmx_should_serialise(OpenTypeFile *file) { + if (!file->glyf) return false; // this table is not for CFF fonts. + return file->vdmx; +} + +bool ots_vdmx_serialise(OTSStream *out, OpenTypeFile *file) { + OpenTypeVDMX * const vdmx = file->vdmx; + + if (!out->WriteU16(vdmx->version) || + !out->WriteU16(vdmx->num_recs) || + !out->WriteU16(vdmx->num_ratios)) { + return OTS_FAILURE(); + } + + for (unsigned i = 0; i < vdmx->rat_ranges.size(); ++i) { + const OpenTypeVDMXRatioRecord& rec = vdmx->rat_ranges[i]; + if (!out->Write(&rec.charset, 1) || + !out->Write(&rec.x_ratio, 1) || + !out->Write(&rec.y_start_ratio, 1) || + !out->Write(&rec.y_end_ratio, 1)) { + return OTS_FAILURE(); + } + } + + for (unsigned i = 0; i < vdmx->offsets.size(); ++i) { + if (!out->WriteU16(vdmx->offsets[i])) { + return OTS_FAILURE(); + } + } + + for (unsigned i = 0; i < vdmx->groups.size(); ++i) { + const OpenTypeVDMXGroup& group = vdmx->groups[i]; + if (!out->WriteU16(group.recs) || + !out->Write(&group.startsz, 1) || + !out->Write(&group.endsz, 1)) { + return OTS_FAILURE(); + } + for (unsigned j = 0; j < group.entries.size(); ++j) { + const OpenTypeVDMXVTable& vt = group.entries[j]; + if (!out->WriteU16(vt.y_pel_height) || + !out->WriteS16(vt.y_max) || + !out->WriteS16(vt.y_min)) { + return OTS_FAILURE(); + } + } + } + + return true; +} + +void ots_vdmx_free(OpenTypeFile *file) { + delete file->vdmx; +} + +} // namespace ots diff --git a/src/vdmx.h b/src/vdmx.h new file mode 100644 index 0000000..1d959ef --- /dev/null +++ b/src/vdmx.h @@ -0,0 +1,45 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_VDMX_H_ +#define OTS_VDMX_H_ + +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeVDMXRatioRecord { + uint8_t charset; + uint8_t x_ratio; + uint8_t y_start_ratio; + uint8_t y_end_ratio; +}; + +struct OpenTypeVDMXVTable { + uint16_t y_pel_height; + int16_t y_max; + int16_t y_min; +}; + +struct OpenTypeVDMXGroup { + uint16_t recs; + uint8_t startsz; + uint8_t endsz; + std::vector<OpenTypeVDMXVTable> entries; +}; + +struct OpenTypeVDMX { + uint16_t version; + uint16_t num_recs; + uint16_t num_ratios; + std::vector<OpenTypeVDMXRatioRecord> rat_ranges; + std::vector<uint16_t> offsets; + std::vector<OpenTypeVDMXGroup> groups; +}; + +} // namespace ots + +#endif // OTS_VDMX_H_ diff --git a/src/vorg.cc b/src/vorg.cc new file mode 100644 index 0000000..9b70a98 --- /dev/null +++ b/src/vorg.cc @@ -0,0 +1,97 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "vorg.h" + +#include <vector> + +// VORG - Vertical Origin Table +// http://www.microsoft.com/opentype/otspec/vorg.htm + +#define DROP_THIS_TABLE \ + do { delete file->vorg; file->vorg = 0; } while (0) + +namespace ots { + +bool ots_vorg_parse(OpenTypeFile *file, const uint8_t *data, size_t length) { + Buffer table(data, length); + file->vorg = new OpenTypeVORG; + OpenTypeVORG * const vorg = file->vorg; + + uint16_t num_recs; + if (!table.ReadU16(&vorg->major_version) || + !table.ReadU16(&vorg->minor_version) || + !table.ReadS16(&vorg->default_vert_origin_y) || + !table.ReadU16(&num_recs)) { + return OTS_FAILURE(); + } + if (vorg->major_version != 1) { + OTS_WARNING("bad major version: %u", vorg->major_version); + DROP_THIS_TABLE; + return true; + } + if (vorg->minor_version != 0) { + OTS_WARNING("bad minor version: %u", vorg->minor_version); + DROP_THIS_TABLE; + return true; + } + + // num_recs might be zero (e.g., DFHSMinchoPro5-W3-Demo.otf). + if (!num_recs) { + return true; + } + + uint16_t last_glyph_index = 0; + vorg->metrics.reserve(num_recs); + for (unsigned i = 0; i < num_recs; ++i) { + OpenTypeVORGMetrics rec; + + if (!table.ReadU16(&rec.glyph_index) || + !table.ReadS16(&rec.vert_origin_y)) { + return OTS_FAILURE(); + } + if ((i != 0) && (rec.glyph_index <= last_glyph_index)) { + OTS_WARNING("the table is not sorted"); + DROP_THIS_TABLE; + return true; + } + last_glyph_index = rec.glyph_index; + + vorg->metrics.push_back(rec); + } + + return true; +} + +bool ots_vorg_should_serialise(OpenTypeFile *file) { + if (!file->cff) return false; // this table is not for fonts with TT glyphs. + return file->vorg; +} + +bool ots_vorg_serialise(OTSStream *out, OpenTypeFile *file) { + OpenTypeVORG * const vorg = file->vorg; + + if (!out->WriteU16(vorg->major_version) || + !out->WriteU16(vorg->minor_version) || + !out->WriteS16(vorg->default_vert_origin_y) || + !out->WriteU16(vorg->metrics.size())) { + return OTS_FAILURE(); + } + + for (unsigned i = 0; i < vorg->metrics.size(); ++i) { + const OpenTypeVORGMetrics& rec = vorg->metrics[i]; + if (!out->WriteU16(rec.glyph_index) || + !out->WriteS16(rec.vert_origin_y)) { + return OTS_FAILURE(); + } + } + + return true; +} + +void ots_vorg_free(OpenTypeFile *file) { + delete file->vorg; +} + +} // namespace ots diff --git a/src/vorg.h b/src/vorg.h new file mode 100644 index 0000000..c3d3ffd --- /dev/null +++ b/src/vorg.h @@ -0,0 +1,28 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_VORG_H_ +#define OTS_VORG_H_ + +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeVORGMetrics { + uint16_t glyph_index; + int16_t vert_origin_y; +}; + +struct OpenTypeVORG { + uint16_t major_version; + uint16_t minor_version; + int16_t default_vert_origin_y; + std::vector<OpenTypeVORGMetrics> metrics; +}; + +} // namespace ots + +#endif // OTS_VORG_H_ diff --git a/test/README b/test/README new file mode 100644 index 0000000..c31f7ce --- /dev/null +++ b/test/README @@ -0,0 +1,243 @@ +------------------------------------------------------------------------------ +ot-sanitise - TTF/OTF font transcoder + +Description: + ot-sanitise is a program which validates and transcodes a truetype or + opentype font file using the OTS library: + + transcoded_font = ValidateAndTranscode(original_font); + if (validation_error) + PrintErrorAndExit; + OutputToStdout(transcoded_font); + +Usage: + $ ./ot-sanitise ttf_or_otf_file > transcoded_file + +Example: + $ ./ot-sanitise sample.otf > transcoded_sample.otf + $ ./ot-sanitise malformed.ttf > transcoded_malformed.ttf + WARNING at ots/src/ots.cc:158: bad range shift + ERROR at ots/src/ots.cc:199 (bool<unnamed>::do_ots_process(ots::OpenTypeFile*, ots::OTSStream*, const uint8_t*, size_t)) + Failed to sanitise file! + $ + +------------------------------------------------------------------------------ +idempotent - TTF/OTF font transcoder (for OTS debugging) + +Description: + idempotent is a program which validates and transcodes a truetype or opentype + font file using OTS. This tool transcodes the original font twice and then + verifies that the two transcoded fonts are identical: + + t1 = ValidateAndTranscode(original_font); + if (validation_error) + PrintErrorAndExit; + t2 = ValidateAndTranscode(t1); + if (validation_error) + PrintErrorAndExit; + if (t1 != t2) + PrintErrorAndExit; + + This tool is basically for OTS developers. + +Usage: + $ ./idempotent ttf_or_otf_file + +Example: + $ ./idempotent sample.otf + $ ./idempotent malformed.ttf + WARNING at ots/src/ots.cc:158: bad range shift + ERROR at ots/src/ots.cc:199 (bool<unnamed>::do_ots_process(ots::OpenTypeFile*, ots::OTSStream*, const uint8_t*, size_t)) + Failed to sanitise file! + $ + +------------------------------------------------------------------------------ +validator_checker - font validation checker + +Description: + validator_checker is a program which is intended to validate malformed fonts. + If the program detects that the font is invalid, it prints "OK" and returns + with 0 (success). If it coulndn't detect any errors, the program then opens + the transcoded font and renders some characters using FreeType2: + + transcoded_font = ValidateAndTranscode(malicious_font); + if (validation_error) + Print("OK"); + OpenAndRenderSomeCharacters(transcoded_font); # may cause SIGSEGV + Print("OK"); + + If SEGV doesn't raise inside FreeType2 library, the program prints "OK" and + returns with 0 as well. You should run this tool under the catchsegv or + valgrind command so that you can easily verify that all transformed fonts + don't crash the library (see the example below). + +Usage: + $ catchsegv ./validator_checker malicous_ttf_or_otf_file + +Example: + $ for f in malformed/*.ttf ; do catchsegv ./validator-checker "$f" ; done + OK: the malicious font was filtered: malformed/1.ttf + OK: the malicious font was filtered: malformed/2.ttf + OK: FreeType2 didn't crash: malformed/3.ttf + OK: the malicious font was filtered: malformed/4.ttf + $ + +------------------------------------------------------------------------------ +perf - performance checker + +Description: + perf is a program which validates and transcodes a truetype or opentype font + file N times using OTS, then prints the elapsed time: + + for (N times) + ValidateAndTranscode(original_font); + Print(elapsed_time_in_us / N); + +Usage: + $ ./perf ttf_or_otf_file + +Example: + $ ./perf sample.ttf + 903 [us] sample.ttf (139332 bytes, 154 [byte/us]) + $ ./perf sample-bold.otf + 291 [us] sample-bold.otf (150652 bytes, 517 [byte/us]) + +------------------------------------------------------------------------------ +side-by-side - font quality checker + +Description: + side-by-side is a program which renders some characters (ASCII, Latin-1, CJK) + using both original font and transcoded font and checks that the two rendering + results are exactly equal. + + The following Unicode characters are used during the test: + 0x0020 - 0x007E // Basic Latin + 0x00A1 - 0x017F // Latin-1 + 0x1100 - 0x11FF // Hangul + 0x3040 - 0x309F // Japanese HIRAGANA letters + 0x3130 - 0x318F // Hangul + 0x4E00 - 0x4F00 // CJK Kanji/Hanja + 0xAC00 - 0xAD00 // Hangul + + This tool uses FreeType2 library. + Note: This tool doesn't check kerning (GPOS/kern) nor font substitution + (GSUB). These should be tested in Layout tests if necessary. + +Usage: + $ ./side-by-side ttf_or_otf_file + +Example: + $ ./side-by-side linux/kochi-gothic.ttf # no problem + $ ./side-by-side free/kredit1.ttf # this is known issue of OTS. + bitmap metrics doesn't match! (14, 57), (37, 45) + EXPECTED: + + +#######*. + +##########+ + .###+.#. .#. + *#* # #* + ##. # ## + ## # ## + ## # ## + ## #. ## + ##. #. .## + ##. #. .## + *#+ *+ +#* + *#+ *+ +#* + *#+ *+ +#* + *#+ *+ +#* + *#+ *+ *#* + *#+ ++ *#+ + +#* +* *#+ + +#* +* *#+ + +#* +* *#+ + +#* +* ##. + +#* +* ##. + .## .# ## + .## .# ## + .## .# ## + ## # ## + ## # ## + ## # .## + ## # .## + ## .#+ +#* + ## +######* + ##.+#######* + *##########* + +##########+ + #########* + .######## + +####+ + + + + + + + .*######* + +##*.*##### + .##+.#+ +# + *#* ## #+ + ##*### ## + ###### ## + ##+.##+ +## + ## ########## + ## +######### + ## +######## + *#. .########* + .#* #########. + +##########+ + +*######* + + ACTUAL: + + .*##*+ + +##+.##*. + .#* .##.+#* + *# ### *#+ + #*######+ .*#+ + #########*. +#*. + ###########* +#* + *############+ *#+ + +##############. .##. + *##############* +#* + +###############+ *#+ + *###############+ .*#+ + .###############*. +#*. + +###############* +#* + *###############+ *#+ + .*###############+ .*#+ + +###############*. +#* + +###############* ** + *###############+ #+ + .###############* ## + +############+ ## + +########* .## + .######. +### + +#####+ .*#..# + +#####* *###..# + *#####. +#######* + +#####+ .*########. + +#####* +#########* + *#####. +##########+ + +#####+ *#########*. + .#####* +##########+ + *#####. +##########* + +#####+ *#########*. + .#####* +##########+ + *#####+ +##########* + .#*++#+ *#########*. + .#+ ## +##########+ + ****###+.##########* + ##################. + ###+ *#########+ + ## +########* + *#+ *########. + ##.#######+ + +#######* + *###*. + + + Glyph mismatch! (file: free/kredit1.ttf, U+0021, 100pt)! + $ +------------------------------------------------------------------------------ diff --git a/test/SConstruct b/test/SConstruct new file mode 100644 index 0000000..4e688d3 --- /dev/null +++ b/test/SConstruct @@ -0,0 +1,43 @@ +# Build script for Linux +# +# Usage: +# $ cd ots/test/ +# $ scons -c # clean +# $ scons # build +# + +# Since the validator-checker tool might handle malicious font files, all hardening options for recent g++/ld are enabled just in case. +# See http://wiki.debian.org/Hardening for details. +env = Environment(CCFLAGS = ['-O2', '-I../include', '-I/usr/include/freetype2', '-ggdb', '-Wall', '-W', '-Wno-unused-parameter', '-fPIE', '-fstack-protector', '-D_FORTIFY_SOURCE=2', '-DOTS_DEBUG'], LINKFLAGS = ['-ggdb', '-Wl,-z,relro', '-Wl,-z,now', '-pie']) +# TODO(yusukes): better to use pkg-config freetype2 --cflags + +env.Library('../src/libots.a', + ['../src/cff.cc', + '../src/cmap.cc', + '../src/cvt.cc', + '../src/fpgm.cc', + '../src/gasp.cc', + '../src/glyf.cc', + '../src/hdmx.cc', + '../src/head.cc', + '../src/hhea.cc', + '../src/hmtx.cc', + '../src/loca.cc', + '../src/ltsh.cc', + '../src/maxp.cc', + '../src/name.cc', + '../src/os2.cc', + '../src/ots.cc', + '../src/post.cc', + '../src/prep.cc', + '../src/vdmx.cc', + '../src/vorg.cc' + ]) + +env.Program('../test/ot-sanitise.cc', LIBS = ['ots'], LIBPATH='../src') +env.Program('../test/idempotent.cc', LIBS = ['ots'], LIBPATH='../src') +env.Program('../test/perf.cc', LIBS = ['ots'], LIBPATH='../src') + +# TODO(yusukes): better to use pkg-config freetype2 --libs +env.Program('../test/side-by-side.cc', LIBS = ['ots', 'freetype', 'z', 'm'], LIBPATH = '../src') +env.Program('../test/validator-checker.cc', LIBS = ['ots', 'freetype', 'z', 'm'], LIBPATH = '../src') diff --git a/test/file-stream.h b/test/file-stream.h new file mode 100644 index 0000000..58f85e1 --- /dev/null +++ b/test/file-stream.h @@ -0,0 +1,39 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef OTS_FILE_STREAM_H_ +#define OTS_FILE_STREAM_H_ + +#include "opentype-sanitiser.h" + +namespace ots { + +class FILEStream : public OTSStream { + public: + explicit FILEStream(FILE *stream) + : file_(stream) { + } + + ~FILEStream() { + } + + bool WriteRaw(const void *data, size_t length) { + return fwrite(data, length, 1, file_) == 1; + } + + bool Seek(off_t position) { + return fseek(file_, position, SEEK_SET) == 0; + } + + off_t Tell() const { + return ftell(file_); + } + + private: + FILE * const file_; +}; + +} // namespace ots + +#endif // OTS_FILE_STREAM_H_ diff --git a/test/idempotent.cc b/test/idempotent.cc new file mode 100644 index 0000000..7f33a47 --- /dev/null +++ b/test/idempotent.cc @@ -0,0 +1,94 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <fcntl.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <cstdio> +#include <cstdlib> +#include <cstring> + +#include "opentype-sanitiser.h" +#include "ots-memory-stream.h" + +namespace { + +int Usage(const char *argv0) { + std::fprintf(stderr, "Usage: %s <ttf file>\n", argv0); + return 1; +} + +} // namespace + +int main(int argc, char **argv) { + if (argc != 2) return Usage(argv[0]); + + const int fd = ::open(argv[1], O_RDONLY); + if (fd < 0) { + ::perror("open"); + return 1; + } + + struct stat st; + ::fstat(fd, &st); + + uint8_t *data = new uint8_t[st.st_size]; + if (::read(fd, data, st.st_size) != st.st_size) { + ::close(fd); + std::fprintf(stderr, "Failed to read file!\n"); + return 1; + } + ::close(fd); + + // A transcoded font is usually smaller than an original font. + // However, it can be slightly bigger than the original one due to + // name table replacement and/or padding for glyf table. + static const size_t kPadLen = 20 * 1024; + uint8_t *result = new uint8_t[st.st_size + kPadLen]; + ots::MemoryStream output(result, st.st_size + kPadLen); + + bool r = ots::Process(&output, data, st.st_size); + if (!r) { + std::fprintf(stderr, "Failed to sanitise file!\n"); + return 1; + } + const size_t result_len = output.Tell(); + free(data); + + uint8_t *result2 = new uint8_t[result_len]; + ots::MemoryStream output2(result2, result_len); + r = ots::Process(&output2, result, result_len); + if (!r) { + std::fprintf(stderr, "Failed to sanitise previous output!\n"); + return 1; + } + const size_t result2_len = output2.Tell(); + + bool dump_results = false; + if (result2_len != result_len) { + std::fprintf(stderr, "Outputs differ in length\n"); + dump_results = true; + } else if (std::memcmp(result2, result, result_len)) { + std::fprintf(stderr, "Outputs differ in content\n"); + dump_results = true; + } + + if (dump_results) { + std::fprintf(stderr, "Dumping results to out1.tff and out2.tff\n"); + int fd1 = ::open("out1.ttf", O_WRONLY | O_CREAT | O_TRUNC, 0600); + int fd2 = ::open("out2.ttf", O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd1 < 0 || fd2 < 0) { + ::perror("opening output file"); + return 1; + } + if ((::write(fd1, result, result_len) < 0) || + (::write(fd2, result2, result2_len) < 0)) { + ::perror("writing output file"); + return 1; + } + } + + return 0; +} diff --git a/test/ot-sanitise.cc b/test/ot-sanitise.cc new file mode 100644 index 0000000..313a6bc --- /dev/null +++ b/test/ot-sanitise.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// A very simple driver program while sanitises the file given as argv[1] and +// writes the sanitised version to stdout. + +#include <fcntl.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <cstdio> +#include <cstdlib> + +#include "file-stream.h" +#include "opentype-sanitiser.h" + +namespace { + +int Usage(const char *argv0) { + std::fprintf(stderr, "Usage: %s ttf_file > dest_ttf_file\n", argv0); + return 1; +} + +} // namespace + +int main(int argc, char **argv) { + if (argc != 2) return Usage(argv[0]); + if (::isatty(1)) return Usage(argv[0]); + + const int fd = ::open(argv[1], O_RDONLY); + if (fd < 0) { + ::perror("open"); + return 1; + } + + struct stat st; + ::fstat(fd, &st); + + uint8_t *data = new uint8_t[st.st_size]; + if (::read(fd, data, st.st_size) != st.st_size) { + ::perror("read"); + return 1; + } + ::close(fd); + + ots::FILEStream output(stdout); + const bool result = ots::Process(&output, data, st.st_size); + + if (!result) { + std::fprintf(stderr, "Failed to sanitise file!\n"); + } + return !result; +} diff --git a/test/perf.cc b/test/perf.cc new file mode 100644 index 0000000..34fe931 --- /dev/null +++ b/test/perf.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/time.h> // for timersub macro. +#include <unistd.h> +#include <time.h> + +#include <cstdio> +#include <cstdlib> +#include <cstring> + +#include "opentype-sanitiser.h" +#include "ots-memory-stream.h" + +namespace { + +int Usage(const char *argv0) { + std::fprintf(stderr, "Usage: %s <ttf file>\n", argv0); + return 1; +} + +} // namespace + +int main(int argc, char **argv) { + ots::DisableDebugOutput(); // turn off ERROR and WARNING outputs. + + if (argc != 2) return Usage(argv[0]); + + const int fd = ::open(argv[1], O_RDONLY); + if (fd < 0) { + ::perror("open"); + return 1; + } + + struct stat st; + ::fstat(fd, &st); + + uint8_t *data = new uint8_t[st.st_size]; + if (::read(fd, data, st.st_size) != st.st_size) { + std::fprintf(stderr, "Failed to read file!\n"); + return 1; + } + + // A transcoded font is usually smaller than an original font. + // However, it can be slightly bigger than the original one due to + // name table replacement and/or padding for glyf table. + static const size_t kPadLen = 20 * 1024; + uint8_t *result = new uint8_t[st.st_size + kPadLen]; + + int num_repeat = 250; + if (st.st_size < 1024 * 1024) { + num_repeat = 2500; + } + if (st.st_size < 1024 * 100) { + num_repeat = 5000; + } + + struct timeval start, end, elapsed; + ::gettimeofday(&start, 0); + for (int i = 0; i < num_repeat; ++i) { + ots::MemoryStream output(result, st.st_size + kPadLen); + bool r = ots::Process(&output, data, st.st_size); + if (!r) { + std::fprintf(stderr, "Failed to sanitise file!\n"); + return 1; + } + } + ::gettimeofday(&end, 0); + timersub(&end, &start, &elapsed); + + uint64_t us = ((elapsed.tv_sec * 1000 * 1000) + elapsed.tv_usec) / num_repeat; + std::fprintf(stderr, "%lu [us] %s (%lu bytes, %lu [byte/us])\n", + us, argv[1], st.st_size, (us ? st.st_size / us : 0)); + + return 0; +} diff --git a/test/side-by-side.cc b/test/side-by-side.cc new file mode 100644 index 0000000..1b5ff90 --- /dev/null +++ b/test/side-by-side.cc @@ -0,0 +1,281 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <fcntl.h> +#include <freetype/ftoutln.h> +#include <ft2build.h> +#include FT_FREETYPE_H +#include <sys/stat.h> +#include <sys/types.h> + +#include <cstdio> +#include <cstdlib> +#include <cstring> + +#include "opentype-sanitiser.h" +#include "ots-memory-stream.h" + +namespace { + +void DumpBitmap(const FT_Bitmap *bitmap) { + for (int i = 0; i < bitmap->rows * bitmap->width; ++i) { + if (bitmap->buffer[i] > 192) { + std::fprintf(stderr, "#"); + } else if (bitmap->buffer[i] > 128) { + std::fprintf(stderr, "*"); + } else if (bitmap->buffer[i] > 64) { + std::fprintf(stderr, "+"); + } else if (bitmap->buffer[i] > 32) { + std::fprintf(stderr, "."); + } else { + std::fprintf(stderr, " "); + } + + if ((i + 1) % bitmap->width == 0) { + std::fprintf(stderr, "\n"); + } + } +} + +int CompareBitmaps(const FT_Bitmap *orig, const FT_Bitmap *trans) { + int ret = 0; + + if (orig->width == trans->width && + orig->rows == trans->rows) { + for (int i = 0; i < orig->rows * orig->width; ++i) { + if (orig->buffer[i] != trans->buffer[i]) { + std::fprintf(stderr, "bitmap data doesn't match!\n"); + ret = 1; + break; + } + } + } else { + std::fprintf(stderr, "bitmap metrics doesn't match! (%d, %d), (%d, %d)\n", + orig->width, orig->rows, trans->width, trans->rows); + ret = 1; + } + + if (ret) { + std::fprintf(stderr, "EXPECTED:\n"); + DumpBitmap(orig); + std::fprintf(stderr, "\nACTUAL:\n"); + DumpBitmap(trans); + std::fprintf(stderr, "\n\n"); + } + + delete[] orig->buffer; + delete[] trans->buffer; + return ret; +} + +int GetBitmap(FT_Library library, FT_Outline *outline, FT_Bitmap *bitmap) { + FT_BBox bbox; + FT_Outline_Get_CBox(outline, &bbox); + + bbox.xMin &= ~63; + bbox.yMin &= ~63; + bbox.xMax = (bbox.xMax + 63) & ~63; + bbox.yMax = (bbox.yMax + 63) & ~63; + FT_Outline_Translate(outline, -bbox.xMin, -bbox.yMin); + + const int w = (bbox.xMax - bbox.xMin) >> 6; + const int h = (bbox.yMax - bbox.yMin) >> 6; + + if (w == 0 || h == 0) { + return -1; // white space + } + if (w < 0 || h < 0) { + std::fprintf(stderr, "bad width/height\n"); + return 1; // error + } + + uint8_t *buf = new uint8_t[w * h]; + std::memset(buf, 0x0, w * h); + + bitmap->width = w; + bitmap->rows = h; + bitmap->pitch = w; + bitmap->buffer = buf; + bitmap->pixel_mode = FT_PIXEL_MODE_GRAY; + bitmap->num_grays = 256; + if (FT_Outline_Get_Bitmap(library, outline, bitmap)) { + std::fprintf(stderr, "can't get outline\n"); + delete[] buf; + return 1; // error. + } + + return 0; +} + +int LoadChar(FT_Face face, bool use_bitmap, int pt, FT_ULong c) { + static const int kDpi = 72; + + FT_Matrix matrix; + matrix.xx = matrix.yy = 1 << 16; + matrix.xy = matrix.yx = 0 << 16; + + FT_Int32 flags = FT_LOAD_DEFAULT | FT_LOAD_TARGET_NORMAL; + if (!use_bitmap) { + // Since the transcoder drops embedded bitmaps from the transcoded one, + // we have to use FT_LOAD_NO_BITMAP flag for the original face. + flags |= FT_LOAD_NO_BITMAP; + } + + FT_Error error = FT_Set_Char_Size(face, pt * (1 << 6), 0, kDpi, 0); + if (error) { + std::fprintf(stderr, "Failed to set the char size!\n"); + return 1; + } + + FT_Set_Transform(face, &matrix, 0); + + error = FT_Load_Char(face, c, flags); + if (error) return -1; // no such glyf in the font. + + if (face->glyph->format != FT_GLYPH_FORMAT_OUTLINE) { + std::fprintf(stderr, "bad format\n"); + return 1; + } + + return 0; +} + +int LoadCharThenCompare(FT_Library library, + FT_Face orig_face, FT_Face trans_face, + int pt, FT_ULong c) { + FT_Bitmap orig_bitmap, trans_bitmap; + + // Load original bitmap. + int ret = LoadChar(orig_face, false, pt, c); + if (ret) return ret; // 1: error, -1: no such glyph + + FT_Outline *outline = &orig_face->glyph->outline; + ret = GetBitmap(library, outline, &orig_bitmap); + if (ret) return ret; // white space? + + // Load transformed bitmap. + ret = LoadChar(trans_face, true, pt, c); + if (ret == -1) { + std::fprintf(stderr, "the glyph is not found on the transcoded font\n"); + } + if (ret) return 1; // -1 should be treated as error. + outline = &trans_face->glyph->outline; + ret = GetBitmap(library, outline, &trans_bitmap); + if (ret) return ret; // white space? + + return CompareBitmaps(&orig_bitmap, &trans_bitmap); +} + +int SideBySide(FT_Library library, const char *file_name, + uint8_t *orig_font, size_t orig_len, + uint8_t *trans_font, size_t trans_len) { + FT_Face orig_face; + FT_Error error + = FT_New_Memory_Face(library, orig_font, orig_len, 0, &orig_face); + if (error) { + std::fprintf(stderr, "Failed to open the original font: %s!\n", file_name); + return 1; + } + + FT_Face trans_face; + error = FT_New_Memory_Face(library, trans_font, trans_len, 0, &trans_face); + if (error) { + std::fprintf(stderr, "Failed to open the transcoded font: %s!\n", + file_name); + return 1; + } + + static const int kPts[] = {100, 20, 18, 16, 12, 10, 8}; // pt + static const size_t kPtsLen = sizeof(kPts) / sizeof(kPts[0]); + + static const int kUnicodeRanges[] = { + 0x0020, 0x007E, // Basic Latin (ASCII) + 0x00A1, 0x017F, // Latin-1 + 0x1100, 0x11FF, // Hangul + 0x3040, 0x309F, // Japanese HIRAGANA letters + 0x3130, 0x318F, // Hangul + 0x4E00, 0x4F00, // CJK Kanji/Hanja + 0xAC00, 0xAD00, // Hangul + }; + static const size_t kUnicodeRangesLen + = sizeof(kUnicodeRanges) / sizeof(kUnicodeRanges[0]); + + for (size_t i = 0; i < kPtsLen; ++i) { + for (size_t j = 0; j < kUnicodeRangesLen; j += 2) { + for (int k = 0; k <= kUnicodeRanges[j + 1] - kUnicodeRanges[j]; ++k) { + int ret = LoadCharThenCompare(library, orig_face, trans_face, + kPts[i], + kUnicodeRanges[j] + k); + if (ret > 0) { + std::fprintf(stderr, "Glyph mismatch! (file: %s, U+%04x, %dpt)!\n", + file_name, kUnicodeRanges[j] + k, kPts[i]); + return 1; + } + } + } + } + + return 0; +} + +} // namespace + +int main(int argc, char **argv) { + ots::DisableDebugOutput(); // turn off ERROR and WARNING outputs. + + if (argc != 2) { + std::fprintf(stderr, "Usage: %s ttf_or_otf_filename\n", argv[0]); + return 1; + } + + // load the font to memory. + const int fd = ::open(argv[1], O_RDONLY); + if (fd < 0) { + ::perror("open"); + return 1; + } + + struct stat st; + ::fstat(fd, &st); + const off_t orig_len = st.st_size; + + uint8_t *orig_font = new uint8_t[orig_len]; + if (::read(fd, orig_font, orig_len) != orig_len) { + std::fprintf(stderr, "Failed to read file!\n"); + return 1; + } + ::close(fd); + + // check if FreeType2 can open the original font. + FT_Library library; + FT_Error error = FT_Init_FreeType(&library); + if (error) { + std::fprintf(stderr, "Failed to initialize FreeType2!\n"); + return 1; + } + FT_Face dummy; + error = FT_New_Memory_Face(library, orig_font, orig_len, 0, &dummy); + if (error) { + std::fprintf(stderr, "Failed to open the original font with FT2! %s\n", + argv[1]); + return 1; + } + + // transcode the original font. + static const size_t kPadLen = 20 * 1024; + uint8_t *trans_font = new uint8_t[orig_len + kPadLen]; + ots::MemoryStream output(trans_font, orig_len + kPadLen); + + bool result = ots::Process(&output, orig_font, orig_len); + if (!result) { + std::fprintf(stderr, "Failed to sanitise file! %s\n", argv[1]); + return 1; + } + const size_t trans_len = output.Tell(); + + // perform side-by-side tests. + return SideBySide(library, argv[1], + orig_font, orig_len, + trans_font, trans_len); +} diff --git a/test/validator-checker.cc b/test/validator-checker.cc new file mode 100644 index 0000000..66c2b75 --- /dev/null +++ b/test/validator-checker.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <fcntl.h> +#include <ft2build.h> +#include FT_FREETYPE_H +#include <sys/stat.h> +#include <sys/types.h> + +#include <cstdio> +#include <cstdlib> +#include <cstring> + +#include "opentype-sanitiser.h" +#include "ots-memory-stream.h" + +namespace { + +void LoadChar(FT_Face face, int pt, FT_ULong c) { + FT_Matrix matrix; + matrix.xx = matrix.yy = 1 << 16; + matrix.xy = matrix.yx = 0 << 16; + + FT_Set_Char_Size(face, pt * (1 << 6), 0, 72, 0); + FT_Set_Transform(face, &matrix, 0); + FT_Load_Char(face, c, FT_LOAD_RENDER); +} + +int OpenAndLoadChars(FT_Library library, const char *file_name, + uint8_t *trans_font, size_t trans_len) { + FT_Face trans_face; + FT_Error error + = FT_New_Memory_Face(library, trans_font, trans_len, 0, &trans_face); + if (error) { + std::fprintf(stderr, + "OK: FreeType2 couldn't open the transcoded font: %s\n", + file_name); + return 0; + } + + static const int kPts[] = {100, 20, 18, 16, 12, 10, 8}; // pt + static const size_t kPtsLen = sizeof(kPts) / sizeof(kPts[0]); + + static const int kUnicodeRanges[] = { + 0x0020, 0x007E, // Basic Latin (ASCII) + 0x00A1, 0x017F, // Latin-1 + 0x1100, 0x11FF, // Hangul + 0x3040, 0x309F, // Japanese HIRAGANA letters + 0x3130, 0x318F, // Hangul + 0x4E00, 0x4F00, // CJK Kanji/Hanja + 0xAC00, 0xAD00, // Hangul + }; + static const size_t kUnicodeRangesLen + = sizeof(kUnicodeRanges) / sizeof(kUnicodeRanges[0]); + + for (size_t i = 0; i < kPtsLen; ++i) { + for (size_t j = 0; j < kUnicodeRangesLen; j += 2) { + for (int k = 0; k <= kUnicodeRanges[j + 1] - kUnicodeRanges[j]; ++k) { + LoadChar(trans_face, kPts[i], kUnicodeRanges[j] + k); + } + } + } + + std::fprintf(stderr, "OK: FreeType2 didn't crash: %s\n", file_name); + return 0; +} + +} // namespace + +int main(int argc, char **argv) { + ots::DisableDebugOutput(); // turn off ERROR and WARNING outputs. + + if (argc != 2) { + std::fprintf(stderr, "Usage: %s ttf_or_otf_filename\n", argv[0]); + return 1; + } + + // load the font to memory. + const int fd = ::open(argv[1], O_RDONLY); + if (fd < 0) { + ::perror("open"); + return 1; + } + + struct stat st; + ::fstat(fd, &st); + const off_t orig_len = st.st_size; + + uint8_t *orig_font = new uint8_t[orig_len]; + if (::read(fd, orig_font, orig_len) != orig_len) { + std::fprintf(stderr, "Failed to read file!\n"); + return 1; + } + ::close(fd); + + // transcode the malicious font. + static const size_t kBigPadLen = 1024 * 1024; // 1MB + uint8_t *trans_font = new uint8_t[orig_len + kBigPadLen]; + ots::MemoryStream output(trans_font, orig_len + kBigPadLen); + + bool result = ots::Process(&output, orig_font, orig_len); + if (!result) { + std::fprintf(stderr, "OK: the malicious font was filtered: %s\n", argv[1]); + return 0; + } + const size_t trans_len = output.Tell(); + + FT_Library library; + FT_Error error = FT_Init_FreeType(&library); + if (error) { + std::fprintf(stderr, "Failed to initialize FreeType2!\n"); + return 1; + } + + return OpenAndLoadChars(library, argv[1], trans_font, trans_len); +} diff --git a/tools/ttf-checksum.py b/tools/ttf-checksum.py new file mode 100755 index 0000000..802e3cc --- /dev/null +++ b/tools/ttf-checksum.py @@ -0,0 +1,44 @@ +import struct +import sys + +def readU32(contents, offset): + wordBytes = contents[offset:offset + 4] + return struct.unpack('>I', wordBytes)[0] + +def readU16(contents, offset): + wordBytes = contents[offset:offset + 2] + return struct.unpack('>H', wordBytes)[0] + +def checkChecksum(infile): + contents = infile.read() + if len(contents) % 4: + print 'File length is not a multiple of 4' + + sum = 0 + for offset in range(0, len(contents), 4): + sum += readU32(contents, offset) + while sum >= 2**32: + sum -= 2**32 + print 'Sum of whole file: %x' % sum + + numTables = readU16(contents, 4) + + for offset in range(12, 12 + numTables * 16, 16): + tag = contents[offset:offset + 4] + chksum = readU32(contents, offset + 4) + toffset = readU32(contents, offset + 8) + tlength = readU32(contents, offset + 12) + + sum = 0 + for offset2 in range(toffset, toffset + tlength, 4): + sum += readU32(contents, offset2) + while sum >= 2**32: + sum -= 2**32 + if sum != chksum: + print 'Bad chksum: %s' % tag + +if __name__ == '__main__': + if len(sys.argv) != 2: + print 'Usage: %s <ttf filename>' % sys.argv[0] + else: + checkChecksum(file(sys.argv[1], 'r')) |