diff options
Diffstat (limited to 'gfx/ots/src')
61 files changed, 12355 insertions, 0 deletions
diff --git a/gfx/ots/src/cff.cc b/gfx/ots/src/cff.cc new file mode 100644 index 000000000..23b6dadac --- /dev/null +++ b/gfx/ots/src/cff.cc @@ -0,0 +1,1052 @@ +// Copyright (c) 2012 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> +#include <vector> + +#include "maxp.h" +#include "cff_type2_charstring.h" + +// CFF - PostScript font program (Compact Font Format) table +// http://www.microsoft.com/typography/otspec/cff.htm +// http://www.microsoft.com/typography/otspec/cffspec.htm + +#define TABLE_NAME "CFF" + +namespace { + +enum DICT_OPERAND_TYPE { + DICT_OPERAND_INTEGER, + DICT_OPERAND_REAL, + DICT_OPERATOR, +}; + +enum DICT_DATA_TYPE { + DICT_DATA_TOPLEVEL, + DICT_DATA_FDARRAY, +}; + +enum FONT_FORMAT { + FORMAT_UNKNOWN, + FORMAT_CID_KEYED, + FORMAT_OTHER, // Including synthetic fonts +}; + +// 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, ots::CFFIndex *index) { + index->off_size = 0; + index->offsets.clear(); + + if (!table->ReadU16(&(index->count))) { + return OTS_FAILURE(); + } + if (index->count == 0) { + // An empty INDEX. + index->offset_to_next = table->offset(); + 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 = 0; + 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) { + // We allow consecutive identical offsets here for zero-length strings. + // See http://crbug.com/69341 for more details. + if (index->offsets[i] < index->offsets[i - 1]) { + return OTS_FAILURE(); + } + } + + index->offset_to_next = index->offsets.back(); + return true; +} + +bool ParseNameData( + ots::Buffer *table, const ots::CFFIndex &index, std::string* out_name) { + uint8_t name[256] = {0}; + if (index.offsets.size() == 0) { // just in case. + return OTS_FAILURE(); + } + for (unsigned i = 1; i < index.offsets.size(); ++i) { + const 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(); + } + } + } + + *out_name = reinterpret_cast<char *>(name); + 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(static_cast<uint32_t>(0), + DICT_OPERAND_REAL)); + return true; + } + return OTS_FAILURE(); + } + if ((nibble & 0x0f) == 0x0f) { + operands->push_back(std::make_pair(static_cast<uint32_t>(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( + static_cast<uint32_t>((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( + static_cast<uint32_t>((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( + static_cast<uint32_t>(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, + DICT_DATA_TYPE type, ots::OpenTypeCFF *out_cff) { + ots::Buffer table(data + offset, dict_length); + std::vector<std::pair<uint32_t, DICT_OPERAND_TYPE> > operands; + + // Since a Private DICT for FDArray might not have a Local Subr (e.g. Hiragino + // Kaku Gothic Std W8), we create an empty Local Subr here to match the size + // of FDArray the size of |local_subrs_per_font|. + if (type == DICT_DATA_FDARRAY) { + out_cff->local_subrs_per_font.push_back(new ots::CFFIndex); + } + + 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) { + // hints + case 6: // BlueValues + case 7: // OtherBlues + case 8: // FamilyBlues + case 9: // FamilyOtherBlues + if (operands.empty() || (operands.size() % 2) != 0) { + return OTS_FAILURE(); + } + break; + + // array + 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 cff_table(data, table_length); + cff_table.set_offset(operands.back().first + offset); + ots::CFFIndex *local_subrs_index = NULL; + if (type == DICT_DATA_FDARRAY) { + if (out_cff->local_subrs_per_font.empty()) { + return OTS_FAILURE(); // not reached. + } + local_subrs_index = out_cff->local_subrs_per_font.back(); + } else { // type == DICT_DATA_TOPLEVEL + if (out_cff->local_subrs) { + return OTS_FAILURE(); // two or more local_subrs? + } + local_subrs_index = new ots::CFFIndex; + out_cff->local_subrs = local_subrs_index; + } + if (!ParseIndex(&cff_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 ots::CFFIndex &index, uint16_t glyphs, + size_t sid_max, DICT_DATA_TYPE type, + ots::OpenTypeCFF *out_cff) { + for (unsigned i = 1; i < index.offsets.size(); ++i) { + if (type == DICT_DATA_TOPLEVEL) { + out_cff->char_strings_array.push_back(new ots::CFFIndex); + } + 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; + + FONT_FORMAT font_format = FORMAT_UNKNOWN; + bool have_ros = false; + uint16_t charstring_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) + 8: // StrokeWidth + case (12U << 8) + 20: // SyntheticBase + if (operands.size() != 1) { + return OTS_FAILURE(); + } + break; + 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(); + } + if (font_format != FORMAT_CID_KEYED) { + return OTS_FAILURE(); + } + break; + case (12U << 8) + 6: // CharstringType + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if(operands.back().second != DICT_OPERAND_INTEGER) { + return OTS_FAILURE(); + } + if (operands.back().first != 2) { + // We only support the "Type 2 Charstring Format." + // TODO(yusukes): Support Type 1 format? Is that still in use? + 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 cff_table(data, table_length); + cff_table.set_offset(operands.back().first); + uint8_t format = 0; + if (!cff_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 (type != DICT_DATA_TOPLEVEL) { + return OTS_FAILURE(); + } + if (operands.size() != 1) { + return OTS_FAILURE(); + } + if (!CheckOffset(operands.back(), table_length)) { + return OTS_FAILURE(); + } + // parse "14. CharStrings INDEX" + ots::Buffer cff_table(data, table_length); + cff_table.set_offset(operands.back().first); + ots::CFFIndex *charstring_index = out_cff->char_strings_array.back(); + if (!ParseIndex(&cff_table, charstring_index)) { + return OTS_FAILURE(); + } + if (charstring_index->count < 2) { + return OTS_FAILURE(); + } + if (charstring_glyphs) { + return OTS_FAILURE(); // multiple charstring tables? + } + charstring_glyphs = charstring_index->count; + if (charstring_glyphs != glyphs) { + return OTS_FAILURE(); // CFF and maxp have different number of glyphs? + } + break; + } + + case (12U << 8) + 36: { // FDArray + if (type != DICT_DATA_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 cff_table(data, table_length); + cff_table.set_offset(operands.back().first); + ots::CFFIndex sub_dict_index; + if (!ParseIndex(&cff_table, &sub_dict_index)) { + return OTS_FAILURE(); + } + if (!ParseDictData(data, table_length, + sub_dict_index, + glyphs, sid_max, DICT_DATA_FDARRAY, + out_cff)) { + return OTS_FAILURE(); + } + if (out_cff->font_dict_length != 0) { + return OTS_FAILURE(); // two or more FDArray found. + } + out_cff->font_dict_length = sub_dict_index.count; + break; + } + + case (12U << 8) + 37: { // FDSelect + if (type != DICT_DATA_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 cff_table(data, table_length); + cff_table.set_offset(operands.back().first); + uint8_t format = 0; + if (!cff_table.ReadU8(&format)) { + return OTS_FAILURE(); + } + if (format == 0) { + for (uint16_t j = 0; j < glyphs; ++j) { + uint8_t fd_index = 0; + if (!cff_table.ReadU8(&fd_index)) { + return OTS_FAILURE(); + } + (out_cff->fd_select)[j] = fd_index; + } + } else if (format == 3) { + uint16_t n_ranges = 0; + if (!cff_table.ReadU16(&n_ranges)) { + return OTS_FAILURE(); + } + if (n_ranges == 0) { + return OTS_FAILURE(); + } + + uint16_t last_gid = 0; + uint8_t fd_index = 0; + for (unsigned j = 0; j < n_ranges; ++j) { + uint16_t first = 0; // GID + if (!cff_table.ReadU16(&first)) { + return OTS_FAILURE(); + } + + // Sanity checks. + if ((j == 0) && (first != 0)) { + return OTS_FAILURE(); + } + if ((j != 0) && (last_gid >= first)) { + return OTS_FAILURE(); // not increasing order. + } + + // Copy the mapping to |out_cff->fd_select|. + if (j != 0) { + for (uint16_t k = last_gid; k < first; ++k) { + if (!out_cff->fd_select.insert( + std::make_pair(k, fd_index)).second) { + return OTS_FAILURE(); + } + } + } + + if (!cff_table.ReadU8(&fd_index)) { + return OTS_FAILURE(); + } + last_gid = first; + // TODO(yusukes): check GID? + } + uint16_t sentinel = 0; + if (!cff_table.ReadU16(&sentinel)) { + return OTS_FAILURE(); + } + if (last_gid >= sentinel) { + return OTS_FAILURE(); + } + for (uint16_t k = last_gid; k < sentinel; ++k) { + if (!out_cff->fd_select.insert( + std::make_pair(k, fd_index)).second) { + 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) { + return OTS_FAILURE(); + } + // parse "15. Private DICT Data" + if (!ParsePrivateDictData(data, table_length, + private_offset, private_length, + type, out_cff)) { + return OTS_FAILURE(); + } + break; + } + + // ROS + case (12U << 8) + 30: + if (font_format != FORMAT_UNKNOWN) { + return OTS_FAILURE(); + } + font_format = FORMAT_CID_KEYED; + 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(); + + if (font_format == FORMAT_UNKNOWN) { + font_format = FORMAT_OTHER; + } + } + + // parse "13. Charsets" + if (charset_offset) { + ots::Buffer cff_table(data, table_length); + cff_table.set_offset(charset_offset); + uint8_t format = 0; + if (!cff_table.ReadU8(&format)) { + return OTS_FAILURE(); + } + switch (format) { + case 0: + for (uint16_t j = 1 /* .notdef is omitted */; j < glyphs; ++j) { + uint16_t sid = 0; + if (!cff_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 = 0; + if (!cff_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 (!cff_table.ReadU8(&left)) { + return OTS_FAILURE(); + } + total += (left + 1); + } else { + uint16_t left = 0; + if (!cff_table.ReadU16(&left)) { + return OTS_FAILURE(); + } + total += (left + 1); + } + } + break; + } + + default: + return OTS_FAILURE(); + } + } + } + return true; +} + +} // namespace + +namespace ots { + +bool ots_cff_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + + font->cff = new OpenTypeCFF; + font->cff->data = data; + font->cff->length = length; + font->cff->font_dict_length = 0; + font->cff->local_subrs = NULL; + + // 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 ((off_size == 0) || (off_size > 4)) { + 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, &(font->cff->name))) { + return OTS_FAILURE(); + } + + // parse "8. Top DICT INDEX" + table.set_offset(name_index.offset_to_next); + 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.offset_to_next); + CFFIndex string_index; + if (!ParseIndex(&table, &string_index)) { + return OTS_FAILURE(); + } + if (string_index.count >= 65000 - kNStdString) { + return OTS_FAILURE(); + } + + const uint16_t num_glyphs = font->maxp->num_glyphs; + 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, + num_glyphs, sid_max, + DICT_DATA_TOPLEVEL, font->cff)) { + return OTS_FAILURE(); + } + + // parse "16. Global Subrs INDEX" + table.set_offset(string_index.offset_to_next); + CFFIndex global_subrs_index; + if (!ParseIndex(&table, &global_subrs_index)) { + return OTS_FAILURE(); + } + + // Check if all fd_index in FDSelect are valid. + std::map<uint16_t, uint8_t>::const_iterator iter; + std::map<uint16_t, uint8_t>::const_iterator end = font->cff->fd_select.end(); + for (iter = font->cff->fd_select.begin(); iter != end; ++iter) { + if (iter->second >= font->cff->font_dict_length) { + return OTS_FAILURE(); + } + } + + // Check if all charstrings (font hinting code for each glyph) are valid. + for (size_t i = 0; i < font->cff->char_strings_array.size(); ++i) { + if (!ValidateType2CharStringIndex(font, + *(font->cff->char_strings_array.at(i)), + global_subrs_index, + font->cff->fd_select, + font->cff->local_subrs_per_font, + font->cff->local_subrs, + &table)) { + return OTS_FAILURE_MSG("Failed validating charstring set %d", (int) i); + } + } + + return true; +} + +bool ots_cff_should_serialise(Font *font) { + return font->cff != NULL; +} + +bool ots_cff_serialise(OTSStream *out, Font *font) { + // TODO(yusukes): would be better to transcode the data, + // rather than simple memcpy. + if (!out->Write(font->cff->data, font->cff->length)) { + return OTS_FAILURE(); + } + return true; +} + +void ots_cff_reuse(Font *font, Font *other) { + font->cff = other->cff; + font->cff_reused = true; +} + +void ots_cff_free(Font *font) { + if (font->cff) { + for (size_t i = 0; i < font->cff->char_strings_array.size(); ++i) { + delete (font->cff->char_strings_array)[i]; + } + for (size_t i = 0; i < font->cff->local_subrs_per_font.size(); ++i) { + delete (font->cff->local_subrs_per_font)[i]; + } + delete font->cff->local_subrs; + delete font->cff; + } +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/cff.h b/gfx/ots/src/cff.h new file mode 100644 index 000000000..5584acc62 --- /dev/null +++ b/gfx/ots/src/cff.h @@ -0,0 +1,46 @@ +// 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" + +#include <map> +#include <string> +#include <vector> + +namespace ots { + +struct CFFIndex { + CFFIndex() + : count(0), off_size(0), offset_to_next(0) {} + uint16_t count; + uint8_t off_size; + std::vector<uint32_t> offsets; + uint32_t offset_to_next; +}; + +struct OpenTypeCFF { + const uint8_t *data; + size_t length; + // Name INDEX. This name is used in name.cc as a postscript font name. + std::string name; + + // The number of fonts the file has. + size_t font_dict_length; + // A map from glyph # to font #. + std::map<uint16_t, uint8_t> fd_select; + + // A list of char strings. + std::vector<CFFIndex *> char_strings_array; + // A list of Local Subrs associated with FDArrays. Can be empty. + std::vector<CFFIndex *> local_subrs_per_font; + // A Local Subrs associated with Top DICT. Can be NULL. + CFFIndex *local_subrs; +}; + +} // namespace ots + +#endif // OTS_CFF_H_ diff --git a/gfx/ots/src/cff_type2_charstring.cc b/gfx/ots/src/cff_type2_charstring.cc new file mode 100644 index 000000000..8c5544b2e --- /dev/null +++ b/gfx/ots/src/cff_type2_charstring.cc @@ -0,0 +1,912 @@ +// Copyright (c) 2010 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 parser for the Type 2 Charstring Format. +// http://www.adobe.com/devnet/font/pdfs/5177.Type2.pdf + +#include "cff_type2_charstring.h" + +#include <climits> +#include <cstdio> +#include <cstring> +#include <stack> +#include <string> +#include <utility> + +#define TABLE_NAME "CFF" + +namespace { + +// Type 2 Charstring Implementation Limits. See Appendix. B in Adobe Technical +// Note #5177. +const int32_t kMaxSubrsCount = 65536; +const size_t kMaxCharStringLength = 65535; +const size_t kMaxArgumentStack = 48; +const size_t kMaxNumberOfStemHints = 96; +const size_t kMaxSubrNesting = 10; + +// |dummy_result| should be a huge positive integer so callsubr and callgsubr +// will fail with the dummy value. +const int32_t dummy_result = INT_MAX; + +bool ExecuteType2CharString(ots::Font *font, + size_t call_depth, + const ots::CFFIndex& global_subrs_index, + const ots::CFFIndex& local_subrs_index, + ots::Buffer *cff_table, + ots::Buffer *char_string, + std::stack<int32_t> *argument_stack, + bool *out_found_endchar, + bool *out_found_width, + size_t *in_out_num_stems); + +#ifdef DUMP_T2CHARSTRING +// Converts |op| to a string and returns it. +const char *Type2CharStringOperatorToString(ots::Type2CharStringOperator op) { + switch (op) { + case ots::kHStem: + return "HStem"; + case ots::kVStem: + return "VStem"; + case ots::kVMoveTo: + return "VMoveTo"; + case ots::kRLineTo: + return "RLineTo"; + case ots::kHLineTo: + return "HLineTo"; + case ots::kVLineTo: + return "VLineTo"; + case ots::kRRCurveTo: + return "RRCurveTo"; + case ots::kCallSubr: + return "CallSubr"; + case ots::kReturn: + return "Return"; + case ots::kEndChar: + return "EndChar"; + case ots::kHStemHm: + return "HStemHm"; + case ots::kHintMask: + return "HintMask"; + case ots::kCntrMask: + return "CntrMask"; + case ots::kRMoveTo: + return "RMoveTo"; + case ots::kHMoveTo: + return "HMoveTo"; + case ots::kVStemHm: + return "VStemHm"; + case ots::kRCurveLine: + return "RCurveLine"; + case ots::kRLineCurve: + return "RLineCurve"; + case ots::kVVCurveTo: + return "VVCurveTo"; + case ots::kHHCurveTo: + return "HHCurveTo"; + case ots::kCallGSubr: + return "CallGSubr"; + case ots::kVHCurveTo: + return "VHCurveTo"; + case ots::kHVCurveTo: + return "HVCurveTo"; + case ots::kDotSection: + return "DotSection"; + case ots::kAnd: + return "And"; + case ots::kOr: + return "Or"; + case ots::kNot: + return "Not"; + case ots::kAbs: + return "Abs"; + case ots::kAdd: + return "Add"; + case ots::kSub: + return "Sub"; + case ots::kDiv: + return "Div"; + case ots::kNeg: + return "Neg"; + case ots::kEq: + return "Eq"; + case ots::kDrop: + return "Drop"; + case ots::kPut: + return "Put"; + case ots::kGet: + return "Get"; + case ots::kIfElse: + return "IfElse"; + case ots::kRandom: + return "Random"; + case ots::kMul: + return "Mul"; + case ots::kSqrt: + return "Sqrt"; + case ots::kDup: + return "Dup"; + case ots::kExch: + return "Exch"; + case ots::kIndex: + return "Index"; + case ots::kRoll: + return "Roll"; + case ots::kHFlex: + return "HFlex"; + case ots::kFlex: + return "Flex"; + case ots::kHFlex1: + return "HFlex1"; + case ots::kFlex1: + return "Flex1"; + } + + return "UNKNOWN"; +} +#endif + +// Read one or more bytes from the |char_string| buffer and stores the number +// read on |out_number|. If the number read is an operator (ex 'vstem'), sets +// true on |out_is_operator|. Returns true if the function read a number. +bool ReadNextNumberFromType2CharString(ots::Buffer *char_string, + int32_t *out_number, + bool *out_is_operator) { + uint8_t v = 0; + if (!char_string->ReadU8(&v)) { + return OTS_FAILURE(); + } + *out_is_operator = false; + + // The conversion algorithm is described in Adobe Technical Note #5177, page + // 13, Table 1. + if (v <= 11) { + *out_number = v; + *out_is_operator = true; + } else if (v == 12) { + uint16_t result = (v << 8); + if (!char_string->ReadU8(&v)) { + return OTS_FAILURE(); + } + result += v; + *out_number = result; + *out_is_operator = true; + } else if (v <= 27) { + // Special handling for v==19 and v==20 are implemented in + // ExecuteType2CharStringOperator(). + *out_number = v; + *out_is_operator = true; + } else if (v == 28) { + if (!char_string->ReadU8(&v)) { + return OTS_FAILURE(); + } + uint16_t result = (v << 8); + if (!char_string->ReadU8(&v)) { + return OTS_FAILURE(); + } + result += v; + *out_number = result; + } else if (v <= 31) { + *out_number = v; + *out_is_operator = true; + } else if (v <= 246) { + *out_number = static_cast<int32_t>(v) - 139; + } else if (v <= 250) { + uint8_t w = 0; + if (!char_string->ReadU8(&w)) { + return OTS_FAILURE(); + } + *out_number = ((static_cast<int32_t>(v) - 247) * 256) + + static_cast<int32_t>(w) + 108; + } else if (v <= 254) { + uint8_t w = 0; + if (!char_string->ReadU8(&w)) { + return OTS_FAILURE(); + } + *out_number = -((static_cast<int32_t>(v) - 251) * 256) - + static_cast<int32_t>(w) - 108; + } else if (v == 255) { + // TODO(yusukes): We should not skip the 4 bytes. Note that when v is 255, + // we should treat the following 4-bytes as a 16.16 fixed-point number + // rather than 32bit signed int. + if (!char_string->Skip(4)) { + return OTS_FAILURE(); + } + *out_number = dummy_result; + } else { + return OTS_FAILURE(); + } + + return true; +} + +// Executes |op| and updates |argument_stack|. Returns true if the execution +// succeeds. If the |op| is kCallSubr or kCallGSubr, the function recursively +// calls ExecuteType2CharString() function. The arguments other than |op| and +// |argument_stack| are passed for that reason. +bool ExecuteType2CharStringOperator(ots::Font *font, + int32_t op, + size_t call_depth, + const ots::CFFIndex& global_subrs_index, + const ots::CFFIndex& local_subrs_index, + ots::Buffer *cff_table, + ots::Buffer *char_string, + std::stack<int32_t> *argument_stack, + bool *out_found_endchar, + bool *in_out_found_width, + size_t *in_out_num_stems) { + const size_t stack_size = argument_stack->size(); + + switch (op) { + case ots::kCallSubr: + case ots::kCallGSubr: { + const ots::CFFIndex& subrs_index = + (op == ots::kCallSubr ? local_subrs_index : global_subrs_index); + + if (stack_size < 1) { + return OTS_FAILURE(); + } + int32_t subr_number = argument_stack->top(); + argument_stack->pop(); + if (subr_number == dummy_result) { + // For safety, we allow subr calls only with immediate subr numbers for + // now. For example, we allow "123 callgsubr", but does not allow "100 12 + // add callgsubr". Please note that arithmetic and conditional operators + // always push the |dummy_result| in this implementation. + return OTS_FAILURE(); + } + + // See Adobe Technical Note #5176 (CFF), "16. Local/GlobalSubrs INDEXes." + int32_t bias = 32768; + if (subrs_index.count < 1240) { + bias = 107; + } else if (subrs_index.count < 33900) { + bias = 1131; + } + subr_number += bias; + + // Sanity checks of |subr_number|. + if (subr_number < 0) { + return OTS_FAILURE(); + } + if (subr_number >= kMaxSubrsCount) { + return OTS_FAILURE(); + } + if (subrs_index.offsets.size() <= static_cast<size_t>(subr_number + 1)) { + return OTS_FAILURE(); // The number is out-of-bounds. + } + + // Prepare ots::Buffer where we're going to jump. + const size_t length = + subrs_index.offsets[subr_number + 1] - subrs_index.offsets[subr_number]; + if (length > kMaxCharStringLength) { + return OTS_FAILURE(); + } + const size_t offset = subrs_index.offsets[subr_number]; + cff_table->set_offset(offset); + if (!cff_table->Skip(length)) { + return OTS_FAILURE(); + } + ots::Buffer char_string_to_jump(cff_table->buffer() + offset, length); + + return ExecuteType2CharString(font, + call_depth + 1, + global_subrs_index, + local_subrs_index, + cff_table, + &char_string_to_jump, + argument_stack, + out_found_endchar, + in_out_found_width, + in_out_num_stems); + } + + case ots::kReturn: + return true; + + case ots::kEndChar: + *out_found_endchar = true; + *in_out_found_width = true; // just in case. + return true; + + case ots::kHStem: + case ots::kVStem: + case ots::kHStemHm: + case ots::kVStemHm: { + bool successful = false; + if (stack_size < 2) { + return OTS_FAILURE(); + } + if ((stack_size % 2) == 0) { + successful = true; + } else if ((!(*in_out_found_width)) && (((stack_size - 1) % 2) == 0)) { + // The -1 is for "width" argument. For details, see Adobe Technical Note + // #5177, page 16, note 4. + successful = true; + } + (*in_out_num_stems) += (stack_size / 2); + if ((*in_out_num_stems) > kMaxNumberOfStemHints) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + *in_out_found_width = true; // always set true since "w" might be 0 byte. + return successful ? true : OTS_FAILURE(); + } + + case ots::kRMoveTo: { + bool successful = false; + if (stack_size == 2) { + successful = true; + } else if ((!(*in_out_found_width)) && (stack_size - 1 == 2)) { + successful = true; + } + while (!argument_stack->empty()) + argument_stack->pop(); + *in_out_found_width = true; + return successful ? true : OTS_FAILURE(); + } + + case ots::kVMoveTo: + case ots::kHMoveTo: { + bool successful = false; + if (stack_size == 1) { + successful = true; + } else if ((!(*in_out_found_width)) && (stack_size - 1 == 1)) { + successful = true; + } + while (!argument_stack->empty()) + argument_stack->pop(); + *in_out_found_width = true; + return successful ? true : OTS_FAILURE(); + } + + case ots::kHintMask: + case ots::kCntrMask: { + bool successful = false; + if (stack_size == 0) { + successful = true; + } else if ((!(*in_out_found_width)) && (stack_size == 1)) { + // A number for "width" is found. + successful = true; + } else if ((!(*in_out_found_width)) || // in this case, any sizes are ok. + ((stack_size % 2) == 0)) { + // The numbers are vstem definition. + // See Adobe Technical Note #5177, page 24, hintmask. + (*in_out_num_stems) += (stack_size / 2); + if ((*in_out_num_stems) > kMaxNumberOfStemHints) { + return OTS_FAILURE(); + } + successful = true; + } + if (!successful) { + return OTS_FAILURE(); + } + + if ((*in_out_num_stems) == 0) { + return OTS_FAILURE(); + } + const size_t mask_bytes = (*in_out_num_stems + 7) / 8; + if (!char_string->Skip(mask_bytes)) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + *in_out_found_width = true; + return true; + } + + case ots::kRLineTo: + if (!(*in_out_found_width)) { + // The first stack-clearing operator should be one of hstem, hstemhm, + // vstem, vstemhm, cntrmask, hintmask, hmoveto, vmoveto, rmoveto, or + // endchar. For details, see Adobe Technical Note #5177, page 16, note 4. + return OTS_FAILURE(); + } + if (stack_size < 2) { + return OTS_FAILURE(); + } + if ((stack_size % 2) != 0) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + + case ots::kHLineTo: + case ots::kVLineTo: + if (!(*in_out_found_width)) { + return OTS_FAILURE(); + } + if (stack_size < 1) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + + case ots::kRRCurveTo: + if (!(*in_out_found_width)) { + return OTS_FAILURE(); + } + if (stack_size < 6) { + return OTS_FAILURE(); + } + if ((stack_size % 6) != 0) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + + case ots::kRCurveLine: + if (!(*in_out_found_width)) { + return OTS_FAILURE(); + } + if (stack_size < 8) { + return OTS_FAILURE(); + } + if (((stack_size - 2) % 6) != 0) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + + case ots::kRLineCurve: + if (!(*in_out_found_width)) { + return OTS_FAILURE(); + } + if (stack_size < 8) { + return OTS_FAILURE(); + } + if (((stack_size - 6) % 2) != 0) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + + case ots::kVVCurveTo: + if (!(*in_out_found_width)) { + return OTS_FAILURE(); + } + if (stack_size < 4) { + return OTS_FAILURE(); + } + if (((stack_size % 4) != 0) && + (((stack_size - 1) % 4) != 0)) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + + case ots::kHHCurveTo: { + bool successful = false; + if (!(*in_out_found_width)) { + return OTS_FAILURE(); + } + if (stack_size < 4) { + return OTS_FAILURE(); + } + if ((stack_size % 4) == 0) { + // {dxa dxb dyb dxc}+ + successful = true; + } else if (((stack_size - 1) % 4) == 0) { + // dy1? {dxa dxb dyb dxc}+ + successful = true; + } + while (!argument_stack->empty()) + argument_stack->pop(); + return successful ? true : OTS_FAILURE(); + } + + case ots::kVHCurveTo: + case ots::kHVCurveTo: { + bool successful = false; + if (!(*in_out_found_width)) { + return OTS_FAILURE(); + } + if (stack_size < 4) { + return OTS_FAILURE(); + } + if (((stack_size - 4) % 8) == 0) { + // dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* + successful = true; + } else if ((stack_size >= 5) && + ((stack_size - 5) % 8) == 0) { + // dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf + successful = true; + } else if ((stack_size >= 8) && + ((stack_size - 8) % 8) == 0) { + // {dxa dxb dyb dyc dyd dxe dye dxf}+ + successful = true; + } else if ((stack_size >= 9) && + ((stack_size - 9) % 8) == 0) { + // {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf? + successful = true; + } + while (!argument_stack->empty()) + argument_stack->pop(); + return successful ? true : OTS_FAILURE(); + } + + case ots::kDotSection: + // Deprecated operator but harmless, we probably should drop it some how. + if (stack_size != 0) { + return OTS_FAILURE(); + } + return true; + + case ots::kAnd: + case ots::kOr: + case ots::kEq: + case ots::kAdd: + case ots::kSub: + if (stack_size < 2) { + return OTS_FAILURE(); + } + argument_stack->pop(); + argument_stack->pop(); + argument_stack->push(dummy_result); + // TODO(yusukes): Implement this. We should push a real value for all + // arithmetic and conditional operations. + return true; + + case ots::kNot: + case ots::kAbs: + case ots::kNeg: + if (stack_size < 1) { + return OTS_FAILURE(); + } + argument_stack->pop(); + argument_stack->push(dummy_result); + // TODO(yusukes): Implement this. We should push a real value for all + // arithmetic and conditional operations. + return true; + + case ots::kDiv: + // TODO(yusukes): Should detect div-by-zero errors. + if (stack_size < 2) { + return OTS_FAILURE(); + } + argument_stack->pop(); + argument_stack->pop(); + argument_stack->push(dummy_result); + // TODO(yusukes): Implement this. We should push a real value for all + // arithmetic and conditional operations. + return true; + + case ots::kDrop: + if (stack_size < 1) { + return OTS_FAILURE(); + } + argument_stack->pop(); + return true; + + case ots::kPut: + case ots::kGet: + case ots::kIndex: + // For now, just call OTS_FAILURE since there is no way to check whether the + // index argument, |i|, is out-of-bounds or not. Fortunately, no OpenType + // fonts I have (except malicious ones!) use the operators. + // TODO(yusukes): Implement them in a secure way. + return OTS_FAILURE(); + + case ots::kRoll: + // Likewise, just call OTS_FAILURE for kRoll since there is no way to check + // whether |N| is smaller than the current stack depth or not. + // TODO(yusukes): Implement them in a secure way. + return OTS_FAILURE(); + + case ots::kRandom: + // For now, we don't handle the 'random' operator since the operator makes + // it hard to analyze hinting code statically. + return OTS_FAILURE(); + + case ots::kIfElse: + if (stack_size < 4) { + return OTS_FAILURE(); + } + argument_stack->pop(); + argument_stack->pop(); + argument_stack->pop(); + argument_stack->pop(); + argument_stack->push(dummy_result); + // TODO(yusukes): Implement this. We should push a real value for all + // arithmetic and conditional operations. + return true; + + case ots::kMul: + // TODO(yusukes): Should detect overflows. + if (stack_size < 2) { + return OTS_FAILURE(); + } + argument_stack->pop(); + argument_stack->pop(); + argument_stack->push(dummy_result); + // TODO(yusukes): Implement this. We should push a real value for all + // arithmetic and conditional operations. + return true; + + case ots::kSqrt: + // TODO(yusukes): Should check if the argument is negative. + if (stack_size < 1) { + return OTS_FAILURE(); + } + argument_stack->pop(); + argument_stack->push(dummy_result); + // TODO(yusukes): Implement this. We should push a real value for all + // arithmetic and conditional operations. + return true; + + case ots::kDup: + if (stack_size < 1) { + return OTS_FAILURE(); + } + argument_stack->pop(); + argument_stack->push(dummy_result); + argument_stack->push(dummy_result); + if (argument_stack->size() > kMaxArgumentStack) { + return OTS_FAILURE(); + } + // TODO(yusukes): Implement this. We should push a real value for all + // arithmetic and conditional operations. + return true; + + case ots::kExch: + if (stack_size < 2) { + return OTS_FAILURE(); + } + argument_stack->pop(); + argument_stack->pop(); + argument_stack->push(dummy_result); + argument_stack->push(dummy_result); + // TODO(yusukes): Implement this. We should push a real value for all + // arithmetic and conditional operations. + return true; + + case ots::kHFlex: + if (!(*in_out_found_width)) { + return OTS_FAILURE(); + } + if (stack_size != 7) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + + case ots::kFlex: + if (!(*in_out_found_width)) { + return OTS_FAILURE(); + } + if (stack_size != 13) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + + case ots::kHFlex1: + if (!(*in_out_found_width)) { + return OTS_FAILURE(); + } + if (stack_size != 9) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + + case ots::kFlex1: + if (!(*in_out_found_width)) { + return OTS_FAILURE(); + } + if (stack_size != 11) { + return OTS_FAILURE(); + } + while (!argument_stack->empty()) + argument_stack->pop(); + return true; + } + + return OTS_FAILURE_MSG("Undefined operator: %d (0x%x)", op, op); +} + +// Executes |char_string| and updates |argument_stack|. +// +// call_depth: The current call depth. Initial value is zero. +// global_subrs_index: Global subroutines. +// local_subrs_index: Local subroutines for the current glyph. +// cff_table: A whole CFF table which contains all global and local subroutines. +// char_string: A charstring we'll execute. |char_string| can be a main routine +// in CharString INDEX, or a subroutine in GlobalSubr/LocalSubr. +// argument_stack: The stack which an operator in |char_string| operates. +// out_found_endchar: true is set if |char_string| contains 'endchar'. +// in_out_found_width: true is set if |char_string| contains 'width' byte (which +// is 0 or 1 byte.) +// in_out_num_stems: total number of hstems and vstems processed so far. +bool ExecuteType2CharString(ots::Font *font, + size_t call_depth, + const ots::CFFIndex& global_subrs_index, + const ots::CFFIndex& local_subrs_index, + ots::Buffer *cff_table, + ots::Buffer *char_string, + std::stack<int32_t> *argument_stack, + bool *out_found_endchar, + bool *in_out_found_width, + size_t *in_out_num_stems) { + if (call_depth > kMaxSubrNesting) { + return OTS_FAILURE(); + } + *out_found_endchar = false; + + const size_t length = char_string->length(); + while (char_string->offset() < length) { + int32_t operator_or_operand = 0; + bool is_operator = false; + if (!ReadNextNumberFromType2CharString(char_string, + &operator_or_operand, + &is_operator)) { + return OTS_FAILURE(); + } + +#ifdef DUMP_T2CHARSTRING + /* + You can dump all operators and operands (except mask bytes for hintmask + and cntrmask) by the following code: + */ + + if (!is_operator) { + std::fprintf(stderr, "#%d# ", operator_or_operand); + } else { + std::fprintf(stderr, "#%s#\n", + Type2CharStringOperatorToString( + ots::Type2CharStringOperator(operator_or_operand)) + ); + } +#endif + + if (!is_operator) { + argument_stack->push(operator_or_operand); + if (argument_stack->size() > kMaxArgumentStack) { + return OTS_FAILURE(); + } + continue; + } + + // An operator is found. Execute it. + if (!ExecuteType2CharStringOperator(font, + operator_or_operand, + call_depth, + global_subrs_index, + local_subrs_index, + cff_table, + char_string, + argument_stack, + out_found_endchar, + in_out_found_width, + in_out_num_stems)) { + return OTS_FAILURE(); + } + if (*out_found_endchar) { + return true; + } + if (operator_or_operand == ots::kReturn) { + return true; + } + } + + // No endchar operator is found. + return OTS_FAILURE(); +} + +// Selects a set of subroutings for |glyph_index| from |cff| and sets it on +// |out_local_subrs_to_use|. Returns true on success. +bool SelectLocalSubr(const std::map<uint16_t, uint8_t> &fd_select, + const std::vector<ots::CFFIndex *> &local_subrs_per_font, + const ots::CFFIndex *local_subrs, + uint16_t glyph_index, // 0-origin + const ots::CFFIndex **out_local_subrs_to_use) { + *out_local_subrs_to_use = NULL; + + // First, find local subrs from |local_subrs_per_font|. + if ((fd_select.size() > 0) && + (!local_subrs_per_font.empty())) { + // Look up FDArray index for the glyph. + std::map<uint16_t, uint8_t>::const_iterator iter = + fd_select.find(glyph_index); + if (iter == fd_select.end()) { + return OTS_FAILURE(); + } + const uint8_t fd_index = iter->second; + if (fd_index >= local_subrs_per_font.size()) { + return OTS_FAILURE(); + } + *out_local_subrs_to_use = local_subrs_per_font.at(fd_index); + } else if (local_subrs) { + // Second, try to use |local_subrs|. Most Latin fonts don't have FDSelect + // entries. If The font has a local subrs index associated with the Top + // DICT (not FDArrays), use it. + *out_local_subrs_to_use = local_subrs; + } else { + // Just return NULL. + *out_local_subrs_to_use = NULL; + } + + return true; +} + +} // namespace + +namespace ots { + +bool ValidateType2CharStringIndex( + ots::Font *font, + const CFFIndex& char_strings_index, + const CFFIndex& global_subrs_index, + const std::map<uint16_t, uint8_t> &fd_select, + const std::vector<CFFIndex *> &local_subrs_per_font, + const CFFIndex *local_subrs, + Buffer* cff_table) { + if (char_strings_index.offsets.size() == 0) { + return OTS_FAILURE(); // no charstring. + } + + // For each glyph, validate the corresponding charstring. + for (unsigned i = 1; i < char_strings_index.offsets.size(); ++i) { + // Prepare a Buffer object, |char_string|, which contains the charstring + // for the |i|-th glyph. + const size_t length = + char_strings_index.offsets[i] - char_strings_index.offsets[i - 1]; + if (length > kMaxCharStringLength) { + return OTS_FAILURE(); + } + const size_t offset = char_strings_index.offsets[i - 1]; + cff_table->set_offset(offset); + if (!cff_table->Skip(length)) { + return OTS_FAILURE(); + } + Buffer char_string(cff_table->buffer() + offset, length); + + // Get a local subrs for the glyph. + const unsigned glyph_index = i - 1; // index in the map is 0-origin. + const CFFIndex *local_subrs_to_use = NULL; + if (!SelectLocalSubr(fd_select, + local_subrs_per_font, + local_subrs, + glyph_index, + &local_subrs_to_use)) { + return OTS_FAILURE(); + } + // If |local_subrs_to_use| is still NULL, use an empty one. + CFFIndex default_empty_subrs; + if (!local_subrs_to_use){ + local_subrs_to_use = &default_empty_subrs; + } + + // Check a charstring for the |i|-th glyph. + std::stack<int32_t> argument_stack; + bool found_endchar = false; + bool found_width = false; + size_t num_stems = 0; + if (!ExecuteType2CharString(font, + 0 /* initial call_depth is zero */, + global_subrs_index, *local_subrs_to_use, + cff_table, &char_string, &argument_stack, + &found_endchar, &found_width, &num_stems)) { + return OTS_FAILURE(); + } + if (!found_endchar) { + return OTS_FAILURE(); + } + } + return true; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/cff_type2_charstring.h b/gfx/ots/src/cff_type2_charstring.h new file mode 100644 index 000000000..be44bc72c --- /dev/null +++ b/gfx/ots/src/cff_type2_charstring.h @@ -0,0 +1,101 @@ +// Copyright (c) 2010 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_TYPE2_CHARSTRING_H_ +#define OTS_CFF_TYPE2_CHARSTRING_H_ + +#include "cff.h" +#include "ots.h" + +#include <map> +#include <vector> + +namespace ots { + +// Validates all charstrings in |char_strings_index|. Charstring is a small +// language for font hinting defined in Adobe Technical Note #5177. +// http://www.adobe.com/devnet/font/pdfs/5177.Type2.pdf +// +// The validation will fail if one of the following conditions is met: +// 1. The code uses more than 48 values of argument stack. +// 2. The code uses deeply nested subroutine calls (more than 10 levels.) +// 3. The code passes invalid number of operands to an operator. +// 4. The code calls an undefined global or local subroutine. +// 5. The code uses one of the following operators that are unlikely used in +// an ordinary fonts, and could be dangerous: random, put, get, index, roll. +// +// Arguments: +// global_subrs_index: Global subroutines which could be called by a charstring +// in |char_strings_index|. +// fd_select: A map from glyph # to font #. +// local_subrs_per_font: A list of Local Subrs associated with FDArrays. Can be +// empty. +// local_subrs: A Local Subrs associated with Top DICT. Can be NULL. +// cff_table: A buffer which contains actual byte code of charstring, global +// subroutines and local subroutines. +bool ValidateType2CharStringIndex( + Font *font, + const CFFIndex &char_strings_index, + const CFFIndex &global_subrs_index, + const std::map<uint16_t, uint8_t> &fd_select, + const std::vector<CFFIndex *> &local_subrs_per_font, + const CFFIndex *local_subrs, + Buffer *cff_table); + +// The list of Operators. See Appendix. A in Adobe Technical Note #5177. +enum Type2CharStringOperator { + kHStem = 1, + kVStem = 3, + kVMoveTo = 4, + kRLineTo = 5, + kHLineTo = 6, + kVLineTo = 7, + kRRCurveTo = 8, + kCallSubr = 10, + kReturn = 11, + kEndChar = 14, + kHStemHm = 18, + kHintMask = 19, + kCntrMask = 20, + kRMoveTo = 21, + kHMoveTo = 22, + kVStemHm = 23, + kRCurveLine = 24, + kRLineCurve = 25, + kVVCurveTo = 26, + kHHCurveTo = 27, + kCallGSubr = 29, + kVHCurveTo = 30, + kHVCurveTo = 31, + kDotSection = 12 << 8, + kAnd = (12 << 8) + 3, + kOr = (12 << 8) + 4, + kNot = (12 << 8) + 5, + kAbs = (12 << 8) + 9, + kAdd = (12 << 8) + 10, + kSub = (12 << 8) + 11, + kDiv = (12 << 8) + 12, + kNeg = (12 << 8) + 14, + kEq = (12 << 8) + 15, + kDrop = (12 << 8) + 18, + kPut = (12 << 8) + 20, + kGet = (12 << 8) + 21, + kIfElse = (12 << 8) + 22, + kRandom = (12 << 8) + 23, + kMul = (12 << 8) + 24, + kSqrt = (12 << 8) + 26, + kDup = (12 << 8) + 27, + kExch = (12 << 8) + 28, + kIndex = (12 << 8) + 29, + kRoll = (12 << 8) + 30, + kHFlex = (12 << 8) + 34, + kFlex = (12 << 8) + 35, + kHFlex1 = (12 << 8) + 36, + kFlex1 = (12 << 8) + 37, + // Operators that are undocumented, such as 'blend', will be rejected. +}; + +} // namespace ots + +#endif // OTS_CFF_TYPE2_CHARSTRING_H_ diff --git a/gfx/ots/src/cmap.cc b/gfx/ots/src/cmap.cc new file mode 100644 index 000000000..325f8e0d1 --- /dev/null +++ b/gfx/ots/src/cmap.cc @@ -0,0 +1,1088 @@ +// 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 <algorithm> +#include <set> +#include <utility> +#include <vector> + +#include "maxp.h" +#include "os2.h" + +// cmap - Character To Glyph Index Mapping Table +// http://www.microsoft.com/typography/otspec/cmap.htm + +#define TABLE_NAME "cmap" + +namespace { + +struct CMAPSubtableHeader { + uint16_t platform; + uint16_t encoding; + uint32_t offset; + uint16_t format; + uint32_t length; + uint32_t language; +}; + +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; +}; + +// Glyph array size for the Mac Roman (format 0) table. +const size_t kFormat0ArraySize = 256; + +// The upper limit of the Unicode code point. +const uint32_t kUnicodeUpperLimit = 0x10FFFF; + +// The maximum number of UVS records (See below). +const uint32_t kMaxCMAPSelectorRecords = 259; +// The range of UVSes are: +// 0x180B-0x180D (3 code points) +// 0xFE00-0xFE0F (16 code points) +// 0xE0100-0xE01EF (240 code points) +const uint32_t kMongolianVSStart = 0x180B; +const uint32_t kMongolianVSEnd = 0x180D; +const uint32_t kVSStart = 0xFE00; +const uint32_t kVSEnd = 0xFE0F; +const uint32_t kIVSStart = 0xE0100; +const uint32_t kIVSEnd = 0xE01EF; +const uint32_t kUVSUpperLimit = 0xFFFFFF; + +// Parses Format 4 tables +bool ParseFormat4(ots::Font *font, int platform, int encoding, + const uint8_t *data, size_t length, uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // 0.3.4, 3.0.4 or 3.1.4 subtables are complex and, rather than expanding the + // whole thing and recompacting it, we validate it and include it verbatim + // in the output. + + if (!font->os2) { + return OTS_FAILURE_MSG("Required OS/2 table missing"); + } + + if (!subtable.Skip(4)) { + return OTS_FAILURE_MSG("Can't read 4 bytes at start of cmap format 4 subtable"); + } + uint16_t language = 0; + if (!subtable.ReadU16(&language)) { + return OTS_FAILURE_MSG("Can't read language"); + } + if (language) { + // Platform ID 3 (windows) subtables should have language '0'. + return OTS_FAILURE_MSG("Languages should be 0 (%d)", language); + } + + uint16_t segcountx2, search_range, entry_selector, range_shift; + segcountx2 = search_range = entry_selector = range_shift = 0; + if (!subtable.ReadU16(&segcountx2) || + !subtable.ReadU16(&search_range) || + !subtable.ReadU16(&entry_selector) || + !subtable.ReadU16(&range_shift)) { + return OTS_FAILURE_MSG("Failed to read subcmap structure"); + } + + if (segcountx2 & 1 || search_range & 1) { + return OTS_FAILURE_MSG("Bad subcmap structure"); + } + const uint16_t segcount = segcountx2 >> 1; + // There must be at least one segment according the spec. + if (segcount < 1) { + return OTS_FAILURE_MSG("Segcount < 1 (%d)", segcount); + } + + // 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_MSG("expected search range != search range (%d != %d)", expected_search_range, search_range); + } + + if (entry_selector != log2segcount) { + return OTS_FAILURE_MSG("entry selector != log2(segement count) (%d != %d)", entry_selector, log2segcount); + } + + const uint16_t expected_range_shift = segcountx2 - search_range; + if (range_shift != expected_range_shift) { + return OTS_FAILURE_MSG("unexpected range shift (%d != %d)", range_shift, expected_range_shift); + } + + std::vector<Subtable314Range> ranges(segcount); + + for (unsigned i = 0; i < segcount; ++i) { + if (!subtable.ReadU16(&ranges[i].end_range)) { + return OTS_FAILURE_MSG("Failed to read segment %d", i); + } + } + + uint16_t padding; + if (!subtable.ReadU16(&padding)) { + return OTS_FAILURE_MSG("Failed to read cmap subtable segment padding"); + } + if (padding) { + return OTS_FAILURE_MSG("Non zero cmap subtable segment padding (%d)", padding); + } + + for (unsigned i = 0; i < segcount; ++i) { + if (!subtable.ReadU16(&ranges[i].start_range)) { + return OTS_FAILURE_MSG("Failed to read segment start range %d", i); + } + } + for (unsigned i = 0; i < segcount; ++i) { + if (!subtable.ReadS16(&ranges[i].id_delta)) { + return OTS_FAILURE_MSG("Failed to read segment delta %d", i); + } + } + 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_MSG("Failed to read segment range offset %d", i); + } + + 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_MSG("Bad segment offset (%d)", ranges[i].id_range_offset); + } + } + } + + // 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_MSG("Out of order end range (%d <= %d)", ranges[i].end_range, ranges[i-1].end_range); + } + if (ranges[i].start_range <= ranges[i - 1].end_range) { + return OTS_FAILURE_MSG("out of order start range (%d <= %d)", ranges[i].start_range, ranges[i-1].end_range); + } + + // On many fonts, the value of {first, last}_char_index are incorrect. + // Fix them. + if (font->os2->first_char_index != 0xFFFF && + ranges[i].start_range != 0xFFFF && + font->os2->first_char_index > ranges[i].start_range) { + font->os2->first_char_index = ranges[i].start_range; + } + if (font->os2->last_char_index != 0xFFFF && + ranges[i].end_range != 0xFFFF && + font->os2->last_char_index < ranges[i].end_range) { + font->os2->last_char_index = ranges[i].end_range; + } + } + + // The last range must end at 0xffff + if (ranges[segcount - 1].start_range != 0xffff || ranges[segcount - 1].end_range != 0xffff) { + return OTS_FAILURE_MSG("Final segment start and end must be 0xFFFF (0x%04X-0x%04X)", + ranges[segcount - 1].start_range, ranges[segcount - 1].end_range); + } + + // 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 = 0; i < segcount; ++i) { + for (unsigned cp = ranges[i].start_range; cp <= ranges[i].end_range; ++cp) { + const uint16_t code_point = static_cast<uint16_t>(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_MSG("Range glyph reference too high (%d > %d)", glyph, num_glyphs - 1); + } + } 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_MSG("bad glyph id offset (%d > %ld)", glyph_id_offset, length); + } + uint16_t glyph; + std::memcpy(&glyph, data + glyph_id_offset, 2); + glyph = ntohs(glyph); + if (glyph >= num_glyphs) { + return OTS_FAILURE_MSG("Range glyph reference too high (%d > %d)", glyph, num_glyphs - 1); + } + } + } + } + + // We accept the table. + // TODO(yusukes): transcode the subtable. + if (platform == 3 && encoding == 0) { + font->cmap->subtable_3_0_4_data = data; + font->cmap->subtable_3_0_4_length = length; + } else if (platform == 3 && encoding == 1) { + font->cmap->subtable_3_1_4_data = data; + font->cmap->subtable_3_1_4_length = length; + } else if (platform == 0 && encoding == 3) { + font->cmap->subtable_0_3_4_data = data; + font->cmap->subtable_0_3_4_length = length; + } else { + return OTS_FAILURE_MSG("Unknown cmap subtable type (platform=%d, encoding=%d)", platform, encoding); + } + + return true; +} + +bool Parse31012(ots::Font *font, + 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_MSG("failed to skip the first 8 bytes of format 12 subtable"); + } + uint32_t language = 0; + if (!subtable.ReadU32(&language)) { + return OTS_FAILURE_MSG("can't read format 12 subtable language"); + } + if (language) { + return OTS_FAILURE_MSG("format 12 subtable language should be zero (%d)", language); + } + + uint32_t num_groups = 0; + if (!subtable.ReadU32(&num_groups)) { + return OTS_FAILURE_MSG("can't read number of format 12 subtable groups"); + } + if (num_groups == 0 || subtable.remaining() / 12 < num_groups) { + return OTS_FAILURE_MSG("Bad format 12 subtable group count %d", num_groups); + } + + std::vector<ots::OpenTypeCMAPSubtableRange> &groups + = font->cmap->subtable_3_10_12; + 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_MSG("can't read format 12 subtable group"); + } + + if (groups[i].start_range > kUnicodeUpperLimit || + groups[i].end_range > kUnicodeUpperLimit || + groups[i].start_glyph_id > 0xFFFF) { + return OTS_FAILURE_MSG("bad format 12 subtable group (startCharCode=0x%4X, endCharCode=0x%4X, startGlyphID=%d)", + groups[i].start_range, groups[i].end_range, groups[i].start_glyph_id); + } + + // We assert that the glyph value is within range. Because of the range + // limits, above, we don't need to worry about overflow. + if (groups[i].end_range < groups[i].start_range) { + return OTS_FAILURE_MSG("format 12 subtable group endCharCode before startCharCode (0x%4X < 0x%4X)", + groups[i].end_range, groups[i].start_range); + } + if ((groups[i].end_range - groups[i].start_range) + + groups[i].start_glyph_id > num_glyphs) { + return OTS_FAILURE_MSG("bad format 12 subtable group startGlyphID (%d)", groups[i].start_glyph_id); + } + } + + // 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_MSG("out of order format 12 subtable group (startCharCode=0x%4X <= startCharCode=0x%4X of previous group)", + groups[i].start_range, groups[i-1].start_range); + } + if (groups[i].start_range <= groups[i - 1].end_range) { + return OTS_FAILURE_MSG("overlapping format 12 subtable groups (startCharCode=0x%4X <= endCharCode=0x%4X of previous group)", + groups[i].start_range, groups[i-1].end_range); + } + } + + return true; +} + +bool Parse31013(ots::Font *font, + 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_MSG("Bad cmap subtable length"); + } + uint32_t language = 0; + if (!subtable.ReadU32(&language)) { + return OTS_FAILURE_MSG("Can't read cmap subtable language"); + } + if (language) { + return OTS_FAILURE_MSG("Cmap subtable language should be zero but is %d", language); + } + + uint32_t num_groups = 0; + if (!subtable.ReadU32(&num_groups)) { + return OTS_FAILURE_MSG("Can't read number of groups in a cmap subtable"); + } + + // 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 || subtable.remaining() / 12 < num_groups) { + return OTS_FAILURE_MSG("Bad format 13 subtable group count %d", num_groups); + } + + std::vector<ots::OpenTypeCMAPSubtableRange> &groups + = font->cmap->subtable_3_10_13; + 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_MSG("Can't read subrange structure in a cmap subtable"); + } + + // We conservatively limit all of the values to protect some parsers from + // overflows + if (groups[i].start_range > kUnicodeUpperLimit || + groups[i].end_range > kUnicodeUpperLimit || + groups[i].start_glyph_id > 0xFFFF) { + return OTS_FAILURE_MSG("Bad subrange with start_range=%d, end_range=%d, start_glyph_id=%d", groups[i].start_range, groups[i].end_range, groups[i].start_glyph_id); + } + + if (groups[i].start_glyph_id >= num_glyphs) { + return OTS_FAILURE_MSG("Subrange starting glyph id too high (%d > %d)", groups[i].start_glyph_id, num_glyphs); + } + } + + // 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_MSG("Overlapping subrange starts (%d >= %d)", groups[i]. start_range, groups[i-1].start_range); + } + if (groups[i].start_range <= groups[i - 1].end_range) { + return OTS_FAILURE_MSG("Overlapping subranges (%d <= %d)", groups[i].start_range, groups[i-1].end_range); + } + } + + return true; +} + +bool Parse0514(ots::Font *font, + const uint8_t *data, size_t length, uint16_t num_glyphs) { + // Unicode Variation Selector table + ots::Buffer subtable(data, length); + + // Format 14 tables are simple. We parse these and fully serialise them + // later. + + // Skip format (USHORT) and length (ULONG) + if (!subtable.Skip(6)) { + return OTS_FAILURE_MSG("Can't read start of cmap subtable"); + } + + uint32_t num_records = 0; + if (!subtable.ReadU32(&num_records)) { + return OTS_FAILURE_MSG("Can't read number of records in cmap subtable"); + } + if (num_records == 0 || num_records > kMaxCMAPSelectorRecords) { + return OTS_FAILURE_MSG("Bad format 14 subtable records count %d", num_records); + } + + std::vector<ots::OpenTypeCMAPSubtableVSRecord>& records + = font->cmap->subtable_0_5_14; + records.resize(num_records); + + for (unsigned i = 0; i < num_records; ++i) { + if (!subtable.ReadU24(&records[i].var_selector) || + !subtable.ReadU32(&records[i].default_offset) || + !subtable.ReadU32(&records[i].non_default_offset)) { + return OTS_FAILURE_MSG("Can't read record structure of record %d in cmap subtale", i); + } + // Checks the value of variation selector + if (!((records[i].var_selector >= kMongolianVSStart && + records[i].var_selector <= kMongolianVSEnd) || + (records[i].var_selector >= kVSStart && + records[i].var_selector <= kVSEnd) || + (records[i].var_selector >= kIVSStart && + records[i].var_selector <= kIVSEnd))) { + return OTS_FAILURE_MSG("Bad record variation selector (%04X) in record %i", records[i].var_selector, i); + } + if (i > 0 && + records[i-1].var_selector >= records[i].var_selector) { + return OTS_FAILURE_MSG("Out of order variation selector (%04X >= %04X) in record %d", records[i-1].var_selector, records[i].var_selector, i); + } + + // Checks offsets + if (!records[i].default_offset && !records[i].non_default_offset) { + return OTS_FAILURE_MSG("No default aoffset in variation selector record %d", i); + } + if (records[i].default_offset && + records[i].default_offset >= length) { + return OTS_FAILURE_MSG("Default offset too high (%d >= %ld) in record %d", records[i].default_offset, length, i); + } + if (records[i].non_default_offset && + records[i].non_default_offset >= length) { + return OTS_FAILURE_MSG("Non default offset too high (%d >= %ld) in record %d", records[i].non_default_offset, length, i); + } + } + + for (unsigned i = 0; i < num_records; ++i) { + // Checks default UVS table + if (records[i].default_offset) { + subtable.set_offset(records[i].default_offset); + uint32_t num_ranges = 0; + if (!subtable.ReadU32(&num_ranges)) { + return OTS_FAILURE_MSG("Can't read number of ranges in record %d", i); + } + if (num_ranges == 0 || subtable.remaining() / 4 < num_ranges) { + return OTS_FAILURE_MSG("Bad number of ranges (%d) in record %d", num_ranges, i); + } + + uint32_t last_unicode_value = 0; + std::vector<ots::OpenTypeCMAPSubtableVSRange>& ranges + = records[i].ranges; + ranges.resize(num_ranges); + + for (unsigned j = 0; j < num_ranges; ++j) { + if (!subtable.ReadU24(&ranges[j].unicode_value) || + !subtable.ReadU8(&ranges[j].additional_count)) { + return OTS_FAILURE_MSG("Can't read range info in variation selector record %d", i); + } + const uint32_t check_value = + ranges[j].unicode_value + ranges[j].additional_count; + if (ranges[j].unicode_value == 0 || + ranges[j].unicode_value > kUnicodeUpperLimit || + check_value > kUVSUpperLimit || + (last_unicode_value && + ranges[j].unicode_value <= last_unicode_value)) { + return OTS_FAILURE_MSG("Bad Unicode value *%04X) in variation selector range %d record %d", ranges[j].unicode_value, j, i); + } + last_unicode_value = check_value; + } + } + + // Checks non default UVS table + if (records[i].non_default_offset) { + subtable.set_offset(records[i].non_default_offset); + uint32_t num_mappings = 0; + if (!subtable.ReadU32(&num_mappings)) { + return OTS_FAILURE_MSG("Can't read number of mappings in variation selector record %d", i); + } + if (num_mappings == 0 || subtable.remaining() / 5 < num_mappings) { + return OTS_FAILURE_MSG("Bad number of mappings (%d) in variation selector record %d", num_mappings, i); + } + + uint32_t last_unicode_value = 0; + std::vector<ots::OpenTypeCMAPSubtableVSMapping>& mappings + = records[i].mappings; + mappings.resize(num_mappings); + + for (unsigned j = 0; j < num_mappings; ++j) { + if (!subtable.ReadU24(&mappings[j].unicode_value) || + !subtable.ReadU16(&mappings[j].glyph_id)) { + return OTS_FAILURE_MSG("Can't read mapping %d in variation selector record %d", j, i); + } + if (mappings[j].glyph_id == 0 || + mappings[j].unicode_value == 0 || + mappings[j].unicode_value > kUnicodeUpperLimit || + (last_unicode_value && + mappings[j].unicode_value <= last_unicode_value)) { + return OTS_FAILURE_MSG("Bad mapping (%04X -> %d) in mapping %d of variation selector %d", mappings[j].unicode_value, mappings[j].glyph_id, j, i); + } + last_unicode_value = mappings[j].unicode_value; + } + } + } + + if (subtable.offset() != length) { + return OTS_FAILURE_MSG("Bad subtable offset (%ld != %ld)", subtable.offset(), length); + } + font->cmap->subtable_0_5_14_length = subtable.offset(); + return true; +} + +bool Parse100(ots::Font *font, const uint8_t *data, size_t length) { + // Mac Roman table + ots::Buffer subtable(data, length); + + if (!subtable.Skip(4)) { + return OTS_FAILURE_MSG("Bad cmap subtable"); + } + uint16_t language = 0; + if (!subtable.ReadU16(&language)) { + return OTS_FAILURE_MSG("Can't read language in cmap subtable"); + } + if (language) { + // simsun.ttf has non-zero language id. + OTS_WARNING("language id should be zero: %u", language); + } + + font->cmap->subtable_1_0_0.reserve(kFormat0ArraySize); + for (size_t i = 0; i < kFormat0ArraySize; ++i) { + uint8_t glyph_id = 0; + if (!subtable.ReadU8(&glyph_id)) { + return OTS_FAILURE_MSG("Can't read glyph id at array[%ld] in cmap subtable", i); + } + font->cmap->subtable_1_0_0.push_back(glyph_id); + } + + return true; +} + +} // namespace + +namespace ots { + +bool ots_cmap_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + font->cmap = new OpenTypeCMAP; + + uint16_t version = 0; + uint16_t num_tables = 0; + if (!table.ReadU16(&version) || + !table.ReadU16(&num_tables)) { + return OTS_FAILURE_MSG("Can't read structure of cmap"); + } + + if (version != 0) { + return OTS_FAILURE_MSG("Non zero cmap version (%d)", version); + } + if (!num_tables) { + return OTS_FAILURE_MSG("No subtables in cmap!"); + } + + 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_MSG("Can't read subtable information cmap subtable %d", i); + } + + subtable_headers.push_back(subt); + } + + const size_t data_offset = table.offset(); + + // make sure that all the offsets are valid. + for (unsigned i = 0; i < num_tables; ++i) { + if (subtable_headers[i].offset > 1024 * 1024 * 1024) { + return OTS_FAILURE_MSG("Bad subtable offset in cmap subtable %d", i); + } + if (subtable_headers[i].offset < data_offset || + subtable_headers[i].offset >= length) { + return OTS_FAILURE_MSG("Bad subtable offset (%d) in cmap subtable %d", subtable_headers[i].offset, i); + } + } + + // the format of the table is the first couple of bytes in the table. The + // length of the table is stored in a format-specific way. + 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_MSG("Can't read cmap subtable header format %d", i); + } + + uint16_t len = 0; + uint16_t lang = 0; + switch (subtable_headers[i].format) { + case 0: + case 4: + if (!table.ReadU16(&len)) { + return OTS_FAILURE_MSG("Can't read cmap subtable %d length", i); + } + if (!table.ReadU16(&lang)) { + return OTS_FAILURE_MSG("Can't read cmap subtable %d language", i); + } + subtable_headers[i].length = len; + subtable_headers[i].language = lang; + break; + case 12: + case 13: + if (!table.Skip(2)) { + return OTS_FAILURE_MSG("Bad cmap subtable %d structure", i); + } + if (!table.ReadU32(&subtable_headers[i].length)) { + return OTS_FAILURE_MSG("Can read cmap subtable %d length", i); + } + if (!table.ReadU32(&subtable_headers[i].language)) { + return OTS_FAILURE_MSG("Can't read cmap subtable %d language", i); + } + break; + case 14: + if (!table.ReadU32(&subtable_headers[i].length)) { + return OTS_FAILURE_MSG("Can't read cmap subtable %d length", i); + } + subtable_headers[i].language = 0; + break; + default: + subtable_headers[i].length = 0; + subtable_headers[i].language = 0; + break; + } + } + + // check if the table is sorted first by platform ID, then by encoding ID. + for (unsigned i = 1; i < num_tables; ++i) { + if (subtable_headers[i - 1].platform > subtable_headers[i].platform || + (subtable_headers[i - 1].platform == subtable_headers[i].platform && + (subtable_headers[i - 1].encoding > subtable_headers[i].encoding || + (subtable_headers[i - 1].encoding == subtable_headers[i].encoding && + subtable_headers[i - 1].language > subtable_headers[i].language)))) + OTS_WARNING("subtable %d with platform ID %d, encoding ID %d, language ID %d " + "following subtable with platform ID %d, encoding ID %d, language ID %d", + i, + subtable_headers[i].platform, + subtable_headers[i].encoding, + subtable_headers[i].language, + subtable_headers[i - 1].platform, + subtable_headers[i - 1].encoding, + subtable_headers[i - 1].language); + } + + // 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_MSG("Bad cmap subtable %d length", i); + } + // 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_MSG("Over long cmap subtable %d @ %d for %d", i, subtable_headers[i].offset, subtable_headers[i].length); + } + } + + // check that the cmap subtables are not overlapping. + std::set<std::pair<uint32_t, uint32_t> > uniq_checker; + std::vector<std::pair<uint32_t, uint8_t> > overlap_checker; + for (unsigned i = 0; i < num_tables; ++i) { + const uint32_t end_byte + = subtable_headers[i].offset + subtable_headers[i].length; + + if (!uniq_checker.insert(std::make_pair(subtable_headers[i].offset, + end_byte)).second) { + // Sometimes Unicode table and MS table share exactly the same data. + // We'll allow this. + continue; + } + overlap_checker.push_back( + std::make_pair(subtable_headers[i].offset, + static_cast<uint8_t>(1) /* start */)); + overlap_checker.push_back( + std::make_pair(end_byte, static_cast<uint8_t>(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_MSG("Excessive overlap count %d", overlap_count); + } + } + + // 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 (!font->maxp) { + return OTS_FAILURE_MSG("No maxp table in font! Needed by cmap."); + } + const uint16_t num_glyphs = font->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 1 4 (Unicode 1.1) + // 0 3 4 (Unicode BMP) + // 0 3 12 (Unicode UCS-4) + // 0 5 14 (Unicode Variation Sequences) + // 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) + // + // Note: + // * 0-0-4 and 0-1-4 tables are (usually) written as a 3-1-4 table. If 3-1-4 table + // also exists, the 0-0-4 or 0-1-4 tables are ignored. + // * Unlike 0-0-4 table, 0-3-4 table is written as a 0-3-4 table. + // Some fonts which include 0-5-14 table seems to be required 0-3-4 + // table. The 0-3-4 table will be wriiten even if 3-1-4 table also exists. + // * 0-3-12 table is written as a 3-10-12 table. If 3-10-12 table also + // exists, the 0-3-12 table is ignored. + // + + 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].encoding == 1) && + (subtable_headers[i].format == 4)) { + // parse and output the 0-0-4 and 0-1-4 tables 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 (!ParseFormat4(font, 3, 1, data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse format 4 cmap subtable %d", i); + } + } else if ((subtable_headers[i].encoding == 3) && + (subtable_headers[i].format == 4)) { + // parse and output the 0-3-4 table as 0-3-4 table. + if (!ParseFormat4(font, 0, 3, data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse format 4 cmap subtable %d", i); + } + } 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(font, data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse format 12 cmap subtable %d", i); + } + } else if ((subtable_headers[i].encoding == 5) && + (subtable_headers[i].format == 14)) { + if (!Parse0514(font, data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse format 14 cmap subtable %d", i); + } + } + } 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(font, data + subtable_headers[i].offset, + subtable_headers[i].length)) { + return OTS_FAILURE(); + } + } + } else if (subtable_headers[i].platform == 3) { + // MS platform + + switch (subtable_headers[i].encoding) { + case 0: + case 1: + if (subtable_headers[i].format == 4) { + // parse 3-0-4 or 3-1-4 table. + if (!ParseFormat4(font, subtable_headers[i].platform, + subtable_headers[i].encoding, + data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE(); + } + } + break; + case 10: + if (subtable_headers[i].format == 12) { + font->cmap->subtable_3_10_12.clear(); + if (!Parse31012(font, data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE(); + } + } else if (subtable_headers[i].format == 13) { + font->cmap->subtable_3_10_13.clear(); + if (!Parse31013(font, data + subtable_headers[i].offset, + subtable_headers[i].length, num_glyphs)) { + return OTS_FAILURE(); + } + } + break; + } + } + } + + return true; +} + +bool ots_cmap_should_serialise(Font *font) { + return font->cmap != NULL; +} + +bool ots_cmap_serialise(OTSStream *out, Font *font) { + const bool have_034 = font->cmap->subtable_0_3_4_data != NULL; + const bool have_0514 = font->cmap->subtable_0_5_14.size() != 0; + const bool have_100 = font->cmap->subtable_1_0_0.size() != 0; + const bool have_304 = font->cmap->subtable_3_0_4_data != NULL; + // 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) && font->cmap->subtable_3_1_4_data; + const bool have_31012 = font->cmap->subtable_3_10_12.size() != 0; + const bool have_31013 = font->cmap->subtable_3_10_13.size() != 0; + const uint16_t num_subtables = static_cast<uint16_t>(have_034) + + static_cast<uint16_t>(have_0514) + + static_cast<uint16_t>(have_100) + + static_cast<uint16_t>(have_304) + + static_cast<uint16_t>(have_314) + + static_cast<uint16_t>(have_31012) + + static_cast<uint16_t>(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 && !have_034 && !have_31012 && !have_31013) { + return OTS_FAILURE_MSG("no supported subtables were found"); + } + + 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_034 = out->Tell(); + if (have_034) { + if (!out->Write(font->cmap->subtable_0_3_4_data, + font->cmap->subtable_0_3_4_length)) { + return OTS_FAILURE(); + } + } + + const off_t offset_0514 = out->Tell(); + if (have_0514) { + const std::vector<ots::OpenTypeCMAPSubtableVSRecord> &records + = font->cmap->subtable_0_5_14; + const unsigned num_records = records.size(); + if (!out->WriteU16(14) || + !out->WriteU32(font->cmap->subtable_0_5_14_length) || + !out->WriteU32(num_records)) { + return OTS_FAILURE(); + } + for (unsigned i = 0; i < num_records; ++i) { + if (!out->WriteU24(records[i].var_selector) || + !out->WriteU32(records[i].default_offset) || + !out->WriteU32(records[i].non_default_offset)) { + return OTS_FAILURE(); + } + } + for (unsigned i = 0; i < num_records; ++i) { + if (records[i].default_offset) { + const std::vector<ots::OpenTypeCMAPSubtableVSRange> &ranges + = records[i].ranges; + const unsigned num_ranges = ranges.size(); + if (!out->Seek(records[i].default_offset + offset_0514) || + !out->WriteU32(num_ranges)) { + return OTS_FAILURE(); + } + for (unsigned j = 0; j < num_ranges; ++j) { + if (!out->WriteU24(ranges[j].unicode_value) || + !out->WriteU8(ranges[j].additional_count)) { + return OTS_FAILURE(); + } + } + } + if (records[i].non_default_offset) { + const std::vector<ots::OpenTypeCMAPSubtableVSMapping> &mappings + = records[i].mappings; + const unsigned num_mappings = mappings.size(); + if (!out->Seek(records[i].non_default_offset + offset_0514) || + !out->WriteU32(num_mappings)) { + return OTS_FAILURE(); + } + for (unsigned j = 0; j < num_mappings; ++j) { + if (!out->WriteU24(mappings[j].unicode_value) || + !out->WriteU16(mappings[j].glyph_id)) { + 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(&(font->cmap->subtable_1_0_0[0]), kFormat0ArraySize)) { + return OTS_FAILURE(); + } + } + + const off_t offset_304 = out->Tell(); + if (have_304) { + if (!out->Write(font->cmap->subtable_3_0_4_data, + font->cmap->subtable_3_0_4_length)) { + return OTS_FAILURE(); + } + } + + const off_t offset_314 = out->Tell(); + if (have_314) { + if (!out->Write(font->cmap->subtable_3_1_4_data, + font->cmap->subtable_3_1_4_length)) { + return OTS_FAILURE(); + } + } + + const off_t offset_31012 = out->Tell(); + if (have_31012) { + std::vector<OpenTypeCMAPSubtableRange> &groups + = font->cmap->subtable_3_10_12; + 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 + = font->cmap->subtable_3_10_13; + const unsigned num_groups = groups.size(); + if (!out->WriteU16(13) || + !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 table_end = out->Tell(); + + // Now seek back and write the table of offsets + if (!out->Seek(record_offset)) { + return OTS_FAILURE(); + } + + if (have_034) { + if (!out->WriteU16(0) || + !out->WriteU16(3) || + !out->WriteU32(offset_034 - table_start)) { + return OTS_FAILURE(); + } + } + + if (have_0514) { + if (!out->WriteU16(0) || + !out->WriteU16(5) || + !out->WriteU32(offset_0514 - table_start)) { + 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(); + } + + return true; +} + +void ots_cmap_reuse(Font *font, Font *other) { + font->cmap = other->cmap; + font->cmap_reused = true; +} + +void ots_cmap_free(Font *font) { + delete font->cmap; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/cmap.h b/gfx/ots/src/cmap.h new file mode 100644 index 000000000..5b09556b7 --- /dev/null +++ b/gfx/ots/src/cmap.h @@ -0,0 +1,74 @@ +// 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 OpenTypeCMAPSubtableVSRange { + uint32_t unicode_value; + uint8_t additional_count; +}; + +struct OpenTypeCMAPSubtableVSMapping { + uint32_t unicode_value; + uint16_t glyph_id; +}; + +struct OpenTypeCMAPSubtableVSRecord { + uint32_t var_selector; + uint32_t default_offset; + uint32_t non_default_offset; + std::vector<OpenTypeCMAPSubtableVSRange> ranges; + std::vector<OpenTypeCMAPSubtableVSMapping> mappings; +}; + +struct OpenTypeCMAP { + OpenTypeCMAP() + : subtable_0_3_4_data(NULL), + subtable_0_3_4_length(0), + subtable_0_5_14_length(0), + subtable_3_0_4_data(NULL), + subtable_3_0_4_length(0), + subtable_3_1_4_data(NULL), + subtable_3_1_4_length(0) { + } + + // Platform 0, Encoding 3, Format 4, Unicode BMP table. + const uint8_t *subtable_0_3_4_data; + size_t subtable_0_3_4_length; + + // Platform 0, Encoding 5, Format 14, Unicode Variation Sequence table. + size_t subtable_0_5_14_length; + std::vector<OpenTypeCMAPSubtableVSRecord> subtable_0_5_14; + + // Platform 3, Encoding 0, Format 4, MS Symbol table. + const uint8_t *subtable_3_0_4_data; + size_t subtable_3_0_4_length; + // Platform 3, Encoding 1, Format 4, MS Unicode BMP table. + const uint8_t *subtable_3_1_4_data; + size_t subtable_3_1_4_length; + + // Platform 3, Encoding 10, Format 12, MS Unicode UCS-4 table. + std::vector<OpenTypeCMAPSubtableRange> subtable_3_10_12; + // Platform 3, Encoding 10, Format 13, MS UCS-4 Fallback table. + std::vector<OpenTypeCMAPSubtableRange> subtable_3_10_13; + // Platform 1, Encoding 0, Format 0, Mac Roman table. + std::vector<uint8_t> subtable_1_0_0; +}; + +} // namespace ots + +#endif diff --git a/gfx/ots/src/cvt.cc b/gfx/ots/src/cvt.cc new file mode 100644 index 000000000..1402e7c06 --- /dev/null +++ b/gfx/ots/src/cvt.cc @@ -0,0 +1,65 @@ +// 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/typography/otspec/cvt.htm + +#define TABLE_NAME "cvt" + +namespace ots { + +bool ots_cvt_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypeCVT *cvt = new OpenTypeCVT; + font->cvt = cvt; + + if (length >= 128 * 1024u) { + return OTS_FAILURE_MSG("Length (%d) > 120K"); // almost all cvt tables are less than 4k bytes. + } + + if (length % 2 != 0) { + return OTS_FAILURE_MSG("Uneven cvt length (%d)", length); + } + + if (!table.Skip(length)) { + return OTS_FAILURE_MSG("Length too high"); + } + + cvt->data = data; + cvt->length = length; + return true; +} + +bool ots_cvt_should_serialise(Font *font) { + if (!font->glyf) { + return false; // this table is not for CFF fonts. + } + return font->cvt != NULL; +} + +bool ots_cvt_serialise(OTSStream *out, Font *font) { + const OpenTypeCVT *cvt = font->cvt; + + if (!out->Write(cvt->data, cvt->length)) { + return OTS_FAILURE_MSG("Failed to write CVT table"); + } + + return true; +} + +void ots_cvt_reuse(Font *font, Font *other) { + font->cvt = other->cvt; + font->cvt_reused = true; +} + +void ots_cvt_free(Font *font) { + delete font->cvt; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/cvt.h b/gfx/ots/src/cvt.h new file mode 100644 index 000000000..3c25f06f6 --- /dev/null +++ b/gfx/ots/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/gfx/ots/src/fpgm.cc b/gfx/ots/src/fpgm.cc new file mode 100644 index 000000000..faa9a2392 --- /dev/null +++ b/gfx/ots/src/fpgm.cc @@ -0,0 +1,59 @@ +// 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/typography/otspec/fpgm.htm + +#define TABLE_NAME "fpgm" + +namespace ots { + +bool ots_fpgm_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypeFPGM *fpgm = new OpenTypeFPGM; + font->fpgm = fpgm; + + if (length >= 128 * 1024u) { + return OTS_FAILURE_MSG("length (%ld) > 120", length); // almost all fpgm tables are less than 5k bytes. + } + + if (!table.Skip(length)) { + return OTS_FAILURE_MSG("Bad fpgm length"); + } + + fpgm->data = data; + fpgm->length = length; + return true; +} + +bool ots_fpgm_should_serialise(Font *font) { + if (!font->glyf) return false; // this table is not for CFF fonts. + return font->fpgm != NULL; +} + +bool ots_fpgm_serialise(OTSStream *out, Font *font) { + const OpenTypeFPGM *fpgm = font->fpgm; + + if (!out->Write(fpgm->data, fpgm->length)) { + return OTS_FAILURE_MSG("Failed to write fpgm"); + } + + return true; +} + +void ots_fpgm_reuse(Font *font, Font *other) { + font->fpgm = other->fpgm; + font->fpgm_reused = true; +} + +void ots_fpgm_free(Font *font) { + delete font->fpgm; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/fpgm.h b/gfx/ots/src/fpgm.h new file mode 100644 index 000000000..8fabac36d --- /dev/null +++ b/gfx/ots/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/gfx/ots/src/gasp.cc b/gfx/ots/src/gasp.cc new file mode 100644 index 000000000..5ebf5d84b --- /dev/null +++ b/gfx/ots/src/gasp.cc @@ -0,0 +1,119 @@ +// 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/typography/otspec/gasp.htm + +#define TABLE_NAME "gasp" + +#define DROP_THIS_TABLE(...) \ + do { \ + OTS_FAILURE_MSG_(font->file, TABLE_NAME ": " __VA_ARGS__); \ + OTS_FAILURE_MSG("Table discarded"); \ + delete font->gasp; \ + font->gasp = 0; \ + } while (0) + +namespace ots { + +bool ots_gasp_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypeGASP *gasp = new OpenTypeGASP; + font->gasp = gasp; + + uint16_t num_ranges = 0; + if (!table.ReadU16(&gasp->version) || + !table.ReadU16(&num_ranges)) { + return OTS_FAILURE_MSG("Failed to read table header"); + } + + if (gasp->version > 1) { + // Lots of Linux fonts have bad version numbers... + DROP_THIS_TABLE("bad version: %u", gasp->version); + return true; + } + + if (num_ranges == 0) { + DROP_THIS_TABLE("num_ranges is zero"); + return true; + } + + gasp->gasp_ranges.reserve(num_ranges); + for (unsigned i = 0; i < num_ranges; ++i) { + uint16_t max_ppem = 0; + uint16_t behavior = 0; + if (!table.ReadU16(&max_ppem) || + !table.ReadU16(&behavior)) { + return OTS_FAILURE_MSG("Failed to read subrange %d", i); + } + 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. + DROP_THIS_TABLE("ranges are not sorted"); + return true; + } + if ((i == num_ranges - 1u) && // never underflow. + (max_ppem != 0xffffu)) { + DROP_THIS_TABLE("The last record should be 0xFFFF as a sentinel value " + "for rangeMaxPPEM"); + 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(Font *font) { + return font->gasp != NULL; +} + +bool ots_gasp_serialise(OTSStream *out, Font *font) { + const OpenTypeGASP *gasp = font->gasp; + + const uint16_t num_ranges = static_cast<uint16_t>(gasp->gasp_ranges.size()); + if (num_ranges != gasp->gasp_ranges.size() || + !out->WriteU16(gasp->version) || + !out->WriteU16(num_ranges)) { + return OTS_FAILURE_MSG("failed to write gasp header"); + } + + for (uint16_t i = 0; i < num_ranges; ++i) { + if (!out->WriteU16(gasp->gasp_ranges[i].first) || + !out->WriteU16(gasp->gasp_ranges[i].second)) { + return OTS_FAILURE_MSG("Failed to write gasp subtable %d", i); + } + } + + return true; +} + +void ots_gasp_reuse(Font *font, Font *other) { + font->gasp = other->gasp; + font->gasp_reused = true; +} + +void ots_gasp_free(Font *font) { + delete font->gasp; +} + +} // namespace ots + +#undef TABLE_NAME +#undef DROP_THIS_TABLE diff --git a/gfx/ots/src/gasp.h b/gfx/ots/src/gasp.h new file mode 100644 index 000000000..48d7e7c16 --- /dev/null +++ b/gfx/ots/src/gasp.h @@ -0,0 +1,24 @@ +// 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 <new> +#include <utility> +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeGASP { + uint16_t version; + // A array of (max PPEM, GASP behavior) pairs. + std::vector<std::pair<uint16_t, uint16_t> > gasp_ranges; +}; + +} // namespace ots + +#endif // OTS_GASP_H_ diff --git a/gfx/ots/src/gdef.cc b/gfx/ots/src/gdef.cc new file mode 100644 index 000000000..71c6fc592 --- /dev/null +++ b/gfx/ots/src/gdef.cc @@ -0,0 +1,373 @@ +// Copyright (c) 2011 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 "gdef.h" + +#include <limits> +#include <vector> + +#include "gpos.h" +#include "gsub.h" +#include "layout.h" +#include "maxp.h" + +// GDEF - The Glyph Definition Table +// http://www.microsoft.com/typography/otspec/gdef.htm + +#define TABLE_NAME "GDEF" + +namespace { + +// The maximum class value in class definition tables. +const uint16_t kMaxClassDefValue = 0xFFFF; +// The maximum class value in the glyph class definision table. +const uint16_t kMaxGlyphClassDefValue = 4; +// The maximum format number of caret value tables. +// We don't support format 3 for now. See the comment in +// ParseLigCaretListTable() for the reason. +const uint16_t kMaxCaretValueFormat = 2; + +bool ParseGlyphClassDefTable(ots::Font *font, const uint8_t *data, + size_t length, const uint16_t num_glyphs) { + return ots::ParseClassDefTable(font, data, length, num_glyphs, + kMaxGlyphClassDefValue); +} + +bool ParseAttachListTable(ots::Font *font, const uint8_t *data, + size_t length, const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + uint16_t offset_coverage = 0; + uint16_t glyph_count = 0; + if (!subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&glyph_count)) { + return OTS_FAILURE_MSG("Failed to read gdef header"); + } + const unsigned attach_points_end = + 2 * static_cast<unsigned>(glyph_count) + 4; + if (attach_points_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad glyph count in gdef"); + } + if (offset_coverage == 0 || offset_coverage >= length || + offset_coverage < attach_points_end) { + return OTS_FAILURE_MSG("Bad coverage offset %d", offset_coverage); + } + if (glyph_count > num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph count %u", glyph_count); + } + + std::vector<uint16_t> attach_points; + attach_points.resize(glyph_count); + for (unsigned i = 0; i < glyph_count; ++i) { + if (!subtable.ReadU16(&attach_points[i])) { + return OTS_FAILURE_MSG("Can't read attachment point %d", i); + } + if (attach_points[i] >= length || + attach_points[i] < attach_points_end) { + return OTS_FAILURE_MSG("Bad attachment point %d of %d", i, attach_points[i]); + } + } + + // Parse coverage table + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Bad coverage table"); + } + + // Parse attach point table + for (unsigned i = 0; i < attach_points.size(); ++i) { + subtable.set_offset(attach_points[i]); + uint16_t point_count = 0; + if (!subtable.ReadU16(&point_count)) { + return OTS_FAILURE_MSG("Can't read point count %d", i); + } + if (point_count == 0) { + return OTS_FAILURE_MSG("zero point count %d", i); + } + uint16_t last_point_index = 0; + uint16_t point_index = 0; + for (unsigned j = 0; j < point_count; ++j) { + if (!subtable.ReadU16(&point_index)) { + return OTS_FAILURE_MSG("Can't read point index %d in point %d", j, i); + } + // Contour point indeces are in increasing numerical order + if (last_point_index != 0 && last_point_index >= point_index) { + return OTS_FAILURE_MSG("bad contour indeces: %u >= %u", + last_point_index, point_index); + } + last_point_index = point_index; + } + } + return true; +} + +bool ParseLigCaretListTable(ots::Font *font, const uint8_t *data, + size_t length, const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + uint16_t offset_coverage = 0; + uint16_t lig_glyph_count = 0; + if (!subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&lig_glyph_count)) { + return OTS_FAILURE_MSG("Can't read caret structure"); + } + const unsigned lig_glyphs_end = + 2 * static_cast<unsigned>(lig_glyph_count) + 4; + if (lig_glyphs_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad caret structure"); + } + if (offset_coverage == 0 || offset_coverage >= length || + offset_coverage < lig_glyphs_end) { + return OTS_FAILURE_MSG("Bad caret coverate offset %d", offset_coverage); + } + if (lig_glyph_count > num_glyphs) { + return OTS_FAILURE_MSG("bad ligature glyph count: %u", lig_glyph_count); + } + + std::vector<uint16_t> lig_glyphs; + lig_glyphs.resize(lig_glyph_count); + for (unsigned i = 0; i < lig_glyph_count; ++i) { + if (!subtable.ReadU16(&lig_glyphs[i])) { + return OTS_FAILURE_MSG("Can't read ligature glyph location %d", i); + } + if (lig_glyphs[i] >= length || lig_glyphs[i] < lig_glyphs_end) { + return OTS_FAILURE_MSG("Bad ligature glyph location %d in glyph %d", lig_glyphs[i], i); + } + } + + // Parse coverage table + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Can't parse caret coverage table"); + } + + // Parse ligature glyph table + for (unsigned i = 0; i < lig_glyphs.size(); ++i) { + subtable.set_offset(lig_glyphs[i]); + uint16_t caret_count = 0; + if (!subtable.ReadU16(&caret_count)) { + return OTS_FAILURE_MSG("Can't read caret count for glyph %d", i); + } + if (caret_count == 0) { + return OTS_FAILURE_MSG("bad caret value count: %u", caret_count); + } + + std::vector<uint16_t> caret_value_offsets; + caret_value_offsets.resize(caret_count); + unsigned caret_value_offsets_end = 2 * static_cast<unsigned>(caret_count) + 2; + for (unsigned j = 0; j < caret_count; ++j) { + if (!subtable.ReadU16(&caret_value_offsets[j])) { + return OTS_FAILURE_MSG("Can't read caret offset %d for glyph %d", j, i); + } + if (caret_value_offsets[j] >= length || caret_value_offsets[j] < caret_value_offsets_end) { + return OTS_FAILURE_MSG("Bad caret offset %d for caret %d glyph %d", caret_value_offsets[j], j, i); + } + } + + // Parse caret values table + for (unsigned j = 0; j < caret_count; ++j) { + subtable.set_offset(lig_glyphs[i] + caret_value_offsets[j]); + uint16_t caret_format = 0; + if (!subtable.ReadU16(&caret_format)) { + return OTS_FAILURE_MSG("Can't read caret values table %d in glyph %d", j, i); + } + // TODO(bashi): We only support caret value format 1 and 2 for now + // because there are no fonts which contain caret value format 3 + // as far as we investigated. + if (caret_format == 0 || caret_format > kMaxCaretValueFormat) { + return OTS_FAILURE_MSG("bad caret value format: %u", caret_format); + } + // CaretValueFormats contain a 2-byte field which could be + // arbitrary value. + if (!subtable.Skip(2)) { + return OTS_FAILURE_MSG("Bad caret value table structure %d in glyph %d", j, i); + } + } + } + return true; +} + +bool ParseMarkAttachClassDefTable(ots::Font *font, const uint8_t *data, + size_t length, const uint16_t num_glyphs) { + return ots::ParseClassDefTable(font, data, length, num_glyphs, kMaxClassDefValue); +} + +bool ParseMarkGlyphSetsDefTable(ots::Font *font, const uint8_t *data, + size_t length, const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + uint16_t format = 0; + uint16_t mark_set_count = 0; + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&mark_set_count)) { + return OTS_FAILURE_MSG("Can' read mark glyph table structure"); + } + if (format != 1) { + return OTS_FAILURE_MSG("bad mark glyph set table format: %u", format); + } + + const unsigned mark_sets_end = 2 * static_cast<unsigned>(mark_set_count) + 4; + if (mark_sets_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad mark_set %d", mark_sets_end); + } + for (unsigned i = 0; i < mark_set_count; ++i) { + uint32_t offset_coverage = 0; + if (!subtable.ReadU32(&offset_coverage)) { + return OTS_FAILURE_MSG("Can't read covrage location for mark set %d", i); + } + if (offset_coverage >= length || + offset_coverage < mark_sets_end) { + return OTS_FAILURE_MSG("Bad coverage location %d for mark set %d", offset_coverage, i); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table for mark set %d", i); + } + } + font->gdef->num_mark_glyph_sets = mark_set_count; + return true; +} + +} // namespace + +namespace ots { + +bool ots_gdef_parse(Font *font, const uint8_t *data, size_t length) { + // Grab the number of glyphs in the font from the maxp table to check + // GlyphIDs in GDEF table. + if (!font->maxp) { + return OTS_FAILURE_MSG("No maxp table in font, needed by GDEF"); + } + const uint16_t num_glyphs = font->maxp->num_glyphs; + + Buffer table(data, length); + + OpenTypeGDEF *gdef = new OpenTypeGDEF; + font->gdef = gdef; + + uint32_t version = 0; + if (!table.ReadU32(&version)) { + return OTS_FAILURE_MSG("Incomplete table"); + } + if (version < 0x00010000 || version == 0x00010001) { + return OTS_FAILURE_MSG("Bad version"); + } + + if (version >= 0x00010002) { + gdef->version_2 = true; + } + + uint16_t offset_glyph_class_def = 0; + uint16_t offset_attach_list = 0; + uint16_t offset_lig_caret_list = 0; + uint16_t offset_mark_attach_class_def = 0; + if (!table.ReadU16(&offset_glyph_class_def) || + !table.ReadU16(&offset_attach_list) || + !table.ReadU16(&offset_lig_caret_list) || + !table.ReadU16(&offset_mark_attach_class_def)) { + return OTS_FAILURE_MSG("Incomplete table"); + } + uint16_t offset_mark_glyph_sets_def = 0; + if (gdef->version_2) { + if (!table.ReadU16(&offset_mark_glyph_sets_def)) { + return OTS_FAILURE_MSG("Incomplete table"); + } + } + + unsigned gdef_header_end = 4 + 4 * 2; + if (gdef->version_2) + gdef_header_end += 2; + + // Parse subtables + if (offset_glyph_class_def) { + if (offset_glyph_class_def >= length || + offset_glyph_class_def < gdef_header_end) { + return OTS_FAILURE_MSG("Invalid offset to glyph classes"); + } + if (!ParseGlyphClassDefTable(font, data + offset_glyph_class_def, + length - offset_glyph_class_def, + num_glyphs)) { + return OTS_FAILURE_MSG("Invalid glyph classes"); + } + gdef->has_glyph_class_def = true; + } + + if (offset_attach_list) { + if (offset_attach_list >= length || + offset_attach_list < gdef_header_end) { + return OTS_FAILURE_MSG("Invalid offset to attachment list"); + } + if (!ParseAttachListTable(font, data + offset_attach_list, + length - offset_attach_list, + num_glyphs)) { + return OTS_FAILURE_MSG("Invalid attachment list"); + } + } + + if (offset_lig_caret_list) { + if (offset_lig_caret_list >= length || + offset_lig_caret_list < gdef_header_end) { + return OTS_FAILURE_MSG("Invalid offset to ligature caret list"); + } + if (!ParseLigCaretListTable(font, data + offset_lig_caret_list, + length - offset_lig_caret_list, + num_glyphs)) { + return OTS_FAILURE_MSG("Invalid ligature caret list"); + } + } + + if (offset_mark_attach_class_def) { + if (offset_mark_attach_class_def >= length || + offset_mark_attach_class_def < gdef_header_end) { + return OTS_FAILURE_MSG("Invalid offset to mark attachment list"); + } + if (!ParseMarkAttachClassDefTable(font, + data + offset_mark_attach_class_def, + length - offset_mark_attach_class_def, + num_glyphs)) { + return OTS_FAILURE_MSG("Invalid mark attachment list"); + } + gdef->has_mark_attachment_class_def = true; + } + + if (offset_mark_glyph_sets_def) { + if (offset_mark_glyph_sets_def >= length || + offset_mark_glyph_sets_def < gdef_header_end) { + return OTS_FAILURE_MSG("invalid offset to mark glyph sets"); + } + if (!ParseMarkGlyphSetsDefTable(font, + data + offset_mark_glyph_sets_def, + length - offset_mark_glyph_sets_def, + num_glyphs)) { + return OTS_FAILURE_MSG("Invalid mark glyph sets"); + } + gdef->has_mark_glyph_sets_def = true; + } + gdef->data = data; + gdef->length = length; + return true; +} + +bool ots_gdef_should_serialise(Font *font) { + return font->gdef != NULL && font->gdef->data != NULL; +} + +bool ots_gdef_serialise(OTSStream *out, Font *font) { + if (!out->Write(font->gdef->data, font->gdef->length)) { + return OTS_FAILURE_MSG("Failed to write GDEF table"); + } + + return true; +} + +void ots_gdef_reuse(Font *font, Font *other) { + font->gdef = other->gdef; + font->gdef_reused = true; +} + +void ots_gdef_free(Font *font) { + delete font->gdef; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/gdef.h b/gfx/ots/src/gdef.h new file mode 100644 index 000000000..f46f419c7 --- /dev/null +++ b/gfx/ots/src/gdef.h @@ -0,0 +1,36 @@ +// Copyright (c) 2011 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_GDEF_H_ +#define OTS_GDEF_H_ + +#include "ots.h" + +namespace ots { + +struct OpenTypeGDEF { + OpenTypeGDEF() + : version_2(false), + has_glyph_class_def(false), + has_mark_attachment_class_def(false), + has_mark_glyph_sets_def(false), + num_mark_glyph_sets(0), + data(NULL), + length(0) { + } + + bool version_2; + bool has_glyph_class_def; + bool has_mark_attachment_class_def; + bool has_mark_glyph_sets_def; + uint16_t num_mark_glyph_sets; + + const uint8_t *data; + size_t length; +}; + +} // namespace ots + +#endif + diff --git a/gfx/ots/src/glyf.cc b/gfx/ots/src/glyf.cc new file mode 100644 index 000000000..311916dc0 --- /dev/null +++ b/gfx/ots/src/glyf.cc @@ -0,0 +1,303 @@ +// 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/typography/otspec/glyf.htm + +#define TABLE_NAME "glyf" + +namespace { + +bool ParseFlagsForSimpleGlyph(ots::Font *font, + ots::Buffer *table, + uint32_t gly_length, + uint32_t num_flags, + uint32_t *flags_count_logical, + uint32_t *flags_count_physical, + uint32_t *xy_coordinates_length) { + uint8_t flag = 0; + if (!table->ReadU8(&flag)) { + return OTS_FAILURE_MSG("Can't read flag"); + } + + 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_MSG("Count too high (%d + 1 >= %d)", *flags_count_logical, num_flags); + } + uint8_t repeat = 0; + if (!table->ReadU8(&repeat)) { + return OTS_FAILURE_MSG("Can't read repeat value"); + } + if (repeat == 0) { + return OTS_FAILURE_MSG("Zero repeat"); + } + delta += (delta * repeat); + + *flags_count_logical += repeat; + if (*flags_count_logical >= num_flags) { + return OTS_FAILURE_MSG("Count too high (%d >= %d)", *flags_count_logical, num_flags); + } + ++(*flags_count_physical); + } + + if ((flag & (1u << 6)) || (flag & (1u << 7))) { // reserved flags + return OTS_FAILURE_MSG("Bad glyph flag value (%d), reserved flags must be set to zero", flag); + } + + *xy_coordinates_length += delta; + if (gly_length < *xy_coordinates_length) { + return OTS_FAILURE_MSG("Glyph coordinates length too low (%d < %d)", gly_length, *xy_coordinates_length); + } + + return true; +} + +bool ParseSimpleGlyph(ots::Font *font, const uint8_t *data, + ots::Buffer *table, int16_t num_contours, + uint32_t gly_offset, uint32_t gly_length, + uint32_t *new_size) { + ots::OpenTypeGLYF *glyf = font->glyf; + + // read the end-points array + uint16_t num_flags = 0; + for (int i = 0; i < num_contours; ++i) { + uint16_t tmp_index = 0; + if (!table->ReadU16(&tmp_index)) { + return OTS_FAILURE_MSG("Can't read contour index %d", i); + } + if (tmp_index == 0xffffu) { + return OTS_FAILURE_MSG("Bad contour index %d", i); + } + // check if the indices are monotonically increasing + if (i && (tmp_index + 1 <= num_flags)) { + return OTS_FAILURE_MSG("Decreasing contour index %d + 1 <= %d", tmp_index, num_flags); + } + num_flags = tmp_index + 1; + } + + uint16_t bytecode_length = 0; + if (!table->ReadU16(&bytecode_length)) { + return OTS_FAILURE_MSG("Can't read bytecode length"); + } + if ((font->maxp->version_1) && + (font->maxp->max_size_glyf_instructions < bytecode_length)) { + return OTS_FAILURE_MSG("Bytecode length too high %d", bytecode_length); + } + + const uint32_t gly_header_length = 10 + num_contours * 2 + 2; + if (gly_length < (gly_header_length + bytecode_length)) { + return OTS_FAILURE_MSG("Glyph header length too high %d", gly_header_length); + } + + glyf->iov.push_back(std::make_pair( + data + gly_offset, + static_cast<size_t>(gly_header_length + bytecode_length))); + + if (!table->Skip(bytecode_length)) { + return OTS_FAILURE_MSG("Can't skip bytecode of length %d", bytecode_length); + } + + 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) { + if (!ParseFlagsForSimpleGlyph(font, + table, + gly_length, + num_flags, + &flags_count_logical, + &flags_count_physical, + &xy_coordinates_length)) { + return OTS_FAILURE_MSG("Failed to parse glyph flags %d", flags_count_logical); + } + } + + if (gly_length < (gly_header_length + bytecode_length + + flags_count_physical + xy_coordinates_length)) { + return OTS_FAILURE_MSG("Glyph too short %d", gly_length); + } + + 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_MSG("Invalid glyph length %d", gly_length); + } + + glyf->iov.push_back(std::make_pair( + data + gly_offset + gly_header_length + bytecode_length, + static_cast<size_t>(flags_count_physical + xy_coordinates_length))); + + *new_size + = gly_header_length + flags_count_physical + xy_coordinates_length + bytecode_length; + + return true; +} + +} // namespace + +namespace ots { + +bool ots_glyf_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + + if (!font->maxp || !font->loca || !font->head) { + return OTS_FAILURE_MSG("Missing maxp or loca or head table needed by glyf table"); + } + + OpenTypeGLYF *glyf = new OpenTypeGLYF; + font->glyf = glyf; + + const unsigned num_glyphs = font->maxp->num_glyphs; + std::vector<uint32_t> &offsets = font->loca->offsets; + + if (offsets.size() != num_glyphs + 1) { + return OTS_FAILURE_MSG("Invalide glyph offsets size %ld != %d", offsets.size(), num_glyphs + 1); + } + + 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_MSG("Glyph %d offset %d too high %ld", i, gly_offset, length); + } + // 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_MSG("Glyph %d length (%d < 0)!", i, gly_length); + } + if (gly_offset + gly_length > length) { + return OTS_FAILURE_MSG("Glyph %d length %d too high", i, gly_length); + } + + 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_MSG("Can't read glyph %d header", i); + } + + if (num_contours <= -2) { + // -2, -3, -4, ... are reserved for future use. + return OTS_FAILURE_MSG("Bad number of contours %d in glyph %d", num_contours, i); + } + + // 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_MSG("Bad bounding box values bl=(%d, %d), tr=(%d, %d) in glyph %d", xmin, ymin, xmax, ymax, i); + } + + unsigned new_size = 0; + if (num_contours >= 0) { + // this is a simple glyph and might contain bytecode + if (!ParseSimpleGlyph(font, data, &table, + num_contours, gly_offset, gly_length, &new_size)) { + return OTS_FAILURE_MSG("Failed to parse glyph %d", i); + } + } else { + // it's a composite glyph without any bytecode. Enqueue the whole thing + glyf->iov.push_back(std::make_pair(data + gly_offset, + static_cast<size_t>(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"), + static_cast<size_t>(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)) && + (font->head->index_to_loc_format != 1)) { + OTS_WARNING("2-bytes indexing is not possible (due to the padding above)"); + font->head->index_to_loc_format = 1; + } + + font->loca->offsets = resulting_offsets; + return true; +} + +bool ots_glyf_should_serialise(Font *font) { + return font->glyf != NULL; +} + +bool ots_glyf_serialise(OTSStream *out, Font *font) { + const OpenTypeGLYF *glyf = font->glyf; + + for (unsigned i = 0; i < glyf->iov.size(); ++i) { + if (!out->Write(glyf->iov[i].first, glyf->iov[i].second)) { + return OTS_FAILURE_MSG("Falied to write glyph %d", i); + } + } + + return true; +} + +void ots_glyf_reuse(Font *font, Font *other) { + font->glyf = other->glyf; + font->glyf_reused = true; +} + +void ots_glyf_free(Font *font) { + delete font->glyf; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/glyf.h b/gfx/ots/src/glyf.h new file mode 100644 index 000000000..9a8baf5ec --- /dev/null +++ b/gfx/ots/src/glyf.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_GLYF_H_ +#define OTS_GLYF_H_ + +#include <new> +#include <utility> +#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/gfx/ots/src/gpos.cc b/gfx/ots/src/gpos.cc new file mode 100644 index 000000000..83d9ab053 --- /dev/null +++ b/gfx/ots/src/gpos.cc @@ -0,0 +1,817 @@ +// Copyright (c) 2011 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 "gpos.h" + +#include <limits> +#include <vector> + +#include "layout.h" +#include "maxp.h" + +// GPOS - The Glyph Positioning Table +// http://www.microsoft.com/typography/otspec/gpos.htm + +#define TABLE_NAME "GPOS" + +namespace { + +enum GPOS_TYPE { + GPOS_TYPE_SINGLE_ADJUSTMENT = 1, + GPOS_TYPE_PAIR_ADJUSTMENT = 2, + GPOS_TYPE_CURSIVE_ATTACHMENT = 3, + GPOS_TYPE_MARK_TO_BASE_ATTACHMENT = 4, + GPOS_TYPE_MARK_TO_LIGATURE_ATTACHMENT = 5, + GPOS_TYPE_MARK_TO_MARK_ATTACHMENT = 6, + GPOS_TYPE_CONTEXT_POSITIONING = 7, + GPOS_TYPE_CHAINED_CONTEXT_POSITIONING = 8, + GPOS_TYPE_EXTENSION_POSITIONING = 9, + GPOS_TYPE_RESERVED = 10 +}; + +// The size of gpos header. +const unsigned kGposHeaderSize = 10; +// The maximum format number for anchor tables. +const uint16_t kMaxAnchorFormat = 3; +// The maximum number of class value. +const uint16_t kMaxClassDefValue = 0xFFFF; + +// Lookup type parsers. +bool ParseSingleAdjustment(const ots::Font *font, + const uint8_t *data, const size_t length); +bool ParsePairAdjustment(const ots::Font *font, + const uint8_t *data, const size_t length); +bool ParseCursiveAttachment(const ots::Font *font, + const uint8_t *data, const size_t length); +bool ParseMarkToBaseAttachment(const ots::Font *font, + const uint8_t *data, const size_t length); +bool ParseMarkToLigatureAttachment(const ots::Font *font, + const uint8_t *data, const size_t length); +bool ParseMarkToMarkAttachment(const ots::Font *font, + const uint8_t *data, const size_t length); +bool ParseContextPositioning(const ots::Font *font, + const uint8_t *data, const size_t length); +bool ParseChainedContextPositioning(const ots::Font *font, + const uint8_t *data, const size_t length); +bool ParseExtensionPositioning(const ots::Font *font, + const uint8_t *data, const size_t length); + +const ots::LookupSubtableParser::TypeParser kGposTypeParsers[] = { + {GPOS_TYPE_SINGLE_ADJUSTMENT, ParseSingleAdjustment}, + {GPOS_TYPE_PAIR_ADJUSTMENT, ParsePairAdjustment}, + {GPOS_TYPE_CURSIVE_ATTACHMENT, ParseCursiveAttachment}, + {GPOS_TYPE_MARK_TO_BASE_ATTACHMENT, ParseMarkToBaseAttachment}, + {GPOS_TYPE_MARK_TO_LIGATURE_ATTACHMENT, ParseMarkToLigatureAttachment}, + {GPOS_TYPE_MARK_TO_MARK_ATTACHMENT, ParseMarkToMarkAttachment}, + {GPOS_TYPE_CONTEXT_POSITIONING, ParseContextPositioning}, + {GPOS_TYPE_CHAINED_CONTEXT_POSITIONING, ParseChainedContextPositioning}, + {GPOS_TYPE_EXTENSION_POSITIONING, ParseExtensionPositioning} +}; + +const ots::LookupSubtableParser kGposLookupSubtableParser = { + arraysize(kGposTypeParsers), + GPOS_TYPE_EXTENSION_POSITIONING, kGposTypeParsers +}; + +// Shared Tables: ValueRecord, Anchor Table, and MarkArray + +bool ParseValueRecord(const ots::Font *font, + ots::Buffer* subtable, const uint8_t *data, + const size_t length, const uint16_t value_format) { + // Check existence of adjustment fields. + for (unsigned i = 0; i < 4; ++i) { + if ((value_format >> i) & 0x1) { + // Just read the field since these fileds could take an arbitrary values. + if (!subtable->Skip(2)) { + return OTS_FAILURE_MSG("Failed to read value reacord component"); + } + } + } + + // Check existence of offsets to device table. + for (unsigned i = 0; i < 4; ++i) { + if ((value_format >> (i + 4)) & 0x1) { + uint16_t offset = 0; + if (!subtable->ReadU16(&offset)) { + return OTS_FAILURE_MSG("Failed to read value record offset"); + } + if (offset) { + // TODO(bashi): Is it possible that device tables locate before + // this record? No fonts contain such offset AKAIF. + if (offset >= length) { + return OTS_FAILURE_MSG("Value record offset too high %d >= %ld", offset, length); + } + if (!ots::ParseDeviceTable(font, data + offset, length - offset)) { + return OTS_FAILURE_MSG("Failed to parse device table in value record"); + } + } + } + } + return true; +} + +bool ParseAnchorTable(const ots::Font *font, + const uint8_t *data, const size_t length) { + ots::Buffer subtable(data, length); + + uint16_t format = 0; + // Read format and skip 2 2-byte fields that could be arbitrary values. + if (!subtable.ReadU16(&format) || + !subtable.Skip(4)) { + return OTS_FAILURE_MSG("Faled to read anchor table"); + } + + if (format == 0 || format > kMaxAnchorFormat) { + return OTS_FAILURE_MSG("Bad Anchor table format %d", format); + } + + // Format 2 and 3 has additional fields. + if (format == 2) { + // Format 2 provides an index to a glyph contour point, which will take + // arbitrary value. + uint16_t anchor_point = 0; + if (!subtable.ReadU16(&anchor_point)) { + return OTS_FAILURE_MSG("Failed to read anchor point in format 2 Anchor Table"); + } + } else if (format == 3) { + uint16_t offset_x_device = 0; + uint16_t offset_y_device = 0; + if (!subtable.ReadU16(&offset_x_device) || + !subtable.ReadU16(&offset_y_device)) { + return OTS_FAILURE_MSG("Failed to read device table offsets in format 3 anchor table"); + } + const unsigned format_end = static_cast<unsigned>(10); + if (offset_x_device) { + if (offset_x_device < format_end || offset_x_device >= length) { + return OTS_FAILURE_MSG("Bad x device table offset %d", offset_x_device); + } + if (!ots::ParseDeviceTable(font, data + offset_x_device, + length - offset_x_device)) { + return OTS_FAILURE_MSG("Failed to parse device table in anchor table"); + } + } + if (offset_y_device) { + if (offset_y_device < format_end || offset_y_device >= length) { + return OTS_FAILURE_MSG("Bad y device table offset %d", offset_y_device); + } + if (!ots::ParseDeviceTable(font, data + offset_y_device, + length - offset_y_device)) { + return OTS_FAILURE_MSG("Failed to parse device table in anchor table"); + } + } + } + return true; +} + +bool ParseMarkArrayTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t class_count) { + ots::Buffer subtable(data, length); + + uint16_t mark_count = 0; + if (!subtable.ReadU16(&mark_count)) { + return OTS_FAILURE_MSG("Can't read mark table length"); + } + + // MarkRecord consists of 4-bytes. + const unsigned mark_records_end = 4 * static_cast<unsigned>(mark_count) + 2; + if (mark_records_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad mark table length"); + } + for (unsigned i = 0; i < mark_count; ++i) { + uint16_t class_value = 0; + uint16_t offset_mark_anchor = 0; + if (!subtable.ReadU16(&class_value) || + !subtable.ReadU16(&offset_mark_anchor)) { + return OTS_FAILURE_MSG("Can't read mark table %d", i); + } + // |class_value| may take arbitrary values including 0 here so we don't + // check the value. + if (offset_mark_anchor < mark_records_end || + offset_mark_anchor >= length) { + return OTS_FAILURE_MSG("Bad mark anchor offset %d for mark table %d", offset_mark_anchor, i); + } + if (!ParseAnchorTable(font, data + offset_mark_anchor, + length - offset_mark_anchor)) { + return OTS_FAILURE_MSG("Faled to parse anchor table for mark table %d", i); + } + } + + return true; +} + +// Lookup Type 1: +// Single Adjustment Positioning Subtable +bool ParseSingleAdjustment(const ots::Font *font, const uint8_t *data, + const size_t length) { + ots::Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t offset_coverage = 0; + uint16_t value_format = 0; + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&value_format)) { + return OTS_FAILURE_MSG("Can't read single adjustment information"); + } + + if (format == 1) { + // Format 1 exactly one value record. + if (!ParseValueRecord(font, &subtable, data, length, value_format)) { + return OTS_FAILURE_MSG("Failed to parse format 1 single adjustment table"); + } + } else if (format == 2) { + uint16_t value_count = 0; + if (!subtable.ReadU16(&value_count)) { + return OTS_FAILURE_MSG("Failed to parse format 2 single adjustment table"); + } + for (unsigned i = 0; i < value_count; ++i) { + if (!ParseValueRecord(font, &subtable, data, length, value_format)) { + return OTS_FAILURE_MSG("Failed to parse value record %d in format 2 single adjustment table", i); + } + } + } else { + return OTS_FAILURE_MSG("Bad format %d in single adjustment table", format); + } + + if (offset_coverage < subtable.offset() || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %d in single adjustment table", offset_coverage); + } + + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, + font->maxp->num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table in single adjustment table"); + } + + return true; +} + +bool ParsePairSetTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t value_format1, + const uint16_t value_format2, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + uint16_t value_count = 0; + if (!subtable.ReadU16(&value_count)) { + return OTS_FAILURE_MSG("Failed to read pair set table structure"); + } + for (unsigned i = 0; i < value_count; ++i) { + // Check pair value record. + uint16_t glyph_id = 0; + if (!subtable.ReadU16(&glyph_id)) { + return OTS_FAILURE_MSG("Failed to read glyph in pair value record %d", i); + } + if (glyph_id >= num_glyphs) { + return OTS_FAILURE_MSG("glyph id %d too high >= %d", glyph_id, num_glyphs); + } + if (!ParseValueRecord(font, &subtable, data, length, value_format1)) { + return OTS_FAILURE_MSG("Failed to parse value record in format 1 pair set table"); + } + if (!ParseValueRecord(font, &subtable, data, length, value_format2)) { + return OTS_FAILURE_MSG("Failed to parse value record in format 2 pair set table"); + } + } + return true; +} + +bool ParsePairPosFormat1(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t value_format1, + const uint16_t value_format2, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // Skip 8 bytes that are already read before. + if (!subtable.Skip(8)) { + return OTS_FAILURE_MSG("Failed to read pair pos table structure"); + } + + uint16_t pair_set_count = 0; + if (!subtable.ReadU16(&pair_set_count)) { + return OTS_FAILURE_MSG("Failed to read pair pos set count"); + } + + const unsigned pair_pos_end = 2 * static_cast<unsigned>(pair_set_count) + 10; + if (pair_pos_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad pair set length %d", pair_pos_end); + } + for (unsigned i = 0; i < pair_set_count; ++i) { + uint16_t pair_set_offset = 0; + if (!subtable.ReadU16(&pair_set_offset)) { + return OTS_FAILURE_MSG("Failed to read pair set offset for pair set %d", i); + } + if (pair_set_offset < pair_pos_end || pair_set_offset >= length) { + return OTS_FAILURE_MSG("Bad pair set offset %d for pair set %d", pair_set_offset, i); + } + // Check pair set tables + if (!ParsePairSetTable(font, data + pair_set_offset, length - pair_set_offset, + value_format1, value_format2, + num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse pair set table %d", i); + } + } + + return true; +} + +bool ParsePairPosFormat2(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t value_format1, + const uint16_t value_format2, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // Skip 8 bytes that are already read before. + if (!subtable.Skip(8)) { + return OTS_FAILURE_MSG("Failed to read pair pos format 2 structure"); + } + + uint16_t offset_class_def1 = 0; + uint16_t offset_class_def2 = 0; + uint16_t class1_count = 0; + uint16_t class2_count = 0; + if (!subtable.ReadU16(&offset_class_def1) || + !subtable.ReadU16(&offset_class_def2) || + !subtable.ReadU16(&class1_count) || + !subtable.ReadU16(&class2_count)) { + return OTS_FAILURE_MSG("Failed to read pair pos format 2 data"); + } + + // Check class 1 records. + for (unsigned i = 0; i < class1_count; ++i) { + // Check class 2 records. + for (unsigned j = 0; j < class2_count; ++j) { + if (value_format1 && !ParseValueRecord(font, &subtable, data, length, + value_format1)) { + return OTS_FAILURE_MSG("Failed to parse value record 1 %d and %d", j, i); + } + if (value_format2 && !ParseValueRecord(font, &subtable, data, length, + value_format2)) { + return OTS_FAILURE_MSG("Falied to parse value record 2 %d and %d", j, i); + } + } + } + + // Check class definition tables. + if (offset_class_def1 < subtable.offset() || offset_class_def1 >= length || + offset_class_def2 < subtable.offset() || offset_class_def2 >= length) { + return OTS_FAILURE_MSG("Bad class definition table offsets %d or %d", offset_class_def1, offset_class_def2); + } + if (!ots::ParseClassDefTable(font, data + offset_class_def1, + length - offset_class_def1, + num_glyphs, kMaxClassDefValue)) { + return OTS_FAILURE_MSG("Failed to parse class definition table 1"); + } + if (!ots::ParseClassDefTable(font, data + offset_class_def2, + length - offset_class_def2, + num_glyphs, kMaxClassDefValue)) { + return OTS_FAILURE_MSG("Failed to parse class definition table 2"); + } + + return true; +} + +// Lookup Type 2: +// Pair Adjustment Positioning Subtable +bool ParsePairAdjustment(const ots::Font *font, const uint8_t *data, + const size_t length) { + ots::Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t offset_coverage = 0; + uint16_t value_format1 = 0; + uint16_t value_format2 = 0; + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&value_format1) || + !subtable.ReadU16(&value_format2)) { + return OTS_FAILURE_MSG("Failed to read pair adjustment structure"); + } + + if (format == 1) { + if (!ParsePairPosFormat1(font, data, length, value_format1, value_format2, + font->maxp->num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse pair pos format 1"); + } + } else if (format == 2) { + if (!ParsePairPosFormat2(font, data, length, value_format1, value_format2, + font->maxp->num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse pair format 2"); + } + } else { + return OTS_FAILURE_MSG("Bad pos pair format %d", format); + } + + if (offset_coverage < subtable.offset() || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad pair pos offset coverage %d", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, + font->maxp->num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table"); + } + + return true; +} + +// Lookup Type 3 +// Cursive Attachment Positioning Subtable +bool ParseCursiveAttachment(const ots::Font *font, const uint8_t *data, + const size_t length) { + ots::Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t offset_coverage = 0; + uint16_t entry_exit_count = 0; + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&entry_exit_count)) { + return OTS_FAILURE_MSG("Failed to read cursive attachment structure"); + } + + if (format != 1) { + return OTS_FAILURE_MSG("Bad cursive attachment format %d", format); + } + + // Check entry exit records. + const unsigned entry_exit_records_end = + 2 * static_cast<unsigned>(entry_exit_count) + 6; + if (entry_exit_records_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad entry exit record end %d", entry_exit_records_end); + } + for (unsigned i = 0; i < entry_exit_count; ++i) { + uint16_t offset_entry_anchor = 0; + uint16_t offset_exit_anchor = 0; + if (!subtable.ReadU16(&offset_entry_anchor) || + !subtable.ReadU16(&offset_exit_anchor)) { + return OTS_FAILURE_MSG("Can't read entry exit record %d", i); + } + // These offsets could be NULL. + if (offset_entry_anchor) { + if (offset_entry_anchor < entry_exit_records_end || + offset_entry_anchor >= length) { + return OTS_FAILURE_MSG("Bad entry anchor offset %d in entry exit record %d", offset_entry_anchor, i); + } + if (!ParseAnchorTable(font, data + offset_entry_anchor, + length - offset_entry_anchor)) { + return OTS_FAILURE_MSG("Failed to parse entry anchor table in entry exit record %d", i); + } + } + if (offset_exit_anchor) { + if (offset_exit_anchor < entry_exit_records_end || + offset_exit_anchor >= length) { + return OTS_FAILURE_MSG("Bad exit anchor offset %d in entry exit record %d", offset_exit_anchor, i); + } + if (!ParseAnchorTable(font, data + offset_exit_anchor, + length - offset_exit_anchor)) { + return OTS_FAILURE_MSG("Failed to parse exit anchor table in entry exit record %d", i); + } + } + } + + if (offset_coverage < subtable.offset() || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset in cursive attachment %d", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, + font->maxp->num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table in cursive attachment"); + } + + return true; +} + +bool ParseAnchorArrayTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t class_count) { + ots::Buffer subtable(data, length); + + uint16_t record_count = 0; + if (!subtable.ReadU16(&record_count)) { + return OTS_FAILURE_MSG("Can't read anchor array length"); + } + + const unsigned anchor_array_end = 2 * static_cast<unsigned>(record_count) * + static_cast<unsigned>(class_count) + 2; + if (anchor_array_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of anchor array %d", anchor_array_end); + } + for (unsigned i = 0; i < record_count; ++i) { + for (unsigned j = 0; j < class_count; ++j) { + uint16_t offset_record = 0; + if (!subtable.ReadU16(&offset_record)) { + return OTS_FAILURE_MSG("Can't read anchor array record offset for class %d and record %d", j, i); + } + // |offset_record| could be NULL. + if (offset_record) { + if (offset_record < anchor_array_end || offset_record >= length) { + return OTS_FAILURE_MSG("Bad record offset %d in class %d, record %d", offset_record, j, i); + } + if (!ParseAnchorTable(font, data + offset_record, + length - offset_record)) { + return OTS_FAILURE_MSG("Failed to parse anchor table for class %d, record %d", j, i); + } + } + } + } + return true; +} + +bool ParseLigatureArrayTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t class_count) { + ots::Buffer subtable(data, length); + + uint16_t ligature_count = 0; + if (!subtable.ReadU16(&ligature_count)) { + return OTS_FAILURE_MSG("Failed to read ligature count"); + } + for (unsigned i = 0; i < ligature_count; ++i) { + uint16_t offset_ligature_attach = 0; + if (!subtable.ReadU16(&offset_ligature_attach)) { + return OTS_FAILURE_MSG("Can't read ligature offset %d", i); + } + if (offset_ligature_attach < 2 || offset_ligature_attach >= length) { + return OTS_FAILURE_MSG("Bad ligature attachment offset %d in ligature %d", offset_ligature_attach, i); + } + if (!ParseAnchorArrayTable(font, data + offset_ligature_attach, + length - offset_ligature_attach, class_count)) { + return OTS_FAILURE_MSG("Failed to parse anchor table for ligature %d", i); + } + } + return true; +} + +// Common parser for Lookup Type 4, 5 and 6. +bool ParseMarkToAttachmentSubtables(const ots::Font *font, + const uint8_t *data, const size_t length, + const GPOS_TYPE type) { + ots::Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t offset_coverage1 = 0; + uint16_t offset_coverage2 = 0; + uint16_t class_count = 0; + uint16_t offset_mark_array = 0; + uint16_t offset_type_specific_array = 0; + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage1) || + !subtable.ReadU16(&offset_coverage2) || + !subtable.ReadU16(&class_count) || + !subtable.ReadU16(&offset_mark_array) || + !subtable.ReadU16(&offset_type_specific_array)) { + return OTS_FAILURE_MSG("Failed to read mark attachment subtable header"); + } + + if (format != 1) { + return OTS_FAILURE_MSG("bad mark attachment subtable format %d", format); + } + + const unsigned header_end = static_cast<unsigned>(subtable.offset()); + if (header_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad mark attachment subtable size ending at %d", header_end); + } + if (offset_coverage1 < header_end || offset_coverage1 >= length) { + return OTS_FAILURE_MSG("Bad coverage 1 offset %d", offset_coverage1); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage1, + length - offset_coverage1, + font->maxp->num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse converge 1 table"); + } + if (offset_coverage2 < header_end || offset_coverage2 >= length) { + return OTS_FAILURE_MSG("Bad coverage 2 offset %d", offset_coverage2); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage2, + length - offset_coverage2, + font->maxp->num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table 2"); + } + + if (offset_mark_array < header_end || offset_mark_array >= length) { + return OTS_FAILURE_MSG("Bad mark array offset %d", offset_mark_array); + } + if (!ParseMarkArrayTable(font, data + offset_mark_array, + length - offset_mark_array, class_count)) { + return OTS_FAILURE_MSG("Failed to parse mark array"); + } + + if (offset_type_specific_array < header_end || + offset_type_specific_array >= length) { + return OTS_FAILURE_MSG("Bad type specific array offset %d", offset_type_specific_array); + } + if (type == GPOS_TYPE_MARK_TO_BASE_ATTACHMENT || + type == GPOS_TYPE_MARK_TO_MARK_ATTACHMENT) { + if (!ParseAnchorArrayTable(font, data + offset_type_specific_array, + length - offset_type_specific_array, + class_count)) { + return OTS_FAILURE_MSG("Failed to parse anchor array"); + } + } else if (type == GPOS_TYPE_MARK_TO_LIGATURE_ATTACHMENT) { + if (!ParseLigatureArrayTable(font, data + offset_type_specific_array, + length - offset_type_specific_array, + class_count)) { + return OTS_FAILURE_MSG("Failed to parse ligature array"); + } + } else { + return OTS_FAILURE_MSG("Bad attachment type %d", type); + } + + return true; +} + +// Lookup Type 4: +// MarkToBase Attachment Positioning Subtable +bool ParseMarkToBaseAttachment(const ots::Font *font, + const uint8_t *data, const size_t length) { + return ParseMarkToAttachmentSubtables(font, data, length, + GPOS_TYPE_MARK_TO_BASE_ATTACHMENT); +} + +// Lookup Type 5: +// MarkToLigature Attachment Positioning Subtable +bool ParseMarkToLigatureAttachment(const ots::Font *font, + const uint8_t *data, const size_t length) { + return ParseMarkToAttachmentSubtables(font, data, length, + GPOS_TYPE_MARK_TO_LIGATURE_ATTACHMENT); +} + +// Lookup Type 6: +// MarkToMark Attachment Positioning Subtable +bool ParseMarkToMarkAttachment(const ots::Font *font, + const uint8_t *data, const size_t length) { + return ParseMarkToAttachmentSubtables(font, data, length, + GPOS_TYPE_MARK_TO_MARK_ATTACHMENT); +} + +// Lookup Type 7: +// Contextual Positioning Subtables +bool ParseContextPositioning(const ots::Font *font, + const uint8_t *data, const size_t length) { + return ots::ParseContextSubtable(font, data, length, font->maxp->num_glyphs, + font->gpos->num_lookups); +} + +// Lookup Type 8: +// Chaining Contexual Positioning Subtable +bool ParseChainedContextPositioning(const ots::Font *font, + const uint8_t *data, const size_t length) { + return ots::ParseChainingContextSubtable(font, data, length, + font->maxp->num_glyphs, + font->gpos->num_lookups); +} + +// Lookup Type 9: +// Extension Positioning +bool ParseExtensionPositioning(const ots::Font *font, + const uint8_t *data, const size_t length) { + return ots::ParseExtensionSubtable(font, data, length, + &kGposLookupSubtableParser); +} + +} // namespace + +namespace ots { + +// As far as I checked, following fonts contain invalid GPOS table and +// OTS will drop their GPOS table. +// +// # invalid delta format in device table +// samanata.ttf +// +// # bad size range in device table +// Sarai_07.ttf +// +// # bad offset to PairSetTable +// chandas1-2.ttf +// +// # bad offset to FeatureTable +// glrso12.ttf +// gllr12.ttf +// glbo12.ttf +// glb12.ttf +// glro12.ttf +// glbso12.ttf +// glrc12.ttf +// glrsc12.ttf +// glbs12.ttf +// glrs12.ttf +// glr12.ttf +// +// # ScriptRecords aren't sorted by tag +// Garogier_unhinted.otf +// +// # bad start coverage index in CoverageFormat2 +// AndBasR.ttf +// CharisSILB.ttf +// CharisSILBI.ttf +// CharisSILI.ttf +// CharisSILR.ttf +// DoulosSILR.ttf +// GenBasBI.ttf +// GenBasI.ttf +// GenBkBasI.ttf +// GenBkBasB.ttf +// GenBkBasR.ttf +// Padauk-Bold.ttf +// Padauk.ttf +// +// # Contour point indexes aren't sorted +// Arial Unicode.ttf + +bool ots_gpos_parse(Font *font, const uint8_t *data, size_t length) { + // Parsing GPOS table requires num_glyphs which is contained in maxp table. + if (!font->maxp) { + return OTS_FAILURE_MSG("missing maxp table needed in GPOS"); + } + + Buffer table(data, length); + + OpenTypeGPOS *gpos = new OpenTypeGPOS; + font->gpos = gpos; + + uint32_t version = 0; + uint16_t offset_script_list = 0; + uint16_t offset_feature_list = 0; + uint16_t offset_lookup_list = 0; + if (!table.ReadU32(&version) || + !table.ReadU16(&offset_script_list) || + !table.ReadU16(&offset_feature_list) || + !table.ReadU16(&offset_lookup_list)) { + return OTS_FAILURE_MSG("Incomplete table"); + } + + if (version != 0x00010000) { + return OTS_FAILURE_MSG("Bad version"); + } + + if (offset_lookup_list) { + if (offset_lookup_list < kGposHeaderSize || offset_lookup_list >= length) { + return OTS_FAILURE_MSG("Bad lookup list offset in table header"); + } + + if (!ParseLookupListTable(font, data + offset_lookup_list, + length - offset_lookup_list, + &kGposLookupSubtableParser, + &gpos->num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse lookup list table"); + } + } + + uint16_t num_features = 0; + if (offset_feature_list) { + if (offset_feature_list < kGposHeaderSize || offset_feature_list >= length) { + return OTS_FAILURE_MSG("Bad feature list offset in table header"); + } + + if (!ParseFeatureListTable(font, data + offset_feature_list, + length - offset_feature_list, gpos->num_lookups, + &num_features)) { + return OTS_FAILURE_MSG("Failed to parse feature list table"); + } + } + + if (offset_script_list) { + if (offset_script_list < kGposHeaderSize || offset_script_list >= length) { + return OTS_FAILURE_MSG("Bad script list offset in table header"); + } + + if (!ParseScriptListTable(font, data + offset_script_list, + length - offset_script_list, num_features)) { + return OTS_FAILURE_MSG("Failed to parse script list table"); + } + } + + gpos->data = data; + gpos->length = length; + return true; +} + +bool ots_gpos_should_serialise(Font *font) { + return font->gpos != NULL && font->gpos->data != NULL; +} + +bool ots_gpos_serialise(OTSStream *out, Font *font) { + if (!out->Write(font->gpos->data, font->gpos->length)) { + return OTS_FAILURE_MSG("Failed to write GPOS table"); + } + + return true; +} + +void ots_gpos_reuse(Font *font, Font *other) { + font->gpos = other->gpos; + font->gpos_reused = true; +} + +void ots_gpos_free(Font *font) { + delete font->gpos; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/gpos.h b/gfx/ots/src/gpos.h new file mode 100644 index 000000000..3a4034f6f --- /dev/null +++ b/gfx/ots/src/gpos.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 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_GPOS_H_ +#define OTS_GPOS_H_ + +#include "ots.h" + +namespace ots { + +struct OpenTypeGPOS { + OpenTypeGPOS() + : num_lookups(0), + data(NULL), + length(0) { + } + + // Number of lookups in GPOS table + uint16_t num_lookups; + + const uint8_t *data; + size_t length; +}; + +} // namespace ots + +#endif + diff --git a/gfx/ots/src/gsub.cc b/gfx/ots/src/gsub.cc new file mode 100644 index 000000000..9baf2e88b --- /dev/null +++ b/gfx/ots/src/gsub.cc @@ -0,0 +1,674 @@ +// Copyright (c) 2011 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 "gsub.h" + +#include <limits> +#include <vector> + +#include "layout.h" +#include "maxp.h" + +// GSUB - The Glyph Substitution Table +// http://www.microsoft.com/typography/otspec/gsub.htm + +#define TABLE_NAME "GSUB" + +namespace { + +// The GSUB header size +const size_t kGsubHeaderSize = 4 + 3 * 2; + +enum GSUB_TYPE { + GSUB_TYPE_SINGLE = 1, + GSUB_TYPE_MULTIPLE = 2, + GSUB_TYPE_ALTERNATE = 3, + GSUB_TYPE_LIGATURE = 4, + GSUB_TYPE_CONTEXT = 5, + GSUB_TYPE_CHANGING_CONTEXT = 6, + GSUB_TYPE_EXTENSION_SUBSTITUTION = 7, + GSUB_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE = 8, + GSUB_TYPE_RESERVED = 9 +}; + +// Lookup type parsers. +bool ParseSingleSubstitution(const ots::Font *font, + const uint8_t *data, const size_t length); +bool ParseMutipleSubstitution(const ots::Font *font, + const uint8_t *data, const size_t length); +bool ParseAlternateSubstitution(const ots::Font *font, + const uint8_t *data, const size_t length); +bool ParseLigatureSubstitution(const ots::Font *font, + const uint8_t *data, const size_t length); +bool ParseContextSubstitution(const ots::Font *font, + const uint8_t *data, const size_t length); +bool ParseChainingContextSubstitution(const ots::Font *font, + const uint8_t *data, + const size_t length); +bool ParseExtensionSubstitution(const ots::Font *font, + const uint8_t *data, const size_t length); +bool ParseReverseChainingContextSingleSubstitution( + const ots::Font *font, const uint8_t *data, const size_t length); + +const ots::LookupSubtableParser::TypeParser kGsubTypeParsers[] = { + {GSUB_TYPE_SINGLE, ParseSingleSubstitution}, + {GSUB_TYPE_MULTIPLE, ParseMutipleSubstitution}, + {GSUB_TYPE_ALTERNATE, ParseAlternateSubstitution}, + {GSUB_TYPE_LIGATURE, ParseLigatureSubstitution}, + {GSUB_TYPE_CONTEXT, ParseContextSubstitution}, + {GSUB_TYPE_CHANGING_CONTEXT, ParseChainingContextSubstitution}, + {GSUB_TYPE_EXTENSION_SUBSTITUTION, ParseExtensionSubstitution}, + {GSUB_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE, + ParseReverseChainingContextSingleSubstitution} +}; + +const ots::LookupSubtableParser kGsubLookupSubtableParser = { + arraysize(kGsubTypeParsers), + GSUB_TYPE_EXTENSION_SUBSTITUTION, kGsubTypeParsers +}; + +// Lookup Type 1: +// Single Substitution Subtable +bool ParseSingleSubstitution(const ots::Font *font, + const uint8_t *data, const size_t length) { + ots::Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t offset_coverage = 0; + + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage)) { + return OTS_FAILURE_MSG("Failed to read single subst table header"); + } + + const uint16_t num_glyphs = font->maxp->num_glyphs; + if (format == 1) { + // Parse SingleSubstFormat1 + int16_t delta_glyph_id = 0; + if (!subtable.ReadS16(&delta_glyph_id)) { + return OTS_FAILURE_MSG("Failed to read glyph shift from format 1 single subst table"); + } + if (std::abs(delta_glyph_id) >= num_glyphs) { + return OTS_FAILURE_MSG("bad glyph shift of %d in format 1 single subst table", delta_glyph_id); + } + } else if (format == 2) { + // Parse SingleSubstFormat2 + uint16_t glyph_count = 0; + if (!subtable.ReadU16(&glyph_count)) { + return OTS_FAILURE_MSG("Failed to read glyph cound in format 2 single subst table"); + } + if (glyph_count > num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph count %d > %d in format 2 single subst table", glyph_count, num_glyphs); + } + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t substitute = 0; + if (!subtable.ReadU16(&substitute)) { + return OTS_FAILURE_MSG("Failed to read substitution %d in format 2 single subst table", i); + } + if (substitute >= num_glyphs) { + return OTS_FAILURE_MSG("too large substitute: %u", substitute); + } + } + } else { + return OTS_FAILURE_MSG("Bad single subst table format %d", format); + } + + if (offset_coverage < subtable.offset() || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %x", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table"); + } + + return true; +} + +bool ParseSequenceTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + uint16_t glyph_count = 0; + if (!subtable.ReadU16(&glyph_count)) { + return OTS_FAILURE_MSG("Failed to read glyph count in sequence table"); + } + if (glyph_count > num_glyphs) { + return OTS_FAILURE_MSG("bad glyph count %d > %d", glyph_count, num_glyphs); + } + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t substitute = 0; + if (!subtable.ReadU16(&substitute)) { + return OTS_FAILURE_MSG("Failedt o read substitution %d in sequence table", i); + } + if (substitute >= num_glyphs) { + return OTS_FAILURE_MSG("Bad subsitution (%d) %d > %d", i, substitute, num_glyphs); + } + } + + return true; +} + +// Lookup Type 2: +// Multiple Substitution Subtable +bool ParseMutipleSubstitution(const ots::Font *font, + const uint8_t *data, const size_t length) { + ots::Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t offset_coverage = 0; + uint16_t sequence_count = 0; + + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&sequence_count)) { + return OTS_FAILURE_MSG("Can't read header of multiple subst table"); + } + + if (format != 1) { + return OTS_FAILURE_MSG("Bad multiple subst table format %d", format); + } + + const uint16_t num_glyphs = font->maxp->num_glyphs; + const unsigned sequence_end = static_cast<unsigned>(6) + + sequence_count * 2; + if (sequence_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad segence end %d, in multiple subst", sequence_end); + } + for (unsigned i = 0; i < sequence_count; ++i) { + uint16_t offset_sequence = 0; + if (!subtable.ReadU16(&offset_sequence)) { + return OTS_FAILURE_MSG("Failed to read sequence offset for sequence %d", i); + } + if (offset_sequence < sequence_end || offset_sequence >= length) { + return OTS_FAILURE_MSG("Bad sequence offset %d for sequence %d", offset_sequence, i); + } + if (!ParseSequenceTable(font, data + offset_sequence, length - offset_sequence, + num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse sequence table %d", i); + } + } + + if (offset_coverage < sequence_end || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %d", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table"); + } + + return true; +} + +bool ParseAlternateSetTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + uint16_t glyph_count = 0; + if (!subtable.ReadU16(&glyph_count)) { + return OTS_FAILURE_MSG("Failed to read alternate set header"); + } + if (glyph_count > num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph count %d > %d in alternate set table", glyph_count, num_glyphs); + } + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t alternate = 0; + if (!subtable.ReadU16(&alternate)) { + return OTS_FAILURE_MSG("Can't read alternate %d", i); + } + if (alternate >= num_glyphs) { + return OTS_FAILURE_MSG("Too large alternate: %u", alternate); + } + } + return true; +} + +// Lookup Type 3: +// Alternate Substitution Subtable +bool ParseAlternateSubstitution(const ots::Font *font, + const uint8_t *data, const size_t length) { + ots::Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t offset_coverage = 0; + uint16_t alternate_set_count = 0; + + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&alternate_set_count)) { + return OTS_FAILURE_MSG("Can't read alternate subst header"); + } + + if (format != 1) { + return OTS_FAILURE_MSG("Bad alternate subst table format %d", format); + } + + const uint16_t num_glyphs = font->maxp->num_glyphs; + const unsigned alternate_set_end = static_cast<unsigned>(6) + + alternate_set_count * 2; + if (alternate_set_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of alternate set %d", alternate_set_end); + } + for (unsigned i = 0; i < alternate_set_count; ++i) { + uint16_t offset_alternate_set = 0; + if (!subtable.ReadU16(&offset_alternate_set)) { + return OTS_FAILURE_MSG("Can't read alternate set offset for set %d", i); + } + if (offset_alternate_set < alternate_set_end || + offset_alternate_set >= length) { + return OTS_FAILURE_MSG("Bad alternate set offset %d for set %d", offset_alternate_set, i); + } + if (!ParseAlternateSetTable(font, data + offset_alternate_set, + length - offset_alternate_set, + num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse alternate set"); + } + } + + if (offset_coverage < alternate_set_end || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %d", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table"); + } + + return true; +} + +bool ParseLigatureTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + uint16_t lig_glyph = 0; + uint16_t comp_count = 0; + + if (!subtable.ReadU16(&lig_glyph) || + !subtable.ReadU16(&comp_count)) { + return OTS_FAILURE_MSG("Failed to read ligatuer table header"); + } + + if (lig_glyph >= num_glyphs) { + return OTS_FAILURE_MSG("too large lig_glyph: %u", lig_glyph); + } + if (comp_count == 0 || comp_count > num_glyphs) { + return OTS_FAILURE_MSG("Bad component count of %d", comp_count); + } + for (unsigned i = 0; i < comp_count - static_cast<unsigned>(1); ++i) { + uint16_t component = 0; + if (!subtable.ReadU16(&component)) { + return OTS_FAILURE_MSG("Can't read ligature component %d", i); + } + if (component >= num_glyphs) { + return OTS_FAILURE_MSG("Bad ligature component %d of %d", i, component); + } + } + + return true; +} + +bool ParseLigatureSetTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + uint16_t ligature_count = 0; + + if (!subtable.ReadU16(&ligature_count)) { + return OTS_FAILURE_MSG("Can't read ligature count in ligature set"); + } + + const unsigned ligature_end = static_cast<unsigned>(2) + ligature_count * 2; + if (ligature_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of ligature %d in ligature set", ligature_end); + } + for (unsigned i = 0; i < ligature_count; ++i) { + uint16_t offset_ligature = 0; + if (!subtable.ReadU16(&offset_ligature)) { + return OTS_FAILURE_MSG("Failed to read ligature offset %d", i); + } + if (offset_ligature < ligature_end || offset_ligature >= length) { + return OTS_FAILURE_MSG("Bad ligature offset %d for ligature %d", offset_ligature, i); + } + if (!ParseLigatureTable(font, data + offset_ligature, length - offset_ligature, + num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse ligature %d", i); + } + } + + return true; +} + +// Lookup Type 4: +// Ligature Substitution Subtable +bool ParseLigatureSubstitution(const ots::Font *font, + const uint8_t *data, const size_t length) { + ots::Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t offset_coverage = 0; + uint16_t lig_set_count = 0; + + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&lig_set_count)) { + return OTS_FAILURE_MSG("Failed to read ligature substitution header"); + } + + if (format != 1) { + return OTS_FAILURE_MSG("Bad ligature substitution table format %d", format); + } + + const uint16_t num_glyphs = font->maxp->num_glyphs; + const unsigned ligature_set_end = static_cast<unsigned>(6) + + lig_set_count * 2; + if (ligature_set_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of ligature set %d in ligature substitution table", ligature_set_end); + } + for (unsigned i = 0; i < lig_set_count; ++i) { + uint16_t offset_ligature_set = 0; + if (!subtable.ReadU16(&offset_ligature_set)) { + return OTS_FAILURE_MSG("Can't read ligature set offset %d", i); + } + if (offset_ligature_set < ligature_set_end || + offset_ligature_set >= length) { + return OTS_FAILURE_MSG("Bad ligature set offset %d for set %d", offset_ligature_set, i); + } + if (!ParseLigatureSetTable(font, data + offset_ligature_set, + length - offset_ligature_set, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse ligature set %d", i); + } + } + + if (offset_coverage < ligature_set_end || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %d", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table"); + } + + return true; +} + +// Lookup Type 5: +// Contextual Substitution Subtable +bool ParseContextSubstitution(const ots::Font *font, + const uint8_t *data, const size_t length) { + return ots::ParseContextSubtable(font, data, length, font->maxp->num_glyphs, + font->gsub->num_lookups); +} + +// Lookup Type 6: +// Chaining Contextual Substitution Subtable +bool ParseChainingContextSubstitution(const ots::Font *font, + const uint8_t *data, + const size_t length) { + return ots::ParseChainingContextSubtable(font, data, length, + font->maxp->num_glyphs, + font->gsub->num_lookups); +} + +// Lookup Type 7: +// Extension Substition +bool ParseExtensionSubstitution(const ots::Font *font, + const uint8_t *data, const size_t length) { + return ots::ParseExtensionSubtable(font, data, length, + &kGsubLookupSubtableParser); +} + +// Lookup Type 8: +// Reverse Chaining Contexual Single Substitution Subtable +bool ParseReverseChainingContextSingleSubstitution( + const ots::Font *font, const uint8_t *data, const size_t length) { + ots::Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t offset_coverage = 0; + + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&offset_coverage)) { + return OTS_FAILURE_MSG("Failed to read reverse chaining header"); + } + + const uint16_t num_glyphs = font->maxp->num_glyphs; + + uint16_t backtrack_glyph_count = 0; + if (!subtable.ReadU16(&backtrack_glyph_count)) { + return OTS_FAILURE_MSG("Failed to read backtrack glyph count in reverse chaining table"); + } + if (backtrack_glyph_count > num_glyphs) { + return OTS_FAILURE_MSG("Bad backtrack glyph count of %d", backtrack_glyph_count); + } + std::vector<uint16_t> offsets_backtrack; + offsets_backtrack.reserve(backtrack_glyph_count); + for (unsigned i = 0; i < backtrack_glyph_count; ++i) { + uint16_t offset = 0; + if (!subtable.ReadU16(&offset)) { + return OTS_FAILURE_MSG("Failed to read backtrack offset %d", i); + } + offsets_backtrack.push_back(offset); + } + + uint16_t lookahead_glyph_count = 0; + if (!subtable.ReadU16(&lookahead_glyph_count)) { + return OTS_FAILURE_MSG("Failed to read look ahead glyph count"); + } + if (lookahead_glyph_count > num_glyphs) { + return OTS_FAILURE_MSG("Bad look ahead glyph count %d", lookahead_glyph_count); + } + std::vector<uint16_t> offsets_lookahead; + offsets_lookahead.reserve(lookahead_glyph_count); + for (unsigned i = 0; i < lookahead_glyph_count; ++i) { + uint16_t offset = 0; + if (!subtable.ReadU16(&offset)) { + return OTS_FAILURE_MSG("Can't read look ahead offset %d", i); + } + offsets_lookahead.push_back(offset); + } + + uint16_t glyph_count = 0; + if (!subtable.ReadU16(&glyph_count)) { + return OTS_FAILURE_MSG("Can't read glyph count in reverse chaining table"); + } + if (glyph_count > num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph count of %d", glyph_count); + } + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t substitute = 0; + if (!subtable.ReadU16(&substitute)) { + return OTS_FAILURE_MSG("Failed to read substitution %d reverse chaining table", i); + } + if (substitute >= num_glyphs) { + return OTS_FAILURE_MSG("Bad substitute glyph %d in reverse chaining table substitution %d", substitute, i); + } + } + + const unsigned substitute_end = static_cast<unsigned>(10) + + (backtrack_glyph_count + lookahead_glyph_count + glyph_count) * 2; + if (substitute_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad substitute end offset in reverse chaining table"); + } + + if (offset_coverage < substitute_end || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %d in reverse chaining table", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table in reverse chaining table"); + } + + for (unsigned i = 0; i < backtrack_glyph_count; ++i) { + if (offsets_backtrack[i] < substitute_end || + offsets_backtrack[i] >= length) { + return OTS_FAILURE_MSG("Bad backtrack offset %d for backtrack %d in reverse chaining table", offsets_backtrack[i], i); + } + if (!ots::ParseCoverageTable(font, data + offsets_backtrack[i], + length - offsets_backtrack[i], num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table for backtrack %d in reverse chaining table", i); + } + } + + for (unsigned i = 0; i < lookahead_glyph_count; ++i) { + if (offsets_lookahead[i] < substitute_end || + offsets_lookahead[i] >= length) { + return OTS_FAILURE_MSG("Bad lookahead offset %d for lookahead %d in reverse chaining table", offsets_lookahead[i], i); + } + if (!ots::ParseCoverageTable(font, data + offsets_lookahead[i], + length - offsets_lookahead[i], num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse lookahead coverage table %d in reverse chaining table", i); + } + } + + return true; +} + +} // namespace + +namespace ots { + +// As far as I checked, following fonts contain invalid values in GSUB table. +// OTS will drop their GSUB table. +// +// # too large substitute (value is 0xFFFF) +// kaiu.ttf +// mingliub2.ttf +// mingliub1.ttf +// mingliub0.ttf +// GraublauWeb.otf +// GraublauWebBold.otf +// +// # too large alternate (value is 0xFFFF) +// ManchuFont.ttf +// +// # bad offset to lang sys table (NULL offset) +// DejaVuMonoSansBold.ttf +// DejaVuMonoSansBoldOblique.ttf +// DejaVuMonoSansOblique.ttf +// DejaVuSansMono-BoldOblique.ttf +// DejaVuSansMono-Oblique.ttf +// DejaVuSansMono-Bold.ttf +// +// # bad start coverage index +// GenBasBI.ttf +// GenBasI.ttf +// AndBasR.ttf +// GenBkBasI.ttf +// CharisSILR.ttf +// CharisSILBI.ttf +// CharisSILI.ttf +// CharisSILB.ttf +// DoulosSILR.ttf +// CharisSILBI.ttf +// GenBkBasB.ttf +// GenBkBasR.ttf +// GenBkBasBI.ttf +// GenBasB.ttf +// GenBasR.ttf +// +// # glyph range is overlapping +// KacstTitleL.ttf +// KacstDecorative.ttf +// KacstTitle.ttf +// KacstArt.ttf +// KacstPoster.ttf +// KacstQurn.ttf +// KacstDigital.ttf +// KacstBook.ttf +// KacstFarsi.ttf + +bool ots_gsub_parse(Font *font, const uint8_t *data, size_t length) { + // Parsing gsub table requires |font->maxp->num_glyphs| + if (!font->maxp) { + return OTS_FAILURE_MSG("Missing maxp table in font, needed by GSUB"); + } + + Buffer table(data, length); + + OpenTypeGSUB *gsub = new OpenTypeGSUB; + font->gsub = gsub; + + uint32_t version = 0; + uint16_t offset_script_list = 0; + uint16_t offset_feature_list = 0; + uint16_t offset_lookup_list = 0; + if (!table.ReadU32(&version) || + !table.ReadU16(&offset_script_list) || + !table.ReadU16(&offset_feature_list) || + !table.ReadU16(&offset_lookup_list)) { + return OTS_FAILURE_MSG("Incomplete table"); + } + + if (version != 0x00010000) { + return OTS_FAILURE_MSG("Bad version"); + } + + if (offset_lookup_list) { + if (offset_lookup_list < kGsubHeaderSize || offset_lookup_list >= length) { + return OTS_FAILURE_MSG("Bad lookup list offset in table header"); + } + + if (!ParseLookupListTable(font, data + offset_lookup_list, + length - offset_lookup_list, + &kGsubLookupSubtableParser, + &gsub->num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse lookup list table"); + } + } + + uint16_t num_features = 0; + if (offset_feature_list) { + if (offset_feature_list < kGsubHeaderSize || offset_feature_list >= length) { + return OTS_FAILURE_MSG("Bad feature list offset in table header"); + } + + if (!ParseFeatureListTable(font, data + offset_feature_list, + length - offset_feature_list, gsub->num_lookups, + &num_features)) { + return OTS_FAILURE_MSG("Failed to parse feature list table"); + } + } + + if (offset_script_list) { + if (offset_script_list < kGsubHeaderSize || offset_script_list >= length) { + return OTS_FAILURE_MSG("Bad script list offset in table header"); + } + + if (!ParseScriptListTable(font, data + offset_script_list, + length - offset_script_list, num_features)) { + return OTS_FAILURE_MSG("Failed to parse script list table"); + } + } + + gsub->data = data; + gsub->length = length; + return true; +} + +bool ots_gsub_should_serialise(Font *font) { + return font->gsub != NULL && font->gsub->data != NULL; +} + +bool ots_gsub_serialise(OTSStream *out, Font *font) { + if (!out->Write(font->gsub->data, font->gsub->length)) { + return OTS_FAILURE_MSG("Failed to write GSUB table"); + } + + return true; +} + +void ots_gsub_reuse(Font *font, Font *other) { + font->gsub = other->gsub; + font->gsub_reused = true; +} + +void ots_gsub_free(Font *font) { + delete font->gsub; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/gsub.h b/gfx/ots/src/gsub.h new file mode 100644 index 000000000..f6f8cd3b1 --- /dev/null +++ b/gfx/ots/src/gsub.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 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_GSUB_H_ +#define OTS_GSUB_H_ + +#include "ots.h" + +namespace ots { + +struct OpenTypeGSUB { + OpenTypeGSUB() + : num_lookups(0), + data(NULL), + length(0) { + } + + // Number of lookups in GPSUB table + uint16_t num_lookups; + + const uint8_t *data; + size_t length; +}; + +} // namespace ots + +#endif // OTS_GSUB_H_ + diff --git a/gfx/ots/src/hdmx.cc b/gfx/ots/src/hdmx.cc new file mode 100644 index 000000000..f57b71f59 --- /dev/null +++ b/gfx/ots/src/hdmx.cc @@ -0,0 +1,147 @@ +// 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/typography/otspec/hdmx.htm + +#define TABLE_NAME "hdmx" + +#define DROP_THIS_TABLE(...) \ + do { \ + OTS_FAILURE_MSG_(font->file, TABLE_NAME ": " __VA_ARGS__); \ + OTS_FAILURE_MSG("Table discarded"); \ + delete font->hdmx; \ + font->hdmx = 0; \ + } while (0) + +namespace ots { + +bool ots_hdmx_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + font->hdmx = new OpenTypeHDMX; + OpenTypeHDMX * const hdmx = font->hdmx; + + if (!font->head || !font->maxp) { + return OTS_FAILURE_MSG("Missing maxp or head tables in font, needed by hdmx"); + } + + if ((font->head->flags & 0x14) == 0) { + // http://www.microsoft.com/typography/otspec/recom.htm + DROP_THIS_TABLE("the table should not be present when bit 2 and 4 of the " + "head->flags are not set"); + return true; + } + + int16_t num_recs; + if (!table.ReadU16(&hdmx->version) || + !table.ReadS16(&num_recs) || + !table.ReadS32(&hdmx->size_device_record)) { + return OTS_FAILURE_MSG("Failed to read hdmx header"); + } + if (hdmx->version != 0) { + DROP_THIS_TABLE("bad version: %u", hdmx->version); + return true; + } + if (num_recs <= 0) { + DROP_THIS_TABLE("bad num_recs: %d", num_recs); + return true; + } + const int32_t actual_size_device_record = font->maxp->num_glyphs + 2; + if (hdmx->size_device_record < actual_size_device_record) { + DROP_THIS_TABLE("bad hdmx->size_device_record: %d", hdmx->size_device_record); + return true; + } + + hdmx->pad_len = hdmx->size_device_record - actual_size_device_record; + if (hdmx->pad_len > 3) { + return OTS_FAILURE_MSG("Bad padding %d", hdmx->pad_len); + } + + 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_MSG("Failed to read hdmx record %d", i); + } + if ((i != 0) && + (rec.pixel_size <= last_pixel_size)) { + DROP_THIS_TABLE("records are not sorted"); + return true; + } + last_pixel_size = rec.pixel_size; + + rec.widths.reserve(font->maxp->num_glyphs); + for (unsigned j = 0; j < font->maxp->num_glyphs; ++j) { + uint8_t width; + if (!table.ReadU8(&width)) { + return OTS_FAILURE_MSG("Failed to read glyph width %d in record %d", j, i); + } + rec.widths.push_back(width); + } + + if ((hdmx->pad_len > 0) && + !table.Skip(hdmx->pad_len)) { + return OTS_FAILURE_MSG("Failed to skip padding %d", hdmx->pad_len); + } + + hdmx->records.push_back(rec); + } + + return true; +} + +bool ots_hdmx_should_serialise(Font *font) { + if (!font->hdmx) return false; + if (!font->glyf) return false; // this table is not for CFF fonts. + return true; +} + +bool ots_hdmx_serialise(OTSStream *out, Font *font) { + OpenTypeHDMX * const hdmx = font->hdmx; + + const int16_t num_recs = static_cast<int16_t>(hdmx->records.size()); + if (hdmx->records.size() > + static_cast<size_t>(std::numeric_limits<int16_t>::max()) || + !out->WriteU16(hdmx->version) || + !out->WriteS16(num_recs) || + !out->WriteS32(hdmx->size_device_record)) { + return OTS_FAILURE_MSG("Failed to write hdmx header"); + } + + for (int16_t i = 0; i < num_recs; ++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_MSG("Failed to write hdmx record %d", i); + } + if ((hdmx->pad_len > 0) && + !out->Write((const uint8_t *)"\x00\x00\x00", hdmx->pad_len)) { + return OTS_FAILURE_MSG("Failed to write hdmx padding of length %d", hdmx->pad_len); + } + } + + return true; +} + +void ots_hdmx_reuse(Font *font, Font *other) { + font->hdmx = other->hdmx; + font->hdmx_reused = true; +} + +void ots_hdmx_free(Font *font) { + delete font->hdmx; +} + +} // namespace ots + +#undef TABLE_NAME +#undef DROP_THIS_TABLE diff --git a/gfx/ots/src/hdmx.h b/gfx/ots/src/hdmx.h new file mode 100644 index 000000000..9ec212437 --- /dev/null +++ b/gfx/ots/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/gfx/ots/src/head.cc b/gfx/ots/src/head.cc new file mode 100644 index 000000000..229ea71f9 --- /dev/null +++ b/gfx/ots/src/head.cc @@ -0,0 +1,159 @@ +// 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/typography/otspec/head.htm + +#define TABLE_NAME "head" + +namespace ots { + +bool ots_head_parse(Font* font, const uint8_t *data, size_t length) { + Buffer table(data, length); + OpenTypeHEAD *head = new OpenTypeHEAD; + font->head = head; + + uint32_t version = 0; + if (!table.ReadU32(&version) || + !table.ReadU32(&head->revision)) { + return OTS_FAILURE_MSG("Failed to read head header"); + } + + if (version >> 16 != 1) { + return OTS_FAILURE_MSG("Bad head table version of %d", version); + } + + // Skip the checksum adjustment + if (!table.Skip(4)) { + return OTS_FAILURE_MSG("Failed to read checksum"); + } + + uint32_t magic; + if (!table.ReadU32(&magic) || magic != 0x5F0F3CF5) { + return OTS_FAILURE_MSG("Failed to read font magic number"); + } + + if (!table.ReadU16(&head->flags)) { + return OTS_FAILURE_MSG("Failed to read head flags"); + } + + // We allow bits 0..4, 11..13 + head->flags &= 0x381f; + + if (!table.ReadU16(&head->ppem)) { + return OTS_FAILURE_MSG("Failed to read pixels per em"); + } + + // ppem must be in range + if (head->ppem < 16 || + head->ppem > 16384) { + return OTS_FAILURE_MSG("Bad ppm of %d", head->ppem); + } + + // ppem must be a power of two +#if 0 + // 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 ((head->ppem - 1) & head->ppem) { + return OTS_FAILURE_MSG("ppm not a power of two: %d", head->ppem); + } +#endif + + if (!table.ReadR64(&head->created) || + !table.ReadR64(&head->modified)) { + return OTS_FAILURE_MSG("Can't read font dates"); + } + + if (!table.ReadS16(&head->xmin) || + !table.ReadS16(&head->ymin) || + !table.ReadS16(&head->xmax) || + !table.ReadS16(&head->ymax)) { + return OTS_FAILURE_MSG("Failed to read font bounding box"); + } + + if (head->xmin > head->xmax) { + return OTS_FAILURE_MSG("Bad x dimension in the font bounding box (%d, %d)", head->xmin, head->xmax); + } + if (head->ymin > head->ymax) { + return OTS_FAILURE_MSG("Bad y dimension in the font bounding box (%d, %d)", head->ymin, head->ymax); + } + + if (!table.ReadU16(&head->mac_style)) { + return OTS_FAILURE_MSG("Failed to read font style"); + } + + // We allow bits 0..6 + head->mac_style &= 0x7f; + + if (!table.ReadU16(&head->min_ppem)) { + return OTS_FAILURE_MSG("Failed to read font minimum ppm"); + } + + // We don't care about the font direction hint + if (!table.Skip(2)) { + return OTS_FAILURE_MSG("Failed to skip font direction hint"); + } + + if (!table.ReadS16(&head->index_to_loc_format)) { + return OTS_FAILURE_MSG("Failed to read index to loc format"); + } + if (head->index_to_loc_format < 0 || + head->index_to_loc_format > 1) { + return OTS_FAILURE_MSG("Bad index to loc format %d", head->index_to_loc_format); + } + + int16_t glyph_data_format; + if (!table.ReadS16(&glyph_data_format) || + glyph_data_format) { + return OTS_FAILURE_MSG("Failed to read glyph data format"); + } + + return true; +} + +bool ots_head_should_serialise(Font *font) { + return font->head != NULL; +} + +bool ots_head_serialise(OTSStream *out, Font *font) { + const OpenTypeHEAD *head = font->head; + if (!out->WriteU32(0x00010000) || + !out->WriteU32(head->revision) || + !out->WriteU32(0) || // check sum not filled in yet + !out->WriteU32(0x5F0F3CF5) || + !out->WriteU16(head->flags) || + !out->WriteU16(head->ppem) || + !out->WriteR64(head->created) || + !out->WriteR64(head->modified) || + !out->WriteS16(head->xmin) || + !out->WriteS16(head->ymin) || + !out->WriteS16(head->xmax) || + !out->WriteS16(head->ymax) || + !out->WriteU16(head->mac_style) || + !out->WriteU16(head->min_ppem) || + !out->WriteS16(2) || + !out->WriteS16(head->index_to_loc_format) || + !out->WriteS16(0)) { + return OTS_FAILURE_MSG("Failed to write head table"); + } + + return true; +} + +void ots_head_reuse(Font *font, Font *other) { + font->head = other->head; + font->head_reused = true; +} + +void ots_head_free(Font *font) { + delete font->head; +} + +} // namespace + +#undef TABLE_NAME diff --git a/gfx/ots/src/head.h b/gfx/ots/src/head.h new file mode 100644 index 000000000..5967c4b89 --- /dev/null +++ b/gfx/ots/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/gfx/ots/src/hhea.cc b/gfx/ots/src/hhea.cc new file mode 100644 index 000000000..624287280 --- /dev/null +++ b/gfx/ots/src/hhea.cc @@ -0,0 +1,58 @@ +// 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/typography/otspec/hhea.htm + +#define TABLE_NAME "hhea" + +namespace ots { + +bool ots_hhea_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + OpenTypeHHEA *hhea = new OpenTypeHHEA; + font->hhea = hhea; + + if (!table.ReadU32(&hhea->header.version)) { + return OTS_FAILURE_MSG("Failed to read hhea version"); + } + if (hhea->header.version >> 16 != 1) { + return OTS_FAILURE_MSG("Bad hhea version of %d", hhea->header.version); + } + + if (!ParseMetricsHeader(font, &table, &hhea->header)) { + return OTS_FAILURE_MSG("Failed to parse horizontal metrics"); + } + + return true; +} + +bool ots_hhea_should_serialise(Font *font) { + return font->hhea != NULL; +} + +bool ots_hhea_serialise(OTSStream *out, Font *font) { + if (!SerialiseMetricsHeader(font, out, &font->hhea->header)) { + return OTS_FAILURE_MSG("Failed to serialise horizontal metrics"); + } + return true; +} + +void ots_hhea_reuse(Font *font, Font *other) { + font->hhea = other->hhea; + font->hhea_reused = true; +} + +void ots_hhea_free(Font *font) { + delete font->hhea; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/hhea.h b/gfx/ots/src/hhea.h new file mode 100644 index 000000000..bdea9aa0d --- /dev/null +++ b/gfx/ots/src/hhea.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_HHEA_H_ +#define OTS_HHEA_H_ + +#include "metrics.h" +#include "ots.h" + +namespace ots { + +struct OpenTypeHHEA { + OpenTypeMetricsHeader header; +}; + +} // namespace ots + +#endif // OTS_HHEA_H_ diff --git a/gfx/ots/src/hmtx.cc b/gfx/ots/src/hmtx.cc new file mode 100644 index 000000000..667d1fe6f --- /dev/null +++ b/gfx/ots/src/hmtx.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 "hmtx.h" + +#include "hhea.h" +#include "maxp.h" + +// hmtx - Horizontal Metrics +// http://www.microsoft.com/typography/otspec/hmtx.htm + +#define TABLE_NAME "hmtx" + +namespace ots { + +bool ots_hmtx_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + OpenTypeHMTX *hmtx = new OpenTypeHMTX; + font->hmtx = hmtx; + + if (!font->hhea || !font->maxp) { + return OTS_FAILURE_MSG("Missing hhea or maxp tables in font, needed by hmtx"); + } + + if (!ParseMetricsTable(font, &table, font->maxp->num_glyphs, + &font->hhea->header, &hmtx->metrics)) { + return OTS_FAILURE_MSG("Failed to parse hmtx metrics"); + } + + return true; +} + +bool ots_hmtx_should_serialise(Font *font) { + return font->hmtx != NULL; +} + +bool ots_hmtx_serialise(OTSStream *out, Font *font) { + if (!SerialiseMetricsTable(font, out, &font->hmtx->metrics)) { + return OTS_FAILURE_MSG("Failed to serialise htmx metrics"); + } + return true; +} + +void ots_hmtx_reuse(Font *font, Font *other) { + font->hmtx = other->hmtx; + font->hmtx_reused = true; +} + +void ots_hmtx_free(Font *font) { + delete font->hmtx; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/hmtx.h b/gfx/ots/src/hmtx.h new file mode 100644 index 000000000..435949c5e --- /dev/null +++ b/gfx/ots/src/hmtx.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_HMTX_H_ +#define OTS_HMTX_H_ + +#include "metrics.h" +#include "ots.h" + +namespace ots { + +struct OpenTypeHMTX { + OpenTypeMetricsTable metrics; +}; + +} // namespace ots + +#endif // OTS_HMTX_H_ diff --git a/gfx/ots/src/kern.cc b/gfx/ots/src/kern.cc new file mode 100644 index 000000000..d4ae8fcc4 --- /dev/null +++ b/gfx/ots/src/kern.cc @@ -0,0 +1,208 @@ +// 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 "kern.h" + +// kern - Kerning +// http://www.microsoft.com/typography/otspec/kern.htm + +#define TABLE_NAME "kern" + +#define DROP_THIS_TABLE(msg_) \ + do { \ + OTS_FAILURE_MSG(msg_ ", table discarded"); \ + delete font->kern; \ + font->kern = 0; \ + } while (0) + +namespace ots { + +bool ots_kern_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypeKERN *kern = new OpenTypeKERN; + font->kern = kern; + + uint16_t num_tables = 0; + if (!table.ReadU16(&kern->version) || + !table.ReadU16(&num_tables)) { + return OTS_FAILURE_MSG("Failed to read kern header"); + } + + if (kern->version > 0) { + DROP_THIS_TABLE("bad table version"); + return true; + } + + if (num_tables == 0) { + DROP_THIS_TABLE("num_tables is zero"); + return true; + } + + kern->subtables.reserve(num_tables); + for (unsigned i = 0; i < num_tables; ++i) { + OpenTypeKERNFormat0 subtable; + uint16_t sub_length = 0; + + if (!table.ReadU16(&subtable.version) || + !table.ReadU16(&sub_length)) { + return OTS_FAILURE_MSG("Failed to read kern subtable %d header", i); + } + + if (subtable.version > 0) { + OTS_WARNING("Bad subtable version: %d", subtable.version); + continue; + } + + const size_t current_offset = table.offset(); + if (current_offset - 4 + sub_length > length) { + return OTS_FAILURE_MSG("Bad kern subtable %d offset %ld", i, current_offset); + } + + if (!table.ReadU16(&subtable.coverage)) { + return OTS_FAILURE_MSG("Cailed to read kern subtable %d coverage", i); + } + + if (!(subtable.coverage & 0x1)) { + OTS_WARNING( + "We don't support vertical data as the renderer doesn't support it."); + continue; + } + if (subtable.coverage & 0xF0) { + DROP_THIS_TABLE("Reserved fields should zero-filled"); + return true; + } + const uint32_t format = (subtable.coverage & 0xFF00) >> 8; + if (format != 0) { + OTS_WARNING("Format %d is not supported.", format); + continue; + } + + // Parse the format 0 field. + uint16_t num_pairs = 0; + if (!table.ReadU16(&num_pairs) || + !table.ReadU16(&subtable.search_range) || + !table.ReadU16(&subtable.entry_selector) || + !table.ReadU16(&subtable.range_shift)) { + return OTS_FAILURE_MSG("Failed to read kern subtable %d format 0 fields", i); + } + + if (!num_pairs) { + DROP_THIS_TABLE("Zero length subtable is found"); + return true; + } + + // Sanity checks for search_range, entry_selector, and range_shift. See the + // comment in ots.cc for details. + const size_t kFormat0PairSize = 6; // left, right, and value. 2 bytes each. + if (num_pairs > (65536 / kFormat0PairSize)) { + // Some fonts (e.g. calibri.ttf, pykes_peak_zero.ttf) have pairs >= 10923. + DROP_THIS_TABLE("Too large subtable"); + return true; + } + unsigned max_pow2 = 0; + while (1u << (max_pow2 + 1) <= num_pairs) { + ++max_pow2; + } + const uint16_t expected_search_range = (1u << max_pow2) * kFormat0PairSize; + if (subtable.search_range != expected_search_range) { + OTS_WARNING("bad search range"); + subtable.search_range = expected_search_range; + } + if (subtable.entry_selector != max_pow2) { + return OTS_FAILURE_MSG("Bad subtable %d entry selector %d", i, subtable.entry_selector); + } + const uint16_t expected_range_shift = + kFormat0PairSize * num_pairs - subtable.search_range; + if (subtable.range_shift != expected_range_shift) { + OTS_WARNING("bad range shift"); + subtable.range_shift = expected_range_shift; + } + + // Read kerning pairs. + subtable.pairs.reserve(num_pairs); + uint32_t last_pair = 0; + for (unsigned j = 0; j < num_pairs; ++j) { + OpenTypeKERNFormat0Pair kerning_pair; + if (!table.ReadU16(&kerning_pair.left) || + !table.ReadU16(&kerning_pair.right) || + !table.ReadS16(&kerning_pair.value)) { + return OTS_FAILURE_MSG("Failed to read subtable %d kerning pair %d", i, j); + } + const uint32_t current_pair + = (kerning_pair.left << 16) + kerning_pair.right; + if (j != 0 && current_pair <= last_pair) { + // Many free fonts don't follow this rule, so we don't call OTS_FAILURE + // in order to support these fonts. + DROP_THIS_TABLE("Kerning pairs are not sorted"); + return true; + } + last_pair = current_pair; + subtable.pairs.push_back(kerning_pair); + } + + kern->subtables.push_back(subtable); + } + + if (!kern->subtables.size()) { + DROP_THIS_TABLE("All subtables are removed"); + return true; + } + + return true; +} + +bool ots_kern_should_serialise(Font *font) { + if (!font->glyf) return false; // this table is not for CFF fonts. + return font->kern != NULL; +} + +bool ots_kern_serialise(OTSStream *out, Font *font) { + const OpenTypeKERN *kern = font->kern; + + const uint16_t num_subtables = static_cast<uint16_t>(kern->subtables.size()); + if (num_subtables != kern->subtables.size() || + !out->WriteU16(kern->version) || + !out->WriteU16(num_subtables)) { + return OTS_FAILURE_MSG("Can't write kern table header"); + } + + for (uint16_t i = 0; i < num_subtables; ++i) { + const size_t length = 14 + (6 * kern->subtables[i].pairs.size()); + if (length > std::numeric_limits<uint16_t>::max() || + !out->WriteU16(kern->subtables[i].version) || + !out->WriteU16(static_cast<uint16_t>(length)) || + !out->WriteU16(kern->subtables[i].coverage) || + !out->WriteU16( + static_cast<uint16_t>(kern->subtables[i].pairs.size())) || + !out->WriteU16(kern->subtables[i].search_range) || + !out->WriteU16(kern->subtables[i].entry_selector) || + !out->WriteU16(kern->subtables[i].range_shift)) { + return OTS_FAILURE_MSG("Failed to write kern subtable %d", i); + } + for (unsigned j = 0; j < kern->subtables[i].pairs.size(); ++j) { + if (!out->WriteU16(kern->subtables[i].pairs[j].left) || + !out->WriteU16(kern->subtables[i].pairs[j].right) || + !out->WriteS16(kern->subtables[i].pairs[j].value)) { + return OTS_FAILURE_MSG("Failed to write kern pair %d for subtable %d", j, i); + } + } + } + + return true; +} + +void ots_kern_reuse(Font *font, Font *other) { + font->kern = other->kern; + font->kern_reused = true; +} + +void ots_kern_free(Font *font) { + delete font->kern; +} + +} // namespace ots + +#undef TABLE_NAME +#undef DROP_THIS_TABLE diff --git a/gfx/ots/src/kern.h b/gfx/ots/src/kern.h new file mode 100644 index 000000000..9350ef7f8 --- /dev/null +++ b/gfx/ots/src/kern.h @@ -0,0 +1,40 @@ +// 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_KERN_H_ +#define OTS_KERN_H_ + +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeKERNFormat0Pair { + uint16_t left; + uint16_t right; + int16_t value; +}; + +struct OpenTypeKERNFormat0 { + uint16_t version; + uint16_t coverage; + uint16_t search_range; + uint16_t entry_selector; + uint16_t range_shift; + std::vector<OpenTypeKERNFormat0Pair> pairs; +}; + +// Format 2 is not supported. Since the format is not supported by Windows, +// WebFonts unlikely use it. I've checked thousands of proprietary fonts and +// free fonts, and found no font uses the format. + +struct OpenTypeKERN { + uint16_t version; + std::vector<OpenTypeKERNFormat0> subtables; +}; + +} // namespace ots + +#endif // OTS_KERN_H_ diff --git a/gfx/ots/src/layout.cc b/gfx/ots/src/layout.cc new file mode 100644 index 000000000..1d87b53d4 --- /dev/null +++ b/gfx/ots/src/layout.cc @@ -0,0 +1,1521 @@ +// Copyright (c) 2011 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 "layout.h" + +#include <limits> +#include <vector> + +#include "gdef.h" + +// OpenType Layout Common Table Formats +// http://www.microsoft.com/typography/otspec/chapter2.htm + +#define TABLE_NAME "Layout" // XXX: use individual table names + +namespace { + +// The 'DFLT' tag of script table. +const uint32_t kScriptTableTagDflt = 0x44464c54; +// The value which represents there is no required feature index. +const uint16_t kNoRequiredFeatureIndexDefined = 0xFFFF; +// The lookup flag bit which indicates existence of MarkFilteringSet. +const uint16_t kUseMarkFilteringSetBit = 0x0010; +// The lookup flags which require GDEF table. +const uint16_t kGdefRequiredFlags = 0x0002 | 0x0004 | 0x0008; +// The mask for MarkAttachmentType. +const uint16_t kMarkAttachmentTypeMask = 0xFF00; +// The maximum type number of format for device tables. +const uint16_t kMaxDeltaFormatType = 3; +// The maximum number of class value. +const uint16_t kMaxClassDefValue = 0xFFFF; + +struct ScriptRecord { + uint32_t tag; + uint16_t offset; +}; + +struct LangSysRecord { + uint32_t tag; + uint16_t offset; +}; + +struct FeatureRecord { + uint32_t tag; + uint16_t offset; +}; + +bool ParseLangSysTable(const ots::Font *font, + ots::Buffer *subtable, const uint32_t tag, + const uint16_t num_features) { + uint16_t offset_lookup_order = 0; + uint16_t req_feature_index = 0; + uint16_t feature_count = 0; + if (!subtable->ReadU16(&offset_lookup_order) || + !subtable->ReadU16(&req_feature_index) || + !subtable->ReadU16(&feature_count)) { + return OTS_FAILURE_MSG("Failed to read langsys header for tag %c%c%c%c", OTS_UNTAG(tag)); + } + // |offset_lookup_order| is reserved and should be NULL. + if (offset_lookup_order != 0) { + return OTS_FAILURE_MSG("Bad lookup offset order %d for langsys tag %c%c%c%c", offset_lookup_order, OTS_UNTAG(tag)); + } + if (req_feature_index != kNoRequiredFeatureIndexDefined && + req_feature_index >= num_features) { + return OTS_FAILURE_MSG("Bad required features index %d for langsys tag %c%c%c%c", req_feature_index, OTS_UNTAG(tag)); + } + if (feature_count > num_features) { + return OTS_FAILURE_MSG("Bad feature count %d for langsys tag %c%c%c%c", feature_count, OTS_UNTAG(tag)); + } + + for (unsigned i = 0; i < feature_count; ++i) { + uint16_t feature_index = 0; + if (!subtable->ReadU16(&feature_index)) { + return OTS_FAILURE_MSG("Failed to read feature index %d for langsys tag %c%c%c%c", i, OTS_UNTAG(tag)); + } + if (feature_index >= num_features) { + return OTS_FAILURE_MSG("Bad feature index %d for feature %d for langsys tag %c%c%c%c", feature_index, i, OTS_UNTAG(tag)); + } + } + return true; +} + +bool ParseScriptTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint32_t tag, const uint16_t num_features) { + ots::Buffer subtable(data, length); + + uint16_t offset_default_lang_sys = 0; + uint16_t lang_sys_count = 0; + if (!subtable.ReadU16(&offset_default_lang_sys) || + !subtable.ReadU16(&lang_sys_count)) { + return OTS_FAILURE_MSG("Failed to read script header for script tag %c%c%c%c", OTS_UNTAG(tag)); + } + + // The spec requires a script table for 'DFLT' tag must contain non-NULL + // |offset_default_lang_sys| and |lang_sys_count| == 0 + // https://www.microsoft.com/typography/otspec/chapter2.htm + if (tag == kScriptTableTagDflt) { + if (offset_default_lang_sys == 0) { + return OTS_FAILURE_MSG("DFLT script doesn't satisfy the spec. DefaultLangSys is NULL"); + } + if (lang_sys_count != 0) { + return OTS_FAILURE_MSG("DFLT script doesn't satisfy the spec. LangSysCount is not zero: %d", lang_sys_count); + } + } + + const unsigned lang_sys_record_end = + 6 * static_cast<unsigned>(lang_sys_count) + 4; + if (lang_sys_record_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of langsys record %d for script tag %c%c%c%c", lang_sys_record_end, OTS_UNTAG(tag)); + } + + std::vector<LangSysRecord> lang_sys_records; + lang_sys_records.resize(lang_sys_count); + uint32_t last_tag = 0; + for (unsigned i = 0; i < lang_sys_count; ++i) { + if (!subtable.ReadU32(&lang_sys_records[i].tag) || + !subtable.ReadU16(&lang_sys_records[i].offset)) { + return OTS_FAILURE_MSG("Failed to read langsys record header %d for script tag %c%c%c%c", i, OTS_UNTAG(tag)); + } + // The record array must store the records alphabetically by tag + if (last_tag != 0 && last_tag > lang_sys_records[i].tag) { + return OTS_FAILURE_MSG("Bad last tag %d for langsys record %d for script tag %c%c%c%c", last_tag, i, OTS_UNTAG(tag)); + } + if (lang_sys_records[i].offset < lang_sys_record_end || + lang_sys_records[i].offset >= length) { + return OTS_FAILURE_MSG("bad offset to lang sys table: %x", + lang_sys_records[i].offset); + } + last_tag = lang_sys_records[i].tag; + } + + // Check lang sys tables + for (unsigned i = 0; i < lang_sys_count; ++i) { + subtable.set_offset(lang_sys_records[i].offset); + if (!ParseLangSysTable(font, &subtable, lang_sys_records[i].tag, num_features)) { + return OTS_FAILURE_MSG("Failed to parse langsys table %d (%c%c%c%c) for script tag %c%c%c%c", i, OTS_UNTAG(lang_sys_records[i].tag), OTS_UNTAG(tag)); + } + } + + return true; +} + +bool ParseFeatureTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t offset_feature_params = 0; + uint16_t lookup_count = 0; + if (!subtable.ReadU16(&offset_feature_params) || + !subtable.ReadU16(&lookup_count)) { + return OTS_FAILURE_MSG("Failed to read feature table header"); + } + + const unsigned feature_table_end = + 2 * static_cast<unsigned>(lookup_count) + 4; + if (feature_table_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of feature table %d", feature_table_end); + } + // |offset_feature_params| is generally set to NULL. + if (offset_feature_params != 0 && + (offset_feature_params < feature_table_end || + offset_feature_params >= length)) { + return OTS_FAILURE_MSG("Bad feature params offset %d", offset_feature_params); + } + + for (unsigned i = 0; i < lookup_count; ++i) { + uint16_t lookup_index = 0; + if (!subtable.ReadU16(&lookup_index)) { + return OTS_FAILURE_MSG("Failed to read lookup index for lookup %d", i); + } + // lookup index starts with 0. + if (lookup_index >= num_lookups) { + return OTS_FAILURE_MSG("Bad lookup index %d for lookup %d", lookup_index, i); + } + } + return true; +} + +bool ParseLookupTable(ots::Font *font, const uint8_t *data, + const size_t length, + const ots::LookupSubtableParser* parser) { + ots::Buffer subtable(data, length); + + uint16_t lookup_type = 0; + uint16_t lookup_flag = 0; + uint16_t subtable_count = 0; + if (!subtable.ReadU16(&lookup_type) || + !subtable.ReadU16(&lookup_flag) || + !subtable.ReadU16(&subtable_count)) { + return OTS_FAILURE_MSG("Failed to read lookup table header"); + } + + if (lookup_type == 0 || lookup_type > parser->num_types) { + return OTS_FAILURE_MSG("Bad lookup type %d", lookup_type); + } + + // Check lookup flags. + if ((lookup_flag & kGdefRequiredFlags) && + (!font->gdef || !font->gdef->has_glyph_class_def)) { + return OTS_FAILURE_MSG("Lookup flags require GDEF table, " + "but none was found: %d", lookup_flag); + } + if ((lookup_flag & kMarkAttachmentTypeMask) && + (!font->gdef || !font->gdef->has_mark_attachment_class_def)) { + return OTS_FAILURE_MSG("Lookup flags ask for mark attachment, " + "but there is no GDEF table or it has no " + "mark attachment classes: %d", lookup_flag); + } + bool use_mark_filtering_set = false; + if (lookup_flag & kUseMarkFilteringSetBit) { + if (!font->gdef || !font->gdef->has_mark_glyph_sets_def) { + return OTS_FAILURE_MSG("Lookup flags ask for mark filtering, " + "but there is no GDEF table or it has no " + "mark filtering sets: %d", lookup_flag); + } + use_mark_filtering_set = true; + } + + std::vector<uint16_t> subtables; + subtables.reserve(subtable_count); + // If the |kUseMarkFilteringSetBit| of |lookup_flag| is set, + // extra 2 bytes will follow after subtable offset array. + const unsigned lookup_table_end = 2 * static_cast<unsigned>(subtable_count) + + (use_mark_filtering_set ? 8 : 6); + if (lookup_table_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of lookup %d", lookup_table_end); + } + for (unsigned i = 0; i < subtable_count; ++i) { + uint16_t offset_subtable = 0; + if (!subtable.ReadU16(&offset_subtable)) { + return OTS_FAILURE_MSG("Failed to read subtable offset %d", i); + } + if (offset_subtable < lookup_table_end || + offset_subtable >= length) { + return OTS_FAILURE_MSG("Bad subtable offset %d for subtable %d", offset_subtable, i); + } + subtables.push_back(offset_subtable); + } + if (subtables.size() != subtable_count) { + return OTS_FAILURE_MSG("Bad subtable size %ld", subtables.size()); + } + + if (use_mark_filtering_set) { + uint16_t mark_filtering_set = 0; + if (!subtable.ReadU16(&mark_filtering_set)) { + return OTS_FAILURE_MSG("Failed to read mark filtering set"); + } + if (font->gdef->num_mark_glyph_sets == 0 || + mark_filtering_set >= font->gdef->num_mark_glyph_sets) { + return OTS_FAILURE_MSG("Bad mark filtering set %d", mark_filtering_set); + } + } + + // Parse lookup subtables for this lookup type. + for (unsigned i = 0; i < subtable_count; ++i) { + if (!parser->Parse(font, data + subtables[i], length - subtables[i], + lookup_type)) { + return OTS_FAILURE_MSG("Failed to parse subtable %d", i); + } + } + return true; +} + +bool ParseClassDefFormat1(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t num_classes) { + ots::Buffer subtable(data, length); + + // Skip format field. + if (!subtable.Skip(2)) { + return OTS_FAILURE_MSG("Failed to skip class definition header"); + } + + uint16_t start_glyph = 0; + if (!subtable.ReadU16(&start_glyph)) { + return OTS_FAILURE_MSG("Failed to read starting glyph of class definition"); + } + if (start_glyph > num_glyphs) { + return OTS_FAILURE_MSG("Bad starting glyph %d in class definition", start_glyph); + } + + uint16_t glyph_count = 0; + if (!subtable.ReadU16(&glyph_count)) { + return OTS_FAILURE_MSG("Failed to read glyph count in class definition"); + } + if (glyph_count > num_glyphs) { + return OTS_FAILURE_MSG("bad glyph count: %u", glyph_count); + } + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t class_value = 0; + if (!subtable.ReadU16(&class_value)) { + return OTS_FAILURE_MSG("Failed to read class value for glyph %d in class definition", i); + } + if (class_value > num_classes) { + return OTS_FAILURE_MSG("Bad class value %d for glyph %d in class definition", class_value, i); + } + } + + return true; +} + +bool ParseClassDefFormat2(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t num_classes) { + ots::Buffer subtable(data, length); + + // Skip format field. + if (!subtable.Skip(2)) { + return OTS_FAILURE_MSG("Failed to skip format of class defintion header"); + } + + uint16_t range_count = 0; + if (!subtable.ReadU16(&range_count)) { + return OTS_FAILURE_MSG("Failed to read range count in class definition"); + } + if (range_count > num_glyphs) { + return OTS_FAILURE_MSG("bad range count: %u", range_count); + } + + uint16_t last_end = 0; + for (unsigned i = 0; i < range_count; ++i) { + uint16_t start = 0; + uint16_t end = 0; + uint16_t class_value = 0; + if (!subtable.ReadU16(&start) || + !subtable.ReadU16(&end) || + !subtable.ReadU16(&class_value)) { + return OTS_FAILURE_MSG("Failed to read class definition reange %d", i); + } + if (start > end || (last_end && start <= last_end)) { + return OTS_FAILURE_MSG("glyph range is overlapping.in range %d", i); + } + if (class_value > num_classes) { + return OTS_FAILURE_MSG("bad class value: %u", class_value); + } + last_end = end; + } + + return true; +} + +bool ParseCoverageFormat1(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t expected_num_glyphs) { + ots::Buffer subtable(data, length); + + // Skip format field. + if (!subtable.Skip(2)) { + return OTS_FAILURE_MSG("Failed to skip coverage format"); + } + + uint16_t glyph_count = 0; + if (!subtable.ReadU16(&glyph_count)) { + return OTS_FAILURE_MSG("Failed to read glyph count in coverage"); + } + if (glyph_count > num_glyphs) { + return OTS_FAILURE_MSG("bad glyph count: %u", glyph_count); + } + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t glyph = 0; + if (!subtable.ReadU16(&glyph)) { + return OTS_FAILURE_MSG("Failed to read glyph %d in coverage", i); + } + if (glyph > num_glyphs) { + return OTS_FAILURE_MSG("bad glyph ID: %u", glyph); + } + } + + if (expected_num_glyphs && expected_num_glyphs != glyph_count) { + return OTS_FAILURE_MSG("unexpected number of glyphs: %u", glyph_count); + } + + return true; +} + +bool ParseCoverageFormat2(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t expected_num_glyphs) { + ots::Buffer subtable(data, length); + + // Skip format field. + if (!subtable.Skip(2)) { + return OTS_FAILURE_MSG("Failed to skip format of coverage type 2"); + } + + uint16_t range_count = 0; + if (!subtable.ReadU16(&range_count)) { + return OTS_FAILURE_MSG("Failed to read range count in coverage"); + } + if (range_count > num_glyphs) { + return OTS_FAILURE_MSG("bad range count: %u", range_count); + } + uint16_t last_end = 0; + uint16_t last_start_coverage_index = 0; + for (unsigned i = 0; i < range_count; ++i) { + uint16_t start = 0; + uint16_t end = 0; + uint16_t start_coverage_index = 0; + if (!subtable.ReadU16(&start) || + !subtable.ReadU16(&end) || + !subtable.ReadU16(&start_coverage_index)) { + return OTS_FAILURE_MSG("Failed to read range %d in coverage", i); + } + + // Some of the Adobe Pro fonts have ranges that overlap by one element: the + // start of one range is equal to the end of the previous range. Therefore + // the < in the following condition should be <= were it not for this. + // See crbug.com/134135. + if (start > end || (last_end && start < last_end)) { + return OTS_FAILURE_MSG("glyph range is overlapping."); + } + if (start_coverage_index != last_start_coverage_index) { + return OTS_FAILURE_MSG("bad start coverage index."); + } + last_end = end; + last_start_coverage_index += end - start + 1; + } + + if (expected_num_glyphs && + expected_num_glyphs != last_start_coverage_index) { + return OTS_FAILURE_MSG("unexpected number of glyphs: %u", last_start_coverage_index); + } + + return true; +} + +// Parsers for Contextual subtables in GSUB/GPOS tables. + +bool ParseLookupRecord(const ots::Font *font, + ots::Buffer *subtable, const uint16_t num_glyphs, + const uint16_t num_lookups) { + uint16_t sequence_index = 0; + uint16_t lookup_list_index = 0; + if (!subtable->ReadU16(&sequence_index) || + !subtable->ReadU16(&lookup_list_index)) { + return OTS_FAILURE_MSG("Failed to read header for lookup record"); + } + if (sequence_index >= num_glyphs) { + return OTS_FAILURE_MSG("Bad sequence index %d in lookup record", sequence_index); + } + if (lookup_list_index >= num_lookups) { + return OTS_FAILURE_MSG("Bad lookup list index %d in lookup record", lookup_list_index); + } + return true; +} + +bool ParseRuleSubtable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t glyph_count = 0; + uint16_t lookup_count = 0; + if (!subtable.ReadU16(&glyph_count) || + !subtable.ReadU16(&lookup_count)) { + return OTS_FAILURE_MSG("Failed to read rule subtable header"); + } + + if (glyph_count == 0 || glyph_count >= num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph count %d in rule subtable", glyph_count); + } + for (unsigned i = 0; i < glyph_count - static_cast<unsigned>(1); ++i) { + uint16_t glyph_id = 0; + if (!subtable.ReadU16(&glyph_id)) { + return OTS_FAILURE_MSG("Failed to read glyph %d", i); + } + if (glyph_id > num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph %d for entry %d", glyph_id, i); + } + } + + for (unsigned i = 0; i < lookup_count; ++i) { + if (!ParseLookupRecord(font, &subtable, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse lookup record %d", i); + } + } + return true; +} + +bool ParseRuleSetTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t rule_count = 0; + if (!subtable.ReadU16(&rule_count)) { + return OTS_FAILURE_MSG("Failed to read rule count in rule set"); + } + const unsigned rule_end = 2 * static_cast<unsigned>(rule_count) + 2; + if (rule_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of rule %d in rule set", rule_end); + } + + for (unsigned i = 0; i < rule_count; ++i) { + uint16_t offset_rule = 0; + if (!subtable.ReadU16(&offset_rule)) { + return OTS_FAILURE_MSG("Failed to read rule offset for rule set %d", i); + } + if (offset_rule < rule_end || offset_rule >= length) { + return OTS_FAILURE_MSG("Bad rule offset %d in set %d", offset_rule, i); + } + if (!ParseRuleSubtable(font, data + offset_rule, length - offset_rule, + num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse rule set %d", i); + } + } + + return true; +} + +bool ParseContextFormat1(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t offset_coverage = 0; + uint16_t rule_set_count = 0; + // Skip format field. + if (!subtable.Skip(2) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&rule_set_count)) { + return OTS_FAILURE_MSG("Failed to read header of context format 1"); + } + + const unsigned rule_set_end = static_cast<unsigned>(6) + + rule_set_count * 2; + if (rule_set_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of rule set %d of context format 1", rule_set_end); + } + if (offset_coverage < rule_set_end || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %d in context format 1", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table in context format 1"); + } + + for (unsigned i = 0; i < rule_set_count; ++i) { + uint16_t offset_rule = 0; + if (!subtable.ReadU16(&offset_rule)) { + return OTS_FAILURE_MSG("Failed to read rule offset %d in context format 1", i); + } + if (offset_rule < rule_set_end || offset_rule >= length) { + return OTS_FAILURE_MSG("Bad rule offset %d in rule %d in context format 1", offset_rule, i); + } + if (!ParseRuleSetTable(font, data + offset_rule, length - offset_rule, + num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse rule set %d in context format 1", i); + } + } + + return true; +} + +bool ParseClassRuleTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t glyph_count = 0; + uint16_t lookup_count = 0; + if (!subtable.ReadU16(&glyph_count) || + !subtable.ReadU16(&lookup_count)) { + return OTS_FAILURE_MSG("Failed to read header of class rule table"); + } + + if (glyph_count == 0 || glyph_count >= num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph count %d in class rule table", glyph_count); + } + + // ClassRule table contains an array of classes. Each value of classes + // could take arbitrary values including zero so we don't check these value. + const unsigned num_classes = glyph_count - static_cast<unsigned>(1); + if (!subtable.Skip(2 * num_classes)) { + return OTS_FAILURE_MSG("Failed to skip classes in class rule table"); + } + + for (unsigned i = 0; i < lookup_count; ++i) { + if (!ParseLookupRecord(font, &subtable, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse lookup record %d in class rule table", i); + } + } + return true; +} + +bool ParseClassSetTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t class_rule_count = 0; + if (!subtable.ReadU16(&class_rule_count)) { + return OTS_FAILURE_MSG("Failed to read class rule count in class set table"); + } + const unsigned class_rule_end = + 2 * static_cast<unsigned>(class_rule_count) + 2; + if (class_rule_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("bad class rule end %d in class set table", class_rule_end); + } + for (unsigned i = 0; i < class_rule_count; ++i) { + uint16_t offset_class_rule = 0; + if (!subtable.ReadU16(&offset_class_rule)) { + return OTS_FAILURE_MSG("Failed to read class rule offset %d in class set table", i); + } + if (offset_class_rule < class_rule_end || offset_class_rule >= length) { + return OTS_FAILURE_MSG("Bad class rule offset %d in class %d", offset_class_rule, i); + } + if (!ParseClassRuleTable(font, data + offset_class_rule, + length - offset_class_rule, num_glyphs, + num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse class rule table %d", i); + } + } + + return true; +} + +bool ParseContextFormat2(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t offset_coverage = 0; + uint16_t offset_class_def = 0; + uint16_t class_set_cnt = 0; + // Skip format field. + if (!subtable.Skip(2) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&offset_class_def) || + !subtable.ReadU16(&class_set_cnt)) { + return OTS_FAILURE_MSG("Failed to read header for context format 2"); + } + + const unsigned class_set_end = 2 * static_cast<unsigned>(class_set_cnt) + 8; + if (class_set_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of class set %d for context format 2", class_set_end); + } + if (offset_coverage < class_set_end || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %d in context format 2", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table in context format 2"); + } + + if (offset_class_def < class_set_end || offset_class_def >= length) { + return OTS_FAILURE_MSG("bad class definition offset %d in context format 2", offset_class_def); + } + if (!ots::ParseClassDefTable(font, data + offset_class_def, + length - offset_class_def, + num_glyphs, kMaxClassDefValue)) { + return OTS_FAILURE_MSG("Failed to parse class definition table in context format 2"); + } + + for (unsigned i = 0; i < class_set_cnt; ++i) { + uint16_t offset_class_rule = 0; + if (!subtable.ReadU16(&offset_class_rule)) { + return OTS_FAILURE_MSG("Failed to read class rule offset %d in context format 2", i); + } + if (offset_class_rule) { + if (offset_class_rule < class_set_end || offset_class_rule >= length) { + return OTS_FAILURE_MSG("Bad class rule offset %d for rule %d in context format 2", offset_class_rule, i); + } + if (!ParseClassSetTable(font, data + offset_class_rule, + length - offset_class_rule, num_glyphs, + num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse class set %d in context format 2", i); + } + } + } + + return true; +} + +bool ParseContextFormat3(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t glyph_count = 0; + uint16_t lookup_count = 0; + // Skip format field. + if (!subtable.Skip(2) || + !subtable.ReadU16(&glyph_count) || + !subtable.ReadU16(&lookup_count)) { + return OTS_FAILURE_MSG("Failed to read header in context format 3"); + } + + if (glyph_count >= num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph count %d in context format 3", glyph_count); + } + const unsigned lookup_record_end = 2 * static_cast<unsigned>(glyph_count) + + 4 * static_cast<unsigned>(lookup_count) + 6; + if (lookup_record_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of lookup %d in context format 3", lookup_record_end); + } + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t offset_coverage = 0; + if (!subtable.ReadU16(&offset_coverage)) { + return OTS_FAILURE_MSG("Failed to read coverage offset %d in conxtext format 3", i); + } + if (offset_coverage < lookup_record_end || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %d for glyph %d in context format 3", offset_coverage, i); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table for glyph %d in context format 3", i); + } + } + + for (unsigned i = 0; i < lookup_count; ++i) { + if (!ParseLookupRecord(font, &subtable, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse lookup record %d in context format 3", i); + } + } + + return true; +} + +// Parsers for Chaning Contextual subtables in GSUB/GPOS tables. + +bool ParseChainRuleSubtable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t backtrack_count = 0; + if (!subtable.ReadU16(&backtrack_count)) { + return OTS_FAILURE_MSG("Failed to read backtrack count in chain rule subtable"); + } + if (backtrack_count >= num_glyphs) { + return OTS_FAILURE_MSG("Bad backtrack count %d in chain rule subtable", backtrack_count); + } + for (unsigned i = 0; i < backtrack_count; ++i) { + uint16_t glyph_id = 0; + if (!subtable.ReadU16(&glyph_id)) { + return OTS_FAILURE_MSG("Failed to read backtrack glyph %d in chain rule subtable", i); + } + if (glyph_id > num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph id %d for bactrack glyph %d in chain rule subtable", glyph_id, i); + } + } + + uint16_t input_count = 0; + if (!subtable.ReadU16(&input_count)) { + return OTS_FAILURE_MSG("Failed to read input count in chain rule subtable"); + } + if (input_count == 0 || input_count >= num_glyphs) { + return OTS_FAILURE_MSG("Bad input count %d in chain rule subtable", input_count); + } + for (unsigned i = 0; i < input_count - static_cast<unsigned>(1); ++i) { + uint16_t glyph_id = 0; + if (!subtable.ReadU16(&glyph_id)) { + return OTS_FAILURE_MSG("Failed to read input glyph %d in chain rule subtable", i); + } + if (glyph_id > num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph id %d for input glyph %d in chain rule subtable", glyph_id, i); + } + } + + uint16_t lookahead_count = 0; + if (!subtable.ReadU16(&lookahead_count)) { + return OTS_FAILURE_MSG("Failed to read lookahead count in chain rule subtable"); + } + if (lookahead_count >= num_glyphs) { + return OTS_FAILURE_MSG("Bad lookahead count %d in chain rule subtable", lookahead_count); + } + for (unsigned i = 0; i < lookahead_count; ++i) { + uint16_t glyph_id = 0; + if (!subtable.ReadU16(&glyph_id)) { + return OTS_FAILURE_MSG("Failed to read lookahead glyph %d in chain rule subtable", i); + } + if (glyph_id > num_glyphs) { + return OTS_FAILURE_MSG("Bad glyph id %d for lookadhead glyph %d in chain rule subtable", glyph_id, i); + } + } + + uint16_t lookup_count = 0; + if (!subtable.ReadU16(&lookup_count)) { + return OTS_FAILURE_MSG("Failed to read lookup count in chain rule subtable"); + } + for (unsigned i = 0; i < lookup_count; ++i) { + if (!ParseLookupRecord(font, &subtable, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse lookup record %d in chain rule subtable", i); + } + } + + return true; +} + +bool ParseChainRuleSetTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t chain_rule_count = 0; + if (!subtable.ReadU16(&chain_rule_count)) { + return OTS_FAILURE_MSG("Failed to read rule count in chain rule set"); + } + const unsigned chain_rule_end = + 2 * static_cast<unsigned>(chain_rule_count) + 2; + if (chain_rule_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of chain rule %d in chain rule set", chain_rule_end); + } + for (unsigned i = 0; i < chain_rule_count; ++i) { + uint16_t offset_chain_rule = 0; + if (!subtable.ReadU16(&offset_chain_rule)) { + return OTS_FAILURE_MSG("Failed to read chain rule offset %d in chain rule set", i); + } + if (offset_chain_rule < chain_rule_end || offset_chain_rule >= length) { + return OTS_FAILURE_MSG("Bad chain rule offset %d for chain rule %d in chain rule set", offset_chain_rule, i); + } + if (!ParseChainRuleSubtable(font, data + offset_chain_rule, + length - offset_chain_rule, + num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse chain rule %d in chain rule set", i); + } + } + + return true; +} + +bool ParseChainContextFormat1(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t offset_coverage = 0; + uint16_t chain_rule_set_count = 0; + // Skip format field. + if (!subtable.Skip(2) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&chain_rule_set_count)) { + return OTS_FAILURE_MSG("Failed to read header of chain context format 1"); + } + + const unsigned chain_rule_set_end = + 2 * static_cast<unsigned>(chain_rule_set_count) + 6; + if (chain_rule_set_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad chain rule end %d in chain context format 1", chain_rule_set_end); + } + if (offset_coverage < chain_rule_set_end || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %d in chain context format 1", chain_rule_set_end); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table for chain context format 1"); + } + + for (unsigned i = 0; i < chain_rule_set_count; ++i) { + uint16_t offset_chain_rule_set = 0; + if (!subtable.ReadU16(&offset_chain_rule_set)) { + return OTS_FAILURE_MSG("Failed to read chain rule offset %d in chain context format 1", i); + } + if (offset_chain_rule_set < chain_rule_set_end || + offset_chain_rule_set >= length) { + return OTS_FAILURE_MSG("Bad chain rule set offset %d for chain rule set %d in chain context format 1", offset_chain_rule_set, i); + } + if (!ParseChainRuleSetTable(font, data + offset_chain_rule_set, + length - offset_chain_rule_set, + num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse chain rule set %d in chain context format 1", i); + } + } + + return true; +} + +bool ParseChainClassRuleSubtable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + // In this subtable, we don't check the value of classes for now since + // these could take arbitrary values. + + uint16_t backtrack_count = 0; + if (!subtable.ReadU16(&backtrack_count)) { + return OTS_FAILURE_MSG("Failed to read backtrack count in chain class rule subtable"); + } + if (backtrack_count >= num_glyphs) { + return OTS_FAILURE_MSG("Bad backtrack count %d in chain class rule subtable", backtrack_count); + } + if (!subtable.Skip(2 * backtrack_count)) { + return OTS_FAILURE_MSG("Failed to skip backtrack offsets in chain class rule subtable"); + } + + uint16_t input_count = 0; + if (!subtable.ReadU16(&input_count)) { + return OTS_FAILURE_MSG("Failed to read input count in chain class rule subtable"); + } + if (input_count == 0 || input_count >= num_glyphs) { + return OTS_FAILURE_MSG("Bad input count %d in chain class rule subtable", input_count); + } + if (!subtable.Skip(2 * (input_count - 1))) { + return OTS_FAILURE_MSG("Failed to skip input offsets in chain class rule subtable"); + } + + uint16_t lookahead_count = 0; + if (!subtable.ReadU16(&lookahead_count)) { + return OTS_FAILURE_MSG("Failed to read lookahead count in chain class rule subtable"); + } + if (lookahead_count >= num_glyphs) { + return OTS_FAILURE_MSG("Bad lookahead count %d in chain class rule subtable", lookahead_count); + } + if (!subtable.Skip(2 * lookahead_count)) { + return OTS_FAILURE_MSG("Failed to skip lookahead offsets in chain class rule subtable"); + } + + uint16_t lookup_count = 0; + if (!subtable.ReadU16(&lookup_count)) { + return OTS_FAILURE_MSG("Failed to read lookup count in chain class rule subtable"); + } + for (unsigned i = 0; i < lookup_count; ++i) { + if (!ParseLookupRecord(font, &subtable, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse lookup record %d in chain class rule subtable", i); + } + } + + return true; +} + +bool ParseChainClassSetTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t chain_class_rule_count = 0; + if (!subtable.ReadU16(&chain_class_rule_count)) { + return OTS_FAILURE_MSG("Failed to read rule count in chain class set"); + } + const unsigned chain_class_rule_end = + 2 * static_cast<unsigned>(chain_class_rule_count) + 2; + if (chain_class_rule_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of chain class set %d in chain class set", chain_class_rule_end); + } + for (unsigned i = 0; i < chain_class_rule_count; ++i) { + uint16_t offset_chain_class_rule = 0; + if (!subtable.ReadU16(&offset_chain_class_rule)) { + return OTS_FAILURE_MSG("Failed to read chain class rule offset %d in chain class set", i); + } + if (offset_chain_class_rule < chain_class_rule_end || + offset_chain_class_rule >= length) { + return OTS_FAILURE_MSG("Bad chain class rule offset %d for chain class %d in chain class set", offset_chain_class_rule, i); + } + if (!ParseChainClassRuleSubtable(font, data + offset_chain_class_rule, + length - offset_chain_class_rule, + num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse chain class rule %d in chain class set", i); + } + } + + return true; +} + +bool ParseChainContextFormat2(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t offset_coverage = 0; + uint16_t offset_backtrack_class_def = 0; + uint16_t offset_input_class_def = 0; + uint16_t offset_lookahead_class_def = 0; + uint16_t chain_class_set_count = 0; + // Skip format field. + if (!subtable.Skip(2) || + !subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&offset_backtrack_class_def) || + !subtable.ReadU16(&offset_input_class_def) || + !subtable.ReadU16(&offset_lookahead_class_def) || + !subtable.ReadU16(&chain_class_set_count)) { + return OTS_FAILURE_MSG("Failed to read header of chain context format 2"); + } + + const unsigned chain_class_set_end = + 2 * static_cast<unsigned>(chain_class_set_count) + 12; + if (chain_class_set_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad chain class set end %d in chain context format 2", chain_class_set_end); + } + if (offset_coverage < chain_class_set_end || offset_coverage >= length) { + return OTS_FAILURE_MSG("Bad coverage offset %d in chain context format 2", offset_coverage); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse coverage table in chain context format 2"); + } + + // Classes for backtrack/lookahead sequences might not be defined. + if (offset_backtrack_class_def) { + if (offset_backtrack_class_def < chain_class_set_end || + offset_backtrack_class_def >= length) { + return OTS_FAILURE_MSG("Bad backtrack class offset %d in chain context format 2", offset_backtrack_class_def); + } + if (!ots::ParseClassDefTable(font, data + offset_backtrack_class_def, + length - offset_backtrack_class_def, + num_glyphs, kMaxClassDefValue)) { + return OTS_FAILURE_MSG("Failed to parse backtrack class defn table in chain context format 2"); + } + } + + if (offset_input_class_def < chain_class_set_end || + offset_input_class_def >= length) { + return OTS_FAILURE_MSG("Bad input class defn offset %d in chain context format 2", offset_input_class_def); + } + if (!ots::ParseClassDefTable(font, data + offset_input_class_def, + length - offset_input_class_def, + num_glyphs, kMaxClassDefValue)) { + return OTS_FAILURE_MSG("Failed to parse input class defn in chain context format 2"); + } + + if (offset_lookahead_class_def) { + if (offset_lookahead_class_def < chain_class_set_end || + offset_lookahead_class_def >= length) { + return OTS_FAILURE_MSG("Bad lookahead class defn offset %d in chain context format 2", offset_lookahead_class_def); + } + if (!ots::ParseClassDefTable(font, data + offset_lookahead_class_def, + length - offset_lookahead_class_def, + num_glyphs, kMaxClassDefValue)) { + return OTS_FAILURE_MSG("Failed to parse lookahead class defn in chain context format 2"); + } + } + + for (unsigned i = 0; i < chain_class_set_count; ++i) { + uint16_t offset_chain_class_set = 0; + if (!subtable.ReadU16(&offset_chain_class_set)) { + return OTS_FAILURE_MSG("Failed to read chain class set offset %d", i); + } + // |offset_chain_class_set| could be NULL. + if (offset_chain_class_set) { + if (offset_chain_class_set < chain_class_set_end || + offset_chain_class_set >= length) { + return OTS_FAILURE_MSG("Bad chain set class offset %d for chain set %d in chain context format 2", offset_chain_class_set, i); + } + if (!ParseChainClassSetTable(font, data + offset_chain_class_set, + length - offset_chain_class_set, + num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse chain class set table %d in chain context format 2", i); + } + } + } + + return true; +} + +bool ParseChainContextFormat3(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + ots::Buffer subtable(data, length); + + uint16_t backtrack_count = 0; + // Skip format field. + if (!subtable.Skip(2) || + !subtable.ReadU16(&backtrack_count)) { + return OTS_FAILURE_MSG("Failed to read backtrack count in chain context format 3"); + } + + if (backtrack_count >= num_glyphs) { + return OTS_FAILURE_MSG("Bad backtrack count %d in chain context format 3", backtrack_count); + } + std::vector<uint16_t> offsets_backtrack; + offsets_backtrack.reserve(backtrack_count); + for (unsigned i = 0; i < backtrack_count; ++i) { + uint16_t offset = 0; + if (!subtable.ReadU16(&offset)) { + return OTS_FAILURE_MSG("Failed to read backtrack offset %d in chain context format 3", i); + } + offsets_backtrack.push_back(offset); + } + if (offsets_backtrack.size() != backtrack_count) { + return OTS_FAILURE_MSG("Bad backtrack offsets size %ld in chain context format 3", offsets_backtrack.size()); + } + + uint16_t input_count = 0; + if (!subtable.ReadU16(&input_count)) { + return OTS_FAILURE_MSG("Failed to read input count in chain context format 3"); + } + if (input_count >= num_glyphs) { + return OTS_FAILURE_MSG("Bad input count %d in chain context format 3", input_count); + } + std::vector<uint16_t> offsets_input; + offsets_input.reserve(input_count); + for (unsigned i = 0; i < input_count; ++i) { + uint16_t offset = 0; + if (!subtable.ReadU16(&offset)) { + return OTS_FAILURE_MSG("Failed to read input offset %d in chain context format 3", i); + } + offsets_input.push_back(offset); + } + if (offsets_input.size() != input_count) { + return OTS_FAILURE_MSG("Bad input offsets size %ld in chain context format 3", offsets_input.size()); + } + + uint16_t lookahead_count = 0; + if (!subtable.ReadU16(&lookahead_count)) { + return OTS_FAILURE_MSG("Failed ot read lookahead count in chain context format 3"); + } + if (lookahead_count >= num_glyphs) { + return OTS_FAILURE_MSG("Bad lookahead count %d in chain context format 3", lookahead_count); + } + std::vector<uint16_t> offsets_lookahead; + offsets_lookahead.reserve(lookahead_count); + for (unsigned i = 0; i < lookahead_count; ++i) { + uint16_t offset = 0; + if (!subtable.ReadU16(&offset)) { + return OTS_FAILURE_MSG("Failed to read lookahead offset %d in chain context format 3", i); + } + offsets_lookahead.push_back(offset); + } + if (offsets_lookahead.size() != lookahead_count) { + return OTS_FAILURE_MSG("Bad lookahead offsets size %ld in chain context format 3", offsets_lookahead.size()); + } + + uint16_t lookup_count = 0; + if (!subtable.ReadU16(&lookup_count)) { + return OTS_FAILURE_MSG("Failed to read lookup count in chain context format 3"); + } + for (unsigned i = 0; i < lookup_count; ++i) { + if (!ParseLookupRecord(font, &subtable, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse lookup %d in chain context format 3", i); + } + } + + const unsigned lookup_record_end = + 2 * (static_cast<unsigned>(backtrack_count) + + static_cast<unsigned>(input_count) + + static_cast<unsigned>(lookahead_count)) + + 4 * static_cast<unsigned>(lookup_count) + 10; + if (lookup_record_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of lookup record %d in chain context format 3", lookup_record_end); + } + for (unsigned i = 0; i < backtrack_count; ++i) { + if (offsets_backtrack[i] < lookup_record_end || + offsets_backtrack[i] >= length) { + return OTS_FAILURE_MSG("Bad backtrack offset of %d for backtrack %d in chain context format 3", offsets_backtrack[i], i); + } + if (!ots::ParseCoverageTable(font, data + offsets_backtrack[i], + length - offsets_backtrack[i], num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse backtrack coverage %d in chain context format 3", i); + } + } + for (unsigned i = 0; i < input_count; ++i) { + if (offsets_input[i] < lookup_record_end || offsets_input[i] >= length) { + return OTS_FAILURE_MSG("Bad input offset %d for input %d in chain context format 3", offsets_input[i], i); + } + if (!ots::ParseCoverageTable(font, data + offsets_input[i], + length - offsets_input[i], num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse input coverage table %d in chain context format 3", i); + } + } + for (unsigned i = 0; i < lookahead_count; ++i) { + if (offsets_lookahead[i] < lookup_record_end || + offsets_lookahead[i] >= length) { + return OTS_FAILURE_MSG("Bad lookadhead offset %d for lookahead %d in chain context format 3", offsets_lookahead[i], i); + } + if (!ots::ParseCoverageTable(font, data + offsets_lookahead[i], + length - offsets_lookahead[i], num_glyphs)) { + return OTS_FAILURE_MSG("Failed to parse lookahead coverage table %d in chain context format 3", i); + } + } + + return true; +} + +} // namespace + +namespace ots { + +bool LookupSubtableParser::Parse(const Font *font, const uint8_t *data, + const size_t length, + const uint16_t lookup_type) const { + for (unsigned i = 0; i < num_types; ++i) { + if (parsers[i].type == lookup_type && parsers[i].parse) { + if (!parsers[i].parse(font, data, length)) { + return OTS_FAILURE_MSG("Failed to parse lookup subtable %d", i); + } + return true; + } + } + return OTS_FAILURE_MSG("No lookup subtables to parse"); +} + +// Parsing ScriptListTable requires number of features so we need to +// parse FeatureListTable before calling this function. +bool ParseScriptListTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_features) { + Buffer subtable(data, length); + + uint16_t script_count = 0; + if (!subtable.ReadU16(&script_count)) { + return OTS_FAILURE_MSG("Failed to read script count in script list table"); + } + + const unsigned script_record_end = + 6 * static_cast<unsigned>(script_count) + 2; + if (script_record_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of script record %d in script list table", script_record_end); + } + std::vector<ScriptRecord> script_list; + script_list.reserve(script_count); + uint32_t last_tag = 0; + for (unsigned i = 0; i < script_count; ++i) { + ScriptRecord record; + if (!subtable.ReadU32(&record.tag) || + !subtable.ReadU16(&record.offset)) { + return OTS_FAILURE_MSG("Failed to read script record %d in script list table", i); + } + // Script tags should be arranged alphabetically by tag + if (last_tag != 0 && last_tag > record.tag) { + // Several fonts don't arrange tags alphabetically. + // It seems that the order of tags might not be a security issue + // so we just warn it. + OTS_WARNING("tags aren't arranged alphabetically."); + } + last_tag = record.tag; + if (record.offset < script_record_end || record.offset >= length) { + return OTS_FAILURE_MSG("Bad record offset %d for script %c%c%c%c entry %d in script list table", record.offset, OTS_UNTAG(record.tag), i); + } + script_list.push_back(record); + } + if (script_list.size() != script_count) { + return OTS_FAILURE_MSG("Bad script list size %ld in script list table", script_list.size()); + } + + // Check script records. + for (unsigned i = 0; i < script_count; ++i) { + if (!ParseScriptTable(font, data + script_list[i].offset, + length - script_list[i].offset, + script_list[i].tag, num_features)) { + return OTS_FAILURE_MSG("Failed to parse script table %d", i); + } + } + + return true; +} + +// Parsing FeatureListTable requires number of lookups so we need to parse +// LookupListTable before calling this function. +bool ParseFeatureListTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_lookups, + uint16_t* num_features) { + Buffer subtable(data, length); + + uint16_t feature_count = 0; + if (!subtable.ReadU16(&feature_count)) { + return OTS_FAILURE_MSG("Failed to read feature count"); + } + + std::vector<FeatureRecord> feature_records; + feature_records.resize(feature_count); + const unsigned feature_record_end = + 6 * static_cast<unsigned>(feature_count) + 2; + if (feature_record_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of feature record %d", feature_record_end); + } + uint32_t last_tag = 0; + for (unsigned i = 0; i < feature_count; ++i) { + if (!subtable.ReadU32(&feature_records[i].tag) || + !subtable.ReadU16(&feature_records[i].offset)) { + return OTS_FAILURE_MSG("Failed to read feature header %d", i); + } + // Feature record array should be arranged alphabetically by tag + if (last_tag != 0 && last_tag > feature_records[i].tag) { + // Several fonts don't arrange tags alphabetically. + // It seems that the order of tags might not be a security issue + // so we just warn it. + OTS_WARNING("tags aren't arranged alphabetically."); + } + last_tag = feature_records[i].tag; + if (feature_records[i].offset < feature_record_end || + feature_records[i].offset >= length) { + return OTS_FAILURE_MSG("Bad feature offset %d for feature %d %c%c%c%c", feature_records[i].offset, i, OTS_UNTAG(feature_records[i].tag)); + } + } + + for (unsigned i = 0; i < feature_count; ++i) { + if (!ParseFeatureTable(font, data + feature_records[i].offset, + length - feature_records[i].offset, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse feature table %d", i); + } + } + *num_features = feature_count; + return true; +} + +// For parsing GPOS/GSUB tables, this function should be called at first to +// obtain the number of lookups because parsing FeatureTableList requires +// the number. +bool ParseLookupListTable(Font *font, const uint8_t *data, + const size_t length, + const LookupSubtableParser* parser, + uint16_t *num_lookups) { + Buffer subtable(data, length); + + if (!subtable.ReadU16(num_lookups)) { + return OTS_FAILURE_MSG("Failed to read number of lookups"); + } + + std::vector<uint16_t> lookups; + lookups.reserve(*num_lookups); + const unsigned lookup_end = + 2 * static_cast<unsigned>(*num_lookups) + 2; + if (lookup_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE_MSG("Bad end of lookups %d", lookup_end); + } + for (unsigned i = 0; i < *num_lookups; ++i) { + uint16_t offset = 0; + if (!subtable.ReadU16(&offset)) { + return OTS_FAILURE_MSG("Failed to read lookup offset %d", i); + } + if (offset < lookup_end || offset >= length) { + return OTS_FAILURE_MSG("Bad lookup offset %d for lookup %d", offset, i); + } + lookups.push_back(offset); + } + if (lookups.size() != *num_lookups) { + return OTS_FAILURE_MSG("Bad lookup offsets list size %ld", lookups.size()); + } + + for (unsigned i = 0; i < *num_lookups; ++i) { + if (!ParseLookupTable(font, data + lookups[i], length - lookups[i], + parser)) { + return OTS_FAILURE_MSG("Failed to parse lookup %d", i); + } + } + + return true; +} + +bool ParseClassDefTable(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t num_classes) { + Buffer subtable(data, length); + + uint16_t format = 0; + if (!subtable.ReadU16(&format)) { + return OTS_FAILURE_MSG("Failed to read class defn format"); + } + if (format == 1) { + return ParseClassDefFormat1(font, data, length, num_glyphs, num_classes); + } else if (format == 2) { + return ParseClassDefFormat2(font, data, length, num_glyphs, num_classes); + } + + return OTS_FAILURE_MSG("Bad class defn format %d", format); +} + +bool ParseCoverageTable(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t expected_num_glyphs) { + Buffer subtable(data, length); + + uint16_t format = 0; + if (!subtable.ReadU16(&format)) { + return OTS_FAILURE_MSG("Failed to read coverage table format"); + } + if (format == 1) { + return ParseCoverageFormat1(font, data, length, num_glyphs, expected_num_glyphs); + } else if (format == 2) { + return ParseCoverageFormat2(font, data, length, num_glyphs, expected_num_glyphs); + } + + return OTS_FAILURE_MSG("Bad coverage table format %d", format); +} + +bool ParseDeviceTable(const ots::Font *font, + const uint8_t *data, size_t length) { + Buffer subtable(data, length); + + uint16_t start_size = 0; + uint16_t end_size = 0; + uint16_t delta_format = 0; + if (!subtable.ReadU16(&start_size) || + !subtable.ReadU16(&end_size) || + !subtable.ReadU16(&delta_format)) { + return OTS_FAILURE_MSG("Failed to read device table header"); + } + if (start_size > end_size) { + return OTS_FAILURE_MSG("bad size range: %u > %u", start_size, end_size); + } + if (delta_format == 0 || delta_format > kMaxDeltaFormatType) { + return OTS_FAILURE_MSG("bad delta format: %u", delta_format); + } + // The number of delta values per uint16. The device table should contain + // at least |num_units| * 2 bytes compressed data. + const unsigned num_units = (end_size - start_size) / + (1 << (4 - delta_format)) + 1; + // Just skip |num_units| * 2 bytes since the compressed data could take + // arbitrary values. + if (!subtable.Skip(num_units * 2)) { + return OTS_FAILURE_MSG("Failed to skip data in device table"); + } + return true; +} + +bool ParseContextSubtable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + Buffer subtable(data, length); + + uint16_t format = 0; + if (!subtable.ReadU16(&format)) { + return OTS_FAILURE_MSG("Failed to read context subtable format"); + } + + if (format == 1) { + if (!ParseContextFormat1(font, data, length, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse context format 1 subtable"); + } + } else if (format == 2) { + if (!ParseContextFormat2(font, data, length, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse context format 2 subtable"); + } + } else if (format == 3) { + if (!ParseContextFormat3(font, data, length, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse context format 3 subtable"); + } + } else { + return OTS_FAILURE_MSG("Bad context subtable format %d", format); + } + + return true; +} + +bool ParseChainingContextSubtable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups) { + Buffer subtable(data, length); + + uint16_t format = 0; + if (!subtable.ReadU16(&format)) { + return OTS_FAILURE_MSG("Failed to read chaining context subtable format"); + } + + if (format == 1) { + if (!ParseChainContextFormat1(font, data, length, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse chaining context format 1 subtable"); + } + } else if (format == 2) { + if (!ParseChainContextFormat2(font, data, length, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse chaining context format 2 subtable"); + } + } else if (format == 3) { + if (!ParseChainContextFormat3(font, data, length, num_glyphs, num_lookups)) { + return OTS_FAILURE_MSG("Failed to parse chaining context format 3 subtable"); + } + } else { + return OTS_FAILURE_MSG("Bad chaining context subtable format %d", format); + } + + return true; +} + +bool ParseExtensionSubtable(const Font *font, + const uint8_t *data, const size_t length, + const LookupSubtableParser* parser) { + Buffer subtable(data, length); + + uint16_t format = 0; + uint16_t lookup_type = 0; + uint32_t offset_extension = 0; + if (!subtable.ReadU16(&format) || + !subtable.ReadU16(&lookup_type) || + !subtable.ReadU32(&offset_extension)) { + return OTS_FAILURE_MSG("Failed to read extension table header"); + } + + if (format != 1) { + return OTS_FAILURE_MSG("Bad extension table format %d", format); + } + // |lookup_type| should be other than |parser->extension_type|. + if (lookup_type < 1 || lookup_type > parser->num_types || + lookup_type == parser->extension_type) { + return OTS_FAILURE_MSG("Bad lookup type %d in extension table", lookup_type); + } + + const unsigned format_end = static_cast<unsigned>(8); + if (offset_extension < format_end || + offset_extension >= length) { + return OTS_FAILURE_MSG("Bad extension offset %d", offset_extension); + } + + // Parse the extension subtable of |lookup_type|. + if (!parser->Parse(font, data + offset_extension, length - offset_extension, + lookup_type)) { + return OTS_FAILURE_MSG("Failed to parse lookup from extension lookup"); + } + + return true; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/layout.h b/gfx/ots/src/layout.h new file mode 100644 index 000000000..d195646d4 --- /dev/null +++ b/gfx/ots/src/layout.h @@ -0,0 +1,76 @@ +// Copyright (c) 2011 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_LAYOUT_H_ +#define OTS_LAYOUT_H_ + +#include "ots.h" + +// Utility functions for OpenType layout common table formats. +// http://www.microsoft.com/typography/otspec/chapter2.htm + +namespace ots { + + +struct LookupSubtableParser { + struct TypeParser { + uint16_t type; + bool (*parse)(const Font *font, const uint8_t *data, + const size_t length); + }; + size_t num_types; + uint16_t extension_type; + const TypeParser *parsers; + + bool Parse(const Font *font, const uint8_t *data, + const size_t length, const uint16_t lookup_type) const; +}; + +bool ParseScriptListTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_features); + +bool ParseFeatureListTable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_lookups, + uint16_t *num_features); + +bool ParseLookupListTable(Font *font, const uint8_t *data, + const size_t length, + const LookupSubtableParser* parser, + uint16_t* num_lookups); + +bool ParseClassDefTable(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t num_classes); + +bool ParseCoverageTable(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs, + const uint16_t expected_num_glyphs = 0); + +bool ParseDeviceTable(const ots::Font *font, + const uint8_t *data, size_t length); + +// Parser for 'Contextual' subtable shared by GSUB/GPOS tables. +bool ParseContextSubtable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups); + +// Parser for 'Chaining Contextual' subtable shared by GSUB/GPOS tables. +bool ParseChainingContextSubtable(const ots::Font *font, + const uint8_t *data, const size_t length, + const uint16_t num_glyphs, + const uint16_t num_lookups); + +bool ParseExtensionSubtable(const Font *font, + const uint8_t *data, const size_t length, + const LookupSubtableParser* parser); + +} // namespace ots + +#endif // OTS_LAYOUT_H_ + diff --git a/gfx/ots/src/loca.cc b/gfx/ots/src/loca.cc new file mode 100644 index 000000000..aae04c25a --- /dev/null +++ b/gfx/ots/src/loca.cc @@ -0,0 +1,109 @@ +// 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/typography/otspec/loca.htm + +#define TABLE_NAME "loca" + +namespace ots { + +bool ots_loca_parse(Font *font, 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; + font->loca = loca; + + if (!font->maxp || !font->head) { + return OTS_FAILURE_MSG("maxp or head tables missing from font, needed by loca"); + } + + const unsigned num_glyphs = font->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 (font->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 = 0; + if (!table.ReadU16(&offset)) { + return OTS_FAILURE_MSG("Failed to read offset for glyph %d", i); + } + if (offset < last_offset) { + return OTS_FAILURE_MSG("Out of order offset %d < %d for glyph %d", offset, last_offset, i); + } + last_offset = offset; + loca->offsets[i] = offset * 2; + } + } else { + for (unsigned i = 0; i <= num_glyphs; ++i) { + uint32_t offset = 0; + if (!table.ReadU32(&offset)) { + return OTS_FAILURE_MSG("Failed to read offset for glyph %d", i); + } + if (offset < last_offset) { + return OTS_FAILURE_MSG("Out of order offset %d < %d for glyph %d", offset, last_offset, i); + } + last_offset = offset; + loca->offsets[i] = offset; + } + } + + return true; +} + +bool ots_loca_should_serialise(Font *font) { + return font->loca != NULL; +} + +bool ots_loca_serialise(OTSStream *out, Font *font) { + const OpenTypeLOCA *loca = font->loca; + const OpenTypeHEAD *head = font->head; + + if (!head) { + return OTS_FAILURE_MSG("Missing head table in font needed by loca"); + } + + if (head->index_to_loc_format == 0) { + for (unsigned i = 0; i < loca->offsets.size(); ++i) { + const uint16_t offset = static_cast<uint16_t>(loca->offsets[i] >> 1); + if ((offset != (loca->offsets[i] >> 1)) || + !out->WriteU16(offset)) { + return OTS_FAILURE_MSG("Failed to write glyph offset for glyph %d", i); + } + } + } else { + for (unsigned i = 0; i < loca->offsets.size(); ++i) { + if (!out->WriteU32(loca->offsets[i])) { + return OTS_FAILURE_MSG("Failed to write glyph offset for glyph %d", i); + } + } + } + + return true; +} + +void ots_loca_reuse(Font *font, Font *other) { + font->loca = other->loca; + font->loca_reused = true; +} + +void ots_loca_free(Font *font) { + delete font->loca; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/loca.h b/gfx/ots/src/loca.h new file mode 100644 index 000000000..255ef06ec --- /dev/null +++ b/gfx/ots/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/gfx/ots/src/ltsh.cc b/gfx/ots/src/ltsh.cc new file mode 100644 index 000000000..5b34cf4ad --- /dev/null +++ b/gfx/ots/src/ltsh.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 "ltsh.h" + +#include "maxp.h" + +// LTSH - Linear Threshold +// http://www.microsoft.com/typography/otspec/ltsh.htm + +#define TABLE_NAME "LTSH" + +#define DROP_THIS_TABLE(...) \ + do { \ + OTS_FAILURE_MSG_(font->file, TABLE_NAME ": " __VA_ARGS__); \ + OTS_FAILURE_MSG("Table discarded"); \ + delete font->ltsh; \ + font->ltsh = 0; \ + } while (0) + +namespace ots { + +bool ots_ltsh_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + + if (!font->maxp) { + return OTS_FAILURE_MSG("Missing maxp table from font needed by ltsh"); + } + + OpenTypeLTSH *ltsh = new OpenTypeLTSH; + font->ltsh = ltsh; + + uint16_t num_glyphs = 0; + if (!table.ReadU16(<sh->version) || + !table.ReadU16(&num_glyphs)) { + return OTS_FAILURE_MSG("Failed to read ltsh header"); + } + + if (ltsh->version != 0) { + DROP_THIS_TABLE("bad version: %u", ltsh->version); + return true; + } + + if (num_glyphs != font->maxp->num_glyphs) { + DROP_THIS_TABLE("bad num_glyphs: %u", num_glyphs); + 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_MSG("Failed to read pixels for glyph %d", i); + } + ltsh->ypels.push_back(pel); + } + + return true; +} + +bool ots_ltsh_should_serialise(Font *font) { + if (!font->glyf) return false; // this table is not for CFF fonts. + return font->ltsh != NULL; +} + +bool ots_ltsh_serialise(OTSStream *out, Font *font) { + const OpenTypeLTSH *ltsh = font->ltsh; + + const uint16_t num_ypels = static_cast<uint16_t>(ltsh->ypels.size()); + if (num_ypels != ltsh->ypels.size() || + !out->WriteU16(ltsh->version) || + !out->WriteU16(num_ypels)) { + return OTS_FAILURE_MSG("Failed to write pels size"); + } + for (uint16_t i = 0; i < num_ypels; ++i) { + if (!out->Write(&(ltsh->ypels[i]), 1)) { + return OTS_FAILURE_MSG("Failed to write pixel size for glyph %d", i); + } + } + + return true; +} + +void ots_ltsh_reuse(Font *font, Font *other) { + font->ltsh = other->ltsh; + font->ltsh_reused = true; +} + +void ots_ltsh_free(Font *font) { + delete font->ltsh; +} + +} // namespace ots + +#undef TABLE_NAME +#undef DROP_THIS_TABLE diff --git a/gfx/ots/src/ltsh.h b/gfx/ots/src/ltsh.h new file mode 100644 index 000000000..23d97d784 --- /dev/null +++ b/gfx/ots/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/gfx/ots/src/math.cc b/gfx/ots/src/math.cc new file mode 100644 index 000000000..36417dc29 --- /dev/null +++ b/gfx/ots/src/math.cc @@ -0,0 +1,619 @@ +// Copyright (c) 2014 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. + +// We use an underscore to avoid confusion with the standard math.h library. +#include "math_.h" + +#include <limits> +#include <vector> + +#include "layout.h" +#include "maxp.h" + +// MATH - The MATH Table +// The specification is not yet public but has been submitted to the MPEG group +// in response to the 'Call for Proposals for ISO/IEC 14496-22 "Open Font +// Format" Color Font Technology and MATH layout support'. Meanwhile, you can +// contact Microsoft's engineer Murray Sargent to obtain a copy. + +#define TABLE_NAME "MATH" + +namespace { + +// The size of MATH header. +// Version +// MathConstants +// MathGlyphInfo +// MathVariants +const unsigned kMathHeaderSize = 4 + 3 * 2; + +// The size of the MathGlyphInfo header. +// MathItalicsCorrectionInfo +// MathTopAccentAttachment +// ExtendedShapeCoverage +// MathKernInfo +const unsigned kMathGlyphInfoHeaderSize = 4 * 2; + +// The size of the MathValueRecord. +// Value +// DeviceTable +const unsigned kMathValueRecordSize = 2 * 2; + +// The size of the GlyphPartRecord. +// glyph +// StartConnectorLength +// EndConnectorLength +// FullAdvance +// PartFlags +const unsigned kGlyphPartRecordSize = 5 * 2; + +// Shared Table: MathValueRecord + +bool ParseMathValueRecord(const ots::Font *font, + ots::Buffer* subtable, const uint8_t *data, + const size_t length) { + // Check the Value field. + if (!subtable->Skip(2)) { + return OTS_FAILURE(); + } + + // Check the offset to device table. + uint16_t offset = 0; + if (!subtable->ReadU16(&offset)) { + return OTS_FAILURE(); + } + if (offset) { + if (offset >= length) { + return OTS_FAILURE(); + } + if (!ots::ParseDeviceTable(font, data + offset, length - offset)) { + return OTS_FAILURE(); + } + } + + return true; +} + +bool ParseMathConstantsTable(const ots::Font *font, + const uint8_t *data, size_t length) { + ots::Buffer subtable(data, length); + + // Part 1: int16 or uint16 constants. + // ScriptPercentScaleDown + // ScriptScriptPercentScaleDown + // DelimitedSubFormulaMinHeight + // DisplayOperatorMinHeight + if (!subtable.Skip(4 * 2)) { + return OTS_FAILURE(); + } + + // Part 2: MathValueRecord constants. + // MathLeading + // AxisHeight + // AccentBaseHeight + // FlattenedAccentBaseHeight + // SubscriptShiftDown + // SubscriptTopMax + // SubscriptBaselineDropMin + // SuperscriptShiftUp + // SuperscriptShiftUpCramped + // SuperscriptBottomMin + // + // SuperscriptBaselineDropMax + // SubSuperscriptGapMin + // SuperscriptBottomMaxWithSubscript + // SpaceAfterScript + // UpperLimitGapMin + // UpperLimitBaselineRiseMin + // LowerLimitGapMin + // LowerLimitBaselineDropMin + // StackTopShiftUp + // StackTopDisplayStyleShiftUp + // + // StackBottomShiftDown + // StackBottomDisplayStyleShiftDown + // StackGapMin + // StackDisplayStyleGapMin + // StretchStackTopShiftUp + // StretchStackBottomShiftDown + // StretchStackGapAboveMin + // StretchStackGapBelowMin + // FractionNumeratorShiftUp + // FractionNumeratorDisplayStyleShiftUp + // + // FractionDenominatorShiftDown + // FractionDenominatorDisplayStyleShiftDown + // FractionNumeratorGapMin + // FractionNumDisplayStyleGapMin + // FractionRuleThickness + // FractionDenominatorGapMin + // FractionDenomDisplayStyleGapMin + // SkewedFractionHorizontalGap + // SkewedFractionVerticalGap + // OverbarVerticalGap + // + // OverbarRuleThickness + // OverbarExtraAscender + // UnderbarVerticalGap + // UnderbarRuleThickness + // UnderbarExtraDescender + // RadicalVerticalGap + // RadicalDisplayStyleVerticalGap + // RadicalRuleThickness + // RadicalExtraAscender + // RadicalKernBeforeDegree + // + // RadicalKernAfterDegree + for (unsigned i = 0; i < static_cast<unsigned>(51); ++i) { + if (!ParseMathValueRecord(font, &subtable, data, length)) { + return OTS_FAILURE(); + } + } + + // Part 3: uint16 constant + // RadicalDegreeBottomRaisePercent + if (!subtable.Skip(2)) { + return OTS_FAILURE(); + } + + return true; +} + +bool ParseMathValueRecordSequenceForGlyphs(const ots::Font *font, + ots::Buffer* subtable, + const uint8_t *data, + const size_t length, + const uint16_t num_glyphs) { + // Check the header. + uint16_t offset_coverage = 0; + uint16_t sequence_count = 0; + if (!subtable->ReadU16(&offset_coverage) || + !subtable->ReadU16(&sequence_count)) { + return OTS_FAILURE(); + } + + const unsigned sequence_end = static_cast<unsigned>(2 * 2) + + sequence_count * kMathValueRecordSize; + if (sequence_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE(); + } + + // Check coverage table. + if (offset_coverage < sequence_end || offset_coverage >= length) { + return OTS_FAILURE(); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, + num_glyphs, sequence_count)) { + return OTS_FAILURE(); + } + + // Check sequence. + for (unsigned i = 0; i < sequence_count; ++i) { + if (!ParseMathValueRecord(font, subtable, data, length)) { + return OTS_FAILURE(); + } + } + + return true; +} + +bool ParseMathItalicsCorrectionInfoTable(const ots::Font *font, + const uint8_t *data, + size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + return ParseMathValueRecordSequenceForGlyphs(font, &subtable, data, length, + num_glyphs); +} + +bool ParseMathTopAccentAttachmentTable(const ots::Font *font, + const uint8_t *data, + size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + return ParseMathValueRecordSequenceForGlyphs(font, &subtable, data, length, + num_glyphs); +} + +bool ParseMathKernTable(const ots::Font *font, + const uint8_t *data, size_t length) { + ots::Buffer subtable(data, length); + + // Check the Height count. + uint16_t height_count = 0; + if (!subtable.ReadU16(&height_count)) { + return OTS_FAILURE(); + } + + // Check the Correction Heights. + for (unsigned i = 0; i < height_count; ++i) { + if (!ParseMathValueRecord(font, &subtable, data, length)) { + return OTS_FAILURE(); + } + } + + // Check the Kern Values. + for (unsigned i = 0; i <= height_count; ++i) { + if (!ParseMathValueRecord(font, &subtable, data, length)) { + return OTS_FAILURE(); + } + } + + return true; +} + +bool ParseMathKernInfoTable(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // Check the header. + uint16_t offset_coverage = 0; + uint16_t sequence_count = 0; + if (!subtable.ReadU16(&offset_coverage) || + !subtable.ReadU16(&sequence_count)) { + return OTS_FAILURE(); + } + + const unsigned sequence_end = static_cast<unsigned>(2 * 2) + + sequence_count * 4 * 2; + if (sequence_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE(); + } + + // Check coverage table. + if (offset_coverage < sequence_end || offset_coverage >= length) { + return OTS_FAILURE(); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, length - offset_coverage, + num_glyphs, sequence_count)) { + return OTS_FAILURE(); + } + + // Check sequence of MathKernInfoRecord + for (unsigned i = 0; i < sequence_count; ++i) { + // Check TopRight, TopLeft, BottomRight and BottomLeft Math Kern. + for (unsigned j = 0; j < 4; ++j) { + uint16_t offset_math_kern = 0; + if (!subtable.ReadU16(&offset_math_kern)) { + return OTS_FAILURE(); + } + if (offset_math_kern) { + if (offset_math_kern < sequence_end || offset_math_kern >= length || + !ParseMathKernTable(font, data + offset_math_kern, + length - offset_math_kern)) { + return OTS_FAILURE(); + } + } + } + } + + return true; +} + +bool ParseMathGlyphInfoTable(const ots::Font *font, + const uint8_t *data, size_t length, + const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // Check Header. + uint16_t offset_math_italics_correction_info = 0; + uint16_t offset_math_top_accent_attachment = 0; + uint16_t offset_extended_shaped_coverage = 0; + uint16_t offset_math_kern_info = 0; + if (!subtable.ReadU16(&offset_math_italics_correction_info) || + !subtable.ReadU16(&offset_math_top_accent_attachment) || + !subtable.ReadU16(&offset_extended_shaped_coverage) || + !subtable.ReadU16(&offset_math_kern_info)) { + return OTS_FAILURE(); + } + + // Check subtables. + // The specification does not say whether the offsets for + // MathItalicsCorrectionInfo, MathTopAccentAttachment and MathKernInfo may + // be NULL, but that's the case in some fonts (e.g STIX) so we accept that. + if (offset_math_italics_correction_info) { + if (offset_math_italics_correction_info >= length || + offset_math_italics_correction_info < kMathGlyphInfoHeaderSize || + !ParseMathItalicsCorrectionInfoTable( + font, data + offset_math_italics_correction_info, + length - offset_math_italics_correction_info, + num_glyphs)) { + return OTS_FAILURE(); + } + } + if (offset_math_top_accent_attachment) { + if (offset_math_top_accent_attachment >= length || + offset_math_top_accent_attachment < kMathGlyphInfoHeaderSize || + !ParseMathTopAccentAttachmentTable(font, data + + offset_math_top_accent_attachment, + length - + offset_math_top_accent_attachment, + num_glyphs)) { + return OTS_FAILURE(); + } + } + if (offset_extended_shaped_coverage) { + if (offset_extended_shaped_coverage >= length || + offset_extended_shaped_coverage < kMathGlyphInfoHeaderSize || + !ots::ParseCoverageTable(font, data + offset_extended_shaped_coverage, + length - offset_extended_shaped_coverage, + num_glyphs)) { + return OTS_FAILURE(); + } + } + if (offset_math_kern_info) { + if (offset_math_kern_info >= length || + offset_math_kern_info < kMathGlyphInfoHeaderSize || + !ParseMathKernInfoTable(font, data + offset_math_kern_info, + length - offset_math_kern_info, num_glyphs)) { + return OTS_FAILURE(); + } + } + + return true; +} + +bool ParseGlyphAssemblyTable(const ots::Font *font, + const uint8_t *data, + size_t length, const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // Check the header. + uint16_t part_count = 0; + if (!ParseMathValueRecord(font, &subtable, data, length) || + !subtable.ReadU16(&part_count)) { + return OTS_FAILURE(); + } + + const unsigned sequence_end = kMathValueRecordSize + + static_cast<unsigned>(2) + part_count * kGlyphPartRecordSize; + if (sequence_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE(); + } + + // Check the sequence of GlyphPartRecord. + for (unsigned i = 0; i < part_count; ++i) { + uint16_t glyph = 0; + uint16_t part_flags = 0; + if (!subtable.ReadU16(&glyph) || + !subtable.Skip(2 * 3) || + !subtable.ReadU16(&part_flags)) { + return OTS_FAILURE(); + } + if (glyph >= num_glyphs) { + return OTS_FAILURE_MSG("bad glyph ID: %u", glyph); + } + if (part_flags & ~0x00000001) { + return OTS_FAILURE_MSG("unknown part flag: %u", part_flags); + } + } + + return true; +} + +bool ParseMathGlyphConstructionTable(const ots::Font *font, + const uint8_t *data, + size_t length, const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // Check the header. + uint16_t offset_glyph_assembly = 0; + uint16_t variant_count = 0; + if (!subtable.ReadU16(&offset_glyph_assembly) || + !subtable.ReadU16(&variant_count)) { + return OTS_FAILURE(); + } + + const unsigned sequence_end = static_cast<unsigned>(2 * 2) + + variant_count * 2 * 2; + if (sequence_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE(); + } + + // Check the GlyphAssembly offset. + if (offset_glyph_assembly) { + if (offset_glyph_assembly >= length || + offset_glyph_assembly < sequence_end) { + return OTS_FAILURE(); + } + if (!ParseGlyphAssemblyTable(font, data + offset_glyph_assembly, + length - offset_glyph_assembly, num_glyphs)) { + return OTS_FAILURE(); + } + } + + // Check the sequence of MathGlyphVariantRecord. + for (unsigned i = 0; i < variant_count; ++i) { + uint16_t glyph = 0; + if (!subtable.ReadU16(&glyph) || + !subtable.Skip(2)) { + return OTS_FAILURE(); + } + if (glyph >= num_glyphs) { + return OTS_FAILURE_MSG("bad glyph ID: %u", glyph); + } + } + + return true; +} + +bool ParseMathGlyphConstructionSequence(const ots::Font *font, + ots::Buffer* subtable, + const uint8_t *data, + size_t length, + const uint16_t num_glyphs, + uint16_t offset_coverage, + uint16_t glyph_count, + const unsigned sequence_end) { + // Zero glyph count, nothing to parse. + if (!glyph_count) { + return true; + } + + // Check coverage table. + if (offset_coverage < sequence_end || offset_coverage >= length) { + return OTS_FAILURE(); + } + if (!ots::ParseCoverageTable(font, data + offset_coverage, + length - offset_coverage, + num_glyphs, glyph_count)) { + return OTS_FAILURE(); + } + + // Check sequence of MathGlyphConstruction. + for (unsigned i = 0; i < glyph_count; ++i) { + uint16_t offset_glyph_construction = 0; + if (!subtable->ReadU16(&offset_glyph_construction)) { + return OTS_FAILURE(); + } + if (offset_glyph_construction < sequence_end || + offset_glyph_construction >= length || + !ParseMathGlyphConstructionTable(font, data + offset_glyph_construction, + length - offset_glyph_construction, + num_glyphs)) { + return OTS_FAILURE(); + } + } + + return true; +} + +bool ParseMathVariantsTable(const ots::Font *font, + const uint8_t *data, + size_t length, const uint16_t num_glyphs) { + ots::Buffer subtable(data, length); + + // Check the header. + uint16_t offset_vert_glyph_coverage = 0; + uint16_t offset_horiz_glyph_coverage = 0; + uint16_t vert_glyph_count = 0; + uint16_t horiz_glyph_count = 0; + if (!subtable.Skip(2) || // MinConnectorOverlap + !subtable.ReadU16(&offset_vert_glyph_coverage) || + !subtable.ReadU16(&offset_horiz_glyph_coverage) || + !subtable.ReadU16(&vert_glyph_count) || + !subtable.ReadU16(&horiz_glyph_count)) { + return OTS_FAILURE(); + } + + const unsigned sequence_end = 5 * 2 + vert_glyph_count * 2 + + horiz_glyph_count * 2; + if (sequence_end > std::numeric_limits<uint16_t>::max()) { + return OTS_FAILURE(); + } + + if (!ParseMathGlyphConstructionSequence(font, &subtable, data, length, num_glyphs, + offset_vert_glyph_coverage, + vert_glyph_count, + sequence_end) || + !ParseMathGlyphConstructionSequence(font, &subtable, data, length, num_glyphs, + offset_horiz_glyph_coverage, + horiz_glyph_count, + sequence_end)) { + return OTS_FAILURE(); + } + + return true; +} + +} // namespace + +#define DROP_THIS_TABLE(msg_) \ + do { \ + OTS_FAILURE_MSG(msg_ ", table discarded"); \ + font->math->data = 0; \ + font->math->length = 0; \ + } while (0) + +namespace ots { + +bool ots_math_parse(Font *font, const uint8_t *data, size_t length) { + // Grab the number of glyphs in the font from the maxp table to check + // GlyphIDs in MATH table. + if (!font->maxp) { + return OTS_FAILURE(); + } + const uint16_t num_glyphs = font->maxp->num_glyphs; + + Buffer table(data, length); + + OpenTypeMATH* math = new OpenTypeMATH; + font->math = math; + + uint32_t version = 0; + if (!table.ReadU32(&version)) { + return OTS_FAILURE(); + } + if (version != 0x00010000) { + DROP_THIS_TABLE("bad MATH version"); + return true; + } + + uint16_t offset_math_constants = 0; + uint16_t offset_math_glyph_info = 0; + uint16_t offset_math_variants = 0; + if (!table.ReadU16(&offset_math_constants) || + !table.ReadU16(&offset_math_glyph_info) || + !table.ReadU16(&offset_math_variants)) { + return OTS_FAILURE(); + } + + if (offset_math_constants >= length || + offset_math_constants < kMathHeaderSize || + offset_math_glyph_info >= length || + offset_math_glyph_info < kMathHeaderSize || + offset_math_variants >= length || + offset_math_variants < kMathHeaderSize) { + DROP_THIS_TABLE("bad offset in MATH header"); + return true; + } + + if (!ParseMathConstantsTable(font, data + offset_math_constants, + length - offset_math_constants)) { + DROP_THIS_TABLE("failed to parse MathConstants table"); + return true; + } + if (!ParseMathGlyphInfoTable(font, data + offset_math_glyph_info, + length - offset_math_glyph_info, num_glyphs)) { + DROP_THIS_TABLE("failed to parse MathGlyphInfo table"); + return true; + } + if (!ParseMathVariantsTable(font, data + offset_math_variants, + length - offset_math_variants, num_glyphs)) { + DROP_THIS_TABLE("failed to parse MathVariants table"); + return true; + } + + math->data = data; + math->length = length; + return true; +} + +bool ots_math_should_serialise(Font *font) { + return font->math != NULL && font->math->data != NULL; +} + +bool ots_math_serialise(OTSStream *out, Font *font) { + if (!out->Write(font->math->data, font->math->length)) { + return OTS_FAILURE(); + } + + return true; +} + +void ots_math_reuse(Font *font, Font *other) { + font->math = other->math; + font->math_reused = true; +} + +void ots_math_free(Font *font) { + delete font->math; +} + +} // namespace ots + +#undef TABLE_NAME +#undef DROP_THIS_TABLE diff --git a/gfx/ots/src/math_.h b/gfx/ots/src/math_.h new file mode 100644 index 000000000..91c54dbe1 --- /dev/null +++ b/gfx/ots/src/math_.h @@ -0,0 +1,25 @@ +// Copyright (c) 2014 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_MATH_H_ +#define OTS_MATH_H_ + +#include "ots.h" + +namespace ots { + +struct OpenTypeMATH { + OpenTypeMATH() + : data(NULL), + length(0) { + } + + const uint8_t *data; + size_t length; +}; + +} // namespace ots + +#endif + diff --git a/gfx/ots/src/maxp.cc b/gfx/ots/src/maxp.cc new file mode 100644 index 000000000..41e29a745 --- /dev/null +++ b/gfx/ots/src/maxp.cc @@ -0,0 +1,125 @@ +// 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/typography/otspec/maxp.htm + +#define TABLE_NAME "maxp" + +namespace ots { + +bool ots_maxp_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypeMAXP *maxp = new OpenTypeMAXP; + font->maxp = maxp; + + uint32_t version = 0; + if (!table.ReadU32(&version)) { + return OTS_FAILURE_MSG("Failed to read version of maxp table"); + } + + if (version >> 16 > 1) { + return OTS_FAILURE_MSG("Bad maxp version %d", version); + } + + if (!table.ReadU16(&maxp->num_glyphs)) { + return OTS_FAILURE_MSG("Failed to read number of glyphs from maxp table"); + } + + if (!maxp->num_glyphs) { + return OTS_FAILURE_MSG("Bad number of glyphs 0 in maxp table"); + } + + 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_size_glyf_instructions) || + !table.ReadU16(&maxp->max_c_components) || + !table.ReadU16(&maxp->max_c_depth)) { + return OTS_FAILURE_MSG("Failed to read maxp table"); + } + + 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_MSG("Bad max zones %d in maxp", maxp->max_zones); + } + } else { + maxp->version_1 = false; + } + + return true; +} + +bool ots_maxp_should_serialise(Font *font) { + return font->maxp != NULL; +} + +bool ots_maxp_serialise(OTSStream *out, Font *font) { + const OpenTypeMAXP *maxp = font->maxp; + + if (!out->WriteU32(maxp->version_1 ? 0x00010000 : 0x00005000) || + !out->WriteU16(maxp->num_glyphs)) { + return OTS_FAILURE_MSG("Failed to write maxp version or number of glyphs"); + } + + 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_MSG("Failed to write maxp"); + } + + 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_size_glyf_instructions)) { + return OTS_FAILURE_MSG("Failed to write more maxp"); + } + + if (!out->WriteU16(maxp->max_c_components) || + !out->WriteU16(maxp->max_c_depth)) { + return OTS_FAILURE_MSG("Failed to write yet more maxp"); + } + + return true; +} + +void ots_maxp_reuse(Font *font, Font *other) { + font->maxp = other->maxp; + font->maxp_reused = true; +} + +void ots_maxp_free(Font *font) { + delete font->maxp; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/maxp.h b/gfx/ots/src/maxp.h new file mode 100644 index 000000000..efca0c91a --- /dev/null +++ b/gfx/ots/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_size_glyf_instructions; + + uint16_t max_c_components; + uint16_t max_c_depth; +}; + +} // namespace ots + +#endif // OTS_MAXP_H_ diff --git a/gfx/ots/src/metrics.cc b/gfx/ots/src/metrics.cc new file mode 100644 index 000000000..cd89f4555 --- /dev/null +++ b/gfx/ots/src/metrics.cc @@ -0,0 +1,165 @@ +// Copyright (c) 2011 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 "metrics.h" + +#include "head.h" +#include "maxp.h" + +// OpenType horizontal and vertical common header format +// http://www.microsoft.com/typography/otspec/hhea.htm +// http://www.microsoft.com/typography/otspec/vhea.htm + +#define TABLE_NAME "metrics" // XXX: use individual table names + +namespace ots { + +bool ParseMetricsHeader(Font *font, Buffer *table, + OpenTypeMetricsHeader *header) { + if (!table->ReadS16(&header->ascent) || + !table->ReadS16(&header->descent) || + !table->ReadS16(&header->linegap) || + !table->ReadU16(&header->adv_width_max) || + !table->ReadS16(&header->min_sb1) || + !table->ReadS16(&header->min_sb2) || + !table->ReadS16(&header->max_extent) || + !table->ReadS16(&header->caret_slope_rise) || + !table->ReadS16(&header->caret_slope_run) || + !table->ReadS16(&header->caret_offset)) { + return OTS_FAILURE_MSG("Failed to read metrics header"); + } + + if (header->ascent < 0) { + OTS_WARNING("bad ascent: %d", header->ascent); + header->ascent = 0; + } + if (header->linegap < 0) { + OTS_WARNING("bad linegap: %d", header->linegap); + header->linegap = 0; + } + + if (!font->head) { + return OTS_FAILURE_MSG("Missing head font table"); + } + + // if the font is non-slanted, caret_offset should be zero. + if (!(font->head->mac_style & 2) && + (header->caret_offset != 0)) { + OTS_WARNING("bad caret offset: %d", header->caret_offset); + header->caret_offset = 0; + } + + // skip the reserved bytes + if (!table->Skip(8)) { + return OTS_FAILURE_MSG("Failed to skip reserverd bytes"); + } + + int16_t data_format; + if (!table->ReadS16(&data_format)) { + return OTS_FAILURE_MSG("Failed to read data format"); + } + if (data_format) { + return OTS_FAILURE_MSG("Bad data format %d", data_format); + } + + if (!table->ReadU16(&header->num_metrics)) { + return OTS_FAILURE_MSG("Failed to read number of metrics"); + } + + if (!font->maxp) { + return OTS_FAILURE_MSG("Missing maxp font table"); + } + + if (header->num_metrics > font->maxp->num_glyphs) { + return OTS_FAILURE_MSG("Bad number of metrics %d", header->num_metrics); + } + + return true; +} + +bool SerialiseMetricsHeader(const ots::Font *font, + OTSStream *out, + const OpenTypeMetricsHeader *header) { + if (!out->WriteU32(header->version) || + !out->WriteS16(header->ascent) || + !out->WriteS16(header->descent) || + !out->WriteS16(header->linegap) || + !out->WriteU16(header->adv_width_max) || + !out->WriteS16(header->min_sb1) || + !out->WriteS16(header->min_sb2) || + !out->WriteS16(header->max_extent) || + !out->WriteS16(header->caret_slope_rise) || + !out->WriteS16(header->caret_slope_run) || + !out->WriteS16(header->caret_offset) || + !out->WriteR64(0) || // reserved + !out->WriteS16(0) || // metric data format + !out->WriteU16(header->num_metrics)) { + return OTS_FAILURE_MSG("Failed to write metrics"); + } + + return true; +} + +bool ParseMetricsTable(const ots::Font *font, + Buffer *table, + const uint16_t num_glyphs, + const OpenTypeMetricsHeader *header, + OpenTypeMetricsTable *metrics) { + // |num_metrics| 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_metrics = header->num_metrics; + + if (num_metrics > num_glyphs) { + return OTS_FAILURE_MSG("Bad number of metrics %d", num_metrics); + } + if (!num_metrics) { + return OTS_FAILURE_MSG("No metrics!"); + } + const unsigned num_sbs = num_glyphs - num_metrics; + + metrics->entries.reserve(num_metrics); + for (unsigned i = 0; i < num_metrics; ++i) { + uint16_t adv = 0; + int16_t sb = 0; + if (!table->ReadU16(&adv) || !table->ReadS16(&sb)) { + return OTS_FAILURE_MSG("Failed to read metric %d", i); + } + metrics->entries.push_back(std::make_pair(adv, sb)); + } + + metrics->sbs.reserve(num_sbs); + for (unsigned i = 0; i < num_sbs; ++i) { + int16_t sb; + if (!table->ReadS16(&sb)) { + // Some Japanese fonts (e.g., mona.ttf) fail this test. + return OTS_FAILURE_MSG("Failed to read side bearing %d", i + num_metrics); + } + metrics->sbs.push_back(sb); + } + + return true; +} + +bool SerialiseMetricsTable(const ots::Font *font, + OTSStream *out, + const OpenTypeMetricsTable *metrics) { + for (unsigned i = 0; i < metrics->entries.size(); ++i) { + if (!out->WriteU16(metrics->entries[i].first) || + !out->WriteS16(metrics->entries[i].second)) { + return OTS_FAILURE_MSG("Failed to write metric %d", i); + } + } + + for (unsigned i = 0; i < metrics->sbs.size(); ++i) { + if (!out->WriteS16(metrics->sbs[i])) { + return OTS_FAILURE_MSG("Failed to write side bearing %ld", i + metrics->entries.size()); + } + } + + return true; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/metrics.h b/gfx/ots/src/metrics.h new file mode 100644 index 000000000..767be2fdb --- /dev/null +++ b/gfx/ots/src/metrics.h @@ -0,0 +1,54 @@ +// Copyright (c) 2011 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_METRICS_H_ +#define OTS_METRICS_H_ + +#include <new> +#include <utility> +#include <vector> + +#include "ots.h" + +namespace ots { + +struct OpenTypeMetricsHeader { + uint32_t version; + int16_t ascent; + int16_t descent; + int16_t linegap; + uint16_t adv_width_max; + int16_t min_sb1; + int16_t min_sb2; + int16_t max_extent; + int16_t caret_slope_rise; + int16_t caret_slope_run; + int16_t caret_offset; + uint16_t num_metrics; +}; + +struct OpenTypeMetricsTable { + std::vector<std::pair<uint16_t, int16_t> > entries; + std::vector<int16_t> sbs; +}; + +bool ParseMetricsHeader(Font *font, Buffer *table, + OpenTypeMetricsHeader *header); +bool SerialiseMetricsHeader(const ots::Font *font, + OTSStream *out, + const OpenTypeMetricsHeader *header); + +bool ParseMetricsTable(const ots::Font *font, + Buffer *table, + const uint16_t num_glyphs, + const OpenTypeMetricsHeader *header, + OpenTypeMetricsTable *metrics); +bool SerialiseMetricsTable(const ots::Font *font, + OTSStream *out, + const OpenTypeMetricsTable *metrics); + +} // namespace ots + +#endif // OTS_METRICS_H_ + diff --git a/gfx/ots/src/moz.build b/gfx/ots/src/moz.build new file mode 100644 index 000000000..f8e60d5f8 --- /dev/null +++ b/gfx/ots/src/moz.build @@ -0,0 +1,60 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + '../include/opentype-sanitiser.h', + '../include/ots-memory-stream.h', +] + +SOURCES += [ + # don't unify sources that use a (file-specific) DROP_THIS_TABLE macro + 'gasp.cc', + 'gdef.cc', + 'gpos.cc', + 'gsub.cc', + 'hdmx.cc', + 'kern.cc', + 'ltsh.cc', + 'math.cc', + 'vdmx.cc', + 'vorg.cc', +] + +UNIFIED_SOURCES += [ + 'cff.cc', + 'cff_type2_charstring.cc', + 'cmap.cc', + 'cvt.cc', + 'fpgm.cc', + 'glyf.cc', + 'head.cc', + 'hhea.cc', + 'hmtx.cc', + 'layout.cc', + 'loca.cc', + 'maxp.cc', + 'metrics.cc', + 'name.cc', + 'os2.cc', + 'ots.cc', + 'post.cc', + 'prep.cc', + 'vhea.cc', + 'vmtx.cc', +] + +# We allow warnings for third-party code that can be updated from upstream. +ALLOW_COMPILER_WARNINGS = True + +FINAL_LIBRARY = 'gkmedias' + +DEFINES['PACKAGE_VERSION'] = '"moz"' +DEFINES['PACKAGE_BUGREPORT'] = '"http://bugzilla.mozilla.org/"' + +USE_LIBS += [ + 'brotli', + 'woff2', +] diff --git a/gfx/ots/src/name.cc b/gfx/ots/src/name.cc new file mode 100644 index 000000000..e55be7537 --- /dev/null +++ b/gfx/ots/src/name.cc @@ -0,0 +1,330 @@ +// Copyright (c) 2011 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 "name.h" + +#include <algorithm> +#include <cstring> + +// name - Naming Table +// http://www.microsoft.com/typography/otspec/name.htm + +#define TABLE_NAME "name" + +namespace { + +bool ValidInPsName(char c) { + return (c > 0x20 && c < 0x7f && !std::strchr("[](){}<>/%", c)); +} + +bool CheckPsNameAscii(const std::string& name) { + for (unsigned i = 0; i < name.size(); ++i) { + if (!ValidInPsName(name[i])) { + return false; + } + } + return true; +} + +bool CheckPsNameUtf16Be(const std::string& name) { + if ((name.size() & 1) != 0) + return false; + + for (unsigned i = 0; i < name.size(); i += 2) { + if (name[i] != 0) { + return false; + } + if (!ValidInPsName(name[i+1])) { + return false; + } + } + return true; +} + +void AssignToUtf16BeFromAscii(std::string* target, + const std::string& source) { + target->resize(source.size() * 2); + for (unsigned i = 0, j = 0; i < source.size(); i++) { + (*target)[j++] = '\0'; + (*target)[j++] = source[i]; + } +} + +} // namespace + + +namespace ots { + +bool ots_name_parse(Font *font, const uint8_t* data, size_t length) { + Buffer table(data, length); + + OpenTypeNAME* name = new OpenTypeNAME; + font->name = name; + + uint16_t format = 0; + if (!table.ReadU16(&format) || format > 1) { + return OTS_FAILURE_MSG("Failed to read name table format or bad format %d", format); + } + + uint16_t count = 0; + if (!table.ReadU16(&count)) { + return OTS_FAILURE_MSG("Failed to read name count"); + } + + uint16_t string_offset = 0; + if (!table.ReadU16(&string_offset) || string_offset > length) { + return OTS_FAILURE_MSG("Failed to read strings offset"); + } + const char* string_base = reinterpret_cast<const char*>(data) + + string_offset; + + bool sort_required = false; + + // Read all the names, discarding any with invalid IDs, + // and any where the offset/length would be outside the table. + // A stricter alternative would be to reject the font if there + // are invalid name records, but it's not clear that is necessary. + for (unsigned i = 0; i < count; ++i) { + NameRecord rec; + uint16_t name_length, name_offset = 0; + if (!table.ReadU16(&rec.platform_id) || + !table.ReadU16(&rec.encoding_id) || + !table.ReadU16(&rec.language_id) || + !table.ReadU16(&rec.name_id) || + !table.ReadU16(&name_length) || + !table.ReadU16(&name_offset)) { + return OTS_FAILURE_MSG("Failed to read name entry %d", i); + } + // check platform & encoding, discard names with unknown values + switch (rec.platform_id) { + case 0: // Unicode + if (rec.encoding_id > 6) { + continue; + } + break; + case 1: // Macintosh + if (rec.encoding_id > 32) { + continue; + } + break; + case 2: // ISO + if (rec.encoding_id > 2) { + continue; + } + break; + case 3: // Windows: IDs 7 to 9 are "reserved" + if (rec.encoding_id > 6 && rec.encoding_id != 10) { + continue; + } + break; + case 4: // Custom (OTF Windows NT compatibility) + if (rec.encoding_id > 255) { + continue; + } + break; + default: // unknown platform + continue; + } + + const unsigned name_end = static_cast<unsigned>(string_offset) + + name_offset + name_length; + if (name_end > length) { + continue; + } + rec.text.resize(name_length); + rec.text.assign(string_base + name_offset, name_length); + + if (rec.name_id == 6) { + // PostScript name: check that it is valid, if not then discard it + if (rec.platform_id == 1) { + if (!CheckPsNameAscii(rec.text)) { + continue; + } + } else if (rec.platform_id == 0 || rec.platform_id == 3) { + if (!CheckPsNameUtf16Be(rec.text)) { + continue; + } + } + } + + if (!name->names.empty() && !(name->names.back() < rec)) { + OTS_WARNING("name records are not sorted."); + sort_required = true; + } + + name->names.push_back(rec); + } + + if (format == 1) { + // extended name table format with language tags + uint16_t lang_tag_count; + if (!table.ReadU16(&lang_tag_count)) { + return OTS_FAILURE_MSG("Failed to read language tag count"); + } + for (unsigned i = 0; i < lang_tag_count; ++i) { + uint16_t tag_length = 0; + uint16_t tag_offset = 0; + if (!table.ReadU16(&tag_length) || !table.ReadU16(&tag_offset)) { + return OTS_FAILURE_MSG("Faile to read tag length or offset"); + } + const unsigned tag_end = static_cast<unsigned>(string_offset) + + tag_offset + tag_length; + if (tag_end > length) { + return OTS_FAILURE_MSG("bad end of tag %d > %ld for name entry %d", tag_end, length, i); + } + std::string tag(string_base + tag_offset, tag_length); + name->lang_tags.push_back(tag); + } + } + + if (table.offset() > string_offset) { + // the string storage apparently overlapped the name/tag records; + // consider this font to be badly broken + return OTS_FAILURE_MSG("Bad table offset %ld > %d", table.offset(), string_offset); + } + + // check existence of required name strings (synthesize if necessary) + // [0 - copyright - skip] + // 1 - family + // 2 - subfamily + // [3 - unique ID - skip] + // 4 - full name + // 5 - version + // 6 - postscript name + static const uint16_t kStdNameCount = 7; + static const char* kStdNames[kStdNameCount] = { + NULL, + "OTS derived font", + "Unspecified", + NULL, + "OTS derived font", + "1.000", + "OTS-derived-font" + }; + + // scan the names to check whether the required "standard" ones are present; + // if not, we'll add our fixed versions here + bool mac_name[kStdNameCount] = { 0 }; + bool win_name[kStdNameCount] = { 0 }; + for (std::vector<NameRecord>::iterator name_iter = name->names.begin(); + name_iter != name->names.end(); ++name_iter) { + const uint16_t id = name_iter->name_id; + if (id >= kStdNameCount || kStdNames[id] == NULL) { + continue; + } + if (name_iter->platform_id == 1) { + mac_name[id] = true; + continue; + } + if (name_iter->platform_id == 3) { + win_name[id] = true; + continue; + } + } + + for (uint16_t i = 0; i < kStdNameCount; ++i) { + if (kStdNames[i] == NULL) { + continue; + } + if (!mac_name[i] && !win_name[i]) { + NameRecord mac_rec(1 /* platform_id */, 0 /* encoding_id */, + 0 /* language_id */ , i /* name_id */); + mac_rec.text.assign(kStdNames[i]); + + NameRecord win_rec(3 /* platform_id */, 1 /* encoding_id */, + 1033 /* language_id */ , i /* name_id */); + AssignToUtf16BeFromAscii(&win_rec.text, std::string(kStdNames[i])); + + name->names.push_back(mac_rec); + name->names.push_back(win_rec); + sort_required = true; + } + } + + if (sort_required) { + std::sort(name->names.begin(), name->names.end()); + } + + return true; +} + +bool ots_name_should_serialise(Font *font) { + return font->name != NULL; +} + +bool ots_name_serialise(OTSStream* out, Font *font) { + const OpenTypeNAME* name = font->name; + + uint16_t name_count = static_cast<uint16_t>(name->names.size()); + uint16_t lang_tag_count = static_cast<uint16_t>(name->lang_tags.size()); + uint16_t format = 0; + size_t string_offset = 6 + name_count * 12; + + if (name->lang_tags.size() > 0) { + // lang tags require a format-1 name table + format = 1; + string_offset += 2 + lang_tag_count * 4; + } + if (string_offset > 0xffff) { + return OTS_FAILURE_MSG("Bad string offset %ld", string_offset); + } + if (!out->WriteU16(format) || + !out->WriteU16(name_count) || + !out->WriteU16(static_cast<uint16_t>(string_offset))) { + return OTS_FAILURE_MSG("Failed to write name header"); + } + + std::string string_data; + for (std::vector<NameRecord>::const_iterator name_iter = name->names.begin(); + name_iter != name->names.end(); ++name_iter) { + const NameRecord& rec = *name_iter; + if (string_data.size() + rec.text.size() > + std::numeric_limits<uint16_t>::max() || + !out->WriteU16(rec.platform_id) || + !out->WriteU16(rec.encoding_id) || + !out->WriteU16(rec.language_id) || + !out->WriteU16(rec.name_id) || + !out->WriteU16(static_cast<uint16_t>(rec.text.size())) || + !out->WriteU16(static_cast<uint16_t>(string_data.size())) ) { + return OTS_FAILURE_MSG("Faile to write name entry"); + } + string_data.append(rec.text); + } + + if (format == 1) { + if (!out->WriteU16(lang_tag_count)) { + return OTS_FAILURE_MSG("Faile to write language tag count"); + } + for (std::vector<std::string>::const_iterator tag_iter = + name->lang_tags.begin(); + tag_iter != name->lang_tags.end(); ++tag_iter) { + if (string_data.size() + tag_iter->size() > + std::numeric_limits<uint16_t>::max() || + !out->WriteU16(static_cast<uint16_t>(tag_iter->size())) || + !out->WriteU16(static_cast<uint16_t>(string_data.size()))) { + return OTS_FAILURE_MSG("Failed to write string"); + } + string_data.append(*tag_iter); + } + } + + if (!out->Write(string_data.data(), string_data.size())) { + return OTS_FAILURE_MSG("Faile to write string data"); + } + + return true; +} + +void ots_name_reuse(Font *font, Font *other) { + font->name = other->name; + font->name_reused = true; +} + +void ots_name_free(Font *font) { + delete font->name; +} + +} // namespace + +#undef TABLE_NAME diff --git a/gfx/ots/src/name.h b/gfx/ots/src/name.h new file mode 100644 index 000000000..a11965f46 --- /dev/null +++ b/gfx/ots/src/name.h @@ -0,0 +1,53 @@ +// Copyright (c) 2011 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_NAME_H_ +#define OTS_NAME_H_ + +#include <new> +#include <string> +#include <utility> +#include <vector> + +#include "ots.h" + +namespace ots { + +struct NameRecord { + NameRecord() { + } + + NameRecord(uint16_t platformID, uint16_t encodingID, + uint16_t languageID, uint16_t nameID) + : platform_id(platformID), + encoding_id(encodingID), + language_id(languageID), + name_id(nameID) { + } + + uint16_t platform_id; + uint16_t encoding_id; + uint16_t language_id; + uint16_t name_id; + std::string text; + + bool operator<(const NameRecord& rhs) const { + if (platform_id < rhs.platform_id) return true; + if (platform_id > rhs.platform_id) return false; + if (encoding_id < rhs.encoding_id) return true; + if (encoding_id > rhs.encoding_id) return false; + if (language_id < rhs.language_id) return true; + if (language_id > rhs.language_id) return false; + return name_id < rhs.name_id; + } +}; + +struct OpenTypeNAME { + std::vector<NameRecord> names; + std::vector<std::string> lang_tags; +}; + +} // namespace ots + +#endif // OTS_NAME_H_ diff --git a/gfx/ots/src/os2.cc b/gfx/ots/src/os2.cc new file mode 100644 index 000000000..fd5cdd1d6 --- /dev/null +++ b/gfx/ots/src/os2.cc @@ -0,0 +1,336 @@ +// 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 <string> + +#include "os2.h" +#include "head.h" + +// OS/2 - OS/2 and Windows Metrics +// http://www.microsoft.com/typography/otspec/os2.htm + +#define TABLE_NAME "OS/2" + +namespace ots { + +bool ots_os2_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypeOS2 *os2 = new OpenTypeOS2; + font->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_MSG("Error reading basic table elements"); + } + + if (os2->version > 5) { + return OTS_FAILURE_MSG("Unsupported table version: %u", os2->version); + } + + // Follow WPF Font Selection Model's advice. + if (1 <= os2->weight_class && os2->weight_class <= 9) { + OTS_WARNING("Bad usWeightClass: %u, changing it to: %u", os2->weight_class, os2->weight_class * 100); + os2->weight_class *= 100; + } + // Ditto. + if (os2->weight_class > 999) { + OTS_WARNING("Bad usWeightClass: %u, changing it to: %d", os2->weight_class, 999); + os2->weight_class = 999; + } + + if (os2->width_class < 1) { + OTS_WARNING("Bad usWidthClass: %u, changing it to: %d", os2->width_class, 1); + os2->width_class = 1; + } else if (os2->width_class > 9) { + OTS_WARNING("Bad usWidthClass: %u, changing it to: %d", os2->width_class, 9); + 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; + +#define SET_TO_ZERO(a, b) \ + if (os2->b < 0) { \ + OTS_WARNING("Bad " a ": %d, setting it to zero", os2->b); \ + os2->b = 0; \ + } + + SET_TO_ZERO("ySubscriptXSize", subscript_x_size); + SET_TO_ZERO("ySubscriptYSize", subscript_y_size); + SET_TO_ZERO("ySuperscriptXSize", superscript_x_size); + SET_TO_ZERO("ySuperscriptYSize", superscript_y_size); + SET_TO_ZERO("yStrikeoutSize", strikeout_size); +#undef SET_TO_ZERO + + static std::string panose_strings[10] = { + "bFamilyType", + "bSerifStyle", + "bWeight", + "bProportion", + "bContrast", + "bStrokeVariation", + "bArmStyle", + "bLetterform", + "bMidline", + "bXHeight", + }; + for (unsigned i = 0; i < 10; ++i) { + if (!table.ReadU8(&os2->panose[i])) { + return OTS_FAILURE_MSG("Error reading PANOSE %s", panose_strings[i].c_str()); + } + } + + 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_MSG("Error reading more basic table fields"); + } + + // 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 (!font->head) { + return OTS_FAILURE_MSG("Needed head table is missing from the font"); + } + if ((os2->selection & 0x1) && + !(font->head->mac_style & 0x2)) { + OTS_WARNING("adjusting Mac style (italic)"); + font->head->mac_style |= 0x2; + } + if ((os2->selection & 0x2) && + !(font->head->mac_style & 0x4)) { + OTS_WARNING("adjusting Mac style (underscore)"); + font->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) && + (font->head->mac_style & 0x3)) { + OTS_WARNING("adjusting Mac style (regular)"); + font->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_MSG("Version %d incompatible with selection %d", os2->version, os2->selection); + } + + // mask reserved bits. use only 0..9 bits. + os2->selection &= 0x3ff; + + if (os2->first_char_index > os2->last_char_index) { + return OTS_FAILURE_MSG("first char index %d > last char index %d in os2", os2->first_char_index, os2->last_char_index); + } + 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_MSG("Failed to read codepage ranges"); + } + + 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_MSG("Failed to read version 2-specific fields"); + } + + 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; + } + + if (os2->version < 5) { + // http://www.microsoft.com/typography/otspec/os2ver4.htm + return true; + } + + if (!table.ReadU16(&os2->lower_optical_pointsize) || + !table.ReadU16(&os2->upper_optical_pointsize)) { + return OTS_FAILURE_MSG("Failed to read version 5-specific fields"); + } + + if (os2->lower_optical_pointsize > 0xFFFE) { + OTS_WARNING("'usLowerOpticalPointSize' is bigger than 0xFFFE: %d", os2->lower_optical_pointsize); + os2->lower_optical_pointsize = 0xFFFE; + } + + if (os2->upper_optical_pointsize < 2) { + OTS_WARNING("'usUpperOpticalPointSize' is lower than 2: %d", os2->upper_optical_pointsize); + os2->upper_optical_pointsize = 2; + } + + return true; +} + +bool ots_os2_should_serialise(Font *font) { + return font->os2 != NULL; +} + +bool ots_os2_serialise(OTSStream *out, Font *font) { + const OpenTypeOS2 *os2 = font->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_MSG("Failed to write basic OS2 information"); + } + + for (unsigned i = 0; i < 10; ++i) { + if (!out->Write(&os2->panose[i], 1)) { + return OTS_FAILURE_MSG("Failed to write os2 panose information"); + } + } + + 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_MSG("Failed to write version 1-specific fields"); + } + + if (os2->version < 1) { + return true; + } + + if (!out->WriteU32(os2->code_page_range_1) || + !out->WriteU32(os2->code_page_range_2)) { + return OTS_FAILURE_MSG("Failed to write codepage ranges"); + } + + 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_MSG("Failed to write version 2-specific fields"); + } + + if (os2->version < 5) { + return true; + } + + if (!out->WriteU16(os2->lower_optical_pointsize) || + !out->WriteU16(os2->upper_optical_pointsize)) { + return OTS_FAILURE_MSG("Failed to write version 5-specific fields"); + } + + return true; +} + +void ots_os2_reuse(Font *font, Font *other) { + font->os2 = other->os2; + font->os2_reused = true; +} + +void ots_os2_free(Font *font) { + delete font->os2; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/os2.h b/gfx/ots/src/os2.h new file mode 100644 index 000000000..01511c5dc --- /dev/null +++ b/gfx/ots/src/os2.h @@ -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. + +#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; + uint16_t lower_optical_pointsize; + uint16_t upper_optical_pointsize; +}; + +} // namespace ots + +#endif // OTS_OS2_H_ diff --git a/gfx/ots/src/ots.cc b/gfx/ots/src/ots.cc new file mode 100644 index 000000000..15794475a --- /dev/null +++ b/gfx/ots/src/ots.cc @@ -0,0 +1,907 @@ +// 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 <zlib.h> + +#include <algorithm> +#include <cstdlib> +#include <cstring> +#include <limits> +#include <map> +#include <vector> + +#include "woff2_dec.h" + +// The OpenType Font File +// http://www.microsoft.com/typography/otspec/cmap.htm + +namespace { + +// Generate a message with or without a table tag, when 'header' is the OpenTypeFile pointer +#define OTS_FAILURE_MSG_TAG(msg_,tag_) OTS_FAILURE_MSG_TAG_(header, msg_, tag_) +#define OTS_FAILURE_MSG_HDR(msg_) OTS_FAILURE_MSG_(header, msg_) +#define OTS_WARNING_MSG_HDR(msg_) OTS_WARNING_MSG_(header, msg_) + + +struct OpenTypeTable { + uint32_t tag; + uint32_t chksum; + uint32_t offset; + uint32_t length; + uint32_t uncompressed_length; +}; + +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 Arena { + public: + ~Arena() { + for (std::vector<uint8_t*>::iterator + i = hunks_.begin(); i != hunks_.end(); ++i) { + delete[] *i; + } + } + + uint8_t* Allocate(size_t length) { + uint8_t* p = new uint8_t[length]; + hunks_.push_back(p); + return p; + } + + private: + std::vector<uint8_t*> hunks_; +}; + +const struct { + uint32_t tag; + bool (*parse)(ots::Font *font, const uint8_t *data, size_t length); + bool (*serialise)(ots::OTSStream *out, ots::Font *font); + bool (*should_serialise)(ots::Font *font); + void (*reuse)(ots::Font *font, ots::Font *other); + bool required; +} table_parsers[] = { + { OTS_TAG('m','a','x','p'), ots::ots_maxp_parse, ots::ots_maxp_serialise, + ots::ots_maxp_should_serialise, ots::ots_maxp_reuse, true }, + { OTS_TAG('h','e','a','d'), ots::ots_head_parse, ots::ots_head_serialise, + ots::ots_head_should_serialise, ots::ots_head_reuse, true }, + { OTS_TAG('O','S','/','2'), ots::ots_os2_parse, ots::ots_os2_serialise, + ots::ots_os2_should_serialise, ots::ots_os2_reuse, true }, + { OTS_TAG('c','m','a','p'), ots::ots_cmap_parse, ots::ots_cmap_serialise, + ots::ots_cmap_should_serialise, ots::ots_cmap_reuse, true }, + { OTS_TAG('h','h','e','a'), ots::ots_hhea_parse, ots::ots_hhea_serialise, + ots::ots_hhea_should_serialise, ots::ots_hhea_reuse, true }, + { OTS_TAG('h','m','t','x'), ots::ots_hmtx_parse, ots::ots_hmtx_serialise, + ots::ots_hmtx_should_serialise, ots::ots_hmtx_reuse, true }, + { OTS_TAG('n','a','m','e'), ots::ots_name_parse, ots::ots_name_serialise, + ots::ots_name_should_serialise, ots::ots_name_reuse, true }, + { OTS_TAG('p','o','s','t'), ots::ots_post_parse, ots::ots_post_serialise, + ots::ots_post_should_serialise, ots::ots_post_reuse, true }, + { OTS_TAG('l','o','c','a'), ots::ots_loca_parse, ots::ots_loca_serialise, + ots::ots_loca_should_serialise, ots::ots_loca_reuse, false }, + { OTS_TAG('g','l','y','f'), ots::ots_glyf_parse, ots::ots_glyf_serialise, + ots::ots_glyf_should_serialise, ots::ots_glyf_reuse, false }, + { OTS_TAG('C','F','F',' '), ots::ots_cff_parse, ots::ots_cff_serialise, + ots::ots_cff_should_serialise, ots::ots_cff_reuse, false }, + { OTS_TAG('V','D','M','X'), ots::ots_vdmx_parse, ots::ots_vdmx_serialise, + ots::ots_vdmx_should_serialise, ots::ots_vdmx_reuse, false }, + { OTS_TAG('h','d','m','x'), ots::ots_hdmx_parse, ots::ots_hdmx_serialise, + ots::ots_hdmx_should_serialise, ots::ots_hdmx_reuse, false }, + { OTS_TAG('g','a','s','p'), ots::ots_gasp_parse, ots::ots_gasp_serialise, + ots::ots_gasp_should_serialise, ots::ots_gasp_reuse, false }, + { OTS_TAG('c','v','t',' '), ots::ots_cvt_parse, ots::ots_cvt_serialise, + ots::ots_cvt_should_serialise, ots::ots_cvt_reuse, false }, + { OTS_TAG('f','p','g','m'), ots::ots_fpgm_parse, ots::ots_fpgm_serialise, + ots::ots_fpgm_should_serialise, ots::ots_fpgm_reuse, false }, + { OTS_TAG('p','r','e','p'), ots::ots_prep_parse, ots::ots_prep_serialise, + ots::ots_prep_should_serialise, ots::ots_prep_reuse, false }, + { OTS_TAG('L','T','S','H'), ots::ots_ltsh_parse, ots::ots_ltsh_serialise, + ots::ots_ltsh_should_serialise, ots::ots_ltsh_reuse, false }, + { OTS_TAG('V','O','R','G'), ots::ots_vorg_parse, ots::ots_vorg_serialise, + ots::ots_vorg_should_serialise, ots::ots_vorg_reuse, false }, + { OTS_TAG('k','e','r','n'), ots::ots_kern_parse, ots::ots_kern_serialise, + ots::ots_kern_should_serialise, ots::ots_kern_reuse, false }, + // We need to parse GDEF table in advance of parsing GSUB/GPOS tables + // because they could refer GDEF table. + { OTS_TAG('G','D','E','F'), ots::ots_gdef_parse, ots::ots_gdef_serialise, + ots::ots_gdef_should_serialise, ots::ots_gdef_reuse, false }, + { OTS_TAG('G','P','O','S'), ots::ots_gpos_parse, ots::ots_gpos_serialise, + ots::ots_gpos_should_serialise, ots::ots_gpos_reuse, false }, + { OTS_TAG('G','S','U','B'), ots::ots_gsub_parse, ots::ots_gsub_serialise, + ots::ots_gsub_should_serialise, ots::ots_gsub_reuse, false }, + { OTS_TAG('v','h','e','a'), ots::ots_vhea_parse, ots::ots_vhea_serialise, + ots::ots_vhea_should_serialise, ots::ots_vhea_reuse, false }, + { OTS_TAG('v','m','t','x'), ots::ots_vmtx_parse, ots::ots_vmtx_serialise, + ots::ots_vmtx_should_serialise, ots::ots_vmtx_reuse, false }, + { OTS_TAG('M','A','T','H'), ots::ots_math_parse, ots::ots_math_serialise, + ots::ots_math_should_serialise, ots::ots_math_reuse, false }, + { 0, NULL, NULL, NULL, NULL, false }, +}; + +bool ProcessGeneric(ots::OpenTypeFile *header, + ots::Font *font, + uint32_t signature, + ots::OTSStream *output, + const uint8_t *data, size_t length, + const std::vector<OpenTypeTable>& tables, + ots::Buffer& file); + +bool ProcessTTF(ots::OpenTypeFile *header, + ots::Font *font, + ots::OTSStream *output, const uint8_t *data, size_t length, + uint32_t offset = 0) { + ots::Buffer file(data + offset, length - offset); + + if (offset > length) { + return OTS_FAILURE_MSG_HDR("offset beyond end of file"); + } + + // we disallow all files > 1GB in size for sanity. + if (length > 1024 * 1024 * 1024) { + return OTS_FAILURE_MSG_HDR("file exceeds 1GB"); + } + + if (!file.ReadU32(&font->version)) { + return OTS_FAILURE_MSG_HDR("error reading version tag"); + } + if (!ots::IsValidVersionTag(font->version)) { + return OTS_FAILURE_MSG_HDR("invalid version tag"); + } + + if (!file.ReadU16(&font->num_tables) || + !file.ReadU16(&font->search_range) || + !file.ReadU16(&font->entry_selector) || + !file.ReadU16(&font->range_shift)) { + return OTS_FAILURE_MSG_HDR("error reading table directory search header"); + } + + // 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 (font->num_tables >= 4096 || font->num_tables < 1) { + return OTS_FAILURE_MSG_HDR("excessive (or zero) number of tables"); + } + + unsigned max_pow2 = 0; + while (1u << (max_pow2 + 1) <= font->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 unexpected search_range value. + if (font->search_range != expected_search_range) { + OTS_WARNING_MSG_HDR("bad search range"); + font->search_range = expected_search_range; // Fix the value. + } + + // entry_selector is Log2(maximum power of 2 <= numTables) + if (font->entry_selector != max_pow2) { + return OTS_FAILURE_MSG_HDR("incorrect entrySelector for table directory"); + } + + // 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 > font->search_range by construction of search_range. + const uint16_t expected_range_shift = + 16 * font->num_tables - font->search_range; + if (font->range_shift != expected_range_shift) { + OTS_WARNING_MSG_HDR("bad range shift"); + font->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 < font->num_tables; ++i) { + OpenTypeTable table; + if (!file.ReadU32(&table.tag) || + !file.ReadU32(&table.chksum) || + !file.ReadU32(&table.offset) || + !file.ReadU32(&table.length)) { + return OTS_FAILURE_MSG_HDR("error reading table directory"); + } + + table.uncompressed_length = table.length; + tables.push_back(table); + } + + return ProcessGeneric(header, font, font->version, output, data, length, + tables, file); +} + +bool ProcessTTC(ots::OpenTypeFile *header, + ots::OTSStream *output, + const uint8_t *data, + size_t length, + uint32_t index) { + ots::Buffer file(data, length); + + // we disallow all files > 1GB in size for sanity. + if (length > 1024 * 1024 * 1024) { + return OTS_FAILURE_MSG_HDR("file exceeds 1GB"); + } + + uint32_t ttc_tag; + if (!file.ReadU32(&ttc_tag)) { + return OTS_FAILURE_MSG_HDR("Error reading TTC tag"); + } + if (ttc_tag != OTS_TAG('t','t','c','f')) { + return OTS_FAILURE_MSG_HDR("Invalid TTC tag"); + } + + uint32_t ttc_version; + if (!file.ReadU32(&ttc_version)) { + return OTS_FAILURE_MSG_HDR("Error reading TTC version"); + } + if (ttc_version != 0x00010000 && ttc_version != 0x00020000) { + return OTS_FAILURE_MSG_HDR("Invalid TTC version"); + } + + uint32_t num_fonts; + if (!file.ReadU32(&num_fonts)) { + return OTS_FAILURE_MSG_HDR("Error reading number of TTC fonts"); + } + // Limit the allowed number of subfonts to have same memory allocation. + if (num_fonts > 0x10000) { + return OTS_FAILURE_MSG_HDR("Too many fonts in TTC"); + } + + std::vector<uint32_t> offsets(num_fonts); + for (unsigned i = 0; i < num_fonts; i++) { + if (!file.ReadU32(&offsets[i])) { + return OTS_FAILURE_MSG_HDR("Error reading offset to OffsetTable"); + } + } + + if (ttc_version == 0x00020000) { + // We don't care about these fields of the header: + // uint32_t dsig_tag, dsig_length, dsig_offset + if (!file.Skip(3 * 4)) { + return OTS_FAILURE_MSG_HDR("Error reading DSIG offset and length in TTC font"); + } + } + + if (index == static_cast<uint32_t>(-1)) { + if (!output->WriteU32(ttc_tag) || + !output->WriteU32(0x00010000) || + !output->WriteU32(num_fonts) || + !output->Seek((3 + num_fonts) * 4)) { + return OTS_FAILURE_MSG_HDR("Error writing output"); + } + + // Keep references to the fonts processed in the loop below, as we need + // them for reused tables. + std::vector<ots::Font> fonts(num_fonts, ots::Font(header)); + + for (unsigned i = 0; i < num_fonts; i++) { + uint32_t out_offset = output->Tell(); + if (!output->Seek((3 + i) * 4) || + !output->WriteU32(out_offset) || + !output->Seek(out_offset)) { + return OTS_FAILURE_MSG_HDR("Error writing output"); + } + if (!ProcessTTF(header, &fonts[i], output, data, length, offsets[i])) { + return false; + } + } + + return true; + } else { + if (index >= num_fonts) { + return OTS_FAILURE_MSG_HDR("Requested font index is bigger than the number of fonts in the TTC file"); + } + + ots::Font font(header); + return ProcessTTF(header, &font, output, data, length, offsets[index]); + } +} + +bool ProcessWOFF(ots::OpenTypeFile *header, + ots::Font *font, + 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_MSG_HDR("file exceeds 1GB"); + } + + uint32_t woff_tag; + if (!file.ReadU32(&woff_tag)) { + return OTS_FAILURE_MSG_HDR("error reading WOFF marker"); + } + + if (woff_tag != OTS_TAG('w','O','F','F')) { + return OTS_FAILURE_MSG_HDR("invalid WOFF marker"); + } + + if (!file.ReadU32(&font->version)) { + return OTS_FAILURE_MSG_HDR("error reading version tag"); + } + if (!ots::IsValidVersionTag(font->version)) { + return OTS_FAILURE_MSG_HDR("invalid version tag"); + } + + uint32_t reported_length; + if (!file.ReadU32(&reported_length) || length != reported_length) { + return OTS_FAILURE_MSG_HDR("incorrect file size in WOFF header"); + } + + if (!file.ReadU16(&font->num_tables) || !font->num_tables) { + return OTS_FAILURE_MSG_HDR("error reading number of tables"); + } + + uint16_t reserved_value; + if (!file.ReadU16(&reserved_value) || reserved_value) { + return OTS_FAILURE_MSG_HDR("error in reserved field of WOFF header"); + } + + uint32_t reported_total_sfnt_size; + if (!file.ReadU32(&reported_total_sfnt_size)) { + return OTS_FAILURE_MSG_HDR("error reading total sfnt size"); + } + + // We don't care about these fields of the header: + // uint16_t major_version, minor_version + if (!file.Skip(2 * 2)) { + return OTS_FAILURE_MSG_HDR("Failed to read 'majorVersion' or 'minorVersion'"); + } + + // Checks metadata block size. + uint32_t meta_offset; + uint32_t meta_length; + uint32_t meta_length_orig; + if (!file.ReadU32(&meta_offset) || + !file.ReadU32(&meta_length) || + !file.ReadU32(&meta_length_orig)) { + return OTS_FAILURE_MSG_HDR("Failed to read header metadata block fields"); + } + if (meta_offset) { + if (meta_offset >= length || length - meta_offset < meta_length) { + return OTS_FAILURE_MSG_HDR("Invalid metadata block offset or length"); + } + } + + // Checks private data block size. + uint32_t priv_offset; + uint32_t priv_length; + if (!file.ReadU32(&priv_offset) || + !file.ReadU32(&priv_length)) { + return OTS_FAILURE_MSG_HDR("Failed to read header private block fields"); + } + if (priv_offset) { + if (priv_offset >= length || length - priv_offset < priv_length) { + return OTS_FAILURE_MSG_HDR("Invalid private block offset or length"); + } + } + + // Next up is the list of tables. + std::vector<OpenTypeTable> tables; + + uint32_t first_index = 0; + uint32_t last_index = 0; + // Size of sfnt header plus size of table records. + uint64_t total_sfnt_size = 12 + 16 * font->num_tables; + for (unsigned i = 0; i < font->num_tables; ++i) { + OpenTypeTable table; + if (!file.ReadU32(&table.tag) || + !file.ReadU32(&table.offset) || + !file.ReadU32(&table.length) || + !file.ReadU32(&table.uncompressed_length) || + !file.ReadU32(&table.chksum)) { + return OTS_FAILURE_MSG_HDR("error reading table directory"); + } + + total_sfnt_size += ots::Round4(table.uncompressed_length); + if (total_sfnt_size > std::numeric_limits<uint32_t>::max()) { + return OTS_FAILURE_MSG_HDR("sfnt size overflow"); + } + tables.push_back(table); + if (i == 0 || tables[first_index].offset > table.offset) + first_index = i; + if (i == 0 || tables[last_index].offset < table.offset) + last_index = i; + } + + if (reported_total_sfnt_size != total_sfnt_size) { + return OTS_FAILURE_MSG_HDR("uncompressed sfnt size mismatch"); + } + + // Table data must follow immediately after the header. + if (tables[first_index].offset != ots::Round4(file.offset())) { + return OTS_FAILURE_MSG_HDR("junk before tables in WOFF file"); + } + + if (tables[last_index].offset >= length || + length - tables[last_index].offset < tables[last_index].length) { + return OTS_FAILURE_MSG_HDR("invalid table location/size"); + } + // Blocks must follow immediately after the previous block. + // (Except for padding with a maximum of three null bytes) + uint64_t block_end = ots::Round4( + static_cast<uint64_t>(tables[last_index].offset) + + static_cast<uint64_t>(tables[last_index].length)); + if (block_end > std::numeric_limits<uint32_t>::max()) { + return OTS_FAILURE_MSG_HDR("invalid table location/size"); + } + if (meta_offset) { + if (block_end != meta_offset) { + return OTS_FAILURE_MSG_HDR("Invalid metadata block offset"); + } + block_end = ots::Round4(static_cast<uint64_t>(meta_offset) + + static_cast<uint64_t>(meta_length)); + if (block_end > std::numeric_limits<uint32_t>::max()) { + return OTS_FAILURE_MSG_HDR("Invalid metadata block length"); + } + } + if (priv_offset) { + if (block_end != priv_offset) { + return OTS_FAILURE_MSG_HDR("Invalid private block offset"); + } + block_end = ots::Round4(static_cast<uint64_t>(priv_offset) + + static_cast<uint64_t>(priv_length)); + if (block_end > std::numeric_limits<uint32_t>::max()) { + return OTS_FAILURE_MSG_HDR("Invalid private block length"); + } + } + if (block_end != ots::Round4(length)) { + return OTS_FAILURE_MSG_HDR("File length mismatch (trailing junk?)"); + } + + return ProcessGeneric(header, font, woff_tag, output, data, length, tables, file); +} + +bool ProcessWOFF2(ots::OpenTypeFile *header, + ots::OTSStream *output, + const uint8_t *data, + size_t length, + uint32_t index) { + size_t decompressed_size = woff2::ComputeWOFF2FinalSize(data, length); + + if (decompressed_size == 0) { + return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 is set to 0"); + } + // decompressed font must be <= 30MB + if (decompressed_size > 30 * 1024 * 1024) { + return OTS_FAILURE_MSG_HDR("Size of decompressed WOFF 2.0 font exceeds 30MB"); + } + + std::string buf(decompressed_size, 0); + woff2::WOFF2StringOut out(&buf); + if (!woff2::ConvertWOFF2ToTTF(data, length, &out)) { + return OTS_FAILURE_MSG_HDR("Failed to convert WOFF 2.0 font to SFNT"); + } + const uint8_t *decompressed = reinterpret_cast<const uint8_t*>(buf.data()); + + if (data[4] == 't' && data[5] == 't' && data[6] == 'c' && data[7] == 'f') { + return ProcessTTC(header, output, decompressed, out.Size(), index); + } else { + ots::Font font(header); + return ProcessTTF(header, &font, output, decompressed, out.Size()); + } +} + +ots::TableAction GetTableAction(ots::OpenTypeFile *header, uint32_t tag) { + ots::TableAction action = header->context->GetTableAction(tag); + + if (action == ots::TABLE_ACTION_DEFAULT) { + action = ots::TABLE_ACTION_DROP; + + for (unsigned i = 0; ; ++i) { + if (table_parsers[i].parse == NULL) break; + + if (table_parsers[i].tag == tag) { + action = ots::TABLE_ACTION_SANITIZE; + break; + } + } + } + + assert(action != ots::TABLE_ACTION_DEFAULT); // Should never return this. + return action; +} + +bool GetTableData(const uint8_t *data, + const OpenTypeTable& table, + Arena *arena, + size_t *table_length, + const uint8_t **table_data) { + if (table.uncompressed_length != table.length) { + // Compressed table. Need to uncompress into memory first. + *table_length = table.uncompressed_length; + *table_data = (*arena).Allocate(*table_length); + uLongf dest_len = *table_length; + int r = uncompress((Bytef*) *table_data, &dest_len, + data + table.offset, table.length); + if (r != Z_OK || dest_len != *table_length) { + return false; + } + } else { + // Uncompressed table. We can process directly from memory. + *table_data = data + table.offset; + *table_length = table.length; + } + + return true; +} + +bool ProcessGeneric(ots::OpenTypeFile *header, + ots::Font *font, + uint32_t signature, + ots::OTSStream *output, + const uint8_t *data, size_t length, + const std::vector<OpenTypeTable>& tables, + ots::Buffer& file) { + const size_t data_offset = file.offset(); + + uint32_t uncompressed_sum = 0; + + for (unsigned i = 0; i < font->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 = tables[i].tag; + const uint32_t prev_tag = tables[i - 1].tag; + if (this_tag <= prev_tag) { + OTS_WARNING_MSG_HDR("Table directory is not correctly ordered"); + } + } + + // all tag names must be built from printable ASCII characters + if (!CheckTag(tables[i].tag)) { + return OTS_FAILURE_MSG_TAG("invalid table tag", tables[i].tag); + } + + // tables must be 4-byte aligned + if (tables[i].offset & 3) { + return OTS_FAILURE_MSG_TAG("misaligned table", tables[i].tag); + } + + // and must be within the file + if (tables[i].offset < data_offset || tables[i].offset >= length) { + return OTS_FAILURE_MSG_TAG("invalid table offset", tables[i].tag); + } + // disallow all tables with a zero length + if (tables[i].length < 1) { + // Note: malayalam.ttf has zero length CVT table... + return OTS_FAILURE_MSG_TAG("zero-length table", tables[i].tag); + } + // disallow all tables with a length > 1GB + if (tables[i].length > 1024 * 1024 * 1024) { + return OTS_FAILURE_MSG_TAG("table length exceeds 1GB", tables[i].tag); + } + // disallow tables where the uncompressed size is < the compressed size. + if (tables[i].uncompressed_length < tables[i].length) { + return OTS_FAILURE_MSG_TAG("invalid compressed table", tables[i].tag); + } + if (tables[i].uncompressed_length > tables[i].length) { + // We'll probably be decompressing this table. + + // disallow all tables which uncompress to > 30 MB + if (tables[i].uncompressed_length > 30 * 1024 * 1024) { + return OTS_FAILURE_MSG_TAG("uncompressed length exceeds 30MB", tables[i].tag); + } + if (uncompressed_sum + tables[i].uncompressed_length < uncompressed_sum) { + return OTS_FAILURE_MSG_TAG("overflow of uncompressed sum", tables[i].tag); + } + + uncompressed_sum += tables[i].uncompressed_length; + } + // since we required that the file be < 1GB in length, and that the table + // length is < 1GB, the following addtion doesn't overflow + uint32_t end_byte = tables[i].offset + tables[i].length; + // Tables in the WOFF file must be aligned 4-byte boundary. + if (signature == OTS_TAG('w','O','F','F')) { + end_byte = ots::Round4(end_byte); + } + if (!end_byte || end_byte > length) { + return OTS_FAILURE_MSG_TAG("table overruns end of file", tables[i].tag); + } + } + + // All decompressed tables uncompressed must be <= 30MB. + if (uncompressed_sum > 30 * 1024 * 1024) { + return OTS_FAILURE_MSG_HDR("uncompressed sum exceeds 30MB"); + } + + std::map<uint32_t, OpenTypeTable> table_map; + for (unsigned i = 0; i < font->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 < font->num_tables; ++i) { + overlap_checker.push_back( + std::make_pair(tables[i].offset, static_cast<uint8_t>(1) /* start */)); + overlap_checker.push_back( + std::make_pair(tables[i].offset + tables[i].length, + static_cast<uint8_t>(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_MSG_HDR("overlapping tables"); + } + } + + Arena arena; + + for (unsigned i = 0; ; ++i) { + if (table_parsers[i].parse == NULL) break; + + uint32_t tag = table_parsers[i].tag; + const std::map<uint32_t, OpenTypeTable>::const_iterator it = table_map.find(tag); + + ots::TableAction action = GetTableAction(header, tag); + if (it == table_map.end()) { + if (table_parsers[i].required && action == ots::TABLE_ACTION_SANITIZE) { + return OTS_FAILURE_MSG_TAG("missing required table", table_parsers[i].tag); + } + continue; + } + + uint32_t input_offset = it->second.offset; + const ots::TableMap::const_iterator ot = header->tables.find(input_offset); + if (ot == header->tables.end()) { + const uint8_t* table_data; + size_t table_length; + + if (!GetTableData(data, it->second, &arena, &table_length, &table_data)) { + return OTS_FAILURE_MSG_TAG("uncompress failed", table_parsers[i].tag); + } + + if (action == ots::TABLE_ACTION_SANITIZE && + !table_parsers[i].parse(font, table_data, table_length)) { + return OTS_FAILURE(); + } + } else if (action == ots::TABLE_ACTION_SANITIZE) { + table_parsers[i].reuse(font, ot->second.first); + } + } + + if (font->cff) { + // font with PostScript glyph + if (font->version != OTS_TAG('O','T','T','O')) { + return OTS_FAILURE_MSG_HDR("wrong font version for PostScript glyph data"); + } + if (font->glyf || font->loca) { + // mixing outline formats is not recommended + return OTS_FAILURE_MSG_HDR("font contains both PS and TT glyphs"); + } + } else { + if (!font->glyf || !font->loca) { + // No TrueType glyph found. +#define PASSTHRU_TABLE(tag_) (table_map.find(tag_) != table_map.end() && \ + GetTableAction(header, tag_) == ots::TABLE_ACTION_PASSTHRU) + // We don't sanitise bitmap table, but don't reject bitmap-only fonts if + // we keep the tables. + if (!PASSTHRU_TABLE(OTS_TAG('C','B','D','T')) || + !PASSTHRU_TABLE(OTS_TAG('C','B','L','C'))) { + return OTS_FAILURE_MSG_HDR("no supported glyph shapes table(s) present"); + } +#undef PASSTHRU_TABLE + } + } + + uint16_t num_output_tables = 0; + for (std::map<uint32_t, OpenTypeTable>::const_iterator it = table_map.begin(); + it != table_map.end(); ++it) { + ots::TableAction action = GetTableAction(header, it->first); + if (action == ots::TABLE_ACTION_PASSTHRU) { + num_output_tables++; + } else { + for (unsigned i = 0; table_parsers[i].parse != NULL; ++i) { + if (table_parsers[i].tag == it->first && + table_parsers[i].should_serialise(font)) { + num_output_tables++; + break; + } + } + } + } + + uint16_t max_pow2 = 0; + while (1u << (max_pow2 + 1) <= num_output_tables) { + max_pow2++; + } + const uint16_t output_search_range = (1u << max_pow2) << 4; + + // most of the errors here are highly unlikely - they'd only occur if the + // output stream returns a failure, e.g. lack of space to write + output->ResetChecksum(); + if (!output->WriteU32(font->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_MSG_HDR("error writing output"); + } + 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_MSG_HDR("error writing output"); + } + + std::vector<ots::OutputTable> out_tables; + + size_t head_table_offset = 0; + for (std::map<uint32_t, OpenTypeTable>::const_iterator it = table_map.begin(); + it != table_map.end(); ++it) { + uint32_t input_offset = it->second.offset; + const ots::TableMap::const_iterator ot = header->tables.find(input_offset); + if (ot != header->tables.end()) { + ots::OutputTable out = ot->second.second; + if (out.tag == OTS_TAG('h','e','a','d')) { + head_table_offset = out.offset; + } + out_tables.push_back(out); + } else { + ots::OutputTable out; + out.tag = it->first; + out.offset = output->Tell(); + + if (out.tag == OTS_TAG('h','e','a','d')) { + head_table_offset = out.offset; + } + + ots::TableAction action = GetTableAction(header, it->first); + if (action == ots::TABLE_ACTION_PASSTHRU) { + output->ResetChecksum(); + const uint8_t* table_data; + size_t table_length; + + if (!GetTableData(data, it->second, &arena, &table_length, &table_data)) { + return OTS_FAILURE_MSG_HDR("Failed to uncompress table"); + } + + if (!output->Write(table_data, table_length)) { + return OTS_FAILURE_MSG_HDR("Failed to serialize table"); + } + + const size_t end_offset = output->Tell(); + if (end_offset <= out.offset) { + // paranoid check. |end_offset| is supposed to be greater than the offset, + // as long as the Tell() interface is implemented correctly. + return OTS_FAILURE_MSG_HDR("error writing output"); + } + out.length = end_offset - out.offset; + + // align tables to four bytes + if (!output->Pad((4 - (end_offset & 3)) % 4)) { + return OTS_FAILURE_MSG_HDR("error writing output"); + } + out.chksum = output->chksum(); + out_tables.push_back(out); + header->tables[input_offset] = std::make_pair(font, out); + } else { + for (unsigned i = 0; table_parsers[i].parse != NULL; ++i) { + if (table_parsers[i].tag == it->first && + table_parsers[i].should_serialise(font)) { + output->ResetChecksum(); + if (!table_parsers[i].serialise(output, font)) { + return OTS_FAILURE_MSG_TAG("failed to serialize table", table_parsers[i].tag); + } + + const size_t end_offset = output->Tell(); + if (end_offset <= out.offset) { + // paranoid check. |end_offset| is supposed to be greater than the offset, + // as long as the Tell() interface is implemented correctly. + return OTS_FAILURE_MSG_HDR("error writing output"); + } + out.length = end_offset - out.offset; + + // align tables to four bytes + if (!output->Pad((4 - (end_offset & 3)) % 4)) { + return OTS_FAILURE_MSG_HDR("error writing output"); + } + out.chksum = output->chksum(); + out_tables.push_back(out); + header->tables[input_offset] = std::make_pair(font, out); + + break; + } + } + } + } + } + + 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()); + if (!output->Seek(table_record_offset)) { + return OTS_FAILURE_MSG_HDR("error writing output"); + } + + output->ResetChecksum(); + uint32_t tables_chksum = 0; + for (unsigned i = 0; i < out_tables.size(); ++i) { + if (!output->WriteU32(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_MSG_HDR("error writing output"); + } + 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 + if (!head_table_offset) { + return OTS_FAILURE_MSG_HDR("internal error!"); + } + if (!output->Seek(head_table_offset + 8)) { + return OTS_FAILURE_MSG_HDR("error writing output"); + } + if (!output->WriteU32(chksum_magic)) { + return OTS_FAILURE_MSG_HDR("error writing output"); + } + + if (!output->Seek(end_of_file)) { + return OTS_FAILURE_MSG_HDR("error writing output"); + } + + return true; +} + +} // namespace + +namespace ots { + +bool IsValidVersionTag(uint32_t tag) { + return tag == 0x000010000 || + // OpenType fonts with CFF data have 'OTTO' tag. + tag == OTS_TAG('O','T','T','O') || + // Older Mac fonts might have 'true' or 'typ1' tag. + tag == OTS_TAG('t','r','u','e') || + tag == OTS_TAG('t','y','p','1'); +} + +bool OTSContext::Process(OTSStream *output, + const uint8_t *data, + size_t length, + uint32_t index) { + OpenTypeFile header; + Font font(&header); + header.context = this; + + if (length < 4) { + return OTS_FAILURE_MSG_(&header, "file less than 4 bytes"); + } + + bool result; + if (data[0] == 'w' && data[1] == 'O' && data[2] == 'F' && data[3] == 'F') { + result = ProcessWOFF(&header, &font, output, data, length); + } else if (data[0] == 'w' && data[1] == 'O' && data[2] == 'F' && data[3] == '2') { + result = ProcessWOFF2(&header, output, data, length, index); + } else if (data[0] == 't' && data[1] == 't' && data[2] == 'c' && data[3] == 'f') { + result = ProcessTTC(&header, output, data, length, index); + } else { + result = ProcessTTF(&header, &font, output, data, length); + } + + return result; +} + +} // namespace ots diff --git a/gfx/ots/src/ots.h b/gfx/ots/src/ots.h new file mode 100644 index 000000000..2d13f8d6d --- /dev/null +++ b/gfx/ots/src/ots.h @@ -0,0 +1,294 @@ +// 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 <stddef.h> +#include <cstdarg> +#include <cstddef> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <limits> +#include <map> + +#include "opentype-sanitiser.h" + +// arraysize borrowed from base/basictypes.h +template <typename T, size_t N> +char (&ArraySizeHelper(T (&array)[N]))[N]; +#define arraysize(array) (sizeof(ArraySizeHelper(array))) + +namespace ots { + +#if !defined(OTS_DEBUG) +#define OTS_FAILURE() false +#else +#define OTS_FAILURE() \ + (\ + std::fprintf(stderr, "ERROR at %s:%d (%s)\n", \ + __FILE__, __LINE__, __FUNCTION__) \ + && false\ + ) +#endif + +// All OTS_FAILURE_* macros ultimately evaluate to 'false', just like the original +// message-less OTS_FAILURE(), so that the current parser will return 'false' as +// its result (indicating a failure). + +#if !defined(OTS_DEBUG) +#define OTS_MESSAGE_(level,otf_,...) \ + (otf_)->context->Message(level,__VA_ARGS__) +#else +#define OTS_MESSAGE_(level,otf_,...) \ + OTS_FAILURE(), \ + (otf_)->context->Message(level,__VA_ARGS__) +#endif + +// Generate a simple message +#define OTS_FAILURE_MSG_(otf_,...) \ + (OTS_MESSAGE_(0,otf_,__VA_ARGS__), false) + +#define OTS_WARNING_MSG_(otf_,...) \ + OTS_MESSAGE_(1,otf_,__VA_ARGS__) + +// Generate a message with an associated table tag +#define OTS_FAILURE_MSG_TAG_(otf_,msg_,tag_) \ + (OTS_MESSAGE_(0,otf_,"%c%c%c%c: %s", OTS_UNTAG(tag_), msg_), false) + +// Convenience macros for use in files that only handle a single table tag, +// defined as TABLE_NAME at the top of the file; the 'file' variable is +// expected to be the current OpenTypeFile pointer. +#define OTS_FAILURE_MSG(...) OTS_FAILURE_MSG_(font->file, TABLE_NAME ": " __VA_ARGS__) + +#define OTS_WARNING(...) OTS_WARNING_MSG_(font->file, TABLE_NAME ": " __VA_ARGS__) + +// ----------------------------------------------------------------------------- +// 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 *buf, size_t len) + : buffer_(buf), + length_(len), + offset_(0) { } + + bool Skip(size_t n_bytes) { + return Read(NULL, n_bytes); + } + + bool Read(uint8_t *buf, 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 (buf) { + std::memcpy(buf, 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 ReadU24(uint32_t *value) { + if (offset_ + 3 > length_) { + return OTS_FAILURE(); + } + *value = static_cast<uint32_t>(buffer_[offset_]) << 16 | + static_cast<uint32_t>(buffer_[offset_ + 1]) << 8 | + static_cast<uint32_t>(buffer_[offset_ + 2]); + offset_ += 3; + return true; + } + + 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 ReadR64(uint64_t *value) { + if (offset_ + 8 > length_) { + return OTS_FAILURE(); + } + std::memcpy(value, buffer_ + offset_, sizeof(uint64_t)); + offset_ += 8; + return true; + } + + const uint8_t *buffer() const { return buffer_; } + size_t offset() const { return offset_; } + size_t length() const { return length_; } + size_t remaining() const { return length_ - offset_; } + + void set_offset(size_t newoffset) { offset_ = newoffset; } + + private: + const uint8_t * const buffer_; + const size_t length_; + size_t offset_; +}; + +// Round a value up to the nearest multiple of 4. Don't round the value in the +// case that rounding up overflows. +template<typename T> T Round4(T value) { + if (std::numeric_limits<T>::max() - value < 3) { + return value; + } + return (value + 3) & ~3; +} + +template<typename T> T Round2(T value) { + if (value == std::numeric_limits<T>::max()) { + return value; + } + return (value + 1) & ~1; +} + +bool IsValidVersionTag(uint32_t tag); + +#define FOR_EACH_TABLE_TYPE \ + F(cff, CFF) \ + F(cmap, CMAP) \ + F(cvt, CVT) \ + F(fpgm, FPGM) \ + F(gasp, GASP) \ + F(gdef, GDEF) \ + F(glyf, GLYF) \ + F(gpos, GPOS) \ + F(gsub, GSUB) \ + F(hdmx, HDMX) \ + F(head, HEAD) \ + F(hhea, HHEA) \ + F(hmtx, HMTX) \ + F(kern, KERN) \ + F(loca, LOCA) \ + F(ltsh, LTSH) \ + F(math, MATH) \ + F(maxp, MAXP) \ + F(name, NAME) \ + F(os2, OS2) \ + F(post, POST) \ + F(prep, PREP) \ + F(vdmx, VDMX) \ + F(vorg, VORG) \ + F(vhea, VHEA) \ + F(vmtx, VMTX) + +#define F(name, capname) struct OpenType##capname; +FOR_EACH_TABLE_TYPE +#undef F + +struct Font; +struct OpenTypeFile; + +#define F(name, capname) \ +bool ots_##name##_parse(Font *f, const uint8_t *d, size_t l); \ +bool ots_##name##_should_serialise(Font *f); \ +bool ots_##name##_serialise(OTSStream *s, Font *f); \ +void ots_##name##_reuse(Font *f, Font *o);\ +void ots_##name##_free(Font *f); +FOR_EACH_TABLE_TYPE +#undef F + +struct Font { + explicit Font(const OpenTypeFile *f) + : file(f), + version(0), + num_tables(0), + search_range(0), + entry_selector(0), + range_shift(0) { +#define F(name, capname) \ + name = NULL; \ + name##_reused = false; + FOR_EACH_TABLE_TYPE +#undef F + } + + ~Font() { +#define F(name, capname) \ + if (!name##_reused) {\ + ots_##name##_free(this); \ + } + FOR_EACH_TABLE_TYPE +#undef F + } + + const OpenTypeFile *file; + + 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; \ + bool name##_reused; +FOR_EACH_TABLE_TYPE +#undef F +}; + +struct OutputTable { + uint32_t tag; + size_t offset; + size_t length; + uint32_t chksum; + + bool operator<(const OutputTable& other) const { + return tag < other.tag; + } +}; + +typedef std::map<uint32_t, std::pair<Font*, OutputTable> > TableMap; + +struct OpenTypeFile { + OTSContext *context; + TableMap tables; +}; + +} // namespace ots + +#undef FOR_EACH_TABLE_TYPE + +#endif // OTS_H_ diff --git a/gfx/ots/src/post.cc b/gfx/ots/src/post.cc new file mode 100644 index 000000000..a110b2dea --- /dev/null +++ b/gfx/ots/src/post.cc @@ -0,0 +1,193 @@ +// 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/typography/otspec/post.htm + +#define TABLE_NAME "post" + +namespace ots { + +bool ots_post_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypePOST *post = new OpenTypePOST; + font->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_MSG("Failed to read post header"); + } + + 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_MSG("Bad post version %x", post->version); + } + + // 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_MSG("Failed to skip memory usage in post table"); + } + + uint16_t num_glyphs = 0; + if (!table.ReadU16(&num_glyphs)) { + return OTS_FAILURE_MSG("Failed to read number of glyphs"); + } + + if (!font->maxp) { + return OTS_FAILURE_MSG("No maxp table required by post table"); + } + + if (num_glyphs == 0) { + if (font->maxp->num_glyphs > 258) { + return OTS_FAILURE_MSG("Can't have no glyphs in the post table if there are more than 256 glyphs in the font"); + } + 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 != font->maxp->num_glyphs) { + // Note: Fixedsys500c.ttf seems to have inconsistent num_glyphs values. + return OTS_FAILURE_MSG("Bad number of glyphs in post table %d", num_glyphs); + } + + 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_MSG("Failed to read post information for glyph %d", i); + } + // Note: A strict interpretation of the specification requires name indexes + // are less than 32768. This, however, excludes fonts like unifont.ttf + // which cover all of unicode. + } + + // 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_MSG("Bad string length %d", string_length); + } + if (std::memchr(strings + 1, '\0', string_length)) { + return OTS_FAILURE_MSG("Bad string of length %d", string_length); + } + 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_MSG("Bad string index %d", offset); + } + } + + return true; +} + +bool ots_post_should_serialise(Font *font) { + return font->post != NULL; +} + +bool ots_post_serialise(OTSStream *out, Font *font) { + const OpenTypePOST *post = font->post; + + // OpenType with CFF glyphs must have v3 post table. + if (post && font->cff && post->version != 0x00030000) { + return OTS_FAILURE_MSG("Bad post version %x", post->version); + } + + 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_MSG("Failed to write post header"); + } + + if (post->version != 0x00020000) { + return true; // v1.0 and v3.0 does not have glyph names. + } + + const uint16_t num_indexes = + static_cast<uint16_t>(post->glyph_name_index.size()); + if (num_indexes != post->glyph_name_index.size() || + !out->WriteU16(num_indexes)) { + return OTS_FAILURE_MSG("Failed to write number of indices"); + } + + for (uint16_t i = 0; i < num_indexes; ++i) { + if (!out->WriteU16(post->glyph_name_index[i])) { + return OTS_FAILURE_MSG("Failed to write name index %d", i); + } + } + + // 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 = static_cast<uint8_t>(s.size()); + if (string_length != s.size() || + !out->Write(&string_length, 1)) { + return OTS_FAILURE_MSG("Failed to write string %d", i); + } + // 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_MSG("Failed to write string length for string %d", i); + } + } + + return true; +} + +void ots_post_reuse(Font *font, Font *other) { + font->post = other->post; + font->post_reused = true; +} + +void ots_post_free(Font *font) { + delete font->post; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/post.h b/gfx/ots/src/post.h new file mode 100644 index 000000000..f220d4fc7 --- /dev/null +++ b/gfx/ots/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/gfx/ots/src/prep.cc b/gfx/ots/src/prep.cc new file mode 100644 index 000000000..1c9b45f91 --- /dev/null +++ b/gfx/ots/src/prep.cc @@ -0,0 +1,59 @@ +// 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/typography/otspec/prep.htm + +#define TABLE_NAME "prep" + +namespace ots { + +bool ots_prep_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + + OpenTypePREP *prep = new OpenTypePREP; + font->prep = prep; + + if (length >= 128 * 1024u) { + return OTS_FAILURE_MSG("table length %ld > 120K", length); // almost all prep tables are less than 9k bytes. + } + + if (!table.Skip(length)) { + return OTS_FAILURE_MSG("Failed to read table of length %ld", length); + } + + prep->data = data; + prep->length = length; + return true; +} + +bool ots_prep_should_serialise(Font *font) { + if (!font->glyf) return false; // this table is not for CFF fonts. + return font->prep != NULL; +} + +bool ots_prep_serialise(OTSStream *out, Font *font) { + const OpenTypePREP *prep = font->prep; + + if (!out->Write(prep->data, prep->length)) { + return OTS_FAILURE_MSG("Failed to write table length"); + } + + return true; +} + +void ots_prep_reuse(Font *font, Font *other) { + font->prep = other->prep; + font->prep_reused = true; +} + +void ots_prep_free(Font *font) { + delete font->prep; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/prep.h b/gfx/ots/src/prep.h new file mode 100644 index 000000000..935ca111c --- /dev/null +++ b/gfx/ots/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/gfx/ots/src/vdmx.cc b/gfx/ots/src/vdmx.cc new file mode 100644 index 000000000..cd80946ae --- /dev/null +++ b/gfx/ots/src/vdmx.cc @@ -0,0 +1,186 @@ +// 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/typography/otspec/vdmx.htm + +#define TABLE_NAME "VDMX" + +#define DROP_THIS_TABLE(...) \ + do { \ + OTS_FAILURE_MSG_(font->file, TABLE_NAME ": " __VA_ARGS__); \ + OTS_FAILURE_MSG("Table discarded"); \ + delete font->vdmx; \ + font->vdmx = 0; \ + } while (0) + +namespace ots { + +bool ots_vdmx_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + font->vdmx = new OpenTypeVDMX; + OpenTypeVDMX * const vdmx = font->vdmx; + + if (!table.ReadU16(&vdmx->version) || + !table.ReadU16(&vdmx->num_recs) || + !table.ReadU16(&vdmx->num_ratios)) { + return OTS_FAILURE_MSG("Failed to read table header"); + } + + if (vdmx->version > 1) { + DROP_THIS_TABLE("bad version: %u", vdmx->version); + 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_MSG("Failed to read ratio header %d", i); + } + + if (rec.charset > 1) { + DROP_THIS_TABLE("bad charset: %u", rec.charset); + return true; + } + + if (rec.y_start_ratio > rec.y_end_ratio) { + DROP_THIS_TABLE("bad y ratio"); + 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. + DROP_THIS_TABLE("superfluous terminator found"); + return true; + } + + 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_MSG("Failed to read ratio offset %d", i); + } + if (current_offset + offset >= length) { // thus doesn't overflow. + return OTS_FAILURE_MSG("Bad ratio offset %d for ration %d", offset, i); + } + + 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_MSG("Failed to read record header %d", i); + } + 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_MSG("Failed to read reacord %d group %d", i, j); + } + if (vt.y_max < vt.y_min) { + DROP_THIS_TABLE("bad y min/max"); + 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)) { + DROP_THIS_TABLE("the table is not sorted"); + return true; + } + + group.entries.push_back(vt); + } + vdmx->groups.push_back(group); + } + + return true; +} + +bool ots_vdmx_should_serialise(Font *font) { + if (!font->glyf) return false; // this table is not for CFF fonts. + return font->vdmx != NULL; +} + +bool ots_vdmx_serialise(OTSStream *out, Font *font) { + OpenTypeVDMX * const vdmx = font->vdmx; + + if (!out->WriteU16(vdmx->version) || + !out->WriteU16(vdmx->num_recs) || + !out->WriteU16(vdmx->num_ratios)) { + return OTS_FAILURE_MSG("Failed to write table header"); + } + + 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_MSG("Failed to write ratio %d", i); + } + } + + for (unsigned i = 0; i < vdmx->offsets.size(); ++i) { + if (!out->WriteU16(vdmx->offsets[i])) { + return OTS_FAILURE_MSG("Failed to write ratio offset %d", i); + } + } + + 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_MSG("Failed to write group %d", i); + } + 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_MSG("Failed to write group %d entry %d", i, j); + } + } + } + + return true; +} + +void ots_vdmx_reuse(Font *font, Font *other) { + font->vdmx = other->vdmx; + font->vdmx_reused = true; +} + +void ots_vdmx_free(Font *font) { + delete font->vdmx; +} + +} // namespace ots + +#undef TABLE_NAME +#undef DROP_THIS_TABLE diff --git a/gfx/ots/src/vdmx.h b/gfx/ots/src/vdmx.h new file mode 100644 index 000000000..1d959efe5 --- /dev/null +++ b/gfx/ots/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/gfx/ots/src/vhea.cc b/gfx/ots/src/vhea.cc new file mode 100644 index 000000000..e721b971e --- /dev/null +++ b/gfx/ots/src/vhea.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2011 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 "vhea.h" + +#include "head.h" +#include "maxp.h" + +// vhea - Vertical Header Table +// http://www.microsoft.com/typography/otspec/vhea.htm + +#define TABLE_NAME "vhea" + +namespace ots { + +bool ots_vhea_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + OpenTypeVHEA *vhea = new OpenTypeVHEA; + font->vhea = vhea; + + if (!table.ReadU32(&vhea->header.version)) { + return OTS_FAILURE_MSG("Failed to read version"); + } + if (vhea->header.version != 0x00010000 && + vhea->header.version != 0x00011000) { + return OTS_FAILURE_MSG("Bad vhea version %x", vhea->header.version); + } + + if (!ParseMetricsHeader(font, &table, &vhea->header)) { + return OTS_FAILURE_MSG("Failed to parse metrics in vhea"); + } + + return true; +} + +bool ots_vhea_should_serialise(Font *font) { + // vhea should'nt serialise when vmtx doesn't exist. + return font->vhea != NULL && font->vmtx != NULL; +} + +bool ots_vhea_serialise(OTSStream *out, Font *font) { + if (!SerialiseMetricsHeader(font, out, &font->vhea->header)) { + return OTS_FAILURE_MSG("Failed to write vhea metrics"); + } + return true; +} + +void ots_vhea_reuse(Font *font, Font *other) { + font->vhea = other->vhea; + font->vhea_reused = true; +} + +void ots_vhea_free(Font *font) { + delete font->vhea; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/vhea.h b/gfx/ots/src/vhea.h new file mode 100644 index 000000000..f8efde731 --- /dev/null +++ b/gfx/ots/src/vhea.h @@ -0,0 +1,20 @@ +// Copyright (c) 2011 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_VHEA_H_ +#define OTS_VHEA_H_ + +#include "metrics.h" +#include "ots.h" + +namespace ots { + +struct OpenTypeVHEA { + OpenTypeMetricsHeader header; +}; + +} // namespace ots + +#endif // OTS_VHEA_H_ + diff --git a/gfx/ots/src/vmtx.cc b/gfx/ots/src/vmtx.cc new file mode 100644 index 000000000..64a706148 --- /dev/null +++ b/gfx/ots/src/vmtx.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2011 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 "vmtx.h" + +#include "maxp.h" +#include "vhea.h" + +// vmtx - Vertical Metrics Table +// http://www.microsoft.com/typography/otspec/vmtx.htm + +#define TABLE_NAME "vmtx" + +namespace ots { + +bool ots_vmtx_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + OpenTypeVMTX *vmtx = new OpenTypeVMTX; + font->vmtx = vmtx; + + if (!font->vhea || !font->maxp) { + return OTS_FAILURE_MSG("vhea or maxp table missing as needed by vmtx"); + } + + if (!ParseMetricsTable(font, &table, font->maxp->num_glyphs, + &font->vhea->header, &vmtx->metrics)) { + return OTS_FAILURE_MSG("Failed to parse vmtx metrics"); + } + + return true; +} + +bool ots_vmtx_should_serialise(Font *font) { + // vmtx should serialise when vhea is preserved. + return font->vmtx != NULL && font->vhea != NULL; +} + +bool ots_vmtx_serialise(OTSStream *out, Font *font) { + if (!SerialiseMetricsTable(font, out, &font->vmtx->metrics)) { + return OTS_FAILURE_MSG("Failed to write vmtx metrics"); + } + return true; +} + +void ots_vmtx_reuse(Font *font, Font *other) { + font->vmtx = other->vmtx; + font->vmtx_reused = true; +} + +void ots_vmtx_free(Font *font) { + delete font->vmtx; +} + +} // namespace ots + +#undef TABLE_NAME diff --git a/gfx/ots/src/vmtx.h b/gfx/ots/src/vmtx.h new file mode 100644 index 000000000..061dc73ed --- /dev/null +++ b/gfx/ots/src/vmtx.h @@ -0,0 +1,20 @@ +// Copyright (c) 2011 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_VMTX_H_ +#define OTS_VMTX_H_ + +#include "metrics.h" +#include "ots.h" + +namespace ots { + +struct OpenTypeVMTX { + OpenTypeMetricsTable metrics; +}; + +} // namespace ots + +#endif // OTS_VMTX_H_ + diff --git a/gfx/ots/src/vorg.cc b/gfx/ots/src/vorg.cc new file mode 100644 index 000000000..358923125 --- /dev/null +++ b/gfx/ots/src/vorg.cc @@ -0,0 +1,111 @@ +// 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/typography/otspec/vorg.htm + +#define TABLE_NAME "VORG" + +#define DROP_THIS_TABLE(...) \ + do { \ + OTS_FAILURE_MSG_(font->file, TABLE_NAME ": " __VA_ARGS__); \ + OTS_FAILURE_MSG("Table discarded"); \ + delete font->vorg; \ + font->vorg = 0; \ + } while (0) + +namespace ots { + +bool ots_vorg_parse(Font *font, const uint8_t *data, size_t length) { + Buffer table(data, length); + font->vorg = new OpenTypeVORG; + OpenTypeVORG * const vorg = font->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_MSG("Failed to read header"); + } + if (vorg->major_version != 1) { + DROP_THIS_TABLE("bad major version: %u", vorg->major_version); + return true; + } + if (vorg->minor_version != 0) { + DROP_THIS_TABLE("bad minor version: %u", vorg->minor_version); + 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_MSG("Failed to read record %d", i); + } + if ((i != 0) && (rec.glyph_index <= last_glyph_index)) { + DROP_THIS_TABLE("the table is not sorted"); + return true; + } + last_glyph_index = rec.glyph_index; + + vorg->metrics.push_back(rec); + } + + return true; +} + +bool ots_vorg_should_serialise(Font *font) { + if (!font->cff) return false; // this table is not for fonts with TT glyphs. + return font->vorg != NULL; +} + +bool ots_vorg_serialise(OTSStream *out, Font *font) { + OpenTypeVORG * const vorg = font->vorg; + + const uint16_t num_metrics = static_cast<uint16_t>(vorg->metrics.size()); + if (num_metrics != vorg->metrics.size() || + !out->WriteU16(vorg->major_version) || + !out->WriteU16(vorg->minor_version) || + !out->WriteS16(vorg->default_vert_origin_y) || + !out->WriteU16(num_metrics)) { + return OTS_FAILURE_MSG("Failed to write table header"); + } + + for (uint16_t i = 0; i < num_metrics; ++i) { + const OpenTypeVORGMetrics& rec = vorg->metrics[i]; + if (!out->WriteU16(rec.glyph_index) || + !out->WriteS16(rec.vert_origin_y)) { + return OTS_FAILURE_MSG("Failed to write record %d", i); + } + } + + return true; +} + +void ots_vorg_reuse(Font *font, Font *other) { + font->vorg = other->vorg; + font->vorg_reused = true; +} + +void ots_vorg_free(Font *font) { + delete font->vorg; +} + +} // namespace ots + +#undef TABLE_NAME +#undef DROP_THIS_TABLE diff --git a/gfx/ots/src/vorg.h b/gfx/ots/src/vorg.h new file mode 100644 index 000000000..c3d3ffdae --- /dev/null +++ b/gfx/ots/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_ |