aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--INSTALL5
-rw-r--r--LICENSE27
-rw-r--r--README20
-rw-r--r--include/opentype-sanitiser.h180
-rw-r--r--include/ots-memory-stream.h50
-rw-r--r--src/cff.cc895
-rw-r--r--src/cff.h19
-rw-r--r--src/cmap.cc757
-rw-r--r--src/cmap.h39
-rw-r--r--src/cvt.cc56
-rw-r--r--src/cvt.h19
-rw-r--r--src/fpgm.cc50
-rw-r--r--src/fpgm.h19
-rw-r--r--src/gasp.cc106
-rw-r--r--src/gasp.h22
-rw-r--r--src/glyf.cc265
-rw-r--r--src/glyf.h21
-rw-r--r--src/hdmx.cc132
-rw-r--r--src/hdmx.h29
-rw-r--r--src/head.cc146
-rw-r--r--src/head.h29
-rw-r--r--src/hhea.cc120
-rw-r--r--src/hhea.h28
-rw-r--r--src/hmtx.cc107
-rw-r--r--src/hmtx.h22
-rw-r--r--src/loca.cc98
-rw-r--r--src/loca.h20
-rw-r--r--src/ltsh.cc82
-rw-r--r--src/ltsh.h21
-rw-r--r--src/maxp.cc128
-rw-r--r--src/maxp.h35
-rw-r--r--src/name.cc86
-rw-r--r--src/os2.cc290
-rw-r--r--src/os2.h54
-rw-r--r--src/ots.cc505
-rw-r--r--src/ots.h192
-rw-r--r--src/post.cc181
-rw-r--r--src/post.h29
-rw-r--r--src/prep.cc50
-rw-r--r--src/prep.h19
-rw-r--r--src/vdmx.cc176
-rw-r--r--src/vdmx.h45
-rw-r--r--src/vorg.cc97
-rw-r--r--src/vorg.h28
-rw-r--r--test/README243
-rw-r--r--test/SConstruct43
-rw-r--r--test/file-stream.h39
-rw-r--r--test/idempotent.cc94
-rw-r--r--test/ot-sanitise.cc54
-rw-r--r--test/perf.cc79
-rw-r--r--test/side-by-side.cc281
-rw-r--r--test/validator-checker.cc117
-rwxr-xr-xtools/ttf-checksum.py44
53 files changed, 6293 insertions, 0 deletions
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..232307e
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,5 @@
+How to build (on Linux):
+
+ $ cd ots/test/
+ $ sudo apt-get install scons g++ libfreetype6-dev
+ $ scons
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..a7531cf
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/README b/README
new file mode 100644
index 0000000..b74d519
--- /dev/null
+++ b/README
@@ -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(&ltsh->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'))